From 0c87a2cdcd832a2f370b645001c866c01b7ba0a8 Mon Sep 17 00:00:00 2001 From: Bruce Date: Tue, 27 Jan 2026 22:47:49 +0800 Subject: [PATCH] Update about Manager. --- AppxPackage/DataInterface.cs | 29 + AppxPackage/ManifestReader.cs | 75 +- AppxPackage/PackageReader.cs | 97 +- AppxPackage/PkgReadNative.cs | 2 + AppxPackage/PriFileNative.cs | 4 +- AppxPackage/PriReader.cs | 29 +- Bridge/SysInit.cs | 174 ++ Manager/Manager.csproj | 4 - Manager/ManagerShell.cs | 5 +- PriFileFormat/ComStreamWrapper.cs | 159 +- PriFileFormat/PriFile.cs | 3 +- PriFormat.zip | Bin 0 -> 126072 bytes PriFormat/ByteSpan.cs | 22 + PriFormat/DataItemSection.cs | 82 + PriFormat/Datas.cs | 819 +++++ PriFormat/DecisionInfoSection.cs | 243 ++ PriFormat/HierarchicalSchemaSection.cs | 347 +++ PriFormat/Polyfill.cs | 63 + PriFormat/PriDescriptorSection.cs | 139 + PriFormat/PriFile.cs | 144 + PriFormat/PriFormat.csproj | 69 + PriFormat/PriReader.cs | 817 +++++ PriFormat/Properties/AssemblyInfo.cs | 36 + PriFormat/ReferencedFileSection.cs | 248 ++ PriFormat/ResourceMapSection.cs | 414 +++ PriFormat/ReverseMapSection.cs | 210 ++ PriFormat/Section.cs | 126 + PriFormat/StreamHelper.cs | 171 ++ PriFormat/SubStream.cs | 110 + PriFormat/TocEntry.cs | 35 + PriFormat/UnknownSection.cs | 28 + WAShell/WebAppForm.Designer.cs | 1 + WAShell/WebAppForm.cs | 4 + dlltest/dlltest.vcxproj | 4 +- dlltest/main.cpp | 160 +- pkgread/pkgread.cpp | 41 + pkgread/pkgread.h | 709 +++++ priformatcli/priformatcli.cpp | 13 +- priread (never using)/ReadMe.txt | 30 + priread (never using)/dllmain.cpp | 19 + priread (never using)/localeex.h | 163 + priread (never using)/nstring.h | 456 +++ priread (never using)/prifile.h | 2671 +++++++++++++++++ priread (never using)/priread.cpp | 22 + priread (never using)/priread.h | 22 + priread (never using)/priread.vcxproj | 182 ++ priread (never using)/priread.vcxproj.filters | 54 + priread (never using)/stdafx.cpp | 8 + priread (never using)/stdafx.h | 20 + priread (never using)/targetver.h | 8 + priread (never using)/themeinfo.h | 40 + shared/html/js/pkginfo.js | 40 +- 52 files changed, 9170 insertions(+), 201 deletions(-) create mode 100644 PriFormat.zip create mode 100644 PriFormat/ByteSpan.cs create mode 100644 PriFormat/DataItemSection.cs create mode 100644 PriFormat/Datas.cs create mode 100644 PriFormat/DecisionInfoSection.cs create mode 100644 PriFormat/HierarchicalSchemaSection.cs create mode 100644 PriFormat/Polyfill.cs create mode 100644 PriFormat/PriDescriptorSection.cs create mode 100644 PriFormat/PriFile.cs create mode 100644 PriFormat/PriFormat.csproj create mode 100644 PriFormat/PriReader.cs create mode 100644 PriFormat/Properties/AssemblyInfo.cs create mode 100644 PriFormat/ReferencedFileSection.cs create mode 100644 PriFormat/ResourceMapSection.cs create mode 100644 PriFormat/ReverseMapSection.cs create mode 100644 PriFormat/Section.cs create mode 100644 PriFormat/StreamHelper.cs create mode 100644 PriFormat/SubStream.cs create mode 100644 PriFormat/TocEntry.cs create mode 100644 PriFormat/UnknownSection.cs create mode 100644 priread (never using)/ReadMe.txt create mode 100644 priread (never using)/dllmain.cpp create mode 100644 priread (never using)/localeex.h create mode 100644 priread (never using)/nstring.h create mode 100644 priread (never using)/prifile.h create mode 100644 priread (never using)/priread.cpp create mode 100644 priread (never using)/priread.h create mode 100644 priread (never using)/priread.vcxproj create mode 100644 priread (never using)/priread.vcxproj.filters create mode 100644 priread (never using)/stdafx.cpp create mode 100644 priread (never using)/stdafx.h create mode 100644 priread (never using)/targetver.h create mode 100644 priread (never using)/themeinfo.h 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 0000000000000000000000000000000000000000..55e272abfe3bafd9fd1c5e378074ae0070c3e4ac GIT binary patch literal 126072 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

@XA_^y0T?2u3CAMmp*a*fl9 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 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