Skip to content

Commit 04323cc

Browse files
Add CISO compress/decompress, SHA3-256 checksum and BlockDevice abstraction parity
- CISO v1 (DEFLATE) writer/reader with plain-sector optimization, dynamic align, BCL-only compression and LZ4 read interop (CisoWriter/CisoReader) - BlockDevice abstraction (IBlockDevice, File/Memory/Offset/CisoBlockDevice) mirroring xdvdfs blockdev.rs OffsetWrapper and CISO random access - XisoReader VerifyXiso/AuditXiso IBlockDevice overloads for in-memory and CISO testing - CLI verbs compress/cso, decompress/uncso/decso (--ciso-level 0..9, --ciso-split reserved) and checksum (SHA3-256 sorted path+data) via XisoChecksum - Glob parity: GlobMatcher.MatchWithGroups/TryMatch capturing groups via WaxGlob delegation (2.6)
1 parent 6441948 commit 04323cc

11 files changed

Lines changed: 2047 additions & 2 deletions

File tree

XISOSharp.Cli/Program.cs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ private static int Main(string[] args)
9696
{
9797
return RunImageSpec(args, 1);
9898
}
99+
else if (args.Length > 0 && (string.Equals(args[0], "compress", StringComparison.OrdinalIgnoreCase) ||
100+
string.Equals(args[0], "cso", StringComparison.OrdinalIgnoreCase)))
101+
{
102+
return RunCompressMode(args, 1);
103+
}
104+
else if (args.Length > 0 && (string.Equals(args[0], "decompress", StringComparison.OrdinalIgnoreCase) ||
105+
string.Equals(args[0], "uncso", StringComparison.OrdinalIgnoreCase) ||
106+
string.Equals(args[0], "decso", StringComparison.OrdinalIgnoreCase)))
107+
{
108+
return RunDecompressMode(args, 1);
109+
}
110+
else if (args.Length > 0 && string.Equals(args[0], "checksum", StringComparison.OrdinalIgnoreCase))
111+
{
112+
return RunChecksumMode(args, 1);
113+
}
99114

100115
for (var i = optind; i < args.Length; i++)
101116
{
@@ -1670,6 +1685,249 @@ private static int RunImageSpec(string[] args, int optind)
16701685
return 0;
16711686
}
16721687

