Add an emulated gamecard and storage creator

This commit is contained in:
Alex Barney 2019-10-07 18:39:06 -05:00
parent 9934f477d5
commit 51a13068df
14 changed files with 498 additions and 55 deletions

79
src/LibHac/Fs/GameCard.cs Normal file
View file

@ -0,0 +1,79 @@
using System;
using LibHac.FsService;
namespace LibHac.Fs
{
public static class GameCard
{
public static Result OpenGameCardPartition(this FileSystemClient fs, out IStorage storage,
GameCardHandle handle, GameCardPartitionRaw partitionType)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.OpenGameCardStorage(out storage, handle, partitionType);
}
public static Result GetGameCardHandle(this FileSystemClient fs, out GameCardHandle handle)
{
handle = default;
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
if (rc.IsFailure()) return rc;
return deviceOperator.GetGameCardHandle(out handle);
}
public static bool IsGameCardInserted(this FileSystemClient fs)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator);
if (rc.IsFailure()) throw new LibHacException("Abort");
rc = deviceOperator.IsGameCardInserted(out bool isInserted);
if (rc.IsFailure()) throw new LibHacException("Abort");
return isInserted;
}
public static long GetGameCardSizeBytes(GameCardSize size)
{
switch (size)
{
case GameCardSize.Size1Gb: return 0x3B800000;
case GameCardSize.Size2Gb: return 0x77000000;
case GameCardSize.Size4Gb: return 0xEE000000;
case GameCardSize.Size8Gb: return 0x1DC000000;
case GameCardSize.Size16Gb: return 0x3B8000000;
case GameCardSize.Size32Gb: return 0x770000000;
default:
throw new ArgumentOutOfRangeException(nameof(size), size, null);
}
}
public static long CardPageToOffset(int page)
{
return (long)page << 9;
}
}
public enum GameCardSize
{
Size1Gb = 0xFA,
Size2Gb = 0xF8,
Size4Gb = 0xF0,
Size8Gb = 0xE0,
Size16Gb = 0xE1,
Size32Gb = 0xE2
}
[Flags]
public enum GameCardAttribute : byte
{
AutoBoot = 1 << 0,
HistoryErase = 1 << 1,
RepairTool = 1 << 2
}
}

View file

@ -15,6 +15,15 @@
public static Result TargetNotFound => new Result(ModuleFs, 1002);
public static Result ExternalKeyNotFound => new Result(ModuleFs, 1004);
public static Result InvalidBufferForGameCard => new Result(ModuleFs, 2503);
public static Result GameCardNotInserted => new Result(ModuleFs, 2520);
public static Result GameCardNotInsertedOnGetHandle => new Result(ModuleFs, 2951);
public static Result InvalidGameCardHandleOnRead => new Result(ModuleFs, 2952);
public static Result InvalidGameCardHandleOnGetCardInfo => new Result(ModuleFs, 2954);
public static Result InvalidGameCardHandleOnOpenNormalPartition => new Result(ModuleFs, 2960);
public static Result InvalidGameCardHandleOnOpenSecurePartition => new Result(ModuleFs, 2961);
public static Result NotImplemented => new Result(ModuleFs, 3001);
public static Result Result3002 => new Result(ModuleFs, 3002);
public static Result SaveDataPathAlreadyExists => new Result(ModuleFs, 3003);
@ -98,6 +107,8 @@
public static Result UnsupportedOperationInAesCtrExStorageWrite => new Result(ModuleFs, 6310);
public static Result UnsupportedOperationInIndirectStorageWrite => new Result(ModuleFs, 6324);
public static Result UnsupportedOperationInIndirectStorageSetSize => new Result(ModuleFs, 6325);
public static Result UnsupportedOperationInRoGameCardStorageWrite => new Result(ModuleFs, 6350);
public static Result UnsupportedOperationInRoGameCardStorageSetSize => new Result(ModuleFs, 6351);
public static Result UnsupportedOperationInConcatFsQueryEntry => new Result(ModuleFs, 6359);
public static Result UnsupportedOperationModifyRomFsFileSystem => new Result(ModuleFs, 6364);
public static Result UnsupportedOperationRomFsFileSystemGetSpace => new Result(ModuleFs, 6366);

