Files
App-Installer-For-Windows-8…/PriFormat/PriReader.cs
T
2026-01-27 22:47:49 +08:00

818 lines
24 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
namespace PriFormat
{
internal enum SearchState
{
Pending,
Searching,
Found,
NotFound
}
internal sealed class SearchTask
{
public PriPath Path { get; }
public BaseResources Result { get; set; }
public SearchState State { get; set; }
public SearchTask (PriPath path)
{
Path = path;
State = SearchState.Pending;
}
}
public sealed class PriReader: IDisposable
{
private PriFile _pri;
private Stream _stream;
private readonly bool _fromFile;
private readonly object _lock = new object ();
private readonly Dictionary<PriPath, SearchTask> _tasks =
new Dictionary<PriPath, SearchTask> ();
private Thread _searchThread;
private bool _searchRunning;
private bool _disposed;
private readonly AutoResetEvent _searchWakeup = new AutoResetEvent (false);
private PriReader (string filePath)
{
_fromFile = true;
_stream = new FileStream (filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
_pri = PriFile.Parse (_stream);
}
private PriReader (Stream stream)
{
_fromFile = false;
_stream = stream;
_pri = PriFile.Parse (stream);
}
public static PriReader Open (string filePath) => new PriReader (filePath);
public static PriReader Open (Stream stream) => new PriReader (stream);
public void AddSearch (IEnumerable<string> resourceNames)
{
if (resourceNames == null) return;
bool added = false;
lock (_lock)
{
foreach (var name in resourceNames)
{
if (string.IsNullOrEmpty (name)) continue;
var path = new PriResourceIdentifier (name).Path;
if (!_tasks.ContainsKey (path))
{
_tasks [path] = new SearchTask (path);
added = true;
}
}
}
if (added)
EnsureSearchThread ();
}
public void AddSearch (string resname) { AddSearch (new string [] { resname }); }
private void EnsureSearchThread ()
{
lock (_lock)
{
if (_disposed) return;
if (_searchRunning) return;
_searchRunning = true;
_searchThread = new Thread (SearchThreadProc);
_searchThread.IsBackground = true;
_searchThread.Start ();
}
}
private void SearchThreadProc ()
{
try
{
while (true)
{
if (_disposed) return;
bool hasPending = false;
lock (_lock)
{
foreach (var task in _tasks.Values)
{
if (task.State == SearchState.Pending)
{
hasPending = true;
break;
}
}
}
if (!hasPending)
{
// 没任务了,休眠等待唤醒
_searchWakeup.WaitOne (200);
continue;
}
// 真正跑一次搜索
RunSearch (TimeSpan.FromSeconds (10));
}
}
finally
{
lock (_lock)
{
_searchRunning = false;
_searchThread = null;
}
}
}
public BaseResources GetValue (string resourceName)
{
if (string.IsNullOrEmpty (resourceName)) return null;
var path = new PriResourceIdentifier (resourceName).Path;
SearchTask task;
lock (_lock)
{
if (!_tasks.TryGetValue (path, out task))
{
task = new SearchTask (path);
_tasks [path] = task;
EnsureSearchThread ();
}
}
// 已有结果
if (task.State == SearchState.Found)
return task.Result;
// 等待搜索完成
while (true)
{
if (_disposed) return null;
lock (_lock)
{
if (task.State == SearchState.Found)
return task.Result;
if (task.State == SearchState.NotFound)
return null;
}
Thread.Sleep (50);
}
}
public void RunSearch (TimeSpan timeout)
{
var begin = DateTime.Now;
foreach (var rmsRef in _pri.PriDescriptorSection.ResourceMapSections)
{
var rms = _pri.GetSectionByRef (rmsRef);
if (rms == null || rms.HierarchicalSchemaReference != null) continue;
var decision = _pri.GetSectionByRef (rms.DecisionInfoSection);
foreach (var candidateSet in rms.CandidateSets.Values)
{
if (DateTime.Now - begin > timeout) return;
var item = _pri.GetResourceMapItemByRef (candidateSet.ResourceMapItem);
var fullName = item.FullName.Trim ('\\');
var parts = fullName.Split (new [] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
var itemPath = new PriPath (fullName, true);
SearchTask task;
lock (_lock)
{
if (!_tasks.TryGetValue (itemPath, out task))
continue;
if (task.State != SearchState.Pending)
continue;
task.State = SearchState.Searching;
}
var result = ReadCandidate (candidateSet, decision);
lock (_lock)
{
task.Result = result;
task.State = result != null
? SearchState.Found
: SearchState.NotFound;
}
}
}
lock (_lock)
{
foreach (var kv in _tasks)
{
if (kv.Value.State == SearchState.Pending)
{
kv.Value.State = SearchState.NotFound;
}
}
}
if (DateTime.Now - begin > timeout) return;
}
private BaseResources ReadCandidate (
CandidateSet candidateSet,
DecisionInfoSection decisionInfo)
{
string value = System.String.Empty;
int restype = 0; // 0 string, 1 file
Dictionary<StringQualifier, string> strdict = new Dictionary<StringQualifier, string> ();
Dictionary<FileQualifier, string> filedict = new Dictionary<FileQualifier, string> ();
foreach (var candidate in candidateSet.Candidates)
{
if (candidate.SourceFile != null)
{
}
else
{
var byteSpan = new ByteSpan ();
if (candidate.DataItem != null) byteSpan = _pri.GetDataItemByRef (candidate.DataItem);
else byteSpan = candidate.Data;
_stream.Seek (byteSpan.Offset, SeekOrigin.Begin);
var binaryReader = new BinaryReader (_stream, Encoding.Default);
{
var data = binaryReader.ReadBytes ((int)byteSpan.Length);
switch (candidate.Type)
{
case ResourceValueType.AsciiPath:
case ResourceValueType.AsciiString:
value = Encoding.ASCII.GetString (data).TrimEnd ('\0'); break;
case ResourceValueType.Utf8Path:
case ResourceValueType.Utf8String:
value = Encoding.UTF8.GetString (data).TrimEnd ('\0'); break;
case ResourceValueType.Path:
case ResourceValueType.String:
value = Encoding.Unicode.GetString (data).TrimEnd ('\0'); break;
case ResourceValueType.EmbeddedData:
value = Convert.ToBase64String (data); break;
}
}
}
var qualifierSet = decisionInfo.QualifierSets [candidate.QualifierSet];
var qualis = new Dictionary <QualifierType, object> ();
foreach (var quali in qualifierSet.Qualifiers)
{
var qtype = quali.Type;
var qvalue = quali.Value;
qualis.Add (qtype, qvalue);
}
if (qualis.ContainsKey (QualifierType.Language))
{
restype = 0;
strdict.Add (new StringQualifier (qualis [QualifierType.Language].ToString ()), value);
}
else
{
restype = 1;
if (qualis.ContainsKey (QualifierType.Scale))
{
var cons = qualis.ContainsKey (QualifierType.Contrast) ? qualis [QualifierType.Contrast].ToString () : "None";
Contrast cs = Contrast.None;
switch (cons?.Trim ()?.ToLower ())
{
case "white": cs = Contrast.White; break;
case "black": cs = Contrast.Black; break;
case "high": cs = Contrast.High; break;
case "low": cs = Contrast.Low; break;
case "none": cs = Contrast.None; break;
}
filedict.Add (new FileQualifier (Convert.ToInt32 (qualis [QualifierType.Scale]), cs), value);
}
}
}
if (strdict.Count > 0 && filedict.Count > 0)
{
if (strdict.Count >= filedict.Count) return new StringResources (strdict, true);
else return new FileResources (filedict, true);
}
else if (strdict.Count > 0) return new StringResources (strdict, true);
else if (filedict.Count > 0) return new FileResources (filedict, true);
return new StringResources ();
}
public void Dispose ()
{
_disposed = true;
_searchWakeup.Set ();
lock (_lock)
{
_tasks.Clear ();
}
_pri?.Dispose ();
_pri = null;
if (_fromFile)
_stream?.Dispose ();
_stream = null;
}
public string Resource (string resname) { return GetValue (resname)?.SuitableValue ?? ""; }
public Dictionary<string, string> Resources (IEnumerable<string> list)
{
var ret = new Dictionary<string, string> ();
AddSearch (list);
foreach (var item in list) ret [item] = Resource (item);
return ret;
}
public string Path (string resname) => Resource (resname);
public Dictionary<string, string> Paths (IEnumerable<string> resnames) => Resources (resnames);
public string String (string resname) => Resource (resname);
public Dictionary<string, string> Strings (IEnumerable<string> resnames) => Resources (resnames);
}
public sealed class PriReaderBundle: IDisposable
{
private sealed class PriInst
{
// 0b01 = lang, 0b10 = scale, 0b11 = both
public readonly byte Type;
public PriReader Reader;
public PriInst (byte type, Stream stream)
{
Type = (byte)(type & 0x03);
Reader = PriReader.Open (stream);
}
public bool IsValid
{
get { return (Type & 0x03) != 0 && Reader != null; }
}
}
private readonly List<PriInst> _priFiles = new List<PriInst> (3);
private readonly Dictionary<byte, PriInst> _mapPri = new Dictionary<byte, PriInst> ();
// -----------------------------
// Set
// -----------------------------
// type: 1 = language, 2 = scale, 3 = both
public bool Set (byte type, Stream priStream)
{
byte realType = (byte)(type & 0x03);
if (realType == 0 || priStream == null)
return false;
PriInst inst;
if (_mapPri.TryGetValue (realType, out inst))
{
inst.Reader.Dispose ();
inst.Reader = PriReader.Open (priStream);
}
else
{
inst = new PriInst (realType, priStream);
_priFiles.Add (inst);
_mapPri [realType] = inst;
}
return true;
}
// 如果你外部仍然是 IStream / IntPtr,这里假定你已有封装成 Stream 的工具
public bool Set (byte type, IntPtr priStream)
{
if (priStream == IntPtr.Zero)
return false;
Stream stream = StreamHelper.FromIStream (priStream);
return Set (type, stream);
}
// -----------------------------
// 内部路由
// -----------------------------
private PriReader Get (byte type, bool mustReturn)
{
type = (byte)(type & 0x03);
PriInst inst;
if (_mapPri.TryGetValue (type, out inst))
return inst.Reader;
// fallback: both
if (type != 0x03 && _mapPri.TryGetValue (0x03, out inst))
return inst.Reader;
if (mustReturn && _priFiles.Count > 0)
return _priFiles [0].Reader;
return null;
}
private static bool IsMsResourcePrefix (string s)
{
try
{
return MSRUriHelper.IsMsResourceUri (s) || MSRUriHelper.IsFullMsResourceUri (s) || MSRUriHelper.IsRelativeMsResourceUri (s);
}
catch
{
return MSRUriHelper.IsMsResourceUri (s);
}
}
// -----------------------------
// AddSearch
// -----------------------------
public void AddSearch (IEnumerable<string> arr)
{
if (arr == null)
return;
List<string> langRes = new List<string> ();
List<string> scaleRes = new List<string> ();
foreach (string it in arr)
{
if (string.IsNullOrEmpty (it))
continue;
if (IsMsResourcePrefix (it))
langRes.Add (it);
else
scaleRes.Add (it);
}
PriReader langPri = Get (1, true);
PriReader scalePri = Get (2, true);
if (langPri != null && langRes.Count > 0)
langPri.AddSearch (langRes);
if (scalePri != null && scaleRes.Count > 0)
scalePri.AddSearch (scaleRes);
}
public void AddSearch (string resName)
{
if (string.IsNullOrEmpty (resName))
return;
if (IsMsResourcePrefix (resName))
{
PriReader langPri = Get (1, true);
if (langPri != null)
langPri.AddSearch (resName);
}
else
{
PriReader scalePri = Get (2, true);
if (scalePri != null)
scalePri.AddSearch (resName);
}
}
// -----------------------------
// Resource / Path / String
// -----------------------------
public string Resource (string resName)
{
if (string.IsNullOrEmpty (resName))
return string.Empty;
PriReader reader;
if (IsMsResourcePrefix (resName))
reader = Get (1, true);
else
reader = Get (2, true);
if (reader == null)
return string.Empty;
var res = reader.GetValue (resName);
return res != null ? res.ToString () : string.Empty;
}
public Dictionary<string, string> Resources (IEnumerable<string> resNames)
{
if (resNames == null)
throw new ArgumentNullException ("resNames");
AddSearch (resNames);
Dictionary<string, string> result = new Dictionary<string, string> ();
foreach (string name in resNames)
result [name] = Resource (name);
return result;
}
public string Path (string resName)
{
return Resource (resName);
}
public Dictionary<string, string> Paths (IEnumerable<string> resNames)
{
return Resources (resNames);
}
public string String (string resName)
{
return Resource (resName);
}
public Dictionary<string, string> Strings (IEnumerable<string> resNames)
{
return Resources (resNames);
}
public void Dispose ()
{
foreach (var inst in _priFiles)
{
inst.Reader.Dispose ();
}
_mapPri.Clear ();
_priFiles.Clear ();
}
}
public abstract class BaseQualifier { }
public class StringQualifier: BaseQualifier, IEquatable<StringQualifier>, IEquatable<string>
{
public string LocaleName { get; private set; }
public int LCID => CultureInfo.GetCultureInfo (LocaleName).LCID;
public StringQualifier (string localeName) { LocaleName = localeName; }
public StringQualifier (int lcid) { LocaleName = CultureInfo.GetCultureInfo (lcid).Name; }
public override string ToString () { return $"String Qualifier: {LocaleName} ({LCID})"; }
public bool Equals (StringQualifier other)
{
var ca = new CultureInfo (this.LocaleName);
var cb = new CultureInfo (other.LocaleName);
return string.Equals (ca.Name, cb.Name, StringComparison.OrdinalIgnoreCase);
}
public bool Equals (string other) { return this.Equals (new StringQualifier (other)); }
public override int GetHashCode () => CultureInfo.GetCultureInfo (LocaleName).Name.GetHashCode ();
}
public enum Contrast
{
None,
Black,
White,
High,
Low
};
public class FileQualifier: BaseQualifier, IEquatable<FileQualifier>, IEquatable<int>, IEquatable<Tuple<int, Contrast>>
{
public Contrast Contrast { get; private set; } = Contrast.None;
public int Scale { get; private set; } = 0;
public FileQualifier (int scale, Contrast contrast = Contrast.None)
{
Scale = scale;
this.Contrast = contrast;
}
public override string ToString () { return $"File Qualifier: Scale {Scale}, Contrast {this.Contrast}"; }
public bool Equals (FileQualifier other)
{
return this.Contrast == other.Contrast && this.Scale == other.Scale;
}
public bool Equals (int other)
{
return this.Scale == other && this.Contrast == Contrast.None;
}
public bool Equals (Tuple<int, Contrast> other)
{
return this.Contrast == other.Item2 && this.Scale == other.Item1;
}
public override int GetHashCode ()
{
unchecked
{
int hash = 17;
hash = hash * 31 + Scale.GetHashCode ();
hash = hash * 31 + Contrast.GetHashCode ();
return hash;
}
}
}
public abstract class BaseResources: IDisposable
{
public virtual void Dispose () { }
public virtual string SuitableValue { get; } = String.Empty;
public virtual Dictionary<BaseQualifier, string> AllValue { get; } = new Dictionary<BaseQualifier, string> ();
/// <summary>
/// 表示是否寻找过,如果真则不用再次寻找。
/// </summary>
public bool IsFind { get; set; }
}
public class StringResources: BaseResources, IDictionary<StringQualifier, string>, IDictionary<string, string>
{
private Dictionary<StringQualifier, string> dict = new Dictionary<StringQualifier, string> ();
public string this [string key]
{
get { return dict [new StringQualifier (key)]; }
set { }
}
public string this [StringQualifier key]
{
get { return dict [key]; }
set { }
}
public int Count => dict.Count;
public bool IsReadOnly => true;
public ICollection<StringQualifier> Keys => dict.Keys;
public ICollection<string> Values => dict.Values;
ICollection<string> IDictionary<string, string>.Keys => dict.Keys.Select (k => k.LocaleName).ToList ();
public void Add (KeyValuePair<string, string> item) { }
public void Add (KeyValuePair<StringQualifier, string> item) { }
public void Add (string key, string value) { }
public void Add (StringQualifier key, string value) { }
public void Clear () { }
public bool Contains (KeyValuePair<string, string> item)
{
string value;
if (TryGetValue (item.Key, out value)) return value == item.Value;
return false;
}
public bool Contains (KeyValuePair<StringQualifier, string> item) => dict.Contains (item);
public bool ContainsKey (string key)
{
foreach (var kv in dict)
{
if (string.Equals (kv.Key.LocaleName, key, StringComparison.OrdinalIgnoreCase)) return true;
}
return false;
}
public bool ContainsKey (StringQualifier key) => dict.ContainsKey (key);
public void CopyTo (KeyValuePair<string, string> [] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException ("array");
if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException ("arrayIndex");
if (array.Length - arrayIndex < dict.Count) throw new ArgumentException ("The destination array is not large enough.");
foreach (var kv in dict)
{
array [arrayIndex++] = new KeyValuePair<string, string> (
kv.Key.LocaleName, kv.Value);
}
}
public void CopyTo (KeyValuePair<StringQualifier, string> [] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException ("array");
if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException ("arrayIndex");
if (array.Length - arrayIndex < dict.Count) throw new ArgumentException ("The destination array is not large enough.");
foreach (var kv in dict)
{
array [arrayIndex++] = new KeyValuePair<StringQualifier, string> (
kv.Key, kv.Value);
}
}
public IEnumerator<KeyValuePair<StringQualifier, string>> GetEnumerator () => dict.GetEnumerator ();
public bool Remove (KeyValuePair<StringQualifier, string> item) { return false; }
public bool Remove (string key) => false;
public bool Remove (KeyValuePair<string, string> item) => false;
public bool Remove (StringQualifier key) { return false; }
public bool TryGetValue (string key, out string value)
{
foreach (var kv in dict)
{
if (string.Equals (kv.Key.LocaleName, key, StringComparison.OrdinalIgnoreCase))
{
value = kv.Value;
return true;
}
}
value = null;
return false;
}
public bool TryGetValue (StringQualifier key, out string value) => dict.TryGetValue (key, out value);
IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator ()
{
foreach (var kv in dict)
{
yield return new KeyValuePair<string, string> (kv.Key.LocaleName, kv.Value);
}
}
IEnumerator IEnumerable.GetEnumerator () => dict.GetEnumerator ();
internal static bool LocaleNameEqualsIgnoreRegion (string a, string b)
{
var ca = new CultureInfo (a);
var cb = new CultureInfo (b);
return string.Equals (ca.TwoLetterISOLanguageName, cb.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase);
}
public static string GetCoincidentValue (Dictionary<StringQualifier, string> d, string localeName)
{
if (d == null) return null;
foreach (var kv in d)
{
if (kv.Key.LocaleName?.Trim ()?.ToLower () == localeName?.Trim ()?.ToLower ()) return kv.Value;
}
var targetLang = new StringQualifier (localeName);
foreach (var kv in d)
{
if (kv.Key.LCID == targetLang.LCID) return kv.Value;
}
foreach (var kv in d)
{
if (LocaleNameEqualsIgnoreRegion (kv.Key.LocaleName, localeName)) return kv.Value;
}
return String.Empty;
}
public static string GetSuitableValue (Dictionary<StringQualifier, string> d)
{
var ret = GetCoincidentValue (d, LocaleExt.GetComputerLocaleCode ());
if (String.IsNullOrEmpty (ret)) ret = GetCoincidentValue (d, "en-us");
if (String.IsNullOrEmpty (ret) && d.Count > 0) ret = d.ElementAt (0).Value;
return ret;
}
public override string SuitableValue => GetSuitableValue (dict);
public override Dictionary<BaseQualifier, string> AllValue => dict.ToDictionary (kv => (BaseQualifier)kv.Key, kv => kv.Value);
public override void Dispose ()
{
dict.Clear ();
dict = null;
base.Dispose ();
}
~StringResources () { Dispose (); }
public StringResources (Dictionary<StringQualifier, string> _dict, bool isfind = true)
{
dict = _dict;
IsFind = isfind;
}
public StringResources (Dictionary<string, string> _dict, bool isfind = true)
{
dict = _dict.ToDictionary (kv => new StringQualifier (kv.Key), kv => kv.Value);
IsFind = isfind;
}
public StringResources () { IsFind = false; }
}
public class FileResources: BaseResources, IDictionary<FileQualifier, string>
{
private Dictionary<FileQualifier, string> dict = new Dictionary<FileQualifier, string> ();
public string this [FileQualifier key]
{
get { return dict [key]; }
set { }
}
public int Count => dict.Count;
public bool IsReadOnly => false;
public ICollection<FileQualifier> Keys => dict.Keys;
public ICollection<string> Values => dict.Values;
public void Add (KeyValuePair<FileQualifier, string> item) { }
public void Add (FileQualifier key, string value) { }
public void Clear () { }
public bool Contains (KeyValuePair<FileQualifier, string> item) => dict.Contains (item);
public bool ContainsKey (FileQualifier key) => dict.ContainsKey (key);
public void CopyTo (KeyValuePair<FileQualifier, string> [] array, int arrayIndex) { }
public IEnumerator<KeyValuePair<FileQualifier, string>> GetEnumerator () => dict.GetEnumerator ();
public bool Remove (KeyValuePair<FileQualifier, string> item) => false;
public bool Remove (FileQualifier key) => false;
public bool TryGetValue (FileQualifier key, out string value) => dict.TryGetValue (key, out value);
IEnumerator IEnumerable.GetEnumerator () => dict.GetEnumerator ();
public static string GetCoincidentValue (Dictionary<FileQualifier, string> d, int scale, Contrast contrast = Contrast.None)
{
var td = d.OrderBy (k => k.Key.Contrast).ThenBy (k => k.Key.Scale);
foreach (var kv in td)
{
if (kv.Key.Contrast == contrast)
{
if (kv.Key.Scale >= scale) return kv.Value;
}
}
foreach (var kv in td)
{
if (kv.Key.Contrast == Contrast.None)
if (kv.Key.Scale >= scale) return kv.Value;
}
foreach (var kv in td)
{
if (kv.Key.Contrast == Contrast.Black)
if (kv.Key.Scale >= scale) return kv.Value;
}
foreach (var kv in td)
{
if (kv.Key.Scale >= scale) return kv.Value;
}
if (d.Count > 0) return d.ElementAt (0).Value;
return String.Empty;
}
public static string GetSuitableValue (Dictionary<FileQualifier, string> d, Contrast contrast = Contrast.None) => GetCoincidentValue (d, UIExt.DPI, contrast);
public override string SuitableValue => GetSuitableValue (dict);
public override Dictionary<BaseQualifier, string> AllValue => dict.ToDictionary (kv => (BaseQualifier)kv.Key, kv => kv.Value);
public override void Dispose ()
{
dict.Clear ();
dict = null;
base.Dispose ();
}
~FileResources () { Dispose (); }
public FileResources (Dictionary<FileQualifier, string> _dict, bool isfind = true)
{
dict = _dict;
IsFind = isfind;
}
public FileResources (Dictionary<Tuple<int, Contrast>, string> _dict, bool isfind = true)
{
dict = _dict.ToDictionary (kv => new FileQualifier (kv.Key.Item1, kv.Key.Item2), kv => kv.Value);
IsFind = isfind;
}
public FileResources () { IsFind = false; }
}
}