mirror of
https://github.com/modernw/App-Installer-For-Windows-8.x-Reset.git
synced 2026-04-11 17:57:19 +10:00
Organized the project files.
And also fixed some bugs.
This commit is contained in:
@@ -1 +0,0 @@
|
||||
E:/Profiles/Bruce/Documents/Visual Studio 2015/Projects/PriFileFormat/PriFileFormat
|
||||
14
PriFileFormat/ByteSpan.cs
Normal file
14
PriFileFormat/ByteSpan.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public struct ByteSpan
|
||||
{
|
||||
public long Offset;
|
||||
public uint Length;
|
||||
internal ByteSpan (long offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
PriFileFormat/ComStreamWrapper.cs
Normal file
122
PriFileFormat/ComStreamWrapper.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class ComStreamWrapper: Stream
|
||||
{
|
||||
private IStream comStream;
|
||||
public ComStreamWrapper (IStream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException ("stream");
|
||||
comStream = stream;
|
||||
}
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
System.Runtime.InteropServices.ComTypes.STATSTG stat;
|
||||
comStream.Stat (out stat, 1); // STATFLAG_NONAME = 1
|
||||
return stat.cbSize;
|
||||
}
|
||||
}
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
IntPtr posPtr = Marshal.AllocHGlobal (sizeof (long));
|
||||
try
|
||||
{
|
||||
// SEEK_CUR = 1
|
||||
comStream.Seek (0, 1, posPtr);
|
||||
return Marshal.ReadInt64 (posPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal (posPtr);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
// SEEK_SET = 0
|
||||
comStream.Seek (value, 0, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
public override void Flush ()
|
||||
{
|
||||
comStream.Commit (0); // STGC_DEFAULT = 0
|
||||
}
|
||||
public override int Read (byte [] buffer, int offset, int count)
|
||||
{
|
||||
if (offset != 0)
|
||||
throw new NotSupportedException ("Offset != 0 not supported in this wrapper.");
|
||||
|
||||
IntPtr bytesRead = Marshal.AllocHGlobal (sizeof (int));
|
||||
try
|
||||
{
|
||||
comStream.Read (buffer, count, bytesRead);
|
||||
return Marshal.ReadInt32 (bytesRead);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal (bytesRead);
|
||||
}
|
||||
}
|
||||
public override void Write (byte [] buffer, int offset, int count)
|
||||
{
|
||||
if (offset != 0)
|
||||
throw new NotSupportedException ("Offset != 0 not supported in this wrapper.");
|
||||
|
||||
IntPtr bytesWritten = Marshal.AllocHGlobal (sizeof (int));
|
||||
try
|
||||
{
|
||||
comStream.Write (buffer, count, bytesWritten);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal (bytesWritten);
|
||||
}
|
||||
}
|
||||
public override long Seek (long offset, SeekOrigin origin)
|
||||
{
|
||||
int originInt = 0;
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin: originInt = 0; break; // STREAM_SEEK_SET
|
||||
case SeekOrigin.Current: originInt = 1; break; // STREAM_SEEK_CUR
|
||||
case SeekOrigin.End: originInt = 2; break; // STREAM_SEEK_END
|
||||
}
|
||||
|
||||
IntPtr posPtr = Marshal.AllocHGlobal (sizeof (long));
|
||||
try
|
||||
{
|
||||
comStream.Seek (offset, originInt, posPtr);
|
||||
return Marshal.ReadInt64 (posPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal (posPtr);
|
||||
}
|
||||
}
|
||||
public override void SetLength (long value)
|
||||
{
|
||||
comStream.SetSize (value);
|
||||
}
|
||||
~ComStreamWrapper () { comStream = null;}
|
||||
}
|
||||
}
|
||||
71
PriFileFormat/DataItemSection.cs
Normal file
71
PriFileFormat/DataItemSection.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class DataItemSection: Section
|
||||
{
|
||||
private List<ByteSpan> _dataItems;
|
||||
public IReadOnlyList<ByteSpan> DataItems
|
||||
{
|
||||
get { return _dataItems; }
|
||||
}
|
||||
internal const string Identifier = "[mrm_dataitem] \0";
|
||||
internal DataItemSection (PriFile priFile) : base (Identifier, priFile)
|
||||
{
|
||||
}
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
long sectionPosition = (binaryReader.BaseStream as SubStream)?.SubStreamPosition ?? 0;
|
||||
binaryReader.ExpectUInt32 (0);
|
||||
ushort numStrings = binaryReader.ReadUInt16 ();
|
||||
ushort numBlobs = binaryReader.ReadUInt16 ();
|
||||
uint totalDataLength = binaryReader.ReadUInt32 ();
|
||||
List <ByteSpan> dataItems = new List<ByteSpan> (numStrings + numBlobs);
|
||||
long dataStartOffset = binaryReader.BaseStream.Position +
|
||||
numStrings * 2 * sizeof (ushort) + numBlobs * 2 * sizeof (uint);
|
||||
|
||||
for (int i = 0; i < numStrings; i++)
|
||||
{
|
||||
ushort stringOffset = binaryReader.ReadUInt16 ();
|
||||
ushort stringLength = binaryReader.ReadUInt16 ();
|
||||
dataItems.Add (new ByteSpan (sectionPosition + dataStartOffset + stringOffset, stringLength));
|
||||
}
|
||||
|
||||
for (int i = 0; i < numBlobs; i++)
|
||||
{
|
||||
uint blobOffset = binaryReader.ReadUInt32 ();
|
||||
uint blobLength = binaryReader.ReadUInt32 ();
|
||||
dataItems.Add (new ByteSpan (sectionPosition + dataStartOffset + blobOffset, blobLength));
|
||||
}
|
||||
|
||||
_dataItems = dataItems;
|
||||
|
||||
return true;
|
||||
}
|
||||
public void ClearData ()
|
||||
{
|
||||
_dataItems?.Clear ();
|
||||
_dataItems = null;
|
||||
}
|
||||
~DataItemSection () { ClearData (); }
|
||||
}
|
||||
|
||||
public struct DataItemRef
|
||||
{
|
||||
internal SectionRef<DataItemSection> dataItemSection;
|
||||
internal int itemIndex;
|
||||
|
||||
internal DataItemRef (SectionRef<DataItemSection> dataItemSection, int itemIndex)
|
||||
{
|
||||
this.dataItemSection = dataItemSection;
|
||||
this.itemIndex = itemIndex;
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"Data item {itemIndex} in section {dataItemSection.sectionIndex}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
222
PriFileFormat/DecisionInfoSection.cs
Normal file
222
PriFileFormat/DecisionInfoSection.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class DecisionInfoSection: Section
|
||||
{
|
||||
public IReadOnlyList <Decision> Decisions { get; private set; }
|
||||
public IReadOnlyList <QualifierSet> QualifierSets { get; private set; }
|
||||
public IReadOnlyList <Qualifier> Qualifiers { get; private set; }
|
||||
internal const string Identifier = "[mrm_decn_info]\0";
|
||||
internal DecisionInfoSection (PriFile priFile) : base (Identifier, priFile) {}
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
ushort numDistinctQualifiers = binaryReader.ReadUInt16 ();
|
||||
ushort numQualifiers = binaryReader.ReadUInt16 ();
|
||||
ushort numQualifierSets = binaryReader.ReadUInt16 ();
|
||||
ushort numDecisions = binaryReader.ReadUInt16 ();
|
||||
ushort numIndexTableEntries = binaryReader.ReadUInt16 ();
|
||||
ushort totalDataLength = binaryReader.ReadUInt16 ();
|
||||
|
||||
List<DecisionInfo> decisionInfos = new List<DecisionInfo> (numDecisions);
|
||||
for (int i = 0; i < numDecisions; i++)
|
||||
{
|
||||
ushort firstQualifierSetIndexIndex = binaryReader.ReadUInt16 ();
|
||||
ushort numQualifierSetsInDecision = binaryReader.ReadUInt16 ();
|
||||
decisionInfos.Add (new DecisionInfo (firstQualifierSetIndexIndex, numQualifierSetsInDecision));
|
||||
}
|
||||
|
||||
List<QualifierSetInfo> qualifierSetInfos = new List<QualifierSetInfo> (numQualifierSets);
|
||||
for (int i = 0; i < numQualifierSets; i++)
|
||||
{
|
||||
ushort firstQualifierIndexIndex = binaryReader.ReadUInt16 ();
|
||||
ushort numQualifiersInSet = binaryReader.ReadUInt16 ();
|
||||
qualifierSetInfos.Add (new QualifierSetInfo (firstQualifierIndexIndex, numQualifiersInSet));
|
||||
}
|
||||
|
||||
List<QualifierInfo> qualifierInfos = new List<QualifierInfo> (numQualifiers);
|
||||
for (int i = 0; i < numQualifiers; i++)
|
||||
{
|
||||
ushort index = binaryReader.ReadUInt16 ();
|
||||
ushort priority = binaryReader.ReadUInt16 ();
|
||||
ushort fallbackScore = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
qualifierInfos.Add (new QualifierInfo (index, priority, fallbackScore));
|
||||
}
|
||||
|
||||
List<DistinctQualifierInfo> distinctQualifierInfos = new List<DistinctQualifierInfo> (numDistinctQualifiers);
|
||||
for (int i = 0; i < numDistinctQualifiers; i++)
|
||||
{
|
||||
binaryReader.ReadUInt16 ();
|
||||
QualifierType qualifierType = (QualifierType)binaryReader.ReadUInt16 ();
|
||||
binaryReader.ReadUInt16 ();
|
||||
binaryReader.ReadUInt16 ();
|
||||
uint operandValueOffset = binaryReader.ReadUInt32 ();
|
||||
distinctQualifierInfos.Add (new DistinctQualifierInfo (qualifierType, operandValueOffset));
|
||||
}
|
||||
|
||||
ushort [] indexTable = new ushort [numIndexTableEntries];
|
||||
|
||||
for (int i = 0; i < numIndexTableEntries; i++)
|
||||
indexTable [i] = binaryReader.ReadUInt16 ();
|
||||
|
||||
long dataStartOffset = binaryReader.BaseStream.Position;
|
||||
|
||||
List<Qualifier> qualifiers = new List<Qualifier> (numQualifiers);
|
||||
|
||||
for (int i = 0; i < numQualifiers; i++)
|
||||
{
|
||||
DistinctQualifierInfo distinctQualifierInfo = distinctQualifierInfos [qualifierInfos [i].Index];
|
||||
|
||||
binaryReader.BaseStream.Seek (dataStartOffset + distinctQualifierInfo.OperandValueOffset * 2, SeekOrigin.Begin);
|
||||
|
||||
string value = binaryReader.ReadNullTerminatedString (Encoding.Unicode);
|
||||
|
||||
qualifiers.Add (new Qualifier (
|
||||
(ushort)i,
|
||||
distinctQualifierInfo.QualifierType,
|
||||
qualifierInfos [i].Priority,
|
||||
qualifierInfos [i].FallbackScore / 1000f,
|
||||
value));
|
||||
}
|
||||
|
||||
Qualifiers = qualifiers;
|
||||
|
||||
List<QualifierSet> qualifierSets = new List<QualifierSet> (numQualifierSets);
|
||||
|
||||
for (int i = 0; i < numQualifierSets; i++)
|
||||
{
|
||||
List<Qualifier> qualifiersInSet = new List<Qualifier> (qualifierSetInfos [i].NumQualifiersInSet);
|
||||
|
||||
for (int j = 0; j < qualifierSetInfos [i].NumQualifiersInSet; j++)
|
||||
qualifiersInSet.Add (qualifiers [indexTable [qualifierSetInfos [i].FirstQualifierIndexIndex + j]]);
|
||||
|
||||
qualifierSets.Add (new QualifierSet ((ushort)i, qualifiersInSet));
|
||||
}
|
||||
|
||||
QualifierSets = qualifierSets;
|
||||
|
||||
List<Decision> decisions = new List<Decision> (numDecisions);
|
||||
|
||||
for (int i = 0; i < numDecisions; i++)
|
||||
{
|
||||
List<QualifierSet> qualifierSetsInDecision = new List<QualifierSet> (decisionInfos [i].NumQualifierSetsInDecision);
|
||||
|
||||
for (int j = 0; j < decisionInfos [i].NumQualifierSetsInDecision; j++)
|
||||
qualifierSetsInDecision.Add (qualifierSets [indexTable [decisionInfos [i].FirstQualifierSetIndexIndex + j]]);
|
||||
|
||||
decisions.Add (new Decision ((ushort)i, qualifierSetsInDecision));
|
||||
}
|
||||
|
||||
Decisions = decisions;
|
||||
|
||||
return true;
|
||||
}
|
||||
private struct DecisionInfo
|
||||
{
|
||||
public ushort FirstQualifierSetIndexIndex;
|
||||
public ushort NumQualifierSetsInDecision;
|
||||
public DecisionInfo (ushort firstQualifierSetIndexIndex, ushort numQualifierSetsInDecision)
|
||||
{
|
||||
FirstQualifierSetIndexIndex = firstQualifierSetIndexIndex;
|
||||
NumQualifierSetsInDecision = numQualifierSetsInDecision;
|
||||
}
|
||||
}
|
||||
private struct QualifierSetInfo
|
||||
{
|
||||
public ushort FirstQualifierIndexIndex;
|
||||
public ushort NumQualifiersInSet;
|
||||
public QualifierSetInfo (ushort firstQualifierIndexIndex, ushort numQualifiersInSet)
|
||||
{
|
||||
FirstQualifierIndexIndex = firstQualifierIndexIndex;
|
||||
NumQualifiersInSet = numQualifiersInSet;
|
||||
}
|
||||
}
|
||||
private struct QualifierInfo
|
||||
{
|
||||
public ushort Index;
|
||||
public ushort Priority;
|
||||
public ushort FallbackScore;
|
||||
public QualifierInfo (ushort index, ushort priority, ushort fallbackScore)
|
||||
{
|
||||
Index = index;
|
||||
Priority = priority;
|
||||
FallbackScore = fallbackScore;
|
||||
}
|
||||
}
|
||||
private struct DistinctQualifierInfo
|
||||
{
|
||||
public QualifierType QualifierType;
|
||||
public uint OperandValueOffset;
|
||||
public DistinctQualifierInfo (QualifierType qualifierType, uint operandValueOffset)
|
||||
{
|
||||
QualifierType = qualifierType;
|
||||
OperandValueOffset = operandValueOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
public enum QualifierType
|
||||
{
|
||||
Language,
|
||||
Contrast,
|
||||
Scale,
|
||||
HomeRegion,
|
||||
TargetSize,
|
||||
LayoutDirection,
|
||||
Theme,
|
||||
AlternateForm,
|
||||
DXFeatureLevel,
|
||||
Configuration,
|
||||
DeviceFamily,
|
||||
Custom
|
||||
}
|
||||
public class Qualifier
|
||||
{
|
||||
public ushort Index { get; }
|
||||
public QualifierType Type { get; }
|
||||
public ushort Priority { get; }
|
||||
public float FallbackScore { get; }
|
||||
public string Value { get; }
|
||||
internal Qualifier (ushort index, QualifierType type, ushort priority, float fallbackScore, string value)
|
||||
{
|
||||
Index = index;
|
||||
Type = type;
|
||||
Priority = priority;
|
||||
FallbackScore = fallbackScore;
|
||||
Value = value;
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"Index: {Index} Type: {Type} Value: {Value} Priority: {Priority} FallbackScore: {FallbackScore}";
|
||||
}
|
||||
}
|
||||
public class QualifierSet
|
||||
{
|
||||
public ushort Index { get; }
|
||||
public IReadOnlyList <Qualifier> Qualifiers { get; }
|
||||
internal QualifierSet (ushort index, IReadOnlyList <Qualifier> qualifiers)
|
||||
{
|
||||
Index = index;
|
||||
Qualifiers = qualifiers;
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"Index: {Index} Qualifiers: {Qualifiers.Count}";
|
||||
}
|
||||
}
|
||||
public class Decision
|
||||
{
|
||||
public ushort Index { get; }
|
||||
public IReadOnlyList <QualifierSet> QualifierSets { get; }
|
||||
internal Decision (ushort index, IReadOnlyList<QualifierSet> qualifierSets)
|
||||
{
|
||||
Index = index;
|
||||
QualifierSets = qualifierSets;
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"Index: {Index} Qualifier sets: {QualifierSets.Count}";
|
||||
}
|
||||
}
|
||||
}
|
||||
289
PriFileFormat/HierarchicalSchemaSection.cs
Normal file
289
PriFileFormat/HierarchicalSchemaSection.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class HierarchicalSchemaSection: Section
|
||||
{
|
||||
public HierarchicalSchemaVersionInfo Version { get; private set; }
|
||||
public string UniqueName { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public IReadOnlyList <ResourceMapScope> Scopes { get; private set; }
|
||||
public IReadOnlyList <ResourceMapItem> Items { get; private set; }
|
||||
bool extendedVersion;
|
||||
internal const string Identifier1 = "[mrm_hschema] \0";
|
||||
internal const string Identifier2 = "[mrm_hschemaex] ";
|
||||
internal HierarchicalSchemaSection (PriFile priFile, bool extendedVersion) : base (extendedVersion ? Identifier2 : Identifier1, priFile)
|
||||
{
|
||||
this.extendedVersion = extendedVersion;
|
||||
}
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
if (binaryReader.BaseStream.Length == 0)
|
||||
{
|
||||
Version = null;
|
||||
UniqueName = null;
|
||||
Name = null;
|
||||
Scopes = Array.Empty<ResourceMapScope> ();
|
||||
Items = Array.Empty<ResourceMapItem> ();
|
||||
return true;
|
||||
}
|
||||
binaryReader.ExpectUInt16 (1);
|
||||
ushort uniqueNameLength = binaryReader.ReadUInt16 ();
|
||||
ushort nameLength = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
bool extendedHNames;
|
||||
if (extendedVersion)
|
||||
{
|
||||
extendedHNames = false;
|
||||
string extstr = new string (binaryReader.ReadChars (16));
|
||||
if (extstr == "[def_hnamesx] \0") extendedHNames = true;
|
||||
else if (extstr == "[def_hnames] \0") extendedHNames = false;
|
||||
else throw new InvalidDataException ();
|
||||
}
|
||||
else extendedHNames = false;
|
||||
// hierarchical schema version info
|
||||
ushort majorVersion = binaryReader.ReadUInt16 ();
|
||||
ushort minorVersion = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt32 (0);
|
||||
uint checksum = binaryReader.ReadUInt32 ();
|
||||
uint numScopes = binaryReader.ReadUInt32 ();
|
||||
uint numItems = binaryReader.ReadUInt32 ();
|
||||
Version = new HierarchicalSchemaVersionInfo (majorVersion, minorVersion, checksum, numScopes, numItems);
|
||||
UniqueName = binaryReader.ReadNullTerminatedString (Encoding.Unicode);
|
||||
Name = binaryReader.ReadNullTerminatedString (Encoding.Unicode);
|
||||
if (UniqueName.Length != uniqueNameLength - 1 || Name.Length != nameLength - 1) throw new InvalidDataException ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
ushort maxFullPathLength = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
binaryReader.ExpectUInt32 (numScopes + numItems);
|
||||
binaryReader.ExpectUInt32 (numScopes);
|
||||
binaryReader.ExpectUInt32 (numItems);
|
||||
uint unicodeDataLength = binaryReader.ReadUInt32 ();
|
||||
binaryReader.ReadUInt32 (); // meaning unknown
|
||||
if (extendedHNames) binaryReader.ReadUInt32 (); // meaning unknown
|
||||
List <ScopeAndItemInfo> scopeAndItemInfos = new List<ScopeAndItemInfo> ((int)(numScopes + numItems));
|
||||
for (int i = 0; i < numScopes + numItems; i++)
|
||||
{
|
||||
ushort parent = binaryReader.ReadUInt16 ();
|
||||
ushort fullPathLength = binaryReader.ReadUInt16 ();
|
||||
char uppercaseFirstChar = (char)binaryReader.ReadUInt16 ();
|
||||
byte nameLength2 = binaryReader.ReadByte ();
|
||||
byte flags = binaryReader.ReadByte ();
|
||||
uint nameOffset = binaryReader.ReadUInt16 () | (uint)((flags & 0xF) << 16);
|
||||
ushort index = binaryReader.ReadUInt16 ();
|
||||
bool isScope = (flags & 0x10) != 0;
|
||||
bool nameInAscii = (flags & 0x20) != 0;
|
||||
scopeAndItemInfos.Add (new ScopeAndItemInfo (parent, fullPathLength, isScope, nameInAscii, nameOffset, index));
|
||||
}
|
||||
List <ScopeExInfo> scopeExInfos = new List <ScopeExInfo> ((int)numScopes);
|
||||
for (int i = 0; i < numScopes; i++)
|
||||
{
|
||||
ushort scopeIndex = binaryReader.ReadUInt16 ();
|
||||
ushort childCount = binaryReader.ReadUInt16 ();
|
||||
ushort firstChildIndex = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
scopeExInfos.Add (new ScopeExInfo (scopeIndex, childCount, firstChildIndex));
|
||||
}
|
||||
ushort [] itemIndexPropertyToIndex = new ushort [numItems];
|
||||
for (int i = 0; i < numItems; i++) itemIndexPropertyToIndex [i] = binaryReader.ReadUInt16 ();
|
||||
long unicodeDataOffset = binaryReader.BaseStream.Position;
|
||||
long asciiDataOffset = binaryReader.BaseStream.Position + unicodeDataLength * 2;
|
||||
ResourceMapScope [] scopes = new ResourceMapScope [numScopes];
|
||||
ResourceMapItem [] items = new ResourceMapItem [numItems];
|
||||
for (int i = 0; i < numScopes + numItems; i++)
|
||||
{
|
||||
long pos;
|
||||
if (scopeAndItemInfos [i].NameInAscii) pos = asciiDataOffset + scopeAndItemInfos [i].NameOffset;
|
||||
else pos = unicodeDataOffset + scopeAndItemInfos [i].NameOffset * 2;
|
||||
binaryReader.BaseStream.Seek (pos, SeekOrigin.Begin);
|
||||
string name;
|
||||
if (scopeAndItemInfos [i].FullPathLength != 0) name = binaryReader.ReadNullTerminatedString (scopeAndItemInfos [i].NameInAscii ? Encoding.ASCII : Encoding.Unicode);
|
||||
else name = string.Empty;
|
||||
ushort index = scopeAndItemInfos [i].Index;
|
||||
if (scopeAndItemInfos [i].IsScope)
|
||||
{
|
||||
if (scopes [index] != null) throw new InvalidDataException ();
|
||||
scopes [index] = new ResourceMapScope (index, null, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (items [index] != null) throw new InvalidDataException ();
|
||||
items [index] = new ResourceMapItem (index, null, name);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < numScopes + numItems; i++)
|
||||
{
|
||||
ushort index = scopeAndItemInfos [i].Index;
|
||||
ushort parent = scopeAndItemInfos [scopeAndItemInfos [i].Parent].Index;
|
||||
if (parent != 0xFFFF)
|
||||
if (scopeAndItemInfos [i].IsScope)
|
||||
{
|
||||
if (parent != index) scopes [index].Parent = scopes [parent];
|
||||
}
|
||||
else items [index].Parent = scopes [parent];
|
||||
}
|
||||
for (int i = 0; i < numScopes; i++)
|
||||
{
|
||||
List <ResourceMapEntry> children = new List<ResourceMapEntry> (scopeExInfos [i].ChildCount);
|
||||
for (int j = 0; j < scopeExInfos [i].ChildCount; j++)
|
||||
{
|
||||
ScopeAndItemInfo saiInfo = scopeAndItemInfos [scopeExInfos [i].FirstChildIndex + j];
|
||||
if (saiInfo.IsScope) children.Add (scopes [saiInfo.Index]);
|
||||
else children.Add (items [saiInfo.Index]);
|
||||
}
|
||||
scopes [i].Children = children;
|
||||
}
|
||||
Scopes = scopes;
|
||||
Items = items;
|
||||
//if (checksum != ComputeHierarchicalSchemaVersionInfoChecksum())
|
||||
// throw new Exception();
|
||||
return true;
|
||||
}
|
||||
private struct ScopeAndItemInfo
|
||||
{
|
||||
public ushort Parent;
|
||||
public ushort FullPathLength;
|
||||
public bool IsScope;
|
||||
public bool NameInAscii;
|
||||
public uint NameOffset;
|
||||
public ushort Index;
|
||||
public ScopeAndItemInfo (ushort parent, ushort fullPathLength, bool isScope, bool nameInAscii, uint nameOffset, ushort index)
|
||||
{
|
||||
Parent = parent;
|
||||
FullPathLength = fullPathLength;
|
||||
IsScope = isScope;
|
||||
NameInAscii = nameInAscii;
|
||||
NameOffset = nameOffset;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
private struct ScopeExInfo
|
||||
{
|
||||
public ushort ScopeIndex;
|
||||
public ushort ChildCount;
|
||||
public ushort FirstChildIndex;
|
||||
public ScopeExInfo (ushort scopeIndex, ushort childCount, ushort firstChildIndex)
|
||||
{
|
||||
ScopeIndex = scopeIndex;
|
||||
ChildCount = childCount;
|
||||
FirstChildIndex = firstChildIndex;
|
||||
}
|
||||
}
|
||||
~HierarchicalSchemaSection ()
|
||||
{
|
||||
Version = null;
|
||||
foreach (var item in Items) { item.Parent = null; }
|
||||
foreach (var scope in Scopes) { scope.Parent = null; }
|
||||
Scopes = null;
|
||||
Items = null;
|
||||
}
|
||||
// Checksum computation is buggy for some files
|
||||
|
||||
//private uint ComputeHierarchicalSchemaVersionInfoChecksum()
|
||||
//{
|
||||
// CRC32 crc32 = new CRC32();
|
||||
|
||||
// StringChecksum(crc32, UniqueName);
|
||||
// StringChecksum(crc32, Name);
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(Version.MajorVersion), 0, 2, new byte[2], 0);
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(Version.MinorVersion), 0, 2, new byte[2], 0);
|
||||
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(0), 0, 4, new byte[4], 0);
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(0), 0, 4, new byte[4], 0);
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(1), 0, 4, new byte[4], 0);
|
||||
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(Scopes.Count), 0, 4, new byte[4], 0);
|
||||
// foreach (ResourceMapScope scope in Scopes)
|
||||
// StringChecksum(crc32, scope.FullName.Replace('\\', '/').TrimStart('/'));
|
||||
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(0), 0, 4, new byte[4], 0);
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(0), 0, 4, new byte[4], 0);
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(1), 0, 4, new byte[4], 0);
|
||||
|
||||
// crc32.TransformBlock(BitConverter.GetBytes(Items.Count), 0, 4, new byte[4], 0);
|
||||
// foreach (ResourceMapItem item in Items)
|
||||
// StringChecksum(crc32, item.FullName.Replace('\\', '/').TrimStart('/'));
|
||||
|
||||
// return crc32.Result;
|
||||
//}
|
||||
|
||||
//private void StringChecksum(CRC32 crc32, string s)
|
||||
//{
|
||||
// if (s == null)
|
||||
// {
|
||||
// byte[] data = new byte[8];
|
||||
|
||||
// crc32.TransformBlock(data, 0, data.Length, new byte[data.Length], 0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// byte[] data = Encoding.Unicode.GetBytes(s.ToLowerInvariant() + '\0');
|
||||
// byte[] l = BitConverter.GetBytes(data.Length);
|
||||
|
||||
// crc32.TransformBlock(l, 0, l.Length, new byte[l.Length], 0);
|
||||
// crc32.TransformBlock(data, 0, data.Length, new byte[data.Length], 0);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
public class HierarchicalSchemaVersionInfo
|
||||
{
|
||||
public ushort MajorVersion { get; }
|
||||
public ushort MinorVersion { get; }
|
||||
public uint Checksum { get; }
|
||||
public uint NumScopes { get; }
|
||||
public uint NumItems { get; }
|
||||
internal HierarchicalSchemaVersionInfo (ushort majorVersion, ushort minorVersion, uint checksum, uint numScopes, uint numItems)
|
||||
{
|
||||
MajorVersion = majorVersion;
|
||||
MinorVersion = minorVersion;
|
||||
Checksum = checksum;
|
||||
NumScopes = numScopes;
|
||||
NumItems = numItems;
|
||||
}
|
||||
}
|
||||
public class ResourceMapEntry
|
||||
{
|
||||
public ushort Index { get; }
|
||||
public ResourceMapScope Parent { get; internal set; }
|
||||
public string Name { get; }
|
||||
internal ResourceMapEntry (ushort index, ResourceMapScope parent, string name)
|
||||
{
|
||||
Index = index;
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
}
|
||||
string fullName;
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fullName == null)
|
||||
if (Parent == null) fullName = Name;
|
||||
else fullName = Parent.FullName + "\\" + Name;
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
~ResourceMapEntry () { Parent = null; }
|
||||
}
|
||||
public class ResourceMapScope: ResourceMapEntry
|
||||
{
|
||||
internal ResourceMapScope (ushort index, ResourceMapScope parent, string name) : base (index, parent, name) {}
|
||||
public IReadOnlyList <ResourceMapEntry> Children { get; internal set; }
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"Scope {Index} {FullName} ({Children.Count} children)";
|
||||
}
|
||||
~ResourceMapScope () { Children = null; }
|
||||
}
|
||||
public class ResourceMapItem: ResourceMapEntry
|
||||
{
|
||||
internal ResourceMapItem (ushort index, ResourceMapScope parent, string name) : base (index, parent, name) {}
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"Item {Index} {FullName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
74
PriFileFormat/PriDescriptorSection.cs
Normal file
74
PriFileFormat/PriDescriptorSection.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class PriDescriptorSection: Section
|
||||
{
|
||||
public PriDescriptorFlags PriFlags { get; private set; }
|
||||
public IReadOnlyList <SectionRef <HierarchicalSchemaSection>> HierarchicalSchemaSections { get; private set; }
|
||||
public IReadOnlyList <SectionRef <DecisionInfoSection>> DecisionInfoSections { get; private set; }
|
||||
public IReadOnlyList<SectionRef <ResourceMapSection>> ResourceMapSections { get; private set; }
|
||||
public IReadOnlyList <SectionRef <ReferencedFileSection>> ReferencedFileSections { get; private set; }
|
||||
public IReadOnlyList<SectionRef <DataItemSection>> DataItemSections { get; private set; }
|
||||
public SectionRef <ResourceMapSection> ?PrimaryResourceMapSection { get; private set; }
|
||||
internal const string Identifier = "[mrm_pridescex]\0";
|
||||
internal PriDescriptorSection (PriFile priFile) : base (Identifier, priFile) {}
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
PriFlags = (PriDescriptorFlags)binaryReader.ReadUInt16 ();
|
||||
ushort includedFileListSection = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
ushort numHierarchicalSchemaSections = binaryReader.ReadUInt16 ();
|
||||
ushort numDecisionInfoSections = binaryReader.ReadUInt16 ();
|
||||
ushort numResourceMapSections = binaryReader.ReadUInt16 ();
|
||||
ushort primaryResourceMapSection = binaryReader.ReadUInt16 ();
|
||||
if (primaryResourceMapSection != 0xFFFF) PrimaryResourceMapSection = new SectionRef<ResourceMapSection> (primaryResourceMapSection);
|
||||
else PrimaryResourceMapSection = null;
|
||||
ushort numReferencedFileSections = binaryReader.ReadUInt16 ();
|
||||
ushort numDataItemSections = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
List <SectionRef <HierarchicalSchemaSection>> hierarchicalSchemaSections = new List <SectionRef <HierarchicalSchemaSection>> (numHierarchicalSchemaSections);
|
||||
for (int i = 0; i < numHierarchicalSchemaSections; i++) hierarchicalSchemaSections.Add (new SectionRef <HierarchicalSchemaSection> (binaryReader.ReadUInt16 ()));
|
||||
HierarchicalSchemaSections = hierarchicalSchemaSections;
|
||||
List <SectionRef <DecisionInfoSection>> decisionInfoSections = new List <SectionRef <DecisionInfoSection>> (numDecisionInfoSections);
|
||||
for (int i = 0; i < numDecisionInfoSections; i++) decisionInfoSections.Add (new SectionRef <DecisionInfoSection> (binaryReader.ReadUInt16 ()));
|
||||
DecisionInfoSections = decisionInfoSections;
|
||||
List<SectionRef<ResourceMapSection>> resourceMapSections = new List<SectionRef<ResourceMapSection>> (numResourceMapSections);
|
||||
for (int i = 0; i < numResourceMapSections; i++) resourceMapSections.Add (new SectionRef <ResourceMapSection> (binaryReader.ReadUInt16 ()));
|
||||
ResourceMapSections = resourceMapSections;
|
||||
List <SectionRef <ReferencedFileSection>> referencedFileSections = new List <SectionRef <ReferencedFileSection>> (numReferencedFileSections);
|
||||
for (int i = 0; i < numReferencedFileSections; i++) referencedFileSections.Add (new SectionRef <ReferencedFileSection> (binaryReader.ReadUInt16 ()));
|
||||
ReferencedFileSections = referencedFileSections;
|
||||
List <SectionRef <DataItemSection>> dataItemSections = new List <SectionRef <DataItemSection>> (numDataItemSections);
|
||||
for (int i = 0; i < numDataItemSections; i++) dataItemSections.Add (new SectionRef <DataItemSection> (binaryReader.ReadUInt16 ()));
|
||||
DataItemSections = dataItemSections;
|
||||
return true;
|
||||
}
|
||||
~PriDescriptorSection ()
|
||||
{
|
||||
HierarchicalSchemaSections = null;
|
||||
DecisionInfoSections = null;
|
||||
ResourceMapSections = null;
|
||||
ReferencedFileSections = null;
|
||||
DataItemSections = null;
|
||||
PrimaryResourceMapSection = null;
|
||||
}
|
||||
}
|
||||
[Flags]
|
||||
public enum PriDescriptorFlags: ushort
|
||||
{
|
||||
AutoMerge = 1,
|
||||
IsDeploymentMergeable = 2,
|
||||
IsDeploymentMergeResult = 4,
|
||||
IsAutomergeMergeResult = 8
|
||||
}
|
||||
public struct SectionRef <T> where T : Section
|
||||
{
|
||||
internal int sectionIndex;
|
||||
internal SectionRef (int sectionIndex) { this.sectionIndex = sectionIndex; }
|
||||
public override string ToString () { return $"Section {typeof (T).Name} at index {sectionIndex}"; }
|
||||
}
|
||||
|
||||
}
|
||||
187
PriFileFormat/PriFile.cs
Normal file
187
PriFileFormat/PriFile.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class PriFile: IDisposable
|
||||
{
|
||||
public string Version { get; private set; }
|
||||
public uint TotalFileSize { get; private set; }
|
||||
public IReadOnlyList <TocEntry> TableOfContents { get; private set; }
|
||||
public IReadOnlyList <Section> Sections { get; private set; }
|
||||
private bool _disposed = false;
|
||||
private Stream _internalStream; // 跟踪内部流
|
||||
private PriFile ()
|
||||
{
|
||||
}
|
||||
|
||||
public static PriFile Parse (Stream stream)
|
||||
{
|
||||
PriFile priFile = new PriFile ();
|
||||
priFile.ParseInternal (stream, false);
|
||||
return priFile;
|
||||
}
|
||||
|
||||
public static PriFile Parse (System.Runtime.InteropServices.ComTypes.IStream stream)
|
||||
{
|
||||
|
||||
ComStreamWrapper csw = new ComStreamWrapper (stream);
|
||||
PriFile priFile = new PriFile ();
|
||||
priFile.ParseInternal (csw, true);
|
||||
return priFile;
|
||||
}
|
||||
|
||||
private void ParseInternal (Stream stream, bool ownStream)
|
||||
{
|
||||
if (ownStream) { _internalStream = stream; }
|
||||
using (BinaryReader binaryReader = new BinaryReader (stream, Encoding.ASCII, true))
|
||||
{
|
||||
long fileStartOffset = binaryReader.BaseStream.Position;
|
||||
|
||||
string magic = new string (binaryReader.ReadChars (8));
|
||||
|
||||
switch (magic)
|
||||
{
|
||||
case "mrm_pri0":
|
||||
case "mrm_pri1":
|
||||
case "mrm_pri2":
|
||||
case "mrm_prif":
|
||||
Version = magic;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException ("Data does not start with a PRI file header.");
|
||||
}
|
||||
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
binaryReader.ExpectUInt16 (1);
|
||||
TotalFileSize = binaryReader.ReadUInt32 ();
|
||||
uint tocOffset = binaryReader.ReadUInt32 ();
|
||||
uint sectionStartOffset = binaryReader.ReadUInt32 ();
|
||||
ushort numSections = binaryReader.ReadUInt16 ();
|
||||
|
||||
binaryReader.ExpectUInt16 (0xFFFF);
|
||||
binaryReader.ExpectUInt32 (0);
|
||||
|
||||
binaryReader.BaseStream.Seek (fileStartOffset + TotalFileSize - 16, SeekOrigin.Begin);
|
||||
|
||||
binaryReader.ExpectUInt32 (0xDEFFFADE);
|
||||
binaryReader.ExpectUInt32 (TotalFileSize);
|
||||
binaryReader.ExpectString (magic);
|
||||
|
||||
binaryReader.BaseStream.Seek (tocOffset, SeekOrigin.Begin);
|
||||
|
||||
List<TocEntry> toc = new List<TocEntry> (numSections);
|
||||
|
||||
for (int i = 0; i < numSections; i++)
|
||||
toc.Add (TocEntry.Parse (binaryReader));
|
||||
|
||||
TableOfContents = toc;
|
||||
|
||||
Section [] sections = new Section [numSections];
|
||||
|
||||
Sections = sections;
|
||||
|
||||
bool parseSuccess = false;
|
||||
bool parseFailure = false;
|
||||
|
||||
do
|
||||
{
|
||||
for (int i = 0; i < sections.Length; i++)
|
||||
if (sections [i] == null)
|
||||
{
|
||||
binaryReader.BaseStream.Seek (sectionStartOffset + toc [i].SectionOffset, SeekOrigin.Begin);
|
||||
|
||||
Section section = Section.CreateForIdentifier (toc [i].SectionIdentifier, this);
|
||||
|
||||
if (section.Parse (binaryReader))
|
||||
{
|
||||
sections [i] = section;
|
||||
parseSuccess = true;
|
||||
}
|
||||
else
|
||||
parseFailure = true;
|
||||
}
|
||||
} while (parseFailure && parseSuccess);
|
||||
|
||||
if (parseFailure)
|
||||
throw new InvalidDataException ();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
Dispose (true);
|
||||
GC.SuppressFinalize (this);
|
||||
}
|
||||
protected virtual void Dispose (bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
// 释放托管资源
|
||||
if (_internalStream != null)
|
||||
{
|
||||
_internalStream.Dispose ();
|
||||
_internalStream = null;
|
||||
}
|
||||
|
||||
// 释放 section 内容
|
||||
if (Sections != null)
|
||||
{
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
var unknown = section as UnknownSection;
|
||||
if (unknown != null)
|
||||
unknown.ClearContent ();
|
||||
|
||||
var dataSection = section as DataItemSection;
|
||||
if (dataSection != null)
|
||||
dataSection.ClearData ();
|
||||
}
|
||||
Sections = null;
|
||||
}
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
~PriFile ()
|
||||
{
|
||||
Dispose (false);
|
||||
}
|
||||
|
||||
PriDescriptorSection priDescriptorSection;
|
||||
|
||||
public PriDescriptorSection PriDescriptorSection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (priDescriptorSection == null) priDescriptorSection = Sections.OfType<PriDescriptorSection> ().Single ();
|
||||
return priDescriptorSection;
|
||||
}
|
||||
}
|
||||
|
||||
public T GetSectionByRef<T> (SectionRef<T> sectionRef) where T : Section
|
||||
{
|
||||
return (T)Sections [sectionRef.sectionIndex];
|
||||
}
|
||||
|
||||
public ResourceMapItem GetResourceMapItemByRef (ResourceMapItemRef resourceMapItemRef)
|
||||
{
|
||||
return GetSectionByRef (resourceMapItemRef.schemaSection).Items [resourceMapItemRef.itemIndex];
|
||||
}
|
||||
|
||||
public ByteSpan GetDataItemByRef (DataItemRef dataItemRef)
|
||||
{
|
||||
return GetSectionByRef (dataItemRef.dataItemSection).DataItems [dataItemRef.itemIndex];
|
||||
}
|
||||
|
||||
public ReferencedFile GetReferencedFileByRef (ReferencedFileRef referencedFileRef)
|
||||
{
|
||||
return GetSectionByRef (PriDescriptorSection.ReferencedFileSections.First ()).ReferencedFiles [referencedFileRef.fileIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
87
PriFileFormat/PriFile.csproj
Normal file
87
PriFileFormat/PriFile.csproj
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{EF4012D4-EF08-499C-B803-177739350B2D}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PriFileFormat</RootNamespace>
|
||||
<AssemblyName>PriFileFormat</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
<RegisterForComInterop>false</RegisterForComInterop>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<RegisterForComInterop>false</RegisterForComInterop>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ByteSpan.cs" />
|
||||
<Compile Include="ComStreamWrapper.cs" />
|
||||
<Compile Include="DataItemSection.cs" />
|
||||
<Compile Include="DecisionInfoSection.cs" />
|
||||
<Compile Include="HierarchicalSchemaSection.cs" />
|
||||
<Compile Include="PriDescriptorSection.cs" />
|
||||
<Compile Include="PriFile.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ReferencedFileSection.cs" />
|
||||
<Compile Include="Replenish.cs" />
|
||||
<Compile Include="ResourceMapSection.cs" />
|
||||
<Compile Include="ReverseMapSection.cs" />
|
||||
<Compile Include="Section.cs" />
|
||||
<Compile Include="SubStream.cs" />
|
||||
<Compile Include="TocEntry.cs" />
|
||||
<Compile Include="UnknownSection.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
36
PriFileFormat/Properties/AssemblyInfo.cs
Normal file
36
PriFileFormat/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 有关程序集的一般信息由以下
|
||||
// 控制。更改这些特性值可修改
|
||||
// 与程序集关联的信息。
|
||||
[assembly: AssemblyTitle ("Pri File Format")]
|
||||
[assembly: AssemblyDescription ("Pri File Reader")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany ("")]
|
||||
[assembly: AssemblyProduct ("Pri File Reader")]
|
||||
[assembly: AssemblyCopyright ("© 2025 Windows Modern. Based on chausner/PriTools (Apache 2.0). All rights reserved.")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
//将 ComVisible 设置为 false 将使此程序集中的类型
|
||||
//对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
|
||||
//请将此类型的 ComVisible 特性设置为 true。
|
||||
[assembly: ComVisible (false)]
|
||||
|
||||
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
|
||||
[assembly: Guid("ef4012d4-ef08-499c-b803-177739350b2d")]
|
||||
|
||||
// 程序集的版本信息由下列四个值组成:
|
||||
//
|
||||
// 主版本
|
||||
// 次版本
|
||||
// 生成号
|
||||
// 修订号
|
||||
//
|
||||
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
|
||||
// 方法是按如下所示使用“*”: :
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
63
PriFileFormat/Properties/Resources.Designer.cs
generated
Normal file
63
PriFileFormat/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 此代码由工具生成。
|
||||
// 运行时版本:4.0.30319.42000
|
||||
//
|
||||
// 对此文件的更改可能会导致不正确的行为,并且如果
|
||||
// 重新生成代码,这些更改将会丢失。
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PriFileFormat.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 一个强类型的资源类,用于查找本地化的字符串等。
|
||||
/// </summary>
|
||||
// 此类是由 StronglyTypedResourceBuilder
|
||||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
|
||||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
|
||||
// (以 /str 作为命令选项),或重新生成 VS 项目。
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回此类使用的缓存的 ResourceManager 实例。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PriFileFormat.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用此强类型资源类,为所有资源查找
|
||||
/// 重写当前线程的 CurrentUICulture 属性。
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
PriFileFormat/Properties/Resources.resx
Normal file
101
PriFileFormat/Properties/Resources.resx
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
174
PriFileFormat/ReferencedFileSection.cs
Normal file
174
PriFileFormat/ReferencedFileSection.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class ReferencedFileSection: Section
|
||||
{
|
||||
public List <ReferencedFile> ReferencedFiles { get; private set; }
|
||||
internal const string Identifier = "[def_file_list]\0";
|
||||
internal ReferencedFileSection (PriFile priFile) : base (Identifier, priFile) {}
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
ushort numRoots = binaryReader.ReadUInt16 ();
|
||||
ushort numFolders = binaryReader.ReadUInt16 ();
|
||||
ushort numFiles = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
uint totalDataLength = binaryReader.ReadUInt32 ();
|
||||
List <FolderInfo> folderInfos = new List <FolderInfo> (numFolders);
|
||||
for (int i = 0; i < numFolders; i++)
|
||||
{
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
ushort parentFolder = binaryReader.ReadUInt16 ();
|
||||
ushort numFoldersInFolder = binaryReader.ReadUInt16 ();
|
||||
ushort firstFolderInFolder = binaryReader.ReadUInt16 ();
|
||||
ushort numFilesInFolder = binaryReader.ReadUInt16 ();
|
||||
ushort firstFileInFolder = binaryReader.ReadUInt16 ();
|
||||
ushort folderNameLength = binaryReader.ReadUInt16 ();
|
||||
ushort fullPathLength = binaryReader.ReadUInt16 ();
|
||||
uint folderNameOffset = binaryReader.ReadUInt32 ();
|
||||
folderInfos.Add (new FolderInfo (parentFolder, numFoldersInFolder, firstFolderInFolder, numFilesInFolder, firstFileInFolder, folderNameLength, fullPathLength, folderNameOffset));
|
||||
}
|
||||
List <FileInfo> fileInfos = new List <FileInfo> (numFiles);
|
||||
for (int i = 0; i < numFiles; i++)
|
||||
{
|
||||
binaryReader.ReadUInt16 ();
|
||||
ushort parentFolder = binaryReader.ReadUInt16 ();
|
||||
ushort fullPathLength = binaryReader.ReadUInt16 ();
|
||||
ushort fileNameLength = binaryReader.ReadUInt16 ();
|
||||
uint fileNameOffset = binaryReader.ReadUInt32 ();
|
||||
fileInfos.Add (new FileInfo (parentFolder, fullPathLength, fileNameLength, fileNameOffset));
|
||||
}
|
||||
long dataStartPosition = binaryReader.BaseStream.Position;
|
||||
List <ReferencedFolder> referencedFolders = new List <ReferencedFolder> (numFolders);
|
||||
for (int i = 0; i < numFolders; i++)
|
||||
{
|
||||
binaryReader.BaseStream.Seek (dataStartPosition + folderInfos [i].FolderNameOffset * 2, SeekOrigin.Begin);
|
||||
string name = binaryReader.ReadString (Encoding.Unicode, folderInfos [i].FolderNameLength);
|
||||
referencedFolders.Add (new ReferencedFolder (null, name));
|
||||
}
|
||||
for (int i = 0; i < numFolders; i++)
|
||||
if (folderInfos [i].ParentFolder != 0xFFFF)
|
||||
referencedFolders [i].Parent = referencedFolders [folderInfos [i].ParentFolder];
|
||||
List <ReferencedFile> referencedFiles = new List<ReferencedFile> (numFiles);
|
||||
for (int i = 0; i < numFiles; i++)
|
||||
{
|
||||
binaryReader.BaseStream.Seek (dataStartPosition + fileInfos [i].FileNameOffset * 2, SeekOrigin.Begin);
|
||||
|
||||
string name = binaryReader.ReadString (Encoding.Unicode, fileInfos [i].FileNameLength);
|
||||
|
||||
ReferencedFolder parentFolder;
|
||||
|
||||
if (fileInfos [i].ParentFolder != 0xFFFF)
|
||||
parentFolder = referencedFolders [fileInfos [i].ParentFolder];
|
||||
else
|
||||
parentFolder = null;
|
||||
|
||||
referencedFiles.Add (new ReferencedFile (parentFolder, name));
|
||||
}
|
||||
|
||||
for (int i = 0; i < numFolders; i++)
|
||||
{
|
||||
List<ReferencedEntry> children = new List<ReferencedEntry> (folderInfos [i].NumFoldersInFolder + folderInfos [i].NumFilesInFolder);
|
||||
|
||||
for (int j = 0; j < folderInfos [i].NumFoldersInFolder; j++)
|
||||
children.Add (referencedFolders [folderInfos [i].FirstFolderInFolder + j]);
|
||||
|
||||
for (int j = 0; j < folderInfos [i].NumFilesInFolder; j++)
|
||||
children.Add (referencedFiles [folderInfos [i].FirstFileInFolder + j]);
|
||||
|
||||
referencedFolders [i].Children = children;
|
||||
}
|
||||
|
||||
ReferencedFiles = referencedFiles;
|
||||
|
||||
return true;
|
||||
}
|
||||
private struct FolderInfo
|
||||
{
|
||||
public ushort ParentFolder;
|
||||
public ushort NumFoldersInFolder;
|
||||
public ushort FirstFolderInFolder;
|
||||
public ushort NumFilesInFolder;
|
||||
public ushort FirstFileInFolder;
|
||||
public ushort FolderNameLength;
|
||||
public ushort FullPathLength;
|
||||
public uint FolderNameOffset;
|
||||
public FolderInfo (ushort parentFolder, ushort numFoldersInFolder, ushort firstFolderInFolder, ushort numFilesInFolder, ushort firstFileInFolder, ushort folderNameLength, ushort fullPathLength, uint folderNameOffset)
|
||||
{
|
||||
ParentFolder = parentFolder;
|
||||
NumFoldersInFolder = numFoldersInFolder;
|
||||
FirstFolderInFolder = firstFolderInFolder;
|
||||
NumFilesInFolder = numFilesInFolder;
|
||||
FirstFileInFolder = firstFileInFolder;
|
||||
FolderNameLength = folderNameLength;
|
||||
FullPathLength = fullPathLength;
|
||||
FolderNameOffset = folderNameOffset;
|
||||
}
|
||||
}
|
||||
private struct FileInfo
|
||||
{
|
||||
public ushort ParentFolder;
|
||||
public ushort FullPathLength;
|
||||
public ushort FileNameLength;
|
||||
public uint FileNameOffset;
|
||||
public FileInfo (ushort parentFolder, ushort fullPathLength, ushort fileNameLength, uint fileNameOffset)
|
||||
{
|
||||
ParentFolder = parentFolder;
|
||||
FullPathLength = fullPathLength;
|
||||
FileNameLength = fileNameLength;
|
||||
FileNameOffset = fileNameOffset;
|
||||
}
|
||||
}
|
||||
~ReferencedFileSection ()
|
||||
{
|
||||
foreach (var file in ReferencedFiles) { file.Parent = null; }
|
||||
ReferencedFiles = null;
|
||||
}
|
||||
}
|
||||
public class ReferencedEntry
|
||||
{
|
||||
public ReferencedFolder Parent { get; internal set; }
|
||||
public string Name { get; }
|
||||
internal ReferencedEntry (ReferencedFolder parent, string name)
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
}
|
||||
string fullName;
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fullName == null)
|
||||
if (Parent == null)
|
||||
fullName = Name;
|
||||
else
|
||||
fullName = Parent.FullName + "\\" + Name;
|
||||
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
~ReferencedEntry () { Parent = null; }
|
||||
}
|
||||
public class ReferencedFolder: ReferencedEntry
|
||||
{
|
||||
internal ReferencedFolder (ReferencedFolder parent, string name) : base (parent, name) {}
|
||||
public IReadOnlyList<ReferencedEntry> Children { get; internal set; }
|
||||
~ReferencedFolder () { Children = null; }
|
||||
}
|
||||
public class ReferencedFile: ReferencedEntry
|
||||
{
|
||||
internal ReferencedFile (ReferencedFolder parent, string name) : base (parent, name) {}
|
||||
}
|
||||
public struct ReferencedFileRef
|
||||
{
|
||||
internal int fileIndex;
|
||||
internal ReferencedFileRef (int fileIndex)
|
||||
{
|
||||
this.fileIndex = fileIndex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
55
PriFileFormat/Replenish.cs
Normal file
55
PriFileFormat/Replenish.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
internal static class Replenish
|
||||
{
|
||||
public static string ReadString (this BinaryReader reader, Encoding encoding, int length)
|
||||
{
|
||||
using (BinaryReader r = new BinaryReader (reader.BaseStream, encoding, true))
|
||||
return new string (r.ReadChars (length));
|
||||
}
|
||||
|
||||
public static string ReadNullTerminatedString (this BinaryReader reader, Encoding encoding)
|
||||
{
|
||||
using (BinaryReader r = new BinaryReader (reader.BaseStream, encoding, true))
|
||||
{
|
||||
StringBuilder result = new StringBuilder ();
|
||||
char c;
|
||||
while ((c = r.ReadChar ()) != '\0')
|
||||
result.Append (c);
|
||||
return result.ToString ();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExpectByte (this BinaryReader reader, byte expectedValue)
|
||||
{
|
||||
if (reader.ReadByte () != expectedValue)
|
||||
throw new InvalidDataException ("Unexpected value read.");
|
||||
}
|
||||
|
||||
public static void ExpectUInt16 (this BinaryReader reader, ushort expectedValue)
|
||||
{
|
||||
if (reader.ReadUInt16 () != expectedValue)
|
||||
throw new InvalidDataException ("Unexpected value read.");
|
||||
}
|
||||
|
||||
public static void ExpectUInt32 (this BinaryReader reader, uint expectedValue)
|
||||
{
|
||||
if (reader.ReadUInt32 () != expectedValue)
|
||||
throw new InvalidDataException ("Unexpected value read.");
|
||||
}
|
||||
|
||||
public static void ExpectString (this BinaryReader reader, string s)
|
||||
{
|
||||
if (new string (reader.ReadChars (s.Length)) != s)
|
||||
throw new InvalidDataException ("Unexpected value read.");
|
||||
}
|
||||
public static string Limit (this string s, int length)
|
||||
{
|
||||
if (string.IsNullOrEmpty (s)) return s;
|
||||
return s.Length <= length ? s : s.Substring (0, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
390
PriFileFormat/ResourceMapSection.cs
Normal file
390
PriFileFormat/ResourceMapSection.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class ResourceMapSection: Section
|
||||
{
|
||||
public HierarchicalSchemaReference HierarchicalSchemaReference { get; private set; }
|
||||
public SectionRef<HierarchicalSchemaSection> SchemaSection { get; private set; }
|
||||
public SectionRef<DecisionInfoSection> DecisionInfoSection { get; private set; }
|
||||
public IReadOnlyDictionary <ushort, CandidateSet> CandidateSets { get; private set; }
|
||||
bool version2;
|
||||
internal const string Identifier1 = "[mrm_res_map__]\0";
|
||||
internal const string Identifier2 = "[mrm_res_map2_]\0";
|
||||
internal ResourceMapSection (PriFile priFile, bool version2) : base (version2 ? Identifier2 : Identifier1, priFile)
|
||||
{
|
||||
this.version2 = version2;
|
||||
}
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
long sectionPosition = (binaryReader.BaseStream as SubStream)?.SubStreamPosition ?? 0;
|
||||
|
||||
ushort environmentReferencesLength = binaryReader.ReadUInt16 ();
|
||||
ushort numEnvironmentReferences = binaryReader.ReadUInt16 ();
|
||||
if (!version2)
|
||||
{
|
||||
if (environmentReferencesLength == 0 || numEnvironmentReferences == 0)
|
||||
throw new InvalidDataException ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (environmentReferencesLength != 0 || numEnvironmentReferences != 0)
|
||||
throw new InvalidDataException ();
|
||||
}
|
||||
SchemaSection = new SectionRef<HierarchicalSchemaSection> (binaryReader.ReadUInt16 ());
|
||||
ushort hierarchicalSchemaReferenceLength = binaryReader.ReadUInt16 ();
|
||||
DecisionInfoSection = new SectionRef<DecisionInfoSection> (binaryReader.ReadUInt16 ());
|
||||
ushort resourceValueTypeTableSize = binaryReader.ReadUInt16 ();
|
||||
ushort ItemToItemInfoGroupCount = binaryReader.ReadUInt16 ();
|
||||
ushort itemInfoGroupCount = binaryReader.ReadUInt16 ();
|
||||
uint itemInfoCount = binaryReader.ReadUInt32 ();
|
||||
uint numCandidates = binaryReader.ReadUInt32 ();
|
||||
uint dataLength = binaryReader.ReadUInt32 ();
|
||||
uint largeTableLength = binaryReader.ReadUInt32 ();
|
||||
|
||||
if (PriFile.GetSectionByRef (DecisionInfoSection) == null)
|
||||
return false;
|
||||
|
||||
byte [] environmentReferencesData = binaryReader.ReadBytes (environmentReferencesLength);
|
||||
|
||||
byte [] schemaReferenceData = binaryReader.ReadBytes (hierarchicalSchemaReferenceLength);
|
||||
|
||||
if (schemaReferenceData.Length != 0)
|
||||
using (BinaryReader r = new BinaryReader (new MemoryStream (schemaReferenceData, false)))
|
||||
{
|
||||
ushort majorVersion = r.ReadUInt16 ();
|
||||
ushort minorVersion = r.ReadUInt16 ();
|
||||
r.ExpectUInt32 (0);
|
||||
uint checksum = r.ReadUInt32 ();
|
||||
uint numScopes = r.ReadUInt32 ();
|
||||
uint numItems = r.ReadUInt32 ();
|
||||
|
||||
HierarchicalSchemaVersionInfo versionInfo = new HierarchicalSchemaVersionInfo (majorVersion, minorVersion, checksum, numScopes, numItems);
|
||||
|
||||
ushort stringDataLength = r.ReadUInt16 ();
|
||||
r.ExpectUInt16 (0);
|
||||
uint unknown1 = r.ReadUInt32 ();
|
||||
uint unknown2 = r.ReadUInt32 ();
|
||||
string uniqueName = r.ReadNullTerminatedString (Encoding.Unicode);
|
||||
|
||||
if (uniqueName.Length != stringDataLength - 1)
|
||||
throw new InvalidDataException ();
|
||||
|
||||
HierarchicalSchemaReference = new HierarchicalSchemaReference (versionInfo, unknown1, unknown2, uniqueName);
|
||||
}
|
||||
|
||||
List<ResourceValueType> resourceValueTypeTable = new List<ResourceValueType> (resourceValueTypeTableSize);
|
||||
for (int i = 0; i < resourceValueTypeTableSize; i++)
|
||||
{
|
||||
binaryReader.ExpectUInt32 (4);
|
||||
ResourceValueType resourceValueType = (ResourceValueType)binaryReader.ReadUInt32 ();
|
||||
resourceValueTypeTable.Add (resourceValueType);
|
||||
}
|
||||
|
||||
List<ItemToItemInfoGroup> itemToItemInfoGroups = new List<ItemToItemInfoGroup> ();
|
||||
for (int i = 0; i < ItemToItemInfoGroupCount; i++)
|
||||
{
|
||||
ushort firstItem = binaryReader.ReadUInt16 ();
|
||||
ushort itemInfoGroup = binaryReader.ReadUInt16 ();
|
||||
itemToItemInfoGroups.Add (new ItemToItemInfoGroup (firstItem, itemInfoGroup));
|
||||
}
|
||||
|
||||
List<ItemInfoGroup> itemInfoGroups = new List<ItemInfoGroup> ();
|
||||
for (int i = 0; i < itemInfoGroupCount; i++)
|
||||
{
|
||||
ushort groupSize = binaryReader.ReadUInt16 ();
|
||||
ushort firstItemInfo = binaryReader.ReadUInt16 ();
|
||||
itemInfoGroups.Add (new ItemInfoGroup (groupSize, firstItemInfo));
|
||||
}
|
||||
|
||||
List<ItemInfo> itemInfos = new List<ItemInfo> ();
|
||||
for (int i = 0; i < itemInfoCount; i++)
|
||||
{
|
||||
ushort decision = binaryReader.ReadUInt16 ();
|
||||
ushort firstCandidate = binaryReader.ReadUInt16 ();
|
||||
itemInfos.Add (new ItemInfo (decision, firstCandidate));
|
||||
}
|
||||
|
||||
byte [] largeTable = binaryReader.ReadBytes ((int)largeTableLength);
|
||||
|
||||
if (largeTable.Length != 0)
|
||||
using (BinaryReader r = new BinaryReader (new MemoryStream (largeTable, false)))
|
||||
{
|
||||
uint ItemToItemInfoGroupCountLarge = r.ReadUInt32 ();
|
||||
uint itemInfoGroupCountLarge = r.ReadUInt32 ();
|
||||
uint itemInfoCountLarge = r.ReadUInt32 ();
|
||||
|
||||
for (int i = 0; i < ItemToItemInfoGroupCountLarge; i++)
|
||||
{
|
||||
uint firstItem = r.ReadUInt32 ();
|
||||
uint itemInfoGroup = r.ReadUInt32 ();
|
||||
itemToItemInfoGroups.Add (new ItemToItemInfoGroup (firstItem, itemInfoGroup));
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemInfoGroupCountLarge; i++)
|
||||
{
|
||||
uint groupSize = r.ReadUInt32 ();
|
||||
uint firstItemInfo = r.ReadUInt32 ();
|
||||
itemInfoGroups.Add (new ItemInfoGroup (groupSize, firstItemInfo));
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemInfoCountLarge; i++)
|
||||
{
|
||||
uint decision = r.ReadUInt32 ();
|
||||
uint firstCandidate = r.ReadUInt32 ();
|
||||
itemInfos.Add (new ItemInfo (decision, firstCandidate));
|
||||
}
|
||||
|
||||
if (r.BaseStream.Position != r.BaseStream.Length)
|
||||
throw new InvalidDataException ();
|
||||
}
|
||||
|
||||
List<CandidateInfo> candidateInfos = new List<CandidateInfo> ((int)numCandidates);
|
||||
for (int i = 0; i < numCandidates; i++)
|
||||
{
|
||||
byte type = binaryReader.ReadByte ();
|
||||
|
||||
if (type == 0x01)
|
||||
{
|
||||
ResourceValueType resourceValueType = resourceValueTypeTable [binaryReader.ReadByte ()];
|
||||
ushort sourceFileIndex = binaryReader.ReadUInt16 ();
|
||||
ushort valueLocation = binaryReader.ReadUInt16 ();
|
||||
ushort dataItemSection = binaryReader.ReadUInt16 ();
|
||||
candidateInfos.Add (new CandidateInfo (resourceValueType, sourceFileIndex, valueLocation, dataItemSection));
|
||||
}
|
||||
else if (type == 0x00)
|
||||
{
|
||||
ResourceValueType resourceValueType = resourceValueTypeTable [binaryReader.ReadByte ()];
|
||||
ushort length = binaryReader.ReadUInt16 ();
|
||||
uint stringOffset = binaryReader.ReadUInt32 ();
|
||||
candidateInfos.Add (new CandidateInfo (resourceValueType, length, stringOffset));
|
||||
}
|
||||
else
|
||||
throw new InvalidDataException ();
|
||||
}
|
||||
|
||||
long stringDataStartOffset = binaryReader.BaseStream.Position;
|
||||
|
||||
Dictionary<ushort, CandidateSet> candidateSets = new Dictionary<ushort, CandidateSet> ();
|
||||
|
||||
for (int itemToItemInfoGroupIndex = 0; itemToItemInfoGroupIndex < itemToItemInfoGroups.Count; itemToItemInfoGroupIndex++)
|
||||
{
|
||||
ItemToItemInfoGroup itemToItemInfoGroup = itemToItemInfoGroups [itemToItemInfoGroupIndex];
|
||||
|
||||
ItemInfoGroup itemInfoGroup;
|
||||
|
||||
if (itemToItemInfoGroup.ItemInfoGroup < itemInfoGroups.Count)
|
||||
itemInfoGroup = itemInfoGroups [(int)itemToItemInfoGroup.ItemInfoGroup];
|
||||
else
|
||||
itemInfoGroup = new ItemInfoGroup (1, (uint)(itemToItemInfoGroup.ItemInfoGroup - itemInfoGroups.Count));
|
||||
|
||||
for (uint itemInfoIndex = itemInfoGroup.FirstItemInfo; itemInfoIndex < itemInfoGroup.FirstItemInfo + itemInfoGroup.GroupSize; itemInfoIndex++)
|
||||
{
|
||||
ItemInfo itemInfo = itemInfos [(int)itemInfoIndex];
|
||||
|
||||
ushort decisionIndex = (ushort)itemInfo.Decision;
|
||||
|
||||
Decision decision = PriFile.GetSectionByRef (DecisionInfoSection).Decisions [decisionIndex];
|
||||
|
||||
List<Candidate> candidates = new List<Candidate> (decision.QualifierSets.Count);
|
||||
|
||||
for (int i = 0; i < decision.QualifierSets.Count; i++)
|
||||
{
|
||||
CandidateInfo candidateInfo = candidateInfos [(int)itemInfo.FirstCandidate + i];
|
||||
|
||||
if (candidateInfo.Type == 0x01)
|
||||
{
|
||||
ReferencedFileRef? sourceFile;
|
||||
|
||||
if (candidateInfo.SourceFileIndex == 0)
|
||||
sourceFile = null;
|
||||
else
|
||||
sourceFile = new ReferencedFileRef (candidateInfo.SourceFileIndex - 1);
|
||||
|
||||
candidates.Add (new Candidate (decision.QualifierSets [i].Index, candidateInfo.ResourceValueType, sourceFile,
|
||||
new DataItemRef (new SectionRef<DataItemSection> (candidateInfo.DataItemSection), candidateInfo.DataItemIndex)));
|
||||
}
|
||||
else if (candidateInfo.Type == 0x00)
|
||||
{
|
||||
ByteSpan data = new ByteSpan (sectionPosition + stringDataStartOffset + candidateInfo.DataOffset, candidateInfo.DataLength);
|
||||
|
||||
candidates.Add (new Candidate (decision.QualifierSets [i].Index, candidateInfo.ResourceValueType, data));
|
||||
}
|
||||
}
|
||||
|
||||
ushort resourceMapItemIndex = (ushort)(itemToItemInfoGroup.FirstItem + (itemInfoIndex - itemInfoGroup.FirstItemInfo));
|
||||
|
||||
CandidateSet candidateSet = new CandidateSet (
|
||||
new ResourceMapItemRef (SchemaSection, resourceMapItemIndex),
|
||||
decisionIndex,
|
||||
candidates);
|
||||
|
||||
candidateSets.Add (resourceMapItemIndex, candidateSet);
|
||||
}
|
||||
}
|
||||
|
||||
CandidateSets = candidateSets;
|
||||
|
||||
return true;
|
||||
}
|
||||
private struct ItemToItemInfoGroup
|
||||
{
|
||||
public uint FirstItem;
|
||||
public uint ItemInfoGroup;
|
||||
public ItemToItemInfoGroup (uint firstItem, uint itemInfoGroup)
|
||||
{
|
||||
FirstItem = firstItem;
|
||||
ItemInfoGroup = itemInfoGroup;
|
||||
}
|
||||
}
|
||||
private struct ItemInfoGroup
|
||||
{
|
||||
public uint GroupSize;
|
||||
public uint FirstItemInfo;
|
||||
public ItemInfoGroup (uint groupSize, uint firstItemInfo)
|
||||
{
|
||||
GroupSize = groupSize;
|
||||
FirstItemInfo = firstItemInfo;
|
||||
}
|
||||
}
|
||||
private struct ItemInfo
|
||||
{
|
||||
public uint Decision;
|
||||
public uint FirstCandidate;
|
||||
|
||||
public ItemInfo (uint decision, uint firstCandidate)
|
||||
{
|
||||
Decision = decision;
|
||||
FirstCandidate = firstCandidate;
|
||||
}
|
||||
}
|
||||
private struct CandidateInfo
|
||||
{
|
||||
public byte Type;
|
||||
public ResourceValueType ResourceValueType;
|
||||
|
||||
// Type 1
|
||||
public ushort SourceFileIndex;
|
||||
public ushort DataItemIndex;
|
||||
public ushort DataItemSection;
|
||||
|
||||
// Type 0
|
||||
public ushort DataLength;
|
||||
public uint DataOffset;
|
||||
|
||||
public CandidateInfo (ResourceValueType resourceValueType, ushort sourceFileIndex, ushort dataItemIndex, ushort dataItemSection)
|
||||
{
|
||||
Type = 0x01;
|
||||
ResourceValueType = resourceValueType;
|
||||
SourceFileIndex = sourceFileIndex;
|
||||
DataItemIndex = dataItemIndex;
|
||||
DataItemSection = dataItemSection;
|
||||
DataLength = 0;
|
||||
DataOffset = 0;
|
||||
}
|
||||
|
||||
public CandidateInfo (ResourceValueType resourceValueType, ushort dataLength, uint dataOffset)
|
||||
{
|
||||
Type = 0x00;
|
||||
ResourceValueType = resourceValueType;
|
||||
SourceFileIndex = 0;
|
||||
DataItemIndex = 0;
|
||||
DataItemSection = 0;
|
||||
DataLength = dataLength;
|
||||
DataOffset = dataOffset;
|
||||
}
|
||||
}
|
||||
~ResourceMapSection ()
|
||||
{
|
||||
HierarchicalSchemaReference = null;
|
||||
CandidateSets = null;
|
||||
}
|
||||
}
|
||||
public enum ResourceValueType
|
||||
{
|
||||
String,
|
||||
Path,
|
||||
EmbeddedData,
|
||||
AsciiString,
|
||||
Utf8String,
|
||||
AsciiPath,
|
||||
Utf8Path
|
||||
}
|
||||
public class CandidateSet
|
||||
{
|
||||
public ResourceMapItemRef ResourceMapItem { get; }
|
||||
public ushort DecisionIndex { get; }
|
||||
public IReadOnlyList <Candidate> Candidates { get; private set; }
|
||||
internal CandidateSet (ResourceMapItemRef resourceMapItem, ushort decisionIndex, IReadOnlyList<Candidate> candidates)
|
||||
{
|
||||
ResourceMapItem = resourceMapItem;
|
||||
DecisionIndex = decisionIndex;
|
||||
Candidates = candidates;
|
||||
}
|
||||
~CandidateSet ()
|
||||
{
|
||||
Candidates = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class Candidate
|
||||
{
|
||||
public ushort QualifierSet { get; }
|
||||
public ResourceValueType Type { get; }
|
||||
public ReferencedFileRef? SourceFile { get; private set; }
|
||||
public DataItemRef? DataItem { get; private set; }
|
||||
public ByteSpan? Data { get; private set; }
|
||||
internal Candidate (ushort qualifierSet, ResourceValueType type, ReferencedFileRef? sourceFile, DataItemRef dataItem)
|
||||
{
|
||||
QualifierSet = qualifierSet;
|
||||
Type = type;
|
||||
SourceFile = sourceFile;
|
||||
DataItem = dataItem;
|
||||
Data = null;
|
||||
}
|
||||
internal Candidate (ushort qualifierSet, ResourceValueType type, ByteSpan data)
|
||||
{
|
||||
QualifierSet = qualifierSet;
|
||||
Type = type;
|
||||
SourceFile = null;
|
||||
DataItem = null;
|
||||
Data = data;
|
||||
}
|
||||
~Candidate ()
|
||||
{
|
||||
SourceFile = null;
|
||||
DataItem = null;
|
||||
Data = null;
|
||||
}
|
||||
}
|
||||
public class HierarchicalSchemaReference
|
||||
{
|
||||
public HierarchicalSchemaVersionInfo VersionInfo { get; private set; }
|
||||
public uint Unknown1 { get; }
|
||||
public uint Unknown2 { get; }
|
||||
public string UniqueName { get; }
|
||||
internal HierarchicalSchemaReference (HierarchicalSchemaVersionInfo versionInfo, uint unknown1, uint unknown2, string uniqueName)
|
||||
{
|
||||
VersionInfo = versionInfo;
|
||||
Unknown1 = unknown1;
|
||||
Unknown2 = unknown2;
|
||||
UniqueName = uniqueName;
|
||||
}
|
||||
~HierarchicalSchemaReference ()
|
||||
{
|
||||
VersionInfo = null;
|
||||
}
|
||||
}
|
||||
public struct ResourceMapItemRef
|
||||
{
|
||||
internal SectionRef <HierarchicalSchemaSection> schemaSection;
|
||||
internal int itemIndex;
|
||||
internal ResourceMapItemRef (SectionRef<HierarchicalSchemaSection> schemaSection, int itemIndex)
|
||||
{
|
||||
this.schemaSection = schemaSection;
|
||||
this.itemIndex = itemIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
159
PriFileFormat/ReverseMapSection.cs
Normal file
159
PriFileFormat/ReverseMapSection.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class ReverseMapSection: Section
|
||||
{
|
||||
public uint [] Mapping { get; private set; }
|
||||
public IReadOnlyList<ResourceMapScope> Scopes { get; private set; }
|
||||
public IReadOnlyList<ResourceMapItem> Items { get; private set; }
|
||||
|
||||
internal const string Identifier = "[mrm_rev_map] \0";
|
||||
|
||||
internal ReverseMapSection (PriFile priFile) : base (Identifier, priFile)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
uint numItems = binaryReader.ReadUInt32 ();
|
||||
binaryReader.ExpectUInt32 ((uint)(binaryReader.BaseStream.Length - 8));
|
||||
|
||||
uint [] mapping = new uint [numItems];
|
||||
for (int i = 0; i < numItems; i++)
|
||||
mapping [i] = binaryReader.ReadUInt32 ();
|
||||
Mapping = mapping;
|
||||
|
||||
ushort maxFullPathLength = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
uint numEntries = binaryReader.ReadUInt32 ();
|
||||
uint numScopes = binaryReader.ReadUInt32 ();
|
||||
binaryReader.ExpectUInt32 (numItems);
|
||||
uint unicodeDataLength = binaryReader.ReadUInt32 ();
|
||||
binaryReader.ReadUInt32 ();
|
||||
|
||||
List<Tuple<ushort, ushort, uint, uint, ushort>> scopeAndItemInfo = new List<Tuple<ushort, ushort, uint, uint, ushort>> ();
|
||||
|
||||
for (int i = 0; i < numScopes + numItems; i++)
|
||||
{
|
||||
ushort parent = binaryReader.ReadUInt16 ();
|
||||
ushort fullPathLength = binaryReader.ReadUInt16 ();
|
||||
uint hashCode = binaryReader.ReadUInt32 ();
|
||||
uint nameOffset = binaryReader.ReadUInt16 () | (((hashCode >> 24) & 0xF) << 16);
|
||||
ushort index = binaryReader.ReadUInt16 ();
|
||||
scopeAndItemInfo.Add (new Tuple<ushort, ushort, uint, uint, ushort> (parent, fullPathLength, hashCode, nameOffset, index));
|
||||
}
|
||||
|
||||
List<Tuple<ushort, ushort, ushort>> scopeExInfo = new List<Tuple<ushort, ushort, ushort>> ();
|
||||
|
||||
for (int i = 0; i < numScopes; i++)
|
||||
{
|
||||
ushort scopeIndex = binaryReader.ReadUInt16 ();
|
||||
ushort childCount = binaryReader.ReadUInt16 ();
|
||||
ushort firstChildIndex = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
scopeExInfo.Add (new Tuple<ushort, ushort, ushort> (scopeIndex, childCount, firstChildIndex));
|
||||
}
|
||||
|
||||
ushort [] itemIndexPropertyToIndex = new ushort [numItems];
|
||||
for (int i = 0; i < numItems; i++)
|
||||
{
|
||||
itemIndexPropertyToIndex [i] = binaryReader.ReadUInt16 ();
|
||||
}
|
||||
|
||||
long unicodeDataOffset = binaryReader.BaseStream.Position;
|
||||
long asciiDataOffset = binaryReader.BaseStream.Position + unicodeDataLength * 2;
|
||||
|
||||
ResourceMapScope [] scopes = new ResourceMapScope [numScopes];
|
||||
ResourceMapItem [] items = new ResourceMapItem [numItems];
|
||||
|
||||
for (int i = 0; i < numScopes + numItems; i++)
|
||||
{
|
||||
bool nameInAscii = (scopeAndItemInfo [i].Item3 & 0x20000000) != 0;
|
||||
long pos = (nameInAscii ? asciiDataOffset : unicodeDataOffset) + (scopeAndItemInfo [i].Item4 * (nameInAscii ? 1 : 2));
|
||||
|
||||
binaryReader.BaseStream.Seek (pos, SeekOrigin.Begin);
|
||||
|
||||
string name;
|
||||
|
||||
if (scopeAndItemInfo [i].Item2 != 0)
|
||||
name = binaryReader.ReadNullTerminatedString (nameInAscii ? Encoding.ASCII : Encoding.Unicode);
|
||||
else
|
||||
name = string.Empty;
|
||||
|
||||
ushort index = scopeAndItemInfo [i].Item5;
|
||||
|
||||
bool isScope = (scopeAndItemInfo [i].Item3 & 0x10000000) != 0;
|
||||
|
||||
if (isScope)
|
||||
{
|
||||
if (scopes [index] != null)
|
||||
throw new InvalidDataException ();
|
||||
|
||||
scopes [index] = new ResourceMapScope (index, null, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (items [index] != null)
|
||||
throw new InvalidDataException ();
|
||||
|
||||
items [index] = new ResourceMapItem (index, null, name);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < numScopes + numItems; i++)
|
||||
{
|
||||
ushort index = scopeAndItemInfo [i].Item5;
|
||||
|
||||
bool isScope = (scopeAndItemInfo [i].Item3 & 0x10000000) != 0;
|
||||
|
||||
ushort parent = scopeAndItemInfo [i].Item1;
|
||||
|
||||
parent = scopeAndItemInfo [parent].Item5;
|
||||
|
||||
if (parent != 0xFFFF)
|
||||
if (isScope)
|
||||
{
|
||||
if (parent != index)
|
||||
scopes [index].Parent = scopes [parent];
|
||||
}
|
||||
else
|
||||
items [index].Parent = scopes [parent];
|
||||
}
|
||||
|
||||
for (int i = 0; i < numScopes; i++)
|
||||
{
|
||||
ResourceMapEntry [] children = new ResourceMapEntry [scopeExInfo [i].Item2];
|
||||
|
||||
for (int j = 0; j < children.Length; j++)
|
||||
{
|
||||
var saiInfo = scopeAndItemInfo [scopeExInfo [i].Item3 + j];
|
||||
|
||||
bool isScope = (saiInfo.Item3 & 0x10000000) != 0;
|
||||
|
||||
if (isScope)
|
||||
children [j] = scopes [saiInfo.Item5];
|
||||
else
|
||||
children [j] = items [saiInfo.Item5];
|
||||
}
|
||||
|
||||
scopes [i].Children = children;
|
||||
}
|
||||
|
||||
Scopes = scopes;
|
||||
Items = items;
|
||||
|
||||
return true;
|
||||
}
|
||||
~ReverseMapSection ()
|
||||
{
|
||||
Mapping = null;
|
||||
Scopes = null;
|
||||
Items = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
89
PriFileFormat/Section.cs
Normal file
89
PriFileFormat/Section.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public abstract class Section
|
||||
{
|
||||
protected PriFile PriFile { get; private set; }
|
||||
public string SectionIdentifier { get; private set; }
|
||||
public uint SectionQualifier { get; private set; }
|
||||
public uint Flags { get; private set; }
|
||||
public uint SectionFlags { get; private set; }
|
||||
public uint SectionLength { get; private set; }
|
||||
|
||||
protected Section (string sectionIdentifier, PriFile priFile)
|
||||
{
|
||||
if (sectionIdentifier.Length != 16)
|
||||
throw new ArgumentException ("Section identifiers need to be exactly 16 characters long.", nameof (sectionIdentifier));
|
||||
|
||||
SectionIdentifier = sectionIdentifier;
|
||||
PriFile = priFile;
|
||||
}
|
||||
|
||||
internal bool Parse (BinaryReader binaryReader)
|
||||
{
|
||||
if (new string (binaryReader.ReadChars (16)) != SectionIdentifier)
|
||||
throw new InvalidDataException ("Unexpected section identifier.");
|
||||
|
||||
SectionQualifier = binaryReader.ReadUInt32 ();
|
||||
Flags = binaryReader.ReadUInt16 ();
|
||||
SectionFlags = binaryReader.ReadUInt16 ();
|
||||
SectionLength = binaryReader.ReadUInt32 ();
|
||||
binaryReader.ExpectUInt32 (0);
|
||||
|
||||
binaryReader.BaseStream.Seek (SectionLength - 16 - 24, SeekOrigin.Current);
|
||||
|
||||
binaryReader.ExpectUInt32 (0xDEF5FADE);
|
||||
binaryReader.ExpectUInt32 (SectionLength);
|
||||
|
||||
binaryReader.BaseStream.Seek (-8 - (SectionLength - 16 - 24), SeekOrigin.Current);
|
||||
|
||||
using (SubStream subStream = new SubStream (binaryReader.BaseStream, binaryReader.BaseStream.Position, (int)SectionLength - 16 - 24))
|
||||
using (BinaryReader subBinaryReader = new BinaryReader (subStream, Encoding.ASCII))
|
||||
{
|
||||
return ParseSectionContent (subBinaryReader);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool ParseSectionContent (BinaryReader binaryReader);
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"{SectionIdentifier.TrimEnd ('\0', ' ')} length: {SectionLength}";
|
||||
}
|
||||
|
||||
internal static Section CreateForIdentifier (string sectionIdentifier, PriFile priFile)
|
||||
{
|
||||
switch (sectionIdentifier)
|
||||
{
|
||||
case PriDescriptorSection.Identifier:
|
||||
return new PriDescriptorSection (priFile);
|
||||
case HierarchicalSchemaSection.Identifier1:
|
||||
return new HierarchicalSchemaSection (priFile, false);
|
||||
case HierarchicalSchemaSection.Identifier2:
|
||||
return new HierarchicalSchemaSection (priFile, true);
|
||||
case DecisionInfoSection.Identifier:
|
||||
return new DecisionInfoSection (priFile);
|
||||
case ResourceMapSection.Identifier1:
|
||||
return new ResourceMapSection (priFile, false);
|
||||
case ResourceMapSection.Identifier2:
|
||||
return new ResourceMapSection (priFile, true);
|
||||
case DataItemSection.Identifier:
|
||||
return new DataItemSection (priFile);
|
||||
case ReverseMapSection.Identifier:
|
||||
return new ReverseMapSection (priFile);
|
||||
case ReferencedFileSection.Identifier:
|
||||
return new ReferencedFileSection (priFile);
|
||||
default:
|
||||
return new UnknownSection (sectionIdentifier, priFile);
|
||||
}
|
||||
}
|
||||
|
||||
~Section ()
|
||||
{
|
||||
PriFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
PriFileFormat/SubStream.cs
Normal file
76
PriFileFormat/SubStream.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class SubStream: Stream
|
||||
{
|
||||
Stream baseStream;
|
||||
long subStreamPosition;
|
||||
long subStreamLength;
|
||||
|
||||
public SubStream (Stream baseStream, long subStreamPosition, long subStreamLength)
|
||||
{
|
||||
this.baseStream = baseStream;
|
||||
this.subStreamPosition = subStreamPosition;
|
||||
this.subStreamLength = subStreamLength;
|
||||
}
|
||||
|
||||
public long SubStreamPosition => subStreamPosition;
|
||||
|
||||
public override bool CanRead => baseStream.CanRead;
|
||||
|
||||
public override bool CanSeek => baseStream.CanSeek;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => subStreamLength;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return baseStream.Position - subStreamPosition; }
|
||||
set { baseStream.Position = subStreamPosition + value; }
|
||||
}
|
||||
|
||||
public override void Flush ()
|
||||
{
|
||||
}
|
||||
|
||||
public override int Read (byte [] buffer, int offset, int count)
|
||||
{
|
||||
if (Position < 0)
|
||||
throw new InvalidOperationException ("Cannot read when position is negative.");
|
||||
if (Position + count > subStreamLength)
|
||||
count = (int)(subStreamLength - Position);
|
||||
|
||||
return baseStream.Read (buffer, offset, count);
|
||||
}
|
||||
|
||||
public override long Seek (long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
return baseStream.Seek (subStreamPosition + offset, SeekOrigin.Begin) - subStreamPosition;
|
||||
case SeekOrigin.Current:
|
||||
return baseStream.Seek (offset, SeekOrigin.Current) - subStreamPosition;
|
||||
case SeekOrigin.End:
|
||||
return baseStream.Seek (subStreamPosition + subStreamLength + offset, SeekOrigin.Begin) - subStreamPosition;
|
||||
default:
|
||||
throw new ArgumentException ("Invalid origin.", nameof (origin));
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetLength (long value)
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
|
||||
public override void Write (byte [] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
|
||||
~SubStream () { baseStream = null; }
|
||||
}
|
||||
}
|
||||
37
PriFileFormat/TocEntry.cs
Normal file
37
PriFileFormat/TocEntry.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class TocEntry
|
||||
{
|
||||
public string SectionIdentifier { get; }
|
||||
public ushort Flags { get; }
|
||||
public ushort SectionFlags { get; }
|
||||
public uint SectionQualifier { get; }
|
||||
public uint SectionOffset { get; }
|
||||
public uint SectionLength { get; }
|
||||
private TocEntry (string sectionIdentifier, ushort flags, ushort sectionFlags, uint sectionQualifier, uint sectionOffset, uint sectionLength)
|
||||
{
|
||||
SectionIdentifier = sectionIdentifier;
|
||||
Flags = flags;
|
||||
SectionFlags = sectionFlags;
|
||||
SectionQualifier = sectionQualifier;
|
||||
SectionOffset = sectionOffset;
|
||||
SectionLength = sectionLength;
|
||||
}
|
||||
internal static TocEntry Parse (BinaryReader binaryReader)
|
||||
{
|
||||
return new TocEntry (
|
||||
new string (binaryReader.ReadChars (16)),
|
||||
binaryReader.ReadUInt16 (),
|
||||
binaryReader.ReadUInt16 (),
|
||||
binaryReader.ReadUInt32 (),
|
||||
binaryReader.ReadUInt32 (),
|
||||
binaryReader.ReadUInt32 ());
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"{SectionIdentifier.TrimEnd ('\0', ' ')} length: {SectionLength}";
|
||||
}
|
||||
}
|
||||
}
|
||||
23
PriFileFormat/UnknownSection.cs
Normal file
23
PriFileFormat/UnknownSection.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace PriFileFormat
|
||||
{
|
||||
public class UnknownSection: Section
|
||||
{
|
||||
public byte [] SectionContent { get; private set; }
|
||||
internal UnknownSection (string sectionIdentifier, PriFile priFile) : base (sectionIdentifier, priFile) {}
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
int contentLength = (int)(binaryReader.BaseStream.Length - binaryReader.BaseStream.Position);
|
||||
|
||||
SectionContent = binaryReader.ReadBytes (contentLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
public void ClearContent ()
|
||||
{
|
||||
SectionContent = null;
|
||||
}
|
||||
~UnknownSection () { ClearContent (); }
|
||||
}
|
||||
}
|
||||
3
PriFileFormat/app.config
Normal file
3
PriFileFormat/app.config
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user