Organized the project files.

And also fixed some bugs.
This commit is contained in:
Bruce
2025-12-08 16:06:13 +08:00
parent ed7fe3af4b
commit d1813637c5
95 changed files with 46744 additions and 36366 deletions

View File

@@ -1 +0,0 @@
E:/Profiles/Bruce/Documents/Visual Studio 2015/Projects/PriFileFormat/PriFileFormat

14
PriFileFormat/ByteSpan.cs Normal file
View 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;
}
}
}

View 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;}
}
}

View 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}";
}
}
}

View 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}";
}
}
}

View 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}";
}
}
}

View 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
View 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];
}
}
}

View 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>

View 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")]

View 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;
}
}
}
}

View 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>

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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
View 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;
}
}
}

View 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
View 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}";
}
}
}

View 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
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>