mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-02-12 05:35:31 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
327fd68f04 | ||
|
|
f12d48861f | ||
|
|
5a613be9bf | ||
|
|
226031f3bd | ||
|
|
e5edf43624 | ||
|
|
f2019b7ac4 | ||
|
|
0efb6d08e7 | ||
|
|
7ed1717f56 |
@@ -6,14 +6,21 @@ namespace SabreTools.Serialization.Files
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public MetadataFile Deserialize(string path)
|
||||
public MetadataFile Deserialize(string path) => Deserialize(path, true);
|
||||
#else
|
||||
public MetadataFile? Deserialize(string? path)
|
||||
public MetadataFile? Deserialize(string? path) => Deserialize(path, true);
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public MetadataFile Deserialize(string path, bool quotes)
|
||||
#else
|
||||
public MetadataFile? Deserialize(string? path, bool quotes)
|
||||
#endif
|
||||
{
|
||||
using (var stream = PathProcessor.OpenStream(path))
|
||||
{
|
||||
return new Streams.ClrMamePro().Deserialize(stream);
|
||||
return new Streams.ClrMamePro().Deserialize(stream, quotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
625
Files/CueSheet.Deserializer.cs
Normal file
625
Files/CueSheet.Deserializer.cs
Normal file
@@ -0,0 +1,625 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SabreTools.Models.CueSheets;
|
||||
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class CueSheet : IFileSerializer<Models.CueSheets.CueSheet>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public Models.CueSheets.CueSheet Deserialize(string path)
|
||||
#else
|
||||
public Models.CueSheets.CueSheet? Deserialize(string? path)
|
||||
#endif
|
||||
{
|
||||
// Check that the file exists
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
return null;
|
||||
|
||||
// Check the extension
|
||||
string ext = Path.GetExtension(path).TrimStart('.');
|
||||
if (!string.Equals(ext, "cue", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(ext, "txt", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create the holding objects
|
||||
var cueSheet = new Models.CueSheets.CueSheet();
|
||||
var cueFiles = new List<CueFile>();
|
||||
|
||||
// Open the file and begin reading
|
||||
string[] cueLines = File.ReadAllLines(path);
|
||||
for (int i = 0; i < cueLines.Length; i++)
|
||||
{
|
||||
string line = cueLines[i].Trim();
|
||||
|
||||
// http://stackoverflow.com/questions/554013/regular-expression-to-split-on-spaces-unless-in-quotes
|
||||
string[] splitLine = Regex
|
||||
.Matches(line, @"[^\s""]+|""[^""]*""")
|
||||
.Cast<Match>()
|
||||
.Select(m => m.Groups[0].Value)
|
||||
.ToArray();
|
||||
|
||||
// If we have an empty line, we skip
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
switch (splitLine[0])
|
||||
{
|
||||
// Read comments
|
||||
case "REM":
|
||||
// We ignore all comments for now
|
||||
break;
|
||||
|
||||
// Read MCN
|
||||
case "CATALOG":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"CATALOG line malformed: {line}");
|
||||
|
||||
cueSheet.Catalog = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read external CD-Text file path
|
||||
case "CDTEXTFILE":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"CDTEXTFILE line malformed: {line}");
|
||||
|
||||
cueSheet.CdTextFile = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced performer
|
||||
case "PERFORMER":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"PERFORMER line malformed: {line}");
|
||||
|
||||
cueSheet.Performer = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced songwriter
|
||||
case "SONGWRITER":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"SONGWRITER line malformed: {line}");
|
||||
|
||||
cueSheet.Songwriter = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced title
|
||||
case "TITLE":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"TITLE line malformed: {line}");
|
||||
|
||||
cueSheet.Title = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read file information
|
||||
case "FILE":
|
||||
if (splitLine.Length < 3)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
var file = CreateCueFile(splitLine[1], splitLine[2], cueLines, ref i);
|
||||
if (file == default)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
cueFiles.Add(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cueSheet.Files = cueFiles.ToArray();
|
||||
return cueSheet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill a FILE from an array of lines
|
||||
/// </summary>
|
||||
/// <param name="fileName">File name to set</param>
|
||||
/// <param name="fileType">File type to set</param>
|
||||
/// <param name="cueLines">Lines array to pull from</param>
|
||||
/// <param name="i">Reference to index in array</param>
|
||||
#if NET48
|
||||
private static CueFile CreateCueFile(string fileName, string fileType, string[] cueLines, ref int i)
|
||||
#else
|
||||
private static CueFile? CreateCueFile(string fileName, string fileType, string[]? cueLines, ref int i)
|
||||
#endif
|
||||
{
|
||||
// Check the required parameters
|
||||
if (cueLines == null)
|
||||
throw new ArgumentNullException(nameof(cueLines));
|
||||
else if (i < 0 || i > cueLines.Length)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
// Create the holding objects
|
||||
var cueFile = new CueFile();
|
||||
var cueTracks = new List<CueTrack>();
|
||||
|
||||
// Set the current fields
|
||||
cueFile.FileName = fileName.Trim('"');
|
||||
cueFile.FileType = GetFileType(fileType);
|
||||
|
||||
// Increment to start
|
||||
i++;
|
||||
|
||||
for (; i < cueLines.Length; i++)
|
||||
{
|
||||
string line = cueLines[i].Trim();
|
||||
string[] splitLine = line.Split(' ');
|
||||
|
||||
// If we have an empty line, we skip
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
switch (splitLine[0])
|
||||
{
|
||||
// Read comments
|
||||
case "REM":
|
||||
// We ignore all comments for now
|
||||
break;
|
||||
|
||||
// Read track information
|
||||
case "TRACK":
|
||||
if (splitLine.Length < 3)
|
||||
throw new FormatException($"TRACK line malformed: {line}");
|
||||
|
||||
var track = CreateCueTrack(splitLine[1], splitLine[2], cueLines, ref i);
|
||||
if (track == default)
|
||||
throw new FormatException($"TRACK line malformed: {line}");
|
||||
|
||||
cueTracks.Add(track);
|
||||
break;
|
||||
|
||||
// Default means return
|
||||
default:
|
||||
i--;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
cueFile.Tracks = cueTracks.ToArray();
|
||||
return cueFile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill a TRACK from an array of lines
|
||||
/// </summary>
|
||||
/// <param name="number">Number to set</param>
|
||||
/// <param name="dataType">Data type to set</param>
|
||||
/// <param name="cueLines">Lines array to pull from</param>
|
||||
/// <param name="i">Reference to index in array</param>
|
||||
#if NET48
|
||||
private static CueTrack CreateCueTrack(string number, string dataType, string[] cueLines, ref int i)
|
||||
#else
|
||||
private static CueTrack? CreateCueTrack(string number, string dataType, string[]? cueLines, ref int i)
|
||||
#endif
|
||||
{
|
||||
// Check the required parameters
|
||||
if (cueLines == null)
|
||||
throw new ArgumentNullException(nameof(cueLines));
|
||||
else if (i < 0 || i > cueLines.Length)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
// Set the current fields
|
||||
if (!int.TryParse(number, out int parsedNumber))
|
||||
throw new ArgumentException($"Number was not a number: {number}");
|
||||
else if (parsedNumber < 1 || parsedNumber > 99)
|
||||
throw new IndexOutOfRangeException($"Index must be between 1 and 99: {parsedNumber}");
|
||||
|
||||
// Create the holding objects
|
||||
var cueTrack = new CueTrack();
|
||||
var cueIndices = new List<CueIndex>();
|
||||
|
||||
cueTrack.Number = parsedNumber;
|
||||
cueTrack.DataType = GetDataType(dataType);
|
||||
|
||||
// Increment to start
|
||||
i++;
|
||||
|
||||
for (; i < cueLines.Length; i++)
|
||||
{
|
||||
string line = cueLines[i].Trim();
|
||||
string[] splitLine = line.Split(' ');
|
||||
|
||||
// If we have an empty line, we skip
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
switch (splitLine[0])
|
||||
{
|
||||
// Read comments
|
||||
case "REM":
|
||||
// We ignore all comments for now
|
||||
break;
|
||||
|
||||
// Read flag information
|
||||
case "FLAGS":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"FLAGS line malformed: {line}");
|
||||
|
||||
cueTrack.Flags = GetFlags(splitLine);
|
||||
break;
|
||||
|
||||
// Read International Standard Recording Code
|
||||
case "ISRC":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"ISRC line malformed: {line}");
|
||||
|
||||
cueTrack.ISRC = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced performer
|
||||
case "PERFORMER":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"PERFORMER line malformed: {line}");
|
||||
|
||||
cueTrack.Performer = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced songwriter
|
||||
case "SONGWRITER":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"SONGWRITER line malformed: {line}");
|
||||
|
||||
cueTrack.Songwriter = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced title
|
||||
case "TITLE":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"TITLE line malformed: {line}");
|
||||
|
||||
cueTrack.Title = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read pregap information
|
||||
case "PREGAP":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"PREGAP line malformed: {line}");
|
||||
|
||||
var pregap = CreatePreGap(splitLine[1]);
|
||||
if (pregap == default)
|
||||
throw new FormatException($"PREGAP line malformed: {line}");
|
||||
|
||||
cueTrack.PreGap = pregap;
|
||||
break;
|
||||
|
||||
// Read index information
|
||||
case "INDEX":
|
||||
if (splitLine.Length < 3)
|
||||
throw new FormatException($"INDEX line malformed: {line}");
|
||||
|
||||
var index = CreateCueIndex(splitLine[1], splitLine[2]);
|
||||
if (index == default)
|
||||
throw new FormatException($"INDEX line malformed: {line}");
|
||||
|
||||
cueIndices.Add(index);
|
||||
break;
|
||||
|
||||
// Read postgap information
|
||||
case "POSTGAP":
|
||||
if (splitLine.Length < 2)
|
||||
throw new FormatException($"POSTGAP line malformed: {line}");
|
||||
|
||||
var postgap = CreatePostGap(splitLine[1]);
|
||||
if (postgap == default)
|
||||
throw new FormatException($"POSTGAP line malformed: {line}");
|
||||
|
||||
cueTrack.PostGap = postgap;
|
||||
break;
|
||||
|
||||
// Default means return
|
||||
default:
|
||||
i--;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
cueTrack.Indices = cueIndices.ToArray();
|
||||
return cueTrack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a PREGAP from a mm:ss:ff length
|
||||
/// </summary>
|
||||
/// <param name="length">String to get length information from</param>
|
||||
#if NET48
|
||||
private static PreGap CreatePreGap(string length)
|
||||
#else
|
||||
private static PreGap CreatePreGap(string? length)
|
||||
#endif
|
||||
{
|
||||
// Ignore empty lines
|
||||
if (string.IsNullOrWhiteSpace(length))
|
||||
throw new ArgumentException("Length was null or whitespace");
|
||||
|
||||
// Ignore lines that don't contain the correct information
|
||||
if (length.Length != 8 || length.Count(c => c == ':') != 2)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
// Split the line
|
||||
string[] splitLength = length.Split(':');
|
||||
if (splitLength.Length != 3)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
// Parse the lengths
|
||||
int[] lengthSegments = new int[3];
|
||||
|
||||
// Minutes
|
||||
if (!int.TryParse(splitLength[0], out lengthSegments[0]))
|
||||
throw new FormatException($"Minutes segment was not a number: {splitLength[0]}");
|
||||
else if (lengthSegments[0] < 0)
|
||||
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
|
||||
|
||||
// Seconds
|
||||
if (!int.TryParse(splitLength[1], out lengthSegments[1]))
|
||||
throw new FormatException($"Seconds segment was not a number: {splitLength[1]}");
|
||||
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
|
||||
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
|
||||
|
||||
// Frames
|
||||
if (!int.TryParse(splitLength[2], out lengthSegments[2]))
|
||||
throw new FormatException($"Frames segment was not a number: {splitLength[2]}");
|
||||
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
|
||||
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
|
||||
|
||||
// Set the values
|
||||
var preGap = new PreGap
|
||||
{
|
||||
Minutes = lengthSegments[0],
|
||||
Seconds = lengthSegments[1],
|
||||
Frames = lengthSegments[2],
|
||||
};
|
||||
return preGap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill a INDEX from an array of lines
|
||||
/// </summary>
|
||||
/// <param name="index">Index to set</param>
|
||||
/// <param name="startTime">Start time to set</param>
|
||||
#if NET48
|
||||
private static CueIndex CreateCueIndex(string index, string startTime)
|
||||
#else
|
||||
private static CueIndex CreateCueIndex(string? index, string? startTime)
|
||||
#endif
|
||||
{
|
||||
// Set the current fields
|
||||
if (!int.TryParse(index, out int parsedIndex))
|
||||
throw new ArgumentException($"Index was not a number: {index}");
|
||||
else if (parsedIndex < 0 || parsedIndex > 99)
|
||||
throw new IndexOutOfRangeException($"Index must be between 0 and 99: {parsedIndex}");
|
||||
|
||||
// Ignore empty lines
|
||||
if (string.IsNullOrWhiteSpace(startTime))
|
||||
throw new ArgumentException("Start time was null or whitespace");
|
||||
|
||||
// Ignore lines that don't contain the correct information
|
||||
if (startTime.Length != 8 || startTime.Count(c => c == ':') != 2)
|
||||
throw new FormatException($"Start time was not in a recognized format: {startTime}");
|
||||
|
||||
// Split the line
|
||||
string[] splitTime = startTime.Split(':');
|
||||
if (splitTime.Length != 3)
|
||||
throw new FormatException($"Start time was not in a recognized format: {startTime}");
|
||||
|
||||
// Parse the lengths
|
||||
int[] lengthSegments = new int[3];
|
||||
|
||||
// Minutes
|
||||
if (!int.TryParse(splitTime[0], out lengthSegments[0]))
|
||||
throw new FormatException($"Minutes segment was not a number: {splitTime[0]}");
|
||||
else if (lengthSegments[0] < 0)
|
||||
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
|
||||
|
||||
// Seconds
|
||||
if (!int.TryParse(splitTime[1], out lengthSegments[1]))
|
||||
throw new FormatException($"Seconds segment was not a number: {splitTime[1]}");
|
||||
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
|
||||
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
|
||||
|
||||
// Frames
|
||||
if (!int.TryParse(splitTime[2], out lengthSegments[2]))
|
||||
throw new FormatException($"Frames segment was not a number: {splitTime[2]}");
|
||||
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
|
||||
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
|
||||
|
||||
// Set the values
|
||||
var cueIndex = new CueIndex
|
||||
{
|
||||
Index = parsedIndex,
|
||||
Minutes = lengthSegments[0],
|
||||
Seconds = lengthSegments[1],
|
||||
Frames = lengthSegments[2],
|
||||
};
|
||||
return cueIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a POSTGAP from a mm:ss:ff length
|
||||
/// </summary>
|
||||
/// <param name="length">String to get length information from</param>
|
||||
#if NET48
|
||||
private static PostGap CreatePostGap(string length)
|
||||
#else
|
||||
private static PostGap CreatePostGap(string? length)
|
||||
#endif
|
||||
{
|
||||
// Ignore empty lines
|
||||
if (string.IsNullOrWhiteSpace(length))
|
||||
throw new ArgumentException("Length was null or whitespace");
|
||||
|
||||
// Ignore lines that don't contain the correct information
|
||||
if (length.Length != 8 || length.Count(c => c == ':') != 2)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
// Split the line
|
||||
string[] splitLength = length.Split(':');
|
||||
if (splitLength.Length != 3)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
// Parse the lengths
|
||||
int[] lengthSegments = new int[3];
|
||||
|
||||
// Minutes
|
||||
if (!int.TryParse(splitLength[0], out lengthSegments[0]))
|
||||
throw new FormatException($"Minutes segment was not a number: {splitLength[0]}");
|
||||
else if (lengthSegments[0] < 0)
|
||||
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
|
||||
|
||||
// Seconds
|
||||
if (!int.TryParse(splitLength[1], out lengthSegments[1]))
|
||||
throw new FormatException($"Seconds segment was not a number: {splitLength[1]}");
|
||||
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
|
||||
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
|
||||
|
||||
// Frames
|
||||
if (!int.TryParse(splitLength[2], out lengthSegments[2]))
|
||||
throw new FormatException($"Frames segment was not a number: {splitLength[2]}");
|
||||
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
|
||||
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
|
||||
|
||||
// Set the values
|
||||
var postGap = new PostGap
|
||||
{
|
||||
Minutes = lengthSegments[0],
|
||||
Seconds = lengthSegments[1],
|
||||
Frames = lengthSegments[2],
|
||||
};
|
||||
return postGap;
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Get the file type from a given string
|
||||
/// </summary>
|
||||
/// <param name="fileType">String to get value from</param>
|
||||
/// <returns>CueFileType, if possible</returns>
|
||||
#if NET48
|
||||
private static CueFileType GetFileType(string fileType)
|
||||
#else
|
||||
private static CueFileType GetFileType(string? fileType)
|
||||
#endif
|
||||
{
|
||||
switch (fileType?.ToLowerInvariant())
|
||||
{
|
||||
case "binary":
|
||||
return CueFileType.BINARY;
|
||||
|
||||
case "motorola":
|
||||
return CueFileType.MOTOROLA;
|
||||
|
||||
case "aiff":
|
||||
return CueFileType.AIFF;
|
||||
|
||||
case "wave":
|
||||
return CueFileType.WAVE;
|
||||
|
||||
case "mp3":
|
||||
return CueFileType.MP3;
|
||||
|
||||
default:
|
||||
return CueFileType.BINARY;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the data type from a given string
|
||||
/// </summary>
|
||||
/// <param name="dataType">String to get value from</param>
|
||||
/// <returns>CueTrackDataType, if possible (default AUDIO)</returns>
|
||||
#if NET48
|
||||
private static CueTrackDataType GetDataType(string dataType)
|
||||
#else
|
||||
private static CueTrackDataType GetDataType(string? dataType)
|
||||
#endif
|
||||
{
|
||||
switch (dataType?.ToLowerInvariant())
|
||||
{
|
||||
case "audio":
|
||||
return CueTrackDataType.AUDIO;
|
||||
|
||||
case "cdg":
|
||||
return CueTrackDataType.CDG;
|
||||
|
||||
case "mode1/2048":
|
||||
return CueTrackDataType.MODE1_2048;
|
||||
|
||||
case "mode1/2352":
|
||||
return CueTrackDataType.MODE1_2352;
|
||||
|
||||
case "mode2/2336":
|
||||
return CueTrackDataType.MODE2_2336;
|
||||
|
||||
case "mode2/2352":
|
||||
return CueTrackDataType.MODE2_2352;
|
||||
|
||||
case "cdi/2336":
|
||||
return CueTrackDataType.CDI_2336;
|
||||
|
||||
case "cdi/2352":
|
||||
return CueTrackDataType.CDI_2352;
|
||||
|
||||
default:
|
||||
return CueTrackDataType.AUDIO;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the flag value for an array of strings
|
||||
/// </summary>
|
||||
/// <param name="flagStrings">Possible flags as strings</param>
|
||||
/// <returns>CueTrackFlag value representing the strings, if possible</returns>
|
||||
#if NET48
|
||||
private static CueTrackFlag GetFlags(string[] flagStrings)
|
||||
#else
|
||||
private static CueTrackFlag GetFlags(string?[]? flagStrings)
|
||||
#endif
|
||||
{
|
||||
CueTrackFlag flag = 0;
|
||||
if (flagStrings == null)
|
||||
return flag;
|
||||
|
||||
#if NET48
|
||||
foreach (string flagString in flagStrings)
|
||||
#else
|
||||
foreach (string? flagString in flagStrings)
|
||||
#endif
|
||||
{
|
||||
switch (flagString?.ToLowerInvariant())
|
||||
{
|
||||
case "flags":
|
||||
// No-op since this is the start of the line
|
||||
break;
|
||||
|
||||
case "dcp":
|
||||
flag |= CueTrackFlag.DCP;
|
||||
break;
|
||||
|
||||
case "4ch":
|
||||
flag |= CueTrackFlag.FourCH;
|
||||
break;
|
||||
|
||||
case "pre":
|
||||
flag |= CueTrackFlag.PRE;
|
||||
break;
|
||||
|
||||
case "scms":
|
||||
flag |= CueTrackFlag.SCMS;
|
||||
break;
|
||||
|
||||
case "data":
|
||||
flag |= CueTrackFlag.DATA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
30
Files/CueSheet.Serializer.cs
Normal file
30
Files/CueSheet.Serializer.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class CueSheet : IFileSerializer<Models.CueSheets.CueSheet>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(Models.CueSheets.CueSheet obj, string path)
|
||||
#else
|
||||
public bool Serialize(Models.CueSheets.CueSheet? obj, string? path)
|
||||
#endif
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return false;
|
||||
|
||||
using (var stream = new Streams.CueSheet().Serialize(obj))
|
||||
{
|
||||
if (stream == null)
|
||||
return false;
|
||||
|
||||
using (var fs = File.OpenWrite(path))
|
||||
{
|
||||
stream.CopyTo(fs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,21 @@ namespace SabreTools.Serialization.Files
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public Models.Hashfile.Hashfile Deserialize(string path)
|
||||
public Models.Hashfile.Hashfile Deserialize(string path) => Deserialize(path, Hash.CRC);
|
||||
#else
|
||||
public Models.Hashfile.Hashfile? Deserialize(string? path)
|
||||
public Models.Hashfile.Hashfile? Deserialize(string? path) => Deserialize(path, Hash.CRC);
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public Models.Hashfile.Hashfile Deserialize(string path, Hash hash)
|
||||
#else
|
||||
public Models.Hashfile.Hashfile? Deserialize(string? path, Hash hash)
|
||||
#endif
|
||||
{
|
||||
using (var stream = PathProcessor.OpenStream(path))
|
||||
{
|
||||
return new Streams.Hashfile().Deserialize(stream);
|
||||
return new Streams.Hashfile().Deserialize(stream, hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,22 @@ namespace SabreTools.Serialization.Files
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(Models.Hashfile.Hashfile obj, string path)
|
||||
public bool Serialize(Models.Hashfile.Hashfile obj, string path) => Serialize(obj, path, Hash.CRC);
|
||||
#else
|
||||
public bool Serialize(Models.Hashfile.Hashfile? obj, string? path)
|
||||
public bool Serialize(Models.Hashfile.Hashfile? obj, string? path) => Serialize(obj, path, Hash.CRC);
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(Models.Hashfile.Hashfile obj, string path, Hash hash)
|
||||
#else
|
||||
public bool Serialize(Models.Hashfile.Hashfile? obj, string? path, Hash hash)
|
||||
#endif
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return false;
|
||||
|
||||
using (var stream = new Streams.Hashfile().Serialize(obj))
|
||||
using (var stream = new Streams.Hashfile().Serialize(obj, hash))
|
||||
{
|
||||
if (stream == null)
|
||||
return false;
|
||||
|
||||
20
Files/PIC.Deserializer.cs
Normal file
20
Files/PIC.Deserializer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using SabreTools.Models.PIC;
|
||||
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class PIC : IFileSerializer<DiscInformation>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public DiscInformation Deserialize(string path)
|
||||
#else
|
||||
public DiscInformation? Deserialize(string? path)
|
||||
#endif
|
||||
{
|
||||
using (var stream = PathProcessor.OpenStream(path))
|
||||
{
|
||||
return new Streams.PIC().Deserialize(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Files/PIC.Serializer.cs
Normal file
30
Files/PIC.Serializer.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using SabreTools.Models.PIC;
|
||||
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class PIC : IFileSerializer<DiscInformation>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(DiscInformation obj, string path)
|
||||
#else
|
||||
public bool Serialize(DiscInformation? obj, string? path)
|
||||
#endif
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return false;
|
||||
|
||||
using (var stream = new Streams.PIC().Serialize(obj))
|
||||
{
|
||||
if (stream == null)
|
||||
return false;
|
||||
|
||||
using (var fs = System.IO.File.OpenWrite(path))
|
||||
{
|
||||
stream.CopyTo(fs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,21 @@ namespace SabreTools.Serialization.Files
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public MetadataFile Deserialize(string path)
|
||||
public MetadataFile Deserialize(string path) => Deserialize(path, ',');
|
||||
#else
|
||||
public MetadataFile? Deserialize(string? path)
|
||||
public MetadataFile? Deserialize(string? path) => Deserialize(path, ',');
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public MetadataFile Deserialize(string path, char delim)
|
||||
#else
|
||||
public MetadataFile? Deserialize(string? path, char delim)
|
||||
#endif
|
||||
{
|
||||
using (var stream = PathProcessor.OpenStream(path))
|
||||
{
|
||||
return new Streams.SeparatedValue().Deserialize(stream);
|
||||
return new Streams.SeparatedValue().Deserialize(stream, delim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,22 @@ namespace SabreTools.Serialization.Files
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(MetadataFile obj, string path)
|
||||
public bool Serialize(MetadataFile obj, string path) => Serialize(obj, path, ',');
|
||||
#else
|
||||
public bool Serialize(MetadataFile? obj, string? path)
|
||||
public bool Serialize(MetadataFile? obj, string? path) => Serialize(obj, path, ',');
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(MetadataFile obj, string path, char delim)
|
||||
#else
|
||||
public bool Serialize(MetadataFile? obj, string? path, char delim)
|
||||
#endif
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return false;
|
||||
|
||||
using (var stream = new Streams.SeparatedValue().Serialize(obj))
|
||||
using (var stream = new Streams.SeparatedValue().Serialize(obj, delim))
|
||||
{
|
||||
if (stream == null)
|
||||
return false;
|
||||
|
||||
214
Files/XMID.Deserializer.cs
Normal file
214
Files/XMID.Deserializer.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class XMID : IFileSerializer<Models.Xbox.XMID>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>This treats the input path like a parseable string</remarks>
|
||||
#if NET48
|
||||
public Models.Xbox.XMID Deserialize(string path)
|
||||
#else
|
||||
public Models.Xbox.XMID? Deserialize(string? path)
|
||||
#endif
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return null;
|
||||
|
||||
string xmid = path.TrimEnd('\0');
|
||||
if (string.IsNullOrWhiteSpace(xmid))
|
||||
return null;
|
||||
|
||||
return ParseXMID(xmid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse an XGD2/3 XMID string
|
||||
/// </summary>
|
||||
/// <param name="xmidString">XMID string to attempt to parse</param>
|
||||
/// <returns>Filled XMID on success, null on error</returns>
|
||||
#if NET48
|
||||
private static Models.Xbox.XMID ParseXMID(string xmidString)
|
||||
#else
|
||||
private static Models.Xbox.XMID? ParseXMID(string? xmidString)
|
||||
#endif
|
||||
{
|
||||
if (xmidString == null || xmidString.Length != 8)
|
||||
return null;
|
||||
|
||||
var xmid = new Models.Xbox.XMID();
|
||||
|
||||
xmid.PublisherIdentifier = xmidString.Substring(0, 2);
|
||||
if (string.IsNullOrEmpty(PublisherName(xmid)))
|
||||
return null;
|
||||
|
||||
xmid.GameID = xmidString.Substring(2, 3);
|
||||
xmid.VersionNumber = xmidString.Substring(5, 2);
|
||||
xmid.RegionIdentifier = xmidString[7];
|
||||
if (InternalRegion(xmid) == null)
|
||||
return null;
|
||||
|
||||
return xmid;
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name derived from the publisher identifier
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public static string PublisherName(Models.Xbox.XMID xmid) => GetPublisher(xmid.PublisherIdentifier);
|
||||
#else
|
||||
public static string? PublisherName(Models.Xbox.XMID xmid) => GetPublisher(xmid.PublisherIdentifier);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Internally represented region
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public static string InternalRegion(Models.Xbox.XMID xmid) => GetRegion(xmid.RegionIdentifier);
|
||||
#else
|
||||
public static string? InternalRegion(Models.Xbox.XMID xmid) => GetRegion(xmid.RegionIdentifier);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get the full name of the publisher from the 2-character identifier
|
||||
/// </summary>
|
||||
/// <param name="publisherIdentifier">Case-sensitive 2-character identifier</param>
|
||||
/// <returns>Publisher name, if possible</returns>
|
||||
/// <see cref="https://xboxdevwiki.net/Xbe#Title_ID"/>
|
||||
#if NET48
|
||||
private static string GetPublisher(string publisherIdentifier)
|
||||
#else
|
||||
private static string? GetPublisher(string? publisherIdentifier)
|
||||
#endif
|
||||
{
|
||||
switch (publisherIdentifier)
|
||||
{
|
||||
case "AC": return "Acclaim Entertainment";
|
||||
case "AH": return "ARUSH Entertainment";
|
||||
case "AQ": return "Aqua System";
|
||||
case "AS": return "ASK";
|
||||
case "AT": return "Atlus";
|
||||
case "AV": return "Activision";
|
||||
case "AY": return "Aspyr Media";
|
||||
case "BA": return "Bandai";
|
||||
case "BL": return "Black Box";
|
||||
case "BM": return "BAM! Entertainment";
|
||||
case "BR": return "Broccoli Co.";
|
||||
case "BS": return "Bethesda Softworks";
|
||||
case "BU": return "Bunkasha Co.";
|
||||
case "BV": return "Buena Vista Games";
|
||||
case "BW": return "BBC Multimedia";
|
||||
case "BZ": return "Blizzard";
|
||||
case "CC": return "Capcom";
|
||||
case "CK": return "Kemco Corporation"; // TODO: Confirm
|
||||
case "CM": return "Codemasters";
|
||||
case "CV": return "Crave Entertainment";
|
||||
case "DC": return "DreamCatcher Interactive";
|
||||
case "DX": return "Davilex";
|
||||
case "EA": return "Electronic Arts (EA)";
|
||||
case "EC": return "Encore inc";
|
||||
case "EL": return "Enlight Software";
|
||||
case "EM": return "Empire Interactive";
|
||||
case "ES": return "Eidos Interactive";
|
||||
case "FI": return "Fox Interactive";
|
||||
case "FS": return "From Software";
|
||||
case "GE": return "Genki Co.";
|
||||
case "GV": return "Groove Games";
|
||||
case "HE": return "Tru Blu (Entertainment division of Home Entertainment Suppliers)";
|
||||
case "HP": return "Hip games";
|
||||
case "HU": return "Hudson Soft";
|
||||
case "HW": return "Highwaystar";
|
||||
case "IA": return "Mad Catz Interactive";
|
||||
case "IF": return "Idea Factory";
|
||||
case "IG": return "Infogrames";
|
||||
case "IL": return "Interlex Corporation";
|
||||
case "IM": return "Imagine Media";
|
||||
case "IO": return "Ignition Entertainment";
|
||||
case "IP": return "Interplay Entertainment";
|
||||
case "IX": return "InXile Entertainment"; // TODO: Confirm
|
||||
case "JA": return "Jaleco";
|
||||
case "JW": return "JoWooD";
|
||||
case "KB": return "Kemco"; // TODO: Confirm
|
||||
case "KI": return "Kids Station Inc."; // TODO: Confirm
|
||||
case "KN": return "Konami";
|
||||
case "KO": return "KOEI";
|
||||
case "KU": return "Kobi and / or GAE (formerly Global A Entertainment)"; // TODO: Confirm
|
||||
case "LA": return "LucasArts";
|
||||
case "LS": return "Black Bean Games (publishing arm of Leader S.p.A.)";
|
||||
case "MD": return "Metro3D";
|
||||
case "ME": return "Medix";
|
||||
case "MI": return "Microïds";
|
||||
case "MJ": return "Majesco Entertainment";
|
||||
case "MM": return "Myelin Media";
|
||||
case "MP": return "MediaQuest"; // TODO: Confirm
|
||||
case "MS": return "Microsoft Game Studios";
|
||||
case "MW": return "Midway Games";
|
||||
case "MX": return "Empire Interactive"; // TODO: Confirm
|
||||
case "NK": return "NewKidCo";
|
||||
case "NL": return "NovaLogic";
|
||||
case "NM": return "Namco";
|
||||
case "OX": return "Oxygen Interactive";
|
||||
case "PC": return "Playlogic Entertainment";
|
||||
case "PL": return "Phantagram Co., Ltd.";
|
||||
case "RA": return "Rage";
|
||||
case "SA": return "Sammy";
|
||||
case "SC": return "SCi Games";
|
||||
case "SE": return "SEGA";
|
||||
case "SN": return "SNK";
|
||||
case "SS": return "Simon & Schuster";
|
||||
case "SU": return "Success Corporation";
|
||||
case "SW": return "Swing! Deutschland";
|
||||
case "TA": return "Takara";
|
||||
case "TC": return "Tecmo";
|
||||
case "TD": return "The 3DO Company (or just 3DO)";
|
||||
case "TK": return "Takuyo";
|
||||
case "TM": return "TDK Mediactive";
|
||||
case "TQ": return "THQ";
|
||||
case "TS": return "Titus Interactive";
|
||||
case "TT": return "Take-Two Interactive Software";
|
||||
case "US": return "Ubisoft";
|
||||
case "VC": return "Victor Interactive Software";
|
||||
case "VN": return "Vivendi Universal (just took Interplays publishing rights)"; // TODO: Confirm
|
||||
case "VU": return "Vivendi Universal Games";
|
||||
case "VV": return "Vivendi Universal Games"; // TODO: Confirm
|
||||
case "WE": return "Wanadoo Edition";
|
||||
case "WR": return "Warner Bros. Interactive Entertainment"; // TODO: Confirm
|
||||
case "XI": return "XPEC Entertainment and Idea Factory";
|
||||
case "XK": return "Xbox kiosk disk?"; // TODO: Confirm
|
||||
case "XL": return "Xbox special bundled or live demo disk?"; // TODO: Confirm
|
||||
case "XM": return "Evolved Games"; // TODO: Confirm
|
||||
case "XP": return "XPEC Entertainment";
|
||||
case "XR": return "Panorama";
|
||||
case "YB": return "YBM Sisa (South-Korea)";
|
||||
case "ZD": return "Zushi Games (formerly Zoo Digital Publishing)";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the region based on the XGD serial character
|
||||
/// </summary>
|
||||
/// <param name="region">Character denoting the region</param>
|
||||
/// <returns>Region, if possible</returns>
|
||||
#if NET48
|
||||
private static string GetRegion(char region)
|
||||
#else
|
||||
private static string? GetRegion(char region)
|
||||
#endif
|
||||
{
|
||||
switch (region)
|
||||
{
|
||||
case 'W': return "World";
|
||||
case 'A': return "USA";
|
||||
case 'J': return "Japan / Asia";
|
||||
case 'E': return "Europe";
|
||||
case 'K': return "USA / Japan";
|
||||
case 'L': return "USA / Europe";
|
||||
case 'H': return "Japan / Europe";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
14
Files/XMID.Serializer.cs
Normal file
14
Files/XMID.Serializer.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class XMID : IFileSerializer<Models.Xbox.XMID>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(Models.Xbox.XMID obj, string path) => throw new NotImplementedException();
|
||||
#else
|
||||
public bool Serialize(Models.Xbox.XMID? obj, string? path) => throw new NotImplementedException();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
273
Files/XeMID.Deserializer.cs
Normal file
273
Files/XeMID.Deserializer.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class XeMID : IFileSerializer<Models.Xbox.XeMID>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>This treats the input path like a parseable string</remarks>
|
||||
#if NET48
|
||||
public Models.Xbox.XeMID Deserialize(string path)
|
||||
#else
|
||||
public Models.Xbox.XeMID? Deserialize(string? path)
|
||||
#endif
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return null;
|
||||
|
||||
string xemid = path.TrimEnd('\0');
|
||||
if (string.IsNullOrWhiteSpace(xemid))
|
||||
return null;
|
||||
|
||||
return ParseXeMID(xemid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse an XGD2/3 XeMID string
|
||||
/// </summary>
|
||||
/// <param name="xemidString">XeMID string to attempt to parse</param>
|
||||
/// <returns>Filled XeMID on success, null on error</returns>
|
||||
#if NET48
|
||||
private static Models.Xbox.XeMID ParseXeMID(string xemidString)
|
||||
#else
|
||||
private static Models.Xbox.XeMID? ParseXeMID(string? xemidString)
|
||||
#endif
|
||||
{
|
||||
if (xemidString == null
|
||||
|| (xemidString.Length != 13 && xemidString.Length != 14
|
||||
&& xemidString.Length != 21 && xemidString.Length != 22))
|
||||
return null;
|
||||
|
||||
var xemid = new Models.Xbox.XeMID();
|
||||
|
||||
xemid.PublisherIdentifier = xemidString.Substring(0, 2);
|
||||
if (string.IsNullOrEmpty(PublisherName(xemid)))
|
||||
return null;
|
||||
|
||||
xemid.PlatformIdentifier = xemidString[2];
|
||||
if (xemid.PlatformIdentifier != '2')
|
||||
return null;
|
||||
|
||||
xemid.GameID = xemidString.Substring(3, 3);
|
||||
xemid.SKU = xemidString.Substring(6, 2);
|
||||
xemid.RegionIdentifier = xemidString[8];
|
||||
if (InternalRegion(xemid) == null)
|
||||
return null;
|
||||
|
||||
if (xemidString.Length == 13 || xemidString.Length == 21)
|
||||
{
|
||||
xemid.BaseVersion = xemidString.Substring(9, 1);
|
||||
xemid.MediaSubtypeIdentifier = xemidString[10];
|
||||
if (string.IsNullOrEmpty(MediaSubtype(xemid)))
|
||||
return null;
|
||||
|
||||
xemid.DiscNumberIdentifier = xemidString.Substring(11, 2);
|
||||
}
|
||||
else if (xemidString.Length == 14 || xemidString.Length == 22)
|
||||
{
|
||||
xemid.BaseVersion = xemidString.Substring(9, 2);
|
||||
xemid.MediaSubtypeIdentifier = xemidString[11];
|
||||
if (string.IsNullOrEmpty(MediaSubtype(xemid)))
|
||||
return null;
|
||||
|
||||
xemid.DiscNumberIdentifier = xemidString.Substring(12, 2);
|
||||
}
|
||||
|
||||
if (xemidString.Length == 21)
|
||||
xemid.CertificationSubmissionIdentifier = xemidString.Substring(13);
|
||||
else if (xemidString.Length == 22)
|
||||
xemid.CertificationSubmissionIdentifier = xemidString.Substring(14);
|
||||
|
||||
return xemid;
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable name derived from the publisher identifier
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public static string PublisherName(Models.Xbox.XeMID xemid) => GetPublisher(xemid.PublisherIdentifier);
|
||||
#else
|
||||
public static string? PublisherName(Models.Xbox.XeMID xemid) => GetPublisher(xemid.PublisherIdentifier);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Internally represented region
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public static string InternalRegion(Models.Xbox.XeMID xemid) => GetRegion(xemid.RegionIdentifier);
|
||||
#else
|
||||
public static string? InternalRegion(Models.Xbox.XeMID xemid) => GetRegion(xemid.RegionIdentifier);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable subtype derived from the media identifier
|
||||
/// </summary>
|
||||
#if NET48
|
||||
public static string MediaSubtype(Models.Xbox.XeMID xemid) => GetMediaSubtype(xemid.MediaSubtypeIdentifier);
|
||||
#else
|
||||
public static string? MediaSubtype(Models.Xbox.XeMID xemid) => GetMediaSubtype(xemid.MediaSubtypeIdentifier);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Determine the XGD type based on the XGD2/3 media type identifier character
|
||||
/// </summary>
|
||||
/// <param name="mediaTypeIdentifier">Character denoting the media type</param>
|
||||
/// <returns>Media subtype as a string, if possible</returns>
|
||||
#if NET48
|
||||
private static string GetMediaSubtype(char mediaTypeIdentifier)
|
||||
#else
|
||||
private static string? GetMediaSubtype(char mediaTypeIdentifier)
|
||||
#endif
|
||||
{
|
||||
switch (mediaTypeIdentifier)
|
||||
{
|
||||
case 'F': return "XGD3";
|
||||
case 'X': return "XGD2";
|
||||
case 'Z': return "Games on Demand / Marketplace Demo";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full name of the publisher from the 2-character identifier
|
||||
/// </summary>
|
||||
/// <param name="publisherIdentifier">Case-sensitive 2-character identifier</param>
|
||||
/// <returns>Publisher name, if possible</returns>
|
||||
/// <see cref="https://xboxdevwiki.net/Xbe#Title_ID"/>
|
||||
#if NET48
|
||||
private static string GetPublisher(string publisherIdentifier)
|
||||
#else
|
||||
private static string? GetPublisher(string? publisherIdentifier)
|
||||
#endif
|
||||
{
|
||||
switch (publisherIdentifier)
|
||||
{
|
||||
case "AC": return "Acclaim Entertainment";
|
||||
case "AH": return "ARUSH Entertainment";
|
||||
case "AQ": return "Aqua System";
|
||||
case "AS": return "ASK";
|
||||
case "AT": return "Atlus";
|
||||
case "AV": return "Activision";
|
||||
case "AY": return "Aspyr Media";
|
||||
case "BA": return "Bandai";
|
||||
case "BL": return "Black Box";
|
||||
case "BM": return "BAM! Entertainment";
|
||||
case "BR": return "Broccoli Co.";
|
||||
case "BS": return "Bethesda Softworks";
|
||||
case "BU": return "Bunkasha Co.";
|
||||
case "BV": return "Buena Vista Games";
|
||||
case "BW": return "BBC Multimedia";
|
||||
case "BZ": return "Blizzard";
|
||||
case "CC": return "Capcom";
|
||||
case "CK": return "Kemco Corporation"; // TODO: Confirm
|
||||
case "CM": return "Codemasters";
|
||||
case "CV": return "Crave Entertainment";
|
||||
case "DC": return "DreamCatcher Interactive";
|
||||
case "DX": return "Davilex";
|
||||
case "EA": return "Electronic Arts (EA)";
|
||||
case "EC": return "Encore inc";
|
||||
case "EL": return "Enlight Software";
|
||||
case "EM": return "Empire Interactive";
|
||||
case "ES": return "Eidos Interactive";
|
||||
case "FI": return "Fox Interactive";
|
||||
case "FS": return "From Software";
|
||||
case "GE": return "Genki Co.";
|
||||
case "GV": return "Groove Games";
|
||||
case "HE": return "Tru Blu (Entertainment division of Home Entertainment Suppliers)";
|
||||
case "HP": return "Hip games";
|
||||
case "HU": return "Hudson Soft";
|
||||
case "HW": return "Highwaystar";
|
||||
case "IA": return "Mad Catz Interactive";
|
||||
case "IF": return "Idea Factory";
|
||||
case "IG": return "Infogrames";
|
||||
case "IL": return "Interlex Corporation";
|
||||
case "IM": return "Imagine Media";
|
||||
case "IO": return "Ignition Entertainment";
|
||||
case "IP": return "Interplay Entertainment";
|
||||
case "IX": return "InXile Entertainment"; // TODO: Confirm
|
||||
case "JA": return "Jaleco";
|
||||
case "JW": return "JoWooD";
|
||||
case "KB": return "Kemco"; // TODO: Confirm
|
||||
case "KI": return "Kids Station Inc."; // TODO: Confirm
|
||||
case "KN": return "Konami";
|
||||
case "KO": return "KOEI";
|
||||
case "KU": return "Kobi and / or GAE (formerly Global A Entertainment)"; // TODO: Confirm
|
||||
case "LA": return "LucasArts";
|
||||
case "LS": return "Black Bean Games (publishing arm of Leader S.p.A.)";
|
||||
case "MD": return "Metro3D";
|
||||
case "ME": return "Medix";
|
||||
case "MI": return "Microïds";
|
||||
case "MJ": return "Majesco Entertainment";
|
||||
case "MM": return "Myelin Media";
|
||||
case "MP": return "MediaQuest"; // TODO: Confirm
|
||||
case "MS": return "Microsoft Game Studios";
|
||||
case "MW": return "Midway Games";
|
||||
case "MX": return "Empire Interactive"; // TODO: Confirm
|
||||
case "NK": return "NewKidCo";
|
||||
case "NL": return "NovaLogic";
|
||||
case "NM": return "Namco";
|
||||
case "OX": return "Oxygen Interactive";
|
||||
case "PC": return "Playlogic Entertainment";
|
||||
case "PL": return "Phantagram Co., Ltd.";
|
||||
case "RA": return "Rage";
|
||||
case "SA": return "Sammy";
|
||||
case "SC": return "SCi Games";
|
||||
case "SE": return "SEGA";
|
||||
case "SN": return "SNK";
|
||||
case "SS": return "Simon & Schuster";
|
||||
case "SU": return "Success Corporation";
|
||||
case "SW": return "Swing! Deutschland";
|
||||
case "TA": return "Takara";
|
||||
case "TC": return "Tecmo";
|
||||
case "TD": return "The 3DO Company (or just 3DO)";
|
||||
case "TK": return "Takuyo";
|
||||
case "TM": return "TDK Mediactive";
|
||||
case "TQ": return "THQ";
|
||||
case "TS": return "Titus Interactive";
|
||||
case "TT": return "Take-Two Interactive Software";
|
||||
case "US": return "Ubisoft";
|
||||
case "VC": return "Victor Interactive Software";
|
||||
case "VN": return "Vivendi Universal (just took Interplays publishing rights)"; // TODO: Confirm
|
||||
case "VU": return "Vivendi Universal Games";
|
||||
case "VV": return "Vivendi Universal Games"; // TODO: Confirm
|
||||
case "WE": return "Wanadoo Edition";
|
||||
case "WR": return "Warner Bros. Interactive Entertainment"; // TODO: Confirm
|
||||
case "XI": return "XPEC Entertainment and Idea Factory";
|
||||
case "XK": return "Xbox kiosk disk?"; // TODO: Confirm
|
||||
case "XL": return "Xbox special bundled or live demo disk?"; // TODO: Confirm
|
||||
case "XM": return "Evolved Games"; // TODO: Confirm
|
||||
case "XP": return "XPEC Entertainment";
|
||||
case "XR": return "Panorama";
|
||||
case "YB": return "YBM Sisa (South-Korea)";
|
||||
case "ZD": return "Zushi Games (formerly Zoo Digital Publishing)";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine the region based on the XGD serial character
|
||||
/// </summary>
|
||||
/// <param name="region">Character denoting the region</param>
|
||||
/// <returns>Region, if possible</returns>
|
||||
#if NET48
|
||||
private static string GetRegion(char region)
|
||||
#else
|
||||
private static string? GetRegion(char region)
|
||||
#endif
|
||||
{
|
||||
switch (region)
|
||||
{
|
||||
case 'W': return "World";
|
||||
case 'A': return "USA";
|
||||
case 'J': return "Japan / Asia";
|
||||
case 'E': return "Europe";
|
||||
case 'K': return "USA / Japan";
|
||||
case 'L': return "USA / Europe";
|
||||
case 'H': return "Japan / Europe";
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
14
Files/XeMID.Serializer.cs
Normal file
14
Files/XeMID.Serializer.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Serialization.Files
|
||||
{
|
||||
public partial class XeMID : IFileSerializer<Models.Xbox.XeMID>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public bool Serialize(Models.Xbox.XeMID obj, string path) => throw new NotImplementedException();
|
||||
#else
|
||||
public bool Serialize(Models.Xbox.XeMID? obj, string? path) => throw new NotImplementedException();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
# SabreTools.Serialization
|
||||
|
||||
This library comprises of serializers that both read and write from files and streams to the dedicated models as well as convert to and from the common internal models. This library is partially used by the current parsing and writing code but none of the internal model serialization is used.
|
||||
|
||||
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.Serialization).
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net48;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.1.2</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<!-- Package Properties -->
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.1.1" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.1.1" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
15
Streams/CueSheet.Deserializer.cs
Normal file
15
Streams/CueSheet.Deserializer.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.Serialization.Streams
|
||||
{
|
||||
public partial class CueSheet : IStreamSerializer<Models.CueSheets.CueSheet>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public Models.CueSheets.CueSheet Deserialize(Stream data) => throw new NotImplementedException();
|
||||
#else
|
||||
public Models.CueSheets.CueSheet? Deserialize(Stream? data) => throw new NotImplementedException();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
281
Streams/CueSheet.Serializer.cs
Normal file
281
Streams/CueSheet.Serializer.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Models.CueSheets;
|
||||
|
||||
namespace SabreTools.Serialization.Streams
|
||||
{
|
||||
public partial class CueSheet : IStreamSerializer<Models.CueSheets.CueSheet>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public Stream Serialize(Models.CueSheets.CueSheet obj)
|
||||
#else
|
||||
public Stream? Serialize(Models.CueSheets.CueSheet? obj)
|
||||
#endif
|
||||
{
|
||||
// If the cuesheet is null
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
|
||||
|
||||
// If we don't have any files, it's invalid
|
||||
if (obj?.Files == null)
|
||||
throw new ArgumentNullException(nameof(obj.Files));
|
||||
else if (obj.Files.Length == 0)
|
||||
throw new ArgumentException("No files provided to write");
|
||||
|
||||
// Setup the writer and output
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream, Encoding.ASCII, 1024, true);
|
||||
|
||||
// Write the file
|
||||
WriteCueSheet(obj, writer);
|
||||
|
||||
// Return the stream
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the cuesheet out to a stream
|
||||
/// </summary>
|
||||
/// <param name="cueSheet">CueSheet to write</param>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
private static void WriteCueSheet(Models.CueSheets.CueSheet cueSheet, StreamWriter sw)
|
||||
{
|
||||
// If we don't have any files, it's invalid
|
||||
if (cueSheet.Files == null)
|
||||
throw new ArgumentNullException(nameof(cueSheet.Files));
|
||||
else if (cueSheet.Files.Length == 0)
|
||||
throw new ArgumentException("No files provided to write");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueSheet.Catalog))
|
||||
sw.WriteLine($"CATALOG {cueSheet.Catalog}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueSheet.CdTextFile))
|
||||
sw.WriteLine($"CDTEXTFILE {cueSheet.CdTextFile}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueSheet.Performer))
|
||||
sw.WriteLine($"PERFORMER {cueSheet.Performer}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueSheet.Songwriter))
|
||||
sw.WriteLine($"SONGWRITER {cueSheet.Songwriter}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueSheet.Title))
|
||||
sw.WriteLine($"TITLE {cueSheet.Title}");
|
||||
|
||||
foreach (var cueFile in cueSheet.Files)
|
||||
{
|
||||
WriteCueFile(cueFile, sw);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the FILE out to a stream
|
||||
/// </summary>
|
||||
/// <param name="cueFile">CueFile to write</param>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
#if NET48
|
||||
private static void WriteCueFile(CueFile cueFile, StreamWriter sw)
|
||||
#else
|
||||
private static void WriteCueFile(CueFile? cueFile, StreamWriter sw)
|
||||
#endif
|
||||
{
|
||||
// If we don't have any tracks, it's invalid
|
||||
if (cueFile?.Tracks == null)
|
||||
throw new ArgumentNullException(nameof(cueFile.Tracks));
|
||||
else if (cueFile.Tracks.Length == 0)
|
||||
throw new ArgumentException("No tracks provided to write");
|
||||
|
||||
sw.WriteLine($"FILE \"{cueFile.FileName}\" {FromFileType(cueFile.FileType)}");
|
||||
|
||||
foreach (var track in cueFile.Tracks)
|
||||
{
|
||||
WriteCueTrack(track, sw);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the TRACK out to a stream
|
||||
/// </summary>
|
||||
/// <param name="cueFile">CueFile to write</param>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
#if NET48
|
||||
private static void WriteCueTrack(CueTrack cueTrack, StreamWriter sw)
|
||||
#else
|
||||
private static void WriteCueTrack(CueTrack? cueTrack, StreamWriter sw)
|
||||
#endif
|
||||
{
|
||||
// If we don't have any indices, it's invalid
|
||||
if (cueTrack?.Indices == null)
|
||||
throw new ArgumentNullException(nameof(cueTrack.Indices));
|
||||
else if (cueTrack.Indices.Length == 0)
|
||||
throw new ArgumentException("No indices provided to write");
|
||||
|
||||
sw.WriteLine($" TRACK {cueTrack.Number:D2} {FromDataType(cueTrack.DataType)}");
|
||||
|
||||
if (cueTrack.Flags != 0)
|
||||
sw.WriteLine($" FLAGS {FromFlags(cueTrack.Flags)}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueTrack.ISRC))
|
||||
sw.WriteLine($" ISRC {cueTrack.ISRC}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueTrack.Performer))
|
||||
sw.WriteLine($" PERFORMER {cueTrack.Performer}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueTrack.Songwriter))
|
||||
sw.WriteLine($" SONGWRITER {cueTrack.Songwriter}");
|
||||
|
||||
if (!string.IsNullOrEmpty(cueTrack.Title))
|
||||
sw.WriteLine($" TITLE {cueTrack.Title}");
|
||||
|
||||
if (cueTrack.PreGap != null)
|
||||
WritePreGap(cueTrack.PreGap, sw);
|
||||
|
||||
foreach (var index in cueTrack.Indices)
|
||||
{
|
||||
WriteCueIndex(index, sw);
|
||||
}
|
||||
|
||||
if (cueTrack.PostGap != null)
|
||||
WritePostGap(cueTrack.PostGap, sw);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the PREGAP out to a stream
|
||||
/// </summary>
|
||||
/// <param name="preGap">PreGap to write</param>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
private static void WritePreGap(PreGap preGap, StreamWriter sw)
|
||||
{
|
||||
sw.WriteLine($" PREGAP {preGap.Minutes:D2}:{preGap.Seconds:D2}:{preGap.Frames:D2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the INDEX out to a stream
|
||||
/// </summary>
|
||||
/// <param name="cueIndex">CueIndex to write</param>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
#if NET48
|
||||
private static void WriteCueIndex(CueIndex cueIndex, StreamWriter sw)
|
||||
#else
|
||||
private static void WriteCueIndex(CueIndex? cueIndex, StreamWriter sw)
|
||||
#endif
|
||||
{
|
||||
if (cueIndex == null)
|
||||
throw new ArgumentNullException(nameof(cueIndex));
|
||||
|
||||
sw.WriteLine($" INDEX {cueIndex.Index:D2} {cueIndex.Minutes:D2}:{cueIndex.Seconds:D2}:{cueIndex.Frames:D2}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the POSTGAP out to a stream
|
||||
/// </summary>
|
||||
/// <param name="postGap">PostGap to write</param>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
private static void WritePostGap(PostGap postGap, StreamWriter sw)
|
||||
{
|
||||
sw.WriteLine($" POSTGAP {postGap.Minutes:D2}:{postGap.Seconds:D2}:{postGap.Frames:D2}");
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Get the string from a given file type
|
||||
/// </summary>
|
||||
/// <param name="fileType">CueFileType to get value from</param>
|
||||
/// <returns>String, if possible (default BINARY)</returns>
|
||||
private static string FromFileType(CueFileType fileType)
|
||||
{
|
||||
switch (fileType)
|
||||
{
|
||||
case CueFileType.BINARY:
|
||||
return "BINARY";
|
||||
|
||||
case CueFileType.MOTOROLA:
|
||||
return "MOTOROLA";
|
||||
|
||||
case CueFileType.AIFF:
|
||||
return "AIFF";
|
||||
|
||||
case CueFileType.WAVE:
|
||||
return "WAVE";
|
||||
|
||||
case CueFileType.MP3:
|
||||
return "MP3";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string from a given data type
|
||||
/// </summary>
|
||||
/// <param name="dataType">CueTrackDataType to get value from</param>
|
||||
/// <returns>string, if possible</returns>
|
||||
private static string FromDataType(CueTrackDataType dataType)
|
||||
{
|
||||
switch (dataType)
|
||||
{
|
||||
case CueTrackDataType.AUDIO:
|
||||
return "AUDIO";
|
||||
|
||||
case CueTrackDataType.CDG:
|
||||
return "CDG";
|
||||
|
||||
case CueTrackDataType.MODE1_2048:
|
||||
return "MODE1/2048";
|
||||
|
||||
case CueTrackDataType.MODE1_2352:
|
||||
return "MODE1/2352";
|
||||
|
||||
case CueTrackDataType.MODE2_2336:
|
||||
return "MODE2/2336";
|
||||
|
||||
case CueTrackDataType.MODE2_2352:
|
||||
return "MODE2/2352";
|
||||
|
||||
case CueTrackDataType.CDI_2336:
|
||||
return "CDI/2336";
|
||||
|
||||
case CueTrackDataType.CDI_2352:
|
||||
return "CDI/2352";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string value for a set of track flags
|
||||
/// </summary>
|
||||
/// <param name="flags">CueTrackFlag to get value from</param>
|
||||
/// <returns>String value representing the CueTrackFlag, if possible</returns>
|
||||
private static string FromFlags(CueTrackFlag flags)
|
||||
{
|
||||
string outputFlagString = string.Empty;
|
||||
|
||||
if (flags.HasFlag(CueTrackFlag.DCP))
|
||||
outputFlagString += "DCP ";
|
||||
|
||||
if (flags.HasFlag(CueTrackFlag.FourCH))
|
||||
outputFlagString += "4CH ";
|
||||
|
||||
if (flags.HasFlag(CueTrackFlag.PRE))
|
||||
outputFlagString += "PRE ";
|
||||
|
||||
if (flags.HasFlag(CueTrackFlag.SCMS))
|
||||
outputFlagString += "SCMS ";
|
||||
|
||||
if (flags.HasFlag(CueTrackFlag.DATA))
|
||||
outputFlagString += "DATA ";
|
||||
|
||||
return outputFlagString.Trim();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
202
Streams/PIC.Deserializer.cs
Normal file
202
Streams/PIC.Deserializer.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.Models.PIC;
|
||||
using static SabreTools.Models.PIC.Constants;
|
||||
|
||||
namespace SabreTools.Serialization.Streams
|
||||
{
|
||||
public partial class PIC : IStreamSerializer<DiscInformation>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public DiscInformation Deserialize(Stream data)
|
||||
#else
|
||||
public DiscInformation? Deserialize(Stream? data)
|
||||
#endif
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
return null;
|
||||
|
||||
var di = new DiscInformation();
|
||||
|
||||
// Read the initial disc information
|
||||
di.DataStructureLength = data.ReadUInt16BigEndian();
|
||||
di.Reserved0 = data.ReadByteValue();
|
||||
di.Reserved1 = data.ReadByteValue();
|
||||
|
||||
// Create a list for the units
|
||||
var diUnits = new List<DiscInformationUnit>();
|
||||
|
||||
// Loop and read all available units
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
var unit = ParseDiscInformationUnit(data);
|
||||
if (unit == null)
|
||||
continue;
|
||||
|
||||
diUnits.Add(unit);
|
||||
}
|
||||
|
||||
// Assign the units and return
|
||||
di.Units = diUnits.ToArray();
|
||||
return di;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a disc information unit
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled disc information unit on success, null on error</returns>
|
||||
#if NET48
|
||||
private static DiscInformationUnit ParseDiscInformationUnit(Stream data)
|
||||
#else
|
||||
private static DiscInformationUnit? ParseDiscInformationUnit(Stream data)
|
||||
#endif
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var unit = new DiscInformationUnit();
|
||||
|
||||
#region Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseDiscInformationUnitHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the information unit header
|
||||
unit.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Body
|
||||
|
||||
// Try to parse the body
|
||||
var body = ParseDiscInformationUnitBody(data);
|
||||
if (body == null)
|
||||
return null;
|
||||
|
||||
// Set the information unit body
|
||||
unit.Body = body;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Trailer
|
||||
|
||||
if (unit.Body.DiscTypeIdentifier == DiscTypeIdentifierReWritable || unit.Body.DiscTypeIdentifier == DiscTypeIdentifierRecordable)
|
||||
{
|
||||
// Try to parse the trailer
|
||||
var trailer = ParseDiscInformationUnitTrailer(data);
|
||||
if (trailer == null)
|
||||
return null;
|
||||
|
||||
// Set the information unit trailer
|
||||
unit.Trailer = trailer;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a disc information unit header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled disc information unit header on success, null on error</returns>
|
||||
#if NET48
|
||||
private static DiscInformationUnitHeader ParseDiscInformationUnitHeader(Stream data)
|
||||
#else
|
||||
private static DiscInformationUnitHeader? ParseDiscInformationUnitHeader(Stream data)
|
||||
#endif
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var header = new DiscInformationUnitHeader();
|
||||
|
||||
// We only accept Disc Information units, not Emergency Brake or other
|
||||
#if NET48
|
||||
byte[] dic = data.ReadBytes(2);
|
||||
#else
|
||||
byte[]? dic = data.ReadBytes(2);
|
||||
#endif
|
||||
if (dic == null)
|
||||
return null;
|
||||
|
||||
header.DiscInformationIdentifier = Encoding.ASCII.GetString(dic);
|
||||
if (header.DiscInformationIdentifier != "DI")
|
||||
return null;
|
||||
|
||||
header.DiscInformationFormat = data.ReadByteValue();
|
||||
header.NumberOfUnitsInBlock = data.ReadByteValue();
|
||||
header.Reserved0 = data.ReadByteValue();
|
||||
header.SequenceNumber = data.ReadByteValue();
|
||||
header.BytesInUse = data.ReadByteValue();
|
||||
header.Reserved1 = data.ReadByteValue();
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a disc information unit body
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled disc information unit body on success, null on error</returns>
|
||||
#if NET48
|
||||
private static DiscInformationUnitBody ParseDiscInformationUnitBody(Stream data)
|
||||
#else
|
||||
private static DiscInformationUnitBody? ParseDiscInformationUnitBody(Stream data)
|
||||
#endif
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var body = new DiscInformationUnitBody();
|
||||
|
||||
#if NET48
|
||||
byte[] dti = data.ReadBytes(3);
|
||||
#else
|
||||
byte[]? dti = data.ReadBytes(3);
|
||||
#endif
|
||||
if (dti == null)
|
||||
return null;
|
||||
|
||||
body.DiscTypeIdentifier = Encoding.ASCII.GetString(dti);
|
||||
body.DiscSizeClassVersion = data.ReadByteValue();
|
||||
switch (body.DiscTypeIdentifier)
|
||||
{
|
||||
case DiscTypeIdentifierROM:
|
||||
case DiscTypeIdentifierROMUltra:
|
||||
body.FormatDependentContents = data.ReadBytes(52);
|
||||
break;
|
||||
case DiscTypeIdentifierReWritable:
|
||||
case DiscTypeIdentifierRecordable:
|
||||
body.FormatDependentContents = data.ReadBytes(100);
|
||||
break;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a disc information unit trailer
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled disc information unit trailer on success, null on error</returns>
|
||||
private static DiscInformationUnitTrailer ParseDiscInformationUnitTrailer(Stream data)
|
||||
{
|
||||
// TODO: Use marshalling here instead of building
|
||||
var trailer = new DiscInformationUnitTrailer();
|
||||
|
||||
trailer.DiscManufacturerID = data.ReadBytes(6);
|
||||
trailer.MediaTypeID = data.ReadBytes(3);
|
||||
trailer.TimeStamp = data.ReadUInt16();
|
||||
trailer.ProductRevisionNumber = data.ReadByteValue();
|
||||
|
||||
return trailer;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Streams/PIC.Serializer.cs
Normal file
16
Streams/PIC.Serializer.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SabreTools.Models.PIC;
|
||||
|
||||
namespace SabreTools.Serialization.Streams
|
||||
{
|
||||
public partial class PIC : IStreamSerializer<DiscInformation>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
#if NET48
|
||||
public Stream Serialize(DiscInformation obj) => throw new NotImplementedException();
|
||||
#else
|
||||
public Stream? Serialize(DiscInformation? obj) => throw new NotImplementedException();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user