View file

@ -0,0 +1,125 @@
using System;
using LibHac.Fs;
namespace LibHac.FsService.Creators
{
class EmulatedGameCardStorageCreator : IGameCardStorageCreator
{
private EmulatedGameCard GameCard { get; }
public EmulatedGameCardStorageCreator(EmulatedGameCard gameCard)
{
GameCard = gameCard;
}
public Result CreateNormal(GameCardHandle handle, out IStorage storage)
{
storage = default;
if (GameCard.IsGameCardHandleInvalid(handle))
{
return ResultFs.InvalidGameCardHandleOnOpenNormalPartition.Log();
}
var baseStorage = new ReadOnlyGameCardStorage(GameCard, handle);
Result rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle);
if (rc.IsFailure()) return rc;
storage = new SubStorage2(baseStorage, 0, cardInfo.SecureAreaOffset);
return Result.Success;
}
public Result CreateSecure(GameCardHandle handle, out IStorage storage)
{
storage = default;
if (GameCard.IsGameCardHandleInvalid(handle))
{
return ResultFs.InvalidGameCardHandleOnOpenSecurePartition.Log();
}
Span<byte> deviceId = stackalloc byte[0x10];
Span<byte> imageHash = stackalloc byte[0x20];
Result rc = GameCard.GetGameCardDeviceId(deviceId);
if (rc.IsFailure()) return rc;
rc = GameCard.GetGameCardImageHash(imageHash);
if (rc.IsFailure()) return rc;
var baseStorage = new ReadOnlyGameCardStorage(GameCard, handle, deviceId, imageHash);
rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle);
if (rc.IsFailure()) return rc;
storage = new SubStorage2(baseStorage, cardInfo.SecureAreaOffset, cardInfo.SecureAreaSize);
return Result.Success;
}
public Result CreateWritable(GameCardHandle handle, out IStorage storage)
{
throw new NotImplementedException();
}
private class ReadOnlyGameCardStorage : StorageBase
{
private EmulatedGameCard GameCard { get; }
private GameCardHandle Handle { get; set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Local
private bool IsSecureMode { get; }
private byte[] DeviceId { get; } = new byte[0x10];
private byte[] ImageHash { get; } = new byte[0x20];
public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle)
{
GameCard = gameCard;
Handle = handle;
}
public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle, ReadOnlySpan<byte> deviceId, ReadOnlySpan<byte> imageHash)
{
GameCard = gameCard;
Handle = handle;
IsSecureMode = true;
deviceId.CopyTo(DeviceId);
imageHash.CopyTo(ImageHash);
}
public override Result Read(long offset, Span<byte> destination)
{
// In secure mode, if Handle is old and the card's device ID and
// header hash are still the same, Handle is updated to the new handle
return GameCard.Read(Handle, offset, destination);
}
public override Result Write(long offset, ReadOnlySpan<byte> source)
{
return ResultFs.UnsupportedOperationInRoGameCardStorageWrite.Log();
}
public override Result Flush()
{
return Result.Success;
}
public override Result SetSize(long size)
{
return ResultFs.UnsupportedOperationInRoGameCardStorageSetSize.Log();
}
public override Result GetSize(out long size)
{
size = 0;
Result rc = GameCard.GetCardInfo(out GameCardInfo info, Handle);
if (rc.IsFailure()) return rc;
size = info.Size;
return Result.Success;
}
}
}
}

View file

@ -21,17 +21,24 @@ namespace LibHac.FsService.Creators
public IBuiltInStorageFileSystemCreator BuiltInStorageFileSystemCreator { get; set; }
public ISdFileSystemCreator SdFileSystemCreator { get; set; }
public static FileSystemCreators GetDefaultEmulatedCreators(IFileSystem rootFileSystem, Keyset keyset)
public IDeviceOperator DeviceOperator { get; set; }
public static (FileSystemCreators fsCreators, EmulatedGameCard gameCard) GetDefaultEmulatedCreators(
IFileSystem rootFileSystem, Keyset keyset)
{
var creators = new FileSystemCreators();
var gameCard = new EmulatedGameCard();
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(keyset);
creators.GameCardStorageCreator = new EmulatedGameCardStorageCreator(gameCard);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keyset);
creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(rootFileSystem);
creators.SdFileSystemCreator = new EmulatedSdFileSystemCreator(rootFileSystem);
return creators;
creators.DeviceOperator = new EmulatedDeviceOperator(gameCard);
return (creators, gameCard);
}
}
}

