-
Notifications
You must be signed in to change notification settings - Fork 4
Custom ISO Stream for full CDROM support #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 11 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
39ff75a
Implement custom ISO stream for CDROM support
Deterous c8c2f69
Delete old files
Deterous af55176
Delete old test
Deterous da46ee3
Leaner CDROM wrapper
Deterous 7fac120
Fix custom stream
Deterous 8d3b42f
Merge branch 'main' into main
Deterous 950a138
Merge branch 'SabreTools:main' into main
Deterous 09f6c2c
WrapperBase using ISO stream
Deterous 2b854ba
Reset stream after deserializing
Deterous 3d2ab64
Fix SeekOrigin
Deterous d277ece
Return to cached position
Deterous e8b22ce
CDROM Constants
Deterous e1ae393
correct ns
Deterous 881053b
fix href
Deterous File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,279 @@ | ||
| using System; | ||
| using System.IO; | ||
| using SabreTools.Data.Models.CDROM; | ||
| using SabreTools.IO; | ||
| using SabreTools.IO.Extensions; | ||
|
|
||
| namespace SabreTools.Data.Extensions | ||
| { | ||
| public static class CDROM | ||
| { | ||
| /// <summary> | ||
| /// Creates a stream that provides only the user data of a CDROM stream | ||
| /// </summary> | ||
| public class ISO9660Stream : Stream | ||
| { | ||
| // Constant variables | ||
| private readonly Stream _baseStream; | ||
| private const long _baseSectorSize = 2352; | ||
| private long _isoSectorSize = 2048; | ||
| // TODO: Support flexible sector size (MODE2_FORM2) | ||
|
|
||
| // State variables | ||
| private long _position = 0; | ||
| private SectorMode _currentMode = SectorMode.UNKNOWN; | ||
| private long _userDataStart = 16; | ||
| private long _userDataEnd = 2064; | ||
|
|
||
| public ISO9660Stream(Stream inputStream) | ||
| { | ||
| if (inputStream == null) | ||
| throw new ArgumentNullException("Stream cannot be null.", nameof(inputStream)); | ||
mnadareski marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| else if (!inputStream.CanSeek || !inputStream.CanRead) | ||
| throw new ArgumentException("Stream must be readable and seekable.", nameof(inputStream)); | ||
| _baseStream = inputStream; | ||
| } | ||
|
|
||
| public override bool CanRead => true; | ||
| public override bool CanSeek => true; | ||
| public override bool CanWrite => false; | ||
|
|
||
| public override void Flush() | ||
| { | ||
| _baseStream.Flush(); | ||
| } | ||
|
|
||
| public override long Length | ||
| { | ||
| get | ||
| { | ||
| return (_baseStream.Length / _baseSectorSize) * _isoSectorSize; | ||
| } | ||
| } | ||
|
|
||
| public override void SetLength(long value) | ||
| { | ||
| throw new NotSupportedException("Setting the length of this stream is not supported."); | ||
| } | ||
|
|
||
| public override void Write(byte[] buffer, int offset, int count) | ||
| { | ||
| throw new NotSupportedException("Writing to this stream is not supported."); | ||
| } | ||
|
|
||
| protected override void Dispose(bool disposing) | ||
| { | ||
| if (disposing) | ||
| { | ||
| _baseStream.Dispose(); | ||
| } | ||
| base.Dispose(disposing); | ||
| } | ||
|
|
||
| public override long Position | ||
| { | ||
| // Get the position of the underlying ISO9660 stream | ||
| get | ||
| { | ||
| // Get the user data location based on the current sector mode | ||
| SetState(_position); | ||
|
|
||
| // Get the number of ISO sectors before current position | ||
| long isoPosition = (_position / _baseSectorSize) * _isoSectorSize; | ||
|
|
||
| // Add the within-sector position | ||
| long remainder = _position % _baseSectorSize; | ||
| if (remainder > _userDataEnd) | ||
| isoPosition += _isoSectorSize; | ||
| else if (remainder > _userDataStart) | ||
| isoPosition += remainder - _userDataStart; | ||
|
|
||
| return isoPosition; | ||
| } | ||
| set | ||
| { | ||
| // Seek to the underlying ISO9660 position | ||
| Seek(value, SeekOrigin.Begin); | ||
| } | ||
| } | ||
|
|
||
| public override int Read(byte[] buffer, int offset, int count) | ||
| { | ||
| bool readEntireSector = false; | ||
| int totalRead = 0; | ||
| int remaining = count; | ||
|
|
||
| while (remaining > 0 && _position < _baseStream.Length) | ||
| { | ||
| // Determine location of current sector | ||
| long baseStreamOffset = (_position / _baseSectorSize) * _baseSectorSize; | ||
|
|
||
| // Set the current sector's mode and user data location | ||
| SetState(baseStreamOffset); | ||
|
|
||
| // Deal with case where base position is not in ISO stream | ||
| long remainder = _position % _baseSectorSize; | ||
| long sectorOffset = remainder - _userDataStart; | ||
| if (remainder < _userDataStart) | ||
| { | ||
| baseStreamOffset += _userDataStart; | ||
| sectorOffset = 0; | ||
| _position += _userDataStart; | ||
| } | ||
| else if (remainder >= _userDataEnd) | ||
| { | ||
| baseStreamOffset += _baseSectorSize; | ||
| sectorOffset = 0; | ||
| _position += _baseSectorSize - _userDataEnd + _userDataStart; | ||
| } | ||
| else | ||
| baseStreamOffset += remainder; | ||
|
|
||
| // Sanity check on read location before seeking | ||
| if (baseStreamOffset < 0 || baseStreamOffset > _baseStream.Length) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(offset), "Attempted to seek outside the stream boundaries."); | ||
| } | ||
|
|
||
| // Seek to target position in base CDROM stream | ||
| _baseStream.Seek(baseStreamOffset, SeekOrigin.Begin); | ||
|
|
||
| // Read the remaining bytes, up to max of one ISO sector (2048 bytes) | ||
| int bytesToRead = (int)Math.Min(remaining, _isoSectorSize - sectorOffset); | ||
|
|
||
| // Don't overshoot end of stream | ||
| bytesToRead = (int)Math.Min(bytesToRead, _baseStream.Length - _position); | ||
|
|
||
| if (bytesToRead == (_isoSectorSize - sectorOffset)) | ||
| readEntireSector = true; | ||
| else | ||
| readEntireSector = false; | ||
|
|
||
| // Finish reading if no more bytes to be read | ||
| if (bytesToRead <= 0) | ||
| break; | ||
|
|
||
| // Read up to 2048 bytes from base CDROM stream | ||
| int bytesRead = _baseStream.Read(buffer, offset + totalRead, bytesToRead); | ||
|
|
||
| // Update state for base stream | ||
| _position = _baseStream.Position; | ||
| if (readEntireSector) | ||
| _position += (_baseSectorSize - _userDataEnd) + _userDataStart; | ||
|
|
||
| // Update state for ISO stream | ||
| totalRead += bytesRead; | ||
| remaining -= bytesRead; | ||
|
|
||
| if (bytesRead == 0) | ||
| break; | ||
| } | ||
|
|
||
| return totalRead; | ||
| } | ||
|
|
||
| public override long Seek(long offset, SeekOrigin origin) | ||
| { | ||
| // Get the intended position for the ISO9660 stream | ||
| long targetPosition; | ||
| switch (origin) | ||
| { | ||
| case SeekOrigin.Begin: | ||
| targetPosition = offset; | ||
| break; | ||
| case SeekOrigin.Current: | ||
| targetPosition = Position + offset; | ||
| break; | ||
| case SeekOrigin.End: | ||
| targetPosition = Length + offset; | ||
| break; | ||
| default: | ||
| throw new ArgumentException("Invalid SeekOrigin.", nameof(origin)); | ||
| } | ||
|
|
||
| // Get the number of ISO sectors before current position | ||
| long newPosition = (targetPosition / _isoSectorSize) * _baseSectorSize; | ||
|
|
||
| // Set the current sector's mode and user data location | ||
| SetState(newPosition); | ||
|
|
||
| // Add the within-sector position | ||
| newPosition += _userDataStart + (targetPosition % _isoSectorSize); | ||
|
|
||
| if (newPosition < 0 || newPosition > _baseStream.Length) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(offset), "Attempted to seek outside the stream boundaries."); | ||
| } | ||
|
|
||
| _position = _baseStream.Seek(newPosition, SeekOrigin.Begin); | ||
| return Position; | ||
| } | ||
|
|
||
| private void SetState(long sectorLocation) | ||
| { | ||
| long oldPosition = _baseStream.Position; | ||
| long modePosition = (sectorLocation - sectorLocation % _baseSectorSize) + 15; | ||
| _baseStream.Seek(modePosition, SeekOrigin.Begin); | ||
| byte modeByte = _baseStream.ReadByteValue(); | ||
| if (modeByte == 0) | ||
| _currentMode = SectorMode.MODE0; | ||
| else if (modeByte == 1) | ||
| _currentMode = SectorMode.MODE1; | ||
| else if (modeByte == 2) | ||
| { | ||
| _baseStream.Seek(modePosition + 3, SeekOrigin.Begin); | ||
| byte submode = _baseStream.ReadByteValue(); | ||
| if ((submode & 0x20) == 0x20) | ||
| _currentMode = SectorMode.MODE2_FORM2; | ||
| else | ||
| _currentMode = SectorMode.MODE2_FORM1; | ||
| } | ||
| else | ||
| _currentMode = SectorMode.UNKNOWN; | ||
|
|
||
| // Set the user data location variables | ||
| switch (_currentMode) | ||
| { | ||
| case SectorMode.MODE1: | ||
| _userDataStart = 16; | ||
| _userDataEnd = 2064; | ||
| //_isoSectorSize = 2048; | ||
| break; | ||
|
|
||
| case SectorMode.MODE2_FORM1: | ||
| _userDataStart = 24; | ||
| _userDataEnd = 2072; | ||
| //_isoSectorSize = 2048; | ||
| break; | ||
|
|
||
| case SectorMode.MODE2_FORM2: | ||
| _userDataStart = 24; | ||
| _userDataEnd = 2072; | ||
| // TODO: Support flexible sector length | ||
| //_userDataEnd = 2348; | ||
| //_isoSectorSize = 2324; | ||
| break; | ||
|
|
||
| case SectorMode.MODE0: | ||
| case SectorMode.MODE2: | ||
| _userDataStart = 16; | ||
| _userDataEnd = 2064; | ||
| // TODO: Support flexible sector length | ||
| //_userDataEnd = 2352; | ||
| //_isoSectorSize = 2336; | ||
| break; | ||
|
|
||
| case SectorMode.UNKNOWN: | ||
| _userDataStart = 16; | ||
| _userDataEnd = 2064; | ||
| //_isoSectorSize = 2048; | ||
| break; | ||
| } | ||
|
|
||
| _baseStream.Seek(oldPosition, SeekOrigin.Begin); | ||
|
|
||
| return; | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,16 @@ | ||
| namespace SabreTools.Data.Models.CDROM | ||
| { | ||
| /// <summary> | ||
| /// A CD-ROM disc image, made up of multiple data tracks, ISO 10149 / ECMA-130 | ||
| /// Specifically not a mixed-mode CD disc image, pure CD-ROM disc | ||
| /// A CD-ROM disc image, made up of data sectors, ISO 10149 / ECMA-130 | ||
| /// Intentionally not a mixed-mode CD disc image, pure CD-ROM disc (no audio sectors) | ||
| /// This model intentionally does not take tracks into consideration | ||
| /// </summary> | ||
| /// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/> | ||
| public sealed class CDROM | ||
| { | ||
| /// <summary> | ||
| /// CD-ROM data tracks | ||
| /// CD-ROM data sectors | ||
| /// </summary> | ||
| public DataTrack[] Tracks { get; set; } = []; | ||
| public DataSector[] Sectors { get; set; } = []; | ||
| } | ||
| } |
4 changes: 2 additions & 2 deletions
4
...ools.Serialization/Models/CDROM/Sector.cs → ....Serialization/Models/CDROM/DataSector.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.