34 Commits
Author SHA1 Message Date
Deterous 5ce81f9663 Bump version 2024-02-18 17:28:21 +09:00
Deterous f74220efea Read ISO in larger chunks 2024-02-18 16:59:04 +09:00
Deterous 6e529f0050 Reuse ISO hash if searching redump 2024-02-16 11:11:51 +09:00
Deterous b862057123 Only print warning for non-contiguous files 2024-02-11 12:39:13 +09:00
Deterous cf19596ea7 Alphabetical order, key file path 2024-02-08 09:38:46 +09:00
Deterous 6af41a9bb3 Include dependencies in nupkg 2024-02-07 15:43:23 +09:00
Deterous 89c23622bd Remove DiscUtils as a public dependency 2024-02-07 14:41:14 +09:00
Deterous 8ab6a9d694 Fix file hash for split files with irregular filesize 2024-02-07 09:32:56 +09:00
Deterous bc3df52e75 Cleanup 2024-02-05 09:20:59 +09:00
Deterous 23f8dce272 Bump version, provide build script 2024-02-04 12:09:11 +09:00
Deterous 8e45432aab Throw error for BD-Video hybrid discs without layerbreak 2024-02-03 23:15:13 +09:00
Deterous e9bb0ae00a Allow for output folder argument, bump version 2024-02-01 10:55:23 +09:00
Deterous 4116c56b7d Add verbose flag to IRDKit, improve printing 2024-02-01 10:31:07 +09:00
Deterous e30ac0961a Finalise diff function, bump version 2024-02-01 00:52:35 +09:00
Deterous 1ef9f06e35 Improve diff 2024-01-31 23:41:46 +09:00
Deterous 563b05b44f Bump version 2024-01-31 20:24:34 +09:00
Deterous 07a3a3a7f1 Correct number of files for non-contiguous ISOs 2024-01-31 20:13:42 +09:00
Deterous df9bb8c7f2 Diff command for comparing IRDs 2024-01-31 16:56:35 +09:00
Deterous 1a74e8fed0 Better path handling 2024-01-31 15:03:37 +09:00
Deterous e6f6d1f41a Hash non-contiguous files 2024-01-31 14:44:35 +09:00
Deterous 2fe56e994e Add DiscUtils submodule 2024-01-30 18:19:56 +09:00
Deterous e910312d6e Fix param parsing 2023-12-23 08:56:13 +13:00
Deterous 26bde9d753 Simplify conditionals 2023-12-23 00:07:02 +13:00
Deterous c1fa640e0b Don't assume PARAM.SFO fields exist 2023-12-22 23:53:39 +13:00
Deterous ec6caca3e9 Output file path flag 2023-12-06 13:26:05 +13:00
Deterous 8ccbc8430c Print filename for ISO info, remove trailing comma for JSON 2023-12-06 12:56:31 +13:00
Deterous a036481560 Proper printing 2023-12-05 00:02:09 +13:00
Deterous ba7bacc11c Print all data to file 2023-12-04 23:51:07 +13:00
Deterous 8b82ec3d4e Catch filenotfound errors for non-PS3 ISOs 2023-12-04 23:45:04 +13:00
Deterous 0443ba5b9f Fix info command when not given outpath 2023-12-04 23:40:46 +13:00
Deterous 493c92220a Allow info command to operate in directory 2023-12-04 23:35:52 +13:00
Deterous 602394a182 Automatically detect key files with same name 2023-11-30 13:37:27 +13:00
Deterous 338f7a00b7 Fix issue with manual IRD creation 2023-11-30 12:51:15 +13:00
Deterous 7f9fd1c9d6 Fix info printing 2023-11-30 12:36:24 +13:00
13 changed files with 1208 additions and 418 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "DiscUtils"]
path = DiscUtils
url = https://github.com/Deterous/DiscUtils
Submodule
+1
Submodule DiscUtils added at 299030c20b
+8 -9
View File
@@ -3,19 +3,18 @@
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<PackAsTool>true</PackAsTool> <TargetName>irdkit</TargetName>
<ToolCommandName>irdkit</ToolCommandName> <AssemblyName>irdkit</AssemblyName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.2.0</Version> <Version>0.5.0</Version>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Deterous</Authors> <Authors>Deterous</Authors>
<Description>Library for ISO Rebuild Data</Description> <Description>Library for ISO Rebuild Data</Description>
<Copyright>Copyright (c) Deterous 2023</Copyright> <Copyright>Copyright (c) Deterous 2023-2024</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Deterous/LibIRD</RepositoryUrl> <RepositoryUrl>https://github.com/Deterous/LibIRD</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
@@ -28,11 +27,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LibIRD\LibIRD.csproj" /> <ProjectReference Include="..\LibIRD\LibIRD.csproj" PrivateAssets="All" />
<ProjectReference Include="..\DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj" PrivateAssets="All" />
<ProjectReference Include="..\DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj" PrivateAssets="All" />
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="DiscUtils.Core" Version="0.16.13" /> <PackageReference Include="SabreTools.RedumpLib" Version="1.3.3" />
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" />
<PackageReference Include="SabreTools.RedumpLib" Version="1.3.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+770 -145
View File
File diff suppressed because it is too large Load Diff
+40 -6
View File
@@ -1,7 +1,41 @@
## How to use IRDKit # How to use IRDKit
IRDKit is a tool that allows direct use of LibIRD functionality from the command line interface. The basic usage is: IRDKit is a tool that allows direct use of LibIRD functionality from the command line interface.
``` For full help instructions, run `irdkit help`
irdkit game.iso game.ird
``` ## Creating ISOs
For detailed usage, run `irdkit --help`
For all options, run `irdkit help create`
To create an IRD from an ISO, run `irdkit create game.iso`
Multiple ISOs can be processed at once with `irdkit create game1.iso game2.iso`
Or a whole directory of ISOs can be processed with `irdkit create ./ISO`, or recursively with `irdkit create -r ./ISO`
The IRD will be created in the same folder as the ISO, with the same filename.
A different IRD path and/or filename can be defined with `-o` or `--output=`
### Key
By default, IRDs will be created by pulling keys from redump.org
A key can be manually provided with `-k` or `--key=`
A key file can be provided with `-f game.key` or `--key-file=`
A key from GetKey log file can be used with `-l game.getkey.log` or `--getkey-log=`
### PIC
By default, a PIC will be generated assuming a default layerbreak
A layerbreak value can be provided with `-b` or `--layerbreak=`
A PIC from a GetKey log file can be used with `-l game.getkey.log` or `--getkey-log=`
## Printing info
For all options, run `irdkit help info`
To print info about an ISO or IRD file, run `irdkit info game.iso` or `irdkit info game.ird`
The info can be printed to a file, e.g. `-o out.txt` or `--output=`
The info can be formatted as a JSON with `-j` or `--json`
## Comparing IRDs
For all options, run `irdkit help diff`
To compare two IRDs, run `irdkit diff game1.ird game2.ird`
The comparison can be printed to a file, e.g. `-o out.txt` or `--output=`
+26
View File
@@ -7,6 +7,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibIRD", "LibIRD\LibIRD.csp
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRDKit", "IRDKit\IRDKit.csproj", "{9060E3AF-E4FB-436F-93A1-5C47389CB88E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRDKit", "IRDKit\IRDKit.csproj", "{9060E3AF-E4FB-436F-93A1-5C47389CB88E}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A8BB1F9F-FC8E-471C-9647-6FFF91D63BA7}"
ProjectSection(SolutionItems) = preProject
.gitattributes = .gitattributes
.gitignore = .gitignore
LICENSE.txt = LICENSE.txt
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscUtils.Core", "DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj", "{9E71F5CD-887D-4A61-8460-9F7E8F110B46}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscUtils.Iso9660", "DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj", "{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscUtils.Streams", "DiscUtils\Library\DiscUtils.Streams\DiscUtils.Streams.csproj", "{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -21,6 +35,18 @@ Global
{9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Debug|Any CPU.Build.0 = Debug|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.Build.0 = Release|Any CPU {9060E3AF-E4FB-436F-93A1-5C47389CB88E}.Release|Any CPU.Build.0 = Release|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E71F5CD-887D-4A61-8460-9F7E8F110B46}.Release|Any CPU.Build.0 = Release|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E1EF05F-388B-42C6-A4E7-98BE20C7C8EC}.Release|Any CPU.Build.0 = Release|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8D0B8B0-F165-4FA1-AC7B-194F5F48A5F9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+212 -137
View File
@@ -1,12 +1,12 @@
using DiscUtils; using DiscUtils;
using DiscUtils.Iso9660; using DiscUtils.Iso9660;
using DiscUtils.Streams;
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.IO.Hashing; using System.IO.Hashing;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json;
namespace LibIRD namespace LibIRD
{ {
@@ -117,7 +117,6 @@ namespace LibIRD
} }
} }
private byte[] _discKey; private byte[] _discKey;
// TODO: Link Data1Key and Disc Key
/// <summary> /// <summary>
/// D1 key /// D1 key
@@ -271,7 +270,7 @@ namespace LibIRD
/// <summary> /// <summary>
/// MD5 hashes for all decrypted files in the image /// MD5 hashes for all decrypted files in the image
/// </summary> /// </summary>
/// <remarks><see cref="FileHashes"/> files, 16-bytes per hash, alternating with each <see cref="FileHashes"/> entry</remarks> /// <remarks><see cref="FileCount"/> files, 16-bytes per hash, alternating with each <see cref="FileHashes"/> entry</remarks>
public byte[][] FileHashes { get; private set; } public byte[][] FileHashes { get; private set; }
/// <summary> /// <summary>
@@ -365,13 +364,12 @@ namespace LibIRD
/// <param name="discKey">Disc Key, byte array of length 16</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="discID">Disc ID, byte array of length 16</param>
/// <param name="discPIC">Disc PIC, byte array of length 115</param> /// <param name="discPIC">Disc PIC, byte array of length 115</param>
/// <param name="redump">True if redump-style IRD</param> /// <param name="redump">True if redump-style IRD (default: false)</param>
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC, bool redump = false) public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC, bool redump = false)
{ {
// Parse ISO, Disc Key, Disc ID, and PIC // Parse ISO, Disc Key, Disc ID, and PIC
DiscKey = discKey; DiscKey = discKey;
GenerateD1(discKey); DiscID = discID;
GenerateD2(discID);
PIC = discPIC; PIC = discPIC;
// Generate IRD files from ISO // Generate IRD files from ISO
@@ -407,7 +405,6 @@ namespace LibIRD
private protected static byte[] GenerateD1(byte[] key) private protected static byte[] GenerateD1(byte[] key)
{ {
// Validate key // Validate key
ArgumentNullException.ThrowIfNull(key, nameof(key));
if (key.Length != 16) if (key.Length != 16)
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(key)); throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(key));
@@ -441,7 +438,6 @@ namespace LibIRD
private protected static byte[] GenerateDiscKey(byte[] d1) private protected static byte[] GenerateDiscKey(byte[] d1)
{ {
// Validate key // Validate key
ArgumentNullException.ThrowIfNull(d1, nameof(d1));
if (d1.Length != 16) if (d1.Length != 16)
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(d1)); throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(d1));
@@ -475,7 +471,6 @@ namespace LibIRD
private protected static byte[] GenerateD2(byte[] d2) private protected static byte[] GenerateD2(byte[] d2)
{ {
// Validate id // Validate id
ArgumentNullException.ThrowIfNull(d2, nameof(d2));
if (d2.Length != 16) throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2)); if (d2.Length != 16) throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
// Setup AES encryption // Setup AES encryption
@@ -508,7 +503,6 @@ namespace LibIRD
private protected static byte[] GenerateDiscID(byte[] d2) private protected static byte[] GenerateDiscID(byte[] d2)
{ {
// Validate id // Validate id
ArgumentNullException.ThrowIfNull(d2 , nameof(d2));
if (d2.Length != 16) if (d2.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2)); throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
@@ -541,9 +535,7 @@ namespace LibIRD
/// <exception cref="InvalidDataException"></exception> /// <exception cref="InvalidDataException"></exception>
private protected void ParseGetKeyLog(string getKeyLog) private protected void ParseGetKeyLog(string getKeyLog)
{ {
// Validate .getkey.log file path // Validate .getkey.log file path
ArgumentNullException.ThrowIfNull(getKeyLog, nameof(getKeyLog));
if (!File.Exists(getKeyLog)) if (!File.Exists(getKeyLog))
throw new FileNotFoundException(nameof(getKeyLog)); throw new FileNotFoundException(nameof(getKeyLog));
@@ -641,34 +633,59 @@ namespace LibIRD
ExtraConfig |= 0x01; ExtraConfig |= 0x01;
// Redump-style IRDs use fields from PS3_DISC.SFB // Redump-style IRDs use fields from PS3_DISC.SFB
using DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_DISC.SFB", FileMode.Open, FileAccess.Read); using SparseStream s = reader.OpenFile("PS3_DISC.SFB", FileMode.Open, FileAccess.Read);
// Parse PS3_DISC.SFB file // Parse PS3_DISC.SFB file
PS3_DiscSFB ps3_DiscSFB = new(s); PS3_DiscSFB ps3_DiscSFB = new(s);
bool title_id_found = ps3_DiscSFB.Field.TryGetValue("TITLE_ID", out string title_id); bool titleIDFound = ps3_DiscSFB.Field.TryGetValue("TITLE_ID", out string titleID);
// If a valid TITLE_ID field is present, remove the hyphen to fit into standard IRD file // If a valid TITLE_ID field is present, remove the hyphen to fit into standard IRD file
if (title_id_found && title_id.Length == 10 && title_id[4] == '-') if (titleIDFound && titleID.Length == 10 && titleID[4] == '-')
TitleID = string.Concat(title_id.AsSpan(0, 4), title_id.AsSpan(5, 5)); TitleID = string.Concat(titleID.AsSpan(0, 4), titleID.AsSpan(5, 5));
// If the version field is present, this is a multi-game disc // If the version field is present, this is a multi-game disc
// Redump-style IRDs use the VERSION field from PS3_DISC.SFB instead of VERSION from PARAM.SFO // Redump-style IRDs use the VERSION field from PS3_DISC.SFB instead of VERSION from PARAM.SFO
bool version_found = ps3_DiscSFB.Field.TryGetValue("VERSION", out string disc_version); bool discVersionFound = ps3_DiscSFB.Field.TryGetValue("VERSION", out string discVersion);
if (version_found) if (discVersionFound)
DiscVersion = disc_version; DiscVersion = discVersion;
} }
// Read PS3 Metadata from PARAM.SFO // Read PS3 Metadata from PARAM.SFO
using (DiscUtils.Streams.SparseStream s = reader.OpenFile("PS3_GAME\\PARAM.SFO", FileMode.Open, FileAccess.Read)) using (SparseStream s = reader.OpenFile("\\PS3_GAME\\PARAM.SFO", FileMode.Open, FileAccess.Read))
{ {
// Parse PARAM.SFO file // Parse PARAM.SFO file
ParamSFO paramSFO = new(s); ParamSFO paramSFO = new(s);
// If PS3_DISC.SFB did not set TitleID, use PARAM.SFO TITLE_ID // If PS3_DISC.SFB did not set TitleID, use PARAM.SFO TITLE_ID
TitleID ??= paramSFO.Field["TITLE_ID"]; if (TitleID == null)
Title = paramSFO.Field["TITLE"]; {
// If PS3_DISC.SFB did not set DiscVersion, use PARAM.SFO VERSION bool titleIDFound = paramSFO.Field.TryGetValue("TITLE_ID", out string titleID);
DiscVersion ??= paramSFO.Field["VERSION"]; if (titleIDFound)
AppVersion = paramSFO.Field["APP_VER"]; TitleID = titleID.Length == 9 ? titleID : titleID.PadRight(9, '\0')[..9];
else
TitleID = "\0\0\0\0\0\0\0\0\0";
}
// Try use Title from PARAM.SFO
bool titleFound = paramSFO.Field.TryGetValue("TITLE", out string title);
Title = titleFound ? title : String.Empty;
// If PS3_DISC.SFB did not set DiscVersion, try use PARAM.SFO VERSION
if (DiscVersion == null)
{
bool discVersionFound = paramSFO.Field.TryGetValue("VERSION", out string discVersion);
if (discVersionFound)
DiscVersion = discVersion.Length == 5 ? discVersion : discVersion.PadRight(5, '\0')[..5];
else
DiscVersion = "\0\0\0\0\0";
}
// Try use App Version from PARAM.SFO
bool appVersionFound = paramSFO.Field.TryGetValue("APP_VER", out string appVersion);
if (appVersionFound)
AppVersion = appVersion.Length == 5 ? appVersion : appVersion.PadRight(5, '\0')[..5];
else
AppVersion = "\0\0\0\0\0";
} }
// Determine system update version // Determine system update version
@@ -697,7 +714,11 @@ namespace LibIRD
FileCount = 0; FileCount = 0;
HashFiles(fs, reader, rootDir); HashFiles(fs, reader, rootDir);
if (FileCount != fileCount) if (FileCount != fileCount)
throw new InvalidFileSystemException("Unexpected ISO filesystem error: "); {
long[] tempFileKeys = FileKeys;
Array.Resize(ref tempFileKeys, (int)FileCount);
FileKeys = tempFileKeys;
}
Array.Sort(FileKeys, FileHashes); Array.Sort(FileKeys, FileHashes);
} }
@@ -715,33 +736,22 @@ namespace LibIRD
private void GetSystemVersion(FileStream fs, CDReader reader) private void GetSystemVersion(FileStream fs, CDReader reader)
{ {
// Determine PUP file offset via cluster // Determine PUP file offset via cluster
DiscUtils.Streams.Range<long, long>[] updateClusters = reader.PathToClusters("\\PS3_UPDATE\\PS3UPDAT.PUP"); Range<long, long>[] updateClusters = reader.PathToClusters("\\PS3_UPDATE\\PS3UPDAT.PUP");
if (updateClusters == null || updateClusters.Length <= 0) if (updateClusters == null && updateClusters.Length == 0 && updateClusters[0] == null)
{ throw new InvalidFileSystemException("Invalid file extents for PS3UPDAT.PUP");
// File too small for dedicated cluster, try get the offset from the file extents instead
DiscUtils.Streams.StreamExtent[] updateExtents = reader.PathToExtents("\\PS3_UPDATE\\PS3UPDAT.PUP"); // PS3UPDAT.PUP file begins at first byte of dedicated cluster
if (updateExtents == null || updateExtents.Length <= 0) UpdateOffset = SectorSize * updateClusters[0].Offset;
throw new InvalidFileSystemException("Unexpected PS3UPDAT.PUP file extent in ISO filestream"); // Update file ends at the last byte of the last cluster
// PS3UPDAT.PUP file begins at start of first extent UpdateEnd = SectorSize * updateClusters[^1].Offset + updateClusters[^1].Count;
UpdateOffset = updateExtents[0].Start;
// Update file ends at the last extent plus its length
UpdateEnd = updateExtents[^1].Start + updateExtents[^1].Length;
}
else
{
// PS3UPDAT.PUP file begins at first byte of dedicated cluster
UpdateOffset = updateClusters[0] != null ? SectorSize * updateClusters[0].Offset : 0;
// Update file ends at the last byte of the last cluster
UpdateEnd = SectorSize * (updateClusters[^1].Offset + updateClusters[^1].Count);
}
// Check PUP file Magic // Check PUP file Magic
fs.Seek(UpdateOffset, SeekOrigin.Begin); fs.Seek(UpdateOffset, SeekOrigin.Begin);
byte[] pupMagic = new byte[5]; byte[] pupMagic = new byte[5];
fs.Read(pupMagic, 0, pupMagic.Length); fs.Read(pupMagic, 0, pupMagic.Length);
// If magic is incorrect, set version to "0000" (unknown) // If magic is incorrect, set version to all nulls, "\0\0\0\0" (unknown)
if (Encoding.ASCII.GetString(pupMagic) != "SCEUF") if (Encoding.ASCII.GetString(pupMagic) != "SCEUF")
SystemVersion = "0000"; SystemVersion = "\0\0\0\0";
else else
{ {
// Determine location of version string // Determine location of version string
@@ -769,24 +779,19 @@ namespace LibIRD
private void GetHeader(FileStream fs, CDReader reader) private void GetHeader(FileStream fs, CDReader reader)
{ {
// Determine the extent of the header via cluster (Sector 0 to first data sector) // Determine the extent of the header via cluster (Sector 0 to first data sector)
DiscUtils.Streams.Range<long, long>[] sfbClusters = reader.PathToClusters("\\PS3_DISC.SFB"); Range<long, long>[] sfbClusters = reader.PathToClusters("\\PS3_DISC.SFB");
if (sfbClusters == null || sfbClusters.Length <= 0) if (sfbClusters == null && sfbClusters.Length == 0 && sfbClusters[0] == null)
{ throw new InvalidFileSystemException("Invalid file extents for PS3_DISC.SFB");
// File too small for dedicated cluster, try get the first sector from the file extents instead // End of header is at beginning of first byte of dedicated cluster
DiscUtils.Streams.StreamExtent[] sfbExtents = reader.PathToExtents("\\PS3_DISC.SFB"); FirstDataSector = sfbClusters[0].Offset;
if (sfbExtents == null || sfbExtents.Length <= 0)
throw new InvalidFileSystemException("Unexpected PS3UPDAT.PUP file extent in ISO filestream");
FirstDataSector = sfbExtents[0].Start;
}
else
{
// End of header is at beginning of first byte of dedicated cluster
FirstDataSector = sfbClusters[0] != null ? sfbClusters[0].Offset : 0;
}
// Begin a GZip stream to write header to // Begin a GZip stream to write header to
using MemoryStream headerStream = new(); using MemoryStream headerStream = new();
#if NET6_0_OR_GREATER
using (GZipStream gzs = new(headerStream, CompressionLevel.SmallestSize)) using (GZipStream gzs = new(headerStream, CompressionLevel.SmallestSize))
#else
using (GZipStream gzs = new(headerStream, CompressionLevel.Optimal))
#endif
{ {
// Start reading data from the beginning of the ISO file // Start reading data from the beginning of the ISO file
fs.Seek(0, SeekOrigin.Begin); fs.Seek(0, SeekOrigin.Begin);
@@ -814,9 +819,13 @@ namespace LibIRD
{ {
// Begin a GZip stream to write footer to // Begin a GZip stream to write footer to
using MemoryStream footerStream = new(); using MemoryStream footerStream = new();
#if NET6_0_OR_GREATER
using (GZipStream gzs = new(footerStream, CompressionLevel.SmallestSize)) using (GZipStream gzs = new(footerStream, CompressionLevel.SmallestSize))
#else
using (GZipStream gzs = new(footerStream, CompressionLevel.Optimal))
#endif
{ {
// Start reading data from after last file // Start reading data from after last file (PS3UPDAT.PUP)
fs.Seek(UpdateEnd, SeekOrigin.Begin); fs.Seek(UpdateEnd, SeekOrigin.Begin);
byte[] buf = new byte[SectorSize]; byte[] buf = new byte[SectorSize];
int numBytes = (int)SectorSize; int numBytes = (int)SectorSize;
@@ -881,8 +890,8 @@ namespace LibIRD
RegionEnd[^1] = (UpdateEnd / SectorSize) - 1; RegionEnd[^1] = (UpdateEnd / SectorSize) - 1;
// Determine MD5 hashes for each region // Determine MD5 hashes for each region
using MD5 md5 = MD5.Create(); int bufSectors = 1024;
byte[] buf = new byte[SectorSize]; byte[] buf = new byte[bufSectors * SectorSize];
for (int i = 0; i < RegionCount; i++) for (int i = 0; i < RegionCount; i++)
{ {
// Start reading data from first sector of region // Start reading data from first sector of region
@@ -890,19 +899,32 @@ namespace LibIRD
// Compute MD5 hash for just the region portion of the ISO file // Compute MD5 hash for just the region portion of the ISO file
int numBytes; int numBytes;
for (long j = RegionStart[i]; j <= RegionEnd[i]; j++) using MD5 md5 = MD5.Create();
int regionSectors = (int)(RegionEnd[i] - RegionStart[i]) + 1;
for (int j = bufSectors; j <= regionSectors; j += bufSectors)
{ {
// Read one sector at a time // Read into buffer
numBytes = fs.Read(buf, 0, buf.Length); numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read // TODO: Process partial buffer if non-zero is returned
if (numBytes < buf.Length) if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly"); throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Process MD5 sum one sector at a time
// Process MD5 sum
md5.TransformBlock(buf, 0, buf.Length, null, 0); md5.TransformBlock(buf, 0, buf.Length, null, 0);
} }
// Read any remaining sectors
int bufRemainder = (int)(SectorSize * (regionSectors % bufSectors));
if (bufRemainder != 0)
{
numBytes = fs.Read(buf, 0, bufRemainder);
// TODO: Process partial buffer if non-zero is returned
if (numBytes < bufRemainder)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
}
// Compute and store MD5 hash of region // Compute and store MD5 hash of region
md5.TransformFinalBlock(buf, 0, 0); md5.TransformFinalBlock(buf, 0, bufRemainder);
RegionHashes[i] = md5.Hash; RegionHashes[i] = md5.Hash;
} }
} }
@@ -919,58 +941,90 @@ namespace LibIRD
{ {
string filePath = fileInfo.FullName; string filePath = fileInfo.FullName;
// Try get the first sector from the file extents instead // Determine the extents of the file via clusters
DiscUtils.Streams.StreamExtent[] fileExtents = reader.PathToExtents(filePath); Range<long, long>[] fileClusters = reader.PathToClusters(filePath);
if (fileExtents == null || fileExtents.Length <= 0)
throw new InvalidFileSystemException("Unexpected file extent in ISO filestream for " + filePath); // If invalid clusters were returned, we can't hash this file
if (fileExtents.Length > 1) if (fileClusters == null && fileClusters.Length == 0)
throw new InvalidFileSystemException("Non-contiguous file detected"); throw new InvalidFileSystemException($"Unexpected file extents for {filePath}");
long firstByte = fileExtents[0].Start;
int firstSector = (int)(firstByte / 2048); // Determine smallest file offset as first sector
long fileLength = fileExtents[0].Length; long smallestOffset = fileClusters[0].Offset;
FileKeys[FileCount] = firstSector; bool nonContiguous = false;
for (int i = 1; i < fileClusters.Length; i++)
{
if (fileClusters[i] == null)
throw new InvalidFileSystemException($"Unexpected file extents for {filePath}");
if (fileClusters[i].Offset * SectorSize != fileClusters[i - 1].Offset * SectorSize + fileClusters[i - 1].Count)
nonContiguous = true;
if (fileClusters[i].Offset < smallestOffset)
smallestOffset = fileClusters[i].Offset;
}
// If already encountered file offset, skip this file
if (Array.Exists(FileKeys, element => element == smallestOffset))
continue;
else if (nonContiguous)
Console.WriteLine($"Non-contiguous file found: {filePath}");
// Add file offset to keys
FileKeys[FileCount] = smallestOffset;
// Determine whether file is in encrypted or decrypted region // Determine whether file is in encrypted or decrypted region
bool encrypted = false; bool encrypted = false;
for (int i = RegionCount - 1; i > 0; i--) for (int i = RegionCount - 1; i > 0; i--)
{ {
if (RegionStart[i] <= firstSector) if (RegionStart[i] <= smallestOffset)
{ {
encrypted = i % 2 == 1; encrypted = i % 2 == 1;
break; break;
} }
} }
// Start reading data from the beginning of the ISO file // Read one sector at a time for small files
fs.Seek(firstByte, SeekOrigin.Begin); int bufSectors = (fileClusters[0].Count < 1024 * SectorSize) ? 1 : 1024;
byte[] buf = new byte[SectorSize]; byte[] buf = new byte[bufSectors * SectorSize];
int numBytes;
// Read all data before the first data sector // Hash each non-contiguous portion of the file
MD5 md5 = MD5.Create(); using MD5 md5 = MD5.Create();
for (int i = 0; i < (fileLength / SectorSize); i++) for (int i = 0; i < fileClusters.Length; i++)
{ {
numBytes = fs.Read(buf, 0, buf.Length); // Start reading data from the beginning of the current extent
// Check that an entire sector was read fs.Seek(fileClusters[i].Offset * SectorSize, SeekOrigin.Begin);
if (numBytes < buf.Length) int numBytes;
throw new InvalidFileSystemException("Disc region ended unexpectedly"); // Read file in buffers
// Decrypt sector if necessary long fileSectors = fileClusters[i].Count / SectorSize;
if (encrypted) for (int j = 0; j <= fileSectors - bufSectors; j += bufSectors)
buf = DecryptSector(buf, firstSector + i); {
// Hash sector numBytes = fs.Read(buf, 0, buf.Length);
md5.TransformBlock(buf, 0, numBytes, null, 0); // TODO: Process partial buffer if non-zero is returned
} if (numBytes != buf.Length)
// Read remaining partial sector throw new InvalidFileSystemException("Disc region ended unexpectedly");
if (fileLength % SectorSize != 0)
{ // Decrypt buffer if necessary
numBytes = fs.Read(buf, 0, buf.Length); if (encrypted)
// Check that an entire sector was read DecryptSectors(ref buf, (int)fileClusters[i].Offset + j);
if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly"); // Hash sector
// Decrypt partial sector if necessary md5.TransformBlock(buf, 0, buf.Length, null, 0);
if (encrypted) }
buf = DecryptSector(buf, firstSector + (int)(fileLength / SectorSize)); // Read remaining partial buffer
// Hash partial sector if ((fileClusters[i].Count % buf.Length) > 0)
md5.TransformBlock(buf, 0, (int)(fileLength % SectorSize), null, 0); {
numBytes = fs.Read(buf, 0, buf.Length);
// TODO: Process partial buffer if non-zero is returned
if (numBytes != buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Decrypt buffer if necessary
if (encrypted)
DecryptSectors(ref buf, (int)fileClusters[i].Offset + bufSectors * (int)(fileClusters[i].Count / buf.Length));
// Hash partial buffer
md5.TransformBlock(buf, 0, (int)(fileClusters[i].Count % buf.Length), null, 0);
}
} }
// Finalise and store MD5 hash // Finalise and store MD5 hash
@@ -987,38 +1041,49 @@ namespace LibIRD
} }
/// <summary> /// <summary>
/// Decrypts a given sector byte array /// Decrypts a given byte array of sector(s)
/// </summary> /// </summary>
/// <param name="sector">Byte array to be decrypted</param> /// <param name="buffer">Byte array to be decrypted</param>
/// <param name="offset">Number of bytes to decrypt</param>
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
private protected byte[] DecryptSector(byte[] sector, int sectorNumber) private protected void DecryptSectors(ref byte[] buffer, int sectorNumber)
{ {
ArgumentNullException.ThrowIfNull(buffer);
if (buffer.Length == 0 || buffer.Length % SectorSize != 0)
throw new ArgumentException("Encrypted buffer must be multiple of SectorSize");
// Setup AES decryption // Setup AES decryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings"); using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
// Set AES settings // Set AES settings
aes.Key = DiscKey; aes.Key = DiscKey;
aes.Padding = PaddingMode.None; aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC; aes.Mode = CipherMode.CBC;
// Determine Initial Value based on sector number // Decrypt buffer one sector at a time
byte[] iv = new byte[16]; for (int i = 0; i < buffer.Length; i += (int)SectorSize)
for (int i = 0; i < 16; i++)
{ {
byte a = (byte)(sectorNumber & 0xFF); // Determine AES Initial Value based on sector number
iv[16 - i - 1] = (byte)(sectorNumber & 0xFF); byte[] iv = new byte[16];
sectorNumber >>= 8; int tempNum = sectorNumber;
for (int j = 0; j < 16; j++)
{
iv[16 - j - 1] = (byte)(tempNum & 0xFF);
tempNum >>= 8;
}
aes.IV = iv;
// Perform AES decryption
using MemoryStream stream = new();
using CryptoStream cs = new(stream, aes.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(buffer, i, (int)SectorSize);
cs.FlushFinalBlock();
// Write decrypted sector to output
stream.ToArray().CopyTo(buffer, i);
sectorNumber++;
} }
aes.IV = iv; return;
// Perform AES decryption
using MemoryStream stream = new();
using ICryptoTransform dec = aes.CreateDecryptor();
using CryptoStream cs = new(stream, dec, CryptoStreamMode.Write);
cs.Write(sector, 0, sector.Length);
cs.FlushFinalBlock();
return stream.ToArray();
} }
/// <summary> /// <summary>
@@ -1147,7 +1212,11 @@ namespace LibIRD
// Create the IRD file stream // Create the IRD file stream
using FileStream fs = new(irdPath, FileMode.Create, FileAccess.Write); using FileStream fs = new(irdPath, FileMode.Create, FileAccess.Write);
// Create a GZipped IRD file stream // Create a GZipped IRD file stream
#if NET6_0_OR_GREATER
using GZipStream gzStream = new(fs, CompressionLevel.SmallestSize); using GZipStream gzStream = new(fs, CompressionLevel.SmallestSize);
#else
using GZipStream gzStream = new(fs, CompressionLevel.Optimal);
#endif
// Write entire gzipped IRD stream to file // Write entire gzipped IRD stream to file
stream.Position = 0; stream.Position = 0;
stream.CopyTo(gzStream); stream.CopyTo(gzStream);
@@ -1214,7 +1283,7 @@ namespace LibIRD
uint fileCount = br.ReadUInt32(); uint fileCount = br.ReadUInt32();
long[] fileKeys = new long[fileCount]; long[] fileKeys = new long[fileCount];
byte[][] fileHashes = new byte[fileCount][]; byte[][] fileHashes = new byte[fileCount][];
for (int i = 0; i < fileCount; i++) for (int i = 0; i < fileCount; i++)
{ {
fileKeys[i] = br.ReadInt64(); fileKeys[i] = br.ReadInt64();
fileHashes[i] = br.ReadBytes(16); fileHashes[i] = br.ReadBytes(16);
@@ -1239,7 +1308,7 @@ namespace LibIRD
// Read UID (for Version 8 onwards) // Read UID (for Version 8 onwards)
if (version > 7) if (version > 7)
uid = br.ReadUInt16(); uid = br.ReadUInt32();
// Read and CRC32 hash // Read and CRC32 hash
byte[] crc = br.ReadBytes(4); byte[] crc = br.ReadBytes(4);
@@ -1268,11 +1337,14 @@ namespace LibIRD
/// Prints IRD fields to console /// Prints IRD fields to console
/// </summary> /// </summary>
/// <param name="printPath">Optional path to save file to</param> /// <param name="printPath">Optional path to save file to</param>
public void Print(string printPath = null) public void Print(string printPath = null, string irdName = null)
{ {
// Build string from parameters // Build string from parameters
StringBuilder printText = new(); StringBuilder printText = new();
printText.AppendLine("IRD Contents:"); if (irdName == null)
printText.AppendLine("IRD Contents:");
else
printText.AppendLine($"IRD Contents: {irdName}");
printText.AppendLine("============="); printText.AppendLine("=============");
// Append IRD fields to string builder // Append IRD fields to string builder
@@ -1305,7 +1377,7 @@ namespace LibIRD
} }
else else
{ {
File.WriteAllText(printPath, printText.ToString()); File.AppendAllText(printPath, printText.ToString());
} }
} }
@@ -1314,7 +1386,7 @@ namespace LibIRD
/// Prints IRD fields to a json object /// Prints IRD fields to a json object
/// </summary> /// </summary>
/// <param name="jsonPath">Optionally print to json file</param> /// <param name="jsonPath">Optionally print to json file</param>
public void PrintJson(string jsonPath = null) public void PrintJson(string jsonPath = null, bool single = true)
{ {
// Build string from parameters // Build string from parameters
StringBuilder json = new(); StringBuilder json = new();
@@ -1334,11 +1406,14 @@ namespace LibIRD
json.AppendLine($" \"Extra Config\": \"{ExtraConfig:X4}\","); json.AppendLine($" \"Extra Config\": \"{ExtraConfig:X4}\",");
if (Attachments != 0x0000) if (Attachments != 0x0000)
json.AppendLine($" \"Attachments\": \"{Attachments:X4}\","); json.AppendLine($" \"Attachments\": \"{Attachments:X4}\",");
json.AppendLine($" \"Unique ID\": \"{UID:X8}"); json.AppendLine($" \"Unique ID\": \"{UID:X8}\",");
json.AppendLine($" \"Data 1 Key\": \"{Convert.ToHexString(Data1Key)}\","); json.AppendLine($" \"Data 1 Key\": \"{Convert.ToHexString(Data1Key)}\",");
json.AppendLine($" \"Data 2 Key\": \"{Convert.ToHexString(Data2Key)}\","); json.AppendLine($" \"Data 2 Key\": \"{Convert.ToHexString(Data2Key)}\",");
json.AppendLine($" \"PIC\": \"{Convert.ToHexString(PIC)}\""); json.AppendLine($" \"PIC\": \"{Convert.ToHexString(PIC)}\"");
json.AppendLine("}"); if (single)
json.AppendLine("}");
else
json.AppendLine("},");
// If no path given, output to console // If no path given, output to console
if (jsonPath == null) if (jsonPath == null)
+18 -7
View File
@@ -2,32 +2,43 @@
<PropertyGroup> <PropertyGroup>
<!-- Assembly Properties --> <!-- Assembly Properties -->
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.2.0</Version> <Version>0.5.0</Version>
<PackageOutputPath>../nupkg</PackageOutputPath>
<!-- Package Properties --> <!-- Package Properties -->
<Authors>Deterous</Authors> <Authors>Deterous</Authors>
<Description>Library for ISO Rebuild Data</Description> <Description>Library for ISO Rebuild Data</Description>
<Copyright>Copyright (c) Deterous 2023</Copyright> <Copyright>Copyright (c) Deterous 2023-2024</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Deterous/LibIRD/</RepositoryUrl> <RepositoryUrl>https://github.com/Deterous/LibIRD/</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageTags>ps3 iso ird redump</PackageTags> <PackageTags>ps3 iso ird redump</PackageTags>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="./README.md" Pack="true" PackagePath=""/> <None Include="./README.md" Pack="true" PackagePath="" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscUtils.Core" Version="0.16.13" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Core\DiscUtils.Core.csproj" PrivateAssets="All" />
<PackageReference Include="DiscUtils.Iso9660" Version="0.16.13" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Iso9660\DiscUtils.Iso9660.csproj" PrivateAssets="All" />
<PackageReference Include="DiscUtils.Streams" Version="0.16.13" /> <ProjectReference Include="..\DiscUtils\Library\DiscUtils.Streams\DiscUtils.Streams.csproj" PrivateAssets="All" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" /> <PackageReference Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')-&gt;WithMetadataValue('PrivateAssets', 'All'))" />
</ItemGroup>
</Target>
</Project> </Project>
+12 -8
View File
@@ -27,18 +27,14 @@ namespace LibIRD
/// A field within the PS3_DISC.SFB file /// A field within the PS3_DISC.SFB file
/// </summary> /// </summary>
/// <remarks>string Key, string Value</remarks> /// <remarks>string Key, string Value</remarks>
public Dictionary<string, string> Field { get; private set; } public Dictionary<string, string> Field { get; private set; }
/// <summary> /// <summary>
/// Constructor using a PARAM.SFO file path /// Constructor using a PARAM.SFO file path
/// </summary> /// </summary>
/// <param name="sfbPath">Full file path to the PS3_DISC.SFB file</param> /// <param name="sfbPath">Full file path to the PS3_DISC.SFB file</param>
/// <exception cref="ArgumentNullException"></exception>
public PS3_DiscSFB(string sfbPath) public PS3_DiscSFB(string sfbPath)
{ {
// Validate file path
ArgumentNullException.ThrowIfNull(sfbPath, nameof(sfbPath));
// Read file as a stream, and parse file // Read file as a stream, and parse file
using FileStream fs = new(sfbPath, FileMode.Open, FileAccess.Read); using FileStream fs = new(sfbPath, FileMode.Open, FileAccess.Read);
Parse(fs); Parse(fs);
@@ -103,11 +99,14 @@ namespace LibIRD
/// Prints formatted parameters extracted from PS3_DISC.SFB to console /// Prints formatted parameters extracted from PS3_DISC.SFB to console
/// </summary> /// </summary>
/// <param name="printPath">Optionally print to text file</param> /// <param name="printPath">Optionally print to text file</param>
public void Print(string printPath = null) public void Print(string printPath = null, string isoName = null)
{ {
// Build string from parameters // Build string from parameters
StringBuilder printText = new(); StringBuilder printText = new();
printText.AppendLine("PS3_DISC.SFB Contents:"); if (isoName != null)
printText.AppendLine($"PS3_DISC.SFB Contents: {isoName}");
else
printText.AppendLine("PS3_DISC.SFB Contents:");
printText.AppendLine("======================"); printText.AppendLine("======================");
// Loop through all parameters in PARAM.SFO // Loop through all parameters in PARAM.SFO
@@ -132,6 +131,11 @@ namespace LibIRD
} }
} }
/// <summary>
/// Define JSON options once
/// </summary>
private readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };
/// <summary> /// <summary>
/// Prints parameters extracted from PS3_DISC.SFB to a json object /// Prints parameters extracted from PS3_DISC.SFB to a json object
/// </summary> /// </summary>
@@ -139,7 +143,7 @@ namespace LibIRD
public void PrintJson(string jsonPath = null) public void PrintJson(string jsonPath = null)
{ {
// Serialise PS3_Disc.SFB data to a JSON object // Serialise PS3_Disc.SFB data to a JSON object
string json = JsonSerializer.Serialize(Field, new JsonSerializerOptions { WriteIndented = true }); string json = JsonSerializer.Serialize(Field, JsonOpts);
// If no path given, output to console // If no path given, output to console
if (jsonPath == null) if (jsonPath == null)
+16 -13
View File
@@ -43,13 +43,8 @@ namespace LibIRD
/// Constructor using a PARAM.SFO file path /// Constructor using a PARAM.SFO file path
/// </summary> /// </summary>
/// <param name="sfoPath">Full file path to the PARAM.SFO file</param> /// <param name="sfoPath">Full file path to the PARAM.SFO file</param>
/// <exception cref="ArgumentNullException"></exception>
public ParamSFO(string sfoPath) public ParamSFO(string sfoPath)
{ {
// Validate file path
if (sfoPath == null || sfoPath.Length <= 0)
throw new ArgumentNullException(nameof(sfoPath));
// Read file as a stream, and parse file // Read file as a stream, and parse file
using FileStream fs = new(sfoPath, FileMode.Open, FileAccess.Read); using FileStream fs = new(sfoPath, FileMode.Open, FileAccess.Read);
Parse(fs); Parse(fs);
@@ -103,7 +98,7 @@ namespace LibIRD
- keyOffset[i]; - keyOffset[i];
// Read ith key name // Read ith key name
string key = Encoding.ASCII.GetString(br.ReadBytes((int) keyLen)).TrimEnd('\0'); string key = Encoding.ASCII.GetString(br.ReadBytes((int)keyLen)).TrimEnd('\0');
// Move stream to ith data // Move stream to ith data
sfoStream.Position = dataTableStart + dataOffset[i]; sfoStream.Position = dataTableStart + dataOffset[i];
@@ -112,13 +107,13 @@ namespace LibIRD
Field[key] = dataFormat[i] switch Field[key] = dataFormat[i] switch
{ {
// Non-null-terminated UTF-8 String // Non-null-terminated UTF-8 String
0x0004 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])), 0x0004 => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])),
// Null-terminated UTF-8 String // Null-terminated UTF-8 String
0x0204 => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'), 0x0204 => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])).TrimEnd('\0'),
// Integer // Integer
0x0404 => br.ReadInt32().ToString(), 0x0404 => br.ReadInt32().ToString(),
// Unknown data format, assume null-terminated string // Unknown data format, assume null-terminated string
_ => Encoding.UTF8.GetString(br.ReadBytes((int) dataLength[i])).TrimEnd('\0'), _ => Encoding.UTF8.GetString(br.ReadBytes((int)dataLength[i])).TrimEnd('\0'),
}; };
} }
} }
@@ -127,12 +122,15 @@ namespace LibIRD
/// Prints formatted parameters extracted from PARAM.SFO to console /// Prints formatted parameters extracted from PARAM.SFO to console
/// </summary> /// </summary>
/// <param name="printPath">Optionally print to text file</param> /// <param name="printPath">Optionally print to text file</param>
public void Print(string printPath = null) public void Print(string printPath = null, string isoName = null)
{ {
// Build string from parameters // Build string from parameters
StringBuilder printText = new(); StringBuilder printText = new();
printText.AppendLine("PARAM.SFO Contents:"); if (isoName != null)
printText.AppendLine("===================="); printText.AppendLine($"PARAM.SFO Contents: {isoName}");
else
printText.AppendLine("PARAM.SFO Contents:");
printText.AppendLine("===================");
// Loop through all parameters in PARAM.SFO // Loop through all parameters in PARAM.SFO
foreach (KeyValuePair<string, string> field in Field) foreach (KeyValuePair<string, string> field in Field)
@@ -156,6 +154,11 @@ namespace LibIRD
} }
} }
/// <summary>
/// Define JSON options once
/// </summary>
private readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true };
/// <summary> /// <summary>
/// Prints parameters extracted from PARAM.SFO to a json object /// Prints parameters extracted from PARAM.SFO to a json object
/// </summary> /// </summary>
@@ -163,7 +166,7 @@ namespace LibIRD
public void PrintJson(string jsonPath = null) public void PrintJson(string jsonPath = null)
{ {
// Serialise PS3_Disc.SFB data to a JSON object // Serialise PS3_Disc.SFB data to a JSON object
string json = JsonSerializer.Serialize(Field, new JsonSerializerOptions { WriteIndented = true }); string json = JsonSerializer.Serialize(Field, JsonOpts);
// If no path given, output to console // If no path given, output to console
if (jsonPath == null) if (jsonPath == null)
+81 -85
View File
@@ -1,4 +1,5 @@
using System; using DiscUtils.Iso9660;
using System;
using System.IO; using System.IO;
using System.IO.Hashing; using System.IO.Hashing;
@@ -51,15 +52,6 @@ namespace LibIRD
/// </summary> /// </summary>
public class ReIRD : IRD public class ReIRD : IRD
{ {
#region Properties
/// <summary>
/// ISO file size
/// </summary>
private long Size { get; set; }
#endregion
#region Constructors #region Constructors
/// <summary> /// <summary>
@@ -67,25 +59,10 @@ namespace LibIRD
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <param name="getKeyLog">Path to the GetKey log file</param> /// <param name="getKeyLog">Path to the GetKey log file</param>
/// <param name="layerbreak">Layerbreak value, in sectors</param> public ReIRD(string isoPath, string getKeyLog) : base(isoPath, getKeyLog, true)
/// <exception cref="InvalidDataException"></exception>
public ReIRD(string isoPath, string getKeyLog, long? layerbreak = null) : base(isoPath, getKeyLog, true)
{ {
// Generate Unique Identifier using ISO CRC32 // Generate Unique Identifier using ISO CRC32
UID = GenerateUID(isoPath); UID = GenerateUID(isoPath);
// Determine ISO file size
Size = CalculateSize(isoPath);
// Generate Data 2 using Disc ID
DiscID = GenerateID(Size);
// Generate Disc PIC
byte[] pic = GeneratePIC(Size, layerbreak * SectorSize);
// Check that GetKey log matches expected PIC
if (!((ReadOnlySpan<byte>)PIC).SequenceEqual(pic))
throw new InvalidDataException("Unexpected PIC in .getkey.log");
} }
/// <summary> /// <summary>
@@ -94,23 +71,24 @@ namespace LibIRD
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param> /// <param name="key">Disc Key, redump-style (AES encrypted Data 1)</param>
/// <param name="layerbreak">Layerbreak value, in sectors</param> /// <param name="layerbreak">Layerbreak value, in sectors</param>
/// <param name="uid">Unique ID, crc32 hash of ISO</param>
/// <param name="region">Disc Region</param> /// <param name="region">Disc Region</param>
public ReIRD(string isoPath, byte[] key, long? layerbreak = null, Region region = Region.NONE) public ReIRD(string isoPath, byte[] key, long? layerbreak = null, uint? uid = null, Region region = Region.NONE) : base()
{ {
// Generate Unique Identifier using ISO CRC32 // Generate Unique Identifier using ISO CRC32
UID = GenerateUID(isoPath); UID = uid == null ? GenerateUID(isoPath) : (uint)uid;
// Determine ISO file size // Determine ISO file size
Size = CalculateSize(isoPath); long size = CalculateSize(isoPath);
// Set Disc Key // Set Disc Key
DiscKey = key; DiscKey = key;
// Generate Data 2 using Disc ID // Generate Data 2 using Disc ID
DiscID = GenerateID(Size, region); DiscID = GenerateID(size, region);
// Generate Disc PIC // Generate Disc PIC
PIC = GeneratePIC(Size, layerbreak * SectorSize); PIC = GeneratePIC(isoPath, size, layerbreak * SectorSize);
// Generate IRD fields // Generate IRD fields
GenerateIRD(isoPath, true); GenerateIRD(isoPath, true);
@@ -143,12 +121,13 @@ namespace LibIRD
/// <param name="layerbreak">Layer break value, byte at which disc layers are split across</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> /// <param name="exactIRD">True to generate a PIC in 3k3y style (0x03 at 115th byte for BD-50 discs)</param>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
private static byte[] GeneratePIC(long size, long? layerbreak = null, bool exactIRD = false) private static byte[] GeneratePIC(string isoPath, long size, long? layerbreak = null, bool exactIRD = false)
{ {
// Validate size // Validate size
if (size <= 0 || (size % SectorSize) != 0) if (size <= 0 || (size % SectorSize) != 0)
throw new ArgumentException("ISO Size in bytes must be a positive integer multiple of 2048", nameof(size)); throw new ArgumentException("ISO Size in bytes must be a positive integer multiple of 2048", nameof(size));
// Validate layerbreak
// Validate provided layerbreak
if (layerbreak != null) if (layerbreak != null)
{ {
if (layerbreak <= 0 || (layerbreak >= size)) if (layerbreak <= 0 || (layerbreak >= size))
@@ -156,70 +135,78 @@ namespace LibIRD
if (layerbreak >= 2 * BDLayerSize || layerbreak % SectorSize != 0) if (layerbreak >= 2 * BDLayerSize || layerbreak % SectorSize != 0)
throw new ArgumentException("Unexpected layerbreak value", nameof(size)); throw new ArgumentException("Unexpected layerbreak value", nameof(size));
} }
// If layerbreak value was not set, assume it is a non-hybrid disc with default layerbreak else
long layer_break = layerbreak ?? BDLayerSize; {
// If no layerbreak provided, ensure ISO is not BD-Video hybrid
using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath);
CDReader reader = new(fs, true, true);
if (reader.DirectoryExists("\\BDMV"))
throw new ArgumentException("Layerbreak must be provided for BD-Video hybrid discs");
// Assume disc has default layerbreak
layerbreak = BDLayerSize;
}
// Generate the PIC based on the size and layerbreak of the ISO // Generate the PIC based on the size and layerbreak of the ISO
byte[] pic; byte[] pic;
if (size > BDLayerSize) // if BD-50 if (size > BDLayerSize) // if BD-50
{ {
// Layer 0 start sector = 0x01000000 // Layer 0 start sector = 0x01000000
long l0_start_sector = 1048576; long l0_start_sector = 1048576;
// Layer 0 end sector = start sector + layerbreak - 2 // Layer 0 end sector = start sector + layerbreak - 2
long l0_end_sector = (layer_break / SectorSize) + l0_start_sector - 2; long l0_end_sector = ((long)layerbreak / SectorSize) + l0_start_sector - 2;
// Convert end sector location to hex values for PIC // Convert end sector location to hex values for PIC
byte[] l0es = [(byte)((l0_end_sector >> 24) & 0xFF), byte[] l0es = [(byte)((l0_end_sector >> 24) & 0xFF),
(byte)((l0_end_sector >> 16) & 0xFF), (byte)((l0_end_sector >> 16) & 0xFF),
(byte)((l0_end_sector >> 8) & 0xFF), (byte)((l0_end_sector >> 8) & 0xFF),
(byte)((l0_end_sector >> 0) & 0xFF)]; (byte)((l0_end_sector >> 0) & 0xFF)];
// Layer 1 start sector = end of disc (0x01EFFFFE) - layerbreak + 2 // Layer 1 start sector = end of disc (0x01EFFFFE) - layerbreak + 2
long l1_start_sector = 32505854 - (layer_break / SectorSize) + 2; long l1_start_sector = 32505854 - ((long)layerbreak! / SectorSize) + 2;
// Convert start of start sector location to hex values for PIC // Convert start of start sector location to hex values for PIC
byte[] l1ss = [(byte)((l1_start_sector >> 24) & 0xFF), byte[] l1ss = [(byte)((l1_start_sector >> 24) & 0xFF),
(byte)((l1_start_sector >> 16) & 0xFF), (byte)((l1_start_sector >> 16) & 0xFF),
(byte)((l1_start_sector >> 8) & 0xFF), (byte)((l1_start_sector >> 8) & 0xFF),
(byte)((l1_start_sector >> 0) & 0xFF)]; (byte)((l1_start_sector >> 0) & 0xFF)];
// Total sectors used = num_sectors + Layer 0 start + sectors_between_layers (usually 0x01358C00 - 0x00CA73FE - 3)
long total_sectors = (size / SectorSize) + l0_start_sector + (l1_start_sector - l0_end_sector - 3);
byte[] ts = BitConverter.GetBytes((uint) total_sectors);
// Define the PIC // Total sectors used = num_sectors + Layer 0 start + sectors_between_layers (usually 0x01358C00 - 0x00CA73FE - 3)
pic = [ long total_sectors = (size / SectorSize) + l0_start_sector + (l1_start_sector - l0_end_sector - 3);
// Initial portion of PIC (24 bytes) byte[] ts = BitConverter.GetBytes((uint)total_sectors);
// [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num]
// Define the PIC
pic = [
// Initial portion of PIC (24 bytes)
// [4098 bytes] [2x 0x00] ["DI"] [v1] [10units] [DI num]
0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00, 0x10, 0x02, 0x00, 0x00, 0x44, 0x49, 0x01, 0x10, 0x00, 0x00, 0x20, 0x00,
// ["BDR"] [2 layers] // ["BDR"] [2 layers]
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc (4 bytes) // Total sectors used on disc (4 bytes)
ts[3], ts[2], ts[1], ts[0], ts[3], ts[2], ts[1], ts[0],
// 1st Layer sector start location (4 bytes) // 1st Layer sector start location (4 bytes)
0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
// 1st Layer sector end location (4 bytes), 0x00CA73FE for default BD layerbreak of 12219392 // 1st Layer sector end location (4 bytes), 0x00CA73FE for default BD layerbreak of 12219392
l0es[0], l0es[1], l0es[2], l0es[3], l0es[0], l0es[1], l0es[2], l0es[3],
// 32 bytes of zeros // 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,
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 // Initial portion of PIC again, for 2nd layer
// ["DI"] [v1] [11unit][DI num] // ["DI"] [v1] [11unit][DI num]
0x44, 0x49, 0x01, 0x11, 0x00, 0x01, 0x20, 0x00, 0x44, 0x49, 0x01, 0x11, 0x00, 0x01, 0x20, 0x00,
// ["BDR"] [2 layers] // ["BDR"] [2 layers]
0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x44, 0x4F, 0x01, 0x21, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
// Total sectors used on disc // Total sectors used on disc
ts[3], ts[2], ts[1], ts[0], ts[3], ts[2], ts[1], ts[0],
// 2nd Layer sector start location, 0x01358C00 for default BD layerbreak of 12219392 // 2nd Layer sector start location, 0x01358C00 for default BD layerbreak of 12219392
l1ss[0], l1ss[1], l1ss[2], l1ss[3], l1ss[0], l1ss[1], l1ss[2], l1ss[3],
// 2nd Layer sector end location // 2nd Layer sector end location
0x01, 0xEF, 0xFF, 0xFE, 0x01, 0xEF, 0xFF, 0xFE,
// Remaining 32 bytes are zeroes // Remaining 32 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 ];
// 3k3y style: 0x03 at last byte // 3k3y style: 0x03 at last byte
if (exactIRD) if (exactIRD)
pic[114] = 0x03; pic[114] = 0x03;
} }
else // if BD-25 else // if BD-25
{ {
// Total sectors used on disc: num_sectors + layer_sector_end (0x00100000) - 1 // Total sectors used on disc: num_sectors + layer_sector_end (0x00100000) - 1
@@ -245,7 +232,7 @@ namespace LibIRD
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 ];
} }
return pic; return pic;
} }
@@ -253,15 +240,19 @@ namespace LibIRD
/// Generates the UID field by computing the CRC32 hash of the ISO /// Generates the UID field by computing the CRC32 hash of the ISO
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception> /// <exception cref="FileNotFoundException"></exception>
private static uint GenerateUID(string isoPath) private static uint GenerateUID(string isoPath)
{ {
// Validate ISO path
ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath));
// Check file exists // Check file exists
var iso = new FileInfo(isoPath); FileInfo iso;
try
{
iso = new FileInfo(isoPath);
}
catch (Exception e)
{
throw new ArgumentException("Invalid ISO Path: " + e.Message);
}
if (!iso.Exists) if (!iso.Exists)
throw new FileNotFoundException(nameof(isoPath)); throw new FileNotFoundException(nameof(isoPath));
@@ -282,15 +273,20 @@ namespace LibIRD
/// Calculates ISO file size /// Calculates ISO file size
/// </summary> /// </summary>
/// <param name="isoPath">Path to the ISO</param> /// <param name="isoPath">Path to the ISO</param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception>
/// <exception cref="FileNotFoundException"></exception> /// <exception cref="FileNotFoundException"></exception>
private static long CalculateSize(string isoPath) private static long CalculateSize(string isoPath)
{ {
// Validate ISO path
ArgumentNullException.ThrowIfNull(isoPath, nameof(isoPath));
// Check file exists // Check file exists
var iso = new FileInfo(isoPath); FileInfo iso;
try
{
iso = new FileInfo(isoPath);
}
catch (Exception e)
{
throw new ArgumentException("Invalid ISO Path: " + e.Message);
}
if (!iso.Exists) if (!iso.Exists)
throw new FileNotFoundException(nameof(isoPath)); throw new FileNotFoundException(nameof(isoPath));
+6 -8
View File
@@ -8,16 +8,14 @@ IRD files contain a summary of what data is on a PlayStation 3 disc. It can be u
## How to use IRDKit ## How to use IRDKit
IRDKit is a tool that allows direct use of LibIRD functionality from the command line interface. The basic usage is: IRDKit is a tool that allows direct use of LibIRD functionality from a command line interface. The basic usage is:
``` - Printing info about an ISO: `irdkit info game.iso`
irdkit create game.iso - Printing info about all ISOs and IRDs in a folder: `irdkit info .`
``` - Creating an IRD from an ISO: `irdkit create game.iso`
- Finding differences between two IRDs: `irdkit diff game1.ird game2.ird`
For detailed usage, read more [here](IRDKit). For detailed usage, read more [here](IRDKit).
## Using the LibIRD library ## Using the LibIRD library
LibIRD was originally made for creating reproducible, redump-style IRDs when dumping PS3 discs with [MPF](https://github.com/SabreTools/MPF). If you wish to integrate LibIRD into your own application, read the examples [here](LibIRD). LibIRD was originally made for creating reproducible, redump-style IRDs when dumping PS3 discs with [MPF](https://github.com/SabreTools/MPF). If you wish to integrate LibIRD into your own application, read the examples [here](LibIRD).
## Limitations
Currently, LibIRD does not produce correct IRDs for ISOs with non-contiguous files. Therefore, do not consider this library "stable" until v1.0.0 is released.
+15
View File
@@ -0,0 +1,15 @@
dotnet publish -c Release -f net8.0 -r win-x86 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r win-x64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r win-arm64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r linux-x64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r linux-arm64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r osx-x64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
dotnet publish -c Release -f net8.0 -r osx-arm64 --self-contained=false -p:PublishSingleFile=true -p:DebugType=None IRDKit\IRDKit.csproj
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/win-x86/publish/irdkit.exe" -Destination "./irdkit-win-x86.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/win-x64/publish/irdkit.exe" -Destination "./irdkit-win-x64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/win-arm64/publish/irdkit.exe" -Destination "./irdkit-win-arm64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/linux-x64/publish/irdkit" -Destination "./irdkit-linux-x64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/linux-arm64/publish/irdkit" -Destination "./irdkit-linux-arm64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/osx-x64/publish/irdkit" -Destination "./irdkit-osx-x64.zip" -CompressionLevel "Optimal"
Compress-Archive -Path "./IRDKit/bin/Release/net8.0/osx-arm64/publish/irdkit" -Destination "./irdkit-osx-arm64.zip" -CompressionLevel "Optimal"