View file

@ -0,0 +1,38 @@
using System;
using LibHac.Fs;
namespace LibHac.FsService
{
class EmulatedDeviceOperator : IDeviceOperator
{
private EmulatedGameCard GameCard { get; set; }
public EmulatedDeviceOperator(EmulatedGameCard gameCard)
{
GameCard = gameCard;
}
public Result IsSdCardInserted(out bool isInserted)
{
throw new NotImplementedException();
}
public Result IsGameCardInserted(out bool isInserted)
{
isInserted = GameCard.IsGameCardInserted();
return Result.Success;
}
public Result GetGameCardHandle(out GameCardHandle handle)
{
if (!GameCard.IsGameCardInserted())
{
handle = default;
return ResultFs.GameCardNotInsertedOnGetHandle.Log();
}
handle = GameCard.GetGameCardHandle();
return Result.Success;
}
}
}

View file

@ -0,0 +1,103 @@
using System;
using LibHac.Fs;
using LibHac.FsSystem;
namespace LibHac.FsService
{
public class EmulatedGameCard
{
private IStorage CardImageStorage { get; set; }
private int Handle { get; set; }
private XciHeader CardHeader { get; set; }
public GameCardHandle GetGameCardHandle()
{
return new GameCardHandle(Handle);
}
public bool IsGameCardHandleInvalid(GameCardHandle handle)
{
return Handle != handle.Value;
}
public bool IsGameCardInserted()
{
return CardImageStorage != null;
}
public void InsertGameCard(IStorage cardImageStorage)
{
RemoveGameCard();
CardImageStorage = cardImageStorage;
CardHeader = new XciHeader(null, cardImageStorage.AsStream());
}
public void RemoveGameCard()
{
if (IsGameCardInserted())
{
CardImageStorage = null;
Handle++;
}
}
public Result Read(GameCardHandle handle, long offset, Span<byte> destination)
{
if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnRead.Log();
if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log();
return CardImageStorage.Read(offset, destination);
}
public Result GetGameCardImageHash(Span<byte> outBuffer)
{
if (outBuffer.Length < 0x20) return ResultFs.InvalidBufferForGameCard.Log();
if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log();
CardHeader.ImageHash.CopyTo(outBuffer.Slice(0, 0x20));
return Result.Success;
}
public Result GetGameCardDeviceId(Span<byte> outBuffer)
{
if (outBuffer.Length < 0x10) return ResultFs.InvalidBufferForGameCard.Log();
if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log();
// Skip the security mode check
// Instead of caching the CardKeyArea data, read the value directly
return CardImageStorage.Read(0x7110, outBuffer.Slice(0, 0x10));
}
internal Result GetCardInfo(out GameCardInfo cardInfo, GameCardHandle handle)
{
cardInfo = default;
if (IsGameCardHandleInvalid(handle)) return ResultFs.InvalidGameCardHandleOnGetCardInfo.Log();
if (!IsGameCardInserted()) return ResultFs.GameCardNotInserted.Log();
cardInfo = GetCardInfoImpl();
return Result.Success;
}
private GameCardInfo GetCardInfoImpl()
{
var info = new GameCardInfo();
CardHeader.RootPartitionHeaderHash.AsSpan().CopyTo(info.RootPartitionHeaderHash);
info.PackageId = CardHeader.PackageId;
info.Size = GameCard.GetGameCardSizeBytes(CardHeader.GameCardSize);
info.RootPartitionOffset = CardHeader.RootPartitionOffset;
info.RootPartitionHeaderSize = CardHeader.RootPartitionHeaderSize;
info.SecureAreaOffset = GameCard.CardPageToOffset(CardHeader.LimAreaPage);
info.SecureAreaSize = info.Size - info.SecureAreaOffset;
info.UpdateVersion = CardHeader.UppVersion;
info.UpdateTitleId = CardHeader.UppId;
info.Attribute = CardHeader.Flags;
return info;
}
}
}

