diff --git a/BurnOutSharp.Builders/PlayJ.cs b/BurnOutSharp.Builders/PlayJ.cs
index 56632611..4ae28ab2 100644
--- a/BurnOutSharp.Builders/PlayJ.cs
+++ b/BurnOutSharp.Builders/PlayJ.cs
@@ -168,47 +168,88 @@ namespace BurnOutSharp.Builders
#endregion
- #region Unknown Value 2
+ #region V1 Only
- // If we have an unknown value 2 offset
- if (entryHeader.UnknownOffset2 > 0)
+ // If we have a V1 file
+ if (entryHeader.Version == 0x00000000)
{
- // Get the unknown value 2 offset
- long offset = entryHeader.UnknownOffset2 + adjust;
- if (offset < 0 || offset >= data.Length)
+ #region Unknown Value 2
+
+ // If we have an unknown value 2 offset
+ if (entryHeader.UnknownOffset2 > 0)
+ {
+ // Get the unknown value 2 offset
+ long offset = entryHeader.UnknownOffset2 + adjust;
+ if (offset < 0 || offset >= data.Length)
+ return null;
+
+ // Seek to the unknown value 2
+ data.Seek(offset, SeekOrigin.Begin);
+ }
+
+ // Set the unknown value 2
+ audioFile.UnknownValue2 = data.ReadUInt32();
+
+ #endregion
+
+ #region Unknown Block 3
+
+ // If we have an unknown block 3 offset
+ if (entryHeader.UnknownOffset2 > 0)
+ {
+ // Get the unknown block 3 offset
+ long offset = entryHeader.UnknownOffset3 + adjust;
+ if (offset < 0 || offset >= data.Length)
+ return null;
+
+ // Seek to the unknown block 3
+ data.Seek(offset, SeekOrigin.Begin);
+ }
+
+ // Try to parse the unknown block 3
+ var unknownBlock3 = ParseUnknownBlock3(data);
+ if (unknownBlock3 == null)
return null;
- // Seek to the unknown value 2
- data.Seek(offset, SeekOrigin.Begin);
- }
+ // Set the unknown block 3
+ audioFile.UnknownBlock3 = unknownBlock3;
- // Set the unknown value 2
- audioFile.UnknownValue2 = data.ReadUInt32();
+ #endregion
+ }
#endregion
- #region Unknown Block 3
+ #region V2 Only
- // If we have an unknown block 3 offset
- if (entryHeader.UnknownOffset3 > 0)
+ // If we have a V2 file
+ if (entryHeader.Version == 0x0000000A)
{
- // Get the unknown block 3 offset
- long offset = entryHeader.UnknownOffset3 + adjust;
- if (offset < 0 || offset >= data.Length)
- return null;
+ #region Data Files Count
- // Seek to the unknown block 3
- data.Seek(offset, SeekOrigin.Begin);
+ // Set the data files count
+ audioFile.DataFilesCount = data.ReadUInt32();
+
+ #endregion
+
+ #region Data Files
+
+ // Create the data files array
+ audioFile.DataFiles = new DataFile[audioFile.DataFilesCount];
+
+ // Try to parse the data files
+ for (int i = 0; i < audioFile.DataFiles.Length; i++)
+ {
+ var dataFile = ParseDataFile(data);
+ if (dataFile == null)
+ return null;
+
+ audioFile.DataFiles[i] = dataFile;
+ }
+
+
+ #endregion
}
- // Try to parse the unknown block 3
- var unknownBlock3 = ParseUnknownBlock3(data);
- if (unknownBlock3 == null)
- return null;
-
- // Set the unknown block 3
- audioFile.UnknownBlock3 = unknownBlock3;
-
#endregion
return audioFile;
@@ -326,8 +367,8 @@ namespace BurnOutSharp.Builders
// TODO: Use marshalling here instead of building
UnknownBlock1 unknownBlock1 = new UnknownBlock1();
- unknownBlock1.Length = data.ReadUInt16();
- unknownBlock1.Data = data.ReadBytes(unknownBlock1.Length);
+ unknownBlock1.Length = data.ReadUInt32();
+ unknownBlock1.Data = data.ReadBytes((int)unknownBlock1.Length);
return unknownBlock1;
}
@@ -347,6 +388,27 @@ namespace BurnOutSharp.Builders
return unknownBlock3;
}
+ ///
+ /// Parse a Stream into a data file
+ ///
+ /// Stream to parse
+ /// Filled data file on success, null on error
+ private static DataFile ParseDataFile(Stream data)
+ {
+ // TODO: Use marshalling here instead of building
+ DataFile dataFile = new DataFile();
+
+ dataFile.FileNameLength = data.ReadUInt16();
+ byte[] fileName = data.ReadBytes(dataFile.FileNameLength);
+ if (fileName != null)
+ dataFile.FileName = Encoding.ASCII.GetString(fileName);
+
+ dataFile.DataLength = data.ReadUInt32();
+ dataFile.Data = data.ReadBytes((int)dataFile.DataLength);
+
+ return dataFile;
+ }
+
#endregion
}
}
diff --git a/BurnOutSharp.Models/PlayJ/AudioFile.cs b/BurnOutSharp.Models/PlayJ/AudioFile.cs
index 04716a05..8d2578aa 100644
--- a/BurnOutSharp.Models/PlayJ/AudioFile.cs
+++ b/BurnOutSharp.Models/PlayJ/AudioFile.cs
@@ -15,6 +15,8 @@ namespace BurnOutSharp.Models.PlayJ
///
public UnknownBlock1 UnknownBlock1 { get; set; }
+ #region V1 Only
+
///
/// Value referred to by
///
@@ -22,8 +24,27 @@ namespace BurnOutSharp.Models.PlayJ
public uint UnknownValue2 { get; set; }
///
- /// Unknown block 3
+ /// Unknown block 3 (V1 only)
///
public UnknownBlock3 UnknownBlock3 { get; set; }
+
+ #endregion
+
+ #region V2 Only
+
+ ///
+ /// Number of data files embedded
+ ///
+ public uint DataFilesCount { get; set; }
+
+ ///
+ /// Data files (V2 only)
+ ///
+ public DataFile[] DataFiles { get; set; }
+
+ // After the data files is a block starting with 0x00000001
+ // This block then contains highly repeating data, possible audio samples?
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/BurnOutSharp.Models/PlayJ/DataFile.cs b/BurnOutSharp.Models/PlayJ/DataFile.cs
new file mode 100644
index 00000000..310c25ea
--- /dev/null
+++ b/BurnOutSharp.Models/PlayJ/DataFile.cs
@@ -0,0 +1,35 @@
+namespace BurnOutSharp.Models.PlayJ
+{
+ ///
+ /// Embedded data file (V2 only?)
+ ///
+ public sealed class DataFile
+ {
+ ///
+ /// Length of the data file name
+ ///
+ public ushort FileNameLength;
+
+ ///
+ /// Data file name
+ ///
+ public string FileName;
+
+ ///
+ /// Length of the data
+ ///
+ public uint DataLength;
+
+ ///
+ /// Data
+ ///
+ public byte[] Data;
+
+ // Notes about Data:
+ // - Each data block in the samples contains a GIF header
+ // - Each GIF header contains an application extension: http://www.vurdalakov.net/misc/gif/application-extension
+ // - Each GIF doesn't always stretch to the end of the data
+ // - Remaining data seems to be padded as 0x00 (typically 8 bytes)
+ // - GIF data is fully formed and may be copied to a standalone file
+ }
+}
\ No newline at end of file
diff --git a/BurnOutSharp.Models/PlayJ/UnknownBlock1.cs b/BurnOutSharp.Models/PlayJ/UnknownBlock1.cs
index c7b7a9ec..5dfc1eb6 100644
--- a/BurnOutSharp.Models/PlayJ/UnknownBlock1.cs
+++ b/BurnOutSharp.Models/PlayJ/UnknownBlock1.cs
@@ -8,11 +8,17 @@ namespace BurnOutSharp.Models.PlayJ
///
/// Length of the following data block
///
- public ushort Length;
+ public uint Length;
///
/// Unknown data
///
public byte[] Data;
+
+ // Notes about Data:
+ // - Might be UInt16 offset and UInt16 length pairs
+ // - Might be relevant to ad data
+ // - Might be relevant to encryption
+ // - Highly repeating patterns in the values with only a few differences
}
}
\ No newline at end of file
diff --git a/BurnOutSharp.Models/PlayJ/UnknownBlock3.cs b/BurnOutSharp.Models/PlayJ/UnknownBlock3.cs
index 1cf366ab..12f1d2a0 100644
--- a/BurnOutSharp.Models/PlayJ/UnknownBlock3.cs
+++ b/BurnOutSharp.Models/PlayJ/UnknownBlock3.cs
@@ -9,5 +9,10 @@ namespace BurnOutSharp.Models.PlayJ
/// Unknown data
///
public byte[] Data;
+
+ // Notes about Data:
+ // - This may be where the encrypted audio samples live
+ // - It is also possible that it's where the ad data lives and samples follow
+ // + See V2 for example of why this would be the case
}
}
\ No newline at end of file
diff --git a/BurnOutSharp.Wrappers/PlayJAudioFile.cs b/BurnOutSharp.Wrappers/PlayJAudioFile.cs
index 31d15a82..8babc0f0 100644
--- a/BurnOutSharp.Wrappers/PlayJAudioFile.cs
+++ b/BurnOutSharp.Wrappers/PlayJAudioFile.cs
@@ -93,13 +93,15 @@ namespace BurnOutSharp.Wrappers
#region Unknown Block 1
///
- public ushort UB1_Length => _audioFile.UnknownBlock1.Length;
+ public uint UB1_Length => _audioFile.UnknownBlock1.Length;
///
public byte[] UB1_Data => _audioFile.UnknownBlock1.Data;
#endregion
+ #region V1 Only
+
#region Unknown Value 2
///
@@ -116,6 +118,26 @@ namespace BurnOutSharp.Wrappers
#endregion
+ #region V2 Only
+
+ #region Data Files Count
+
+ ///
+ public uint DataFilesCount => _audioFile.DataFilesCount;
+
+ #endregion
+
+ #region Unknown Block 3
+
+ ///
+ public Models.PlayJ.DataFile[] DataFiles => _audioFile.DataFiles;
+
+ #endregion
+
+ #endregion
+
+ #endregion
+
#region Instance Variables
///
@@ -192,8 +214,16 @@ namespace BurnOutSharp.Wrappers
PrintEntryHeader(builder);
PrintUnknownBlock1(builder);
- PrintUnknownValue2(builder);
- PrintUnknownBlock3(builder);
+
+ if (Version == 0x00000000)
+ {
+ PrintUnknownValue2(builder);
+ PrintUnknownBlock3(builder);
+ }
+ else if (Version == 0x0000000A)
+ {
+ PrintDataFiles(builder);
+ }
return builder;
}
@@ -249,7 +279,7 @@ namespace BurnOutSharp.Wrappers
}
///
- /// Print unknown value 2 information
+ /// Print unknown value 2 information (V1 only)
///
/// StringBuilder to append information to
private void PrintUnknownValue2(StringBuilder builder)
@@ -261,7 +291,7 @@ namespace BurnOutSharp.Wrappers
}
///
- /// Print unknown block 3 information
+ /// Print unknown block 3 information (V1 only)
///
/// StringBuilder to append information to
private void PrintUnknownBlock3(StringBuilder builder)
@@ -272,6 +302,30 @@ namespace BurnOutSharp.Wrappers
builder.AppendLine();
}
+ ///
+ /// Print data files information (V2 only)
+ ///
+ /// StringBuilder to append information to
+ private void PrintDataFiles(StringBuilder builder)
+ {
+ builder.AppendLine(" Data Files Information:");
+ builder.AppendLine(" -------------------------");
+ builder.AppendLine($" Data files count: {DataFilesCount} (0x{DataFilesCount:X})");
+ if (DataFilesCount != 0 && DataFiles != null && DataFiles.Length != 0)
+ {
+ for (int i = 0; i < DataFiles.Length; i++)
+ {
+ var dataFile = DataFiles[i];
+ builder.AppendLine($" Data File {i}:");
+ builder.AppendLine($" File name length: {dataFile.FileNameLength} (0x{dataFile.FileNameLength:X})");
+ builder.AppendLine($" File name: {dataFile.FileName ?? "[NULL]"}");
+ builder.AppendLine($" Data length: {dataFile.DataLength} (0x{dataFile.DataLength:X})");
+ builder.AppendLine($" Data: {BitConverter.ToString(dataFile.Data ?? new byte[0]).Replace('-', ' ')}");
+ }
+ }
+ builder.AppendLine();
+ }
+
#if NET6_0_OR_GREATER
///