mirror of
https://github.com/modernw/App-Installer-For-Windows-8.x-Reset.git
synced 2026-04-11 17:57:19 +10:00
Update about Manager.
This commit is contained in:
22
PriFormat/ByteSpan.cs
Normal file
22
PriFormat/ByteSpan.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public struct ByteSpan
|
||||
{
|
||||
public long Offset { get; private set; }
|
||||
public uint Length { get; private set; }
|
||||
public ByteSpan (long offset, uint length)
|
||||
{
|
||||
Offset = offset;
|
||||
Length = length;
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return "ByteSpan | Offset = " + Offset + "\t, Length = " + Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
PriFormat/DataItemSection.cs
Normal file
82
PriFormat/DataItemSection.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public class DataItemSection: Section
|
||||
{
|
||||
public IList<ByteSpan> DataItems { get; private set; }
|
||||
|
||||
internal const string Identifier = "[mrm_dataitem] \0";
|
||||
|
||||
internal DataItemSection (PriFile priFile)
|
||||
: base (Identifier, priFile)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ParseSectionContent (BinaryReader binaryReader)
|
||||
{
|
||||
long sectionPosition = 0;
|
||||
SubStream sub = binaryReader.BaseStream as SubStream;
|
||||
if (sub != null)
|
||||
sectionPosition = sub.Position;
|
||||
|
||||
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 = new ReadOnlyCollection<ByteSpan> (dataItems);
|
||||
|
||||
return true;
|
||||
}
|
||||
public override void Dispose ()
|
||||
{
|
||||
DataItems?.Clear ();
|
||||
DataItems = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
}
|
||||
|
||||
public class DataItemRef
|
||||
{
|
||||
internal SectionRef<DataItemSection> dataItemSection;
|
||||
internal int itemIndex;
|
||||
public SectionRef<DataItemSection> DataItemSection => dataItemSection;
|
||||
public int ItemIndex => itemIndex;
|
||||
internal DataItemRef (SectionRef<DataItemSection> dataItemSection, int itemIndex)
|
||||
{
|
||||
this.dataItemSection = dataItemSection;
|
||||
this.itemIndex = itemIndex;
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return string.Format ("Data item {0} in section {1}", itemIndex, dataItemSection.SectionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
819
PriFormat/Datas.cs
Normal file
819
PriFormat/Datas.cs
Normal file
@@ -0,0 +1,819 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public struct SectionRef<T> where T : Section
|
||||
{
|
||||
internal int SectionIndex;
|
||||
internal SectionRef (int sectionIndex)
|
||||
{
|
||||
SectionIndex = sectionIndex;
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return "Section " + typeof (T).Name + " at index " + SectionIndex;
|
||||
}
|
||||
}
|
||||
public static class LocaleExt
|
||||
{
|
||||
// GetLocaleInfoW for LCID-based queries
|
||||
[DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int GetLocaleInfoW (int Locale, int LCType, [Out] StringBuilder lpLCData, int cchData);
|
||||
// GetLocaleInfoEx for locale name based queries
|
||||
[DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int GetLocaleInfoEx (string lpLocaleName, int LCType, [Out] StringBuilder lpLCData, int cchData);
|
||||
// LocaleNameToLCID - available on Vista+; fallback is to use CultureInfo
|
||||
[DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int LocaleNameToLCID (string lpName, uint dwFlags);
|
||||
// LCIDToLocaleName (Vista+)
|
||||
[DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int LCIDToLocaleName (int Locale, [Out] StringBuilder lpName, int cchName, uint dwFlags);
|
||||
// Current locale name like "en-US"
|
||||
public static string CurrentLocale
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
// prefer thread culture name which reflects user culture
|
||||
string name = CultureInfo.CurrentCulture.Name;
|
||||
if (string.IsNullOrEmpty (name))
|
||||
name = CultureInfo.InstalledUICulture.Name;
|
||||
return name ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current LCID (int)
|
||||
public static int CurrentLCID
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return CultureInfo.CurrentCulture.LCID;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return CultureInfo.InvariantCulture.LCID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert LCID -> locale name (e.g. 1033 -> "en-US")
|
||||
public static string ToLocaleName (int lcid)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try managed first
|
||||
var ci = new CultureInfo (lcid);
|
||||
if (!string.IsNullOrEmpty (ci.Name))
|
||||
return ci.Name;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// try Win32 LCIDToLocaleName (Vista+)
|
||||
try
|
||||
{
|
||||
StringBuilder sb = new StringBuilder (LOCALE_NAME_MAX_LENGTH);
|
||||
int res = LCIDToLocaleName (lcid, sb, sb.Capacity, 0);
|
||||
if (res > 0) return sb.ToString ();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Convert locale name -> LCID
|
||||
public static int ToLCID (string localeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty (localeName)) return CultureInfo.InvariantCulture.LCID;
|
||||
try
|
||||
{
|
||||
// prefer managed creation
|
||||
var ci = new CultureInfo (localeName);
|
||||
return ci.LCID;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// try Win32 LocaleNameToLCID (Vista+)
|
||||
try
|
||||
{
|
||||
int lcid = LocaleNameToLCID (localeName, 0);
|
||||
if (lcid != 0) return lcid;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
// fallback: invariant culture
|
||||
return CultureInfo.InvariantCulture.LCID;
|
||||
}
|
||||
|
||||
// Return a locale info string for given LCID and LCTYPE. LCTYPE is the Win32 LOCALE_* constant.
|
||||
// Returns a string (or empty string on failure).
|
||||
public static object LocaleInfo (int lcid, int lctype)
|
||||
{
|
||||
try
|
||||
{
|
||||
// First try mapping common LCTYPE values to managed properties for better correctness
|
||||
// Some common LCTYPE values:
|
||||
// LOCALE_SISO639LANGNAME = 0x59 (89) -> Two-letter ISO language name
|
||||
// LOCALE_SISO3166CTRYNAME = 0x5A (90) -> Two-letter country/region name
|
||||
// LOCALE_SNAME = 0x5c (92) -> locale name like "en-US" (Vista+)
|
||||
// But we cannot rely on all values, so we fallback to native GetLocaleInfoW.
|
||||
if (lctype == 0x59) // LOCALE_SISO639LANGNAME
|
||||
{
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (lcid);
|
||||
return ci.TwoLetterISOLanguageName ?? string.Empty;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else if (lctype == 0x5A) // LOCALE_SISO3166CTRYNAME
|
||||
{
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (lcid);
|
||||
try
|
||||
{
|
||||
var ri = new RegionInfo (ci.Name);
|
||||
return ri.TwoLetterISORegionName ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// some cultures have no region; fallback to parsing name
|
||||
var name = ci.Name;
|
||||
if (!string.IsNullOrEmpty (name) && name.IndexOf ('-') >= 0)
|
||||
{
|
||||
return name.Split ('-') [1];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else if (lctype == 0x5c) // LOCALE_SNAME
|
||||
{
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (lcid);
|
||||
return ci.Name ?? string.Empty;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// Fallback to native GetLocaleInfoW
|
||||
StringBuilder sb = new StringBuilder (256);
|
||||
int ret = GetLocaleInfoW (lcid, lctype, sb, sb.Capacity);
|
||||
if (ret > 0)
|
||||
return sb.ToString ();
|
||||
return string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// LocaleInfoEx: query by locale name string and LCTYPE
|
||||
// Returns string if available; otherwise returns the integer result code (as int) if string empty (mimic C++ behavior).
|
||||
public static object LocaleInfoEx (string localeName, int lctype)
|
||||
{
|
||||
if (string.IsNullOrEmpty (localeName))
|
||||
{
|
||||
// fall back to current culture name
|
||||
localeName = CurrentLocale;
|
||||
if (string.IsNullOrEmpty (localeName)) return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Try managed shortcuts for common types
|
||||
if (lctype == 0x59) // LOCALE_SISO639LANGNAME
|
||||
{
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (localeName);
|
||||
return ci.TwoLetterISOLanguageName ?? string.Empty;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else if (lctype == 0x5A) // LOCALE_SISO3166CTRYNAME
|
||||
{
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (localeName);
|
||||
var ri = new RegionInfo (ci.Name);
|
||||
return ri.TwoLetterISORegionName ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// try to split
|
||||
var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2) return parts [1];
|
||||
}
|
||||
}
|
||||
else if (lctype == 0x5c) // LOCALE_SNAME
|
||||
{
|
||||
// localeName is probably already the name
|
||||
return localeName;
|
||||
}
|
||||
|
||||
// Fallback to GetLocaleInfoEx
|
||||
StringBuilder sb = new StringBuilder (LOCALE_NAME_MAX_LENGTH);
|
||||
int res = GetLocaleInfoEx (localeName, lctype, sb, sb.Capacity);
|
||||
if (res > 0)
|
||||
{
|
||||
string outStr = sb.ToString ();
|
||||
if (!string.IsNullOrEmpty (outStr))
|
||||
return outStr;
|
||||
}
|
||||
// if nothing returned, return the result code
|
||||
return res;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers similar to the C++: restricted (language) and elaborated (region) codes
|
||||
public static string GetLocaleRestrictedCode (string localeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty (localeName)) localeName = CurrentLocale;
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (localeName);
|
||||
return ci.TwoLetterISOLanguageName ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fallback: parse name
|
||||
var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 1) return parts [0];
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLocaleElaboratedCode (string localeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty (localeName)) localeName = CurrentLocale;
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (localeName);
|
||||
// Region part from RegionInfo
|
||||
try
|
||||
{
|
||||
var ri = new RegionInfo (ci.Name);
|
||||
return ri.TwoLetterISORegionName ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fallback: parse
|
||||
var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2) return parts [1];
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 2) return parts [1];
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// LCID -> combined code like "en-US" (with configurable separator)
|
||||
public static string LcidToLocaleCode (int lcid)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo (lcid);
|
||||
if (!string.IsNullOrEmpty (ci.Name)) return ci.Name;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var name = ToLocaleName (lcid);
|
||||
if (!string.IsNullOrEmpty (name)) return name;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Get the user default locale name
|
||||
public static string GetUserDefaultLocaleName ()
|
||||
{
|
||||
try
|
||||
{
|
||||
// In .NET, CurrentCulture corresponds to user default
|
||||
string name = CultureInfo.CurrentCulture.Name;
|
||||
if (!string.IsNullOrEmpty (name)) return name;
|
||||
}
|
||||
catch { }
|
||||
return LcidToLocaleCode (CultureInfo.CurrentCulture.LCID);
|
||||
}
|
||||
|
||||
// Get system default locale name (machine)
|
||||
public static string GetSystemDefaultLocaleName ()
|
||||
{
|
||||
try
|
||||
{
|
||||
// InstalledUICulture / Invariant fallback
|
||||
string name = CultureInfo.InstalledUICulture.Name;
|
||||
if (!string.IsNullOrEmpty (name)) return name;
|
||||
}
|
||||
catch { }
|
||||
return LcidToLocaleCode (CultureInfo.InstalledUICulture.LCID);
|
||||
}
|
||||
|
||||
// Get computer locale code similar to C++ approach
|
||||
public static string GetComputerLocaleCode ()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Thread culture -> user -> system
|
||||
string threadName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
|
||||
if (!string.IsNullOrEmpty (threadName)) return threadName;
|
||||
|
||||
string user = GetUserDefaultLocaleName ();
|
||||
if (!string.IsNullOrEmpty (user)) return user;
|
||||
|
||||
string system = GetSystemDefaultLocaleName ();
|
||||
if (!string.IsNullOrEmpty (system)) return system;
|
||||
}
|
||||
catch { }
|
||||
// fallback to invariant
|
||||
return CultureInfo.InvariantCulture.Name ?? string.Empty;
|
||||
}
|
||||
|
||||
// Compare two locale names; returns true if equal by name or LCID
|
||||
public static bool LocaleNameCompare (string left, string right)
|
||||
{
|
||||
if (string.Equals (left, right, StringComparison.OrdinalIgnoreCase)) return true;
|
||||
try
|
||||
{
|
||||
int l = ToLCID (left);
|
||||
int r = ToLCID (right);
|
||||
return l == r && l != CultureInfo.InvariantCulture.LCID;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Constants
|
||||
private const int LOCALE_NAME_MAX_LENGTH = 85; // defined by Windows
|
||||
}
|
||||
public static class UIExt
|
||||
{
|
||||
// GetDeviceCaps index for DPI X
|
||||
private const int LOGPIXELSX = 88;
|
||||
|
||||
[DllImport ("user32.dll")]
|
||||
private static extern IntPtr GetDC (IntPtr hWnd);
|
||||
|
||||
[DllImport ("user32.dll")]
|
||||
private static extern int ReleaseDC (IntPtr hWnd, IntPtr hDC);
|
||||
|
||||
[DllImport ("gdi32.dll")]
|
||||
private static extern int GetDeviceCaps (IntPtr hdc, int nIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Gets system DPI as percentage (100 = 96 DPI, 125 = 120 DPI, etc.)
|
||||
/// </summary>
|
||||
public static int DPI
|
||||
{
|
||||
get { return GetDPI (); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets system DPI as scale factor (1.0 = 100%, 1.25 = 125%)
|
||||
/// </summary>
|
||||
public static double DPIScale
|
||||
{
|
||||
get { return DPI * 0.01; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets system DPI percentage based on 96 DPI baseline.
|
||||
/// </summary>
|
||||
public static int GetDPI ()
|
||||
{
|
||||
IntPtr hdc = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
hdc = GetDC (IntPtr.Zero);
|
||||
if (hdc == IntPtr.Zero)
|
||||
return 100; // safe default
|
||||
|
||||
int dpiX = GetDeviceCaps (hdc, LOGPIXELSX);
|
||||
if (dpiX <= 0)
|
||||
return 100;
|
||||
|
||||
// 96 DPI == 100%
|
||||
return (int)Math.Round (dpiX * 100.0 / 96.0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (hdc != IntPtr.Zero)
|
||||
ReleaseDC (IntPtr.Zero, hdc);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class MSRUriHelper
|
||||
{
|
||||
public const string MsResScheme = "ms-resource:";
|
||||
public static readonly int MsResSchemeLength = MsResScheme.Length;
|
||||
/// <summary>
|
||||
/// Converts ms-resource URI or file path to path segments.
|
||||
/// </summary>
|
||||
public static int KeyToPath (string key, IList<string> output)
|
||||
{
|
||||
output.Clear ();
|
||||
if (string.IsNullOrEmpty (key))
|
||||
return 0;
|
||||
key = key.Trim ();
|
||||
try
|
||||
{
|
||||
// URI
|
||||
if (IsMsResourceUri (key))
|
||||
{
|
||||
Uri uri = new Uri (key, UriKind.RelativeOrAbsolute);
|
||||
return UriToPath (uri, output);
|
||||
}
|
||||
|
||||
// File path
|
||||
SplitPath (key, '\\', output);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fallback: treat as file path
|
||||
SplitPath (key, '\\', output);
|
||||
}
|
||||
|
||||
return output.Count;
|
||||
}
|
||||
public static List<string> KeyToPath (string key)
|
||||
{
|
||||
List<string> ret = new List<string> ();
|
||||
KeyToPath (key, ret);
|
||||
return ret;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts System.Uri to path segments.
|
||||
/// </summary>
|
||||
public static int UriToPath (Uri uri, IList<string> output)
|
||||
{
|
||||
output.Clear ();
|
||||
if (uri == null)
|
||||
return 0;
|
||||
try
|
||||
{
|
||||
string path = uri.AbsolutePath;
|
||||
string [] parts = path.Split (new [] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (string p in parts)
|
||||
output.Add (p);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
return output.Count;
|
||||
}
|
||||
public static int UriToPath (string uristr, IList<string> output)
|
||||
{
|
||||
var uri = new Uri (uristr);
|
||||
return UriToPath (uri, output);
|
||||
}
|
||||
public static List<string> UriToPath (Uri uri)
|
||||
{
|
||||
List<string> ret = new List<string> ();
|
||||
UriToPath (uri, ret);
|
||||
return ret;
|
||||
}
|
||||
public static List<string> UriToPath (string uristr)
|
||||
{
|
||||
var uri = new Uri (uristr);
|
||||
return UriToPath (uri);
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks whether key starts with ms-resource:
|
||||
/// </summary>
|
||||
public static bool IsMsResourceUri (string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty (key))
|
||||
return false;
|
||||
|
||||
return key.TrimStart ().StartsWith (MsResScheme, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
/// <summary>
|
||||
/// ms-resource://... (full uri)
|
||||
/// </summary>
|
||||
public static bool IsFullMsResourceUri (string key)
|
||||
{
|
||||
if (!IsMsResourceUri (key))
|
||||
return false;
|
||||
|
||||
return key.TrimStart ().StartsWith (
|
||||
MsResScheme + "//",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
/// <summary>
|
||||
/// ms-resource:foo/bar (relative uri)
|
||||
/// </summary>
|
||||
public static bool IsRelativeMsResourceUri (string key)
|
||||
{
|
||||
return IsMsResourceUri (key) && !IsFullMsResourceUri (key);
|
||||
}
|
||||
private static void SplitPath (string value, char sep, IList<string> output)
|
||||
{
|
||||
string [] parts = value.Split (new [] { sep }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (string p in parts)
|
||||
output.Add (p);
|
||||
}
|
||||
}
|
||||
public enum PriPathSeparator
|
||||
{
|
||||
Backslash, // "\"
|
||||
Slash // "/"
|
||||
}
|
||||
|
||||
public sealed class PriPath: IList<string>, IEquatable<PriPath>
|
||||
{
|
||||
private readonly List<string> _segments;
|
||||
|
||||
public bool IgnoreCase { get; }
|
||||
|
||||
public PriPath (bool ignoreCase = true)
|
||||
{
|
||||
_segments = new List<string> ();
|
||||
IgnoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
public PriPath (IEnumerable<string> segments, bool ignoreCase = true)
|
||||
{
|
||||
_segments = new List<string> (segments ?? Enumerable.Empty<string> ());
|
||||
IgnoreCase = ignoreCase;
|
||||
}
|
||||
public PriPath (Uri resuri, bool ignoreCase = true) :
|
||||
this (MSRUriHelper.UriToPath (resuri), ignoreCase)
|
||||
{ }
|
||||
public PriPath (string resname, bool ignoreCase = true) :
|
||||
this (MSRUriHelper.KeyToPath (resname), ignoreCase)
|
||||
{ }
|
||||
public int Count => _segments.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public string this [int index]
|
||||
{
|
||||
get { return _segments [index]; }
|
||||
set { _segments [index] = value; }
|
||||
}
|
||||
public void Add (string item) => _segments.Add (item);
|
||||
public void Clear () => _segments.Clear ();
|
||||
public bool Contains (string item) =>
|
||||
_segments.Contains (item, IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
|
||||
|
||||
public void CopyTo (string [] array, int arrayIndex) => _segments.CopyTo (array, arrayIndex);
|
||||
public IEnumerator<string> GetEnumerator () => _segments.GetEnumerator ();
|
||||
IEnumerator IEnumerable.GetEnumerator () => _segments.GetEnumerator ();
|
||||
|
||||
public int IndexOf (string item) =>
|
||||
_segments.FindIndex (x =>
|
||||
string.Equals (x, item,
|
||||
IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
|
||||
|
||||
public void Insert (int index, string item) => _segments.Insert (index, item);
|
||||
public bool Remove (string item) => _segments.Remove (item);
|
||||
public void RemoveAt (int index) => _segments.RemoveAt (index);
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return ToString (PriPathSeparator.Backslash);
|
||||
}
|
||||
|
||||
public string ToString (PriPathSeparator sep)
|
||||
{
|
||||
string s = sep == PriPathSeparator.Backslash ? "\\" : "/";
|
||||
return string.Join (s, _segments);
|
||||
}
|
||||
|
||||
public string ToUriString ()
|
||||
{
|
||||
// ms-resource: URI style (relative)
|
||||
return "ms-resource:" + ToString (PriPathSeparator.Slash);
|
||||
}
|
||||
|
||||
public static PriPath FromString (string path, bool ignoreCase = true)
|
||||
{
|
||||
if (path == null) return new PriPath (ignoreCase: ignoreCase);
|
||||
|
||||
// detect URI
|
||||
if (path.StartsWith ("ms-resource:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string rest = path.Substring ("ms-resource:".Length);
|
||||
rest = rest.TrimStart ('/');
|
||||
var segs = rest.Split (new [] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new PriPath (segs, ignoreCase);
|
||||
}
|
||||
|
||||
// file path
|
||||
var parts = path.Split (new [] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return new PriPath (parts, ignoreCase);
|
||||
}
|
||||
|
||||
public override bool Equals (object obj)
|
||||
{
|
||||
return Equals (obj as PriPath);
|
||||
}
|
||||
|
||||
public bool Equals (PriPath other)
|
||||
{
|
||||
if (ReferenceEquals (other, null)) return false;
|
||||
if (ReferenceEquals (this, other)) return true;
|
||||
if (Count != other.Count) return false;
|
||||
|
||||
var comparer = IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (!comparer.Equals (_segments [i], other._segments [i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
var comparer = IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
||||
int hash = 17;
|
||||
foreach (var seg in _segments)
|
||||
{
|
||||
hash = hash * 31 + comparer.GetHashCode (seg?.Trim () ?? "");
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Operators
|
||||
public static bool operator == (PriPath a, PriPath b)
|
||||
{
|
||||
if (ReferenceEquals (a, b)) return true;
|
||||
if (ReferenceEquals (a, null) || ReferenceEquals (b, null)) return false;
|
||||
return a.Equals (b);
|
||||
}
|
||||
|
||||
public static bool operator != (PriPath a, PriPath b) => !(a == b);
|
||||
|
||||
// Concat with another path
|
||||
public static PriPath operator + (PriPath a, PriPath b)
|
||||
{
|
||||
if (a == null) return b == null ? null : new PriPath (b, ignoreCase: true);
|
||||
if (b == null) return new PriPath (a, ignoreCase: a.IgnoreCase);
|
||||
|
||||
var result = new PriPath (a, a.IgnoreCase);
|
||||
result._segments.AddRange (b._segments);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Append segment
|
||||
public static PriPath operator / (PriPath a, string segment)
|
||||
{
|
||||
if (a == null) return new PriPath (new [] { segment });
|
||||
var result = new PriPath (a, a.IgnoreCase);
|
||||
result._segments.Add (segment);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
public sealed class PriResourceIdentifier: IEquatable<PriResourceIdentifier>
|
||||
{
|
||||
public string Key { get; private set; }
|
||||
public int TaskType { get; private set; } // 0: string (ms-resource), 1: file path
|
||||
public PriPath Path { get; private set; }
|
||||
public PriResourceIdentifier ()
|
||||
{
|
||||
Key = string.Empty;
|
||||
TaskType = 1;
|
||||
Path = new PriPath ();
|
||||
}
|
||||
public PriResourceIdentifier (string key, int type = -1)
|
||||
{
|
||||
SetKey (key, type);
|
||||
}
|
||||
public PriResourceIdentifier (Uri uri, int type = -1)
|
||||
{
|
||||
if (uri == null)
|
||||
{
|
||||
SetKey (string.Empty, type);
|
||||
return;
|
||||
}
|
||||
|
||||
SetKey (uri.ToString (), type);
|
||||
}
|
||||
public void SetKey (string value, int type = -1)
|
||||
{
|
||||
Key = value ?? string.Empty;
|
||||
|
||||
if (type < 0 || type > 1)
|
||||
{
|
||||
TaskType = MSRUriHelper.IsMsResourceUri (Key) ? 0 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskType = type;
|
||||
}
|
||||
var arr = MSRUriHelper.KeyToPath (Key);
|
||||
if (TaskType == 1) arr.Insert (0, "Files");
|
||||
else if (TaskType == 0)
|
||||
{
|
||||
if (MSRUriHelper.IsRelativeMsResourceUri (Key)) arr.Insert (0, "resources");
|
||||
}
|
||||
// build path segments
|
||||
Path = new PriPath (arr, ignoreCase: true);
|
||||
}
|
||||
public bool IsUri ()
|
||||
{
|
||||
return TaskType == 0;
|
||||
}
|
||||
public bool IsFilePath ()
|
||||
{
|
||||
return TaskType == 1;
|
||||
}
|
||||
public bool IsRelativeUri ()
|
||||
{
|
||||
return MSRUriHelper.IsRelativeMsResourceUri (Key);
|
||||
}
|
||||
public bool IsFullUri ()
|
||||
{
|
||||
return MSRUriHelper.IsFullMsResourceUri (Key);
|
||||
}
|
||||
public int GetPath (IList<string> output)
|
||||
{
|
||||
if (output == null)
|
||||
throw new ArgumentNullException ("output");
|
||||
|
||||
output.Clear ();
|
||||
if (Path != null)
|
||||
{
|
||||
foreach (var seg in Path)
|
||||
output.Add (seg);
|
||||
}
|
||||
return output.Count;
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return Key;
|
||||
}
|
||||
// Equals / HashCode
|
||||
public override bool Equals (object obj)
|
||||
{
|
||||
return Equals (obj as PriResourceIdentifier);
|
||||
}
|
||||
public bool Equals (PriResourceIdentifier other)
|
||||
{
|
||||
if (ReferenceEquals (other, null))
|
||||
return false;
|
||||
if (ReferenceEquals (this, other))
|
||||
return true;
|
||||
|
||||
// Key and Path should be equivalent
|
||||
return string.Equals (Key, other.Key, StringComparison.OrdinalIgnoreCase)
|
||||
&& ((Path == null && other.Path == null) ||
|
||||
(Path != null && Path.Equals (other.Path)));
|
||||
}
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 31 + (Key ?? "").ToLowerInvariant ().GetHashCode ();
|
||||
hash = hash * 31 + (Path != null ? Path.GetHashCode () : 0);
|
||||
return hash;
|
||||
}
|
||||
public static bool operator == (PriResourceIdentifier a, PriResourceIdentifier b)
|
||||
{
|
||||
if (ReferenceEquals (a, b))
|
||||
return true;
|
||||
if (ReferenceEquals (a, null) || ReferenceEquals (b, null))
|
||||
return false;
|
||||
return a.Equals (b);
|
||||
}
|
||||
public static bool operator != (PriResourceIdentifier a, PriResourceIdentifier b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
||||
243
PriFormat/DecisionInfoSection.cs
Normal file
243
PriFormat/DecisionInfoSection.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public class DecisionInfoSection: Section
|
||||
{
|
||||
public IList<Decision> Decisions { get; private set; }
|
||||
public IList<QualifierSet> QualifierSets { get; private set; }
|
||||
public IList<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 first, ushort num)
|
||||
{
|
||||
FirstQualifierSetIndexIndex = first;
|
||||
NumQualifierSetsInDecision = num;
|
||||
}
|
||||
}
|
||||
|
||||
private struct QualifierSetInfo
|
||||
{
|
||||
public ushort FirstQualifierIndexIndex;
|
||||
public ushort NumQualifiersInSet;
|
||||
|
||||
public QualifierSetInfo (ushort first, ushort num)
|
||||
{
|
||||
FirstQualifierIndexIndex = first;
|
||||
NumQualifiersInSet = num;
|
||||
}
|
||||
}
|
||||
|
||||
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 type, uint offset)
|
||||
{
|
||||
QualifierType = type;
|
||||
OperandValueOffset = offset;
|
||||
}
|
||||
}
|
||||
public override void Dispose ()
|
||||
{
|
||||
Decisions?.Clear ();
|
||||
Decisions = null;
|
||||
QualifierSets?.Clear ();
|
||||
QualifierSets = null;
|
||||
Qualifiers?.Clear ();
|
||||
Qualifiers = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
~DecisionInfoSection () { Dispose (); }
|
||||
}
|
||||
|
||||
public enum QualifierType
|
||||
{
|
||||
Language,
|
||||
Contrast,
|
||||
Scale,
|
||||
HomeRegion,
|
||||
TargetSize,
|
||||
LayoutDirection,
|
||||
Theme,
|
||||
AlternateForm,
|
||||
DXFeatureLevel,
|
||||
Configuration,
|
||||
DeviceFamily,
|
||||
Custom
|
||||
}
|
||||
|
||||
public struct Qualifier
|
||||
{
|
||||
public ushort Index;
|
||||
public QualifierType Type;
|
||||
public ushort Priority;
|
||||
public float FallbackScore;
|
||||
public string Value;
|
||||
|
||||
public Qualifier (ushort index, QualifierType type, ushort priority, float fallbackScore, string value)
|
||||
{
|
||||
Index = index;
|
||||
Type = type;
|
||||
Priority = priority;
|
||||
FallbackScore = fallbackScore;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public struct QualifierSet
|
||||
{
|
||||
public ushort Index;
|
||||
public IList<Qualifier> Qualifiers;
|
||||
|
||||
public QualifierSet (ushort index, IList<Qualifier> qualifiers)
|
||||
{
|
||||
Index = index;
|
||||
Qualifiers = qualifiers;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Decision
|
||||
{
|
||||
public ushort Index;
|
||||
public IList<QualifierSet> QualifierSets;
|
||||
|
||||
public Decision (ushort index, IList<QualifierSet> qualifierSets)
|
||||
{
|
||||
Index = index;
|
||||
QualifierSets = qualifierSets;
|
||||
}
|
||||
}
|
||||
}
|
||||
347
PriFormat/HierarchicalSchemaSection.cs
Normal file
347
PriFormat/HierarchicalSchemaSection.cs
Normal file
@@ -0,0 +1,347 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public class HierarchicalSchemaSection: Section
|
||||
{
|
||||
public HierarchicalSchemaVersionInfo Version { get; private set; }
|
||||
public string UniqueName { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public IList<ResourceMapScope> Scopes { get; private set; }
|
||||
public IList<ResourceMapItem> Items { get; private set; }
|
||||
|
||||
readonly 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 = new List<ResourceMapScope> ();
|
||||
Items = new List<ResourceMapItem> ();
|
||||
return true;
|
||||
}
|
||||
|
||||
binaryReader.ExpectUInt16 (1);
|
||||
ushort uniqueNameLength = binaryReader.ReadUInt16 ();
|
||||
ushort nameLength = binaryReader.ReadUInt16 ();
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
|
||||
bool extendedHNames;
|
||||
if (extendedVersion)
|
||||
{
|
||||
string def = new string (binaryReader.ReadChars (16));
|
||||
switch (def)
|
||||
{
|
||||
case "[def_hnamesx] \0":
|
||||
extendedHNames = true;
|
||||
break;
|
||||
case "[def_hnames] \0":
|
||||
extendedHNames = false;
|
||||
break;
|
||||
default:
|
||||
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 ();
|
||||
scopeAndItemInfos.Add (new ScopeAndItemInfo (parent, fullPathLength, flags, 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;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private struct ScopeAndItemInfo
|
||||
{
|
||||
public ushort Parent;
|
||||
public ushort FullPathLength;
|
||||
public byte Flags;
|
||||
public uint NameOffset;
|
||||
public ushort Index;
|
||||
|
||||
public ScopeAndItemInfo (ushort parent, ushort fullPathLength, byte flags, uint nameOffset, ushort index)
|
||||
{
|
||||
Parent = parent;
|
||||
FullPathLength = fullPathLength;
|
||||
Flags = flags;
|
||||
NameOffset = nameOffset;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public bool IsScope
|
||||
{
|
||||
get { return (Flags & 0x10) != 0; }
|
||||
}
|
||||
|
||||
public bool NameInAscii
|
||||
{
|
||||
get { return (Flags & 0x20) != 0; }
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
public override void Dispose ()
|
||||
{
|
||||
this.Version = null;
|
||||
Scopes?.Clear ();
|
||||
Scopes = null;
|
||||
Items?.Clear ();
|
||||
Items = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
}
|
||||
|
||||
public class HierarchicalSchemaVersionInfo
|
||||
{
|
||||
public ushort MajorVersion { get; private set; }
|
||||
public ushort MinorVersion { get; private set; }
|
||||
public uint Checksum { get; private set; }
|
||||
public uint NumScopes { get; private set; }
|
||||
public uint NumItems { get; private set; }
|
||||
|
||||
public HierarchicalSchemaVersionInfo (ushort major, ushort minor, uint checksum, uint numScopes, uint numItems)
|
||||
{
|
||||
MajorVersion = major;
|
||||
MinorVersion = minor;
|
||||
Checksum = checksum;
|
||||
NumScopes = numScopes;
|
||||
NumItems = numItems;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ResourceMapEntry: IDisposable
|
||||
{
|
||||
public ushort Index { get; private set; }
|
||||
public ResourceMapScope Parent { get; internal set; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
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 ()
|
||||
{
|
||||
Dispose ();
|
||||
}
|
||||
public virtual void Dispose ()
|
||||
{
|
||||
Parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ResourceMapScope: ResourceMapEntry
|
||||
{
|
||||
internal ResourceMapScope (ushort index, ResourceMapScope parent, string name)
|
||||
: base (index, parent, name)
|
||||
{
|
||||
}
|
||||
|
||||
public IList<ResourceMapEntry> Children { get; internal set; }
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return string.Format ("Scope {0} {1} ({2} children)", Index, FullName, Children.Count);
|
||||
}
|
||||
public override void Dispose ()
|
||||
{
|
||||
Children?.Clear ();
|
||||
Children = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
~ResourceMapScope () { Dispose (); }
|
||||
}
|
||||
|
||||
public sealed class ResourceMapItem: ResourceMapEntry
|
||||
{
|
||||
internal ResourceMapItem (ushort index, ResourceMapScope parent, string name)
|
||||
: base (index, parent, name)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return string.Format ("Item {0} {1}", Index, FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
PriFormat/Polyfill.cs
Normal file
63
PriFormat/Polyfill.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public static class Polyfill
|
||||
{
|
||||
public static string ReadString (this BinaryReader reader, Encoding encoding, int length)
|
||||
{
|
||||
//byte [] data = reader.ReadBytes (length * encoding.GetByteCount ("a"));
|
||||
//return encoding.GetString (data, 0, data.Length);
|
||||
// ==========
|
||||
if (length <= 0) return string.Empty;
|
||||
int maxBytes = encoding.GetMaxByteCount (length);
|
||||
byte [] buffer = reader.ReadBytes (maxBytes);
|
||||
if (buffer.Length == 0) return string.Empty;
|
||||
string decoded = encoding.GetString (buffer, 0, buffer.Length);
|
||||
if (decoded.Length > length) decoded = decoded.Substring (0, length);
|
||||
return decoded;
|
||||
}
|
||||
public static string ReadNullTerminatedString (this BinaryReader reader, Encoding encoding)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream ();
|
||||
while (true)
|
||||
{
|
||||
byte b1 = reader.ReadByte ();
|
||||
byte b2 = reader.ReadByte ();
|
||||
|
||||
if (b1 == 0 && b2 == 0)
|
||||
break;
|
||||
|
||||
ms.WriteByte (b1);
|
||||
ms.WriteByte (b2);
|
||||
}
|
||||
return encoding.GetString (ms.ToArray ());
|
||||
// ==========
|
||||
List<byte> bytes = new List<byte> ();
|
||||
byte b;
|
||||
while ((b = reader.ReadByte ()) != 0) bytes.Add (b);
|
||||
return encoding.GetString (bytes.ToArray ());
|
||||
}
|
||||
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.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
139
PriFormat/PriDescriptorSection.cs
Normal file
139
PriFormat/PriDescriptorSection.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public class PriDescriptorSection: Section
|
||||
{
|
||||
public PriDescriptorFlags PriFlags { get; private set; }
|
||||
|
||||
public IList<SectionRef<HierarchicalSchemaSection>> HierarchicalSchemaSections { get; private set; }
|
||||
public IList<SectionRef<DecisionInfoSection>> DecisionInfoSections { get; private set; }
|
||||
public IList<SectionRef<ResourceMapSection>> ResourceMapSections { get; private set; }
|
||||
public IList<SectionRef<ReferencedFileSection>> ReferencedFileSections { get; private set; }
|
||||
public IList<SectionRef<DataItemSection>> DataItemSections { get; private set; }
|
||||
|
||||
public SectionRef<ResourceMapSection> PrimaryResourceMapSection { get; private set; }
|
||||
public bool HasPrimaryResourceMapSection { 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);
|
||||
HasPrimaryResourceMapSection = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
HasPrimaryResourceMapSection = false;
|
||||
}
|
||||
|
||||
ushort numReferencedFileSections = binaryReader.ReadUInt16 ();
|
||||
ushort numDataItemSections = binaryReader.ReadUInt16 ();
|
||||
|
||||
binaryReader.ExpectUInt16 (0);
|
||||
|
||||
// Hierarchical schema sections
|
||||
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;
|
||||
|
||||
// Decision info sections
|
||||
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;
|
||||
|
||||
// Resource map sections
|
||||
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;
|
||||
|
||||
// Referenced file sections
|
||||
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;
|
||||
|
||||
// Data item sections
|
||||
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;
|
||||
}
|
||||
public override void Dispose ()
|
||||
{
|
||||
this.HierarchicalSchemaSections?.Clear ();
|
||||
this.DecisionInfoSections?.Clear ();
|
||||
this.ResourceMapSections?.Clear ();
|
||||
this.ReferencedFileSections?.Clear ();
|
||||
this.DataItemSections?.Clear ();
|
||||
HierarchicalSchemaSections = null;
|
||||
DecisionInfoSections = null;
|
||||
ResourceMapSections = null;
|
||||
ReferencedFileSections = null;
|
||||
DataItemSections = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
}
|
||||
[Flags]
|
||||
public enum PriDescriptorFlags: ushort
|
||||
{
|
||||
AutoMerge = 1,
|
||||
IsDeploymentMergeable = 2,
|
||||
IsDeploymentMergeResult = 4,
|
||||
IsAutomergeMergeResult = 8
|
||||
}
|
||||
}
|
||||
144
PriFormat/PriFile.cs
Normal file
144
PriFormat/PriFile.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public class PriFile: IDisposable
|
||||
{
|
||||
public string Version { get; private set; }
|
||||
public uint TotalFileSize { get; private set; }
|
||||
public IList<TocEntry> TableOfContents { get; private set; }
|
||||
public IList<Section> Sections { get; private set; }
|
||||
private PriFile ()
|
||||
{
|
||||
}
|
||||
public static PriFile Parse (Stream stream)
|
||||
{
|
||||
PriFile priFile = new PriFile ();
|
||||
priFile.ParseInternal (stream);
|
||||
return priFile;
|
||||
}
|
||||
|
||||
private void ParseInternal (Stream stream)
|
||||
{
|
||||
BinaryReader binaryReader = new BinaryReader (stream, Encoding.ASCII);
|
||||
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_pri3":
|
||||
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;
|
||||
bool parseFailure;
|
||||
do
|
||||
{
|
||||
parseSuccess = false;
|
||||
parseFailure = false;
|
||||
for (int i = 0; i < sections.Length; i++)
|
||||
{
|
||||
if (sections [i] != null) continue;
|
||||
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 ("Failed to parse all sections.");
|
||||
}
|
||||
|
||||
private 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)
|
||||
{
|
||||
HierarchicalSchemaSection schema =
|
||||
GetSectionByRef (resourceMapItemRef.SchemaSection);
|
||||
|
||||
return schema.Items [resourceMapItemRef.ItemIndex];
|
||||
}
|
||||
public ByteSpan GetDataItemByRef (DataItemRef dataItemRef)
|
||||
{
|
||||
DataItemSection section =
|
||||
GetSectionByRef (dataItemRef.DataItemSection);
|
||||
|
||||
return section.DataItems [dataItemRef.ItemIndex];
|
||||
}
|
||||
public ReferencedFile GetReferencedFileByRef (
|
||||
ReferencedFileRef referencedFileRef)
|
||||
{
|
||||
SectionRef<ReferencedFileSection> refSection =
|
||||
PriDescriptorSection.ReferencedFileSections.First ();
|
||||
ReferencedFileSection section = GetSectionByRef (refSection);
|
||||
return section.ReferencedFiles [referencedFileRef.FileIndex];
|
||||
}
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
TableOfContents?.Clear ();
|
||||
TableOfContents = null;
|
||||
//Sections?.Clear ();
|
||||
Sections = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
PriFormat/PriFormat.csproj
Normal file
69
PriFormat/PriFormat.csproj
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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>{676E9BD2-A704-4539-9A88-E46654FC94B6}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PriFormat</RootNamespace>
|
||||
<AssemblyName>PriFormat</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</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>
|
||||
</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="DataItemSection.cs" />
|
||||
<Compile Include="Datas.cs" />
|
||||
<Compile Include="DecisionInfoSection.cs" />
|
||||
<Compile Include="HierarchicalSchemaSection.cs" />
|
||||
<Compile Include="Polyfill.cs" />
|
||||
<Compile Include="PriDescriptorSection.cs" />
|
||||
<Compile Include="PriFile.cs" />
|
||||
<Compile Include="PriReader.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ReferencedFileSection.cs" />
|
||||
<Compile Include="ResourceMapSection.cs" />
|
||||
<Compile Include="ReverseMapSection.cs" />
|
||||
<Compile Include="Section.cs" />
|
||||
<Compile Include="StreamHelper.cs" />
|
||||
<Compile Include="SubStream.cs" />
|
||||
<Compile Include="TocEntry.cs" />
|
||||
<Compile Include="UnknownSection.cs" />
|
||||
</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>
|
||||
817
PriFormat/PriReader.cs
Normal file
817
PriFormat/PriReader.cs
Normal file
@@ -0,0 +1,817 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
namespace PriFormat
|
||||
{
|
||||
internal enum SearchState
|
||||
{
|
||||
Pending,
|
||||
Searching,
|
||||
Found,
|
||||
NotFound
|
||||
}
|
||||
internal sealed class SearchTask
|
||||
{
|
||||
public PriPath Path { get; }
|
||||
public BaseResources Result { get; set; }
|
||||
public SearchState State { get; set; }
|
||||
public SearchTask (PriPath path)
|
||||
{
|
||||
Path = path;
|
||||
State = SearchState.Pending;
|
||||
}
|
||||
}
|
||||
public sealed class PriReader: IDisposable
|
||||
{
|
||||
private PriFile _pri;
|
||||
private Stream _stream;
|
||||
private readonly bool _fromFile;
|
||||
private readonly object _lock = new object ();
|
||||
private readonly Dictionary<PriPath, SearchTask> _tasks =
|
||||
new Dictionary<PriPath, SearchTask> ();
|
||||
private Thread _searchThread;
|
||||
private bool _searchRunning;
|
||||
private bool _disposed;
|
||||
private readonly AutoResetEvent _searchWakeup = new AutoResetEvent (false);
|
||||
private PriReader (string filePath)
|
||||
{
|
||||
_fromFile = true;
|
||||
_stream = new FileStream (filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
_pri = PriFile.Parse (_stream);
|
||||
}
|
||||
private PriReader (Stream stream)
|
||||
{
|
||||
_fromFile = false;
|
||||
_stream = stream;
|
||||
_pri = PriFile.Parse (stream);
|
||||
}
|
||||
public static PriReader Open (string filePath) => new PriReader (filePath);
|
||||
public static PriReader Open (Stream stream) => new PriReader (stream);
|
||||
public void AddSearch (IEnumerable<string> resourceNames)
|
||||
{
|
||||
if (resourceNames == null) return;
|
||||
|
||||
bool added = false;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var name in resourceNames)
|
||||
{
|
||||
if (string.IsNullOrEmpty (name)) continue;
|
||||
|
||||
var path = new PriResourceIdentifier (name).Path;
|
||||
if (!_tasks.ContainsKey (path))
|
||||
{
|
||||
_tasks [path] = new SearchTask (path);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (added)
|
||||
EnsureSearchThread ();
|
||||
}
|
||||
public void AddSearch (string resname) { AddSearch (new string [] { resname }); }
|
||||
private void EnsureSearchThread ()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_searchRunning) return;
|
||||
|
||||
_searchRunning = true;
|
||||
_searchThread = new Thread (SearchThreadProc);
|
||||
_searchThread.IsBackground = true;
|
||||
_searchThread.Start ();
|
||||
}
|
||||
}
|
||||
private void SearchThreadProc ()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
bool hasPending = false;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var task in _tasks.Values)
|
||||
{
|
||||
if (task.State == SearchState.Pending)
|
||||
{
|
||||
hasPending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPending)
|
||||
{
|
||||
// 没任务了,休眠等待唤醒
|
||||
_searchWakeup.WaitOne (200);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 真正跑一次搜索
|
||||
RunSearch (TimeSpan.FromSeconds (10));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_searchRunning = false;
|
||||
_searchThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
public BaseResources GetValue (string resourceName)
|
||||
{
|
||||
if (string.IsNullOrEmpty (resourceName)) return null;
|
||||
|
||||
var path = new PriResourceIdentifier (resourceName).Path;
|
||||
SearchTask task;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_tasks.TryGetValue (path, out task))
|
||||
{
|
||||
task = new SearchTask (path);
|
||||
_tasks [path] = task;
|
||||
EnsureSearchThread ();
|
||||
}
|
||||
}
|
||||
|
||||
// 已有结果
|
||||
if (task.State == SearchState.Found)
|
||||
return task.Result;
|
||||
|
||||
// 等待搜索完成
|
||||
while (true)
|
||||
{
|
||||
if (_disposed) return null;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (task.State == SearchState.Found)
|
||||
return task.Result;
|
||||
|
||||
if (task.State == SearchState.NotFound)
|
||||
return null;
|
||||
}
|
||||
|
||||
Thread.Sleep (50);
|
||||
}
|
||||
}
|
||||
public void RunSearch (TimeSpan timeout)
|
||||
{
|
||||
var begin = DateTime.Now;
|
||||
foreach (var rmsRef in _pri.PriDescriptorSection.ResourceMapSections)
|
||||
{
|
||||
var rms = _pri.GetSectionByRef (rmsRef);
|
||||
if (rms == null || rms.HierarchicalSchemaReference != null) continue;
|
||||
var decision = _pri.GetSectionByRef (rms.DecisionInfoSection);
|
||||
foreach (var candidateSet in rms.CandidateSets.Values)
|
||||
{
|
||||
if (DateTime.Now - begin > timeout) return;
|
||||
var item = _pri.GetResourceMapItemByRef (candidateSet.ResourceMapItem);
|
||||
var fullName = item.FullName.Trim ('\\');
|
||||
var parts = fullName.Split (new [] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var itemPath = new PriPath (fullName, true);
|
||||
SearchTask task;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_tasks.TryGetValue (itemPath, out task))
|
||||
continue;
|
||||
|
||||
if (task.State != SearchState.Pending)
|
||||
continue;
|
||||
|
||||
task.State = SearchState.Searching;
|
||||
}
|
||||
var result = ReadCandidate (candidateSet, decision);
|
||||
lock (_lock)
|
||||
{
|
||||
task.Result = result;
|
||||
task.State = result != null
|
||||
? SearchState.Found
|
||||
: SearchState.NotFound;
|
||||
}
|
||||
}
|
||||
}
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var kv in _tasks)
|
||||
{
|
||||
if (kv.Value.State == SearchState.Pending)
|
||||
{
|
||||
kv.Value.State = SearchState.NotFound;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DateTime.Now - begin > timeout) return;
|
||||
}
|
||||
private BaseResources ReadCandidate (
|
||||
CandidateSet candidateSet,
|
||||
DecisionInfoSection decisionInfo)
|
||||
{
|
||||
string value = System.String.Empty;
|
||||
int restype = 0; // 0 string, 1 file
|
||||
Dictionary<StringQualifier, string> strdict = new Dictionary<StringQualifier, string> ();
|
||||
Dictionary<FileQualifier, string> filedict = new Dictionary<FileQualifier, string> ();
|
||||
foreach (var candidate in candidateSet.Candidates)
|
||||
{
|
||||
if (candidate.SourceFile != null)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
var byteSpan = new ByteSpan ();
|
||||
if (candidate.DataItem != null) byteSpan = _pri.GetDataItemByRef (candidate.DataItem);
|
||||
else byteSpan = candidate.Data;
|
||||
_stream.Seek (byteSpan.Offset, SeekOrigin.Begin);
|
||||
var binaryReader = new BinaryReader (_stream, Encoding.Default);
|
||||
{
|
||||
var data = binaryReader.ReadBytes ((int)byteSpan.Length);
|
||||
switch (candidate.Type)
|
||||
{
|
||||
case ResourceValueType.AsciiPath:
|
||||
case ResourceValueType.AsciiString:
|
||||
value = Encoding.ASCII.GetString (data).TrimEnd ('\0'); break;
|
||||
case ResourceValueType.Utf8Path:
|
||||
case ResourceValueType.Utf8String:
|
||||
value = Encoding.UTF8.GetString (data).TrimEnd ('\0'); break;
|
||||
case ResourceValueType.Path:
|
||||
case ResourceValueType.String:
|
||||
value = Encoding.Unicode.GetString (data).TrimEnd ('\0'); break;
|
||||
case ResourceValueType.EmbeddedData:
|
||||
value = Convert.ToBase64String (data); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var qualifierSet = decisionInfo.QualifierSets [candidate.QualifierSet];
|
||||
var qualis = new Dictionary <QualifierType, object> ();
|
||||
foreach (var quali in qualifierSet.Qualifiers)
|
||||
{
|
||||
var qtype = quali.Type;
|
||||
var qvalue = quali.Value;
|
||||
qualis.Add (qtype, qvalue);
|
||||
}
|
||||
if (qualis.ContainsKey (QualifierType.Language))
|
||||
{
|
||||
restype = 0;
|
||||
strdict.Add (new StringQualifier (qualis [QualifierType.Language].ToString ()), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
restype = 1;
|
||||
if (qualis.ContainsKey (QualifierType.Scale))
|
||||
{
|
||||
var cons = qualis.ContainsKey (QualifierType.Contrast) ? qualis [QualifierType.Contrast].ToString () : "None";
|
||||
Contrast cs = Contrast.None;
|
||||
switch (cons?.Trim ()?.ToLower ())
|
||||
{
|
||||
case "white": cs = Contrast.White; break;
|
||||
case "black": cs = Contrast.Black; break;
|
||||
case "high": cs = Contrast.High; break;
|
||||
case "low": cs = Contrast.Low; break;
|
||||
case "none": cs = Contrast.None; break;
|
||||
}
|
||||
filedict.Add (new FileQualifier (Convert.ToInt32 (qualis [QualifierType.Scale]), cs), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (strdict.Count > 0 && filedict.Count > 0)
|
||||
{
|
||||
if (strdict.Count >= filedict.Count) return new StringResources (strdict, true);
|
||||
else return new FileResources (filedict, true);
|
||||
}
|
||||
else if (strdict.Count > 0) return new StringResources (strdict, true);
|
||||
else if (filedict.Count > 0) return new FileResources (filedict, true);
|
||||
return new StringResources ();
|
||||
}
|
||||
public void Dispose ()
|
||||
{
|
||||
_disposed = true;
|
||||
_searchWakeup.Set ();
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_tasks.Clear ();
|
||||
}
|
||||
|
||||
_pri?.Dispose ();
|
||||
_pri = null;
|
||||
|
||||
if (_fromFile)
|
||||
_stream?.Dispose ();
|
||||
|
||||
_stream = null;
|
||||
}
|
||||
public string Resource (string resname) { return GetValue (resname)?.SuitableValue ?? ""; }
|
||||
public Dictionary<string, string> Resources (IEnumerable<string> list)
|
||||
{
|
||||
var ret = new Dictionary<string, string> ();
|
||||
AddSearch (list);
|
||||
foreach (var item in list) ret [item] = Resource (item);
|
||||
return ret;
|
||||
}
|
||||
public string Path (string resname) => Resource (resname);
|
||||
public Dictionary<string, string> Paths (IEnumerable<string> resnames) => Resources (resnames);
|
||||
public string String (string resname) => Resource (resname);
|
||||
public Dictionary<string, string> Strings (IEnumerable<string> resnames) => Resources (resnames);
|
||||
}
|
||||
public sealed class PriReaderBundle: IDisposable
|
||||
{
|
||||
private sealed class PriInst
|
||||
{
|
||||
// 0b01 = lang, 0b10 = scale, 0b11 = both
|
||||
public readonly byte Type;
|
||||
public PriReader Reader;
|
||||
|
||||
public PriInst (byte type, Stream stream)
|
||||
{
|
||||
Type = (byte)(type & 0x03);
|
||||
Reader = PriReader.Open (stream);
|
||||
}
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get { return (Type & 0x03) != 0 && Reader != null; }
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<PriInst> _priFiles = new List<PriInst> (3);
|
||||
private readonly Dictionary<byte, PriInst> _mapPri = new Dictionary<byte, PriInst> ();
|
||||
|
||||
// -----------------------------
|
||||
// Set
|
||||
// -----------------------------
|
||||
// type: 1 = language, 2 = scale, 3 = both
|
||||
public bool Set (byte type, Stream priStream)
|
||||
{
|
||||
byte realType = (byte)(type & 0x03);
|
||||
if (realType == 0 || priStream == null)
|
||||
return false;
|
||||
|
||||
PriInst inst;
|
||||
if (_mapPri.TryGetValue (realType, out inst))
|
||||
{
|
||||
inst.Reader.Dispose ();
|
||||
inst.Reader = PriReader.Open (priStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
inst = new PriInst (realType, priStream);
|
||||
_priFiles.Add (inst);
|
||||
_mapPri [realType] = inst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果你外部仍然是 IStream / IntPtr,这里假定你已有封装成 Stream 的工具
|
||||
public bool Set (byte type, IntPtr priStream)
|
||||
{
|
||||
if (priStream == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
Stream stream = StreamHelper.FromIStream (priStream);
|
||||
return Set (type, stream);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// 内部路由
|
||||
// -----------------------------
|
||||
private PriReader Get (byte type, bool mustReturn)
|
||||
{
|
||||
type = (byte)(type & 0x03);
|
||||
|
||||
PriInst inst;
|
||||
if (_mapPri.TryGetValue (type, out inst))
|
||||
return inst.Reader;
|
||||
|
||||
// fallback: both
|
||||
if (type != 0x03 && _mapPri.TryGetValue (0x03, out inst))
|
||||
return inst.Reader;
|
||||
|
||||
if (mustReturn && _priFiles.Count > 0)
|
||||
return _priFiles [0].Reader;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsMsResourcePrefix (string s)
|
||||
{
|
||||
try
|
||||
{
|
||||
return MSRUriHelper.IsMsResourceUri (s) || MSRUriHelper.IsFullMsResourceUri (s) || MSRUriHelper.IsRelativeMsResourceUri (s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return MSRUriHelper.IsMsResourceUri (s);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// AddSearch
|
||||
// -----------------------------
|
||||
public void AddSearch (IEnumerable<string> arr)
|
||||
{
|
||||
if (arr == null)
|
||||
return;
|
||||
|
||||
List<string> langRes = new List<string> ();
|
||||
List<string> scaleRes = new List<string> ();
|
||||
|
||||
foreach (string it in arr)
|
||||
{
|
||||
if (string.IsNullOrEmpty (it))
|
||||
continue;
|
||||
|
||||
if (IsMsResourcePrefix (it))
|
||||
langRes.Add (it);
|
||||
else
|
||||
scaleRes.Add (it);
|
||||
}
|
||||
|
||||
PriReader langPri = Get (1, true);
|
||||
PriReader scalePri = Get (2, true);
|
||||
|
||||
if (langPri != null && langRes.Count > 0)
|
||||
langPri.AddSearch (langRes);
|
||||
|
||||
if (scalePri != null && scaleRes.Count > 0)
|
||||
scalePri.AddSearch (scaleRes);
|
||||
}
|
||||
|
||||
public void AddSearch (string resName)
|
||||
{
|
||||
if (string.IsNullOrEmpty (resName))
|
||||
return;
|
||||
|
||||
if (IsMsResourcePrefix (resName))
|
||||
{
|
||||
PriReader langPri = Get (1, true);
|
||||
if (langPri != null)
|
||||
langPri.AddSearch (resName);
|
||||
}
|
||||
else
|
||||
{
|
||||
PriReader scalePri = Get (2, true);
|
||||
if (scalePri != null)
|
||||
scalePri.AddSearch (resName);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Resource / Path / String
|
||||
// -----------------------------
|
||||
public string Resource (string resName)
|
||||
{
|
||||
if (string.IsNullOrEmpty (resName))
|
||||
return string.Empty;
|
||||
|
||||
PriReader reader;
|
||||
|
||||
if (IsMsResourcePrefix (resName))
|
||||
reader = Get (1, true);
|
||||
else
|
||||
reader = Get (2, true);
|
||||
|
||||
if (reader == null)
|
||||
return string.Empty;
|
||||
|
||||
var res = reader.GetValue (resName);
|
||||
return res != null ? res.ToString () : string.Empty;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> Resources (IEnumerable<string> resNames)
|
||||
{
|
||||
if (resNames == null)
|
||||
throw new ArgumentNullException ("resNames");
|
||||
|
||||
AddSearch (resNames);
|
||||
|
||||
Dictionary<string, string> result = new Dictionary<string, string> ();
|
||||
foreach (string name in resNames)
|
||||
result [name] = Resource (name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string Path (string resName)
|
||||
{
|
||||
return Resource (resName);
|
||||
}
|
||||
public Dictionary<string, string> Paths (IEnumerable<string> resNames)
|
||||
{
|
||||
return Resources (resNames);
|
||||
}
|
||||
public string String (string resName)
|
||||
{
|
||||
return Resource (resName);
|
||||
}
|
||||
public Dictionary<string, string> Strings (IEnumerable<string> resNames)
|
||||
{
|
||||
return Resources (resNames);
|
||||
}
|
||||
public void Dispose ()
|
||||
{
|
||||
foreach (var inst in _priFiles)
|
||||
{
|
||||
inst.Reader.Dispose ();
|
||||
}
|
||||
|
||||
_mapPri.Clear ();
|
||||
_priFiles.Clear ();
|
||||
}
|
||||
}
|
||||
public abstract class BaseQualifier { }
|
||||
public class StringQualifier: BaseQualifier, IEquatable<StringQualifier>, IEquatable<string>
|
||||
{
|
||||
public string LocaleName { get; private set; }
|
||||
public int LCID => CultureInfo.GetCultureInfo (LocaleName).LCID;
|
||||
public StringQualifier (string localeName) { LocaleName = localeName; }
|
||||
public StringQualifier (int lcid) { LocaleName = CultureInfo.GetCultureInfo (lcid).Name; }
|
||||
public override string ToString () { return $"String Qualifier: {LocaleName} ({LCID})"; }
|
||||
public bool Equals (StringQualifier other)
|
||||
{
|
||||
var ca = new CultureInfo (this.LocaleName);
|
||||
var cb = new CultureInfo (other.LocaleName);
|
||||
return string.Equals (ca.Name, cb.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
public bool Equals (string other) { return this.Equals (new StringQualifier (other)); }
|
||||
public override int GetHashCode () => CultureInfo.GetCultureInfo (LocaleName).Name.GetHashCode ();
|
||||
}
|
||||
public enum Contrast
|
||||
{
|
||||
None,
|
||||
Black,
|
||||
White,
|
||||
High,
|
||||
Low
|
||||
};
|
||||
public class FileQualifier: BaseQualifier, IEquatable<FileQualifier>, IEquatable<int>, IEquatable<Tuple<int, Contrast>>
|
||||
{
|
||||
public Contrast Contrast { get; private set; } = Contrast.None;
|
||||
public int Scale { get; private set; } = 0;
|
||||
public FileQualifier (int scale, Contrast contrast = Contrast.None)
|
||||
{
|
||||
Scale = scale;
|
||||
this.Contrast = contrast;
|
||||
}
|
||||
public override string ToString () { return $"File Qualifier: Scale {Scale}, Contrast {this.Contrast}"; }
|
||||
public bool Equals (FileQualifier other)
|
||||
{
|
||||
return this.Contrast == other.Contrast && this.Scale == other.Scale;
|
||||
}
|
||||
public bool Equals (int other)
|
||||
{
|
||||
return this.Scale == other && this.Contrast == Contrast.None;
|
||||
}
|
||||
public bool Equals (Tuple<int, Contrast> other)
|
||||
{
|
||||
return this.Contrast == other.Item2 && this.Scale == other.Item1;
|
||||
}
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 31 + Scale.GetHashCode ();
|
||||
hash = hash * 31 + Contrast.GetHashCode ();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
public abstract class BaseResources: IDisposable
|
||||
{
|
||||
public virtual void Dispose () { }
|
||||
public virtual string SuitableValue { get; } = String.Empty;
|
||||
public virtual Dictionary<BaseQualifier, string> AllValue { get; } = new Dictionary<BaseQualifier, string> ();
|
||||
/// <summary>
|
||||
/// 表示是否寻找过,如果真则不用再次寻找。
|
||||
/// </summary>
|
||||
public bool IsFind { get; set; }
|
||||
}
|
||||
public class StringResources: BaseResources, IDictionary<StringQualifier, string>, IDictionary<string, string>
|
||||
{
|
||||
private Dictionary<StringQualifier, string> dict = new Dictionary<StringQualifier, string> ();
|
||||
public string this [string key]
|
||||
{
|
||||
get { return dict [new StringQualifier (key)]; }
|
||||
set { }
|
||||
}
|
||||
public string this [StringQualifier key]
|
||||
{
|
||||
get { return dict [key]; }
|
||||
set { }
|
||||
}
|
||||
public int Count => dict.Count;
|
||||
public bool IsReadOnly => true;
|
||||
public ICollection<StringQualifier> Keys => dict.Keys;
|
||||
public ICollection<string> Values => dict.Values;
|
||||
ICollection<string> IDictionary<string, string>.Keys => dict.Keys.Select (k => k.LocaleName).ToList ();
|
||||
public void Add (KeyValuePair<string, string> item) { }
|
||||
public void Add (KeyValuePair<StringQualifier, string> item) { }
|
||||
public void Add (string key, string value) { }
|
||||
public void Add (StringQualifier key, string value) { }
|
||||
public void Clear () { }
|
||||
public bool Contains (KeyValuePair<string, string> item)
|
||||
{
|
||||
string value;
|
||||
if (TryGetValue (item.Key, out value)) return value == item.Value;
|
||||
return false;
|
||||
}
|
||||
public bool Contains (KeyValuePair<StringQualifier, string> item) => dict.Contains (item);
|
||||
public bool ContainsKey (string key)
|
||||
{
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
if (string.Equals (kv.Key.LocaleName, key, StringComparison.OrdinalIgnoreCase)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public bool ContainsKey (StringQualifier key) => dict.ContainsKey (key);
|
||||
public void CopyTo (KeyValuePair<string, string> [] array, int arrayIndex)
|
||||
{
|
||||
if (array == null) throw new ArgumentNullException ("array");
|
||||
if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException ("arrayIndex");
|
||||
if (array.Length - arrayIndex < dict.Count) throw new ArgumentException ("The destination array is not large enough.");
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
array [arrayIndex++] = new KeyValuePair<string, string> (
|
||||
kv.Key.LocaleName, kv.Value);
|
||||
}
|
||||
}
|
||||
public void CopyTo (KeyValuePair<StringQualifier, string> [] array, int arrayIndex)
|
||||
{
|
||||
if (array == null) throw new ArgumentNullException ("array");
|
||||
if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException ("arrayIndex");
|
||||
if (array.Length - arrayIndex < dict.Count) throw new ArgumentException ("The destination array is not large enough.");
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
array [arrayIndex++] = new KeyValuePair<StringQualifier, string> (
|
||||
kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
public IEnumerator<KeyValuePair<StringQualifier, string>> GetEnumerator () => dict.GetEnumerator ();
|
||||
public bool Remove (KeyValuePair<StringQualifier, string> item) { return false; }
|
||||
public bool Remove (string key) => false;
|
||||
public bool Remove (KeyValuePair<string, string> item) => false;
|
||||
public bool Remove (StringQualifier key) { return false; }
|
||||
public bool TryGetValue (string key, out string value)
|
||||
{
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
if (string.Equals (kv.Key.LocaleName, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = kv.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
public bool TryGetValue (StringQualifier key, out string value) => dict.TryGetValue (key, out value);
|
||||
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator ()
|
||||
{
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
yield return new KeyValuePair<string, string> (kv.Key.LocaleName, kv.Value);
|
||||
}
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator () => dict.GetEnumerator ();
|
||||
internal static bool LocaleNameEqualsIgnoreRegion (string a, string b)
|
||||
{
|
||||
var ca = new CultureInfo (a);
|
||||
var cb = new CultureInfo (b);
|
||||
return string.Equals (ca.TwoLetterISOLanguageName, cb.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
public static string GetCoincidentValue (Dictionary<StringQualifier, string> d, string localeName)
|
||||
{
|
||||
if (d == null) return null;
|
||||
foreach (var kv in d)
|
||||
{
|
||||
if (kv.Key.LocaleName?.Trim ()?.ToLower () == localeName?.Trim ()?.ToLower ()) return kv.Value;
|
||||
}
|
||||
var targetLang = new StringQualifier (localeName);
|
||||
foreach (var kv in d)
|
||||
{
|
||||
if (kv.Key.LCID == targetLang.LCID) return kv.Value;
|
||||
}
|
||||
foreach (var kv in d)
|
||||
{
|
||||
if (LocaleNameEqualsIgnoreRegion (kv.Key.LocaleName, localeName)) return kv.Value;
|
||||
}
|
||||
return String.Empty;
|
||||
}
|
||||
public static string GetSuitableValue (Dictionary<StringQualifier, string> d)
|
||||
{
|
||||
var ret = GetCoincidentValue (d, LocaleExt.GetComputerLocaleCode ());
|
||||
if (String.IsNullOrEmpty (ret)) ret = GetCoincidentValue (d, "en-us");
|
||||
if (String.IsNullOrEmpty (ret) && d.Count > 0) ret = d.ElementAt (0).Value;
|
||||
return ret;
|
||||
}
|
||||
public override string SuitableValue => GetSuitableValue (dict);
|
||||
public override Dictionary<BaseQualifier, string> AllValue => dict.ToDictionary (kv => (BaseQualifier)kv.Key, kv => kv.Value);
|
||||
public override void Dispose ()
|
||||
{
|
||||
dict.Clear ();
|
||||
dict = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
~StringResources () { Dispose (); }
|
||||
public StringResources (Dictionary<StringQualifier, string> _dict, bool isfind = true)
|
||||
{
|
||||
dict = _dict;
|
||||
IsFind = isfind;
|
||||
}
|
||||
public StringResources (Dictionary<string, string> _dict, bool isfind = true)
|
||||
{
|
||||
dict = _dict.ToDictionary (kv => new StringQualifier (kv.Key), kv => kv.Value);
|
||||
IsFind = isfind;
|
||||
}
|
||||
public StringResources () { IsFind = false; }
|
||||
}
|
||||
public class FileResources: BaseResources, IDictionary<FileQualifier, string>
|
||||
{
|
||||
private Dictionary<FileQualifier, string> dict = new Dictionary<FileQualifier, string> ();
|
||||
public string this [FileQualifier key]
|
||||
{
|
||||
get { return dict [key]; }
|
||||
set { }
|
||||
}
|
||||
public int Count => dict.Count;
|
||||
public bool IsReadOnly => false;
|
||||
public ICollection<FileQualifier> Keys => dict.Keys;
|
||||
public ICollection<string> Values => dict.Values;
|
||||
public void Add (KeyValuePair<FileQualifier, string> item) { }
|
||||
public void Add (FileQualifier key, string value) { }
|
||||
public void Clear () { }
|
||||
public bool Contains (KeyValuePair<FileQualifier, string> item) => dict.Contains (item);
|
||||
public bool ContainsKey (FileQualifier key) => dict.ContainsKey (key);
|
||||
public void CopyTo (KeyValuePair<FileQualifier, string> [] array, int arrayIndex) { }
|
||||
public IEnumerator<KeyValuePair<FileQualifier, string>> GetEnumerator () => dict.GetEnumerator ();
|
||||
public bool Remove (KeyValuePair<FileQualifier, string> item) => false;
|
||||
public bool Remove (FileQualifier key) => false;
|
||||
public bool TryGetValue (FileQualifier key, out string value) => dict.TryGetValue (key, out value);
|
||||
IEnumerator IEnumerable.GetEnumerator () => dict.GetEnumerator ();
|
||||
public static string GetCoincidentValue (Dictionary<FileQualifier, string> d, int scale, Contrast contrast = Contrast.None)
|
||||
{
|
||||
var td = d.OrderBy (k => k.Key.Contrast).ThenBy (k => k.Key.Scale);
|
||||
foreach (var kv in td)
|
||||
{
|
||||
if (kv.Key.Contrast == contrast)
|
||||
{
|
||||
if (kv.Key.Scale >= scale) return kv.Value;
|
||||
}
|
||||
}
|
||||
foreach (var kv in td)
|
||||
{
|
||||
if (kv.Key.Contrast == Contrast.None)
|
||||
if (kv.Key.Scale >= scale) return kv.Value;
|
||||
}
|
||||
foreach (var kv in td)
|
||||
{
|
||||
if (kv.Key.Contrast == Contrast.Black)
|
||||
if (kv.Key.Scale >= scale) return kv.Value;
|
||||
}
|
||||
foreach (var kv in td)
|
||||
{
|
||||
if (kv.Key.Scale >= scale) return kv.Value;
|
||||
}
|
||||
if (d.Count > 0) return d.ElementAt (0).Value;
|
||||
return String.Empty;
|
||||
}
|
||||
public static string GetSuitableValue (Dictionary<FileQualifier, string> d, Contrast contrast = Contrast.None) => GetCoincidentValue (d, UIExt.DPI, contrast);
|
||||
public override string SuitableValue => GetSuitableValue (dict);
|
||||
public override Dictionary<BaseQualifier, string> AllValue => dict.ToDictionary (kv => (BaseQualifier)kv.Key, kv => kv.Value);
|
||||
public override void Dispose ()
|
||||
{
|
||||
dict.Clear ();
|
||||
dict = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
~FileResources () { Dispose (); }
|
||||
public FileResources (Dictionary<FileQualifier, string> _dict, bool isfind = true)
|
||||
{
|
||||
dict = _dict;
|
||||
IsFind = isfind;
|
||||
}
|
||||
public FileResources (Dictionary<Tuple<int, Contrast>, string> _dict, bool isfind = true)
|
||||
{
|
||||
dict = _dict.ToDictionary (kv => new FileQualifier (kv.Key.Item1, kv.Key.Item2), kv => kv.Value);
|
||||
IsFind = isfind;
|
||||
}
|
||||
public FileResources () { IsFind = false; }
|
||||
}
|
||||
}
|
||||
36
PriFormat/Properties/AssemblyInfo.cs
Normal file
36
PriFormat/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 有关程序集的一般信息由以下
|
||||
// 控制。更改这些特性值可修改
|
||||
// 与程序集关联的信息。
|
||||
[assembly: AssemblyTitle("PriFormat")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("PriFormat")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
//将 ComVisible 设置为 false 将使此程序集中的类型
|
||||
//对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
|
||||
//请将此类型的 ComVisible 特性设置为 true。
|
||||
[assembly: ComVisible (true)]
|
||||
|
||||
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
|
||||
[assembly: Guid("676e9bd2-a704-4539-9a88-e46654fc94b6")]
|
||||
|
||||
// 程序集的版本信息由下列四个值组成:
|
||||
//
|
||||
// 主版本
|
||||
// 次版本
|
||||
// 生成号
|
||||
// 修订号
|
||||
//
|
||||
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
|
||||
// 方法是按如下所示使用“*”: :
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
248
PriFormat/ReferencedFileSection.cs
Normal file
248
PriFormat/ReferencedFileSection.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public class ReferencedFileSection: Section
|
||||
{
|
||||
public IList<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 = new ReadOnlyCollection<ReferencedFile> (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;
|
||||
}
|
||||
}
|
||||
public override void Dispose ()
|
||||
{
|
||||
ReferencedFiles?.Clear ();
|
||||
ReferencedFiles = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
~ReferencedFileSection () { Dispose (); }
|
||||
}
|
||||
|
||||
public abstract class ReferencedEntry: IDisposable
|
||||
{
|
||||
public ReferencedFolder Parent { get; internal set; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
internal ReferencedEntry (ReferencedFolder parent, string name)
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
private string fullName;
|
||||
|
||||
public string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fullName == null)
|
||||
{
|
||||
if (Parent == null)
|
||||
fullName = Name;
|
||||
else
|
||||
fullName = Parent.FullName + "\\" + Name;
|
||||
}
|
||||
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose ()
|
||||
{
|
||||
Parent = null;
|
||||
}
|
||||
~ReferencedEntry () { Dispose (); }
|
||||
}
|
||||
|
||||
public sealed class ReferencedFolder: ReferencedEntry
|
||||
{
|
||||
internal ReferencedFolder (ReferencedFolder parent, string name)
|
||||
: base (parent, name)
|
||||
{
|
||||
}
|
||||
|
||||
public IList<ReferencedEntry> Children { get; internal set; }
|
||||
public override void Dispose ()
|
||||
{
|
||||
Children?.Clear ();
|
||||
Children = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
~ReferencedFolder () { Dispose (); }
|
||||
}
|
||||
|
||||
public sealed class ReferencedFile: ReferencedEntry
|
||||
{
|
||||
internal ReferencedFile (ReferencedFolder parent, string name)
|
||||
: base (parent, name)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class ReferencedFileRef
|
||||
{
|
||||
internal int fileIndex;
|
||||
public int FileIndex => fileIndex;
|
||||
internal ReferencedFileRef (int fileIndex)
|
||||
{
|
||||
this.fileIndex = fileIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
414
PriFormat/ResourceMapSection.cs
Normal file
414
PriFormat/ResourceMapSection.cs
Normal file
@@ -0,0 +1,414 @@
|
||||
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<HierarchicalSchemaSection> SchemaSection { get; private set; }
|
||||
public SectionRef<DecisionInfoSection> DecisionInfoSection { get; private set; }
|
||||
public IDictionary<ushort, CandidateSet> 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<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> (ItemToItemInfoGroupCount);
|
||||
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> (itemInfoGroupCount);
|
||||
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> ((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<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 = 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<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;
|
||||
}
|
||||
}
|
||||
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<Candidate> Candidates { get; private set; }
|
||||
|
||||
internal CandidateSet (ResourceMapItemRef resourceMapItem, ushort decisionIndex, IList<Candidate> 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<HierarchicalSchemaSection> schemaSection;
|
||||
internal int itemIndex;
|
||||
public SectionRef<HierarchicalSchemaSection> SchemaSection => schemaSection;
|
||||
public int ItemIndex => itemIndex;
|
||||
internal ResourceMapItemRef (SectionRef<HierarchicalSchemaSection> schemaSection, int itemIndex)
|
||||
{
|
||||
this.schemaSection = schemaSection;
|
||||
this.itemIndex = itemIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
210
PriFormat/ReverseMapSection.cs
Normal file
210
PriFormat/ReverseMapSection.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public class ReverseMapSection: Section
|
||||
{
|
||||
public uint [] Mapping { get; private set; }
|
||||
public IList<ResourceMapScope> Scopes { get; private set; }
|
||||
public IList<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 ();
|
||||
|
||||
if (numEntries != numScopes + numItems)
|
||||
throw new InvalidDataException ();
|
||||
|
||||
binaryReader.ExpectUInt32 (numItems);
|
||||
uint unicodeDataLength = binaryReader.ReadUInt32 ();
|
||||
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 ();
|
||||
|
||||
scopeAndItemInfos.Add (new ScopeAndItemInfo (parent, fullPathLength, flags, 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 = new ReadOnlyCollection<ResourceMapScope> (scopes);
|
||||
Items = new ReadOnlyCollection<ResourceMapItem> (items);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private struct ScopeAndItemInfo
|
||||
{
|
||||
public ushort Parent;
|
||||
public ushort FullPathLength;
|
||||
public byte Flags;
|
||||
public uint NameOffset;
|
||||
public ushort Index;
|
||||
|
||||
public ScopeAndItemInfo (ushort parent, ushort fullPathLength, byte flags, uint nameOffset, ushort index)
|
||||
{
|
||||
Parent = parent;
|
||||
FullPathLength = fullPathLength;
|
||||
Flags = flags;
|
||||
NameOffset = nameOffset;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public bool IsScope
|
||||
{
|
||||
get { return (Flags & 0x10) != 0; }
|
||||
}
|
||||
|
||||
public bool NameInAscii
|
||||
{
|
||||
get { return (Flags & 0x20) != 0; }
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
public override void Dispose ()
|
||||
{
|
||||
Mapping = null;
|
||||
Scopes?.Clear ();
|
||||
Scopes = null;
|
||||
Items?.Clear ();
|
||||
Items = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
~ReverseMapSection () { Dispose (); }
|
||||
}
|
||||
}
|
||||
126
PriFormat/Section.cs
Normal file
126
PriFormat/Section.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public abstract class Section: IDisposable
|
||||
{
|
||||
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 == null)
|
||||
throw new ArgumentNullException ("sectionIdentifier");
|
||||
|
||||
if (sectionIdentifier.Length != 16)
|
||||
throw new ArgumentException (
|
||||
"Section identifiers must be exactly 16 characters long.",
|
||||
"sectionIdentifier");
|
||||
|
||||
SectionIdentifier = sectionIdentifier;
|
||||
PriFile = priFile;
|
||||
}
|
||||
|
||||
internal bool Parse (BinaryReader binaryReader)
|
||||
{
|
||||
// identifier
|
||||
string identifier = new string (binaryReader.ReadChars (16));
|
||||
if (identifier != SectionIdentifier)
|
||||
throw new InvalidDataException ("Unexpected section identifier.");
|
||||
|
||||
SectionQualifier = binaryReader.ReadUInt32 ();
|
||||
Flags = binaryReader.ReadUInt16 ();
|
||||
SectionFlags = binaryReader.ReadUInt16 ();
|
||||
SectionLength = binaryReader.ReadUInt32 ();
|
||||
|
||||
binaryReader.ExpectUInt32 (0);
|
||||
|
||||
// 跳到 section 尾部校验
|
||||
long contentLength = SectionLength - 16 - 24;
|
||||
|
||||
binaryReader.BaseStream.Seek (contentLength, SeekOrigin.Current);
|
||||
|
||||
binaryReader.ExpectUInt32 (0xDEF5FADE);
|
||||
binaryReader.ExpectUInt32 (SectionLength);
|
||||
|
||||
// 回到 section 内容起始位置
|
||||
binaryReader.BaseStream.Seek (-8 - contentLength, SeekOrigin.Current);
|
||||
|
||||
//关键点:SubStream + BinaryReader 生命周期
|
||||
using (SubStream subStream = new SubStream (
|
||||
binaryReader.BaseStream,
|
||||
binaryReader.BaseStream.Position,
|
||||
contentLength))
|
||||
{
|
||||
using (BinaryReader subReader =
|
||||
new BinaryReader (subStream, Encoding.ASCII))
|
||||
{
|
||||
return ParseSectionContent (subReader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool ParseSectionContent (BinaryReader binaryReader);
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return SectionIdentifier.TrimEnd ('\0', ' ') +
|
||||
" length: " + SectionLength;
|
||||
}
|
||||
|
||||
internal static Section CreateForIdentifier (
|
||||
string sectionIdentifier,
|
||||
PriFile priFile)
|
||||
{
|
||||
if (sectionIdentifier == null)
|
||||
throw new ArgumentNullException ("sectionIdentifier");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose ()
|
||||
{
|
||||
this.PriFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
171
PriFormat/StreamHelper.cs
Normal file
171
PriFormat/StreamHelper.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public static class StreamHelper
|
||||
{
|
||||
public static Stream FromIStream (IStream comStream)
|
||||
{
|
||||
if (comStream == null) return null;
|
||||
return new ComIStreamBufferedReader (comStream);
|
||||
}
|
||||
|
||||
public static Stream FromIStream (IntPtr comStreamPtr)
|
||||
{
|
||||
if (comStreamPtr == IntPtr.Zero) return null;
|
||||
|
||||
IStream comStream =
|
||||
(IStream)Marshal.GetObjectForIUnknown (comStreamPtr);
|
||||
|
||||
return new ComIStreamBufferedReader (comStream);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ComIStreamBufferedReader: Stream
|
||||
{
|
||||
private readonly MemoryStream _memory;
|
||||
private bool _disposed;
|
||||
|
||||
public ComIStreamBufferedReader (IStream comStream)
|
||||
{
|
||||
if (comStream == null)
|
||||
throw new ArgumentNullException ("comStream");
|
||||
|
||||
_memory = LoadAll (comStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次性把 IStream 全部复制到托管内存
|
||||
/// </summary>
|
||||
private static MemoryStream LoadAll (IStream stream)
|
||||
{
|
||||
// 保存原始位置
|
||||
long originalPos = GetPosition (stream);
|
||||
|
||||
try
|
||||
{
|
||||
// Seek 到头
|
||||
stream.Seek (0, 0 /* STREAM_SEEK_SET */, IntPtr.Zero);
|
||||
|
||||
// 获取长度
|
||||
System.Runtime.InteropServices.ComTypes.STATSTG stat;
|
||||
stream.Stat (out stat, 1); // STATFLAG_NONAME
|
||||
long length = stat.cbSize;
|
||||
|
||||
if (length < 0 || length > int.MaxValue)
|
||||
throw new NotSupportedException ("Stream too large to buffer.");
|
||||
|
||||
MemoryStream ms = new MemoryStream ((int)length);
|
||||
|
||||
byte [] buffer = new byte [64 * 1024]; // 64KB
|
||||
IntPtr pcbRead = Marshal.AllocHGlobal (sizeof (int));
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
stream.Read (buffer, buffer.Length, pcbRead);
|
||||
int read = Marshal.ReadInt32 (pcbRead);
|
||||
if (read <= 0)
|
||||
break;
|
||||
|
||||
ms.Write (buffer, 0, read);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal (pcbRead);
|
||||
}
|
||||
|
||||
ms.Position = 0;
|
||||
return ms;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复 COM IStream 的原始位置
|
||||
stream.Seek (originalPos, 0 /* STREAM_SEEK_SET */, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
private static long GetPosition (IStream stream)
|
||||
{
|
||||
IntPtr posPtr = Marshal.AllocHGlobal (sizeof (long));
|
||||
try
|
||||
{
|
||||
stream.Seek (0, 1 /* STREAM_SEEK_CUR */, posPtr);
|
||||
return Marshal.ReadInt64 (posPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal (posPtr);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Stream 重写,全部委托给 MemoryStream =====
|
||||
|
||||
public override bool CanRead { get { return !_disposed; } }
|
||||
public override bool CanSeek { get { return !_disposed; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { EnsureNotDisposed (); return _memory.Length; }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { EnsureNotDisposed (); return _memory.Position; }
|
||||
set { EnsureNotDisposed (); _memory.Position = value; }
|
||||
}
|
||||
|
||||
public override int Read (byte [] buffer, int offset, int count)
|
||||
{
|
||||
EnsureNotDisposed ();
|
||||
return _memory.Read (buffer, offset, count);
|
||||
}
|
||||
|
||||
public override long Seek (long offset, SeekOrigin origin)
|
||||
{
|
||||
EnsureNotDisposed ();
|
||||
return _memory.Seek (offset, origin);
|
||||
}
|
||||
|
||||
public override void Flush ()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
public override void SetLength (long value)
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
|
||||
public override void Write (byte [] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_memory.Dispose ();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
private void EnsureNotDisposed ()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException ("ComIStreamBufferedReader");
|
||||
}
|
||||
}
|
||||
}
|
||||
110
PriFormat/SubStream.cs
Normal file
110
PriFormat/SubStream.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
internal sealed class SubStream: Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private readonly long _baseOffset;
|
||||
private readonly long _length;
|
||||
private long _position;
|
||||
|
||||
public SubStream (Stream baseStream, long offset, long length)
|
||||
{
|
||||
if (baseStream == null)
|
||||
throw new ArgumentNullException ("baseStream");
|
||||
if (!baseStream.CanSeek)
|
||||
throw new ArgumentException ("Base stream must be seekable.");
|
||||
|
||||
_baseStream = baseStream;
|
||||
_baseOffset = offset;
|
||||
_length = length;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanSeek { get { return true; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { return _length; }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return _position; }
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > _length)
|
||||
throw new ArgumentOutOfRangeException ("value");
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read (byte [] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException ("buffer");
|
||||
if (offset < 0 || count < 0 || buffer.Length - offset < count)
|
||||
throw new ArgumentOutOfRangeException ();
|
||||
|
||||
long remaining = _length - _position;
|
||||
if (remaining <= 0)
|
||||
return 0;
|
||||
|
||||
if (count > remaining)
|
||||
count = (int)remaining;
|
||||
|
||||
_baseStream.Position = _baseOffset + _position;
|
||||
int read = _baseStream.Read (buffer, offset, count);
|
||||
_position += read;
|
||||
return read;
|
||||
}
|
||||
|
||||
public override long Seek (long offset, SeekOrigin origin)
|
||||
{
|
||||
long target;
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
target = offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
target = _position + offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
target = _length + offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException ("origin");
|
||||
}
|
||||
|
||||
if (target < 0 || target > _length)
|
||||
throw new IOException ("Seek out of range.");
|
||||
|
||||
_position = target;
|
||||
return _position;
|
||||
}
|
||||
|
||||
public override void Flush ()
|
||||
{
|
||||
// no-op (read-only)
|
||||
}
|
||||
|
||||
public override void SetLength (long value)
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
|
||||
public override void Write (byte [] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
PriFormat/TocEntry.cs
Normal file
35
PriFormat/TocEntry.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
public sealed class TocEntry
|
||||
{
|
||||
public string SectionIdentifier { get; private set; }
|
||||
public ushort Flags { get; private set; }
|
||||
public ushort SectionFlags { get; private set; }
|
||||
public uint SectionQualifier { get; private set; }
|
||||
public uint SectionOffset { get; private set; }
|
||||
public uint SectionLength { get; private set; }
|
||||
private TocEntry () { }
|
||||
internal static TocEntry Parse (BinaryReader reader)
|
||||
{
|
||||
return new TocEntry
|
||||
{
|
||||
SectionIdentifier = new string (reader.ReadChars (16)),
|
||||
Flags = reader.ReadUInt16 (),
|
||||
SectionFlags = reader.ReadUInt16 (),
|
||||
SectionQualifier = reader.ReadUInt32 (),
|
||||
SectionOffset = reader.ReadUInt32 (),
|
||||
SectionLength = reader.ReadUInt32 ()
|
||||
};
|
||||
}
|
||||
public override string ToString ()
|
||||
{
|
||||
return SectionIdentifier.TrimEnd ('\0', ' ') + "\t length: " + SectionLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
PriFormat/UnknownSection.cs
Normal file
28
PriFormat/UnknownSection.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PriFormat
|
||||
{
|
||||
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 override void Dispose ()
|
||||
{
|
||||
SectionContent = null;
|
||||
base.Dispose ();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user