View file

@ -302,12 +302,16 @@ namespace LibHac.FsService
public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId)
{
throw new NotImplementedException();
// Missing permission check and StorageInterfaceAdapter
return FsProxyCore.OpenGameCardStorage(out storage, handle, partitionId);
}
public Result OpenDeviceOperator(out IDeviceOperator deviceOperator)
{
throw new NotImplementedException();
// Missing permission check
return FsProxyCore.OpenDeviceOperator(out deviceOperator);
}
public Result OpenSaveDataInfoReader(out ISaveDataInfoReader infoReader)

View file

@ -11,6 +11,8 @@ namespace LibHac.FsService
{
private FileSystemCreators FsCreators { get; }
private ExternalKeySet ExternalKeys { get; }
private IDeviceOperator DeviceOperator { get; }
private byte[] SdEncryptionSeed { get; } = new byte[0x10];
private const string NintendoDirectoryName = "Nintendo";
@ -18,10 +20,11 @@ namespace LibHac.FsService
private GlobalAccessLogMode LogMode { get; set; }
public FileSystemProxyCore(FileSystemCreators fsCreators, ExternalKeySet externalKeys)
public FileSystemProxyCore(FileSystemCreators fsCreators, ExternalKeySet externalKeys, IDeviceOperator deviceOperator)
{
FsCreators = fsCreators;
ExternalKeys = externalKeys ?? new ExternalKeySet();
DeviceOperator = deviceOperator;
}
public Result OpenBisFileSystem(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId)
@ -34,6 +37,27 @@ namespace LibHac.FsService
return FsCreators.SdFileSystemCreator.Create(out fileSystem);
}
public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId)
{
switch (partitionId)
{
case GameCardPartitionRaw.Normal:
return FsCreators.GameCardStorageCreator.CreateNormal(handle, out storage);
case GameCardPartitionRaw.Secure:
return FsCreators.GameCardStorageCreator.CreateSecure(handle, out storage);
case GameCardPartitionRaw.Writable:
return FsCreators.GameCardStorageCreator.CreateWritable(handle, out storage);
default:
throw new ArgumentOutOfRangeException(nameof(partitionId), partitionId, null);
}
}
public Result OpenDeviceOperator(out IDeviceOperator deviceOperator)
{
deviceOperator = DeviceOperator;
return Result.Success;
}
public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId)
{
fileSystem = default;

View file

@ -1,4 +1,5 @@
using LibHac.Fs;
using System;
using LibHac.Fs;
using LibHac.FsService.Creators;
namespace LibHac.FsService
@ -11,21 +12,22 @@ namespace LibHac.FsService
private FileSystemClient FsClient { get; }
private ITimeSpanGenerator Timer { get; }
/// <summary>
/// Creates a new <see cref="FileSystemServer"/> with a new default <see cref="ITimeSpanGenerator"/>.
/// </summary>
/// <param name="fsCreators">The <see cref="FileSystemCreators"/> used for creating filesystems.</param>
public FileSystemServer(FileSystemCreators fsCreators) : this(fsCreators, null, new StopWatchTimeSpanGenerator()) { }
/// <summary>
/// Creates a new <see cref="FileSystemServer"/>.
/// </summary>
/// <param name="fsCreators">The <see cref="FileSystemCreators"/> used for creating filesystems.</param>
/// <param name="externalKeys">A keyset containing rights IDs and title keys. If null, an empty set will be created.</param>
/// <param name="timer">The <see cref="ITimeSpanGenerator"/> to use for access log timestamps.</param>
public FileSystemServer(FileSystemCreators fsCreators, ExternalKeySet externalKeys, ITimeSpanGenerator timer)
/// <param name="config">The configuration for the created <see cref="FileSystemServer"/>.</param>
public FileSystemServer(FileSystemServerConfig config)
{
FsProxyCore = new FileSystemProxyCore(fsCreators, externalKeys);
if(config.FsCreators == null)
throw new ArgumentException("FsCreators must not be null");
if(config.DeviceOperator == null)
throw new ArgumentException("DeviceOperator must not be null");
ExternalKeySet externalKeySet = config.ExternalKeySet ?? new ExternalKeySet();
ITimeSpanGenerator timer = config.TimeSpanGenerator ?? new StopWatchTimeSpanGenerator();
FsProxyCore = new FileSystemProxyCore(config.FsCreators, externalKeySet, config.DeviceOperator);
FsClient = new FileSystemClient(this, timer);
Timer = timer;
}
@ -53,4 +55,32 @@ namespace LibHac.FsService
return new FileSystemProxy(FsProxyCore, FsClient);
}
}
/// <summary>
/// Contains the configuration for creating a new <see cref="FileSystemServer"/>.
/// </summary>
public class FileSystemServerConfig
{
/// <summary>
/// The <see cref="FileSystemCreators"/> used for creating filesystems.
/// </summary>
public FileSystemCreators FsCreators { get; set; }
/// <summary>
/// An <see cref="IDeviceOperator"/> for managing the gamecard and SD card.
/// </summary>
public IDeviceOperator DeviceOperator { get; set; }
/// <summary>
/// A keyset containing rights IDs and title keys.
/// If null, an empty set will be created.
/// </summary>
public ExternalKeySet ExternalKeySet { get; set; }
/// <summary>
/// Used for generating access log timestamps.
/// If null, a new <see cref="StopWatchTimeSpanGenerator"/> will be created.
/// </summary>
public ITimeSpanGenerator TimeSpanGenerator { get; set; }
}
}