1688+
private static int RunCompressMode(string[] args, int optind)
1689+
{
1690+
string? output = null;
1691+
int level = 9;
1692+
long? splitBytes = null;
1693+
var positionals = new List<string>();
1694+
1695+
for (int i = optind; i < args.Length; i++)
1696+
{
1697+
var a = args[i];
1698+
if (string.Equals(a, "-o", StringComparison.OrdinalIgnoreCase) ||
1699+
string.Equals(a, "--output", StringComparison.OrdinalIgnoreCase))
1700+
{
1701+
if (i + 1 >= args.Length)
1702+
{
1703+
Logger.LogErr("Error: -o requires a filename\n");
1704+
return 1;
1705+
}
1706+
output = args[++i];
1707+
}
1708+
else if (string.Equals(a, "--ciso-level", StringComparison.OrdinalIgnoreCase) ||
1709+
string.Equals(a, "-l", StringComparison.OrdinalIgnoreCase) ||
1710+
string.Equals(a, "--level", StringComparison.OrdinalIgnoreCase))
1711+
{
1712+
if (i + 1 >= args.Length || !int.TryParse(args[i + 1], out level) || level < 0 || level > 9)
1713+
{
1714+
Logger.LogErr("Error: --ciso-level requires an integer 0..9\n");
1715+
return 1;
1716+
}
1717+
i++;
1718+
}
1719+
else if (string.Equals(a, "--ciso-split", StringComparison.OrdinalIgnoreCase) ||
1720+
string.Equals(a, "--split", StringComparison.OrdinalIgnoreCase))
1721+
{
1722+
if (i + 1 >= args.Length || !long.TryParse(args[i + 1], out var sb) || sb <= 0)
1723+
{
1724+
Logger.LogErr("Error: --ciso-split requires a positive integer (bytes)\n");
1725+
return 1;
1726+
}
1727+
splitBytes = sb;
1728+
i++;
1729+
}
1730+
else if (string.Equals(a, "-q", StringComparison.OrdinalIgnoreCase))
1731+
{
1732+
Logger.Quiet = true;
1733+
}
1734+
else if (string.Equals(a, "-Q", StringComparison.OrdinalIgnoreCase))
1735+
{
1736+
Logger.Quiet = Logger.RealQuiet = true;
1737+
}
1738+
else if (string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase) ||
1739+
string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase))
1740+
{
1741+
PrintUsage();
1742+
return 0;
1743+
}
1744+
else if (string.Equals(a, "-v", StringComparison.OrdinalIgnoreCase) ||
1745+
string.Equals(a, "--version", StringComparison.OrdinalIgnoreCase))
1746+
{
1747+
Console.Write(Constants.Banner);
1748+
return 0;
1749+
}
1750+
else if (a.StartsWith('-'))
1751+
{
1752+
Logger.LogErr($"Error: unknown option for compress: {a}\n");
1753+
PrintUsage();
1754+
return 1;
1755+
}
1756+
else
1757+
{
1758+
positionals.Add(a);
1759+
}
1760+
}
1761+
1762+
if (positionals.Count < 1 || positionals.Count > 2)
1763+
{
1764+
Logger.LogErr("Error: compress requires <source> [output.cso]\n");
1765+
PrintUsage();
1766+
return 1;
1767+
}
1768+
1769+
string source = positionals[0];
1770+
string? outCso = positionals.Count == 2 ? positionals[1] : output;
1771+
1772+
try
1773+
{
1774+
Logger.Log(Constants.Banner);
1775+
int rc = CisoWriter.CompressToCso(source, outCso, level, splitBytes);
1776+
return rc;
1777+
}
1778+
catch (Exception ex)
1779+
{
1780+
Logger.LogErr($"Error compressing: {ex.Message}\n");
1781+
return 1;
1782+
}
1783+
}
1784+
1785+
private static int RunDecompressMode(string[] args, int optind)
1786+
{
1787+
string? output = null;
1788+
var positionals = new List<string>();
1789+
1790+
for (int i = optind; i < args.Length; i++)
1791+
{
1792+
var a = args[i];
1793+
if (string.Equals(a, "-o", StringComparison.OrdinalIgnoreCase) ||
1794+
string.Equals(a, "--output", StringComparison.OrdinalIgnoreCase))
1795+
{
1796+
if (i + 1 >= args.Length)
1797+
{
1798+
Logger.LogErr("Error: -o requires a filename\n");
1799+
return 1;
1800+
}
1801+
output = args[++i];
1802+
}
1803+
else if (string.Equals(a, "-q", StringComparison.OrdinalIgnoreCase))
1804+
{
1805+
Logger.Quiet = true;
1806+
}
1807+
else if (string.Equals(a, "-Q", StringComparison.OrdinalIgnoreCase))
1808+
{
1809+
Logger.Quiet = Logger.RealQuiet = true;
1810+
}
1811+
else if (string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase) ||
1812+
string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase))
1813+
{
1814+
PrintUsage();
1815+
return 0;
1816+
}
1817+
else if (string.Equals(a, "-v", StringComparison.OrdinalIgnoreCase) ||
1818+
string.Equals(a, "--version", StringComparison.OrdinalIgnoreCase))
1819+
{
1820+
Console.Write(Constants.Banner);
1821+
return 0;
1822+
}
1823+
else if (a.StartsWith('-'))
1824+
{
1825+
Logger.LogErr($"Error: unknown option for decompress: {a}\n");
1826+
PrintUsage();
1827+
return 1;
1828+
}
1829+
else
1830+
{
1831+
positionals.Add(a);
1832+
}
1833+
}
1834+
1835+
if (positionals.Count < 1 || positionals.Count > 2)
1836+
{
1837+
Logger.LogErr("Error: decompress requires <cso> [output.iso]\n");
1838+
PrintUsage();
1839+
return 1;
1840+
}
1841+
1842+
string source = positionals[0];
1843+
string? outIso = positionals.Count == 2 ? positionals[1] : output;
1844+
1845+
try
1846+
{
1847+
Logger.Log(Constants.Banner);
1848+
int rc = CisoReader.DecompressToIso(source, outIso);
1849+
return rc;
1850+
}
1851+
catch (Exception ex)
1852+
{
1853+
Logger.LogErr($"Error decompressing: {ex.Message}\n");
1854+
return 1;
1855+
}
1856+
}
1857+
1858+
private static int RunChecksumMode(string[] args, int optind)
1859+
{
1860+
bool silent = false;
1861+
var positionals = new List<string>();
1862+
1863+
for (int i = optind; i < args.Length; i++)
1864+
{
1865+
var a = args[i];
1866+
if (string.Equals(a, "--silent", StringComparison.OrdinalIgnoreCase) ||
1867+
string.Equals(a, "-s", StringComparison.OrdinalIgnoreCase))
1868+
{
1869+
silent = true;
1870+
}
1871+
else if (string.Equals(a, "-q", StringComparison.OrdinalIgnoreCase))
1872+
{
1873+
Logger.Quiet = true;
1874+
}
1875+
else if (string.Equals(a, "-Q", StringComparison.OrdinalIgnoreCase))
1876+
{
1877+
Logger.Quiet = Logger.RealQuiet = true;
1878+
}
1879+
else if (string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase) ||
1880+
string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase))
1881+
{
1882+
PrintUsage();
1883+
return 0;
1884+
}
1885+
else if (string.Equals(a, "-v", StringComparison.OrdinalIgnoreCase) ||
1886+
string.Equals(a, "--version", StringComparison.OrdinalIgnoreCase))
1887+
{
1888+
Console.Write(Constants.Banner);
1889+
return 0;
1890+
}
1891+
else if (a.StartsWith('-'))
1892+
{
1893+
Logger.LogErr($"Error: unknown option for checksum: {a}\n");
1894+
PrintUsage();
1895+
return 1;
1896+
}
1897+
else
1898+
{
1899+
positionals.Add(a);
1900+
}
1901+
}
1902+
1903+
if (positionals.Count == 0)
1904+
{
1905+
Logger.LogErr("Error: checksum requires at least one image\n");
1906+
PrintUsage();
1907+
return 1;
1908+
}
1909+
1910+
int exit = 0;
1911+
foreach (var iso in positionals)
1912+
{
1913+
try
1914+
{
1915+
byte[] hash = XisoChecksum.ComputeImageChecksum(iso);
1916+
string hex = Convert.ToHexString(hash).ToLowerInvariant();
1917+
if (silent)
1918+
Logger.Log($"{hex}\n");
1919+
else
1920+
Logger.Log($"{hex}\t{iso}\n");
1921+
}
1922+
catch (Exception ex)
1923+
{
1924+
Logger.LogErr($"Error checksumming {iso}: {ex.Message}\n");
1925+
exit = 1;
1926+
}
1927+
}
1928+
return exit;
1929+
}
1930+
16731931
private static string DeriveRedumpPath(string xisoPath)
16741932
{
16751933
string dir = Path.GetDirectoryName(xisoPath) ?? "";
@@ -2336,6 +2594,18 @@ security sectors (--security-sectors).
23362594
--compress <iso> Alias: --petrify --update --video --zar
23372595
--security-sectors <sectors.txt> Override security sector ranges (start-end, 4096 sectors).
23382596
2597+
CISO / Compression modes:
2598+
2599+
compress <sourceDir|image.iso> [output.cso] [--ciso-level 0..9] [--ciso-split bytes]
2600+
Pack and compress to CISO (CSO). Source may be a directory (packed then compressed)
2601+
or an existing ISO. Level 0=store, 1=fastest … 9=smallest. Default 9.
2602+
Deflate per 2048-byte sector (version 1, BCL) with LZ4 read compat.
2603+
Alias: cso. Split threshold is reserved (single-file now).
2604+
decompress <cso> [output.iso] Decompress CISO/CSO to ISO (handles DEFLATE v1 and LZ4 v2).
2605+
Aliases: uncso, decso.
2606+
checksum <image> [images...] [--silent] Compute SHA3-256 image checksum (xdvdfs-compatible:
2607+
SHA3-256 over sorted path bytes + file data). Output: hex tab path.
2608+
23392609
XDVDFS / Packing modes (ordered remapping):
23402610
23412611
build-image [sourceDir] [output.iso] -f <xdvdfs.toml> -m "hostGlob:imagePath" [-O output] [-D|--dry-run]

0 commit comments

Comments
 (0)