diff --git a/AppxPackage/DataInterface.cs b/AppxPackage/DataInterface.cs index 42adb55..1cb067c 100644 --- a/AppxPackage/DataInterface.cs +++ b/AppxPackage/DataInterface.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.ComponentModel; +using System.Reflection; + namespace AppxPackage { [Serializable] @@ -229,4 +231,31 @@ namespace AppxPackage.Info return a._value < value; } } + public static class JSHelper + { + public static void CallJS (object jsFunc, params object [] args) + { + if (jsFunc == null) return; + + try + { + // 这里固定第一个参数为 thisArg(比如 1) + object [] realArgs = new object [args.Length + 1]; + realArgs [0] = jsFunc; // thisArg + Array.Copy (args, 0, realArgs, 1, args.Length); + + jsFunc.GetType ().InvokeMember ( + "call", + BindingFlags.InvokeMethod, + null, + jsFunc, + realArgs + ); + } + catch + { + // ignore errors in callback invocation + } + } + } } diff --git a/AppxPackage/ManifestReader.cs b/AppxPackage/ManifestReader.cs index 59311c6..ed71c49 100644 --- a/AppxPackage/ManifestReader.cs +++ b/AppxPackage/ManifestReader.cs @@ -7,6 +7,8 @@ using System.Text; using AppxPackage.Info; using NativeWrappers; using System.IO; +using System.Threading; +//using PriFormat; namespace AppxPackage { public static class DataUrlHelper @@ -543,13 +545,14 @@ namespace AppxPackage private MRApplication ReadSingleApplication (IntPtr hKeyValues) { var app = new MRApplication (ref m_hReader, ref m_pri, ref m_usePri, ref m_enablePri, m_reader.Value.FileRoot); - uint pairCount = (uint)Marshal.ReadInt32 (hKeyValues); - int baseOffset = Marshal.SizeOf (typeof (uint)); - int pairSize = Marshal.SizeOf (typeof (PackageReadHelper.PAIR_PVOID)); - for (int j = 0; j < pairCount; j++) + int pairCount = Marshal.ReadInt32 (hKeyValues); + IntPtr arrayBase = IntPtr.Add (hKeyValues, sizeof (uint)); + for (int i = 0; i < pairCount; i++) { - IntPtr pPair = IntPtr.Add (hKeyValues, baseOffset + j * pairSize); - var pair = (PackageReadHelper.PAIR_PVOID)Marshal.PtrToStructure (pPair, typeof (PackageReadHelper.PAIR_PVOID)); + IntPtr pPairPtr = Marshal.ReadIntPtr (arrayBase, i * IntPtr.Size); + if (pPairPtr == IntPtr.Zero) continue; + PackageReadHelper.PAIR_PVOID pair = + (PackageReadHelper.PAIR_PVOID)Marshal.PtrToStructure (pPairPtr, typeof (PackageReadHelper.PAIR_PVOID)); if (pair.lpKey == IntPtr.Zero) continue; string key = Marshal.PtrToStringUni (pair.lpKey); if (string.IsNullOrEmpty (key)) continue; @@ -849,12 +852,12 @@ namespace AppxPackage private string m_filePath = string.Empty; private bool m_usePRI = false; private bool m_enablePRI = false; - private PriReader m_pri = new PriReader (); + private PriReader m_pri = null; public IntPtr Instance => m_hReader; public string FileRoot{ get { return Path.GetPathRoot (m_filePath); } } private void InitPri () { - m_pri.Dispose (); + m_pri?.Dispose (); if (!m_usePRI) return; #region Get PRI IStream switch (Type) @@ -862,45 +865,12 @@ namespace AppxPackage case PackageType.Appx: { var pripath = Path.Combine (FileRoot, "resources.pri"); - m_pri.Create (pripath); + m_pri = new PriReader (pripath); } break; } #endregion - try - { - var resnames = new HashSet (); - using (var prop = Properties) - { - var temp = prop.Description; - if (PriFileHelper.IsMsResourcePrefix (temp)) resnames.Add (temp); - temp = prop.DisplayName; - if (PriFileHelper.IsMsResourcePrefix (temp)) resnames.Add (temp); - temp = prop.Publisher; - if (PriFileHelper.IsMsResourcePrefix (temp)) resnames.Add (temp); - resnames.Add (prop.Logo); - } - using (var apps = Applications) - { - foreach (var app in apps) - { - foreach (var pair in app) - { - foreach (var pathres in ConstData.FilePathItems) - { - if ((pathres?.Trim ()?.ToLower () ?? "") == (pair.Key?.Trim ()?.ToLower ())) - { - resnames.Add (pair.Value); - } - else if (PriFileHelper.IsMsResourcePrefix (pair.Value)) - resnames.Add (pair.Value); - } - } - } - } - m_pri.AddSearch (resnames); - } - catch (Exception) { } + return; } public PackageType Type { @@ -979,6 +949,25 @@ namespace AppxPackage Newtonsoft.Json.Formatting.Indented ); } + public void BuildJsonTextAsync (object callback) + { + if (callback == null) return; + Thread thread = new Thread (() => { + string json = string.Empty; + try + { + json = BuildJsonText (); + } + catch + { + json = string.Empty; + } + JSHelper.CallJS (callback, json); + }); + thread.SetApartmentState (ApartmentState.MTA); + thread.IsBackground = true; + thread.Start (); + } private object BuildJsonObject () { return new diff --git a/AppxPackage/PackageReader.cs b/AppxPackage/PackageReader.cs index a2ca103..05043a0 100644 --- a/AppxPackage/PackageReader.cs +++ b/AppxPackage/PackageReader.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using AppxPackage.Info; using NativeWrappers; +//using PriFormat; namespace AppxPackage { internal static partial class ConstData @@ -220,6 +222,9 @@ namespace AppxPackage if (hr.Succeeded) return ret != 0; else return defaultValue; } +#if DEBUG + public string Debug_StringValue (string attr) => StringValue (attr); +#endif protected string StringResValue (string attr) { var res = StringValue (attr); @@ -237,6 +242,7 @@ namespace AppxPackage protected string PathResValue (string attr) { var res = StringValue (attr); + //var id = new PriFormat.PriResourceIdentifier (res); try { if (m_usePri && m_enablePri) @@ -267,7 +273,7 @@ namespace AppxPackage { IntPtr base64Head = IntPtr.Zero; var base64s = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head); - if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } + //if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } return PackageReadHelper.GetStringAndFreeFromPkgRead (base64s); } catch (Exception) { return ""; } @@ -288,7 +294,7 @@ namespace AppxPackage pic = PackageReadHelper.GetFileFromPayloadPackage (pkg, logopath); IntPtr base64Head = IntPtr.Zero; var lpstr = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head); - if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } + //if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } if (!(lpstr != IntPtr.Zero && !string.IsNullOrEmpty (PackageReadHelper.GetStringFromPkgRead (lpstr)))) { if (lpstr != IntPtr.Zero) PackageReadHelper.GetStringAndFreeFromPkgRead (lpstr); @@ -300,7 +306,7 @@ namespace AppxPackage { pic1 = PackageReadHelper.GetFileFromPayloadPackage (pkg1, logopath); lpstr = PackageReadHelper.StreamToBase64W (pic1, null, 0, out base64Head); - if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } + //if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } } } catch (Exception) { } @@ -478,11 +484,7 @@ namespace AppxPackage pic = PackageReadHelper.GetAppxFileFromAppxPackage (m_hReader, value); IntPtr base64Head = IntPtr.Zero; IntPtr lpstr = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head); - if (base64Head != IntPtr.Zero) - { - PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); - base64Head = IntPtr.Zero; - } + //if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } return lpstr != IntPtr.Zero ? PackageReadHelper.GetStringAndFreeFromPkgRead (lpstr) : ""; @@ -508,16 +510,12 @@ namespace AppxPackage { IntPtr header = IntPtr.Zero; PackageReadHelper.GetSuitablePackageFromBundle (m_hReader, out header, out pkg); - if (header != IntPtr.Zero) PackageReadHelper.GetStringAndFreeFromPkgRead (header); + //if (header != IntPtr.Zero) PackageReadHelper.GetStringAndFreeFromPkgRead (header); header = IntPtr.Zero; pic = PackageReadHelper.GetFileFromPayloadPackage (pkg, value); IntPtr base64Head = IntPtr.Zero; IntPtr lpstr = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head); - if (base64Head != IntPtr.Zero) - { - PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); - base64Head = IntPtr.Zero; - } + //if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; } return lpstr != IntPtr.Zero ? PackageReadHelper.GetStringAndFreeFromPkgRead (lpstr) : ""; @@ -667,13 +665,14 @@ namespace AppxPackage private PRApplication ReadSingleApplication (IntPtr hKeyValues) { var app = new PRApplication (ref m_hReader, ref m_priBundle, ref m_usePri, ref m_enablePri); - uint pairCount = (uint)Marshal.ReadInt32 (hKeyValues); - int baseOffset = Marshal.SizeOf (typeof (uint)); - int pairSize = Marshal.SizeOf (typeof (PackageReadHelper.PAIR_PVOID)); - for (int j = 0; j < pairCount; j++) + int pairCount = Marshal.ReadInt32 (hKeyValues); + IntPtr arrayBase = IntPtr.Add (hKeyValues, sizeof (uint)); + for (int i = 0; i < pairCount; i++) { - IntPtr pPair = IntPtr.Add (hKeyValues, baseOffset + j * pairSize); - var pair = (PackageReadHelper.PAIR_PVOID) Marshal.PtrToStructure (pPair, typeof (PackageReadHelper.PAIR_PVOID)); + IntPtr pPairPtr = Marshal.ReadIntPtr (arrayBase, i * IntPtr.Size); + if (pPairPtr == IntPtr.Zero) continue; + PackageReadHelper.PAIR_PVOID pair = + (PackageReadHelper.PAIR_PVOID)Marshal.PtrToStructure (pPairPtr, typeof (PackageReadHelper.PAIR_PVOID)); if (pair.lpKey == IntPtr.Zero) continue; string key = Marshal.PtrToStringUni (pair.lpKey); if (string.IsNullOrEmpty (key)) continue; @@ -1064,41 +1063,11 @@ namespace AppxPackage } break; } #endregion - try - { - var resnames = new HashSet (); - using (var prop = Properties) - { - var temp = prop.Description; - if (PriFileHelper.IsMsResourcePrefix (temp)) resnames.Add (temp); - temp = prop.DisplayName; - if (PriFileHelper.IsMsResourcePrefix (temp)) resnames.Add (temp); - temp = prop.Publisher; - if (PriFileHelper.IsMsResourcePrefix (temp)) resnames.Add (temp); - resnames.Add (prop.Logo); - } - using (var apps = Applications) - { - foreach (var app in apps) - { - foreach (var pair in app) - { - foreach (var pathres in ConstData.FilePathItems) - { - if ((pathres?.Trim ()?.ToLower () ?? "") == (pair.Key?.Trim ()?.ToLower ())) - { - resnames.Add (pair.Value); - } - else if (PriFileHelper.IsMsResourcePrefix (pair.Value)) - resnames.Add (pair.Value); - } - } - } - } - m_priBundle.AddSearch (resnames); - } - catch (Exception) { } + return; } +#if DEBUG + public PriReaderBundle PriInstance => m_priBundle; +#endif public PackageType Type { get @@ -1176,6 +1145,26 @@ namespace AppxPackage Newtonsoft.Json.Formatting.Indented ); } + public void BuildJsonTextAsync (object callback) + { + if (callback == null) return; + Thread thread = new Thread (() => + { + string json = string.Empty; + try + { + json = BuildJsonText (); + } + catch + { + json = string.Empty; + } + JSHelper.CallJS (callback, json); + }); + thread.SetApartmentState (ApartmentState.MTA); + thread.IsBackground = true; + thread.Start (); + } private object BuildJsonObject () { return new diff --git a/AppxPackage/PkgReadNative.cs b/AppxPackage/PkgReadNative.cs index 2dea4de..c9ed8bc 100644 --- a/AppxPackage/PkgReadNative.cs +++ b/AppxPackage/PkgReadNative.cs @@ -401,6 +401,8 @@ namespace NativeWrappers ); [DllImport (DllName, CallingConvention = CallConv)] public static extern void PackageReaderFreeString (IntPtr p); + [DllImport (DllName, CallingConvention = CallConv, CharSet = CharSet.Unicode)] + public static extern IntPtr GetManifestPrerequistieSystemVersionName (IntPtr hReader, string lpName); } } \ No newline at end of file diff --git a/AppxPackage/PriFileNative.cs b/AppxPackage/PriFileNative.cs index 008fb74..6f25d1a 100644 --- a/AppxPackage/PriFileNative.cs +++ b/AppxPackage/PriFileNative.cs @@ -58,7 +58,7 @@ namespace AppxPackage [return: MarshalAs (UnmanagedType.Bool)] public static extern bool IsMsResourceUri ([MarshalAs (UnmanagedType.LPWStr)] string pResUri); [DllImport (DLL, CallingConvention = CallingConvention.Cdecl)] - public static extern void PriFormatFreeString (IntPtr ptr); + private static extern void PriFormatFreeString (IntPtr ptr); public static string PtrToString (IntPtr ptr) { if (ptr == IntPtr.Zero) return null; @@ -66,8 +66,6 @@ namespace AppxPackage PriFormatFreeString (ptr); // 如果 DLL 返回的内存要求 free return s; } - [DllImport (DLL, CallingConvention = CallingConvention.Cdecl)] - public static extern void FreePriString (IntPtr p); } public static class LpcwstrListHelper { diff --git a/AppxPackage/PriReader.cs b/AppxPackage/PriReader.cs index 08d512b..640b8df 100644 --- a/AppxPackage/PriReader.cs +++ b/AppxPackage/PriReader.cs @@ -1,9 +1,11 @@ -using System; + +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices.ComTypes; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace AppxPackage { @@ -75,17 +77,20 @@ namespace AppxPackage public void AddSearch (string uri) { AddSearch (new string [] { uri }); } public string Resource (string resName) { - IntPtr ret = IntPtr.Zero; - try - { - ret = PriFileHelper.GetPriResource (m_hPriFile, resName); - if (ret == IntPtr.Zero) return string.Empty; - return PriFileHelper.PtrToString (ret); - } - finally - { - if (ret != IntPtr.Zero) PriFileHelper.FreePriString (ret); - } + var task = Task.Factory.StartNew (() => { + IntPtr ret = IntPtr.Zero; + try + { + ret = PriFileHelper.GetPriResource (m_hPriFile, resName); + if (ret == IntPtr.Zero) return string.Empty; + return PriFileHelper.PtrToString (ret); + } + finally + { + //if (ret != IntPtr.Zero) PriFileHelper.FreePriString(ret); + } + }); + return task.Result; } public Dictionary Resources (IEnumerable resnames) { diff --git a/Bridge/SysInit.cs b/Bridge/SysInit.cs index 5789cec..3fde593 100644 --- a/Bridge/SysInit.cs +++ b/Bridge/SysInit.cs @@ -12,6 +12,7 @@ using System.Drawing; using Newtonsoft.Json; using AppxPackage; using ModernNotice; +using System.Threading; namespace Bridge { @@ -494,10 +495,183 @@ namespace Bridge [ClassInterface (ClassInterfaceType.AutoDual)] public class _I_Package { + private static void CallJS (object jsFunc, params object [] args) + { + if (jsFunc == null) return; + try + { + // 这里固定第一个参数为 thisArg(比如 1) + object [] realArgs = new object [args.Length + 1]; + realArgs [0] = jsFunc; // thisArg + Array.Copy (args, 0, realArgs, 1, args.Length); + + jsFunc.GetType ().InvokeMember ( + "call", + BindingFlags.InvokeMethod, + null, + jsFunc, + realArgs + ); + } + catch + { + // ignore errors in callback invocation + } + } public AppxPackage.PackageReader Reader (string packagePath) { return new AppxPackage.PackageReader (packagePath); } public _I_PackageManager Manager => new _I_PackageManager (); public AppxPackage.ManifestReader Manifest (string manifestPath) { return new AppxPackage.ManifestReader (manifestPath); } public AppxPackage.ManifestReader FromInstallLocation (string installLocation) { return Manifest (Path.Combine (installLocation, "AppxManifest.xml")); } + public void ReadFromPackageAsync (string packagePath, bool enablePri, object successCallback, object failedCallback) + { + Thread thread = new Thread (() => { + try + { + using (var reader = Reader (packagePath)) + { + if (enablePri) + { + reader.EnablePri = true; + reader.UsePri = true; + } + if (!reader.IsValid) + { + var failObj = new + { + status = false, + message = "Reader invalid", + jsontext = "" + }; + string failJson = Newtonsoft.Json.JsonConvert.SerializeObject (failObj); + if (failedCallback != null) CallJS (failedCallback, failJson); + return; + } + var obj = new + { + status = true, + message = "ok", + jsontext = reader.BuildJsonText () // 你之前写好的函数 + }; + string json = Newtonsoft.Json.JsonConvert.SerializeObject (obj); + if (successCallback != null) CallJS (successCallback, json); + } + } + catch (Exception ex) + { + var errObj = new + { + status = false, + message = ex.Message, + jsontext = "" + }; + string errJson = Newtonsoft.Json.JsonConvert.SerializeObject (errObj); + if (failedCallback != null) CallJS (failedCallback, errJson); + } + }); + thread.IsBackground = true; + thread.SetApartmentState (ApartmentState.MTA); + thread.Start (); + } + public void ReadFromManifestAsync (string manifestPath, bool enablePri, object successCallback, object failedCallback) + { + Thread thread = new Thread (() => { + try + { + using (var reader = Manifest (manifestPath)) + { + if (enablePri) + { + reader.EnablePri = true; + reader.UsePri = true; + } + if (!reader.IsValid) + { + var failObj = new + { + status = false, + message = "Reader invalid", + jsontext = "" + }; + string failJson = Newtonsoft.Json.JsonConvert.SerializeObject (failObj); + if (failedCallback != null) CallJS (failedCallback, failJson); + return; + } + var obj = new + { + status = true, + message = "ok", + jsontext = reader.BuildJsonText () // 你之前写好的函数 + }; + string json = Newtonsoft.Json.JsonConvert.SerializeObject (obj); + if (successCallback != null) CallJS (successCallback, json); + } + } + catch (Exception ex) + { + var errObj = new + { + status = false, + message = ex.Message, + jsontext = "" + }; + string errJson = Newtonsoft.Json.JsonConvert.SerializeObject (errObj); + if (failedCallback != null) CallJS (failedCallback, errJson); + } + }); + thread.IsBackground = true; + thread.SetApartmentState (ApartmentState.MTA); + thread.Start (); + } + public void ReadFromInstallLocationAsync (string installLocation, bool enablePri, object successCallback, object failedCallback) + { + Thread thread = new Thread (() => { + try + { + using (var reader = FromInstallLocation (installLocation)) + { + if (enablePri) + { + reader.EnablePri = true; + reader.UsePri = true; + } + if (!reader.IsValid) + { + var failObj = new + { + status = false, + message = "Reader invalid", + jsontext = "" + }; + string failJson = Newtonsoft.Json.JsonConvert.SerializeObject (failObj); + if (failedCallback != null) CallJS (failedCallback, failJson); + return; + } + var obj = new + { + status = true, + message = "ok", + jsontext = reader.BuildJsonText () // 你之前写好的函数 + }; + string json = Newtonsoft.Json.JsonConvert.SerializeObject (obj); + if (successCallback != null) CallJS (successCallback, json); + } + } + catch (Exception ex) + { + var errObj = new + { + status = false, + message = ex.Message, + jsontext = "" + }; + string errJson = Newtonsoft.Json.JsonConvert.SerializeObject (errObj); + if (failedCallback != null) CallJS (failedCallback, errJson); + } + }); + thread.IsBackground = true; + thread.SetApartmentState (ApartmentState.MTA); + thread.Start (); + } } [ComVisible (true)] [ClassInterface (ClassInterfaceType.AutoDual)] diff --git a/Manager/Manager.csproj b/Manager/Manager.csproj index 32c9304..98d3c77 100644 --- a/Manager/Manager.csproj +++ b/Manager/Manager.csproj @@ -93,10 +93,6 @@ {ffd3fd52-37a8-4f43-883c-de8d996cb0e0} DataUtils - - {ef4012d4-ef08-499c-b803-177739350b2d} - PriFile - {8e708d9a-6325-4aa9-b5a5-d1b5eca8eef7} PrivateInit diff --git a/Manager/ManagerShell.cs b/Manager/ManagerShell.cs index 8b3d53e..0a2aabe 100644 --- a/Manager/ManagerShell.cs +++ b/Manager/ManagerShell.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; +using System.Threading; namespace Manager { public partial class ManagerShell: WAShell.WebAppForm @@ -65,10 +66,6 @@ namespace Manager { var root = Path.GetDirectoryName (DataUtils.Utilities.GetCurrentProgramPath ()); WebUI.Navigate (Path.Combine (root, "html\\manager.html")); - var pkg = new AppxPackage.PackageReader (@"F:\新建文件夹 (2)\9E2F88E3.Twitter_1.1.13.8_x86__wgeqdkkx372wm.appx"); - pkg.EnablePri = true; - pkg.UsePri = true; - var displayName = pkg.Properties.LogoBase64; } private void ManagerShell_Resize (object sender, EventArgs e) { diff --git a/PriFileFormat/ComStreamWrapper.cs b/PriFileFormat/ComStreamWrapper.cs index 31bfaae..584adb5 100644 --- a/PriFileFormat/ComStreamWrapper.cs +++ b/PriFileFormat/ComStreamWrapper.cs @@ -8,6 +8,7 @@ namespace PriFileFormat public class ComStreamWrapper: Stream { private IStream comStream; + private object _sync = new object (); public ComStreamWrapper (IStream stream) { if (stream == null) @@ -30,20 +31,102 @@ namespace PriFileFormat { get { - System.Runtime.InteropServices.ComTypes.STATSTG stat; - comStream.Stat (out stat, 1); // STATFLAG_NONAME = 1 - return stat.cbSize; + lock (_sync) + { + System.Runtime.InteropServices.ComTypes.STATSTG stat; + comStream.Stat (out stat, 1); // STATFLAG_NONAME = 1 + return stat.cbSize; + } } } public override long Position { get { + lock (_sync) + { + IntPtr posPtr = Marshal.AllocHGlobal (sizeof (long)); + try + { + // SEEK_CUR = 1 + comStream.Seek (0, 1, posPtr); + return Marshal.ReadInt64 (posPtr); + } + finally + { + Marshal.FreeHGlobal (posPtr); + } + } + } + set + { + lock (_sync) + { + // SEEK_SET = 0 + comStream.Seek (value, 0, IntPtr.Zero); + } + } + } + public override void Flush () + { + lock (_sync) + { + comStream.Commit (0); // STGC_DEFAULT = 0 + } + } + public override int Read (byte [] buffer, int offset, int count) + { + lock (_sync) + { + if (offset != 0) + throw new NotSupportedException ("Offset != 0 not supported in this wrapper."); + + IntPtr bytesRead = Marshal.AllocHGlobal (sizeof (int)); + try + { + comStream.Read (buffer, count, bytesRead); + return Marshal.ReadInt32 (bytesRead); + } + finally + { + Marshal.FreeHGlobal (bytesRead); + } + } + } + public override void Write (byte [] buffer, int offset, int count) + { + lock (_sync) + { + if (offset != 0) + throw new NotSupportedException ("Offset != 0 not supported in this wrapper."); + + IntPtr bytesWritten = Marshal.AllocHGlobal (sizeof (int)); + try + { + comStream.Write (buffer, count, bytesWritten); + } + finally + { + Marshal.FreeHGlobal (bytesWritten); + } + } + } + public override long Seek (long offset, SeekOrigin origin) + { + lock (_sync) + { + int originInt = 0; + switch (origin) + { + case SeekOrigin.Begin: originInt = 0; break; // STREAM_SEEK_SET + case SeekOrigin.Current: originInt = 1; break; // STREAM_SEEK_CUR + case SeekOrigin.End: originInt = 2; break; // STREAM_SEEK_END + } + IntPtr posPtr = Marshal.AllocHGlobal (sizeof (long)); try { - // SEEK_CUR = 1 - comStream.Seek (0, 1, posPtr); + comStream.Seek (offset, originInt, posPtr); return Marshal.ReadInt64 (posPtr); } finally @@ -51,71 +134,13 @@ namespace PriFileFormat Marshal.FreeHGlobal (posPtr); } } - set - { - // SEEK_SET = 0 - comStream.Seek (value, 0, IntPtr.Zero); - } - } - public override void Flush () - { - comStream.Commit (0); // STGC_DEFAULT = 0 - } - public override int Read (byte [] buffer, int offset, int count) - { - if (offset != 0) - throw new NotSupportedException ("Offset != 0 not supported in this wrapper."); - - IntPtr bytesRead = Marshal.AllocHGlobal (sizeof (int)); - try - { - comStream.Read (buffer, count, bytesRead); - return Marshal.ReadInt32 (bytesRead); - } - finally - { - Marshal.FreeHGlobal (bytesRead); - } - } - public override void Write (byte [] buffer, int offset, int count) - { - if (offset != 0) - throw new NotSupportedException ("Offset != 0 not supported in this wrapper."); - - IntPtr bytesWritten = Marshal.AllocHGlobal (sizeof (int)); - try - { - comStream.Write (buffer, count, bytesWritten); - } - finally - { - Marshal.FreeHGlobal (bytesWritten); - } - } - public override long Seek (long offset, SeekOrigin origin) - { - int originInt = 0; - switch (origin) - { - case SeekOrigin.Begin: originInt = 0; break; // STREAM_SEEK_SET - case SeekOrigin.Current: originInt = 1; break; // STREAM_SEEK_CUR - case SeekOrigin.End: originInt = 2; break; // STREAM_SEEK_END - } - - IntPtr posPtr = Marshal.AllocHGlobal (sizeof (long)); - try - { - comStream.Seek (offset, originInt, posPtr); - return Marshal.ReadInt64 (posPtr); - } - finally - { - Marshal.FreeHGlobal (posPtr); - } } public override void SetLength (long value) { - comStream.SetSize (value); + lock (_sync) + { + comStream.SetSize (value); + } } ~ComStreamWrapper () { comStream = null;} } diff --git a/PriFileFormat/PriFile.cs b/PriFileFormat/PriFile.cs index c703009..8e0abf2 100644 --- a/PriFileFormat/PriFile.cs +++ b/PriFileFormat/PriFile.cs @@ -25,10 +25,11 @@ namespace PriFileFormat return priFile; } - public static PriFile Parse (System.Runtime.InteropServices.ComTypes.IStream stream) + public static PriFile Parse (System.Runtime.InteropServices.ComTypes.IStream stream, out Stream output) { ComStreamWrapper csw = new ComStreamWrapper (stream); + output = csw; PriFile priFile = new PriFile (); priFile.ParseInternal (csw, true); return priFile; diff --git a/PriFormat.zip b/PriFormat.zip new file mode 100644 index 0000000..55e272a Binary files /dev/null and b/PriFormat.zip differ diff --git a/PriFormat/ByteSpan.cs b/PriFormat/ByteSpan.cs new file mode 100644 index 0000000..1908b36 --- /dev/null +++ b/PriFormat/ByteSpan.cs @@ -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; + } + } +} diff --git a/PriFormat/DataItemSection.cs b/PriFormat/DataItemSection.cs new file mode 100644 index 0000000..c0aebc6 --- /dev/null +++ b/PriFormat/DataItemSection.cs @@ -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 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 dataItems = new List (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 (dataItems); + + return true; + } + public override void Dispose () + { + DataItems?.Clear (); + DataItems = null; + base.Dispose (); + } + } + + public class DataItemRef + { + internal SectionRef dataItemSection; + internal int itemIndex; + public SectionRef DataItemSection => dataItemSection; + public int ItemIndex => itemIndex; + internal DataItemRef (SectionRef 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); + } + } +} diff --git a/PriFormat/Datas.cs b/PriFormat/Datas.cs new file mode 100644 index 0000000..a99db89 --- /dev/null +++ b/PriFormat/Datas.cs @@ -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 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); + + /// + /// Gets system DPI as percentage (100 = 96 DPI, 125 = 120 DPI, etc.) + /// + public static int DPI + { + get { return GetDPI (); } + } + + /// + /// Gets system DPI as scale factor (1.0 = 100%, 1.25 = 125%) + /// + public static double DPIScale + { + get { return DPI * 0.01; } + } + + /// + /// Gets system DPI percentage based on 96 DPI baseline. + /// + 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; + /// + /// Converts ms-resource URI or file path to path segments. + /// + public static int KeyToPath (string key, IList 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 KeyToPath (string key) + { + List ret = new List (); + KeyToPath (key, ret); + return ret; + } + /// + /// Converts System.Uri to path segments. + /// + public static int UriToPath (Uri uri, IList 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 output) + { + var uri = new Uri (uristr); + return UriToPath (uri, output); + } + public static List UriToPath (Uri uri) + { + List ret = new List (); + UriToPath (uri, ret); + return ret; + } + public static List UriToPath (string uristr) + { + var uri = new Uri (uristr); + return UriToPath (uri); + } + /// + /// Checks whether key starts with ms-resource: + /// + public static bool IsMsResourceUri (string key) + { + if (string.IsNullOrEmpty (key)) + return false; + + return key.TrimStart ().StartsWith (MsResScheme, StringComparison.OrdinalIgnoreCase); + } + /// + /// ms-resource://... (full uri) + /// + public static bool IsFullMsResourceUri (string key) + { + if (!IsMsResourceUri (key)) + return false; + + return key.TrimStart ().StartsWith ( + MsResScheme + "//", + StringComparison.OrdinalIgnoreCase); + } + /// + /// ms-resource:foo/bar (relative uri) + /// + public static bool IsRelativeMsResourceUri (string key) + { + return IsMsResourceUri (key) && !IsFullMsResourceUri (key); + } + private static void SplitPath (string value, char sep, IList 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, IEquatable + { + private readonly List _segments; + + public bool IgnoreCase { get; } + + public PriPath (bool ignoreCase = true) + { + _segments = new List (); + IgnoreCase = ignoreCase; + } + + public PriPath (IEnumerable segments, bool ignoreCase = true) + { + _segments = new List (segments ?? Enumerable.Empty ()); + 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 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 + { + 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 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); + } + } +} diff --git a/PriFormat/DecisionInfoSection.cs b/PriFormat/DecisionInfoSection.cs new file mode 100644 index 0000000..dc1278c --- /dev/null +++ b/PriFormat/DecisionInfoSection.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace PriFormat +{ + public class DecisionInfoSection: Section + { + public IList Decisions { get; private set; } + public IList QualifierSets { get; private set; } + public IList 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 decisionInfos = new List (numDecisions); + for (int i = 0; i < numDecisions; i++) + { + ushort firstQualifierSetIndexIndex = binaryReader.ReadUInt16 (); + ushort numQualifierSetsInDecision = binaryReader.ReadUInt16 (); + decisionInfos.Add (new DecisionInfo (firstQualifierSetIndexIndex, numQualifierSetsInDecision)); + } + + List qualifierSetInfos = new List (numQualifierSets); + for (int i = 0; i < numQualifierSets; i++) + { + ushort firstQualifierIndexIndex = binaryReader.ReadUInt16 (); + ushort numQualifiersInSet = binaryReader.ReadUInt16 (); + qualifierSetInfos.Add (new QualifierSetInfo (firstQualifierIndexIndex, numQualifiersInSet)); + } + + List qualifierInfos = new List (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 distinctQualifierInfos = new List (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 qualifiers = new List (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 qualifierSets = new List (numQualifierSets); + + for (int i = 0; i < numQualifierSets; i++) + { + List qualifiersInSet = new List (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 decisions = new List (numDecisions); + + for (int i = 0; i < numDecisions; i++) + { + List qualifierSetsInDecision = new List (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 Qualifiers; + + public QualifierSet (ushort index, IList qualifiers) + { + Index = index; + Qualifiers = qualifiers; + } + } + + public struct Decision + { + public ushort Index; + public IList QualifierSets; + + public Decision (ushort index, IList qualifierSets) + { + Index = index; + QualifierSets = qualifierSets; + } + } +} diff --git a/PriFormat/HierarchicalSchemaSection.cs b/PriFormat/HierarchicalSchemaSection.cs new file mode 100644 index 0000000..307b6dc --- /dev/null +++ b/PriFormat/HierarchicalSchemaSection.cs @@ -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 Scopes { get; private set; } + public IList 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 (); + Items = new List (); + 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 scopeAndItemInfos = new List ((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 scopeExInfos = new List ((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 children = new List (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 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); + } + } +} diff --git a/PriFormat/Polyfill.cs b/PriFormat/Polyfill.cs new file mode 100644 index 0000000..458db5e --- /dev/null +++ b/PriFormat/Polyfill.cs @@ -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 bytes = new List (); + 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."); + } + + } +} diff --git a/PriFormat/PriDescriptorSection.cs b/PriFormat/PriDescriptorSection.cs new file mode 100644 index 0000000..5317db1 --- /dev/null +++ b/PriFormat/PriDescriptorSection.cs @@ -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> HierarchicalSchemaSections { get; private set; } + public IList> DecisionInfoSections { get; private set; } + public IList> ResourceMapSections { get; private set; } + public IList> ReferencedFileSections { get; private set; } + public IList> DataItemSections { get; private set; } + + public SectionRef 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 (primaryResourceMapSection); + HasPrimaryResourceMapSection = true; + } + else + { + HasPrimaryResourceMapSection = false; + } + + ushort numReferencedFileSections = binaryReader.ReadUInt16 (); + ushort numDataItemSections = binaryReader.ReadUInt16 (); + + binaryReader.ExpectUInt16 (0); + + // Hierarchical schema sections + List> hierarchicalSchemaSections = + new List> (numHierarchicalSchemaSections); + + for (int i = 0; i < numHierarchicalSchemaSections; i++) + { + hierarchicalSchemaSections.Add ( + new SectionRef (binaryReader.ReadUInt16 ())); + } + + HierarchicalSchemaSections = hierarchicalSchemaSections; + + // Decision info sections + List> decisionInfoSections = + new List> (numDecisionInfoSections); + + for (int i = 0; i < numDecisionInfoSections; i++) + { + decisionInfoSections.Add ( + new SectionRef (binaryReader.ReadUInt16 ())); + } + + DecisionInfoSections = decisionInfoSections; + + // Resource map sections + List> resourceMapSections = + new List> (numResourceMapSections); + + for (int i = 0; i < numResourceMapSections; i++) + { + resourceMapSections.Add ( + new SectionRef (binaryReader.ReadUInt16 ())); + } + + ResourceMapSections = resourceMapSections; + + // Referenced file sections + List> referencedFileSections = + new List> (numReferencedFileSections); + + for (int i = 0; i < numReferencedFileSections; i++) + { + referencedFileSections.Add ( + new SectionRef (binaryReader.ReadUInt16 ())); + } + + ReferencedFileSections = referencedFileSections; + + // Data item sections + List> dataItemSections = + new List> (numDataItemSections); + + for (int i = 0; i < numDataItemSections; i++) + { + dataItemSections.Add ( + new SectionRef (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 + } +} diff --git a/PriFormat/PriFile.cs b/PriFormat/PriFile.cs new file mode 100644 index 0000000..7404852 --- /dev/null +++ b/PriFormat/PriFile.cs @@ -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 TableOfContents { get; private set; } + public IList
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 toc = new List (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 ().Single (); + } + return _priDescriptorSection; + } + } + + public T GetSectionByRef (SectionRef 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 refSection = + PriDescriptorSection.ReferencedFileSections.First (); + ReferencedFileSection section = GetSectionByRef (refSection); + return section.ReferencedFiles [referencedFileRef.FileIndex]; + } + + public void Dispose () + { + TableOfContents?.Clear (); + TableOfContents = null; + //Sections?.Clear (); + Sections = null; + } + } +} diff --git a/PriFormat/PriFormat.csproj b/PriFormat/PriFormat.csproj new file mode 100644 index 0000000..1a80263 --- /dev/null +++ b/PriFormat/PriFormat.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {676E9BD2-A704-4539-9A88-E46654FC94B6} + Library + Properties + PriFormat + PriFormat + v4.0 + 512 + + + true + full + false + ..\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PriFormat/PriReader.cs b/PriFormat/PriReader.cs new file mode 100644 index 0000000..552bc4b --- /dev/null +++ b/PriFormat/PriReader.cs @@ -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 _tasks = + new Dictionary (); + 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 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 strdict = new Dictionary (); + Dictionary filedict = new Dictionary (); + 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 (); + 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 Resources (IEnumerable list) + { + var ret = new Dictionary (); + AddSearch (list); + foreach (var item in list) ret [item] = Resource (item); + return ret; + } + public string Path (string resname) => Resource (resname); + public Dictionary Paths (IEnumerable resnames) => Resources (resnames); + public string String (string resname) => Resource (resname); + public Dictionary Strings (IEnumerable 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 _priFiles = new List (3); + private readonly Dictionary _mapPri = new Dictionary (); + + // ----------------------------- + // 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 arr) + { + if (arr == null) + return; + + List langRes = new List (); + List scaleRes = new List (); + + 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 Resources (IEnumerable resNames) + { + if (resNames == null) + throw new ArgumentNullException ("resNames"); + + AddSearch (resNames); + + Dictionary result = new Dictionary (); + foreach (string name in resNames) + result [name] = Resource (name); + + return result; + } + + public string Path (string resName) + { + return Resource (resName); + } + public Dictionary Paths (IEnumerable resNames) + { + return Resources (resNames); + } + public string String (string resName) + { + return Resource (resName); + } + public Dictionary Strings (IEnumerable 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, IEquatable + { + 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, IEquatable, IEquatable> + { + 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 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 AllValue { get; } = new Dictionary (); + /// + /// 表示是否寻找过,如果真则不用再次寻找。 + /// + public bool IsFind { get; set; } + } + public class StringResources: BaseResources, IDictionary, IDictionary + { + private Dictionary dict = new Dictionary (); + 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 Keys => dict.Keys; + public ICollection Values => dict.Values; + ICollection IDictionary.Keys => dict.Keys.Select (k => k.LocaleName).ToList (); + public void Add (KeyValuePair item) { } + public void Add (KeyValuePair item) { } + public void Add (string key, string value) { } + public void Add (StringQualifier key, string value) { } + public void Clear () { } + public bool Contains (KeyValuePair item) + { + string value; + if (TryGetValue (item.Key, out value)) return value == item.Value; + return false; + } + public bool Contains (KeyValuePair 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 [] 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 ( + kv.Key.LocaleName, kv.Value); + } + } + public void CopyTo (KeyValuePair [] 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 ( + kv.Key, kv.Value); + } + } + public IEnumerator> GetEnumerator () => dict.GetEnumerator (); + public bool Remove (KeyValuePair item) { return false; } + public bool Remove (string key) => false; + public bool Remove (KeyValuePair 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> IEnumerable>.GetEnumerator () + { + foreach (var kv in dict) + { + yield return new KeyValuePair (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 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 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 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 _dict, bool isfind = true) + { + dict = _dict; + IsFind = isfind; + } + public StringResources (Dictionary _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 + { + private Dictionary dict = new Dictionary (); + public string this [FileQualifier key] + { + get { return dict [key]; } + set { } + } + public int Count => dict.Count; + public bool IsReadOnly => false; + public ICollection Keys => dict.Keys; + public ICollection Values => dict.Values; + public void Add (KeyValuePair item) { } + public void Add (FileQualifier key, string value) { } + public void Clear () { } + public bool Contains (KeyValuePair item) => dict.Contains (item); + public bool ContainsKey (FileQualifier key) => dict.ContainsKey (key); + public void CopyTo (KeyValuePair [] array, int arrayIndex) { } + public IEnumerator> GetEnumerator () => dict.GetEnumerator (); + public bool Remove (KeyValuePair 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 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 d, Contrast contrast = Contrast.None) => GetCoincidentValue (d, UIExt.DPI, contrast); + public override string SuitableValue => GetSuitableValue (dict); + public override Dictionary 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 _dict, bool isfind = true) + { + dict = _dict; + IsFind = isfind; + } + public FileResources (Dictionary, 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; } + } +} diff --git a/PriFormat/Properties/AssemblyInfo.cs b/PriFormat/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..937f9e9 --- /dev/null +++ b/PriFormat/Properties/AssemblyInfo.cs @@ -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")] diff --git a/PriFormat/ReferencedFileSection.cs b/PriFormat/ReferencedFileSection.cs new file mode 100644 index 0000000..0a2aee5 --- /dev/null +++ b/PriFormat/ReferencedFileSection.cs @@ -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 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 folderInfos = new List (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 fileInfos = new List (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 referencedFolders = new List (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 referencedFiles = new List (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 children = new List ( + 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 (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 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; + } + } +} diff --git a/PriFormat/ResourceMapSection.cs b/PriFormat/ResourceMapSection.cs new file mode 100644 index 0000000..c68013a --- /dev/null +++ b/PriFormat/ResourceMapSection.cs @@ -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 SchemaSection { get; private set; } + public SectionRef DecisionInfoSection { get; private set; } + public IDictionary CandidateSets { get; private set; } + + readonly bool version2; + + internal const string Identifier1 = "[mrm_res_map__]\0"; + internal const string Identifier2 = "[mrm_res_map2_]\0"; + + internal ResourceMapSection (PriFile priFile, bool version2) + : base (version2 ? Identifier2 : Identifier1, priFile) + { + this.version2 = version2; + } + + protected override bool ParseSectionContent (BinaryReader binaryReader) + { + long sectionPosition = (binaryReader.BaseStream as SubStream) != null ? + ((SubStream)binaryReader.BaseStream).Position : 0; + + ushort environmentReferencesLength = binaryReader.ReadUInt16 (); + ushort numEnvironmentReferences = binaryReader.ReadUInt16 (); + + if (!version2) + { + if (environmentReferencesLength == 0 || numEnvironmentReferences == 0) + throw new InvalidDataException (); + } + else + { + if (environmentReferencesLength != 0 || numEnvironmentReferences != 0) + throw new InvalidDataException (); + } + + SchemaSection = new SectionRef (binaryReader.ReadUInt16 ()); + ushort hierarchicalSchemaReferenceLength = binaryReader.ReadUInt16 (); + DecisionInfoSection = new SectionRef (binaryReader.ReadUInt16 ()); + ushort resourceValueTypeTableSize = binaryReader.ReadUInt16 (); + ushort ItemToItemInfoGroupCount = binaryReader.ReadUInt16 (); + ushort itemInfoGroupCount = binaryReader.ReadUInt16 (); + uint itemInfoCount = binaryReader.ReadUInt32 (); + uint numCandidates = binaryReader.ReadUInt32 (); + uint dataLength = binaryReader.ReadUInt32 (); + uint largeTableLength = binaryReader.ReadUInt32 (); + + if (PriFile.GetSectionByRef (DecisionInfoSection) == null) + return false; + + byte [] environmentReferencesData = binaryReader.ReadBytes (environmentReferencesLength); + byte [] schemaReferenceData = binaryReader.ReadBytes (hierarchicalSchemaReferenceLength); + + if (schemaReferenceData.Length != 0) + { + using (BinaryReader r = new BinaryReader (new MemoryStream (schemaReferenceData, false))) + { + ushort majorVersion = r.ReadUInt16 (); + ushort minorVersion = r.ReadUInt16 (); + r.ExpectUInt32 (0); + uint checksum = r.ReadUInt32 (); + uint numScopes = r.ReadUInt32 (); + uint numItems = r.ReadUInt32 (); + + HierarchicalSchemaVersionInfo versionInfo = new HierarchicalSchemaVersionInfo ( + majorVersion, minorVersion, checksum, numScopes, numItems); + + ushort stringDataLength = r.ReadUInt16 (); + r.ExpectUInt16 (0); + uint unknown1 = r.ReadUInt32 (); + uint unknown2 = r.ReadUInt32 (); + string uniqueName = r.ReadNullTerminatedString (Encoding.Unicode); + + if (uniqueName.Length != stringDataLength - 1) + throw new InvalidDataException (); + + HierarchicalSchemaReference = new HierarchicalSchemaReference (versionInfo, unknown1, unknown2, uniqueName); + } + } + + List resourceValueTypeTable = new List (resourceValueTypeTableSize); + for (int i = 0; i < resourceValueTypeTableSize; i++) + { + binaryReader.ExpectUInt32 (4); + ResourceValueType resourceValueType = (ResourceValueType)binaryReader.ReadUInt32 (); + resourceValueTypeTable.Add (resourceValueType); + } + + List itemToItemInfoGroups = new List (ItemToItemInfoGroupCount); + for (int i = 0; i < ItemToItemInfoGroupCount; i++) + { + ushort firstItem = binaryReader.ReadUInt16 (); + ushort itemInfoGroup = binaryReader.ReadUInt16 (); + itemToItemInfoGroups.Add (new ItemToItemInfoGroup (firstItem, itemInfoGroup)); + } + + List itemInfoGroups = new List (itemInfoGroupCount); + for (int i = 0; i < itemInfoGroupCount; i++) + { + ushort groupSize = binaryReader.ReadUInt16 (); + ushort firstItemInfo = binaryReader.ReadUInt16 (); + itemInfoGroups.Add (new ItemInfoGroup (groupSize, firstItemInfo)); + } + + List itemInfos = new List ((int)itemInfoCount); + for (int i = 0; i < itemInfoCount; i++) + { + ushort decision = binaryReader.ReadUInt16 (); + ushort firstCandidate = binaryReader.ReadUInt16 (); + itemInfos.Add (new ItemInfo (decision, firstCandidate)); + } + + byte [] largeTable = binaryReader.ReadBytes ((int)largeTableLength); + + if (largeTable.Length != 0) + { + using (BinaryReader r = new BinaryReader (new MemoryStream (largeTable, false))) + { + uint ItemToItemInfoGroupCountLarge = r.ReadUInt32 (); + uint itemInfoGroupCountLarge = r.ReadUInt32 (); + uint itemInfoCountLarge = r.ReadUInt32 (); + + for (int i = 0; i < ItemToItemInfoGroupCountLarge; i++) + { + uint firstItem = r.ReadUInt32 (); + uint itemInfoGroup = r.ReadUInt32 (); + itemToItemInfoGroups.Add (new ItemToItemInfoGroup (firstItem, itemInfoGroup)); + } + + for (int i = 0; i < itemInfoGroupCountLarge; i++) + { + uint groupSize = r.ReadUInt32 (); + uint firstItemInfo = r.ReadUInt32 (); + itemInfoGroups.Add (new ItemInfoGroup (groupSize, firstItemInfo)); + } + + for (int i = 0; i < itemInfoCountLarge; i++) + { + uint decision = r.ReadUInt32 (); + uint firstCandidate = r.ReadUInt32 (); + itemInfos.Add (new ItemInfo (decision, firstCandidate)); + } + + if (r.BaseStream.Position != r.BaseStream.Length) + throw new InvalidDataException (); + } + } + + List candidateInfos = new List ((int)numCandidates); + for (int i = 0; i < numCandidates; i++) + { + byte type = binaryReader.ReadByte (); + + if (type == 0x01) + { + ResourceValueType resourceValueType = resourceValueTypeTable [binaryReader.ReadByte ()]; + ushort sourceFileIndex = binaryReader.ReadUInt16 (); + ushort valueLocation = binaryReader.ReadUInt16 (); + ushort dataItemSection = binaryReader.ReadUInt16 (); + candidateInfos.Add (new CandidateInfo (resourceValueType, sourceFileIndex, valueLocation, dataItemSection)); + } + else if (type == 0x00) + { + ResourceValueType resourceValueType = resourceValueTypeTable [binaryReader.ReadByte ()]; + ushort length = binaryReader.ReadUInt16 (); + uint stringOffset = binaryReader.ReadUInt32 (); + candidateInfos.Add (new CandidateInfo (resourceValueType, length, stringOffset)); + } + else + { + throw new InvalidDataException (); + } + } + + long stringDataStartOffset = binaryReader.BaseStream.Position; + + Dictionary candidateSets = new Dictionary (); + + for (int itemToItemInfoGroupIndex = 0; itemToItemInfoGroupIndex < itemToItemInfoGroups.Count; itemToItemInfoGroupIndex++) + { + ItemToItemInfoGroup itemToItemInfoGroup = itemToItemInfoGroups [itemToItemInfoGroupIndex]; + + ItemInfoGroup itemInfoGroup; + + if (itemToItemInfoGroup.ItemInfoGroup < itemInfoGroups.Count) + itemInfoGroup = itemInfoGroups [(int)itemToItemInfoGroup.ItemInfoGroup]; + else + itemInfoGroup = new ItemInfoGroup (1, (uint)(itemToItemInfoGroup.ItemInfoGroup - itemInfoGroups.Count)); + + for (uint itemInfoIndex = itemInfoGroup.FirstItemInfo; itemInfoIndex < itemInfoGroup.FirstItemInfo + itemInfoGroup.GroupSize; itemInfoIndex++) + { + ItemInfo itemInfo = itemInfos [(int)itemInfoIndex]; + + ushort decisionIndex = (ushort)itemInfo.Decision; + + Decision decision = PriFile.GetSectionByRef (DecisionInfoSection).Decisions [decisionIndex]; + + List candidates = new List (decision.QualifierSets.Count); + + for (int i = 0; i < decision.QualifierSets.Count; i++) + { + CandidateInfo candidateInfo = candidateInfos [(int)itemInfo.FirstCandidate + i]; + + if (candidateInfo.Type == 0x01) + { + ReferencedFileRef sourceFile = null; + + if (candidateInfo.SourceFileIndex != 0) + sourceFile = new ReferencedFileRef (candidateInfo.SourceFileIndex - 1); + + candidates.Add (new Candidate (decision.QualifierSets [i].Index, candidateInfo.ResourceValueType, sourceFile, + new DataItemRef (new SectionRef (candidateInfo.DataItemSection), candidateInfo.DataItemIndex))); + } + else if (candidateInfo.Type == 0x00) + { + ByteSpan data = new ByteSpan (sectionPosition + stringDataStartOffset + candidateInfo.DataOffset, candidateInfo.DataLength); + + candidates.Add (new Candidate (decision.QualifierSets [i].Index, candidateInfo.ResourceValueType, data)); + } + } + + ushort resourceMapItemIndex = (ushort)(itemToItemInfoGroup.FirstItem + (itemInfoIndex - itemInfoGroup.FirstItemInfo)); + + CandidateSet candidateSet = new CandidateSet ( + new ResourceMapItemRef (SchemaSection, resourceMapItemIndex), + decisionIndex, + candidates); + + candidateSets.Add (resourceMapItemIndex, candidateSet); + } + } + + CandidateSets = candidateSets; + + return true; + } + + private struct ItemToItemInfoGroup + { + public uint FirstItem; + public uint ItemInfoGroup; + + public ItemToItemInfoGroup (uint firstItem, uint itemInfoGroup) + { + FirstItem = firstItem; + ItemInfoGroup = itemInfoGroup; + } + } + + private struct ItemInfoGroup + { + public uint GroupSize; + public uint FirstItemInfo; + + public ItemInfoGroup (uint groupSize, uint firstItemInfo) + { + GroupSize = groupSize; + FirstItemInfo = firstItemInfo; + } + } + + private struct ItemInfo + { + public uint Decision; + public uint FirstCandidate; + + public ItemInfo (uint decision, uint firstCandidate) + { + Decision = decision; + FirstCandidate = firstCandidate; + } + } + + private struct CandidateInfo + { + public byte Type; + public ResourceValueType ResourceValueType; + + // Type 1 + public ushort SourceFileIndex; + public ushort DataItemIndex; + public ushort DataItemSection; + + // Type 0 + public ushort DataLength; + public uint DataOffset; + + public CandidateInfo (ResourceValueType resourceValueType, ushort sourceFileIndex, ushort dataItemIndex, ushort dataItemSection) + { + Type = 0x01; + ResourceValueType = resourceValueType; + SourceFileIndex = sourceFileIndex; + DataItemIndex = dataItemIndex; + DataItemSection = dataItemSection; + DataLength = 0; + DataOffset = 0; + } + + public CandidateInfo (ResourceValueType resourceValueType, ushort dataLength, uint dataOffset) + { + Type = 0x00; + ResourceValueType = resourceValueType; + SourceFileIndex = 0; + DataItemIndex = 0; + DataItemSection = 0; + DataLength = dataLength; + DataOffset = dataOffset; + } + } + public override void Dispose () + { + HierarchicalSchemaReference = null; + CandidateSets?.Clear (); + CandidateSets = null; + base.Dispose (); + } + ~ResourceMapSection () { Dispose (); } + } + + public enum ResourceValueType + { + String, + Path, + EmbeddedData, + AsciiString, + Utf8String, + AsciiPath, + Utf8Path + } + + public class CandidateSet: IDisposable + { + public ResourceMapItemRef ResourceMapItem { get; private set; } + public ushort DecisionIndex { get; private set; } + public IList Candidates { get; private set; } + + internal CandidateSet (ResourceMapItemRef resourceMapItem, ushort decisionIndex, IList candidates) + { + ResourceMapItem = resourceMapItem; + DecisionIndex = decisionIndex; + Candidates = candidates; + } + + public virtual void Dispose () + { + Candidates?.Clear (); + Candidates = null; + } + ~CandidateSet () { Dispose (); } + } + + public class Candidate + { + public ushort QualifierSet { get; private set; } + public ResourceValueType Type { get; private set; } + public ReferencedFileRef SourceFile { get; private set; } + public DataItemRef DataItem { get; private set; } + public ByteSpan Data { get; private set; } + + internal Candidate (ushort qualifierSet, ResourceValueType type, ReferencedFileRef sourceFile, DataItemRef dataItem) + { + QualifierSet = qualifierSet; + Type = type; + SourceFile = sourceFile; + DataItem = dataItem; + } + + internal Candidate (ushort qualifierSet, ResourceValueType type, ByteSpan data) + { + QualifierSet = qualifierSet; + Type = type; + SourceFile = null; + DataItem = null; + Data = data; + } + } + + public class HierarchicalSchemaReference + { + public HierarchicalSchemaVersionInfo VersionInfo { get; private set; } + public uint Unknown1 { get; private set; } + public uint Unknown2 { get; private set; } + public string UniqueName { get; private set; } + + internal HierarchicalSchemaReference (HierarchicalSchemaVersionInfo versionInfo, uint unknown1, uint unknown2, string uniqueName) + { + VersionInfo = versionInfo; + Unknown1 = unknown1; + Unknown2 = unknown2; + UniqueName = uniqueName; + } + } + + public struct ResourceMapItemRef + { + internal SectionRef schemaSection; + internal int itemIndex; + public SectionRef SchemaSection => schemaSection; + public int ItemIndex => itemIndex; + internal ResourceMapItemRef (SectionRef schemaSection, int itemIndex) + { + this.schemaSection = schemaSection; + this.itemIndex = itemIndex; + } + } +} diff --git a/PriFormat/ReverseMapSection.cs b/PriFormat/ReverseMapSection.cs new file mode 100644 index 0000000..b1dee79 --- /dev/null +++ b/PriFormat/ReverseMapSection.cs @@ -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 Scopes { get; private set; } + public IList 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 scopeAndItemInfos = new List ((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 scopeExInfos = new List ((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 children = new List (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 (scopes); + Items = new ReadOnlyCollection (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 (); } + } +} diff --git a/PriFormat/Section.cs b/PriFormat/Section.cs new file mode 100644 index 0000000..dd143d2 --- /dev/null +++ b/PriFormat/Section.cs @@ -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; + } + } +} diff --git a/PriFormat/StreamHelper.cs b/PriFormat/StreamHelper.cs new file mode 100644 index 0000000..1aab2bb --- /dev/null +++ b/PriFormat/StreamHelper.cs @@ -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); + } + + /// + /// 一次性把 IStream 全部复制到托管内存 + /// + 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"); + } + } +} diff --git a/PriFormat/SubStream.cs b/PriFormat/SubStream.cs new file mode 100644 index 0000000..8c66561 --- /dev/null +++ b/PriFormat/SubStream.cs @@ -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 (); + } + } +} diff --git a/PriFormat/TocEntry.cs b/PriFormat/TocEntry.cs new file mode 100644 index 0000000..0043864 --- /dev/null +++ b/PriFormat/TocEntry.cs @@ -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; + } + } +} diff --git a/PriFormat/UnknownSection.cs b/PriFormat/UnknownSection.cs new file mode 100644 index 0000000..f688cfe --- /dev/null +++ b/PriFormat/UnknownSection.cs @@ -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 (); + } + } +} diff --git a/WAShell/WebAppForm.Designer.cs b/WAShell/WebAppForm.Designer.cs index 5bbc8e8..de67679 100644 --- a/WAShell/WebAppForm.Designer.cs +++ b/WAShell/WebAppForm.Designer.cs @@ -40,6 +40,7 @@ this.webui.Size = new System.Drawing.Size(661, 416); this.webui.TabIndex = 0; this.webui.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webui_DocumentCompleted); + this.webui.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.webui_PreviewKeyDown); // // WebAppForm // diff --git a/WAShell/WebAppForm.cs b/WAShell/WebAppForm.cs index b474de4..c05af70 100644 --- a/WAShell/WebAppForm.cs +++ b/WAShell/WebAppForm.cs @@ -97,5 +97,9 @@ namespace WAShell { webui.ObjectForScripting = null; } + private void webui_PreviewKeyDown (object sender, PreviewKeyDownEventArgs e) + { + if (e.KeyCode == Keys.F5) e.IsInputKey = true; + } } } diff --git a/dlltest/dlltest.vcxproj b/dlltest/dlltest.vcxproj index 2b864fd..3f5fa53 100644 --- a/dlltest/dlltest.vcxproj +++ b/dlltest/dlltest.vcxproj @@ -87,7 +87,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true @@ -118,7 +118,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true diff --git a/dlltest/main.cpp b/dlltest/main.cpp index 6c97413..c1dd1af 100644 --- a/dlltest/main.cpp +++ b/dlltest/main.cpp @@ -162,6 +162,160 @@ void read_package (const std::wstring &filepath) } } } +void read_manifest (const std::wstring &filepath) +{ + manifest_reader pr (filepath); + pr.enable_pri_convert (true); + pr.use_pri (true); + std::wcout << L"Is Valid: " << (pr.valid () ? L"true" : L"false") << std::endl; + std::wcout << L"Package Type: "; + switch (pr.package_type ()) + { + case PKGTYPE_APPX: std::wcout << L"Appx"; break; + case PKGTYPE_BUNDLE: std::wcout << L"AppxBundle"; break; + } + std::wcout << std::endl; + std::wcout << L"Package Role: "; + switch (pr.package_role ()) + { + case PKGROLE_APPLICATION: std::wcout << L"Application"; break; + case PKGROLE_FRAMEWORK: std::wcout << L"Framework"; break; + case PKGROLE_RESOURCE: std::wcout << L"Resource"; break; + case PKGROLE_UNKNOWN: std::wcout << L"Unknown"; break; + } + std::wcout << std::endl; + auto id = pr.get_identity (); + std::wcout << L"Identity" << std::endl; + std::wcout << L"\tName: " << id.name () << std::endl; + std::wcout << L"\tPublisher: " << id.publisher () << std::endl; + auto ver = id.version (); + std::wcout << L"\tVersion: " << ver.major << L"." << ver.minor << L"." << ver.build << L"." << ver.revision << std::endl; + std::wcout << L"\tPackage Family Name: " << id.package_family_name () << std::endl; + std::wcout << L"\tPackage Full Name: " << id.package_full_name () << std::endl; + std::wcout << L"\tResource Id: " << id.resource_id () << std::endl; + std::wcout << L"\tArchitecture: "; + DWORD archi = id.architecture (); + for (size_t i = 0xF; i > 0; i /= 2) + { + if (archi & i) + { + switch (i) + { + case PKG_ARCHITECTURE_ARM: std::wcout << "Arm"; break; + case PKG_ARCHITECTURE_ARM64: std::wcout << "Arm64"; break; + case PKG_ARCHITECTURE_NEUTRAL: std::wcout << "Neutral"; break; + case PKG_ARCHITECTURE_X64: std::wcout << "x64"; break; + case PKG_ARCHITECTURE_X86: std::wcout << "x86"; break; + default: continue; + } + std::wcout << L", "; + } + } + std::wcout << std::endl; + ver = id.version (true); + std::wcout << L"\tVersion (Appx): " << ver.major << L"." << ver.minor << L"." << ver.build << L"." << ver.revision << std::endl; + std::wcout << L"Properties" << std::endl; + auto prop = pr.get_properties (); + std::wcout << L"\tDisplay Name: " << prop.display_name () << std::endl; + std::wcout << L"\tDescription: " << prop.description () << std::endl; + std::wcout << L"\tPublisher Display Name: " << prop.publisher_display_name () << std::endl; + std::wcout << L"\tLogo: " << prop.logo () << std::endl; + std::wcout << L"\tLogo Base64: " << prop.logo_base64 () << std::endl; + std::wcout << L"\tFramework: " << (prop.framework () ? L"true" : L"false") << std::endl; + std::wcout << L"\tResource Package: " << (prop.resource_package () ? L"true" : L"false") << std::endl; + auto preq = pr.get_prerequisites (); + std::wcout << L"Prerequisites" << std::endl; + ver = preq.os_min_version (); + std::wcout << L"\tOS Min Version: " << ver.major << L"." << ver.minor << L"." << ver.build << L"." << ver.revision << std::endl; + ver = preq.os_max_version_tested (); + std::wcout << L"\tOS Max Version Tested: " << ver.major << L"." << ver.minor << L"." << ver.build << L"." << ver.revision << std::endl; + std::wcout << L"Resources" << std::endl; + auto res = pr.get_resources (); + { + std::vector langs; + std::vector scales; + std::vector dx_levels; + res.languages (langs); + res.scales (scales); + res.dx_feature_level (dx_levels); + std::wcout << L"\tSupported Languages: "; + for (auto &it : langs) + { + std::wcout << it << L", "; + } + std::wcout << std::endl; + std::wcout << L"\tSupported Scales: "; + for (auto &it : scales) + { + std::wcout << it << L", "; + } + std::wcout << std::endl; + std::wcout << L"\tSupported Languages: "; + for (auto &it : dx_levels) + { + switch (it) + { + case PKG_RESOURCES_DXFEATURE_LEVEL9: std::wcout << L"9, "; break; + case PKG_RESOURCES_DXFEATURE_LEVEL10: std::wcout << L"10, "; break; + case PKG_RESOURCES_DXFEATURE_LEVEL11: std::wcout << L"11, "; break; + case PKG_RESOURCES_DXFEATURE_LEVEL12: std::wcout << L"12, "; break; + } + } + std::wcout << std::endl; + } + std::wcout << L"Capabilities" << std::endl; + auto cap = pr.get_capabilities (); + { + std::vector caps; + std::vector devicecaps; + cap.capabilities_name (caps); + cap.device_capabilities (devicecaps); + std::wcout << L"\tCapabilities: "; + for (auto &it : caps) + { + std::wcout << it << L", "; + } + std::wcout << std::endl; + std::wcout << L"\tDevice Capabilities: "; + for (auto &it : devicecaps) + { + std::wcout << it << L", "; + } + std::wcout << std::endl; + } + std::wcout << L"Applications" << std::endl; + auto apps = pr.get_applications (); + { + std::vector appmaps; + apps.get (appmaps); + size_t cnt = 1; + for (auto &it : appmaps) + { + std::wcout << L"\tApplication" << cnt ++ << std::endl; + for (auto &it_s : it) + { + std::wcout << L"\t\t" << it_s.first << L": " << it.newat (it_s.first) << std::endl; + std::wstring base64 = it.newat_base64 (it_s.first); + if (!base64.empty ()) std::wcout << L"\t\t" << it_s.first << L" (Base64): " << base64 << std::endl; + } + } + } + auto deps = pr.get_dependencies (); + std::wcout << L"Dependencies" << std::endl; + { + std::vector deparr; + deps.get (deparr); + size_t cnt = 1; + for (auto &it : deparr) + { + std::wcout << L"\tDependency" << cnt ++ << std::endl; + std::wcout << L"\t\tName: " << it.name << std::endl; + std::wcout << L"\t\tPublisher: " << it.publisher << std::endl; + ver = it.vermin; + std::wcout << L"\t\tMin Version: " << ver.major << L"." << ver.minor << L"." << ver.build << L"." << ver.revision << std::endl; + } + } +} using cbfunc = std::function ; void ProgressCallback (DWORD dwProgress, void *pCustom) { @@ -197,13 +351,15 @@ int main (int argc, char *argv []) std::wstring pkgPathStr = L"E:\\Profiles\\Bruce\\Desktop\\Discourse.appx"; //pkgPathStr = L"F:\\BaiduNetdiskDownload\\Collection4\\Microsoft.BingFinance_2015.709.2014.2069_neutral_~_8wekyb3d8bbwe\\FinanceApp_3.0.4.336_x86.appx"; //pkgPathStr = L"F:\\BaiduNetdiskDownload\\Collection4\\Microsoft.BingFinance_2015.709.2014.2069_neutral_~_8wekyb3d8bbwe.appxbundle"; - pkgPathStr = L"E:\\Profiles\\Bruce\\Desktop\\½ļ\\Microsoft.MSIXPackagingTool_2023.1212.538.0_neutral_~_8wekyb3d8bbwe.Msixbundle"; + //pkgPathStr = L"E:\\Profiles\\Bruce\\Desktop\\½ļ\\Microsoft.MSIXPackagingTool_2023.1212.538.0_neutral_~_8wekyb3d8bbwe.Msixbundle"; + pkgPathStr = L"C:\\Program Files\\WindowsApps\\Microsoft.3DBuilder_20.0.4.0_x64__8wekyb3d8bbwe\\AppxManifest.xml"; if (pkgPathStr.empty ()) std::getline (std::wcin, pkgPathStr); pkgPathStr.erase ( std::remove (pkgPathStr.begin (), pkgPathStr.end (), L'\"'), pkgPathStr.end () ); - read_package (pkgPathStr); + //read_package (pkgPathStr); + read_manifest (pkgPathStr); system ("pause"); return 0; } \ No newline at end of file diff --git a/pkgread/pkgread.cpp b/pkgread/pkgread.cpp index db21d1d..91d6a85 100644 --- a/pkgread/pkgread.cpp +++ b/pkgread/pkgread.cpp @@ -1754,3 +1754,44 @@ BOOL GetManifestPrerequisite (_In_ HPKGMANIFESTREAD hReader, _In_ LPCWSTR lpName } return FALSE; } +LPWSTR GetManifestPrerequistieSystemVersionName (_In_ HPKGMANIFESTREAD hReader, _In_ LPCWSTR lpName) +{ + auto ptr = ToPtrManifest (hReader); + if (!ptr) return nullptr; + switch (ptr->type ()) + { + case PackageType::single: { + auto read = ptr->appx_reader (); + auto pre = read.prerequisites (); + auto ver = pre.get_version (lpName ? lpName : L""); + auto str = GetPrerequistOSVersionDescription (ver); + return _wcsdup (str.c_str ()); + } break; + default: + break; + } + return nullptr; +} +BOOL PackageReaderGetFileRoot (LPWSTR lpFilePath) { return PathRemoveFileSpecW (lpFilePath); } +LPWSTR PackageReaderCombinePath (LPCWSTR lpLeft, LPCWSTR lpRight, LPWSTR lpBuf) { return PathCombineW (lpBuf, lpLeft, lpRight); } +HANDLE PackageReaderGetFileStream (LPCWSTR lpFilePath) +{ + IStream *ptr = nullptr; + HRESULT hr = SHCreateStreamOnFileEx (lpFilePath, STGM_READ | STGM_SHARE_DENY_NONE, 0, FALSE, NULL, &ptr); + if (SUCCEEDED (hr)) return ptr; + else + { + if (ptr) ptr->Release (); + ptr = nullptr; + } + return nullptr; +} +void PackageReaderDestroyFileStream (HANDLE hStream) +{ + auto ptr = (IStream *)hStream; + if (ptr) + { + ptr->Release (); + return; + } +} \ No newline at end of file diff --git a/pkgread/pkgread.h b/pkgread/pkgread.h index 42c62a3..ddd4b06 100644 --- a/pkgread/pkgread.h +++ b/pkgread/pkgread.h @@ -435,6 +435,16 @@ extern "C" // TRUE ʾڸǰ PKGREAD_API BOOL GetManifestPrerequisite (_In_ HPKGMANIFESTREAD hReader, _In_ LPCWSTR lpName, _Outptr_ VERSION *pVerRet); + PKGREAD_API LPWSTR GetManifestPrerequistieSystemVersionName (_In_ HPKGMANIFESTREAD hReader, _In_ LPCWSTR lpName); + + PKGREAD_API BOOL PackageReaderGetFileRoot (_In_ LPWSTR lpFilePath); + + PKGREAD_API LPWSTR PackageReaderCombinePath (_In_ LPCWSTR lpLeft, _In_ LPCWSTR lpRight, _Outptr_ LPWSTR lpBuf); + + PKGREAD_API HANDLE PackageReaderGetFileStream (LPCWSTR lpFilePath); + + PKGREAD_API void PackageReaderDestroyFileStream (HANDLE hStream); + #ifdef _DEFAULT_INIT_VALUE_ #undef _DEFAULT_INIT_VALUE_ #endif @@ -589,6 +599,8 @@ class package_reader std::sort (prifilestreams.begin (), prifilestreams.end ()); auto last = std::unique (prifilestreams.begin (), prifilestreams.end ()); prifilestreams.erase (last, prifilestreams.end ()); + bool lastvalue = resswitch; + resswitch = false; try { std::vector resnames; @@ -619,6 +631,7 @@ class package_reader pribundlereader.add_search (resnames); } catch (const std::exception &e) {} + resswitch = lastvalue; #endif } typedef struct deconstr @@ -1301,6 +1314,702 @@ class package_reader #endif } }; +class manifest_reader +{ + private: + HPKGMANIFESTREAD hReader = nullptr; + std::wstring filepath = L""; + bool usepri = false; + bool resswitch = false; +#ifdef _PRI_READER_CLI_HEADER_ + prifile prireader; +#endif + std::wstring get_fileroot () + { + std::vector root (filepath.capacity ()); + wcscpy (root.data (), filepath.c_str ()); + PackageReaderGetFileRoot (root.data ()); + return root.data (); + } + static std::wstring path_combine (const std::wstring &l, const std::wstring &r) + { + std::vector buffer (l.length () + r.length () + 10); + PackageReaderCombinePath (l.c_str (), r.c_str (), buffer.data ()); + return buffer.data (); + } + void initpri () + { + #ifdef _PRI_READER_CLI_HEADER_ + prireader.destroy (); + switch (this->package_type ()) + { + case PKGTYPE_APPX: { + prireader.create (path_combine (get_fileroot (), L"resources.pri")); + } break; + } + bool lastvalue = resswitch; + resswitch = false; + try + { + std::vector resnames; + { + auto prop = get_properties (); + std::wstring temp = prop.description (); + if (IsMsResourcePrefix (temp.c_str ())) resnames.push_back (temp); + temp = prop.display_name (); + if (IsMsResourcePrefix (temp.c_str ())) resnames.push_back (temp); + temp = prop.publisher_display_name (); + if (IsMsResourcePrefix (temp.c_str ())) resnames.push_back (temp); + resnames.push_back (prop.logo ()); + } + { + auto app = get_applications (); + std::vector apps; + app.get (apps); + for (auto &it_map : apps) + { + for (auto &it_item : it_map) + { + if (std::find (g_filepathitems.begin (), g_filepathitems.end (), it_item.first) != g_filepathitems.end () && !it_item.second.empty ()) + resnames.push_back (it_item.second); + else if (IsMsResourcePrefix (it_item.second.c_str ())) resnames.push_back (it_item.second); + } + } + } + prireader.add_search (resnames); + } + catch (const std::exception &e) {} + resswitch = lastvalue; + #endif + } + typedef struct deconstr + { + std::function endtask = nullptr; + deconstr (std::function pf): endtask (pf) {} + ~deconstr () { if (endtask) endtask (); } + } destruct; + public: + class base_subitems + { + protected: + HPKGMANIFESTREAD &hReader; + public: + base_subitems (HPKGMANIFESTREAD &hReader): hReader (hReader) {} + }; + class identity: public base_subitems + { + using base = base_subitems; + public: + using base::base; + std::wstring string_value (DWORD dwName) const + { + LPWSTR lpstr = nullptr; + deconstr rel ([&lpstr] () { + if (lpstr) free (lpstr); + lpstr = nullptr; + }); + lpstr = GetManifestIdentityStringValue (hReader, dwName); + return lpstr ? lpstr : L""; + } + std::wstring name () const { return string_value (PKG_IDENTITY_NAME); } + std::wstring publisher () const { return string_value (PKG_IDENTITY_PUBLISHER); } + std::wstring package_family_name () const { return string_value (PKG_IDENTITY_PACKAGEFAMILYNAME); } + std::wstring package_full_name () const { return string_value (PKG_IDENTITY_PACKAGEFULLNAME); } + std::wstring resource_id () const { return string_value (PKG_IDENTITY_RESOURCEID); } + VERSION version (bool read_subpkg_ver = false) const { VERSION ver; GetManifestIdentityVersion (hReader, &ver); return ver; } + DWORD architecture () const { DWORD dw = 0; GetManifestIdentityArchitecture (hReader, &dw); return dw; } + }; + class properties: public base_subitems + { + using base = base_subitems; + std::wstring root = L""; + public: + using base::base; + std::wstring string_value (const std::wstring &swName) const + { + LPWSTR lpstr = nullptr; + deconstr rel ([&lpstr] () { + if (lpstr) free (lpstr); + lpstr = nullptr; + }); + lpstr = GetManifestPropertiesStringValue (hReader, swName.c_str ()); + return lpstr ? lpstr : L""; + } + bool bool_value (const std::wstring &swName, bool bRetWhenFailed = false) const + { + BOOL ret = FALSE; + HRESULT hr = GetManifestPropertiesBoolValue (hReader, swName.c_str (), &ret); + if (FAILED (hr)) return bRetWhenFailed; + else return ret != FALSE; + } + std::wstring display_name (bool toprires = true) + { + std::wstring ret = string_value (PKG_PROPERTIES_DISPLAYNAME); + if (!toprires) return ret; + if (!enable_pri ()) return ret; + #ifdef _PRI_READER_CLI_HEADER_ + else + { + if (!IsMsResourcePrefix (ret.c_str ())) return ret; + std::wstring privalue = pri_get_res (ret); + if (privalue.empty ()) return ret; + return privalue; + } + #endif + return ret; + } + std::wstring publisher_display_name (bool toprires = true) + { + std::wstring ret = string_value (PKG_PROPERTIES_PUBLISHER); + if (!toprires) return ret; + if (!enable_pri ()) return ret; + #ifdef _PRI_READER_CLI_HEADER_ + else + { + if (!IsMsResourcePrefix (ret.c_str ())) return ret; + std::wstring privalue = pri_get_res (ret); + if (privalue.empty ()) return ret; + return privalue; + } + #endif + return ret; + } + std::wstring description (bool toprires = true) + { + std::wstring ret = string_value (PKG_PROPERTIES_DESCRIPTION); + if (!toprires) return ret; + if (!enable_pri ()) return ret; + #ifdef _PRI_READER_CLI_HEADER_ + else + { + if (!IsMsResourcePrefix (ret.c_str ())) return ret; + std::wstring privalue = pri_get_res (ret); + if (privalue.empty ()) return ret; + return privalue; + } + #endif + return ret; + } + std::wstring logo (bool toprires = true) + { + std::wstring ret = string_value (PKG_PROPERTIES_LOGO); + if (!toprires) return ret; + if (!enable_pri ()) return ret; + #ifdef _PRI_READER_CLI_HEADER_ + else + { + std::wstring privalue = pri_get_res (ret); + if (privalue.empty ()) return ret; + return privalue; + } + #endif + return ret; + } + std::wstring logo_base64 () + { + switch (GetManifestType (hReader)) + { + case PKGTYPE_APPX: { + auto path = path_combine (root, logo ()); + HANDLE pic = PackageReaderGetFileStream (path.c_str ()); + destruct relp ([&pic] () { + if (pic) PackageReaderDestroyFileStream (pic); + pic = nullptr; + }); + LPWSTR lpstr = nullptr; + destruct rel ([&lpstr] () { + if (lpstr) free (lpstr); + lpstr = nullptr; + }); + lpstr = StreamToBase64W (pic, nullptr, 0, nullptr); + return lpstr ? lpstr : L""; + } break; + } + return L""; + } + bool framework () const { return bool_value (PKG_PROPERTIES_FRAMEWORD); } + bool resource_package () const { return bool_value (PKG_PROPERTIES_IS_RESOURCE); } + #ifdef _PRI_READER_CLI_HEADER_ + prifile *pbreader = nullptr; + bool *usepri = nullptr; + bool *resconvert = nullptr; + #endif + bool enable_pri () const + { + #ifdef _PRI_READER_CLI_HEADER_ + if (!pbreader) return false; + if (!usepri || !*usepri) return false; + if (!resconvert) return false; + return *resconvert; + #else + return false; + #endif + } + std::wstring pri_get_res (const std::wstring &resname) + { + #ifdef _PRI_READER_CLI_HEADER_ + if (resname.empty ()) return L""; + if (!pbreader) return L""; + return pbreader->resource (resname); + #else + return L""; + #endif + } + #ifdef _PRI_READER_CLI_HEADER_ + properties (HPKGMANIFESTREAD &hReader, prifile *pri, bool *up, bool *resc, const std::wstring &pathroot): + base (hReader), pbreader (pri), usepri (up), resconvert (resc), root (pathroot) {} + #endif + }; + class application: public std::map + { + using base = std::map ; + std::wstring root = L""; + public: + using base::base; + application () = default; + std::wstring user_model_id () { return this->at (L"AppUserModelID"); } + friend bool operator == (application &a1, application &a2) { return !_wcsicmp (a1.user_model_id ().c_str (), a2.user_model_id ().c_str ()); } + friend bool operator != (application &a1, application &a2) { return _wcsicmp (a1.user_model_id ().c_str (), a2.user_model_id ().c_str ()); } + explicit operator bool () { return this->user_model_id ().empty (); } + std::wstring &operator [] (const std::wstring &key) + { + auto it = this->find (key); + if (it == this->end ()) + { + it = this->insert (std::make_pair (key, L"")).first; + } + if (!enable_pri ()) return it->second; + #ifdef _PRI_READER_CLI_HEADER_ + if (IsMsResourcePrefix (it->second.c_str ())) + { + std::wstring privalue = pri_get_res (it->second); + if (!privalue.empty ()) return privalue; + return it->second; + } + else if (std::find (g_filepathitems.begin (), g_filepathitems.end (), it->first) != g_filepathitems.end () && !it->second.empty ()) + { + std::wstring privalue = pri_get_res (it->second); + if (!privalue.empty ()) return privalue; + return it->second; + } + #endif + return it->second; + } + typename base::iterator find_case_insensitive (const std::wstring &key) + { + for (auto it = this->begin (); it != this->end (); ++it) + { + if (_wcsicmp (it->first.c_str (), key.c_str ()) == 0) + return it; + } + return this->end (); + } + typename base::const_iterator find_case_insensitive (const std::wstring &key) const + { + for (auto it = this->begin (); it != this->end (); ++ it) + { + if (_wcsicmp (it->first.c_str (), key.c_str ()) == 0) + return it; + } + return this->end (); + } + std::wstring at (const std::wstring &key) + { + auto it = this->find_case_insensitive (key); + if (it == this->end ()) throw std::out_of_range ("application::at: key not found"); + if (!enable_pri ()) return it->second; + #ifdef _PRI_READER_CLI_HEADER_ + if (IsMsResourcePrefix (it->second.c_str ())) + { + std::wstring privalue = pri_get_res (it->second); + if (!privalue.empty ()) return privalue; + } + #endif + return it->second; + } + std::wstring newat (const std::wstring &key, bool to_pri_string = true) + { + auto it = this->find (key); + if (it == this->end ()) + { + it = this->insert (std::make_pair (key, L"")).first; + } + if (!enable_pri () && to_pri_string) return it->second; + #ifdef _PRI_READER_CLI_HEADER_ + if (IsMsResourcePrefix (it->second.c_str ())) + { + std::wstring privalue = pri_get_res (it->second); + if (!privalue.empty ()) return privalue; + return it->second; + } + else if (std::find (g_filepathitems.begin (), g_filepathitems.end (), it->first) != g_filepathitems.end () && !it->second.empty ()) + { + std::wstring privalue = pri_get_res (it->second); + if (!privalue.empty ()) return privalue; + return it->second; + } + #endif + return it->second; + } + // ֧ļ + std::wstring newat_base64 (const std::wstring &key) + { + #ifdef _PRI_READER_CLI_HEADER_ + std::wstring value = newat (key); + if (std::find (g_filepathitems.begin (), g_filepathitems.end (), key) != g_filepathitems.end () && !value.empty ()) + { + switch (GetManifestType (hReader)) + { + case PKGTYPE_APPX: { + auto filepath = path_combine (root, value); + HANDLE pic = PackageReaderGetFileStream (filepath.c_str ()); + destruct relp ([&pic] () { + if (pic) PackageReaderDestroyFileStream (pic); + pic = nullptr; + }); + LPWSTR lpstr = nullptr; + destruct rel ([&lpstr] () { + if (lpstr) free (lpstr); + lpstr = nullptr; + }); + lpstr = StreamToBase64W (pic, nullptr, 0, nullptr); + return lpstr ? lpstr : L""; + } break; + } + return L""; + } + else return L""; + #else + return L""; + #endif + } + #ifdef _PRI_READER_CLI_HEADER_ + HPKGMANIFESTREAD hReader = nullptr; + prifile *pbreader = nullptr; + bool *usepri = nullptr; + bool *resconvert = nullptr; + #endif + bool enable_pri () const + { + #ifdef _PRI_READER_CLI_HEADER_ + if (!pbreader) return false; + if (!usepri || !*usepri) return false; + if (!resconvert) return false; + return *resconvert; + #else + return false; + #endif + } + std::wstring pri_get_res (const std::wstring &resname) + { + #ifdef _PRI_READER_CLI_HEADER_ + if (resname.empty ()) return L""; + if (!pbreader) return L""; + return pbreader->resource (resname); + #else + return L""; + #endif + } + #ifdef _PRI_READER_CLI_HEADER_ + application (HPKGMANIFESTREAD hReader, prifile *pri, bool *up, bool *resc, const std::wstring &pathroot): + hReader (hReader), pbreader (pri), usepri (up), resconvert (resc), root (pathroot) {} + #endif + }; + class applications + { + private: + HPKGMANIFESTREAD &hReader; + HAPPENUMERATOR hList = nullptr; + std::wstring root = L""; + public: + applications (HPKGMANIFESTREAD &hReader): hReader (hReader) + { + hList = GetManifestApplications (hReader); + } + ~applications () + { + if (hList) DestroyPackageApplications (hList); + hList = nullptr; + } + size_t get (std::vector &apps) + { + apps.clear (); + if (!hList) return 0; + HLIST_PVOID hMapList = ApplicationsToMap (hList); + deconstr endt ([&hMapList] { + if (hMapList) DestroyApplicationsMap (hMapList); + hMapList = nullptr; + }); + if (!hMapList) return 0; + for (size_t i = 0; i < hMapList->dwSize; i ++) + { + HLIST_PVOID &hKeyValues = ((HLIST_PVOID *)hMapList->alpVoid) [i]; + #ifdef _PRI_READER_CLI_HEADER_ + application app (hReader, pbreader, usepri, resconvert, root); + #else + application app; + #endif + for (size_t j = 0; j < hKeyValues->dwSize; j ++) + { + HPAIR_PVOID &hPair = ((HPAIR_PVOID *)hKeyValues->alpVoid) [j]; + LPWSTR lpKey = (LPWSTR)hPair->lpKey; + LPWSTR lpValue = (LPWSTR)hPair->lpValue; + if (!lpKey || !*lpKey) continue; + app [lpKey] = lpValue ? lpValue : L""; + } + apps.push_back (app); + } + return apps.size (); + } + #ifdef _PRI_READER_CLI_HEADER_ + prifile *pbreader = nullptr; + bool *usepri = nullptr; + bool *resconvert = nullptr; + applications (HPKGMANIFESTREAD &hReader, prifile *pri, bool *up, bool *resc, const std::wstring &pathroot): + hReader (hReader), pbreader (pri), usepri (up), resconvert (resc), root (pathroot) { hList = GetManifestApplications (hReader); } + #endif + }; + class capabilities: public base_subitems + { + using base = base_subitems; + public: + using base::base; + size_t capabilities_name (std::vector &output) const + { + output.clear (); + HLIST_PVOID hList = GetManifestCapabilitiesList (hReader); + deconstr endt ([&hList] () { + if (hList) DestroyCapabilitiesList (hList); + hList = nullptr; + }); + if (!hList) return 0; + for (size_t i = 0; i < hList->dwSize; i ++) + { + LPWSTR lpstr = (LPWSTR)hList->alpVoid [i]; + if (!lpstr) continue; + output.push_back (lpstr); + } + return output.size (); + } + size_t device_capabilities (std::vector &output) const + { + output.clear (); + HLIST_PVOID hList = GetManifestDeviceCapabilitiesList (hReader); + deconstr endt ([&hList] () { + if (hList) DestroyDeviceCapabilitiesList (hList); + hList = nullptr; + }); + if (!hList) return 0; + for (size_t i = 0; i < hList->dwSize; i ++) + { + LPWSTR lpstr = (LPWSTR)hList->alpVoid [i]; + if (!lpstr) continue; + output.push_back (lpstr); + } + return output.size (); + } + }; + struct dependency + { + std::wstring name, publisher; + VERSION vermin; + dependency (const std::wstring &name = L"", const std::wstring &pub = L"", const VERSION &ver = VERSION ()): + name (name), publisher (pub), vermin (ver) {} + }; + class dependencies: public base_subitems + { + using base = base_subitems; + public: + using base::base; + size_t get (std::vector &output) const + { + auto hList = GetManifestDependencesInfoList (hReader); + deconstr rel ([&hList] () { + DestroyDependencesInfoList (hList); + }); + if (!hList) return 0; + for (size_t i = 0; i < hList->dwSize; i ++) + { + DEPENDENCY_INFO &depinf = ((DEPENDENCY_INFO *)hList->aDepInfo) [i]; + if (!depinf.lpName || !*depinf.lpName) continue; + output.push_back (dependency (depinf.lpName, depinf.lpPublisher ? depinf.lpPublisher : L"", depinf.verMin)); + } + return output.size (); + } + }; + class resources: public base_subitems + { + using base = base_subitems; + public: + using base::base; + size_t languages (std::vector &langs) const + { + langs.clear (); + auto hList = GetManifestResourcesLanguages (hReader); + deconstr rel ([&hList] () { + if (hList) DestroyResourcesLanguagesList (hList); + hList = nullptr; + }); + if (!hList) return 0; + for (size_t i = 0; i < hList->dwSize; i ++) + { + LPWSTR lpstr = ((LPWSTR *)hList->alpVoid) [i]; + if (lpstr && *lpstr) langs.push_back (std::wstring (lpstr)); + } + return langs.size (); + } + size_t languages (std::vector &langs) const + { + langs.clear (); + auto hList = GetManifestResourcesLanguagesToLcid (hReader); + deconstr rel ([&hList] () { + if (hList) DestroyResourcesLanguagesLcidList (hList); + hList = nullptr; + }); + if (!hList) return 0; + for (size_t i = 0; i < hList->dwSize; i ++) + { + if (hList->aLcid [i]) langs.push_back (hList->aLcid [i]); + } + return langs.size (); + } + size_t scales (std::vector &output) const + { + output.clear (); + auto hList = GetManifestResourcesScales (hReader); + deconstr rel ([&hList] () { + if (hList) DestroyResourcesScalesList (hList); + }); + if (!hList) return 0; + for (size_t i = 0; i < hList->dwSize; i ++) + { + UINT32 s = hList->aUI32 [i]; + if (s) output.push_back (s); + } + return output.size (); + } + DWORD dx_feature_level () const { return GetManifestResourcesDxFeatureLevels (hReader); } + // ӵ PKG_RESOURCES_DXFEATURE_* ǰ׺ij + size_t dx_feature_level (std::vector &ret) + { + DWORD dx = dx_feature_level (); + if (dx & PKG_RESOURCES_DXFEATURE_LEVEL9) ret.push_back (PKG_RESOURCES_DXFEATURE_LEVEL9); + else if (dx & PKG_RESOURCES_DXFEATURE_LEVEL10) ret.push_back (PKG_RESOURCES_DXFEATURE_LEVEL10); + else if (dx & PKG_RESOURCES_DXFEATURE_LEVEL11) ret.push_back (PKG_RESOURCES_DXFEATURE_LEVEL11); + else if (dx & PKG_RESOURCES_DXFEATURE_LEVEL12) ret.push_back (PKG_RESOURCES_DXFEATURE_LEVEL12); + return ret.size (); + } + }; + class prerequisites: public base_subitems + { + using base = base_subitems; + public: + using base::base; + VERSION get_version (const std::wstring &name) const + { + VERSION ver; + GetManifestPrerequisite (hReader, name.c_str (), &ver); + return ver; + } + VERSION os_min_version () const { return get_version (PKG_PREREQUISITE_OS_MIN_VERSION); } + VERSION os_max_version_tested () const { return get_version (PKG_PREREQUISITE_OS_MAX_VERSION_TESTED); } + std::wstring get_description (const std::wstring &name) const + { + LPWSTR lpstr = GetManifestPrerequistieSystemVersionName (hReader, name.c_str ()); + deconstr relt ([&lpstr] () { + if (lpstr) free (lpstr); + lpstr = nullptr; + }); + return lpstr ? lpstr : L""; + } + std::wstring os_min_version_description () const { return get_description (PKG_PREREQUISITE_OS_MIN_VERSION); } + std::wstring os_max_version_tested_description () const { return get_description (PKG_PREREQUISITE_OS_MAX_VERSION_TESTED); } + }; + manifest_reader (): hReader (CreateManifestReader ()) {} + manifest_reader (const std::wstring &fpath): hReader (CreateManifestReader ()) + { + file (fpath); + } + ~manifest_reader () + { + DestroyManifestReader (hReader); + hReader = nullptr; + #ifdef _PRI_READER_CLI_HEADER_ + prireader.destroy (); + #endif + } + std::wstring file () const { return filepath; } + bool file (const std::wstring &path) + { + return LoadManifestFromFile (hReader, (filepath = path).c_str ()); + } + // PKGTYPE_* ǰ׺ + WORD package_type () const { return GetManifestType (hReader); } + bool valid () const { return hReader && IsManifestValid (hReader); } + // PKGROLE_* ǰ׺ + WORD package_role () const { return GetManifestRole (hReader); } + identity get_identity () { return identity (hReader); } + properties get_properties () + { + return properties (hReader + #ifdef _PRI_READER_CLI_HEADER_ + , + &prireader, + &usepri, + &resswitch, + get_fileroot () + #endif + ); + } + applications get_applications () + { + return applications (hReader + #ifdef _PRI_READER_CLI_HEADER_ + , + &prireader, + &usepri, + &resswitch, + get_fileroot () + #endif + ); + } + capabilities get_capabilities () { return capabilities (hReader); } + dependencies get_dependencies () { return dependencies (hReader); } + resources get_resources () { return resources (hReader); } + prerequisites get_prerequisites () { return prerequisites (hReader); } + // Ƿʹ PRI + bool use_pri () const + { + #ifdef _PRI_READER_CLI_HEADER_ + return usepri; + #else + return false; + #endif + } + // Ƿʹ PRI + bool use_pri (bool value) + { + #ifdef _PRI_READER_CLI_HEADER_ + bool laststatus = usepri; + usepri = value; + if (laststatus ^ usepri) initpri (); + return usepri; + #else + return usepri = false; + #endif + } + // ǷԶ PRI ȡԴ + bool enable_pri_convert () const { return resswitch; } + // ǷԶ PRI ȡԴ + bool enable_pri_convert (bool value) + { + #ifdef _PRI_READER_CLI_HEADER_ + return resswitch = value; + #else + return resswitch = false; + #endif + } +}; #endif #endif diff --git a/priformatcli/priformatcli.cpp b/priformatcli/priformatcli.cpp index 6407810..00f410b 100644 --- a/priformatcli/priformatcli.cpp +++ b/priformatcli/priformatcli.cpp @@ -48,16 +48,11 @@ ref class PriFileInst PriFile ^inst = nullptr; OpenType opentype = OpenType::Unknown; IStream *isptr = nullptr; - System::IO::FileStream ^fsptr = nullptr; + System::IO::Stream ^fsptr = nullptr; operator PriFile ^ () { return inst; } operator IStream * () { return isptr; } - operator System::IO::FileStream ^ () { return fsptr; } + operator System::IO::Stream ^ () { return fsptr; } explicit operator bool () { return inst && (int)opentype && ((bool)isptr ^ (fsptr != nullptr)); } - operator System::IO::Stream ^ () - { - if (isptr) return gcnew ComStreamWrapper (ComIStreamToCliIStream (isptr)); - else return fsptr; - } size_t Seek (int64_t offset, System::IO::SeekOrigin origin) { if (isptr) @@ -317,11 +312,13 @@ PCSPRIFILE CreatePriFileInstanceFromStream (PCOISTREAM pStream) { HRESULT hr = S_OK; if (pStream) hr = ((IStream *)pStream)->Seek (LARGE_INTEGER {}, STREAM_SEEK_SET, nullptr); - auto pri = PriFile::Parse (ComIStreamToCliIStream (reinterpret_cast (pStream))); + System::IO::Stream ^stream = nullptr; + auto pri = PriFile::Parse (ComIStreamToCliIStream (reinterpret_cast (pStream)), stream); PriFileInst ^inst = gcnew PriFileInst (); inst->inst = pri; inst->opentype = OpenType::IStream; inst->isptr = reinterpret_cast (pStream); + inst->fsptr = stream; auto handle = System::Runtime::InteropServices::GCHandle::Alloc (inst); IntPtr token = System::Runtime::InteropServices::GCHandle::ToIntPtr (handle); return reinterpret_cast (token.ToPointer ()); diff --git a/priread (never using)/ReadMe.txt b/priread (never using)/ReadMe.txt new file mode 100644 index 0000000..98d5062 --- /dev/null +++ b/priread (never using)/ReadMe.txt @@ -0,0 +1,30 @@ +======================================================================== + 动态链接库:priread 项目概述 +======================================================================== + +应用程序向导已为您创建了此 priread DLL。 + +本文件概要介绍组成 priread 应用程序的每个文件的内容。 + + +priread.vcxproj + 这是使用应用程序向导生成的 VC++ 项目的主项目文件,其中包含生成该文件的 Visual C++ 的版本信息,以及有关使用应用程序向导选择的平台、配置和项目功能的信息。 + +priread.vcxproj.filters + 这是使用“应用程序向导”生成的 VC++ 项目筛选器文件。它包含有关项目文件与筛选器之间的关联信息。在 IDE 中,通过这种关联,在特定节点下以分组形式显示具有相似扩展名的文件。例如,“.cpp”文件与“源文件”筛选器关联。 + +priread.cpp + 这是主 DLL 源文件。 + +///////////////////////////////////////////////////////////////////////////// +其他标准文件: + +StdAfx.h, StdAfx.cpp + 这些文件用于生成名为 priread.pch 的预编译头 (PCH) 文件和名为 StdAfx.obj 的预编译类型文件。 + +///////////////////////////////////////////////////////////////////////////// +其他注释: + +应用程序向导使用“TODO:”注释来指示应添加或自定义的源代码部分。 + +///////////////////////////////////////////////////////////////////////////// diff --git a/priread (never using)/dllmain.cpp b/priread (never using)/dllmain.cpp new file mode 100644 index 0000000..260abc6 --- /dev/null +++ b/priread (never using)/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : DLL Ӧóڵ㡣 +#include "stdafx.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/priread (never using)/localeex.h b/priread (never using)/localeex.h new file mode 100644 index 0000000..b528d56 --- /dev/null +++ b/priread (never using)/localeex.h @@ -0,0 +1,163 @@ +#pragma once +#include +#include +static std::wstring StringToWString (const std::string &str, UINT codePage = CP_ACP) +{ + if (str.empty ()) return std::wstring (); + int len = MultiByteToWideChar (codePage, 0, str.c_str (), -1, nullptr, 0); + if (len == 0) return std::wstring (); + std::wstring wstr (len - 1, L'\0'); + MultiByteToWideChar (codePage, 0, str.c_str (), -1, &wstr [0], len); + return wstr; +} + +#undef GetLocaleInfo +std::string GetLocaleInfoA (LCID code, LCTYPE type) +{ + char buf [LOCALE_NAME_MAX_LENGTH] = {0}; + GetLocaleInfoA (code, type, buf, LOCALE_NAME_MAX_LENGTH); + return buf; +} +std::wstring GetLocaleInfoW (LCID code, LCTYPE type) +{ + WCHAR buf [LOCALE_NAME_MAX_LENGTH] = {0}; + GetLocaleInfoW (code, type, buf, LOCALE_NAME_MAX_LENGTH); + return buf; +} +void GetLocaleInfo (LCID code, LCTYPE type, std::wstring &output) +{ + output = GetLocaleInfoW (code, type); +} +void GetLocaleInfo (LCID code, LCTYPE type, std::string &output) +{ + output = GetLocaleInfoA (code, type); +} +int GetLocaleInfoEx (std::wstring lpLocaleName, LCTYPE type, std::wstring &output) +{ + WCHAR buf [LOCALE_NAME_MAX_LENGTH] = {0}; + int res = GetLocaleInfoEx (lpLocaleName.c_str (), type, buf, LOCALE_NAME_MAX_LENGTH); + if (&output) output = std::wstring (buf); + return res; +} + +#undef SetLocaleInfo +BOOL SetLocaleInfoA (LCID code, LCTYPE type, const std::string &lcData) +{ + return SetLocaleInfoA (code, type, lcData.c_str ()); +} +BOOL SetLocaleInfoW (LCID code, LCTYPE type, const std::wstring &lcData) +{ + return SetLocaleInfoW (code, type, lcData.c_str ()); +} +BOOL SetLocaleInfo (LCID code, LCTYPE type, const std::wstring &lcData) +{ + return SetLocaleInfoW (code, type, lcData); +} +BOOL SetLocaleInfo (LCID code, LCTYPE type, const std::string &lcData) +{ + return SetLocaleInfoA (code, type, lcData); +} + +std::string GetLocaleRestrictedCodeFromLcidA (LCID lcid) +{ + return GetLocaleInfoA (lcid, 89); +} +std::wstring GetLocaleRestrictedCodeFromLcidW (LCID lcid) +{ + return GetLocaleInfoW (lcid, 89); +} +void GetLocaleRestrictedCodeFromLcid (LCID lcid, std::string &ret) +{ + ret = GetLocaleRestrictedCodeFromLcidA (lcid); +} +void GetLocaleRestrictedCodeFromLcid (LCID lcid, std::wstring &ret) +{ + ret = GetLocaleRestrictedCodeFromLcidW (lcid); +} + +std::string GetLocaleElaboratedCodeFromLcidA (LCID lcid) +{ + return GetLocaleInfoA (lcid, 90); +} +std::wstring GetLocaleElaboratedCodeFromLcidW (LCID lcid) +{ + return GetLocaleInfoW (lcid, 90); +} +void GetLocaleElaboratedCodeFromLcid (LCID lcid, std::wstring &ret) +{ + ret = GetLocaleElaboratedCodeFromLcidW (lcid); +} +void GetLocaleElaboratedCodeFromLcid (LCID lcid, std::string &ret) +{ + ret = GetLocaleElaboratedCodeFromLcidA (lcid); +} + +LCID LocaleCodeToLcidW (LPCWSTR localeCode) +{ + BYTE buf [LOCALE_NAME_MAX_LENGTH * sizeof (WCHAR)] = {0}; + int res = GetLocaleInfoEx (localeCode, LOCALE_RETURN_NUMBER | LOCALE_ILANGUAGE, (LPWSTR)buf, LOCALE_NAME_MAX_LENGTH); + LCID lcid = *((LCID *)buf); + return lcid; +} +LCID LocaleCodeToLcidA (LPCSTR localeCode) +{ + std::wstring lcWide = StringToWString (std::string (localeCode)); + return LocaleCodeToLcidW (lcWide.c_str ()); +} +LCID LocaleCodeToLcid (const std::wstring &loccode) +{ + return LocaleCodeToLcidW (loccode.c_str ()); +} +LCID LocaleCodeToLcid (const std::string &loccode) +{ + return LocaleCodeToLcidA (loccode.c_str ()); +} + +std::string GetLocaleRestrictedCodeA (LPCSTR lc) +{ + return GetLocaleInfoA (LocaleCodeToLcidA (lc), 89); +} +std::string GetLocaleRestrictedCodeA (const std::string &lc) +{ + return GetLocaleInfoA (LocaleCodeToLcidA (lc.c_str ()), 89); +} +std::wstring GetLocaleRestrictedCodeW (LPCWSTR lc) +{ + return GetLocaleInfoW (LocaleCodeToLcidW (lc), 89); +} +std::wstring GetLocaleRestrictedCodeW (const std::wstring &lc) +{ + return GetLocaleInfoW (LocaleCodeToLcidW (lc.c_str ()), 89); +} +std::wstring GetLocaleRestrictedCode (const std::wstring &lc) { return GetLocaleRestrictedCodeW (lc); } +std::string GetLocaleRestrictedCode (const std::string &lc) { return GetLocaleRestrictedCodeA (lc); } + +std::string GetLocaleElaboratedCodeA (LPCSTR lc) +{ + return GetLocaleInfoA (LocaleCodeToLcidA (lc), 90); +} +std::string GetLocaleElaboratedCodeA (const std::string &lc) +{ + return GetLocaleInfoA (LocaleCodeToLcidA (lc.c_str ()), 90); +} +std::wstring GetLocaleElaboratedCodeW (LPCWSTR lc) +{ + return GetLocaleInfoW (LocaleCodeToLcidW (lc), 90); +} +std::wstring GetLocaleElaboratedCodeW (const std::wstring &lc) +{ + return GetLocaleInfoW (LocaleCodeToLcidW (lc.c_str ()), 90); +} +std::wstring GetLocaleElaboratedCode (const std::wstring &lc) { return GetLocaleElaboratedCodeW (lc); } +std::string GetLocaleElaboratedCode (const std::string &lc) { return GetLocaleElaboratedCodeA (lc); } + +std::string LcidToLocaleCodeA (LCID lcid, char divide = '-') +{ + return GetLocaleRestrictedCodeFromLcidA (lcid) + divide + GetLocaleElaboratedCodeFromLcidA (lcid); +} +std::wstring LcidToLocaleCodeW (LCID lcid, WCHAR divide = L'-') +{ + return GetLocaleRestrictedCodeFromLcidW (lcid) + divide + GetLocaleElaboratedCodeFromLcidW (lcid); +} +std::wstring LcidToLocaleCode (LCID lcid, WCHAR divide = L'-') { return LcidToLocaleCodeW (lcid, divide); } +std::string LcidToLocaleCode (LCID lcid, char divide = '-') { return LcidToLocaleCodeA (lcid, divide); } \ No newline at end of file diff --git a/priread (never using)/nstring.h b/priread (never using)/nstring.h new file mode 100644 index 0000000..ba0e231 --- /dev/null +++ b/priread (never using)/nstring.h @@ -0,0 +1,456 @@ +#pragma once +#include +#include +#include +namespace l0km +{ + template , typename AL = std::allocator > inline std::basic_string toupper (const std::basic_string &src) + { + std::basic_string dst = src; + static const std::locale loc; + const std::ctype &ctype = std::use_facet > (loc); + for (typename std::basic_string ::size_type i = 0; i < src.size (); ++ i) + { + dst [i] = ctype.toupper (src [i]); + } + return dst; + } + template , typename AL = std::allocator > inline std::basic_string tolower (const std::basic_string &src) + { + std::basic_string dst = src; + static const std::locale loc; + const std::ctype &ctype = std::use_facet > (loc); + for (typename std::basic_string ::size_type i = 0; i < src.size (); ++ i) + { + dst [i] = ctype.tolower (src [i]); + } + return dst; + } + inline char toupper (char ch) + { + if (ch < -1) return ch; + static const std::locale loc; + return std::use_facet > (loc).toupper (ch); + } + inline char tolower (char ch) + { + if (ch < -1) return ch; + static const std::locale loc; + return std::use_facet > (loc).tolower (ch); + } + inline wchar_t toupper (wchar_t ch) + { + if (ch < -1) return ch; + static const std::locale loc; + return std::use_facet > (loc).toupper (ch); + } + inline wchar_t tolower (wchar_t ch) + { + if (ch < -1) return ch; + static const std::locale loc; + return std::use_facet > (loc).tolower (ch); + } + inline int toupper (int ch) + { + if (ch < -1) return ch; + static const std::locale loc; + return std::use_facet > (loc).toupper (ch); + } + inline int tolower (int ch) + { + if (ch < -1) return ch; + static const std::locale loc; + return std::use_facet > (loc).tolower (ch); + } +} +template bool is_blank (ct &ch) +{ + return ch == ct (' ') || ch == ct ('\t') || ch == ct ('\n'); +} +template , typename AL = std::allocator > std::basic_string NormalizeString (const std::basic_string &str, bool upper = false, bool includemidblank = false) +{ + typedef std::basic_string string_type; + string_type result; + if (str.empty ()) return result; + auto begin_it = str.begin (); + auto end_it = str.end (); + while (begin_it != end_it && is_blank (*begin_it)) ++begin_it; + while (end_it != begin_it && is_blank (*(end_it - 1))) --end_it; + bool in_space = false; + for (auto it = begin_it; it != end_it; ++ it) + { + if (is_blank (*it)) + { + if (includemidblank) + { + if (!in_space) + { + result.push_back (E (' ')); + in_space = true; + } + } + else + { + result.push_back (*it); + in_space = true; + } + } + else + { + result.push_back (*it); + in_space = false; + } + } + if (upper) return l0km::toupper (result); + else return l0km::tolower (result); +} +template , typename AL = std::allocator > bool IsNormalizeStringEquals (const std::basic_string &l, const std::basic_string &r, bool includemidblank = false) +{ + auto _local_strlen = [] (const E *p) -> size_t { + size_t cnt = 0; + while (*(p + cnt)) { cnt ++; } + return cnt; + }; + const E *pl = l.c_str (); + const E *pr = r.c_str (); + while (*pl && is_blank (*pl)) ++ pl; + while (*pr && is_blank (*pr)) ++ pr; + const E *el = l.c_str () + _local_strlen (l.c_str ()); + const E *er = r.c_str () + _local_strlen (r.c_str ()); + while (el > pl && is_blank (*(el - 1))) --el; + while (er > pr && is_blank (*(er - 1))) --er; + while (pl < el && pr < er) + { + if (includemidblank) + { + if (is_blank (*pl) && is_blank (*pr)) + { + while (pl < el && is_blank (*pl)) ++pl; + while (pr < er && is_blank (*pr)) ++pr; + continue; + } + else if (is_blank (*pl)) + { + while (pl < el && is_blank (*pl)) ++pl; + continue; + } + else if (is_blank (*pr)) + { + while (pr < er && is_blank (*pr)) ++pr; + continue; + } + } + if (l0km::tolower (*pl) != l0km::tolower (*pr)) return false; + ++ pl; + ++ pr; + } + while (pl < el && is_blank (*pl)) ++ pl; + while (pr < er && is_blank (*pr)) ++ pr; + return pl == el && pr == er; +} +template , typename AL = std::allocator > int64_t NormalizeStringCompare (const std::basic_string &l, const std::basic_string &r, bool includemidblank = false) +{ + auto _local_strlen = [] (const E *p) -> size_t { + size_t cnt = 0; + while (*(p + cnt)) { cnt ++; } + return cnt; + }; + const E *pl = l.c_str (); + const E *pr = r.c_str (); + while (*pl && is_blank (*pl)) ++ pl; + while (*pr && is_blank (*pr)) ++ pr; + const E *el = l.c_str () + _local_strlen (l.c_str ()); + const E *er = r.c_str () + _local_strlen (r.c_str ()); + while (el > pl && is_blank (*(el - 1))) -- el; + while (er > pr && is_blank (*(er - 1))) -- er; + while (pl < el && pr < er) + { + if (includemidblank) + { + if (is_blank (*pl) && is_blank (*pr)) + { + while (pl < el && is_blank (*pl)) ++pl; + while (pr < er && is_blank (*pr)) ++pr; + continue; + } + else if (is_blank (*pl)) + { + while (pl < el && is_blank (*pl)) ++pl; + continue; + } + else if (is_blank (*pr)) + { + while (pr < er && is_blank (*pr)) ++pr; + continue; + } + } + E chl = l0km::tolower (*pl); + E chr = l0km::tolower (*pr); + if (chl != chr) return (int64_t)chl - (int64_t)chr; + ++ pl; + ++ pr; + } + while (pl < el && is_blank (*pl)) ++ pl; + while (pr < er && is_blank (*pr)) ++ pr; + if (pl == el && pr == er) return 0; + if (pl == el) return -1; + if (pr == er) return 1; + return (int64_t)l0km::tolower (*pl) - (int64_t)l0km::tolower (*pr); +} +template bool IsNormalizeStringEquals (const CharT *l, const CharT *r, bool includemidblank = false) +{ + if (!l || !r) return l == r; + auto skip_blank = [] (const CharT *&p) + { + while (*p && is_blank (*p)) ++ p; + }; + const CharT *p1 = l; + const CharT *p2 = r; + skip_blank (p1); + skip_blank (p2); + while (*p1 && *p2) + { + CharT ch1 = l0km::tolower (*p1); + CharT ch2 = l0km::tolower (*p2); + if (ch1 != ch2) return false; + ++ p1; + ++ p2; + if (includemidblank) + { + if (is_blank (*p1) || is_blank (*p2)) + { + skip_blank (p1); + skip_blank (p2); + } + } + } + skip_blank (p1); + skip_blank (p2); + return *p1 == 0 && *p2 == 0; +} +template int64_t NormalizeStringCompare (const CharT *l, const CharT *r, bool includemidblank = false) +{ + if (!l || !r) return l ? 1 : (r ? -1 : 0); + auto skip_blank = [] (const CharT *&p) + { + while (*p && is_blank (*p)) ++ p; + }; + const CharT *p1 = l; + const CharT *p2 = r; + skip_blank (p1); + skip_blank (p2); + while (*p1 && *p2) + { + CharT ch1 = l0km::tolower (*p1); + CharT ch2 = l0km::tolower (*p2); + if (ch1 != ch2) return (ch1 < ch2) ? -1 : 1; + ++ p1; + ++ p2; + if (includemidblank) + { + if (is_blank (*p1) || is_blank (*p2)) + { + skip_blank (p1); + skip_blank (p2); + } + } + } + skip_blank (p1); + skip_blank (p2); + if (*p1 == 0 && *p2 == 0) return 0; + if (*p1 == 0) return -1; + return 1; +} +template , typename AL = std::allocator > bool IsNormalizeStringEmpty (const std::basic_string &str) +{ + return IsNormalizeStringEquals (str, std::basic_string ()); +} +template , typename AL = std::allocator > std::basic_string StringTrim (const std::basic_string &str, bool includemidblank = false) +{ + typedef std::basic_string string_type; + typedef typename string_type::size_type size_type; + if (str.empty ()) return string_type (); + size_type first = 0; + size_type last = str.size (); + while (first < last && is_blank (str [first])) ++first; + while (last > first && is_blank (str [last - 1])) --last; + if (first == last) return string_type (); + string_type result; + result.reserve (last - first); + bool in_space = false; + for (size_type i = first; i < last; ++ i) + { + if (is_blank (str [i])) + { + if (includemidblank) + { + if (!in_space) + { + result.push_back (E (' ')); + in_space = true; + } + } + else + { + result.push_back (str [i]); + in_space = true; + } + } + else + { + result.push_back (str [i]); + in_space = false; + } + } + return result; +} +template , typename AL = std::allocator > size_t GetNormalizeStringLength (const std::basic_string &str, bool includemidblank = false) +{ + typedef typename std::basic_string ::size_type size_type; + if (str.empty ()) return 0; + size_type first = 0, last = str.size (); + while (first < last && is_blank (str [first])) ++first; + while (last > first && is_blank (str [last - 1])) --last; + if (first == last) return 0; + size_t length = 0; + bool in_space = false; + for (size_type i = first; i < last; ++i) + { + if (is_blank (str [i])) + { + if (includemidblank) + { + if (!in_space) + { + ++ length; + in_space = true; + } + } + else + { + ++ length; + in_space = true; + } + } + else + { + ++ length; + in_space = false; + } + } + return length; +} +namespace std +{ + + template , typename al = std::allocator > class basic_nstring: public std::basic_string + { + using base = std::basic_string ; + bool default_upper = false, default_include_blank_in_str = false; + public: + using typename base::size_type; + using typename base::value_type; + using base::base; + basic_nstring (): base (), default_upper (false), default_include_blank_in_str (false) {} + basic_nstring (const ct *pStr): base (pStr), default_upper (false), default_include_blank_in_str (false) {} + basic_nstring (const base &str): base (str) {} + basic_nstring (base &&str): base (std::move (str)) {} + basic_nstring (const ct *data, size_type count): base (data, count), default_upper (false), default_include_blank_in_str (false) {} + template basic_nstring (const ct (&arr) [N]) : base (arr, N) {} + template basic_nstring (InputIt first, InputIt last): base (first, last), default_upper (false), default_include_blank_in_str (false) {} + bool upper_default () const { return this->default_upper; } + bool upper_default (bool value) { return this->default_upper = value; } + bool include_blank_in_str_middle () const { return this->default_include_blank_in_str; } + bool include_blank_in_str_middle (bool value) { return this->default_include_blank_in_str = value; } + base normalize (bool upper, bool includemidblank) const + { + return NormalizeString (*this, upper, includemidblank); + } + base normalize (bool upper) const + { + return this->normalize (upper, default_include_blank_in_str); + } + base normalize () const { return this->normalize (default_upper); } + base upper (bool includemidblank) const + { + return NormalizeString (*this, true, includemidblank); + } + base upper () const { return this->upper (default_include_blank_in_str); } + base lower (bool includemidblank) const + { + return NormalizeString (*this, false, includemidblank); + } + base lower () const { return this->lower (default_include_blank_in_str); } + base trim (bool includemidblank) const + { + return StringTrim (*this, includemidblank); + } + base trim () const { return this->trim (default_include_blank_in_str); } + size_t length (bool includemidblank) const { return GetNormalizeStringLength (*this, includemidblank); } + size_t length () const { return length (default_include_blank_in_str); } + bool empty () const + { + return IsNormalizeStringEmpty (*this); + } + bool equals (const base &another, bool includemidblank) const + { + return IsNormalizeStringEquals (*this, another, includemidblank); + } + bool equals (const base &another) const { return equals (another, default_include_blank_in_str); } + int64_t compare (const base &another, bool includemidblank) const + { + return NormalizeStringCompare (*this, another, includemidblank); + } + int64_t compare (const base &another) const { return compare (another, default_include_blank_in_str); } + base &string () { return *this; } + base to_string (bool upper, bool includemidblank) const { return this->normalize (upper, includemidblank); } + base to_string (bool upper) const { return this->normalize (upper, default_include_blank_in_str); } + base to_string () const { return this->normalize (default_upper); } + bool operator == (const base &other) const { return equals (other, false); } + bool operator != (const base &other) const { return !equals (other, false); } + bool operator < (const base &other) const { return compare (other, false) < 0; } + bool operator > (const base &other) const { return compare (other, false) > 0; } + bool operator <= (const base &other) const { return compare (other, false) <= 0; } + bool operator >= (const base &other) const { return compare (other, false) >= 0; } + int64_t operator - (const base &other) const { return compare (other, false); } + template , typename AL = std::allocator > + static bool equals (const std::basic_string &l, const std::basic_string &r, bool remove_mid_blank = false) + { + return IsNormalizeStringEquals (l, r, remove_mid_blank); + } + template , typename AL = std::allocator > + static int64_t compare (const std::basic_string &l, const std::basic_string &r, bool remove_mid_blank = false) + { + return NormalizeStringCompare (l, r, remove_mid_blank); + } + template , typename AL = std::allocator > + static std::basic_string normalize (const std::basic_string &str, bool to_upper = false, bool remove_mid_blank = false) + { + return NormalizeString (str, to_upper, remove_mid_blank); + } + template , typename AL = std::allocator > + static std::basic_string trim (const std::basic_string &str, bool remove_mid_blank = false) + { + return StringTrim (str, remove_mid_blank); + } + template , typename AL = std::allocator > + static size_t length (const std::basic_string &str, bool remove_mid_blank = false) + { + return GetNormalizeStringLength (str, remove_mid_blank); + } + template , typename AL = std::allocator > + static bool empty (const std::basic_string &str) + { + return IsNormalizeStringEmpty (str); + } + template , typename AL = std::allocator > + static std::basic_nstring to_nstring (std::basic_string &str) { return std::basic_nstring (str); } + template , typename AL = std::allocator > + static std::basic_nstring toupper (const std::basic_nstring &str) { return l0km::toupper (str); } + template , typename AL = std::allocator > + static std::basic_nstring tolower (const std::basic_nstring &str) { return l0km::tolower (str); } + }; + + typedef basic_nstring nstring; + typedef basic_nstring wnstring; +} \ No newline at end of file diff --git a/priread (never using)/prifile.h b/priread (never using)/prifile.h new file mode 100644 index 0000000..273703c --- /dev/null +++ b/priread (never using)/prifile.h @@ -0,0 +1,2671 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _CONSOLE +#include +#include +#endif +#include "nstring.h" +#include "localeex.h" +#include "themeinfo.h" +// #define UNALIGN_MEMORY + +#ifdef UNALIGN_MEMORY +#pragma pack(push, 1) +#endif +#ifdef min +#undef min +#endif +#ifdef max +#undef max +#endif +struct destruct +{ + std::function endtask = nullptr; + destruct (std::function init): endtask (init) {} + ~destruct () { if (endtask) endtask (); } +}; +template struct LargeIntBase +{ + BASE_INT val; + LargeIntBase () { val.QuadPart = 0; } + LargeIntBase (T v) { val.QuadPart = v; } + LargeIntBase (const LargeIntBase &other) { val.QuadPart = other.val.QuadPart; } + LargeIntBase (const BASE_INT &other) { val = other; } + operator BASE_INT () const { return val; } + operator T () const { return val.QuadPart; } + explicit operator bool () const { return val.QuadPart != 0; } + T *ptr_num () { return &val.QuadPart; } + BASE_INT *ptr_union () { return &val; } + size_t sizeof_num () const { return sizeof (val.QuadPart); } + size_t sizeof_union () const { return sizeof (val); } + T num () const { return val.QuadPart; } + BASE_INT win_union () const { return val; } + LargeIntBase operator + (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart + rhs.val.QuadPart); } + LargeIntBase operator - (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart - rhs.val.QuadPart); } + LargeIntBase operator * (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart * rhs.val.QuadPart); } + LargeIntBase operator / (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart / rhs.val.QuadPart); } + LargeIntBase operator % (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart % rhs.val.QuadPart); } + LargeIntBase &operator += (const LargeIntBase &rhs) { val.QuadPart += rhs.val.QuadPart; return *this; } + LargeIntBase &operator -= (const LargeIntBase &rhs) { val.QuadPart -= rhs.val.QuadPart; return *this; } + LargeIntBase &operator *= (const LargeIntBase &rhs) { val.QuadPart *= rhs.val.QuadPart; return *this; } + LargeIntBase &operator /= (const LargeIntBase &rhs) { val.QuadPart /= rhs.val.QuadPart; return *this; } + LargeIntBase &operator %= (const LargeIntBase &rhs) { val.QuadPart %= rhs.val.QuadPart; return *this; } + LargeIntBase operator & (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart & rhs.val.QuadPart); } + LargeIntBase operator | (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart | rhs.val.QuadPart); } + LargeIntBase operator ^ (const LargeIntBase &rhs) const { return LargeIntBase (val.QuadPart ^ rhs.val.QuadPart); } + LargeIntBase operator << (int n) const { return LargeIntBase (val.QuadPart << n); } + LargeIntBase operator >> (int n) const { return LargeIntBase (val.QuadPart >> n); } + LargeIntBase &operator &= (const LargeIntBase &rhs) { val.QuadPart &= rhs.val.QuadPart; return *this; } + LargeIntBase &operator |= (const LargeIntBase &rhs) { val.QuadPart |= rhs.val.QuadPart; return *this; } + LargeIntBase &operator ^= (const LargeIntBase &rhs) { val.QuadPart ^= rhs.val.QuadPart; return *this; } + LargeIntBase &operator <<= (int n) { val.QuadPart <<= n; return *this; } + LargeIntBase &operator >>= (int n) { val.QuadPart >>= n; return *this; } + LargeIntBase &operator ++ () { ++val.QuadPart; return *this; } + LargeIntBase operator ++ (int) { LargeIntBase tmp (*this); ++val.QuadPart; return tmp; } + LargeIntBase &operator -- () { --val.QuadPart; return *this; } + LargeIntBase operator -- (int) { LargeIntBase tmp (*this); --val.QuadPart; return tmp; } + bool operator < (const LargeIntBase &rhs) const { return val.QuadPart < rhs.val.QuadPart; } + bool operator > (const LargeIntBase &rhs) const { return val.QuadPart > rhs.val.QuadPart; } + bool operator <= (const LargeIntBase &rhs) const { return val.QuadPart <= rhs.val.QuadPart; } + bool operator >= (const LargeIntBase &rhs) const { return val.QuadPart >= rhs.val.QuadPart; } + bool operator == (const LargeIntBase &rhs) const { return val.QuadPart == rhs.val.QuadPart; } + bool operator != (const LargeIntBase &rhs) const { return val.QuadPart != rhs.val.QuadPart; } + bool operator ! () const { return !val.QuadPart; } + LargeIntBase operator ~ () const { return LargeIntBase (~val.QuadPart); } +}; +typedef LargeIntBase LargeInt, lint; +typedef LargeIntBase ULargeInt, ulint; +template , typename al = std::allocator > class basic_priid: public std::basic_nstring +{ + using base = std::basic_nstring ; + public: + using typename base::size_type; + using typename base::value_type; + using base::base; + basic_priid (const ct *buf, size_t sz = 16): base (buf, sz) {} + template basic_priid (const ct (&arr) [N]) : base (arr, N) {} +}; +typedef basic_priid pri_sectid; +std::string ReadStringEndwithNullA (IStream *pStream) +{ + if (!pStream) return ""; + std::string result; + char ch = 0; + ULONG cbRead = 0; + while (true) + { + HRESULT hr = pStream->Read (&ch, 1, &cbRead); + if (FAILED (hr) || cbRead == 0) break; + if (ch == '\0') break; + result.push_back (ch); + } + return result; +} +std::wstring ReadStringEndwithNullW (IStream *pStream) +{ + if (!pStream) return L""; + std::wstring result; + WCHAR ch = 0; + ULONG cbRead = 0; + while (true) + { + HRESULT hr = pStream->Read (&ch, sizeof (WCHAR), &cbRead); + if (FAILED (hr) || cbRead < sizeof (WCHAR)) break; + if (ch == L'\0') break; + result.push_back (ch); + } + return result; +} +std::string ReadStringEndwithNull (IStream *ifile, std::string &ret) +{ + return ret = ReadStringEndwithNullA (ifile); +} +std::wstring ReadStringEndwithNull (IStream *ifile, std::wstring &ret) +{ + return ret = ReadStringEndwithNullW (ifile); +} +std::string ReadStringA (IStream *pStream, size_t length) +{ + if (!pStream || length <= 0) return ""; + std::string result; + result.resize (length); + ULONG cbRead = 0; + HRESULT hr = pStream->Read (&result [0], length, &cbRead); + if (FAILED (hr) || cbRead == 0) + { + result.clear (); + return result; + } + if (cbRead < (ULONG)length) result.resize (cbRead); + return result; +} +std::wstring ReadStringW (IStream *pStream, size_t length) +{ + if (!pStream || length <= 0) return L""; + std::wstring result; + result.resize (length); + ULONG cbRead = 0; + HRESULT hr = pStream->Read (&result [0], length * sizeof (WCHAR), &cbRead); + if (FAILED (hr) || cbRead == 0) + { + result.clear (); + return result; + } + if (cbRead < (ULONG)(length * sizeof (WCHAR))) result.resize (cbRead / sizeof (WCHAR)); + return result; +} +enum class seekpos: DWORD +{ + start = STREAM_SEEK_SET, + current = STREAM_SEEK_CUR, + end = STREAM_SEEK_END +}; +class bytesstream +{ + std::vector bytes; + size_t pos = 0; + public: + using seekpos = ::seekpos; + // ļ/ƫλá 0 ʼ + using fsize_t = uint64_t; + // ļָƶȣң + using fmove_t = int64_t; + bool read (void *p, size_t size, size_t *ret = nullptr) + { + if (!p || size == 0) return false; + size_t available = 0; + if (pos < bytes.size ()) available = bytes.size () - pos; + size_t readSize = (available >= size) ? size : available; + if (readSize > 0) memcpy_s (p, size, bytes.data () + pos, readSize); + else memset (p, 0, size); + pos += size; + if (ret) *ret = readSize; + return true; + } + template ::value>::type> size_t read (T &var) + { + size_t ret = 0; + read (&var, sizeof (var), &ret); + return ret; + } + template ::value>::type> friend bytesstream &operator >> (bytesstream &s, T &variable) + { + s.read (&variable, sizeof (variable)); + return s; + } + friend bytesstream &operator >> (bytesstream &in, std::string &ret) + { + in.read_string_endwith_null (ret); + return in; + } + friend bytesstream &operator >> (bytesstream &in, std::wstring &ret) + { + in.read_string_endwith_null (ret); + return in; + } + bytesstream (const std::vector &vec): bytes (vec) {} + bytesstream (const BYTE *ptr, size_t len = 0): bytes (len, *ptr) {} + bytesstream () = default; + auto &data () { return bytes; } + void set (const std::vector &bver) + { + bytes = bver; + } + auto position () + { + if (pos > bytes.size ()) pos = bytes.size (); + return pos; + } + auto position (size_t v) { pos = v; if (pos > bytes.size ()) pos = bytes.size (); return pos; } + auto length () const { return bytes.size (); } + auto size () const { return length (); } + std::string read_string_endwith_null_a (); + std::wstring read_string_endwith_null_w (); + size_t read_string_endwith_null (std::string &ret); + size_t read_string_endwith_null (std::wstring &ret); + std::string read_string_a (size_t length); + std::wstring read_string_w (size_t length); + size_t read_string (size_t length, std::wstring &ret); + size_t read_string (size_t length, std::string &ret); + HRESULT seek (fmove_t movelen = 0, seekpos origin = seekpos::current, fsize_t *newpos = nullptr) + { + if (bytes.empty ()) return E_INVALIDARG; + int64_t newPosition = 0; + switch (origin) + { + case seekpos::start: + newPosition = movelen; + break; + case seekpos::current: + newPosition = static_cast (pos) + movelen; + break; + case seekpos::end: + newPosition = static_cast (bytes.size ()) + movelen; + break; + default: return E_INVALIDARG; + } + if (newPosition < 0) newPosition = 0; + pos = static_cast (newPosition); + if (newpos) *newpos = static_cast (pos); + return S_OK; + } + auto &buffer () const { return bytes; } + auto &buffer () { return bytes; } + size_t remain () const + { + if (pos > size ()) return 0; + return size () - pos; + } + void clear () + { + bytes.clear (); + pos = 0; + } + void resize (size_t len) { bytes.resize (len); } + const BYTE *ptr () const { return bytes.data (); } + BYTE *ptr () { return bytes.data (); } + template ::value>::type> + T read_bytes (size_t byteslen = sizeof (T), size_t *returnread = nullptr) + { + std::vector bytesbuf (byteslen); + size_t rl = 0; + read (bytesbuf.data (), byteslen, &rl); + if (returnread) *returnread = rl; + bytesbuf.resize (!rl ? sizeof (T) : rl); + return *(T *)bytesbuf.data (); + } +}; +std::string ReadStringEndwithNullA (bytesstream &stream) +{ + std::string result; + char ch = 0; + size_t cbRead = 0; + while (true) + { + if (!stream.read (&ch, 1, &cbRead) || cbRead == 0) break; + if (ch == '\0') break; + result.push_back (ch); + } + return result; +} +std::wstring ReadStringEndwithNullW (bytesstream &stream) +{ + std::wstring result; + WCHAR ch = 0; + size_t cbRead = 0; + while (true) + { + if (!stream.read (&ch, sizeof (WCHAR), &cbRead) || cbRead < sizeof (WCHAR)) break; + if (ch == L'\0') break; + result.push_back (ch); + } + return result; +} +std::string ReadStringEndwithNull (bytesstream &stream, std::string &ret) +{ + return ret = ReadStringEndwithNullA (stream); +} +std::wstring ReadStringEndwithNull (bytesstream &stream, std::wstring &ret) +{ + return ret = ReadStringEndwithNullW (stream); +} +std::string ReadStringA (bytesstream &stream, size_t length) +{ + if (length == 0) return ""; + std::string result (length, '\0'); + size_t cbRead = 0; + if (!stream.read (&result [0], length, &cbRead) || cbRead == 0) + { + result.clear (); + return result; + } + if (cbRead < length) result.resize (cbRead); + return result; +} +std::wstring ReadStringW (bytesstream &stream, size_t length) +{ + if (length == 0) return L""; + std::wstring result (length, L'\0'); + size_t cbRead = 0; + if (!stream.read (&result [0], length * sizeof (WCHAR), &cbRead) || cbRead == 0) + { + result.clear (); + return result; + } + if (cbRead < length * sizeof (WCHAR)) result.resize (cbRead / sizeof (WCHAR)); + return result; +} +std::string bytesstream::read_string_endwith_null_a () { return ReadStringEndwithNullA (*this); } +std::wstring bytesstream::read_string_endwith_null_w () { return ReadStringEndwithNullW (*this); } +size_t bytesstream::read_string_endwith_null (std::string &ret) +{ + return (ret = read_string_endwith_null_a ()).length (); +} +size_t bytesstream::read_string_endwith_null (std::wstring &ret) +{ + return (ret = read_string_endwith_null_w ()).length (); +} +std::string bytesstream::read_string_a (size_t length) { return ReadStringA (*this, length); } +std::wstring bytesstream::read_string_w (size_t length) { return ReadStringW (*this, length); } +size_t bytesstream::read_string (size_t length, std::wstring &ret) { return (ret = read_string_w (length)).length (); } +size_t bytesstream::read_string (size_t length, std::string &ret) { return (ret = read_string_a (length)).length (); } +// עʱͷָ롣Ϊһֲ +class istreamstream +{ + private: + // COM ӿڣָ + IStream *comStream = nullptr; + // 棬 210 ֽ + bytesstream bsCache; + // Ϊڣ¼ļλá + uint64_t ulWndOffset = 0; + static const size_t MAX_CACHE_SIZE = 210; + public: + // ļ/ƫλá 0 ʼ + using fsize_t = uint64_t; + // ļָƶȣң + using fmove_t = int64_t; + using seekpos = ::seekpos; + istreamstream (IStream *iptr): comStream (iptr) + { + // bsCache.buffer ().reserve (MAX_CACHE_SIZE); + } + auto &buffer () const { return bsCache; } + auto &buffer () { return bsCache; } + void set_stream (IStream *iptr) { comStream = iptr; } + IStream *get_stream () { return comStream; } + // read + // 棺ȡ 210 ֽڵĻ + // ȡ棺ڻжȡֽڣָָֽһƶָֽ뾭һָȡ + HRESULT read (void *buf, size_t size, size_t *readbytes = nullptr) + { + if (!comStream || !buf) return E_INVALIDARG; + if (position () < ulWndOffset || position () >= ulWndOffset + bsCache.size () || position () != ulWndOffset + position ()) bsCache.buffer ().clear (); + if (size > MAX_CACHE_SIZE) + { + bsCache.clear (); + ULONG cbread = 0; + HRESULT hr = comStream->Read (buf, size, &cbread); + if (readbytes) *readbytes = cbread; + return hr; + } + else if (bsCache.remain () < size) + { + bsCache.clear (); + bsCache.resize (MAX_CACHE_SIZE); + auto nowpos = position (); + ULONG cbread = 0; + HRESULT hr = comStream->Read (bsCache.ptr (), MAX_CACHE_SIZE, &cbread); + comStream->Seek (lint (nowpos), STREAM_SEEK_SET, nullptr); + ulWndOffset = nowpos; + bsCache.resize (cbread); + size_t bufread = 0; + bool res = bsCache.read (buf, size, &bufread); + if (readbytes) *readbytes = bufread; + comStream->Seek (lint (bufread), STREAM_SEEK_CUR, nullptr); + if (res) return S_OK; + else return FAILED (hr) ? hr : E_FAIL; + } + else + { + size_t bufread = 0; + bool res = bsCache.read (buf, size, &bufread); + if (readbytes) *readbytes = bufread; + comStream->Seek (lint (bufread), STREAM_SEEK_CUR, nullptr); + return res ? S_OK : E_FAIL; + } + } + // ʹóޣҲִָ֧롣 + template ::value>::type> + HRESULT read (T &variable, size_t *readbytes = nullptr) + { + return read (&variable, sizeof (variable), readbytes); + } + std::string read_string_endwith_null_a () { return ReadStringEndwithNullA (comStream); } + std::wstring read_string_endwith_null_w () { return ReadStringEndwithNullW (comStream); } + size_t read_string_endwith_null (std::string &ret) + { + return (ret = read_string_endwith_null_a ()).length (); + } + size_t read_string_endwith_null (std::wstring &ret) + { + return (ret = read_string_endwith_null_w ()).length (); + } + std::string read_string_a (size_t length) { return ReadStringA (comStream, length); } + std::wstring read_string_w (size_t length) { return ReadStringW (comStream, length); } + size_t read_string (size_t length, std::wstring &ret) { return (ret = read_string_w (length)).length (); } + size_t read_string (size_t length, std::string &ret) { return (ret = read_string_a (length)).length (); } + HRESULT write (const void *bytes, size_t size, size_t *writebytes = nullptr) + { + if (!comStream) return E_INVALIDARG; + ULONG retsize = 0; + HRESULT hr = comStream->Write (bytes, size, &retsize); + if (writebytes) *writebytes = retsize; + return hr; + } + // ʹóޣҲִָ֧롣 + template ::value>::type> + HRESULT write (const T &variable, size_t *writebytes = nullptr) + { + return write (&variable, sizeof (variable), writebytes); + } + HRESULT seek (fmove_t movelen = 0, seekpos origin = seekpos::current, fsize_t *newpos = nullptr) + { + if (!comStream) return E_INVALIDARG; + ulint ret = 0; + HRESULT hr = comStream->Seek (lint (movelen), (DWORD)origin, ret.ptr_union ()); + if ((movelen != 0 || origin != seekpos::current) && bsCache.size ()) bsCache.clear (); + if (newpos) *newpos = ret; + return hr; + } + fsize_t position () + { + fsize_t pos = 0; + seek (0, seekpos::current, &pos); + return pos; + } + HRESULT stat (STATSTG *data, DWORD statflags) + { + if (!comStream) return E_INVALIDARG; + return comStream->Stat (data, statflags); + } + HRESULT size (fsize_t newsize) + { + if (!comStream) return E_INVALIDARG; + return comStream->SetSize (ulint (newsize)); + } + fsize_t size () + { + STATSTG stg = {0}; + if (SUCCEEDED (stat (&stg, STATFLAG_NONAME))) return stg.cbSize.QuadPart; + else return 0; + } + HRESULT copy_to (istreamstream &pstm, fsize_t cb, fsize_t *pcbRead = nullptr, fsize_t *pcbWritten = nullptr) + { + if (!comStream) return E_INVALIDARG; + ulint r = 0, w = 0; + HRESULT hr = comStream->CopyTo (pstm.comStream, ulint (cb), r.ptr_union (), w.ptr_union ()); + if (pcbRead) *pcbRead = r; + if (pcbWritten) *pcbWritten = w; + return hr; + } + HRESULT commit (DWORD grfCommitFlags) + { + if (!comStream) return E_INVALIDARG; + return comStream->Commit (grfCommitFlags); + } + HRESULT revert () + { + if (!comStream) return E_INVALIDARG; + return comStream->Revert (); + } + HRESULT lock_region (fsize_t libOffset, fsize_t cb, DWORD dwLockType) + { + if (!comStream) return E_INVALIDARG; + return comStream->LockRegion (ulint (libOffset), ulint (cb), dwLockType); + } + HRESULT unlock_region (fsize_t libOffset, fsize_t cb, DWORD dwLockType) + { + if (!comStream) return E_INVALIDARG; + return comStream->UnlockRegion (ulint (libOffset), ulint (cb), dwLockType); + } + HRESULT clone (istreamstream &anotherptr) + { + if (!comStream) return E_INVALIDARG; + return comStream->Clone (&anotherptr.comStream); + } + template ::value>::type> + friend istreamstream &operator >> (istreamstream &in, T &variable) + { + in.read (variable); + return in; + } + template ::value>::type> + friend istreamstream &operator << (istreamstream &out, const T &variable) + { + out.write (variable); + return out; + } + friend istreamstream &operator >> (istreamstream &in, std::string &ret) + { + in.read_string_endwith_null (ret); + return in; + } + friend istreamstream &operator >> (istreamstream &in, std::wstring &ret) + { + in.read_string_endwith_null (ret); + return in; + } + operator IStream * () { return comStream; } + IStream *operator -> () { return comStream; } + template ::value>::type> + // ȡһֵжǷԤڡthrowerror ΪʱֵԤͨ׳쳣жϡֵֻͨжϡ + bool expect (const T &expect_value, bool throwerror = true, T *retvalue = nullptr) + { + T value; + this->read (value); + if (retvalue) *retvalue = value; + bool res = value == expect_value; + if (!res && throwerror) throw std::exception ("Unexpected value read."); + return res; + } + template ::value>::type> + T read_bytes (size_t byteslen = sizeof (T), size_t *returnread = nullptr) + { + std::vector bytesbuf (byteslen); + size_t rl = 0; + read (bytesbuf.data (), byteslen, &rl); + if (returnread) *returnread = rl; + bytesbuf.resize (!rl ? sizeof (T) : rl); + return *(T *)bytesbuf.data (); + } +}; + +// 0 1 +// 012345678901234 +#define PRI_SECT_ID_PRI_DESCRIPTOR "[mrm_pridescex]" +#define PRI_SECT_ID_HIERARCHICAL_SCHEMA "[mrm_hschema] " +#define PRI_SECT_ID_HIERARCHICAL_SCHEMAEX "[mrm_hschemaex]" +#define PRI_SECT_ID_DECISION_INFO "[mrm_decn_info]" +#define PRI_SECT_ID_RESOURCE_MAP "[mrm_res_map__]" +#define PRI_SECT_ID_RESOURCE_MAP2 "[mrm_res_map2_]" +#define PRI_SECT_ID_DATA_ITEM "[mrm_dataitem] " +#define PRI_SECT_ID_REVERSE_MAP "[mrm_rev_map] " +#define PRI_SECT_ID_REFERENCED_FILE "[def_file_list]" +enum class SectionType +{ + Unknown, // δ֪ - δʶĽ + PriDescriptor, // PRI - ļṹϢ + HierarchicalSchema, // νṹģʽ - ԴռͲνṹ + HierarchicalSchemaEx, // νṹģʽ - ԴռͲνṹ + DecisionInfo, // Ϣ - Դ޶;߼ + ResourceMap, // Դӳ - Դӳ䵽ѡֵ + ResourceMap2, // Դӳ - Դӳ䵽ѡֵ + DataItem, // - 洢ʵʵԴ + ReverseMap, // ӳ - ṩԴIDӳ + ReferencedFile // ļ - õⲿļ +}; +std::wstring EnumToStringW (SectionType value) +{ + switch (value) + { + case SectionType::PriDescriptor: return L"PriDescriptor"; break; + case SectionType::HierarchicalSchema: return L"HierarchicalSchema"; break; + case SectionType::HierarchicalSchemaEx: return L"HierarchicalSchemaEx"; break; + case SectionType::DecisionInfo: return L"DecisionInfo"; break; + case SectionType::ResourceMap: return L"ResourceMap"; break; + case SectionType::ResourceMap2: return L"ResourceMap2"; break; + case SectionType::DataItem: return L"DataItem"; break; + case SectionType::ReverseMap: return L"ReverseMap"; break; + case SectionType::ReferencedFile: return L"ReferencedFile"; break; + default: + case SectionType::Unknown: return L"Unknown"; break; + } +} + +struct head +{ + // 汾ʶ / version identifier + // mrm_pri0ͻ 6.2.1Windows 8 + // mrm_pri1ͻ 6.3.0Windows 8.1 + // mrm_prifWindows Phone 6.3.1 + // mrm_pri2Universal 10.0.0Windows 10 + CHAR szMagic [8] = {}; + WORD wPlaceholder1 = -1, // δ֪0 / unknown, zero + wPlaceholder2 = 0; // δ֪1 / unknown, one + DWORD dwFileSize = 0, // ļܴС / total file size + dwToCOffset = 0, // Ŀ¼ƫ / offset of table of contents + dwSectStartOffset = 0; // һڵƫ / offset of first section + WORD wSectCount = 0, // / number of sections + wPlaceholder3 = 0; // δ֪0xFFFF / unknown, 0xFFFF + DWORD dwPlaceholder4 = -1; // δ֪0 / unknown, zero + bool valid () + { + CHAR m7 = szMagic [7]; + destruct endt ([this, m7] () { + if (m7) szMagic [7] = m7; + }); + szMagic [7] = '\0'; + if (_stricmp (szMagic, "mrm_pri")) return false; + switch (m7) + { + case '0': case '1': case '2': case 'f': case 'F': break; + default: return false; + } + if (wPlaceholder1 != 0) return false; + if (wPlaceholder2 != 1) return false; + if (wPlaceholder3 != 0xFFFF) return false; + if (dwPlaceholder4 != 0) return false; + return true; + } +}; +struct foot +{ + DWORD dwChkCode = 0; // 0xDEFFFADE + DWORD dwTotalFileSize = 0; // total file size, as in header + CHAR szMagic [8] = {0}; // version identifier, as in header + bool valid (const head &fh) + { + if (dwChkCode != 0xDEFFFADE) return false; + if (dwTotalFileSize != fh.dwFileSize) return false; + for (int i = 0; i < 8; i ++) + { + if (szMagic [i] != fh.szMagic [i] && tolower (szMagic [i]) != tolower (fh.szMagic [i])) return false; + } + return true; + } +}; +struct tocentry +{ + CHAR szIdentifier [16] = {0}; // ڱʶ / section identifier + WORD wFlags = 0; // ־ / flags + WORD wSectFlags = 0; // ڱ־ / section flags + DWORD dwSectQualifier = 0; // ޶ / section qualifier + DWORD dwSectOffset = 0; // ƫƣڵһƫƣ / section offset (relative to offset of first section) + DWORD dwSectLength = 0; // ڳ / section length +}; +struct section_header +{ + CHAR szIdentifier [16] = {0}; // ڱʶ / section identifier + DWORD dwQualifier = 0; // ޶ / section qualifier + WORD wFlags = 0; // ־ / flags + WORD wSectFlags = 0; // ڱ־ / section flags + DWORD dwLength = 0; // ڳ / section length + DWORD dwPlaceholder1 = -1; // δ֪0 / unknown, zero + bool valid () const { return szIdentifier [0] && !dwPlaceholder1; } +}; +struct section_check +{ + DWORD dwChkCode = 0; // ħ 0xF5DEDEFA + DWORD dwSectLength = 0; // ڳȣͷеͬ / section length, as in section header + bool valid (const section_header &h) const { return dwChkCode == 0xDEF5FADE && dwSectLength == h.dwLength; } +}; +struct substream +{ + IStream *&ifile; + ULONGLONG offset = 0; + ULONGLONG size = 0; + substream (IStream *&ifile, ulint ofs = 0, ulint siz = 0): ifile (ifile), offset (ofs), size (siz) {} + void set (ulint p_offset, ulint p_size) + { + offset = p_offset; + size = p_size; + } + HRESULT seek () + { + istreamstream iss (ifile); + return iss.seek (offset, istreamstream::seekpos::start); + } +}; +struct prifile; +struct section +{ + section_header head; + section_check foot; + substream childst; + // type ȡֱͣͨ sect_type ȡΪδʼ + SectionType sect_type = SectionType::Unknown; + section (IStream *&ifile, prifile &prif): childst (ifile), pri_file (prif) {} + bool valid () const { return head.valid () && foot.valid (head); } + SectionType type () + { + if (sect_type == SectionType::Unknown) + { + pri_sectid pid (head.szIdentifier, 16); + if (pid.equals (PRI_SECT_ID_PRI_DESCRIPTOR)) sect_type = SectionType::PriDescriptor; + else if (pid.equals (PRI_SECT_ID_HIERARCHICAL_SCHEMA)) sect_type = SectionType::HierarchicalSchema; + else if (pid.equals (PRI_SECT_ID_HIERARCHICAL_SCHEMAEX)) sect_type = SectionType::HierarchicalSchemaEx; + else if (pid.equals (PRI_SECT_ID_DECISION_INFO)) sect_type = SectionType::DecisionInfo; + else if (pid.equals (PRI_SECT_ID_RESOURCE_MAP)) sect_type = SectionType::ResourceMap; + else if (pid.equals (PRI_SECT_ID_RESOURCE_MAP2)) sect_type = SectionType::ResourceMap2; + else if (pid.equals (PRI_SECT_ID_DATA_ITEM)) sect_type = SectionType::DataItem; + else if (pid.equals (PRI_SECT_ID_REVERSE_MAP)) sect_type = SectionType::ReverseMap; + else if (pid.equals (PRI_SECT_ID_REFERENCED_FILE)) sect_type = SectionType::ReferencedFile; + else sect_type = SectionType::Unknown; + } + return sect_type; + } + size_t length () const { return head.dwLength; } + prifile &pri_file; +#ifdef _CONSOLE + friend std::wostream &operator << (std::wostream &o, section &s) + { + return o << L"Section " << EnumToStringW (s.type ()) << L" Length " << s.head.dwLength; + } +#endif +}; +namespace pri +{ + struct sect_pridesp; // PriDescriptor PRI - ļṹϢ + struct sect_hierasche; // HierarchicalSchema & HierarchicalSchemaEx νṹģʽ - ԴռͲνṹ + struct sect_decinfo; // DecisionInfo Ϣ - Դ޶;߼ + struct sect_resmap; // ResourceMap & ResourceMap2 Դӳ - Դӳ䵽ѡֵ + struct sect_dataitem; // DataItem - 洢ʵʵԴ + struct sect_revmap; // ReverseMap ӳ - ṩԴIDӳ + struct sect_reffile; // ReferencedFile ļ - õⲿļ + struct sect_unknown; // Unknown δ֪ - δʶĽ + + // PriDescriptorFlags + + enum class PRI_SEC_DESP: DWORD + { + INVALID = 0, + AUTOMERGE = 0x1, // AutoMerge + DEPLOY_MERGEABLE = 0x2, // IsDeploymentMergeable + DEPLOY_MERGE_RESULT = 0x4, // IsDeploymentMergeResult + AUTO_MERGE_RESULT = 0x8 // IsAutomergeMergeResult + }; + + // HierarchicalSchema & HierarchicalSchemaEx + + typedef struct _HSCHEMA_VERSION_INFO + { + WORD wMajor = 0; // 汾 / major version + WORD wMinor = 0; // ΰ汾 / minor version + DWORD dwUnknown1 = -1; // δ֪0 + // Уͣchecksum + // checksum: a CRC32-based checksum computed on the unique name, + // the name, the section indices of the Resource Map Section + // and Data Item Section, and the names of all scopes and items + DWORD dwCheckSum = 0; + DWORD dwScopeCount = 0; // scope + DWORD dwItemCount = 0; // item + bool empty () + { + return dwUnknown1 != 0; + } + } HSCHEMA_VERSION_INFO; + // Դƣscope/item¸ʽ洢ʾֶΣparent scope indexfull path ȡ + // ĸдֳȡƫơindex property ȡ + + typedef struct _SCOPE_ITEM_INFO + { + WORD wParentScopeIndex = -1; // parent scope index + WORD wFullPathLength = 0; // length of full path + WCHAR wchUpperFirst = L'\0'; // uppercase first character of name, '\0' if name is empty + // length of name in characters, null-terminator excluded, 0 if the length is bigger than 255 + // ԭߵ C# ôȡģuint nameOffset = binaryReader.ReadUInt16() | (uint)((flags & 0xF) << 16); + // ԲֱʹóԱ bNameLengthʹýṹ name_offset () ȡֵ + BYTE bNameLength = 0; + BYTE bFlags = 0; // flags and upper bits of name offset + WORD wNameOffset = 0; // offset of name in ASCII or Unicode name block in characters + // index property + // bits 0-3: upper bits 16-19 of name offset + // bit 4: set if resource name is a scope, unset if it is an item + // bit 5 : set if name is stored in the ASCII name block, unset if + // it is stored in the Unicode name block + WORD wIndexProp = 0; + bool is_scope () const { return bFlags & 0x10; } + bool name_in_ascii () const { return bFlags & 0x20; } + DWORD name_offset () const { return (DWORD)wNameOffset | ((DWORD)(bFlags & 0xF) << 16); } + DWORD index () const { return wIndexProp; } + //friend istreamstream &operator >> (istreamstream &i, _SCOPE_ITEM_INFO &s) + //{ + // i >> s.wParentScopeIndex >> + // s.wFullPathLength; + // s.wchUpperFirst = i.read_bytes (sizeof (UINT16)); + // s.bNameLength = i.read_bytes (); + // s.bFlags = i.read_bytes (); + // s.wNameOffset = i.read_bytes (); + // s.wIndexProp = i.read_bytes (); + // return i; + //} + //friend bytesstream &operator >> (bytesstream &i, _SCOPE_ITEM_INFO &s) + //{ + // i >> s.wParentScopeIndex >> + // s.wFullPathLength; + // s.wchUpperFirst = i.read_bytes (sizeof (UINT16)); + // s.bNameLength = i.read_bytes (); + // s.bFlags = i.read_bytes (); + // s.wNameOffset = i.read_bytes (); + // s.wIndexProp = i.read_bytes (); + // return i; + //} + } SCOPE_ITEM_INFO; + typedef struct _SCOPE_EX_INFO + { + WORD wScopeIndex = 0; // scope index + WORD wChildCount = 0; // child count + WORD wFirstChild = 0; // scope or item index of first child, all other children follow sequentially + WORD wUnknown1 = 0; // unknown, zero + //friend istreamstream &operator >> (istreamstream &i, _SCOPE_EX_INFO &e) + //{ + // i >> e.wScopeIndex >> + // e.wChildCount >> + // e.wFirstChild; + // i.expect (0, true, &e.wUnknown1); + // return i; + //} + bool valid () const { return !wUnknown1; } + } SCOPE_EX_INFO; + typedef WORD ITEM_INDEX; + enum class RES_MAP_OBJTYPE + { + ENTRY = 0, + SCOPE = 1, + ITEM = 2 + }; + class RES_MAP_SCOPE; + class RES_MAP_ENTRY + { + public: + ITEM_INDEX wIndex = 0; + std::wstring strName; + RES_MAP_SCOPE *pParent = nullptr; + RES_MAP_ENTRY (ITEM_INDEX index = 0, RES_MAP_SCOPE *parent = {}, const std::wstring &name = L"", RES_MAP_OBJTYPE type = RES_MAP_OBJTYPE::ENTRY, bool setnull = true): + wIndex (index), strName (name), pParent (parent), eType (type), bIsNull (setnull) {} + std::wstring full_name (WCHAR divide = L'\\'); + size_t path (std::vector &output); + void refresh_full_name () { strFullName.clear (); } + RES_MAP_OBJTYPE type () const { return eType; } + bool bIsNull = false; + protected: + std::wstring strFullName; + RES_MAP_OBJTYPE eType; + }; + class RES_MAP_SCOPE: public RES_MAP_ENTRY + { + public: + explicit RES_MAP_SCOPE (ITEM_INDEX index = 0, RES_MAP_SCOPE *parent = nullptr, const std::wstring &name = L"", bool setnull = true): + RES_MAP_ENTRY (index, parent, name, RES_MAP_OBJTYPE::SCOPE, setnull) {} + std::vector vecChild; + }; + std::wstring RES_MAP_ENTRY::full_name (WCHAR divide) + { + if (strFullName.empty ()) + { + if (pParent) strFullName = pParent->full_name (divide) + divide + strName; + else strFullName = strName; + } + return strFullName; + } + size_t RES_MAP_ENTRY::path (std::vector &output) + { + output.clear (); + if (pParent) pParent->path (output); + output.push_back (strName); + return output.size (); + } + class RES_MAP_ITEM: public RES_MAP_ENTRY + { + public: + RES_MAP_ITEM (ITEM_INDEX index = 0, RES_MAP_SCOPE *parent = nullptr, const std::wstring &name = L"", bool setnull = true): + RES_MAP_ENTRY (index, parent, name, RES_MAP_OBJTYPE::ITEM, setnull) {} + }; + + // DecisionInfo + + typedef struct _DECISION_INFO + { + WORD wFirstQualiIndex = 0; // index of the first qualifier set index in the index table + WORD wQualiSetsCount = 0; // number of qualifiers sets in decision + } DECISION_INFO; + typedef struct _QUALIFIER_SET_INFO + { + WORD wFirstQualiIndex = 0; // index of the first qualifier index in the index table // firstQualifierIndexIndex + WORD wQualiSetsCount = 0; // number of qualifiers in qualifier set // numQualifiersInSet + } QUALIFIER_SET_INFO; + typedef struct _QUALIFIER_INFO + { + WORD wDistQualiIndex = 0; // index of distinct qualifier + WORD wPriority = 0; // priority + WORD wFallbackScore = -1; // fallback score, values range from 0 to 1000 + WORD wUnknown1 = -1; // unknown, zero + } QUALIFIER_INFO; + enum class QUALIFIER_TYPE: WORD + { + LANGUAGE = 0, // (0) + CONTRAST = 1, // Աȶ (1) + SCALE = 2, // (2) + HOMEREGION = 3, // Ļ (3) + TARGETSIZE = 4, // Ŀߴ (4) + LAYOUTDIR = 5, // ַ (5) + THEME = 6, // (6) + ALTERNATEFORM = 7, // ʽ (7) + DXFEATURELEVEL = 8, // DX ܵȼ (8) + CONFIG = 9, // (9) + DEVICEFAMILY = 10, // 豸ϵ (10) + CUSTOM = 11, // Զ (11) + }; + std::wstring EnumToStringW (QUALIFIER_TYPE value) + { + switch (value) + { + case QUALIFIER_TYPE::LANGUAGE: return L"Language"; break; + case QUALIFIER_TYPE::CONTRAST: return L"Contrast"; break; + case QUALIFIER_TYPE::SCALE: return L"Scale"; break; + case QUALIFIER_TYPE::HOMEREGION: return L"HomeRegion"; break; + case QUALIFIER_TYPE::TARGETSIZE: return L"TargetSize"; break; + case QUALIFIER_TYPE::LAYOUTDIR: return L"LayoutDir"; break; + case QUALIFIER_TYPE::THEME: return L"Theme"; break; + case QUALIFIER_TYPE::ALTERNATEFORM: return L"AlternateForm"; break; + case QUALIFIER_TYPE::DXFEATURELEVEL: return L"DxFeatureLevel"; break; + case QUALIFIER_TYPE::CONFIG: return L"Configure"; break; + case QUALIFIER_TYPE::DEVICEFAMILY: return L"DeviceFamily"; break; + case QUALIFIER_TYPE::CUSTOM: return L"Custom"; break; + } + } + typedef struct _DISTINCE_QUALIFIER_INFO + { + WORD wUnknown1 = 0; // unknown + WORD wQualiType = 0; // qualifier type + WORD wUnknown2 = 0; // unknown + WORD wUnknown3 = 0; // unknown + DWORD wQualiValueOffset = 0; // offset of qualifier value in qualifier value block, in characters + } DISTINCE_QUALIFIER_INFO; + typedef struct _QUALIFIER + { + ITEM_INDEX wIndex = 0; + QUALIFIER_TYPE eType = QUALIFIER_TYPE::CUSTOM; + WORD wPriority = 0; + DOUBLE dFallbackScope = 0; + std::wstring swValue = 0; + _QUALIFIER (ITEM_INDEX index = 0, QUALIFIER_TYPE type = QUALIFIER_TYPE::CUSTOM, WORD priority = 0, DOUBLE fallbackScope = 0, const std::wstring &value = L""): + wIndex (index), eType (type), wPriority (priority), dFallbackScope (fallbackScope), swValue (value) {} + } QUALIFIER; + typedef struct _QUALIFIER_SET + { + WORD wIndex = 0; + std::vector vecQuals; + _QUALIFIER_SET (WORD index = 0, const std::vector &quals = {}): + wIndex (index), vecQuals (quals) {} + } QUALIFIER_SET; + typedef struct _DECISION + { + WORD wIndex = 0; + std::vector verQualSets; + _DECISION (WORD index, const std::vector &qualsets = {}): + wIndex (index), verQualSets (qualsets) {} + } DECISION; + + // ResourceMap & ResourceMap2 + + typedef struct _HSCHEMA_REF_BLOCK + { + HSCHEMA_VERSION_INFO verHschema; // hierarchical schema version info + struct + { + WORD wUniIdLength = 0; // length of unique id in characters, null-terminator included + WORD wUnknown1 = -1; // unknown, zero + DWORD dwUnknown2 = 0; // unknown + DWORD dwUnknown3 = 0; // unknown + } part2; + std::wstring swUniqueId = L""; // unique id + bool empty () + { + return swUniqueId.empty () && part2.wUnknown1 != 0; + } + } HSCHEMA_REF_BLOCK; + enum class RES_VALUE_TYPE: DWORD + { + STRING = 0, // String (0) + PATH = 1, // Path (1) + EMBEDDEDDATA = 2, // EmbeddedData (2) + ASCIISTRING = 3, // AsciiString (3) + UTF8STRING = 4, // Utf8String (4) + ASCIIPATH = 5, // AsciiPath (5) + UTF8PATH = 6 // Utf8Path (6) + }; + typedef struct _RES_VALUE_TYPE_TABLE + { + DWORD dwUnknown1 = 0; // unknown, 4 + DWORD dwResType = -1; // resource value type + } RES_VALUE_TYPE_TABLE; + typedef struct _ITEM_ITEMINFO_GROUP_TABLE_ENTRY + { + WORD wFirstIndexProperty = 0; // index property of first resource item + WORD wItemInfoGroupIndex = 0; // index of iteminfo group + } ITEM_ITEMINFO_GROUP_TABLE_ENTRY; + typedef struct _ITEMINFO_GROUP_TABLE_ENTRY + { + WORD wItemInfoCount; // number of iteminfos in this group + WORD wFirstItemIndex; // index of the first iteminfo in this group + _ITEMINFO_GROUP_TABLE_ENTRY (WORD count = 0, WORD firstIndex = 0): wItemInfoCount (count), wFirstItemIndex (firstIndex) {} + } ITEMINFO_GROUP_TABLE_ENTRY; + typedef struct _ITEM_ITEMINFO_TABLE_ENTRY + { + WORD wDecisionIndex = 0; // index of decision + WORD wFirstCandiIndex = 0; // index of first candidate + } ITEM_ITEMINFO_TABLE_ENTRY; + typedef struct _TABLE_EXT_BLOCK + { + DWORD dwItemAdditEntCount = 0; // number of additional entries of the item to iteminfo group table // ItemToItemInfoGroupCountLarge + DWORD dwItemGroupAdditEntCount = 0; // number of additional entries of the item info group table // itemInfoGroupCountLarge + DWORD dwItemTableAdditEntCount = 0; // number of additional entries of the iteminfo table // itemInfoCountLarge + } TABLE_EXT_BLOCK; + typedef BYTE CANDIDATE_TYPE; + typedef struct _CANDIDATE0_DATA + { + BYTE bResValueType = 0; // resource value type, specified as an index into the resource value type table + WORD wEmbeddedLength = 0; // embedded data length + DWORD dwEmbeddedOffset = 0; // offset of the embedded data in the embedded data block + } CANDIDATE0_DATA; + typedef struct _CANDIDATE1_DATA + { + BYTE bResValueType = 0; // resource value type, specified as an index into the resource value type table + WORD wSrcFile = 0; // source file // sourceFileIndex + WORD wDataIndex = 0; // index of the data item storing the data // valueLocation + WORD wSectIndex = 0; // section index of the Data Item Section storing the data // dataItemSection + } CANDIDATE1_DATA; + typedef struct _CANDIDATE_INFO + { + BYTE bCandidateType = 0; // 0 1 + union CANDIDATE + { + CANDIDATE0_DATA _0; + CANDIDATE1_DATA _1; + CANDIDATE (CANDIDATE0_DATA can0): _0 (can0) {} + CANDIDATE (CANDIDATE1_DATA can1): _1 (can1) {} + CANDIDATE () {} + } objCandidate; + _CANDIDATE_INFO (CANDIDATE0_DATA can0): bCandidateType (0), objCandidate (can0) {} + _CANDIDATE_INFO (CANDIDATE1_DATA can1): bCandidateType (1), objCandidate (can1) {} + _CANDIDATE_INFO (): bCandidateType (-1) {} + bool candidate_0 () const { return bCandidateType == 0; } + bool candidate_1 () const { return bCandidateType == 1; } + bool valid () const { return candidate_0 () ^ candidate_1 (); } + } CANDIDATE_INFO; + struct basic_sect; + template struct ref_sect + { + using classtype = ref_sect ; + // static_assert (std::is_base_of ::value, "SectionType must derive from basic_sect"); + int index; + ref_sect (int sect_index = -1): index (sect_index) {} + using sect_type = SectionType; + explicit operator int () { return index; } + bool valid () const { return index != -1; } + void reset () { index = -1; } + bool operator == (const classtype &another) const { return index == another.index; } + classtype &operator = (const classtype &another) { index = another.index; return *this; } + classtype &operator = (int newvalue) { index = newvalue; return *this; } + }; + typedef struct _RES_MAP_ITEM_REF + { + ref_sect wSchemaSect; + int iItemIndex; + _RES_MAP_ITEM_REF (const ref_sect &ssect, int itemindex): + wSchemaSect (ssect), iItemIndex (itemindex) {} + _RES_MAP_ITEM_REF () {} + } RES_MAP_ITEM_REF; + struct _CANDIDATE; + typedef struct _CANDIDATE_SET + { + RES_MAP_ITEM_REF refResMapItem; + WORD wDecisionIndex = 0; + std::vector <_CANDIDATE> vecCandidates; + } CANDIDATE_SET; + typedef struct _BASIC_REF + { + INT64 llIndex = -1; + bool valid () const { return llIndex >= 0; } + void reset () { llIndex = -1; } + void set (int index) { llIndex = index; } + void setnull () { llIndex = -1; } + int get () { return llIndex; } + bool isnull () { return !valid (); } + _BASIC_REF (int index = 0, bool isnull = false): + llIndex (isnull ? -1 : index) {} + operator int () { return get (); } + _BASIC_REF &operator = (int index) { set (index); return *this; } + _BASIC_REF &operator = (const _BASIC_REF &rfr) { this->llIndex = rfr.llIndex; return *this; } + bool operator == (const _BASIC_REF &another) const { return this->llIndex == another.llIndex; } + bool operator == (int index) const { return (int)this->llIndex == index; } + } BASIC_REF; + typedef struct _REF_FILE_REF: public BASIC_REF + { + using BASIC_REF::_BASIC_REF; + } REF_FILE_REF; + typedef struct _DATA_ITEM_REF: public BASIC_REF + { + ref_sect iDataSectIndex; + _DATA_ITEM_REF (int itemIndex = 0, int itemDataSectIndex = 0, bool isnull = false): + BASIC_REF (itemIndex, isnull), iDataSectIndex (itemDataSectIndex) {} + operator int () = delete; + _DATA_ITEM_REF &operator = (int) = delete; + _DATA_ITEM_REF &operator = (const _BASIC_REF &) = delete; + _DATA_ITEM_REF &operator = (const _DATA_ITEM_REF &another) + { + this->llIndex = another.llIndex; + this->iDataSectIndex = another.iDataSectIndex; + return *this; + } + bool operator == (int) = delete; + bool operator == (const _BASIC_REF &) = delete; + bool operator == (const _DATA_ITEM_REF &another) const + { return llIndex == another.llIndex && iDataSectIndex == another.iDataSectIndex; } + } DATA_ITEM_REF; + typedef struct _BYTE_SPAN + { + ulint offset = 0; + size_t length = 0; + bool isnull = false; + using classtype = _BYTE_SPAN; + _BYTE_SPAN (ulint p_of = 0, size_t len = 0, bool nullstatus = false): offset (p_of), length (len), isnull (nullstatus) {} + // ȡʱļָλ + HRESULT get_bytes (istreamstream istream, std::vector &retbytes, size_t *retbyteslen = nullptr, bool cutbytesnoread = false) + { + retbytes.clear (); + retbytes.resize (length); + size_t bytesread = 0; + if (retbyteslen) *retbyteslen = 0; + HRESULT hr = istream.seek (offset, istreamstream::seekpos::start); + #ifdef _DEBUG + auto allsize = istream.size (); + #endif + if (FAILED (hr)) return hr; + hr = istream.read (retbytes.data (), length, &bytesread); + if (retbyteslen) *retbyteslen = bytesread; + if (cutbytesnoread) retbytes.resize (bytesread); + return hr; + } + } BYTE_SPAN; + typedef struct _CANDIDATE + { + WORD wQualifierSet = 0; + RES_VALUE_TYPE dwResType = RES_VALUE_TYPE::STRING; + REF_FILE_REF iSrcFileIndex = 0; + DATA_ITEM_REF iDataItem = 0; + BYTE_SPAN posData; + _CANDIDATE (WORD qualifierSet, RES_VALUE_TYPE resType, REF_FILE_REF srcFile, DATA_ITEM_REF dataItem): + wQualifierSet (qualifierSet), dwResType (resType), iSrcFileIndex (srcFile), iDataItem (dataItem), posData (0, 0, true) {} + _CANDIDATE (WORD qualifierSet, RES_VALUE_TYPE resType, BYTE_SPAN data): + wQualifierSet (qualifierSet), dwResType (resType), iSrcFileIndex (-1, true), iDataItem (-1, 0, true), posData (data) {} + _CANDIDATE (): iSrcFileIndex (-1, true), iDataItem (-1, 0, true), posData (0, 0, true) {} + // Ч᷵ nullptr + REF_FILE_REF *source_file_index () { return iSrcFileIndex.valid () ? &iSrcFileIndex : nullptr; } + // Ч᷵ nullptr + DATA_ITEM_REF *data_item_ref () { return iDataItem.valid () ? &iDataItem : nullptr; } + // Ч᷵ nullptr + BYTE_SPAN *data_position () { return posData.isnull ? nullptr : &posData; } + } CANDIDATE; + + // DataItem + + typedef struct _STORED_STRING_INFO + { + WORD wStringOffset = 0; // string offset, relative to start of stored data + WORD wStringLength = 0; // string length in bytes + } STORED_STRING_INFO; + typedef struct _STORED_BLOB_INFO + { + DWORD dwBlobOffset = 0; // blob offset, relative to start of stored data + DWORD dwBlobLength = 0; // blob length in bytes + } STORED_BLOB_INFO; + + // ReferencedFile + + typedef struct _REF_FOLDER_INFO + { + WORD wUnknown1 = -1; // unknown, zero + WORD wParentIndex = 0xFFFF; // index of parent folder, 0xFFFF if no parent exists (root) + WORD wFolderCount = 0; // number of folders in this folder + WORD wFirstFolderIndex = 0; // index of first folder in this folder + WORD wFileCount = 0; // number of files in this folder + WORD wFirstFileIndex = 0; // index of first file in this folder + WORD wFolderNameLength = 0; // length of folder name in characters + WORD wFolderFullPathLength = 0; // length of full folder path + DWORD dwFolderNameOffset = 0; // offset of folder name in Unicode name block + friend istreamstream &operator >> (istreamstream &i, _REF_FOLDER_INFO &reff) + { + return i >> + reff.wUnknown1 >> + reff.wParentIndex >> + reff.wFolderCount >> + reff.wFirstFolderIndex >> + reff.wFileCount >> + reff.wFirstFileIndex >> + reff.wFolderNameLength >> + reff.wFolderFullPathLength >> + reff.dwFolderNameOffset; + } + friend bytesstream &operator >> (bytesstream &i, _REF_FOLDER_INFO &reff) + { + return i >> + reff.wUnknown1 >> + reff.wParentIndex >> + reff.wFolderCount >> + reff.wFirstFolderIndex >> + reff.wFileCount >> + reff.wFirstFileIndex >> + reff.wFolderNameLength >> + reff.wFolderFullPathLength >> + reff.dwFolderNameOffset; + } + } REF_FOLDER_INFO; + typedef struct _REF_FILE_INFO + { + WORD wUnknown1 = 0; // unknown + WORD wParentIndex = 0; // index of parent folder + WORD wFileFullPathLength = 0; // length of full file path + WORD wFileNameLength = 0; // length of file name in characters + DWORD dwFileNameOffset = 0; // offset of file name in Unicode name block + friend istreamstream &operator >> (istreamstream &i, _REF_FILE_INFO &r) + { + return i >> r.wUnknown1 >> + r.wParentIndex >> + r.wFileFullPathLength >> + r.wFileNameLength >> + r.dwFileNameOffset; + } + friend bytesstream &operator >> (bytesstream &i, _REF_FILE_INFO &r) + { + return i >> r.wUnknown1 >> + r.wParentIndex >> + r.wFileFullPathLength >> + r.wFileNameLength >> + r.dwFileNameOffset; + } + } REF_FILE_INFO; + struct _REF_FOLDER; + typedef struct _REF_FILE_ENTRY + { + enum class ENTRYTYPE + { + UNKNOWN = 0, + FOLDER = 1, + FILE = 2 + }; + _REF_FOLDER *rfParent; + std::wstring swName = L""; + std::wstring fullname (); + std::wstring refresh_fullname () { swFullName.clear (); } + size_t path (std::vector &output); + ENTRYTYPE type () const { return eType; } + _REF_FILE_ENTRY (const std::wstring &name = L"", _REF_FOLDER *parent = nullptr, ENTRYTYPE type = ENTRYTYPE::UNKNOWN): + swName (name), eType (type), rfParent (parent) {} + protected: + std::wstring swFullName = L""; + ENTRYTYPE eType = ENTRYTYPE::UNKNOWN; + } REF_FILE_ENTRY; + typedef struct _REF_FOLDER: public _REF_FILE_ENTRY + { + std::vector vecChildrens; + _REF_FOLDER (const std::wstring &name = L"", _REF_FOLDER *parent = nullptr): + REF_FILE_ENTRY (name, parent, REF_FILE_ENTRY::ENTRYTYPE::FOLDER) {} + } REF_FOLDER; + std::wstring _REF_FILE_ENTRY::fullname () + { + if (std::wnstring::empty (swFullName)) + { + if (rfParent) swFullName = rfParent->fullname () + L"\\" + swName; + else swFullName = swName; + } + return swFullName; + } + size_t _REF_FILE_ENTRY::path (std::vector &output) + { + output.clear (); + if (rfParent) rfParent->path (output); + output.push_back (swName); + return output.size (); + } + typedef struct _REF_FILE: public REF_FILE_ENTRY + { + _REF_FILE (const std::wstring &name = L"", _REF_FOLDER *parent = nullptr): + REF_FILE_ENTRY (name, parent, REF_FILE_ENTRY::ENTRYTYPE::FILE) {} + } REF_FILE; + + // ReverseMap + + typedef struct _SCOPE_AND_ITEM_INFO + { + WORD wParent; + WORD wFullPathLength; + DWORD dwHashCode; + // ֱʹãʹ÷ name_offset + WORD wNameOffset; + WORD wIndex; + DWORD name_offset () const + { + return (DWORD)wNameOffset | (((dwHashCode >> 24) & 0xF) << 16); + } + bool name_in_ascii () const { return dwHashCode & 0x20000000; } + bool is_scope () const { return dwHashCode & 0x10000000; } + auto Item1 () const { return wParent; } + auto Item2 () const { return wFullPathLength; } + auto Item3 () const { return dwHashCode; } + auto Item4 () const { return name_offset (); } + auto Item5 () const { return wIndex; } + } SCOPE_AND_ITEM_INFO; + + struct basic_sect + { + section § + basic_sect (section &s): sect (s) {} + explicit operator section () { return sect; } + SectionType type () const { return sect.type (); } + }; + struct basic_sect_func + { + public: + virtual bool valid () = 0; + virtual void reset () = 0; + virtual bool parse () = 0; + }; +#define counter(_count_, _variable_) for (size_t _variable_ = 0, _counter_##_variable_##_total_ = _count_; _variable_ < _counter_##_variable_##_total_; _variable_ ++) + struct sect_pridesp: public basic_sect, public basic_sect_func + { + sect_pridesp (section &s): basic_sect (s) + { + if (s.type () != SectionType::PriDescriptor) throw std::exception ("Error: Section type error."); + parse (); + } + struct ContentStruct + { + WORD wFlags = 0; // ־ / flags + WORD wIncFileListIndex = -1; // ļбڣIncluded File ListΪ 0xFFFF + WORD wUnknown1 = -1; // δ֪0 + WORD wHieraScheCount = 0; // Hierarchical Schema + WORD wDecInfoCount = 0; // Decision Info + WORD wResMapCount = 0; // Resource Map + WORD wResMapBegIndex = -1; // Դӳ䣨primary resource mapĽ 0xFFFF + WORD wRefFileCount = 0; // Referenced File + WORD wDataItemCount = 0; // Data Item + WORD wUnknown2 = -1; // δ֪0 + ContentStruct () = default; + ContentStruct (WORD f, WORD inc, WORD unk1, WORD hiera, WORD dec, WORD res, WORD resBeg, WORD ref, WORD data, WORD unk2) + : wFlags (f), wIncFileListIndex (inc), wUnknown1 (unk1), wHieraScheCount (hiera), + wDecInfoCount (dec), wResMapCount (res), wResMapBegIndex (resBeg), wRefFileCount (ref), + wDataItemCount (data), wUnknown2 (unk2) {} + } content; + std::vector > vec_ref_hs; + std::vector > vec_ref_deci; + std::vector > vec_ref_rm; + std::vector > vec_ref_rf; + std::vector > vec_ref_dati; + ref_sect primary_resmap; + bool valid () { return content.wUnknown1 == 0 && content.wUnknown2 == 0; } + void reset () + { + vec_ref_hs.clear (); + vec_ref_deci.clear (); + vec_ref_rm.clear (); + vec_ref_rf.clear (); + vec_ref_dati.clear (); + primary_resmap.reset (); + content = {0, (WORD)-1, (WORD)-1, 0, 0, 0, (WORD)-1, 0, 0, (WORD)-1}; + } + bool parse () + { + reset (); + HRESULT hr = sect.childst.seek (); + istreamstream fp (sect.childst.ifile); + DWORD dwContent = 0; + hr = sect.childst.ifile->Read (&content, sizeof (content), &dwContent); + if (!valid ()) return false; + if (content.wResMapBegIndex != 0xFFFF) primary_resmap.index = content.wResMapBegIndex; + counter (content.wHieraScheCount, i) + { + vec_ref_hs.push_back (fp.read_bytes ()); + } + counter (content.wDecInfoCount, i) + { + vec_ref_deci.push_back (fp.read_bytes ()); + } + counter (content.wResMapCount, i) + { + vec_ref_rm.push_back (fp.read_bytes ()); + } + counter (content.wRefFileCount, i) + { + vec_ref_rf.push_back (fp.read_bytes ()); + } + counter (content.wDataItemCount, i) + { + vec_ref_dati.push_back (fp.read_bytes ()); + } + return true; + } + }; + struct sect_hierasche: public basic_sect, public basic_sect_func + { + struct + { + struct + { + WORD wUnknown1 = 0; // δ֪1 + WORD wUniqRMNameLen = 0; // ԴӳΨһȣַֹ + WORD wResMapNameLen = 0; // ԴӳƳȣַֹ + WORD wUnknown2 = -1; // unknown, zero + } part1; + struct + { + // hname ʶ extended ڣ + // hname identifier: only present in the extended Hierarchical Schema Section. + // Observed values are "[def_hnames] \0" and "[def_hnamesx] \0". + CHAR szHNameExt [16] = {0}; + } part2; + struct + { + HSCHEMA_VERSION_INFO verSchema; // λ schema 汾Ϣ + } part3; + struct + { + std::wstring swUniqueRMName = L""; // ԴӳΨһunique name + std::wstring swResMapName = L""; // name of resource map + WORD wUnknown3 = -1; // unknown, zero + WORD wMaxFullPathLength = 0; // length of longest full path of all resource names + WORD wUnknown3_5 = -1; // unknown, zero + DWORD dwResNameCount = 0; // number of resource names, usually number of scopes + items + DWORD dwScopeCount = 0; // number of scopes + DWORD dwItemsCount = 0; // number of items + DWORD dwUniNameLemgth = 0; // length of Unicode name block + DWORD dwUnknown4 = 0; // unknown + // unknown at 70 + ?: only present in the extended Hierarchical + // Schema Section and if hname identifier is "[def_hnamesx] \0". + DWORD dwUnknown5 = 0; + void *get_buf_first_dir () { return &wUnknown3; } + size_t get_buf_size_of () + { + return sizeof (wUnknown3) + sizeof (wMaxFullPathLength) + sizeof (wUnknown3_5) + + sizeof (dwScopeCount) + sizeof (dwItemsCount) + sizeof (dwResNameCount) + + sizeof (dwUniNameLemgth) + sizeof (dwUnknown4) + + sizeof (dwUnknown5); + } + } part4; + } content; + BOOL ex = FALSE; + BOOL exHName = FALSE; + std::vector vec_scope_and_items; + std::vector vec_scope_ex; + std::vector vec_item_index; + std::vector vec_scopes; + std::vector vec_items; + sect_hierasche (section &s): basic_sect (s) + { + if (s.type () != SectionType::HierarchicalSchema && s.type () != SectionType::HierarchicalSchemaEx) throw std::exception ("Error: Section type error."); + if (s.type () == SectionType::HierarchicalSchemaEx) ex = TRUE; + } + void throwexpect (const std::string &reason = "Error: unexpected value.") + { + throw std::exception (reason.c_str ()); + } + bool valid () + { + return content.part1.wUnknown1 == 1 && + content.part1.wUnknown2 == 0 && + content.part3.verSchema.dwUnknown1 == 0 && + content.part4.wUnknown3 == 0 && + content.part4.wUnknown3_5 == 0 && + content.part4.dwResNameCount == content.part3.verSchema.dwScopeCount + content.part3.verSchema.dwItemCount && + content.part4.dwScopeCount == content.part3.verSchema.dwScopeCount && + content.part4.dwItemsCount == content.part3.verSchema.dwItemCount && + content.part4.dwUniNameLemgth == content.part3.verSchema.dwItemCount && + content.part4.swUniqueRMName.length () == content.part1.wUniqRMNameLen && + content.part4.swResMapName.length () == content.part1.wResMapNameLen - 1; + } + void reset () + { + vec_scope_and_items.clear (); + vec_scope_ex.clear (); + vec_item_index.clear (); + vec_items.clear (); + vec_scopes.clear (); + ZeroMemory (&content.part1, sizeof (content.part1)); + content.part1.wUnknown2 = -1; + ZeroMemory (&content.part2, sizeof (content.part2)); + ZeroMemory (&content.part3, sizeof (content.part3)); + content.part3.verSchema.dwUnknown1 = -1; + content.part4.swUniqueRMName = L""; + content.part4.swResMapName = L""; + ZeroMemory (content.part4.get_buf_first_dir (), content.part4.get_buf_size_of ()); + content.part4.wUnknown3 = -1; + content.part4.wUnknown3_5 = -1; + } + bool parse () + { + reset (); + sect.childst.seek (); + istreamstream fp (sect.childst.ifile); + if (sect.childst.size == 0) return true; + auto &uniqueNameLength = content.part1.wUniqRMNameLen; + auto &nameLength = content.part1.wResMapNameLen; + fp >> content.part1; + if (content.part1.wUnknown1 != 1 || content.part1.wUnknown2 != 0) throwexpect (); + auto &extendedVersion = ex; + if (ex) + { + fp >> content.part2; + if (pri_sectid (content.part2.szHNameExt, 16).equals ("[def_hnamesx]")) exHName = true; + else if (pri_sectid (content.part2.szHNameExt, 16).equals ("[def_hnames]")) exHName = false; + else return false; + } + else exHName = false; + auto &extendedHNames = exHName; + auto &majorVersion = content.part3.verSchema.wMajor; + auto &minorVersion = content.part3.verSchema.wMinor; + auto &checksum = content.part3.verSchema.dwCheckSum; + auto &numScopes = content.part3.verSchema.dwScopeCount; + auto &numItems = content.part3.verSchema.dwItemCount; + fp >> content.part3; + if (content.part3.verSchema.dwUnknown1 != 0) throwexpect (); + auto &Version = content.part3.verSchema; + auto &UniqueName = content.part4.swUniqueRMName; + auto &Name = content.part4.swResMapName; + content.part4.swUniqueRMName = fp.read_string_endwith_null_w (); + content.part4.swResMapName = fp.read_string_endwith_null_w (); + fp.expect (0, true, &content.part4.wUnknown3); + auto &maxFullPathLength = content.part4.wMaxFullPathLength; + fp >> content.part4.wMaxFullPathLength; + fp.expect (0, true, &content.part4.wUnknown3_5); + fp.expect (numScopes + numItems, true, &content.part4.dwResNameCount); + fp.expect (numScopes, true, &content.part4.dwScopeCount); + fp.expect (numItems, true, &content.part4.dwItemsCount); + auto &unicodeDataLength = content.part4.dwUniNameLemgth; + fp >> unicodeDataLength; + fp >> content.part4.dwUnknown4; + if (extendedHNames) fp >> content.part4.dwUnknown5; + auto &scopeAndItemInfos = vec_scope_and_items; + scopeAndItemInfos.resize (content.part4.dwResNameCount); + fp.read (scopeAndItemInfos.data (), (content.part4.dwResNameCount) * sizeof (SCOPE_ITEM_INFO)); + auto &scopeExInfos = vec_scope_ex; + scopeExInfos.resize (numScopes); + fp.read (scopeExInfos.data (), numScopes * sizeof (SCOPE_EX_INFO)); + auto &itemIndexPropertyToIndex = vec_item_index; + itemIndexPropertyToIndex.resize (numItems); + fp.read (itemIndexPropertyToIndex.data (), sizeof (ITEM_INDEX) * numItems); + auto unicodeDataOffset = fp.position (); + auto asciiDataOffset = fp.position () + unicodeDataOffset * 2; + auto &scopes = vec_scopes; + auto &items = vec_items; + scopes.resize (numScopes); + items.resize (numItems); + counter (content.part4.dwResNameCount, i) + { + istreamstream::fsize_t pos = 0; + if (scopeAndItemInfos [i].name_in_ascii ()) + pos = asciiDataOffset + scopeAndItemInfos [i].name_offset (); + else pos = unicodeDataOffset + scopeAndItemInfos [i].name_offset () * 2; + fp.seek (pos, istreamstream::seekpos::start); + std::wstring name; + if (scopeAndItemInfos [i].wFullPathLength != 0) + { + if (scopeAndItemInfos [i].name_in_ascii ()) + name = StringToWString (fp.read_string_endwith_null_a ()); + else name = fp.read_string_endwith_null_w (); + } + ITEM_INDEX index = scopeAndItemInfos [i].index (); + if (scopeAndItemInfos [i].is_scope ()) + { + if (!scopes [index].bIsNull) throwexpect (); + else scopes.at (index) = RES_MAP_SCOPE (index, nullptr, name, false); + } + else + { + if (!items [index].bIsNull) throwexpect (); + else items.at (index) = RES_MAP_ITEM (index, nullptr, name, false); + } + } + counter (content.part4.dwResNameCount, i) + { + ITEM_INDEX index = scopeAndItemInfos [i].index (); + WORD parent = scopeAndItemInfos [scopeAndItemInfos [i].wParentScopeIndex].index (); + if (parent != 0xFFFF) + { + if (scopeAndItemInfos [i].is_scope ()) + { + if (parent != index) scopes.at (index).pParent = &scopes [parent]; + } + else items.at (index).pParent = &scopes [parent]; + } + } + counter (numScopes, i) + { + auto &scope = scopes [i]; + auto &children = scope.vecChild; + counter (scopeExInfos [i].wChildCount, j) + { + auto &saiInfo = scopeAndItemInfos [scopeExInfos [i].wFirstChild + j]; + if (saiInfo.is_scope ()) children.push_back (&scopes.at (saiInfo.index ())); + else children.push_back (&items.at (saiInfo.index ())); + } + } + return valid (); + } + }; + struct sect_decinfo: public basic_sect, public basic_sect_func + { + sect_decinfo (section &s): basic_sect (s) + { + if (s.type () != SectionType::DecisionInfo) throw std::exception ("Error: Section type error."); + } + struct + { + WORD wDistQualiCount = 0; // ͬ distinct qualifiers / number of distinct qualifiers + WORD wQualifierCount = 0; // qualifiers + WORD wQualSetsCount = 0; // qualifier sets + WORD wDecisionCount = 0; // decisions / number of decisions + WORD wEntriesCount = 0; // index table Ŀ / number of entries in the index table + WORD wQualiValueLength = 0; // qualifier value block ȣַ / length of qualifier value block in characters + } content; + std::vector vec_qua_set; + std::vector vec_qua; + std::vector vec_dec; + bool valid () + { + return content.wDecisionCount || + content.wQualifierCount || + content.wDistQualiCount || + content.wQualSetsCount || + content.wEntriesCount || + content.wQualiValueLength || + vec_qua.size () || + vec_qua_set.size () || + vec_dec.size () || + 0; + } + void reset () + { + vec_qua.clear (); + vec_qua_set.clear (); + vec_dec.clear (); + ZeroMemory (&content, sizeof (content)); + } + bool parse () + { + reset (); + istreamstream fp (sect.childst.ifile); + sect.childst.seek (); + auto &numDistinctQualifiers = content.wDistQualiCount; + auto &numQualifiers = content.wQualifierCount; + auto &numQualifierSets = content.wQualSetsCount; + auto &numDecisions = content.wDecisionCount; + auto &numIndexTableEntries = content.wEntriesCount; + auto &totalDataLength = content.wQualiValueLength; + fp >> content; + std::vector decisionInfos (numDecisions); + std::vector qualifierSetInfos (numQualifierSets); + std::vector qualifierInfos (numQualifiers); + std::vector distinctQualifierInfos (numDistinctQualifiers); + std::vector indexTable (numIndexTableEntries); + auto &vec_dis_qua_info = distinctQualifierInfos; + auto &vec_qua_info = qualifierInfos; + auto &vec_qua_set_info = qualifierSetInfos; + auto &indexs = indexTable; + auto &vec_dec_info = decisionInfos; + fp.read (decisionInfos.data (), sizeof (DECISION_INFO) * numDecisions); + fp.read (qualifierSetInfos.data (), sizeof (DECISION_INFO) * numQualifierSets); + fp.read (qualifierInfos.data (), sizeof (QUALIFIER_INFO) * numQualifiers); + for (auto &it : qualifierInfos) { if (it.wUnknown1 != 0) throw std::exception ("Error: unexpective value."); } + fp.read (distinctQualifierInfos.data (), sizeof (DISTINCE_QUALIFIER_INFO) * numDistinctQualifiers); + fp.read (indexTable.data (), sizeof (ITEM_INDEX) * numIndexTableEntries); + auto currentpos = fp.position (); + std::vector buf (128); + fp.read (buf.data (), 128 * sizeof (WCHAR)); + fp.seek (currentpos, istreamstream::seekpos::start); + counter (content.wQualifierCount, i) + { + auto &dinfo = vec_dis_qua_info [vec_qua_info [i].wDistQualiIndex]; + auto &qinfo = vec_qua_info [i]; + fp.seek (currentpos + dinfo.wQualiValueOffset * 2, istreamstream::seekpos::start); + std::wstring value = fp.read_string_endwith_null_w (); + QUALIFIER qual (i, (QUALIFIER_TYPE)dinfo.wQualiType, qinfo.wPriority, (double)qinfo.wFallbackScore * 0.001, value); + vec_qua.push_back (qual); + } + counter (content.wQualSetsCount, i) + { + std::vector quals; + auto qset = vec_qua_set_info [i]; + counter (qset.wQualiSetsCount, j) + { + auto &ind = indexs [qset.wFirstQualiIndex + j]; + auto &qual = vec_qua [ind]; + quals.push_back (qual); + } + vec_qua_set.emplace_back (QUALIFIER_SET (i, quals)); + } + counter (content.wDecisionCount, i) + { + auto &dec = vec_dec_info [i]; + std::vector qsets; + counter (dec.wQualiSetsCount, j) + { + auto &ind = indexs [dec.wFirstQualiIndex + j]; + auto &qset = vec_qua_set.at (ind); + qsets.emplace_back (qset); + } + vec_dec.emplace_back (DECISION (i, qsets)); + } + return valid (); + } + }; + struct sect_resmap: public basic_sect, public basic_sect_func + { + sect_resmap (section &s): basic_sect (s) + { + if (s.type () != SectionType::ResourceMap && s.type () != SectionType::ResourceMap2) throw std::exception ("Error: Section type error."); + if (s.type () == SectionType::ResourceMap2) ver2 = true; + else ver2 = false; + } + struct + { + WORD wEnvRefLength = 0; // length of environment references block // environmentReferencesLength + WORD wRefCount = 0; // number of references in environment references block // numEnvironmentReferences + WORD wHSSectIndex = 0; // section index of Hierarchical Schema Section // SchemaSection + WORD wHSRefLength = 0; // length of hierarchical schema reference block // hierarchicalSchemaReferenceLength + WORD wDecInfSectIndex = 0; // section index of Decision Info Section // DecisionInfoSection + WORD wResTypeEntCount = 0; // number of entries in resource value type table // resourceValueTypeTableSize + WORD wItemEntCount = 0; // number of entries in item to iteminfo group table // ItemToItemInfoGroupCount + WORD wItemGroupEntCount = 0; // number of entries in iteminfo group table // itemInfoGroupCount + DWORD dwItemTableEntCount = 0; // number of entries in iteminfo table // itemInfoCount + DWORD dwCandidateCount = 0; // number of candidates // numCandidates + DWORD dwEmbededDataCount = 0; // length of embedded data bloc // dataLength + DWORD dwTableExtCount = 0; // length of table extension block // largeTableLength + } content; + BOOL ver2 = FALSE; + std::vector bvecEnvRefData; + std::vector bvecScheRefData; + HSCHEMA_REF_BLOCK hschema_ref; + std::vector vecResTypes; + std::vector vecItemToItemInfoGroup; + std::vector vecItemInfoGroups; + std::vector vecItemInfo; + std::vector vecCandidateInfo; + std::map mapCandidateSet; + bool valid () + { + if (parseError) return false; + UINT64 *p = (UINT64 *)&content; + bool res = false; + res = (!ver2) + ? (content.wEnvRefLength != 0 && content.wRefCount != 0) + : (content.wEnvRefLength == 0 && content.wRefCount == 0); + if (!res) return false; + if (content.wHSRefLength != 0) + { + if (hschema_ref.verHschema.dwUnknown1 != 0) return false; + if (hschema_ref.part2.wUnknown1 != 0) return false; + } + return res; + } + void reset () + { + parseError = false; + bvecEnvRefData.clear (); + bvecScheRefData.clear (); + vecResTypes.clear (); + vecItemToItemInfoGroup.clear (); + vecItemInfoGroups.clear (); + vecItemInfo.clear (); + vecCandidateInfo.clear (); + UINT64 *p = (UINT64 *)&content; + bool res = false; + size_t len = sizeof (content) / sizeof (UINT64); + for (size_t i = 0; i < len; i ++) p [i] = 0; + hschema_ref = HSCHEMA_REF_BLOCK (); + } + bool parse (); + private: + BOOL parseError = false; + }; + struct sect_dataitem: public basic_sect + { + sect_dataitem (section &s): basic_sect (s) + { + if (s.type () != SectionType::DataItem) throw std::exception ("Error: Section type error."); + } + struct + { + DWORD dwUnknown1 = -1; // unknown, zero + WORD wStrCount = 0; // number of stored strings + WORD wBlobCount = 0; // number of stored blobs + DWORD dwStoredLength = 0; // total length of stored data + } content; + std::vector vecDataItems; + bool valid () + { + return content.dwUnknown1 == 0; + } + void reset () + { + vecDataItems.clear (); + ZeroMemory (&content, sizeof (content)); + content.dwUnknown1 = -1; + } + bool parse () + { + reset (); + sect.childst.seek (); + istreamstream fp (sect.childst.ifile); + auto sectionPosition = fp.position (); + fp >> content; + std::vector &dataItems = vecDataItems; + istreamstream::fsize_t dataStartOffset = + fp.position () + + content.wStrCount * 2 * sizeof (WORD) + + content.wBlobCount * 2 * sizeof (DWORD); + dataItems.reserve (content.wStrCount + content.wBlobCount); + std::vector storedStringInfos (content.wStrCount); + std::vector storedBlobInfo (content.wBlobCount); + fp.read (storedStringInfos.data (), sizeof (STORED_STRING_INFO) * content.wStrCount); + fp.read (storedBlobInfo.data (), sizeof (STORED_BLOB_INFO) * content.wBlobCount); + size_t cnt = 0; + counter (content.wStrCount, i) + { + auto &sstr = storedStringInfos.at (i); + dataItems.push_back (BYTE_SPAN (dataStartOffset + sstr.wStringOffset, sstr.wStringLength)); + } + counter (content.wBlobCount, i) + { + auto &sblo = storedBlobInfo.at (i); + dataItems.push_back (BYTE_SPAN (dataStartOffset + sblo.dwBlobOffset, sblo.dwBlobLength)); + } + return valid (); + } + }; + struct sect_reffile: public basic_sect + { + sect_reffile (section &s): basic_sect (s) + { + if (s.type () != SectionType::ReferencedFile) throw std::exception ("Error: Section type error."); + } + struct + { + WORD wRootCount = 0; // number of roots + WORD wFolderCount = 0; // number of folders + WORD wFileCount = 0; // number of folders + WORD wUnknown1 = -1; // unknown, zero + DWORD dwNameLength = 0; // length of Unicode name block in characters + } content; + std::vector vecFolderInfo; + std::vector vecFileInfo; + std::vector vecRefFolders; + std::vector vecRefFiles; + bool valid () + { + return content.wUnknown1 == 0; + } + void reset () + { + vecFolderInfo.clear (); + vecFileInfo.clear (); + vecRefFiles.clear (); + vecRefFolders.clear (); + ZeroMemory (&content, sizeof (content)); + content.wUnknown1 = -1; + } + bool parse () + { + reset (); + sect.childst.seek (); + istreamstream fp (sect.childst.ifile); + fp >> content; + counter (content.wFolderCount, i) + { + REF_FOLDER_INFO folder; + fp >> folder; + if (folder.wUnknown1 != 0) throw std::exception ("Error: cannot get valid data in ReferencedFile Section."); + vecFolderInfo.push_back (folder); + } + counter (content.wFileCount, i) + { + REF_FILE_INFO file; + fp >> file; + vecFileInfo.push_back (file); + } + auto dataStartPosition = fp.position (); + auto &referencedFolders = vecRefFolders; + using seekpos = istreamstream::seekpos; + counter (content.wFolderCount, i) + { + fp.seek (dataStartPosition + vecFolderInfo [i].dwFolderNameOffset * 2, seekpos::start); + std::wstring name = fp.read_string_w (vecFolderInfo [i].wFolderNameLength); + referencedFolders.push_back (REF_FOLDER (name)); + } + counter (content.wFolderCount, i) + { + if (vecFolderInfo [i].wParentIndex != 0xFFFF) + { + referencedFolders [i].rfParent = &referencedFolders [vecFolderInfo [i].wParentIndex]; + } + } + counter (content.wFileCount, i) + { + REF_FILE file; + auto &fileInfo = vecFileInfo [i]; + fp.seek (dataStartPosition + fileInfo.dwFileNameOffset * 2, seekpos::start); + std::wstring name = fp.read_string_w (fileInfo.wFileNameLength); + file.swName = name; + REF_FOLDER *parent = nullptr; + if (vecFileInfo [i].wParentIndex != 0xFFFF) parent = &referencedFolders [fileInfo.wParentIndex]; + file.rfParent = parent; + vecRefFiles.push_back (file); + } + counter (content.wFolderCount, i) + { + auto &folderInfo = vecFolderInfo [i]; + auto &referencedFolder = referencedFolders [i]; + counter (folderInfo.wFolderCount, j) + { + auto &folder = referencedFolders [folderInfo.wFirstFolderIndex + j]; + referencedFolder.vecChildrens.push_back (&folder); + } + counter (folderInfo.wFileCount, j) + { + auto &file = vecRefFiles [folderInfo.wFirstFileIndex + j]; + referencedFolder.vecChildrens.push_back (&file); + } + } + return valid (); + } + }; + struct sect_revmap: public basic_sect, public basic_sect_func + { + sect_revmap (section &s): basic_sect (s) + { + if (s.type () != SectionType::ReverseMap) throw std::exception ("Error: Section type error."); + } + struct + { + struct + { + DWORD dwItemsNumber = 0; + DWORD dwCheckCode = 0; + } part1; + struct + { + WORD wFullPathLength = 0; + WORD wUnknown1 = -1; // 0 + DWORD dwEntries = 0; + DWORD dwScopes = 0; + DWORD dwCheckItemsNumber = 0; + DWORD dwUnicodeDataLength = 0; + DWORD dwSkipPadding = 0; + } part2; + } content; + std::vector adwMap; + std::vector aobjScopeAndItem; + std::vector aobjScopeExts; + std::vector awItemIndexs; + std::vector aobjScopes; + std::vector aobjItems; + bool valid () + { + bool res = content.part2.wUnknown1 == 0 && + content.part2.dwCheckItemsNumber == content.part1.dwItemsNumber || + 0; + return res; + } + void reset () + { + adwMap.clear (); + aobjScopeAndItem.clear (); + aobjScopeExts.clear (); + awItemIndexs.clear (); + aobjScopes.clear (); + aobjItems.clear (); + ZeroMemory (&content.part1, sizeof (content.part1)); + ZeroMemory (&content.part2, sizeof (content.part2)); + content.part2.wUnknown1 - 1; + } + bool parse () + { + reset (); + sect.childst.seek (); + istreamstream fp (sect.childst.ifile); + fp >> content.part1; + bool chk = content.part1.dwCheckCode == fp.size () - 8; + if (!chk) return false; + adwMap.resize (content.part1.dwItemsNumber); + fp.read (adwMap.data (), sizeof (DWORD) * content.part1.dwItemsNumber); + fp >> content.part2; + chk = content.part2.wUnknown1 == 0 && content.part2.dwCheckItemsNumber == content.part1.dwItemsNumber; + if (!chk) return false; + counter (content.part2.dwScopes + content.part1.dwItemsNumber, i) + { + SCOPE_AND_ITEM_INFO sii; + fp >> sii; + aobjScopeAndItem.push_back (sii); + } + counter (content.part2.dwScopes, i) + { + SCOPE_EX_INFO sei; + fp >> sei; + if (sei.wUnknown1 != 0) throw std::exception ("Error: read invalid data in ReverseMap Section."); + aobjScopeExts.push_back (sei); + } + awItemIndexs.resize (content.part1.dwItemsNumber); + fp.read (awItemIndexs.data (), content.part1.dwItemsNumber * sizeof (ITEM_INDEX)); + auto unicodeDataOffset = fp.position (), + asciiDataOffset = fp.position () + content.part2.dwUnicodeDataLength * 2; + aobjScopes.resize (content.part2.dwScopes); + aobjItems.resize (content.part1.dwItemsNumber); + counter (content.part1.dwItemsNumber + content.part2.dwScopes, i) + { + auto &sii = aobjScopeAndItem [i]; + bool nameInAscii = sii.name_in_ascii (); + UINT64 pos = (nameInAscii ? asciiDataOffset : unicodeDataOffset) + (sii.Item4 () * (nameInAscii ? 1 : 2)); + fp.seek (pos, istreamstream::seekpos::start); + std::wstring name; + if (sii.Item2 ()) + { + if (nameInAscii) name = StringToWString (fp.read_string_endwith_null_a ()); + else name = fp.read_string_endwith_null_w (); + } + auto index = sii.Item5 (); + bool isScope = sii.is_scope (); + if (isScope) + { + auto &it = aobjScopes.at (index); + if (!it.bIsNull) throw std::exception ("Error: invalid scope data in ReverseMap Section."); + else it = RES_MAP_SCOPE (index, nullptr, name); + } + else + { + auto &it = aobjItems.at (index); + if (!it.bIsNull) throw std::exception ("Error: invalid item data in ReverseMap Section."); + else it = RES_MAP_ITEM (index, nullptr, name); + } + } + counter (content.part1.dwItemsNumber + content.part2.dwScopes, i) + { + auto &sii = aobjScopeAndItem [i]; + auto index = sii.Item5 (); + bool isScope = sii.is_scope (); + auto parent = aobjScopeAndItem [sii.Item1 ()].Item5 (); + if (parent != 0xFFFF) + { + if (isScope && parent != index) + { + auto &it = aobjScopes.at (index); + it.pParent = &aobjScopes.at (parent); + } + else + { + auto &it = aobjItems.at (index); + it.pParent = &aobjScopes.at (parent); + } + } + + } + counter (content.part2.dwScopes, i) + { + auto &sei = aobjScopeExts [i]; + auto &scope = aobjScopes [i]; + counter (sei.wChildCount, j) + { + auto &saiInfo = aobjScopeAndItem [sei.wFirstChild + j]; + bool isScope = saiInfo.is_scope (); + if (isScope) + { + auto &prt = aobjScopes [saiInfo.Item5 ()]; + scope.vecChild.push_back (&prt); + } + else + { + auto &prt = aobjItems [saiInfo.Item5 ()]; + scope.vecChild.push_back (&prt); + } + } + } + return true; + } + }; + struct sect_unknown: public basic_sect + { + sect_unknown (section &s): basic_sect (s) {} + UINT64 dwLength = 0; + void reset () + { + dwLength = 0; + } + bool parse () + { + reset (); + sect.childst.seek (); + istreamstream fp (sect.childst.ifile); + dwLength = fp.size () - fp.position (); + return true; + } + size_t bytes (std::vector &bytes) + { + sect.childst.seek (); + istreamstream fp (sect.childst.ifile); + bytes.resize (dwLength); + size_t readlen = 0; + fp.read (bytes.data (), dwLength, &readlen); + bytes.resize (readlen > dwLength ? dwLength : readlen); + return readlen; + } + }; +} +class prifile +{ + private: + IStream *pfile = nullptr; + head header; foot footer; + std::vector toclist; + std::vector
sectlist; + enum class searchtype + { + unknown, + string, + file + }; + struct search_key + { + std::wnstring key = L""; + searchtype type = searchtype::unknown; + search_key (const std::wstring &k = L"", searchtype t = searchtype::unknown): + key (k), type (t) {} + bool operator == (const search_key &another) const { return key == another.key && type == another.type; } + bool operator == (const std::wstring &an_key) const { return key.equals (an_key); } + operator searchtype () const { return type; } + operator LPCWSTR () const { return key.c_str (); } + }; + struct search_value + { + std::wstring value = L""; + bool isfind = false; + int begindex = -1; + bool finishsearch = false; + operator LPCWSTR () const { return value.c_str (); } + operator bool () const { return isfind; } + }; + std::map vecTaskSearch; + bool isrunningtask = false; + public: + void close () + { + header = head (); + footer = foot (); + toclist.clear (); + sectlist.clear (); + } + bool load (IStream *ifile) + { + close (); + if (!ifile) return false; + ifile->Seek (lint (0), STREAM_SEEK_SET, nullptr); + DWORD dwhead = 0, dwfoot = 0; + ifile->Read (&header, sizeof (header), &dwhead); + if (!dwhead) return false; + if (!header.valid ()) return false; + ifile->Seek (lint (header.dwFileSize - 16), STREAM_SEEK_SET, nullptr); + ifile->Read (&footer, sizeof (footer), &dwfoot); + if (!dwfoot) return false; + if (!footer.valid (header)) return false; + pfile = ifile; + inittoc (); + initsect (); + return true; + } + operator istreamstream () { return istreamstream (pfile); } + pri::sect_pridesp section_pri_descriptor () + { + for (auto &it : sectlist) + { + if (it.type () == SectionType::PriDescriptor) + { + try + { + auto sect = pri::sect_pridesp (it); + sect.parse (); + return sect; + } + catch (const std::exception &e) { continue; } + } + } + throw std::exception ("Error: cannot get the pri descriptor section."); + } + section &get_section_by_ref (int index) + { + return sectlist.at (index); + } + template SectionT get_section_by_ref (pri::ref_sect ref) + { + return SectionT (sectlist.at (ref.index)); + } + void inittoc () + { + toclist.clear (); + pfile->Seek (lint (header.dwToCOffset), STREAM_SEEK_SET, nullptr); + for (size_t i = 0; i < header.wSectCount; i ++) + { + tocentry toc; + DWORD dwRead; + pfile->Read (&toc, sizeof (toc), &dwRead); + toclist.push_back (toc); + } + } + void initsect () + { + sectlist.clear (); + istreamstream iss (pfile); + for (size_t i = 0; i < header.wSectCount; i ++) + { + iss.seek (header.dwSectStartOffset + toclist [i].dwSectOffset, istreamstream::seekpos::start); + section sect (this->pfile, *this); + iss.read (sect.head); + iss.seek (sect.head.dwLength - 16 - 24); + iss.read (sect.foot); + iss.seek (header.dwSectStartOffset + toclist [i].dwSectOffset, istreamstream::seekpos::start); + iss.seek (32); + sect.childst.set (iss.position (), sect.head.dwLength - 16 - 24); + sectlist.push_back (sect); + } + } + // ÷ + // auto rmsect = this->get_resmap_sect_by_ref (candidateSet.refResMapItem); + // rmsect.parse (); + // auto item = rmsect.vec_items [candidateSet.refResMapItem.iItemIndex]; + auto get_resmap_sect_by_ref (pri::RES_MAP_ITEM_REF resourceMapItemRef) + { + return get_section_by_ref (resourceMapItemRef.wSchemaSect); + } + // ÷ + // auto ds = get_dataitem_sect_by_ref (dataItemRef); + // ds.parse (); + // ds.vecDataItems.at (dataItemRef.get ()); + auto get_dataitem_sect_by_ref (pri::DATA_ITEM_REF dataItemRef) + { + return get_section_by_ref (dataItemRef.iDataSectIndex); + } + bool get_reffile_by_ref (pri::REF_FILE_REF referencedFileRef, std::function callback) + { + try + { + auto sect = get_section_by_ref (section_pri_descriptor ().vec_ref_rf.front ()); + auto &rf = sect.vecRefFiles.at (referencedFileRef); + if (callback) callback (rf); + return true; + } + catch (const std::exception &e) + { + return false; + } + } + void end_taskrunning () { isrunningtask = false; } + static void search_task (prifile &priinst) + { + destruct ([&priinst] () { + priinst.end_taskrunning (); + }); + if (priinst.isrunningtask) return; + else priinst.isrunningtask = true; + auto &tasklist = priinst.vecTaskSearch; + + } + void across_all (std::wostream &out) + { + #ifdef _CONSOLE + struct loadingamine + { + const WCHAR *charcollect = L"-\\|/-\\|/"; + WCHAR nowchar = L' '; + bool isend = false; + bool enablecallback = true; + std::function callback = nullptr; + void exectask () + { + size_t cnt = 0; + size_t charlen = lstrlenW (charcollect); + std::function cb = callback; + while (!isend) + { + nowchar = charcollect [(cnt ++) % charlen]; + if (cb && enablecallback) cb (nowchar); + std::this_thread::sleep_for (std::chrono::milliseconds (300)); + } + } + void run () + { + std::thread th (&loadingamine::exectask, this); + th.detach (); + } + void jump () { isend = true; } + } loadchar; + destruct endt ([&loadchar] () { + loadchar.isend = true; + }); + std::wcout << L" 0 %"; + loadchar.callback = [] (const WCHAR &wch) { + wprintf (L"\r %c 0 %%", wch); + }; + loadchar.run (); + auto DiffSystemTimeMs = [] (const SYSTEMTIME &st1, const SYSTEMTIME &st2) -> LONGLONG + { + FILETIME ft1, ft2; + ULARGE_INTEGER t1, t2; + SystemTimeToFileTime (&st1, &ft1); + SystemTimeToFileTime (&st2, &ft2); + + t1.LowPart = ft1.dwLowDateTime; + t1.HighPart = ft1.dwHighDateTime; + t2.LowPart = ft2.dwLowDateTime; + t2.HighPart = ft2.dwHighDateTime; + + // FILETIME λ 100 루1 = 10,000,000 + LONGLONG diff100ns = t2.QuadPart - t1.QuadPart; + + // תΪ + return diff100ns / 10000; // 1 = 10,000 * 100ns + }; + out << L"Read Start: "; + WCHAR buf [64]; + SYSTEMTIME st_start; + GetLocalTime (&st_start); + swprintf (buf, 64, L"%4d.%02d.%02d %02d:%02d:%02d", + st_start.wYear, st_start.wMonth, st_start.wDay, + st_start.wHour, st_start.wMinute, st_start.wSecond); + out << buf << std::endl; + out << L"Sections: " << sectlist.size () << std::endl; + for (auto &it : sectlist) + { + out << L" " << it << std::endl; + } + out << std::endl; + auto pri_desp = this->section_pri_descriptor (); + loadchar.callback = nullptr; + loadchar.enablecallback = false; + out << L"Candidates: " << pri_desp.vec_ref_rm.size () << std::endl; + size_t cnt_i = 0; + for (auto &it : pri_desp.vec_ref_rm) + { + auto resmap_sect = this->get_section_by_ref (it); + resmap_sect.parse (); + if (!resmap_sect.hschema_ref.empty ()) continue; + auto decisionInfoSection = get_section_by_ref (resmap_sect.content.wDecInfSectIndex); + decisionInfoSection.parse (); + size_t cnt_j = 0; + for (auto &it_cs : resmap_sect.mapCandidateSet) + { + auto &candidateSet = it_cs.second; + auto rmsect = this->get_resmap_sect_by_ref (candidateSet.refResMapItem); + rmsect.parse (); + auto item = rmsect.vec_items [candidateSet.refResMapItem.iItemIndex]; + out << L" " << item.full_name () << std::endl; + size_t cnt_k = 0; + for (auto &candidate : candidateSet.vecCandidates) + { + std::wstring value; + if (candidate.source_file_index ()) + { + auto temp = get_reffile_by_ref (*candidate.source_file_index (), [&value] (pri::REF_FILE &rf) { + value += L""; + }); + } + else + { + pri::BYTE_SPAN byteSpan; + if (candidate.data_item_ref ()) + { + auto dis = this->get_dataitem_sect_by_ref (*candidate.data_item_ref ()); + dis.parse (); + byteSpan = dis.vecDataItems.at (candidate.data_item_ref ()->get ()); + } + else + { + if (candidate.data_position ()) byteSpan = *candidate.data_position (); + else byteSpan.isnull = true; + } + std::vector bytes (byteSpan.length + 2); + size_t ret; + HRESULT hr = byteSpan.get_bytes (pfile, bytes, &ret); + bytes.resize (bytes.size () + 2); + using restype = pri::RES_VALUE_TYPE; + switch (candidate.dwResType) + { + case restype::ASCIIPATH: + case restype::ASCIISTRING: + value += StringToWString ((CHAR *)bytes.data ()); break; + case restype::UTF8PATH: + case restype::UTF8STRING: + value += StringToWString ((CHAR *)bytes.data (), CP_UTF8); break; + case restype::STRING: + case restype::PATH: + value += (WCHAR *)bytes.data (); break; + case restype::EMBEDDEDDATA: + value += L"<" + std::to_wstring (ret) + L" bytes>"; break; + } + } + auto qualifierSet = decisionInfoSection.vec_qua_set [candidate.wQualifierSet]; + std::wstring qualifiers; + for (auto qual : qualifierSet.vecQuals) + { + std::wstring str = L" "; + str += EnumToStringW (qual.eType) + L" = " + qual.swValue + L"\n"; + qualifiers += str; + } + out << L" Value {" << value << L"}" << std::endl; + out << qualifiers; + double progress = (double)((cnt_i + (cnt_j + (double)cnt_k / candidateSet.vecCandidates.size ()) / resmap_sect.mapCandidateSet.size ()) / pri_desp.vec_ref_rm.size () * 100.0); + std::wcout << L"\r " + << loadchar.nowchar + << L" " + << std::fixed << std::setprecision (2) + << progress + << L" % [(" + << cnt_k + << L" / " + << candidateSet.vecCandidates.size () + << L") of (" + << cnt_j + << L" / " + << resmap_sect.mapCandidateSet.size () + << L") of (" + << cnt_i + << L" / " + << pri_desp.vec_ref_rm.size () + << L")]" + << L" " + ; + cnt_k ++; + } + cnt_j ++; + } + int i = 0; + cnt_i ++; + } + int j = 0; + std::wcout << L"\r 100 % " << std::endl; + out << L"Read Completed: "; + SYSTEMTIME st_end; + GetLocalTime (&st_end); + ZeroMemory (buf, 60 * sizeof (WCHAR)); + swprintf (buf, 64, L"%4d.%02d.%02d %02d:%02d:%02d", + st_end.wYear, st_end.wMonth, st_end.wDay, + st_end.wHour, st_end.wMinute, st_end.wSecond); + out << buf << std::endl; + out << L"Time Spend: " << DiffSystemTimeMs (st_start, st_end) * 0.001 << L"s" << std::endl; + #endif + } +}; +bool pri::sect_resmap::parse () + +{ + reset (); + istreamstream fp (sect.childst.ifile); + sect.childst.seek (); + ulint sectpos = 0; + fp->Seek (lint (0), STREAM_SEEK_CUR, sectpos.ptr_union ()); + fp->Read (&content, sizeof (content), nullptr); + bool res = (!ver2) ? (content.wEnvRefLength != 0 && content.wRefCount != 0) : (content.wEnvRefLength == 0 && content.wRefCount == 0); + if (!res) return false; + { + auto currpos = fp.position (); + try + { + auto dest = sect.pri_file.get_section_by_ref (ref_sect (content.wDecInfSectIndex)); + dest.parse (); + } + catch (const std::exception &e) + { + parseError = true; + return false; + } + fp.seek (currpos, istreamstream::seekpos::start); + } + bvecEnvRefData.resize (content.wEnvRefLength); + bvecScheRefData.resize (content.wHSRefLength); + ZeroMemory (bvecEnvRefData.data (), sizeof (BYTE) * content.wEnvRefLength); + ZeroMemory (bvecScheRefData.data (), sizeof (BYTE) * content.wHSRefLength); + fp->Read (bvecEnvRefData.data (), sizeof (BYTE) * content.wEnvRefLength, nullptr); + fp->Read (bvecScheRefData.data (), sizeof (BYTE) * content.wHSRefLength, nullptr); + if (content.wHSRefLength != 0) + { + bytesstream srdata (bvecScheRefData); + srdata.read (&hschema_ref.verHschema, sizeof (hschema_ref.verHschema)); + if (hschema_ref.verHschema.dwUnknown1 != 0) return false; + srdata.read (&hschema_ref.part2, sizeof (hschema_ref.part2)); + if (hschema_ref.part2.wUnknown1 != 0) return false; + hschema_ref.swUniqueId = ReadStringEndwithNullW (fp); + } + for (size_t i = 0; i < content.wResTypeEntCount; i ++) + { + RES_VALUE_TYPE_TABLE rvtt; + fp->Read (&rvtt, sizeof (rvtt), nullptr); + if (rvtt.dwUnknown1 != 4) return false; + vecResTypes.push_back ((RES_VALUE_TYPE)rvtt.dwResType); + } + for (size_t i = 0; i < content.wItemEntCount; i ++) + { + ITEM_ITEMINFO_GROUP_TABLE_ENTRY iigte; + fp->Read (&iigte, sizeof (iigte), nullptr); + vecItemToItemInfoGroup.push_back (iigte); + } + for (size_t i = 0; i < content.wItemGroupEntCount; i ++) + { + ITEMINFO_GROUP_TABLE_ENTRY iigte; + fp->Read (&iigte, sizeof (iigte), nullptr); + vecItemInfoGroups.push_back (iigte); + } + for (size_t i = 0; i < content.dwItemTableEntCount; i ++) + { + ITEM_ITEMINFO_TABLE_ENTRY iite; + fp->Read (&iite, sizeof (iite), nullptr); + vecItemInfo.push_back (iite); + } + std::vector largeTable (content.dwTableExtCount); + fp->Read (largeTable.data (), sizeof (BYTE) * content.dwTableExtCount, nullptr); + if (largeTable.size () != 0) + { + bytesstream bytes (largeTable); + TABLE_EXT_BLOCK teb; + bytes.read (&teb, sizeof (teb)); + for (size_t i = 0; i < teb.dwItemAdditEntCount; i ++) + { + ITEM_ITEMINFO_GROUP_TABLE_ENTRY iiigte; + bytes.read (&iiigte, sizeof (iiigte)); + vecItemToItemInfoGroup.push_back (iiigte); + } + for (size_t i = 0; i < teb.dwItemGroupAdditEntCount; i ++) + { + ITEMINFO_GROUP_TABLE_ENTRY iigte; + bytes.read (&iigte, sizeof (iigte)); + vecItemInfoGroups.push_back (iigte); + } + for (size_t i = 0; i < teb.dwItemTableAdditEntCount; i ++) + { + ITEM_ITEMINFO_TABLE_ENTRY iiite; + bytes.read (&iiite, sizeof (iiite)); + vecItemInfo.push_back (iiite); + } + if (bytes.position () > bytes.length ()) throw std::exception ("Error: invalid data in ResourceMap or ResourceMap2 Section."); + } + for (size_t i = 0; i < content.dwCandidateCount; i ++) + { + BYTE bType = -1; + fp->Read (&bType, sizeof (bType), nullptr); + switch (bType) + { + case 0x00: { + CANDIDATE_TYPE rvtype = 0; + fp->Read (&rvtype, sizeof (rvtype), nullptr); + CANDIDATE0_DATA cdata; + cdata.bResValueType = (BYTE)vecResTypes.at (rvtype); + auto &length = cdata.wEmbeddedLength; + auto &stringOffset = cdata.dwEmbeddedOffset; + fp >> length >> stringOffset; + vecCandidateInfo.emplace_back (CANDIDATE_INFO (cdata)); + } break; + case 0x01: { + CANDIDATE_TYPE rvtype = 0; + fp->Read (&rvtype, sizeof (rvtype), nullptr); + CANDIDATE1_DATA cdata; + auto &resourceValueType = cdata.bResValueType; + auto &sourceFileIndex = cdata.wSrcFile; + auto &valueLocation = cdata.wDataIndex; + auto &dataItemSection = cdata.wSectIndex; + resourceValueType = (BYTE)vecResTypes.at (rvtype); + fp >> sourceFileIndex >> valueLocation >> dataItemSection; + vecCandidateInfo.emplace_back (CANDIDATE_INFO (cdata)); + } break; + default: { + throw std::domain_error ("Error: invalid data read in ResourceMap or ResourceMap2 section."); + } break; + } + } + ulint strbegpos = 0; + fp->Seek (lint (0), STREAM_SEEK_CUR, strbegpos.ptr_union ()); + for (size_t i = 0; i < vecItemToItemInfoGroup.size (); i ++) + { + auto &itemToItemGroup = vecItemToItemInfoGroup [i]; + ITEMINFO_GROUP_TABLE_ENTRY itemInfoGroup; + if (itemToItemGroup.wItemInfoGroupIndex < vecItemInfoGroups.size ()) + itemInfoGroup = (vecItemInfoGroups [itemToItemGroup.wItemInfoGroupIndex]); + else itemInfoGroup = {1, (WORD)(itemToItemGroup.wItemInfoGroupIndex - vecItemInfoGroups.size ())}; + for (size_t j = itemInfoGroup.wFirstItemIndex; j < itemInfoGroup.wFirstItemIndex + itemInfoGroup.wItemInfoCount; j ++) + { + auto &itemInfo = vecItemInfo [j]; + auto decIndex = itemInfo.wDecisionIndex; + auto &decSect = sect.pri_file.get_section_by_ref (ref_sect (content.wDecInfSectIndex)); + decSect.parse (); + auto dec = decSect.vec_dec [decIndex]; + CANDIDATE_SET candidateSet; + std::vector &candidates = candidateSet.vecCandidates; + for (size_t k = 0; k < dec.verQualSets.size (); k ++) + { + auto can_info = vecCandidateInfo [itemInfo.wFirstCandiIndex + k]; + switch (can_info.bCandidateType) + { + case 0x01: { + REF_FILE_REF sourceFile; + if (!can_info.objCandidate._1.wSrcFile) sourceFile.setnull (); + else sourceFile.set (can_info.objCandidate._1.wSrcFile - 1); + candidates.push_back ( + CANDIDATE ( + dec.verQualSets [k].wIndex, + (RES_VALUE_TYPE)can_info.objCandidate._1.bResValueType, + sourceFile, + DATA_ITEM_REF (can_info.objCandidate._1.wDataIndex, can_info.objCandidate._1.wSectIndex + ) + ) + ); + } break; + case 0x00: { + BYTE_SPAN bspan ( + sectpos + strbegpos + ulint (can_info.objCandidate._0.dwEmbeddedOffset), + can_info.objCandidate._0.wEmbeddedLength + ); + candidates.push_back ( + CANDIDATE ( + dec.verQualSets [k].wIndex, + (RES_VALUE_TYPE)can_info.objCandidate._1.bResValueType, + bspan + ) + ); + } break; + } + } + WORD resourceMapItemIndex = itemToItemGroup.wFirstIndexProperty + (j - itemInfoGroup.wFirstItemIndex); + candidateSet.refResMapItem = RES_MAP_ITEM_REF (content.wHSSectIndex, resourceMapItemIndex); + candidateSet.wDecisionIndex = decIndex; + mapCandidateSet [resourceMapItemIndex] = candidateSet; + } + } + return valid (); +} +#ifdef UNALIGN_MEMORY +#pragma pack(pop) +#endif \ No newline at end of file diff --git a/priread (never using)/priread.cpp b/priread (never using)/priread.cpp new file mode 100644 index 0000000..d89cbc9 --- /dev/null +++ b/priread (never using)/priread.cpp @@ -0,0 +1,22 @@ +// priread.cpp : DLL Ӧóĵ +// + +#include "stdafx.h" +#include "priread.h" + + +// ǵһʾ +PRIREAD_API int npriread=0; + +// ǵһʾ +PRIREAD_API int fnpriread(void) +{ + return 42; +} + +// ѵĹ캯 +// йඨϢ priread.h +Cpriread::Cpriread() +{ + return; +} diff --git a/priread (never using)/priread.h b/priread (never using)/priread.h new file mode 100644 index 0000000..dc15979 --- /dev/null +++ b/priread (never using)/priread.h @@ -0,0 +1,22 @@ +// ifdef Ǵʹ DLL 򵥵 +// ı׼ DLL еļ϶ PRIREAD_EXPORTS +// űġʹô DLL +// κĿϲӦ˷šԴļаļκĿὫ +// PRIREAD_API ΪǴ DLL ģ DLL ô˺궨 +// ΪDZġ +#ifdef PRIREAD_EXPORTS +#define PRIREAD_API __declspec(dllexport) +#else +#define PRIREAD_API __declspec(dllimport) +#endif + +// Ǵ priread.dll +class PRIREAD_API Cpriread { +public: + Cpriread(void); + // TODO: ڴķ +}; + +extern PRIREAD_API int npriread; + +PRIREAD_API int fnpriread(void); diff --git a/priread (never using)/priread.vcxproj b/priread (never using)/priread.vcxproj new file mode 100644 index 0000000..268ba63 --- /dev/null +++ b/priread (never using)/priread.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {99D714D9-F40D-425B-BAFA-8B41C17971A5} + Win32Proj + priread + 8.1 + + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;PRIREAD_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Use + Level3 + Disabled + _DEBUG;_WINDOWS;_USRDLL;PRIREAD_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;PRIREAD_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + NDEBUG;_WINDOWS;_USRDLL;PRIREAD_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + false + + + false + + + false + + + false + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/priread (never using)/priread.vcxproj.filters b/priread (never using)/priread.vcxproj.filters new file mode 100644 index 0000000..7efecd9 --- /dev/null +++ b/priread (never using)/priread.vcxproj.filters @@ -0,0 +1,54 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/priread (never using)/stdafx.cpp b/priread (never using)/stdafx.cpp new file mode 100644 index 0000000..85e805e --- /dev/null +++ b/priread (never using)/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : ֻ׼ļԴļ +// priread.pch ΪԤͷ +// stdafx.obj ԤϢ + +#include "stdafx.h" + +// TODO: STDAFX.H κĸͷļ +//ڴļ diff --git a/priread (never using)/stdafx.h b/priread (never using)/stdafx.h new file mode 100644 index 0000000..4f79067 --- /dev/null +++ b/priread (never using)/stdafx.h @@ -0,0 +1,20 @@ +// stdafx.h : ׼ϵͳļİļ +// Ǿʹõĵ +// ضĿİļ +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Windows ͷųʹõ +// Windows ͷļ: +#include + + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // ijЩ CString 캯ʽ + +#include +#include + +// TODO: ڴ˴óҪͷļ diff --git a/priread (never using)/targetver.h b/priread (never using)/targetver.h new file mode 100644 index 0000000..416cebf --- /dev/null +++ b/priread (never using)/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// SDKDDKVer.h õ߰汾 Windows ƽ̨ + +// ҪΪǰ Windows ƽ̨Ӧó WinSDKVer.h +// _WIN32_WINNT ΪҪֵ֧ƽ̨Ȼٰ SDKDDKVer.h + +#include diff --git a/priread (never using)/themeinfo.h b/priread (never using)/themeinfo.h new file mode 100644 index 0000000..8d3c712 --- /dev/null +++ b/priread (never using)/themeinfo.h @@ -0,0 +1,40 @@ +#pragma once +#include + +bool IsHighContrastEnabled () +{ + HIGHCONTRAST hc = {sizeof (HIGHCONTRAST)}; + if (SystemParametersInfo (SPI_GETHIGHCONTRAST, sizeof (hc), &hc, 0)) return (hc.dwFlags & HCF_HIGHCONTRASTON) != 0; + return false; +} +enum class HighContrastTheme +{ + None, + Black, + White, + Other +}; +HighContrastTheme GetHighContrastTheme () +{ + HIGHCONTRAST hc = {sizeof (HIGHCONTRAST)}; + if (!SystemParametersInfo (SPI_GETHIGHCONTRAST, sizeof (hc), &hc, 0)) return HighContrastTheme::None; + if (!(hc.dwFlags & HCF_HIGHCONTRASTON)) return HighContrastTheme::None; + COLORREF bgColor = GetSysColor (COLOR_WINDOW); + COLORREF textColor = GetSysColor (COLOR_WINDOWTEXT); + int brightnessBg = (GetRValue (bgColor) + GetGValue (bgColor) + GetBValue (bgColor)) / 3; + int brightnessText = (GetRValue (textColor) + GetGValue (textColor) + GetBValue (textColor)) / 3; + if (brightnessBg < brightnessText) return HighContrastTheme::Black; + else if (brightnessBg > brightnessText) return HighContrastTheme::White; + else return HighContrastTheme::Other; +} +int GetDPI () +{ + HDC hDC = GetDC (NULL); + int DPI_A = (int)(((double)GetDeviceCaps (hDC, 118) / (double)GetDeviceCaps (hDC, 8)) * 100); + int DPI_B = (int)(((double)GetDeviceCaps (hDC, 88) / 96) * 100); + ReleaseDC (NULL, hDC); + if (DPI_A == 100) return DPI_B; + else if (DPI_B == 100) return DPI_A; + else if (DPI_A == DPI_B) return DPI_A; + else return 0; +} diff --git a/shared/html/js/pkginfo.js b/shared/html/js/pkginfo.js index d3c4e45..a165b10 100644 --- a/shared/html/js/pkginfo.js +++ b/shared/html/js/pkginfo.js @@ -7,10 +7,16 @@ try { if (swJson) ret = JSON.parse(swJson); } catch (e) {} + try { + if (ret && typeof ret.jsontext !== "undefined") { + ret["json"] = JSON.parse(ret.jsontext); + delete ret.jsontext; + } + } catch (e) {} if (callback) callback(ret); } global.Package = { - reader: function(pkgPath) { external.Package.reader(pkgPath); }, + reader: function(pkgPath) { return external.Package.reader(pkgPath); }, manager: { add: function(swPkgPath, uOptions) { return new Promise(function(resolve, reject, progress) { @@ -124,5 +130,37 @@ }); }, }, + manifest: function(swManifestPath) { return external.Package.manifest(swManifestPath); }, + manifestFromInstallLocation: function(swInstallLocation) { return external.Package.fromInstallLocation(swInstallLocation); }, + readFromPackage: function(swPkgPath, bUsePri) { + if (bUsePri === null || bUsePri === void 0) bUsePri = false; + return new Promise(function(resolve, reject) { + external.Package.readFromPackageAsync(swPkgPath, bUsePri, function(result) { + parseJsonCallback(result, resolve); + }, function(error) { + parseJsonCallback(error, reject); + }); + }); + }, + readFromManifest: function(swPkgPath, bUsePri) { + if (bUsePri === null || bUsePri === void 0) bUsePri = false; + return new Promise(function(resolve, reject) { + external.Package.readFromManifestAsync(swPkgPath, bUsePri, function(result) { + parseJsonCallback(result, resolve); + }, function(error) { + parseJsonCallback(error, reject); + }); + }); + }, + readFromInstallLocation: function(swPkgPath, bUsePri) { + if (bUsePri === null || bUsePri === void 0) bUsePri = false; + return new Promise(function(resolve, reject) { + external.Package.readFromInstallLocationAsync(swPkgPath, bUsePri, function(result) { + parseJsonCallback(result, resolve); + }, function(error) { + parseJsonCallback(error, reject); + }); + }); + }, }; })(this); \ No newline at end of file