Skip to content

Commit d0006a4

Browse files
authored
Custom ISO Stream for full CDROM support (#40)
* Implement custom ISO stream for CDROM support * Delete old files * Delete old test * Leaner CDROM wrapper * Fix custom stream * WrapperBase using ISO stream * Reset stream after deserializing * Fix SeekOrigin * Return to cached position * CDROM Constants * correct ns * fix href
1 parent c48a139 commit d0006a4

File tree

16 files changed

+350
-328
lines changed

16 files changed

+350
-328
lines changed

SabreTools.Serialization.Test/Readers/CDROMVolumeTests.cs

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
using System;
2+
using System.IO;
3+
using SabreTools.Data.Models.CDROM;
4+
using SabreTools.IO;
5+
using SabreTools.IO.Extensions;
6+
7+
namespace SabreTools.Data.Extensions
8+
{
9+
public static class CDROM
10+
{
11+
/// <summary>
12+
/// Creates a stream that provides only the user data of a CDROM stream
13+
/// </summary>
14+
public class ISO9660Stream : Stream
15+
{
16+
// Base CDROM stream (2352-byte sector)
17+
private readonly Stream _baseStream;
18+
19+
// State variables
20+
private long _position = 0;
21+
private SectorMode _currentMode = SectorMode.UNKNOWN;
22+
private long _userDataStart = 16;
23+
private long _userDataEnd = 2064;
24+
private long _isoSectorSize = Constants.Mode1DataSize;
25+
26+
public ISO9660Stream(Stream inputStream)
27+
{
28+
if (!inputStream.CanSeek || !inputStream.CanRead)
29+
throw new ArgumentException("Stream must be readable and seekable.", nameof(inputStream));
30+
_baseStream = inputStream;
31+
}
32+
33+
public override bool CanRead => true;
34+
public override bool CanSeek => true;
35+
public override bool CanWrite => false;
36+
37+
public override void Flush()
38+
{
39+
_baseStream.Flush();
40+
}
41+
42+
public override long Length
43+
{
44+
get
45+
{
46+
return (_baseStream.Length / Constants.CDROMSectorSize) * _isoSectorSize;
47+
}
48+
}
49+
50+
public override void SetLength(long value)
51+
{
52+
throw new NotSupportedException("Setting the length of this stream is not supported.");
53+
}
54+
55+
public override void Write(byte[] buffer, int offset, int count)
56+
{
57+
throw new NotSupportedException("Writing to this stream is not supported.");
58+
}
59+
60+
protected override void Dispose(bool disposing)
61+
{
62+
if (disposing)
63+
{
64+
_baseStream.Dispose();
65+
}
66+
base.Dispose(disposing);
67+
}
68+
69+
public override long Position
70+
{
71+
// Get the position of the underlying ISO9660 stream
72+
get
73+
{
74+
// Get the user data location based on the current sector mode
75+
SetState(_position);
76+
77+
// Get the number of ISO sectors before current position
78+
long isoPosition = (_position / Constants.CDROMSectorSize) * _isoSectorSize;
79+
80+
// Add the within-sector position
81+
long remainder = _position % Constants.CDROMSectorSize;
82+
if (remainder > _userDataEnd)
83+
isoPosition += _isoSectorSize;
84+
else if (remainder > _userDataStart)
85+
isoPosition += remainder - _userDataStart;
86+
87+
return isoPosition;
88+
}
89+
set
90+
{
91+
// Seek to the underlying ISO9660 position
92+
Seek(value, SeekOrigin.Begin);
93+
}
94+
}
95+
96+
public override int Read(byte[] buffer, int offset, int count)
97+
{
98+
bool readEntireSector = false;
99+
int totalRead = 0;
100+
int remaining = count;
101+
102+
while (remaining > 0 && _position < _baseStream.Length)
103+
{
104+
// Determine location of current sector
105+
long baseStreamOffset = (_position / Constants.CDROMSectorSize) * Constants.CDROMSectorSize;
106+
107+
// Set the current sector's mode and user data location
108+
SetState(baseStreamOffset);
109+
110+
// Deal with case where base position is not in ISO stream
111+
long remainder = _position % Constants.CDROMSectorSize;
112+
long sectorOffset = remainder - _userDataStart;
113+
if (remainder < _userDataStart)
114+
{
115+
baseStreamOffset += _userDataStart;
116+
sectorOffset = 0;
117+
_position += _userDataStart;
118+
}
119+
else if (remainder >= _userDataEnd)
120+
{
121+
baseStreamOffset += Constants.CDROMSectorSize;
122+
sectorOffset = 0;
123+
_position += Constants.CDROMSectorSize - _userDataEnd + _userDataStart;
124+
}
125+
else
126+
baseStreamOffset += remainder;
127+
128+
// Sanity check on read location before seeking
129+
if (baseStreamOffset < 0 || baseStreamOffset > _baseStream.Length)
130+
{
131+
throw new ArgumentOutOfRangeException(nameof(offset), "Attempted to seek outside the stream boundaries.");
132+
}
133+
134+
// Seek to target position in base CDROM stream
135+
_baseStream.Seek(baseStreamOffset, SeekOrigin.Begin);
136+
137+
// Read the remaining bytes, up to max of one ISO sector (2048 bytes)
138+
int bytesToRead = (int)Math.Min(remaining, _isoSectorSize - sectorOffset);
139+
140+
// Don't overshoot end of stream
141+
bytesToRead = (int)Math.Min(bytesToRead, _baseStream.Length - _position);
142+
143+
if (bytesToRead == (_isoSectorSize - sectorOffset))
144+
readEntireSector = true;
145+
else
146+
readEntireSector = false;
147+
148+
// Finish reading if no more bytes to be read
149+
if (bytesToRead <= 0)
150+
break;
151+
152+
// Read up to 2048 bytes from base CDROM stream
153+
int bytesRead = _baseStream.Read(buffer, offset + totalRead, bytesToRead);
154+
155+
// Update state for base stream
156+
_position = _baseStream.Position;
157+
if (readEntireSector)
158+
_position += (Constants.CDROMSectorSize - _userDataEnd) + _userDataStart;
159+
160+
// Update state for ISO stream
161+
totalRead += bytesRead;
162+
remaining -= bytesRead;
163+
164+
if (bytesRead == 0)
165+
break;
166+
}
167+
168+
return totalRead;
169+
}
170+
171+
public override long Seek(long offset, SeekOrigin origin)
172+
{
173+
// Get the intended position for the ISO9660 stream
174+
long targetPosition;
175+
switch (origin)
176+
{
177+
case SeekOrigin.Begin:
178+
targetPosition = offset;
179+
break;
180+
case SeekOrigin.Current:
181+
targetPosition = Position + offset;
182+
break;
183+
case SeekOrigin.End:
184+
targetPosition = Length + offset;
185+
break;
186+
default:
187+
throw new ArgumentException("Invalid SeekOrigin.", nameof(origin));
188+
}
189+
190+
// Get the number of ISO sectors before current position
191+
long newPosition = (targetPosition / _isoSectorSize) * Constants.CDROMSectorSize;
192+
193+
// Set the current sector's mode and user data location
194+
SetState(newPosition);
195+
196+
// Add the within-sector position
197+
newPosition += _userDataStart + (targetPosition % _isoSectorSize);
198+
199+
if (newPosition < 0 || newPosition > _baseStream.Length)
200+
{
201+
throw new ArgumentOutOfRangeException(nameof(offset), "Attempted to seek outside the stream boundaries.");
202+
}
203+
204+
_position = _baseStream.Seek(newPosition, SeekOrigin.Begin);
205+
return Position;
206+
}
207+
208+
private void SetState(long sectorLocation)
209+
{
210+
long oldPosition = _baseStream.Position;
211+
long modePosition = (sectorLocation - sectorLocation % Constants.CDROMSectorSize) + 15;
212+
_baseStream.Seek(modePosition, SeekOrigin.Begin);
213+
byte modeByte = _baseStream.ReadByteValue();
214+
if (modeByte == 0)
215+
_currentMode = SectorMode.MODE0;
216+
else if (modeByte == 1)
217+
_currentMode = SectorMode.MODE1;
218+
else if (modeByte == 2)
219+
{
220+
_baseStream.Seek(modePosition + 3, SeekOrigin.Begin);
221+
byte submode = _baseStream.ReadByteValue();
222+
if ((submode & 0x20) == 0x20)
223+
_currentMode = SectorMode.MODE2_FORM2;
224+
else
225+
_currentMode = SectorMode.MODE2_FORM1;
226+
}
227+
else
228+
_currentMode = SectorMode.UNKNOWN;
229+
230+
// Set the user data location variables
231+
switch (_currentMode)
232+
{
233+
case SectorMode.MODE1:
234+
_userDataStart = 16;
235+
_userDataEnd = 2064;
236+
//_isoSectorSize = Constants.Mode1DataSize;
237+
break;
238+
239+
case SectorMode.MODE2_FORM1:
240+
_userDataStart = 24;
241+
_userDataEnd = 2072;
242+
//_isoSectorSize = Constants.Form1DataSize;
243+
break;
244+
245+
case SectorMode.MODE2_FORM2:
246+
_userDataStart = 24;
247+
_userDataEnd = 2072;
248+
// TODO: Support flexible sector length
249+
//_userDataEnd = 2348;
250+
//_isoSectorSize = Constants.Form2DataSize;
251+
break;
252+
253+
case SectorMode.MODE0:
254+
case SectorMode.MODE2:
255+
_userDataStart = 16;
256+
_userDataEnd = 2064;
257+
// TODO: Support flexible sector length
258+
//_userDataEnd = 2352;
259+
//_isoSectorSize = Constants.Mode0DataSize;
260+
break;
261+
262+
case SectorMode.UNKNOWN:
263+
_userDataStart = 16;
264+
_userDataEnd = 2064;
265+
//_isoSectorSize = Constants.Mode1DataSize;
266+
break;
267+
}
268+
269+
_baseStream.Seek(oldPosition, SeekOrigin.Begin);
270+
271+
return;
272+
}
273+
}
274+
}
275+
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
namespace SabreTools.Data.Models.CDROM
22
{
33
/// <summary>
4-
/// A CD-ROM disc image, made up of multiple data tracks, ISO 10149 / ECMA-130
5-
/// Specifically not a mixed-mode CD disc image, pure CD-ROM disc
4+
/// A CD-ROM disc image, made up of data sectors, ISO 10149 / ECMA-130
5+
/// Intentionally not a mixed-mode CD disc image, pure CD-ROM disc (no audio sectors)
6+
/// This model intentionally does not take tracks into consideration
67
/// </summary>
78
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
89
public sealed class CDROM
910
{
1011
/// <summary>
11-
/// CD-ROM data tracks
12+
/// CD-ROM data sectors
1213
/// </summary>
13-
public DataTrack[] Tracks { get; set; } = [];
14+
public DataSector[] Sectors { get; set; } = [];
1415
}
1516
}

0 commit comments

Comments
 (0)