using System; using System.Collections.Generic; using System.IO; using System.Text; namespace PriFormat { public class ResourceMapSection: Section { public HierarchicalSchemaReference HierarchicalSchemaReference { get; private set; } public SectionRef SchemaSection { get; private set; } public SectionRef DecisionInfoSection { get; private set; } public IDictionary CandidateSets { get; private set; } readonly 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) != null ? ((SubStream)binaryReader.BaseStream).Position : 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 (binaryReader.ReadUInt16 ()); ushort hierarchicalSchemaReferenceLength = binaryReader.ReadUInt16 (); DecisionInfoSection = new SectionRef (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 resourceValueTypeTable = new List (resourceValueTypeTableSize); for (int i = 0; i < resourceValueTypeTableSize; i++) { binaryReader.ExpectUInt32 (4); ResourceValueType resourceValueType = (ResourceValueType)binaryReader.ReadUInt32 (); resourceValueTypeTable.Add (resourceValueType); } List itemToItemInfoGroups = new List (ItemToItemInfoGroupCount); for (int i = 0; i < ItemToItemInfoGroupCount; i++) { ushort firstItem = binaryReader.ReadUInt16 (); ushort itemInfoGroup = binaryReader.ReadUInt16 (); itemToItemInfoGroups.Add (new ItemToItemInfoGroup (firstItem, itemInfoGroup)); } List itemInfoGroups = new List (itemInfoGroupCount); for (int i = 0; i < itemInfoGroupCount; i++) { ushort groupSize = binaryReader.ReadUInt16 (); ushort firstItemInfo = binaryReader.ReadUInt16 (); itemInfoGroups.Add (new ItemInfoGroup (groupSize, firstItemInfo)); } List itemInfos = new List ((int)itemInfoCount); 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 candidateInfos = new List ((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 candidateSets = new Dictionary (); 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 candidates = new List (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 = null; if (candidateInfo.SourceFileIndex != 0) sourceFile = new ReferencedFileRef (candidateInfo.SourceFileIndex - 1); candidates.Add (new Candidate (decision.QualifierSets [i].Index, candidateInfo.ResourceValueType, sourceFile, new DataItemRef (new SectionRef (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; } } public override void Dispose () { HierarchicalSchemaReference = null; CandidateSets?.Clear (); CandidateSets = null; base.Dispose (); } ~ResourceMapSection () { Dispose (); } } public enum ResourceValueType { String, Path, EmbeddedData, AsciiString, Utf8String, AsciiPath, Utf8Path } public class CandidateSet: IDisposable { public ResourceMapItemRef ResourceMapItem { get; private set; } public ushort DecisionIndex { get; private set; } public IList Candidates { get; private set; } internal CandidateSet (ResourceMapItemRef resourceMapItem, ushort decisionIndex, IList candidates) { ResourceMapItem = resourceMapItem; DecisionIndex = decisionIndex; Candidates = candidates; } public virtual void Dispose () { Candidates?.Clear (); Candidates = null; } ~CandidateSet () { Dispose (); } } public class Candidate { public ushort QualifierSet { get; private set; } public ResourceValueType Type { get; private set; } 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; } internal Candidate (ushort qualifierSet, ResourceValueType type, ByteSpan data) { QualifierSet = qualifierSet; Type = type; SourceFile = null; DataItem = null; Data = data; } } public class HierarchicalSchemaReference { public HierarchicalSchemaVersionInfo VersionInfo { get; private set; } public uint Unknown1 { get; private set; } public uint Unknown2 { get; private set; } public string UniqueName { get; private set; } internal HierarchicalSchemaReference (HierarchicalSchemaVersionInfo versionInfo, uint unknown1, uint unknown2, string uniqueName) { VersionInfo = versionInfo; Unknown1 = unknown1; Unknown2 = unknown2; UniqueName = uniqueName; } } public struct ResourceMapItemRef { internal SectionRef schemaSection; internal int itemIndex; public SectionRef SchemaSection => schemaSection; public int ItemIndex => itemIndex; internal ResourceMapItemRef (SectionRef schemaSection, int itemIndex) { this.schemaSection = schemaSection; this.itemIndex = itemIndex; } } }