Skip to content

Commit a3f6d3a

Browse files
Add Latin-1 encoding for filenames; validate root directory sector/size; skip inaccessible files with warning count
1 parent b689a81 commit a3f6d3a

7 files changed

Lines changed: 326 additions & 45 deletions

File tree

.editorconfig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,5 +387,5 @@ dotnet_naming_style.s_camelcase.required_suffix =
387387
dotnet_naming_style.s_camelcase.word_separator =
388388
dotnet_naming_style.s_camelcase.capitalization = camel_case
389389

390-
MA0051.maximum_lines_per_method = 240
391-
MA0051.maximum_statements_per_method = 240
390+
MA0051.maximum_lines_per_method = 500
391+
MA0051.maximum_statements_per_method = 500

XISOSharp.Cli/Program.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,20 @@ private static int Main(string[] args)
137137
}
138138
}
139139

140-
XisoWriter.CreateXiso(dir, outputDir, null, null, out _, isoName, null);
140+
try
141+
{
142+
XisoWriter.CreateXiso(dir, outputDir, null, null, out _, isoName, null);
143+
}
144+
catch (UnauthorizedAccessException ex)
145+
{
146+
Logger.LogErr($"Error: permission denied: {ex.Message}\n");
147+
return 1;
148+
}
149+
catch (IOException ex)
150+
{
151+
Logger.LogErr($"Error: {ex.Message}\n");
152+
return 1;
153+
}
141154
}
142155
return 0;
143156
}
@@ -227,6 +240,16 @@ private static int Main(string[] args)
227240
{
228241
err = 0;
229242
}
243+
catch (UnauthorizedAccessException ex)
244+
{
245+
Logger.LogErr($"Error: permission denied: {ex.Message}\n");
246+
err = 1;
247+
}
248+
catch (IOException ex)
249+
{
250+
Logger.LogErr($"Error: {ex.Message}\n");
251+
err = 1;
252+
}
230253
catch (Exception ex)
231254
{
232255
Logger.LogErr($"failed to {(extract ? "extract" : "list")} xbox iso image {xisoPath}: {ex.Message}\n");

XISOSharp.Core/Latin1Encoding.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Text;
2+
3+
namespace XISOSharp;
4+
5+
/// <summary>
6+
/// ISO-8859-1 (Latin-1) encoding that maps byte values 0–255 directly to
7+
/// Unicode code points U+0000–U+00FF. Used for XISO filenames which may
8+
/// contain extended byte values (e.g. Japanese or accented characters).
9+
/// </summary>
10+
internal static class Latin1Encoding
11+
{
12+
/// <summary>Shared singleton instance.</summary>
13+
internal static readonly Encoding Instance = new Latin1EncodingInternal();
14+
15+
private sealed class Latin1EncodingInternal : Encoding
16+
{
17+
public override int GetByteCount(char[] chars, int index, int count)
18+
{
19+
return count;
20+
}
21+
22+
public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
23+
{
24+
for (var i = 0; i < charCount; i++)
25+
{
26+
var c = chars[charIndex + i];
27+
if (c > 0xFF)
28+
throw new ArgumentException(
29+
$"Character U+{(int)c:X4} at position {charIndex + i} is outside the Latin-1 range (0x00–0xFF).",
30+
nameof(chars));
31+
32+
bytes[byteIndex + i] = (byte)c;
33+
}
34+
35+
return charCount;
36+
}
37+
38+
public override int GetCharCount(byte[] bytes, int index, int count)
39+
{
40+
return count;
41+
}
42+
43+
public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
44+
{
45+
for (var i = 0; i < byteCount; i++)
46+
{
47+
chars[charIndex + i] = (char)bytes[byteIndex + i];
48+
}
49+
50+
return byteCount;
51+
}
52+
53+
public override int GetMaxByteCount(int charCount) => charCount;
54+
public override int GetMaxCharCount(int byteCount) => byteCount;
55+
}
56+
}

XISOSharp.Core/XisoReader.cs

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,31 @@ public static (uint rootDirSector, uint rootDirSize, long discLseek) VerifyXiso(
106106
throw new ExtractErrorException(ExtractError.ErrIsoNoFiles);
107107
}
108108

109+
var fileLength = fs.Length;
110+
var totalSectors = (uint)(fileLength / Constants.SectorSize);
111+
112+
if (rootDirSector >= totalSectors)
113+
{
114+
Logger.LogErr($"{isoName}: root directory sector {rootDirSector} exceeds total sectors {totalSectors}\n");
115+
throw new InvalidDataException(
116+
$"Corrupt XISO: {isoName} — root directory sector {rootDirSector} is beyond end of image ({totalSectors} sectors).");
117+
}
118+
119+
if (rootDirSize == 0)
120+
{
121+
Logger.LogErr($"{isoName}: root directory size is zero but sector is non-zero\n");
122+
throw new InvalidDataException(
123+
$"Corrupt XISO: {isoName} — root directory size is zero with non-zero sector pointer.");
124+
}
125+
126+
var availableBytes = (long)(totalSectors - rootDirSector) * Constants.SectorSize;
127+
if (rootDirSize > availableBytes)
128+
{
129+
Logger.LogErr($"{isoName}: root directory size {rootDirSize} exceeds available space {availableBytes}\n");
130+
throw new InvalidDataException(
131+
$"Corrupt XISO: {isoName} — root directory size {rootDirSize} bytes exceeds available space ({availableBytes} bytes from sector {rootDirSector}).");
132+
}
133+
109134
fs.Seek((long)rootDirSector * Constants.SectorSize + discLseek, SeekOrigin.Begin);
110135

111136
return (rootDirSector, rootDirSize, discLseek);
@@ -192,7 +217,7 @@ internal static int TraverseXiso(
192217

193218
var nameBuf = new byte[filenameLength];
194219
ReadExact(fs, nameBuf);
195-
var filename = Encoding.ASCII.GetString(nameBuf);
220+
var filename = Latin1Encoding.Instance.GetString(nameBuf);
196221

197222
if (string.Equals(filename, ".", StringComparison.Ordinal) || string.Equals(filename, "..", StringComparison.Ordinal) ||
198223
filename.Contains('/') || filename.Contains('\\'))
@@ -217,11 +242,18 @@ internal static int TraverseXiso(
217242
{
218243
llCompat = false;
219244

245+
var leftSeek = dirStart + (long)lOffset * Constants.DwordSize;
246+
if (leftSeek >= fs.Length)
247+
{
248+
Logger.LogErr($"warning: left offset {lOffset} (seek {leftSeek}) exceeds file length {fs.Length}, truncating directory.\n");
249+
goto end_traverse;
250+
}
251+
220252
var left = new DirEntry();
221253
dir.Left = left;
222254
left.Parent = dir;
223255

224-
fs.Seek(dirStart + (long)lOffset * Constants.DwordSize, SeekOrigin.Begin);
256+
fs.Seek(leftSeek, SeekOrigin.Begin);
225257

226258
var savedDir = dir.Left!;
227259
TraverseXiso(fs, savedDir, dirStart, path, mode, ref avlRoot, llCompat, discLseek);
@@ -316,7 +348,14 @@ internal static int TraverseXiso(
316348
}
317349
}
318350

319-
fs.Seek(dirStart + (long)rOffset * Constants.DwordSize, SeekOrigin.Begin);
351+
var rightSeek = dirStart + (long)rOffset * Constants.DwordSize;
352+
if (rightSeek >= fs.Length)
353+
{
354+
Logger.LogErr($"warning: right offset {rOffset} (seek {rightSeek}) exceeds file length {fs.Length}, truncating directory.\n");
355+
break;
356+
}
357+
358+
fs.Seek(rightSeek, SeekOrigin.Begin);
320359

321360
dir.Filename = "";
322361
lOffset = rOffset;
@@ -527,8 +566,21 @@ public static int DecodeXiso(
527566
if (mode == ExtractMode.Extract && outputPath != null)
528567
{
529568
cwd = Directory.GetCurrentDirectory();
530-
Directory.CreateDirectory(outputPath);
531-
Directory.SetCurrentDirectory(outputPath);
569+
try
570+
{
571+
Directory.CreateDirectory(outputPath);
572+
Directory.SetCurrentDirectory(outputPath);
573+
}
574+
catch (UnauthorizedAccessException ex)
575+
{
576+
Logger.LogErr($"Error: permission denied: {outputPath}\n");
577+
throw new IOException($"Permission denied: {outputPath}", ex);
578+
}
579+
catch (IOException ex)
580+
{
581+
Logger.LogErr($"Error: cannot access output directory: {outputPath}: {ex.Message}\n");
582+
throw;
583+
}
532584
}
533585

534586
using var fs = new FileStream(
@@ -553,8 +605,21 @@ public static int DecodeXiso(
553605

554606
if (mode == ExtractMode.Extract && outputPath == null)
555607
{
556-
Directory.CreateDirectory(isoName);
557-
Directory.SetCurrentDirectory(isoName);
608+
try
609+
{
610+
Directory.CreateDirectory(isoName);
611+
Directory.SetCurrentDirectory(isoName);
612+
}
613+
catch (UnauthorizedAccessException ex)
614+
{
615+
Logger.LogErr($"Error: permission denied: {isoName}\n");
616+
throw new IOException($"Permission denied: {isoName}", ex);
617+
}
618+
catch (IOException ex)
619+
{
620+
Logger.LogErr($"Error: cannot create output directory: {isoName}: {ex.Message}\n");
621+
throw;
622+
}
558623
}
559624
}
560625

0 commit comments

Comments
 (0)