Add project files.
This commit is contained in:
+421
@@ -0,0 +1,421 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Hashing;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace LibIRD
|
||||
{
|
||||
/// <summary>
|
||||
/// IRD Generation, Reading and Writing
|
||||
/// </summary>
|
||||
public class IRD
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// "3IRD"
|
||||
/// </summary>
|
||||
///
|
||||
protected static readonly byte[] Magic = new byte[] { 0x33, 0x49, 0x52, 0x44 };
|
||||
|
||||
#endregion
|
||||
|
||||
#region Publicly Settable Properties
|
||||
|
||||
/// <summary>
|
||||
/// IRD Specification Version
|
||||
/// </summary>
|
||||
/// <remarks>1 byte, Versions 6-9 are currently supported</remarks>
|
||||
public byte Version
|
||||
{
|
||||
get => _version;
|
||||
set
|
||||
{
|
||||
if (value == 6 || value == 7 || value == 8 || value == 9)
|
||||
_version = value;
|
||||
}
|
||||
}
|
||||
public byte _version = 9; // Default to latest IRD version = 9
|
||||
|
||||
/// <summary>
|
||||
/// Unique Identifier
|
||||
/// </summary>
|
||||
/// <remarks>Redump-style IRDs use CRC32 hash of the ISO as the UID</remarks>
|
||||
public uint UID { get; set; } = 0x00000000; // Default to zeroed UID
|
||||
|
||||
/// <summary>
|
||||
/// Extra Config, usually 0x0000
|
||||
/// </summary>
|
||||
public ushort ExtraConfig { get; set; } = 0x0000;
|
||||
|
||||
/// <summary>
|
||||
/// Attachments, usually 0x0000
|
||||
/// </summary>
|
||||
public ushort Attachments { get; set; } = 0x0000;
|
||||
|
||||
/// <summary>
|
||||
/// D1 key
|
||||
/// </summary>
|
||||
/// <remarks>16 bytes</remarks>
|
||||
public byte[] Data1Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// D2 key
|
||||
/// </summary>
|
||||
/// <remarks>16 bytes</remarks>
|
||||
public byte[] Data2Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed PIC data
|
||||
/// </summary>
|
||||
/// <remarks>115 bytes, before D1/D2 keys on version 9</remarks>
|
||||
public byte[] PIC { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Privately Settable Properties
|
||||
|
||||
/// <summary>
|
||||
/// The same value stored in PARAM.SFO / TITLE_ID
|
||||
/// </summary>
|
||||
/// <remarks>9 bytes, ASCII, stored without dashes</remarks>
|
||||
public string TitleID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The same value stored in PARAM.SFO / TITLE
|
||||
/// </summary>
|
||||
/// <remarks>ASCII</remarks>
|
||||
public string Title { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The same value stored in PARAM.SFO / PS3_SYSTEM_VER
|
||||
/// </summary>
|
||||
/// <remarks>4 bytes, ASCII, (e.g. "1.20", missing uses "0000"</remarks>
|
||||
public string SystemVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The same value stored in PARAM.SFO / VERSION
|
||||
/// </summary>
|
||||
/// <remarks>5 bytes, ASCII, e.g. "01.20"</remarks>
|
||||
public string GameVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The same value stored in PARAM.SFO / APP_VER
|
||||
/// </summary>
|
||||
/// <remarks>5 bytes, ASCII, e.g. "01.00"</remarks>
|
||||
public string AppVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of the gzip-compressed header data
|
||||
/// </summary>
|
||||
public uint HeaderLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gzip-compressed header data
|
||||
/// </summary>
|
||||
public byte[] Header { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of the gzip-compressed footer data
|
||||
/// </summary>
|
||||
public uint FooterLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gzip-compressed footer data
|
||||
/// </summary>
|
||||
public byte[] Footer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of complete regions in the image
|
||||
/// </summary>
|
||||
public byte RegionCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// MD5 hashes for all complete regions in the image
|
||||
/// </summary>
|
||||
/// <remarks><see cref="RegionCount"/> regions, 16-bytes per hash</remarks>
|
||||
public byte[][] RegionHashes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of decrypted files in the image
|
||||
/// </summary>
|
||||
public uint FileCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting sector for each decrypted file
|
||||
/// </summary>
|
||||
/// <remarks><see cref="FileCount"/> files, alternating with each <see cref="FileHashes"/> entry</remarks>
|
||||
public ulong[] FileKeys { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// MD5 hashes for all decrypted files in the image
|
||||
/// </summary>
|
||||
/// <remarks><see cref="FileHashes"/> files, 16-bytes per hash, alternating with each <see cref="FileHashes"/> entry</remarks>
|
||||
public byte[][] FileHashes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// IRD content CRC
|
||||
/// </summary>
|
||||
public uint CRC { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generate Fields
|
||||
|
||||
/// <summary>
|
||||
/// Generates Data 1, via AES-128 CBC decryption of a Disc Key
|
||||
/// </summary>
|
||||
/// <param name="key">Byte array containing AES encrypted Disc Key</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
protected void GenerateD1(byte[] key)
|
||||
{
|
||||
// Validate key
|
||||
if (key == null || key.Length != 16)
|
||||
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(key));
|
||||
|
||||
// TODO: AES decryption
|
||||
Data2Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates Data 2, via AES-128 CBC encryption of a Disc ID
|
||||
/// </summary>
|
||||
/// <param name="id">Byte array containing AES decrypted Disc ID</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
protected void GenerateD2(byte[] id)
|
||||
{
|
||||
// Validate id
|
||||
if (id == null || id.Length != 16)
|
||||
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(id));
|
||||
|
||||
// TODO: AES encryption
|
||||
Data2Key = id;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
// TODO: Constructor using D1 and D2 directly, rather than Disc Key / Disc ID
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for internal derived classes only: resulting object not in usable state
|
||||
/// </summary>
|
||||
private protected IRD()
|
||||
{
|
||||
// Assumes that derived class will set private variables and generate in its own constructor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that reads an existing IRD file and stores its fields in the IRD object
|
||||
/// </summary>
|
||||
/// <param name="irdPath">Full path to IRD file to read data from</param>
|
||||
public IRD(string irdPath)
|
||||
{
|
||||
Read(irdPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public Constructor must be called with required fields
|
||||
/// </summary>
|
||||
/// <param name="discPath">Full path to the disc drive / mounted ISO</param>
|
||||
/// <param name="discKey">Disc Key, byte array of length 16</param>
|
||||
/// <param name="discID">Disc ID, byte array of length 16</param>
|
||||
/// <param name="discPIC">Disc PIC, bye array of length 115</param>
|
||||
public IRD(string discPath, byte[] discKey, byte[] discID, byte[] discPIC)
|
||||
{
|
||||
// Store IRD fields that cannot be determined from the disc drive or mounted ISO
|
||||
GenerateD1(discKey);
|
||||
GenerateD2(discID);
|
||||
PIC = discPIC;
|
||||
|
||||
// Generate the remaining IRD fields from the disc drive or mounted ISO
|
||||
Generate(discPath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Generate IRD fields from a disc drive or mounted ISO
|
||||
/// </summary>
|
||||
/// <param name="discPath">Full path to the disc drive / mounted ISO</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public void Generate(string discPath)
|
||||
{
|
||||
// TODO: Check that discPath is a valid disc drive/ISO
|
||||
if (discPath == null)
|
||||
throw new ArgumentNullException(nameof(discPath));
|
||||
|
||||
// TODO: Code the difficult part (remove these initial values)
|
||||
TitleID = "ABCD12345";
|
||||
Title = "Title";
|
||||
SystemVersion = "01.00";
|
||||
GameVersion = "01.00";
|
||||
AppVersion = "01.00";
|
||||
HeaderLength = 1;
|
||||
Header = new byte[HeaderLength];
|
||||
Header[0] = 0x00;
|
||||
FooterLength = 1;
|
||||
Footer = new byte[FooterLength];
|
||||
Footer[0] = 0x00;
|
||||
RegionCount = 1;
|
||||
RegionHashes = new byte[RegionCount][];
|
||||
RegionHashes[0] = new byte[] { 0x00 };
|
||||
FileCount = 1;
|
||||
FileKeys = new ulong[FileCount];
|
||||
FileKeys[0] = 0x00;
|
||||
FileHashes = new byte[FileCount][];
|
||||
FileHashes[0] = new byte[] { 0x00 };
|
||||
FileHashes[0][0] = 0x00;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read IRD data from file, store fields into class
|
||||
/// </summary>
|
||||
/// <param name="irdPath">Full path to IRD file to read data from</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public void Read(string irdPath)
|
||||
{
|
||||
// TODO: Check irdPath is a valid IRD file path
|
||||
if (irdPath == null || !File.Exists(irdPath))
|
||||
throw new ArgumentException("IRD File Path invalid", nameof(irdPath));
|
||||
|
||||
// TODO: Implement reading IRD file data into fields (remove these intial values)
|
||||
TitleID = "ABCD12345";
|
||||
Title = "Title";
|
||||
SystemVersion = "01.00";
|
||||
GameVersion = "01.00";
|
||||
AppVersion = "01.00";
|
||||
HeaderLength = 1;
|
||||
Header = new byte[HeaderLength];
|
||||
Header[0] = 0x00;
|
||||
FooterLength = 1;
|
||||
Footer = new byte[FooterLength];
|
||||
Footer[0] = 0x00;
|
||||
RegionCount = 1;
|
||||
RegionHashes = new byte[RegionCount][];
|
||||
RegionHashes[0] = new byte[] { 0x00 };
|
||||
FileCount = 1;
|
||||
FileKeys = new ulong[FileCount];
|
||||
FileKeys[0] = 0x00;
|
||||
FileHashes = new byte[FileCount][];
|
||||
FileHashes[0] = new byte[] { 0x00 };
|
||||
FileHashes[0][0] = 0x00;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write IRD data to file
|
||||
/// </summary>
|
||||
/// <param name="filePath">Full path to IRD file to be written to</param>
|
||||
public void Write(string filePath)
|
||||
{
|
||||
// TODO: Check filePath is a valid path for a new IRD file
|
||||
// Create the IRD file stream
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
// Create a GZipped IRD file stream
|
||||
using (GZipStream outStream = new GZipStream(fs, CompressionLevel.Optimal))
|
||||
{
|
||||
// Keep track of the little-endian 32-bit "IEEE 802.3" CRC value
|
||||
Crc32 crcStream = new Crc32();
|
||||
// TODO: Run as an async Task?
|
||||
crcStream.Append(outStream);
|
||||
|
||||
// Write IRD data to stream in order
|
||||
using (BinaryWriter bw = new BinaryWriter(outStream))
|
||||
{
|
||||
// IRD File Signature
|
||||
bw.Write(Magic);
|
||||
|
||||
// IRD File Version
|
||||
bw.Write(_version);
|
||||
|
||||
// PARAM.SFO / TITLE_ID
|
||||
byte[] titleIDBuf = Encoding.ASCII.GetBytes(TitleID);
|
||||
bw.Write(titleIDBuf, 0, 9);
|
||||
|
||||
// PARAM.SFO / TITLE
|
||||
bw.Write(Title);
|
||||
|
||||
// PARAM.SFO / PS3_SYSTEM_VER
|
||||
byte[] systemVersionBuf = Encoding.ASCII.GetBytes(SystemVersion);
|
||||
bw.Write(systemVersionBuf, 0, 4);
|
||||
|
||||
// PARAM.SFO / VERSION
|
||||
byte[] buf = Encoding.ASCII.GetBytes(GameVersion);
|
||||
byte[] gameVersionBuf = new byte[5];
|
||||
Array.Copy(buf, 0, gameVersionBuf, 0, buf.Length);
|
||||
bw.Write(gameVersionBuf, 0, 5);
|
||||
|
||||
// PARAM.SFO / APP_VER
|
||||
buf = Encoding.ASCII.GetBytes(AppVersion);
|
||||
byte[] appVersionBuf = new byte[5];
|
||||
Array.Copy(buf, 0, appVersionBuf, 0, buf.Length);
|
||||
bw.Write(appVersionBuf, 0, 5);
|
||||
|
||||
// IRD Unique Identifier, for version 7
|
||||
if (_version == 7)
|
||||
bw.Write(UID);
|
||||
|
||||
// Compress Header
|
||||
bw.Write(HeaderLength);
|
||||
bw.Write(Header, 0, (int)HeaderLength);
|
||||
|
||||
// Compress Footer
|
||||
bw.Write(FooterLength);
|
||||
bw.Write(Footer, 0, (int)FooterLength);
|
||||
|
||||
// Number of regions hashed
|
||||
bw.Write(RegionCount);
|
||||
|
||||
// Hashes for each region
|
||||
for (int i = 0; i < RegionCount; i++)
|
||||
bw.Write(RegionHashes[i], 0, 16);
|
||||
|
||||
// Number of files hashed
|
||||
bw.Write(FileCount);
|
||||
|
||||
// Hashes for each file
|
||||
for (int i = 0; i < FileCount; i++)
|
||||
{
|
||||
bw.Write(FileKeys[i]);
|
||||
bw.Write(FileHashes[i], 0, 16);
|
||||
}
|
||||
|
||||
// Reserved fields
|
||||
bw.Write(ExtraConfig);
|
||||
bw.Write(Attachments);
|
||||
|
||||
// PIC data is placed here for Version 9
|
||||
if (_version >= 9)
|
||||
bw.Write(PIC, 0, 115);
|
||||
|
||||
// Disc Authentication keys
|
||||
bw.Write(Data1Key, 0, 16);
|
||||
bw.Write(Data2Key, 0, 16);
|
||||
|
||||
// PIC data is placed here prior to Version 9
|
||||
if (_version < 9)
|
||||
bw.Write(PIC, 0, 115);
|
||||
|
||||
// IRD Unique Identifier, for versions after 7
|
||||
if (_version > 7)
|
||||
bw.Write(UID);
|
||||
}
|
||||
|
||||
// Calculate final CRC32
|
||||
CRC = BitConverter.ToUInt32(crcStream.GetCurrentHash(), 0);
|
||||
|
||||
// Write final CRC32 to stream
|
||||
outStream.Write(BitConverter.GetBytes(CRC), 0, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net6.0;net7.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Version>0.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IO.Hashing" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
using System;
|
||||
|
||||
namespace LibIRD
|
||||
{
|
||||
#region Enumerations
|
||||
|
||||
/// <summary>
|
||||
/// Type used to define all known PS3 disc regions
|
||||
/// </summary>
|
||||
public enum Region
|
||||
{
|
||||
// Null region
|
||||
NONE = 0,
|
||||
|
||||
// Japan and Asia
|
||||
Asia = 0x01, // BLAS/BCAS serials
|
||||
Japan = Asia, // BLJM/BCJS serials
|
||||
Korea = Asia, // BLKS/BCKS serials
|
||||
|
||||
// USA and Canada (NA)
|
||||
NorthAmerica = 0x03,
|
||||
USA = NorthAmerica,
|
||||
Canada = NorthAmerica,
|
||||
|
||||
// Europe, Middle East, and Africa (EMEA)
|
||||
Europe = 0x04,
|
||||
MiddleEast = Europe,
|
||||
Africa = Europe,
|
||||
|
||||
// Australia and New Zealand (Oceania)
|
||||
Australia = 0x06, // Some releases use Region.Europe instead
|
||||
NewZealand = Australia, // Some releases use Region.Europe instead
|
||||
|
||||
// Brazil and Latin America (LATAM)
|
||||
LatinAmerica = 0x09, // e.g. Portuguese + Spanish release
|
||||
Brazil = LatinAmerica,
|
||||
// Mexico is often Region.NorthAmerica
|
||||
|
||||
// Russia and Eastern Europe
|
||||
EasternEurope = 0x0A, // e.g. Russian + Polish language release
|
||||
Russia = EasternEurope,
|
||||
// Poland is Region.Europe
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Create Reproducible Redump-style IRD file from an ISO, CRC32 and Disc Key
|
||||
/// </summary>
|
||||
public class ReIRD : IRD
|
||||
{
|
||||
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// Size of a blu-ray layer in bytes (BD-25 max size)
|
||||
/// </summary>
|
||||
/// <remarks>Can be used as the default PS3 layerbreak value</remarks>
|
||||
private const long BDLayerSize = 25025314816; // 12219392 sectors
|
||||
|
||||
#endregion
|
||||
|
||||
#region Property Generation
|
||||
|
||||
/// <summary>
|
||||
/// Generates a Disc ID given a size and region, where region is a single byte
|
||||
/// </summary>
|
||||
/// <returns>Valid Disc ID, byte array length 16</returns>
|
||||
private static byte[] GenerateID(long size, Region region = Region.NONE)
|
||||
{
|
||||
if (size > BDLayerSize) // if BD-50, Disc ID is fixed
|
||||
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
else // else if BD-25, Disc ID has a byte referring to disc region
|
||||
{
|
||||
return new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
||||
0x00, 0x02, 0x00, (byte)region, 0x00, 0x00, 0x00, 0x01 };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the PIC data for a given ISO size in bytes
|
||||
/// </summary>
|
||||
/// <param name="size">Total ISO size in number of bytes</param>
|
||||
/// <param name="layerbreak">Layer break value, byte at which disc layers are split across</param>
|
||||
/// <param name="exactIRD">True to generate a PIC in 3k3y style (0x03 at 115th byte for BD-50 discs)</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private void GeneratePIC(long size, long layerbreak = BDLayerSize, bool exactIRD = false)
|
||||
{
|
||||
// Validate size
|
||||
if (size == 0 || (size % 2048) != 0)
|
||||
throw new ArgumentException("ISO Size in bytes must be a positive integer multiple of 2048", nameof(size));
|
||||
// Validate layerbreak
|
||||
if (layerbreak == 0 || layerbreak >= size)
|
||||
throw new ArgumentException("Layerbreak in bytes must be a positive integer less than the ISO Size", nameof(size));
|
||||
|
||||
// TODO: Generate correct PICs for Hybrid PS3 discs (BD-50 with layerbreak value other than 12219392)
|
||||
if (size > BDLayerSize) // if BD-50
|
||||
{
|
||||
// num_sectors + layer_sector_end (0x00100000) + sectors_between_layers (0x01358C00 - 0x00CA73FE) - 3
|
||||
byte[] total_sectors = BitConverter.GetBytes((uint)(size / 2048 + 8067071));
|
||||
|
||||
// Initial portion of PIC (24 bytes)
|
||||
PIC = new byte[]{
|
||||
// [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num]
|
||||
0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00,
|
||||
// ["BDR"] [2 layers]
|
||||
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Total sectors used on disc (4 bytes)
|
||||
total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0],
|
||||
// 1st Layer sector start location (4 bytes)
|
||||
0x00, 0x10, 0x00, 0x00,
|
||||
// 1st Layer sector end location (4 bytes)
|
||||
0x00, 0xCA, 0x73, 0xFE,
|
||||
// 32 bytes of zeros
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Initial portion of PIC again, for 2nd layer
|
||||
// ["DI"] [v1] [11unit][DI num]
|
||||
0x44, 0x49, 0x01, 0x11, 0x00, 0x01, 0x20, 0x00,
|
||||
// ["BDR"] [2 layers]
|
||||
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Total sectors used on disc
|
||||
total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0],
|
||||
// 2nd Layer sector start location
|
||||
0x01, 0x35, 0x8C, 0x00,
|
||||
// 2nd Layer sector end location
|
||||
0x01, 0xEF, 0xFF, 0xFE,
|
||||
// Remaining 32 bytes are zeroes
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
// 3k3y style: 0x03 at last byte
|
||||
if (exactIRD)
|
||||
PIC[114] = 0x03;
|
||||
|
||||
}
|
||||
else // if BD-25
|
||||
{
|
||||
// Total sectors used on disc: num_sectors + layer_sector_end (0x00100000) - 1
|
||||
byte[] total_sectors = BitConverter.GetBytes((uint)(size / 2048 + 1048575));
|
||||
// Layer sector end location: num_sectors + layer_sector_end (0x00100000) - 2
|
||||
byte[] end_sector = BitConverter.GetBytes((uint)(size / 2048 + 1048574));
|
||||
|
||||
// Initial portion of PIC (24 bytes)
|
||||
PIC = new byte[]{ 0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x08, 0x00, 0x00, 0x20, 0x00,
|
||||
0x42, 0x44, 0x4F, 0x01, 0x11, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Total sectors used on disc (4 bytes)
|
||||
total_sectors[3], total_sectors[2], total_sectors[1], total_sectors[0],
|
||||
// Layer sector start location (4 bytes)
|
||||
0x00, 0x10, 0x00, 0x00,
|
||||
// Layer sector end location (4 bytes)
|
||||
end_sector[3], end_sector[2], end_sector[1], end_sector[0],
|
||||
// Remaining 79 bytes are zeroes
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the UID field
|
||||
/// </summary>
|
||||
/// <param name="crc32">Redump ISO CRC32 hash</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private void GenerateUID(byte[] crc32)
|
||||
{
|
||||
// Validate CRC32
|
||||
if (crc32 == null || crc32.Length != 8)
|
||||
throw new ArgumentException("CRC32 hash must be an byte array of length 8", nameof(crc32));
|
||||
// Redump ISO CRC32 hash is used as the Unique ID in the IRD
|
||||
UID = BitConverter.ToUInt32(crc32, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with required redump-style IRD fields
|
||||
/// </summary>
|
||||
/// <param name="size">ISO Size in bytes, a multiple of 2048</param>
|
||||
/// <param name="crc32">CRC32 hash of the redump ISO</param>
|
||||
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
|
||||
public ReIRD(long size, byte[] crc32, byte[] key)
|
||||
{
|
||||
// Generate Unique Identifier using ISO CRC32
|
||||
GenerateUID(crc32);
|
||||
|
||||
// Generate Data 1 using Disc Key
|
||||
GenerateD1(key);
|
||||
|
||||
// Generate Data 2 using Disc ID
|
||||
GenerateD2(GenerateID(size));
|
||||
|
||||
// Generate Disc PIC
|
||||
GeneratePIC(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with additional region to generate a specific Disc ID
|
||||
/// </summary>
|
||||
/// <param name="size">ISO Size in bytes, a multiple of 2048</param>
|
||||
/// <param name="crc32">CRC32 hash of the redump ISO</param>
|
||||
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
|
||||
/// <param name="region">Disc Region</param>
|
||||
public ReIRD(long size, byte[] crc32, byte[] key, Region region)
|
||||
{
|
||||
// Generate Unique Identifier using ISO CRC32
|
||||
GenerateUID(crc32);
|
||||
|
||||
// Generate Data 1 using Disc Key
|
||||
GenerateD1(key);
|
||||
|
||||
// Generate Data 2 using Disc ID
|
||||
GenerateD2(GenerateID(size, region));
|
||||
|
||||
// Generate Disc PIC
|
||||
GeneratePIC(size);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Generate and write the redump-style IRD file, after any optional IRD fields have been set
|
||||
/// </summary>
|
||||
/// <param name="discPath">Full path to the disc drive / mounted ISO</param>
|
||||
/// <param name="irdPath">Full path to IRD file to be written to</param>
|
||||
public void Create(string discPath, string irdPath)
|
||||
{
|
||||
// Generate the IRD hashes
|
||||
Generate(discPath);
|
||||
|
||||
// Write to IRD file
|
||||
Write(irdPath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user