View file

@ -1,7 +1,20 @@
namespace LibHac.FsService
using System;
namespace LibHac.FsService
{
public struct GameCardHandle
public struct GameCardHandle : IEquatable<GameCardHandle>
{
public int Value;
internal readonly int Value;
public GameCardHandle(int value)
{
Value = value;
}
public override bool Equals(object obj) => obj is GameCardHandle handle && Equals(handle);
public bool Equals(GameCardHandle other) => Value == other.Value;
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(GameCardHandle left, GameCardHandle right) => left.Equals(right);
public static bool operator !=(GameCardHandle left, GameCardHandle right) => !(left == right);
}
}

View file

@ -0,0 +1,18 @@
using LibHac.Fs;
namespace LibHac.FsService
{
internal class GameCardInfo
{
public byte[] RootPartitionHeaderHash { get; } = new byte[0x20];
public ulong PackageId { get; set; }
public long Size { get; set; }
public long RootPartitionOffset { get; set; }
public long RootPartitionHeaderSize { get; set; }
public long SecureAreaOffset { get; set; }
public long SecureAreaSize { get; set; }
public int UpdateVersion { get; set; }
public ulong UpdateTitleId { get; set; }
public GameCardAttribute Attribute { get; set; }
}
}

View file

@ -20,14 +20,19 @@ namespace LibHac
Fs = new FileSystemClient(timer);
}
public void InitializeFileSystemServer(FileSystemCreators fsCreators)
public void InitializeFileSystemServer(FileSystemCreators fsCreators, IDeviceOperator deviceOperator)
{
if (FsSrv != null) return;
lock (_initLocker)
{
if (FsSrv != null) return;
FsSrv = new FileSystemServer(fsCreators);
var config = new FileSystemServerConfig();
config.FsCreators = fsCreators;
config.DeviceOperator = deviceOperator;
FsSrv = new FileSystemServer(config);
}
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Text;
using LibHac.Fs;
namespace LibHac
{
@ -36,9 +37,9 @@ namespace LibHac
public int BackupAreaStartPage { get; set; }
public byte KekIndex { get; set; }
public byte TitleKeyDecIndex { get; set; }
public RomSize RomSize { get; set; }
public GameCardSize GameCardSize { get; set; }
public byte CardHeaderVersion { get; set; }
public XciFlags Flags { get; set; }
public GameCardAttribute Flags { get; set; }
public ulong PackageId { get; set; }
public long ValidDataEndPage { get; set; }
public byte[] AesCbcIv { get; set; }
@ -62,8 +63,9 @@ namespace LibHac
public byte[] UppHash { get; set; }
public ulong UppId { get; set; }
public Validity SignatureValidity { get; set; }
public byte[] ImageHash { get; }
public Validity SignatureValidity { get; set; }
public Validity PartitionFsHeaderValidity { get; set; }
public XciHeader(Keyset keyset, Stream stream)
@ -88,9 +90,9 @@ namespace LibHac
byte keyIndex = reader.ReadByte();
KekIndex = (byte)(keyIndex >> 4);
TitleKeyDecIndex = (byte)(keyIndex & 7);
RomSize = (RomSize)reader.ReadByte();
GameCardSize = (GameCardSize)reader.ReadByte();
CardHeaderVersion = reader.ReadByte();
Flags = (XciFlags)reader.ReadByte();
Flags = (GameCardAttribute)reader.ReadByte();
PackageId = reader.ReadUInt64();
ValidDataEndPage = reader.ReadInt64();
AesCbcIv = reader.ReadBytes(Crypto.Aes128Size);
@ -104,7 +106,7 @@ namespace LibHac
SelKey = reader.ReadInt32();
LimAreaPage = reader.ReadInt32();
if (!keyset.XciHeaderKey.IsEmpty())
if (keyset != null && !keyset.XciHeaderKey.IsEmpty())
{
byte[] encHeader = reader.ReadBytes(EncryptedHeaderSize);
var decHeader = new byte[EncryptedHeaderSize];
@ -126,30 +128,14 @@ namespace LibHac
}
}
ImageHash = Crypto.ComputeSha256(sigData, 0, sigData.Length);
reader.BaseStream.Position = RootPartitionOffset;
PartitionFsHeaderValidity = Crypto.CheckMemoryHashTable(reader.ReadBytes((int)RootPartitionHeaderSize), RootPartitionHeaderHash, 0, (int)RootPartitionHeaderSize);
}
}
}
public enum RomSize
{
Size1Gb = 0xFA,
Size2Gb = 0xF8,
Size4Gb = 0xF0,
Size8Gb = 0xE0,
Size16Gb = 0xE1,
Size32Gb = 0xE2
}
[Flags]
public enum XciFlags
{
AutoBoot = 1 << 0,
HistoryErase = 1 << 1,
RepairTool = 1 << 2
}
public enum CardClockRate
{
ClockRate25 = 10551312,

View file

@ -149,7 +149,7 @@ namespace hactoolnet
PrintItem(sb, colLen, "Magic:", xci.Header.Magic);
PrintItem(sb, colLen, $"Header Signature{xci.Header.SignatureValidity.GetValidityString()}:", xci.Header.Signature);
PrintItem(sb, colLen, $"Header Hash{xci.Header.PartitionFsHeaderValidity.GetValidityString()}:", xci.Header.RootPartitionHeaderHash);
PrintItem(sb, colLen, "Cartridge Type:", GetCartridgeType(xci.Header.RomSize));
PrintItem(sb, colLen, "Cartridge Type:", GetCartridgeType(xci.Header.GameCardSize));
PrintItem(sb, colLen, "Cartridge Size:", $"0x{Util.MediaToReal(xci.Header.ValidDataEndPage + 1):x12}");
PrintItem(sb, colLen, "Header IV:", xci.Header.AesCbcIv);
@ -192,16 +192,16 @@ namespace hactoolnet
}
}
private static string GetCartridgeType(RomSize size)
private static string GetCartridgeType(GameCardSize size)
{
switch (size)
{
case RomSize.Size1Gb: return "1GB";
case RomSize.Size2Gb: return "2GB";
case RomSize.Size4Gb: return "4GB";
case RomSize.Size8Gb: return "8GB";
case RomSize.Size16Gb: return "16GB";
case RomSize.Size32Gb: return "32GB";
case GameCardSize.Size1Gb: return "1GB";
case GameCardSize.Size2Gb: return "2GB";
case GameCardSize.Size4Gb: return "4GB";
case GameCardSize.Size8Gb: return "8GB";
case GameCardSize.Size16Gb: return "16GB";
case GameCardSize.Size32Gb: return "32GB";
default: return string.Empty;
}
}