Update reader.

This commit is contained in:
Bruce
2026-04-04 19:27:45 +08:00
parent f9f4db3f6c
commit bf54d5a531
22 changed files with 3405 additions and 21 deletions

View File

@@ -1240,6 +1240,8 @@ namespace AppxPackage
};
}
}
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDual)]
public class PRPrerequisites: BaseInfoSection, IPrerequisites
{
public PRPrerequisites (ref IntPtr hReader) : base (ref hReader) { }
@@ -1487,7 +1489,7 @@ namespace AppxPackage
}
public static bool AddApplicationItem (string itemName) => PackageReadHelper.AddPackageApplicationItemGetName (itemName);
public static bool RemoveApplicationItem (string itemName) => PackageReadHelper.RemovePackageApplicationItemGetName (itemName);
public static string [] GetApplicationItem () => PackageReadHelper.GetApplicationItemNames ();
public static string [] GetApplicationItems () => PackageReadHelper.GetApplicationItemNames ();
public static void UpdateApplicationItems (IEnumerable<string> items) => PackageReadHelper.SetApplicationItemNames (items);
public string BuildJsonForUse ()
{

View File

@@ -11,6 +11,8 @@ using ModernNotice;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Generic;
namespace Bridge
{
@@ -762,6 +764,44 @@ namespace Bridge
}
public bool AddApplicationItem (string itemName) => PackageReader.AddApplicationItem (itemName);
public bool RemoveApplicationItem (string itemName) => PackageReader.RemoveApplicationItem (itemName);
private List<object> JsArrayToList (object jsArray)
{
var result = new List<object> ();
if (jsArray == null)
return result;
// JS Array 有 length 属性和数字索引器
var type = jsArray.GetType ();
int length = (int)type.InvokeMember (
"length",
BindingFlags.GetProperty,
null,
jsArray,
null);
for (int i = 0; i < length; i++)
{
object value = type.InvokeMember (
i.ToString (),
BindingFlags.GetProperty,
null,
jsArray,
null);
result.Add (value);
}
return result;
}
public void UpdateApplicationItems (object items)
{
var stritems = JsArrayToList (items).Select (e => e?.ToString ()).ToList ();
PackageReader.UpdateApplicationItems (stritems);
}
public string [] GetApplicationItems () => PackageReader.GetApplicationItems ();
public string GetApplicationItemsToJson () => JsonConvert.SerializeObject (GetApplicationItems ());
// Cache about
private const int MaxCacheItems = 64; // 最大缓存数量
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes (30);
@@ -895,6 +935,7 @@ namespace Bridge
public _I_System System => system;
public _I_Notice Notice => new _I_Notice ();
public _I_Process Process => proc;
public _I_Web Web => new _I_Web ();
public string CmdArgs
{
get

View File

@@ -46,6 +46,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Download.cs" />
<Compile Include="Enumerable.cs" />
<Compile Include="HResult.cs" />
<Compile Include="IE.cs" />
<Compile Include="Locale.cs" />
@@ -61,6 +62,7 @@
<Compile Include="Utils.cs" />
<Compile Include="Version.cs" />
<Compile Include="VisualElements.cs" />
<Compile Include="Web.cs" />
<Compile Include="WebBrowser.cs" />
<Compile Include="Window.cs" />
</ItemGroup>

159
DataUtils/Enumerable.cs Normal file
View File

@@ -0,0 +1,159 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace DataUtils
{
[ComVisible (true)]
[InterfaceType (ComInterfaceType.InterfaceIsDual)]
public interface _I_Enumerable
{
int Length { get; set; }
object this [int index] { get; set; }
void Add (object value); // push
void Push (object value);
object Pop (); // 删除并返回末尾元素
object Shift (); // 删除并返回开头元素
void Unshift (object value); // 在开头插入
void RemoveAt (int index); // 删除任意索引
void Clear (); // 清空数组
_I_Enumerable Slice (int start, int end); // 返回子数组
void Splice (int start, int deleteCount, object [] items); // 删除并插入
int IndexOf (object value); // 查找索引
bool Includes (object value); // 是否包含
void ForEach (object callback, bool suppressExceptions = false); // 遍历callback(item, index)
_I_Enumerable Concat (_I_Enumerable other); // 拼接
string Join (string separator); // 转字符串
object GetItem (int index); // 返回 { key, data } 或直接 data
void SetAt (int index, object value); // 替换元素
int IndexOfKey (int key); // 按内部 key 查找
void Move (int index, int newIndex); // 移动元素
void PushAll (object [] items); // 一次性 push 多个
}
public class _I_List: _I_Enumerable, IList
{
public _I_List (IEnumerable<object> initArr, bool readOnly = false, bool fixedSize = false, bool sync = true)
{
_list = initArr?.ToList () ?? new List<object> ();
IsFixedSize = fixedSize;
IsReadOnly = readOnly;
IsSynchronized = sync;
}
protected List<object> _list;
protected object _lock = new object ();
public object this [int index] { get { return _list [index]; } set { _list [index] = value; } }
public int Count => _list.Count;
public bool IsFixedSize { get; }
public bool IsReadOnly { get; }
public bool IsSynchronized { get; }
public int Length
{
get { return _list.Count; }
set
{
if (!IsFixedSize && !IsReadOnly)
{
_list.Capacity = value;
}
}
}
public object SyncRoot => _lock;
public void Add (object value) => _list.Add (value);
public void Push (object value) => _list.Add (value);
public void RemoveAt (int index) => _list.RemoveAt (index);
public void Clear () => _list.Clear ();
public int IndexOf (object value) => _list.IndexOf (value);
int IList.Add (object value)
{
_list.Add (value);
return _list.Count - 1;
}
public bool Contains (object value) => _list.Contains (value);
public void Insert (int index, object value) => _list.Insert (index, value);
public void Remove (object value) => _list.Remove (value);
public void ForEach (object callback, bool suppressExceptions = false)
{
for (int i = 0; i < _list.Count; i++)
{
var item = _list [i];
if (suppressExceptions)
{
try { JsUtils.Call (callback, item, i); } catch { }
}
else JsUtils.Call (callback, item, i);
}
}
public IEnumerator GetEnumerator () => _list.GetEnumerator ();
public _I_Enumerable Slice (int start, int end)
{
if (end < 0) end = _list.Count + end;
start = Math.Max (0, start);
end = Math.Min (_list.Count, end);
var arr = _list.GetRange (start, Math.Max (0, end - start));
return new _I_List (arr);
}
public void Splice (int start, int deleteCount, object [] items)
{
if (start < 0) start = Math.Max (0, _list.Count + start);
int count = Math.Min (deleteCount, _list.Count - start);
_list.RemoveRange (start, count);
if (items != null && items.Length > 0)
_list.InsertRange (start, items);
}
public bool Includes (object value) => _list.Contains (value);
public _I_Enumerable Concat (_I_Enumerable other)
{
var newList = new List<object> (_list);
if (other is _I_List)
newList.AddRange ((other as _I_List)._list);
return new _I_List (newList);
}
public string Join (string separator)
{
return string.Join (separator ?? ",", _list.Select (x => x?.ToString () ?? ""));
}
public object GetItem (int index) => _list [index];
public void SetAt (int index, object value) => _list [index] = value;
public int IndexOfKey (int key)
{
return key >= 0 && key < _list.Count ? key : -1;
}
public void Move (int index, int newIndex)
{
if (index < 0 || index >= _list.Count || newIndex < 0 || newIndex >= _list.Count) return;
var item = _list [index];
_list.RemoveAt (index);
_list.Insert (newIndex, item);
}
public void PushAll (object [] items)
{
if (items == null || items.Length == 0) return;
_list.AddRange (items);
}
public void CopyTo (Array array, int index)
{
if (array == null) throw new ArgumentNullException (nameof (array));
for (int i = 0; i < _list.Count && index + i < array.Length; i++)
array.SetValue (_list [i], index + i);
}
public object Pop ()
{
if (_list.Count == 0) return null;
var last = _list [_list.Count - 1];
_list.RemoveAt (_list.Count - 1);
return last;
}
public object Shift ()
{
if (_list.Count == 0) return null;
var first = _list [0];
_list.RemoveAt (0);
return first;
}
public void Unshift (object value) => _list.Insert (0, value);
}
}

View File

@@ -131,6 +131,8 @@ namespace DataUtils
[ClassInterface (ClassInterfaceType.AutoDual)]
public class _I_Process
{
public Process Start (string filename, string args) => Process.Start (filename, args);
public Process Open (string url) => Process.Start (url);
public int Run (
string cmdline,
string filepath,

View File

@@ -2,7 +2,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Newtonsoft.Json;
@@ -186,4 +190,194 @@ namespace DataUtils
object BuildJSON ();
}
}
public static class JsUtils
{
/// <summary>
/// 调用 JS 函数。第一个参数作为 this其余作为 JS 函数参数。
/// </summary>
/// <param name="callback">JS 函数对象</param>
/// <param name="args">JS 函数参数,可选</param>
/// <returns>JS 函数返回值</returns>
public static void Call (object jsFunc, params object [] args)
{
if (jsFunc == null) return;
object [] invokeArgs = new object [(args?.Length ?? 0) + 1];
invokeArgs [0] = jsFunc; // this
if (args != null)
for (int i = 0; i < args.Length; i++)
invokeArgs [i + 1] = args [i];
jsFunc.GetType ().InvokeMember (
"call",
BindingFlags.InvokeMethod,
null,
jsFunc,
invokeArgs);
}
}
[ComVisible (true)]
[InterfaceType (ComInterfaceType.InterfaceIsDual)]
public interface _I_IAsyncAction
{
_I_IAsyncAction Then (object resolve, object reject = null, object progress = null);
void Done (object resolve, object reject = null);
void Catch (object reject);
bool IsCompleted { get; }
bool IsCancelled { get; }
bool IsError { get; }
void Cancel ();
void ReportProgress (object progress);
object Error { get; }
}
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDual)]
public class _I_Task: _I_IAsyncAction
{
private object _result;
private Exception _error;
private bool _completed;
private bool _cancelled;
private List<object> _resolveCallbacks = new List<object> ();
private List<object> _rejectCallbacks = new List<object> ();
private List<object> _progressCallbacks = new List<object> ();
private CancellationTokenSource _cts = new CancellationTokenSource ();
public _I_Task (Func<object> func)
{
ThreadPool.QueueUserWorkItem (_ => {
try
{
if (_cts.Token.IsCancellationRequested)
{
_cancelled = true;
return;
}
_result = func ();
_completed = true;
foreach (var cb in _resolveCallbacks)
JsUtils.Call (cb, _result);
}
catch (Exception ex)
{
_error = ex;
foreach (var cb in _rejectCallbacks)
JsUtils.Call (cb, ex);
}
});
}
public _I_IAsyncAction Then (object resolve, object reject = null, object progress = null)
{
if (resolve != null) _resolveCallbacks.Add (resolve);
if (reject != null) _rejectCallbacks.Add (reject);
if (progress != null) _progressCallbacks.Add (progress);
return this;
}
public void Done (object resolve, object reject = null)
{
if (resolve != null) _resolveCallbacks.Add (resolve);
if (reject != null) _rejectCallbacks.Add (reject);
}
public void Catch (object reject)
{
if (reject != null) _rejectCallbacks.Add (reject);
}
public bool IsCompleted => _completed;
public bool IsCancelled => _cancelled;
public bool IsError => _error != null;
public object Error => _error;
public void Cancel ()
{
_cts.Cancel ();
_cancelled = true;
}
public void ReportProgress (object progress)
{
foreach (var cb in _progressCallbacks)
JsUtils.Call (cb, progress);
}
public object Result => _result;
}
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDual)]
public class _I_Thread: _I_IAsyncAction
{
private Thread _thread;
private bool _completed;
private bool _cancelled;
private Exception _error;
private List<object> _resolveCallbacks = new List<object> ();
private List<object> _rejectCallbacks = new List<object> ();
private List<object> _progressCallbacks = new List<object> ();
private CancellationTokenSource _cts = new CancellationTokenSource ();
public _I_Thread (Action action)
{
_thread = new Thread (() => {
try
{
if (_cts.Token.IsCancellationRequested)
{
_cancelled = true;
return;
}
action ();
_completed = true;
foreach (var cb in _resolveCallbacks)
JsUtils.Call (cb);
}
catch (Exception ex)
{
_error = ex;
foreach (var cb in _rejectCallbacks)
JsUtils.Call (cb, ex);
}
});
_thread.IsBackground = true;
_thread.Start ();
}
public _I_IAsyncAction Then (object resolve, object reject = null, object progress = null)
{
if (resolve != null) _resolveCallbacks.Add (resolve);
if (reject != null) _rejectCallbacks.Add (reject);
if (progress != null) _progressCallbacks.Add (progress);
return this;
}
public void Done (object resolve, object reject = null)
{
if (resolve != null) _resolveCallbacks.Add (resolve);
if (reject != null) _rejectCallbacks.Add (reject);
}
public void Catch (object reject)
{
if (reject != null) _rejectCallbacks.Add (reject);
}
public bool IsCompleted => _completed;
public bool IsCancelled => _cancelled;
public bool IsError => _error != null;
public object Error => _error;
public void Cancel ()
{
_cts.Cancel ();
_cancelled = true;
}
public void ReportProgress (object progress)
{
foreach (var cb in _progressCallbacks)
JsUtils.Call (cb, progress);
}
}
}

321
DataUtils/Web.cs Normal file
View File

@@ -0,0 +1,321 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using Newtonsoft.Json;
namespace DataUtils
{
[ComVisible (true)]
[InterfaceType (ComInterfaceType.InterfaceIsDual)]
public interface IHttpResponse: IDisposable
{
int Status { get; } // 兼容旧版,等同于 StatusCode
int StatusCode { get; }
string StatusText { get; } // 等同于 StatusDescription
string StatusDescription { get; }
string ResponseUrl { get; }
Uri ResponseUri { get; }
string ContentType { get; }
long ContentLength { get; } // 注意JS 中 Number 可表示 2^53 以内整数
string CharacterSet { get; }
string ContentEncoding { get; }
DateTime LastModified { get; }
string Method { get; } // 原始请求方法 (GET, POST...)
Version ProtocolVersion { get; } // 如 1.1JS 中可能转为字符串
bool IsFromCache { get; }
bool IsMutuallyAuthenticated { get; }
string Text ();
_I_Enumerable Bytes ();
}
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDual)]
public class HttpResponse: IHttpResponse, IDisposable
{
private readonly byte [] _data;
private readonly Dictionary<string, string> _headersDict;
private bool _disposed = false;
public int Status => StatusCode;
public int StatusCode { get; private set; }
public string StatusText => StatusDescription;
public string StatusDescription { get; private set; }
public string ResponseUrl => ResponseUri?.ToString ();
public Uri ResponseUri { get; private set; }
public string ContentType { get; private set; }
public long ContentLength { get; private set; }
public string CharacterSet { get; private set; }
public string ContentEncoding { get; private set; }
public DateTime LastModified { get; private set; }
public string Method { get; private set; }
public Version ProtocolVersion { get; private set; }
public bool IsFromCache { get; private set; }
public bool IsMutuallyAuthenticated { get; private set; }
public HttpResponse (HttpWebResponse response)
{
if (response == null) throw new ArgumentNullException (nameof (response));
using (var stream = response.GetResponseStream ())
using (var ms = new MemoryStream ())
{
stream.CopyTo (ms);
_data = ms.ToArray ();
}
StatusCode = (int)response.StatusCode;
StatusDescription = response.StatusDescription;
ResponseUri = response.ResponseUri;
Method = response.Method;
ProtocolVersion = new Version ((ushort)response.ProtocolVersion.Major, (ushort)response.ProtocolVersion.Minor, (ushort)response.ProtocolVersion.Build, (ushort)response.ProtocolVersion.Revision);
IsFromCache = response.IsFromCache;
IsMutuallyAuthenticated = response.IsMutuallyAuthenticated;
ContentType = response.ContentType ?? "";
ContentLength = response.ContentLength;
CharacterSet = response.CharacterSet ?? "";
ContentEncoding = response.ContentEncoding ?? "";
LastModified = response.LastModified;
_headersDict = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
foreach (string key in response.Headers.AllKeys)
{
if (!string.IsNullOrEmpty (key))
_headersDict [key] = response.Headers [key];
}
}
public HttpResponse (int statusCode, string statusDescription, string responseUrl, byte [] data, Dictionary<string, string> headers)
{
StatusCode = statusCode;
StatusDescription = statusDescription ?? "";
ResponseUri = string.IsNullOrEmpty (responseUrl) ? null : new Uri (responseUrl);
_data = data ?? new byte [0];
_headersDict = headers ?? new Dictionary<string, string> ();
ContentType = GetHeader ("Content-Type") ?? "";
ContentLength = _data.Length;
CharacterSet = "";
ContentEncoding = "";
LastModified = DateTime.MinValue;
Method = "";
ProtocolVersion = new Version (0, 0);
IsFromCache = false;
IsMutuallyAuthenticated = false;
}
public string GetHeader (string sName)
{
if (string.IsNullOrEmpty (sName)) return null;
try
{
return _headersDict [sName];
}
catch { return null; }
}
public string GetHeadersToJson ()
{
return JsonConvert.SerializeObject (_headersDict);
}
public string Text ()
{
string charset = CharacterSet;
Encoding enc = Encoding.UTF8;
if (!string.IsNullOrEmpty (charset))
{
try { enc = Encoding.GetEncoding (charset); }
catch { /* 保持 UTF-8 */ }
}
return enc.GetString (_data);
}
public _I_Enumerable Bytes ()
{
var list = new List<object> ();
foreach (byte b in _data)
list.Add (b);
return new _I_List (list);
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (!_disposed)
{
_disposed = true;
}
}
}
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDual)]
public class HttpRequest: IDisposable
{
private string _method;
private string _url;
private Dictionary<string, string> _headers = new Dictionary<string, string> ();
public int Timeout { get; set; } = 100000; // 毫秒,默认 100 秒
public int ReadWriteTimeout { get; set; } = 300000; // 读写超时,默认 5 分钟
public bool AllowAutoRedirect { get; set; } = true;
public bool AllowWriteStreamBuffering { get; set; } = true;
public bool KeepAlive { get; set; } = true;
public int MaximumAutomaticRedirections { get; set; } = 50;
public string UserAgent
{
get { return GetHeader ("User-Agent"); }
set { SetHeader ("User-Agent", value); }
}
public string Referer
{
get { return GetHeader ("Referer"); }
set { SetHeader ("Referer", value); }
}
public string ContentType
{
get { return GetHeader ("Content-Type"); }
set { SetHeader ("Content-Type", value); }
}
public string Accept
{
get { return GetHeader ("Accept"); }
set { SetHeader ("Accept", value); }
}
public IWebProxy Proxy { get; set; } = null;
public CookieContainer CookieContainer { get; set; } = null;
private string _httpver = "1.1";
public string ProtocolVersion
{
get { return _httpver; }
set { _httpver = value; }
}
public System.Version ProtVer
{
get
{
switch (_httpver)
{
case "1.0": return HttpVersion.Version10;
default:
case "1.1": return HttpVersion.Version11;
}
}
}
public bool PreAuthenticate { get; set; } = false;
public ICredentials Credentials { get; set; } = null;
public bool AutomaticDecompression { get; set; } = false;
public Action<long, long> UploadProgressCallback { get; set; } = null;
public void Open (string sMethod, string sUrl)
{
_method = sMethod;
_url = sUrl;
}
public void SetHeader (string sName, string sValue) => _headers [sName] = sValue;
public string GetHeader (string sName) => _headers [sName];
public string GetHeadersToJson () => JsonConvert.SerializeObject (_headers);
public void RemoveHeader (string sName) => _headers.Remove (sName);
public void ClearHeader () => _headers.Clear ();
public IHttpResponse Send (string sBody, string encoding)
{
var req = (HttpWebRequest)WebRequest.Create (_url);
req.Method = _method;
req.Timeout = Timeout;
req.ReadWriteTimeout = ReadWriteTimeout;
req.AllowAutoRedirect = AllowAutoRedirect;
req.AllowWriteStreamBuffering = AllowWriteStreamBuffering;
req.KeepAlive = KeepAlive;
req.MaximumAutomaticRedirections = MaximumAutomaticRedirections;
if (Proxy != null) req.Proxy = Proxy;
if (CookieContainer != null) req.CookieContainer = CookieContainer;
req.ProtocolVersion = ProtVer;
req.PreAuthenticate = PreAuthenticate;
if (Credentials != null) req.Credentials = Credentials;
if (AutomaticDecompression)
req.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (_headers.ContainsKey ("User-Agent"))
req.UserAgent = _headers ["User-Agent"];
foreach (var h in _headers)
{
if (string.Equals (h.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
continue;
if (string.Equals (h.Key, "Content-Type", StringComparison.OrdinalIgnoreCase))
{
string ct = h.Value;
if (!string.IsNullOrEmpty (sBody) && !ct.Contains ("charset"))
{
Encoding enc = Encoding.GetEncoding (encoding);
ct = ct + "; charset=" + enc.WebName;
}
req.ContentType = ct;
}
else if (string.Equals (h.Key, "Accept", StringComparison.OrdinalIgnoreCase))
{
req.Accept = h.Value;
}
else if (string.Equals (h.Key, "Referer", StringComparison.OrdinalIgnoreCase))
{
req.Referer = h.Value;
}
else
{
req.Headers [h.Key] = h.Value;
}
}
// 如果没有显式设置 Content-Type 且是 POST/PUT 且有请求体,设置默认值
bool hasContentType = _headers.Keys.Any (k => string.Equals (k, "Content-Type", StringComparison.OrdinalIgnoreCase));
if (!hasContentType && (string.Equals (_method, "POST", StringComparison.OrdinalIgnoreCase) ||
string.Equals (_method, "PUT", StringComparison.OrdinalIgnoreCase)) &&
!string.IsNullOrEmpty (sBody))
{
Encoding enc = Encoding.GetEncoding (encoding);
req.ContentType = "application/x-www-form-urlencoded; charset=" + enc.WebName;
}
// 写入请求体
if (!string.IsNullOrEmpty (sBody))
{
Encoding enc = Encoding.GetEncoding (encoding);
byte [] bytes = enc.GetBytes (sBody);
req.ContentLength = bytes.Length;
using (var stream = req.GetRequestStream ())
{
if (UploadProgressCallback != null)
{
int totalWritten = 0;
int bufferSize = 8192;
for (int offset = 0; offset < bytes.Length; offset += bufferSize)
{
int chunkSize = Math.Min (bufferSize, bytes.Length - offset);
stream.Write (bytes, offset, chunkSize);
totalWritten += chunkSize;
UploadProgressCallback (totalWritten, bytes.Length);
}
}
else
{
stream.Write (bytes, 0, bytes.Length);
}
}
}
using (var res = (HttpWebResponse)req.GetResponse ())
{
return new HttpResponse (res);
}
}
public void SendAsync (string sBody, string encoding, object pfResolve)
{
System.Threading.ThreadPool.QueueUserWorkItem (delegate
{
JsUtils.Call (pfResolve, Send (sBody, encoding));
});
}
public void Dispose () { }
}
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDual)]
public class _I_Http
{
public HttpRequest CreateHttpRequest () => new HttpRequest ();
}
[ComVisible (true)]
[ClassInterface (ClassInterfaceType.AutoDual)]
public class _I_Web
{
public _I_Http Http => new _I_Http ();
}
}

View File

@@ -60,9 +60,9 @@ namespace WAShell
if (!issetdpi)
{
issetdpi = true;
ExecScript ("Bridge.Frame.scale = Bridge.Frame.scale * Bridge.UI.dpi");
ExecScript ("if (typeof Bridge !== \"undefined\") Bridge.Frame.scale = Bridge.Frame.scale * Bridge.UI.dpi");
}
ExecScript ("Windows.UI.DPI.mode = 1");
ExecScript ("if (typeof Windows !== \"undefined\") Windows.UI.DPI.mode = 1");
if (e.Url.ToString () == webui.Url.ToString ())
{
splash.FadeOut ();

View File

@@ -49,6 +49,17 @@ bool IsURI (const std::wstring &str)
}
catch (...) { return false; }
}
std::wstring TrimQuotes (const std::wstring& str) {
if (str.empty ()) return str;
size_t len = str.size ();
size_t start = (str.front () == L'"') ? 1 : 0;
size_t end = len;
if (str.back () == L'"')
{
if (end > start) end = len - 1;
}
return str.substr (start, end - start);
}
enum class paramtype
{
string,
@@ -87,6 +98,7 @@ void ParseCmdArgs (LPWSTR *argv, DWORD argc, std::map <cmdkey, cmdvalue> &parser
arg = arg.trim ();
if (IsFile (arg)) parseresult [cmdkey (arg, paramtype::file)] = cmdvalue {L"", paramtype::file, true};
else if (IsURI (arg)) parseresult [cmdkey (arg, paramtype::uri)] = cmdvalue {L"", paramtype::uri, true};
else if (IsFile (TrimQuotes (arg))) parseresult [cmdkey (TrimQuotes (arg), paramtype::file)] = cmdvalue { L"", paramtype::file, true };
else
{
for (auto &it : g_argslist)
@@ -140,6 +152,11 @@ void ParseCmdArgs (LPWSTR *argv, DWORD argc, std::map <cmdkey, cmdvalue> &parser
auto value = rightpart.substr (valuehead);
paramtype ptype = paramtype::string;
if (IsFile (value)) ptype = paramtype::file;
else if (IsFile (TrimQuotes (value)))
{
value = TrimQuotes (value);
ptype = paramtype::file;
}
else if (IsURI (StringTrim (value))) ptype = paramtype::uri;
parseresult [cmdkey (it.uniquelabel, paramtype::string)] = cmdvalue {value, ptype, false};
}

View File

@@ -396,7 +396,7 @@
</div>
<div class="controls">
<div class="checkbox">
<input type="checkbox" id="preinst-enablelaunch" class="win-checkbox">
<input type="checkbox" id="preinst-enablelaunch" class="win-checkbox" style="margin-left: 0;">
<label for="preinst-enablelaunch" data-res-byname="IDS_LAUNCHWHENREADY"></label>
</div>
<div class="command">

View File

@@ -118,4 +118,12 @@
return Bridge.String.toupper(this);
};
}
if (typeof String.prototype.format !== "function") {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] !== "undefined" ? args[number] : match;
});
};
}
})(this);

View File

@@ -363,6 +363,19 @@
));
}
};
Object.defineProperty(this, "length", {
get: function() {
return _list.length;
},
enumerable: true,
});
this.getDatas = function() { return _list; }
this.forEach = function(callback, args) {
if (typeof callback !== "function") return;
for (var i = 0; i < _list.length; i++) {
callback.apply(this, [_list[i], i].concat(Array.prototype.slice.call(arguments, 1)));
}
};
this._getKey = getKey;
}
var MAX_ANIMATE_COUNT = 100;
@@ -609,10 +622,16 @@
});
PMDataListView.prototype._updateEmptyView = function() {
if (!this._emptyView) return;
// container 中是否还有 item
var hasItem = this.container.children.length > 0;
var itemVisibleLength = 0;
for (var i = 0; i < this.container.children.length; i++) {
var child = this.container.children[i];
if (!child) continue;
if (child.style.display !== "none" && child.style.display !== "hidden" && child.style.opacity !== 0 && child.style.visibility !== "hidden") {
itemVisibleLength++;
}
}
hasItem = hasItem && itemVisibleLength > 0;
if (hasItem) {
if (this._emptyView.parentNode) {
this._emptyView.style.display = "none";
@@ -802,6 +821,7 @@
};
PMDataListView.prototype.refresh = function() {
this._refreshVisibility();
this._updateEmptyView();
};
global.DataView.ChangeEvent = PMChangeEvent;
global.DataView.DataSource = PMDataSource;

562
shared/html/js/dboxapi.js Normal file
View File

@@ -0,0 +1,562 @@
/**
* DBox API
* Docs: https://dbox.tools/api/docs
*/
(function(global) {
"use strict";
function joinUrl(url, path) {
if (url.charAt(url.length - 1) === "/") {
url = url.slice(0, -1);
}
if (path.charAt(0) === "/") {
path = path.slice(1);
}
return url + "/" + path;
}
function buildParams(params) {
var queryString = "";
var keys = Object.keys(params);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = params[key];
if (value === null || value === void 0) {
continue;
}
queryString += encodeURIComponent(key) + "=" + encodeURIComponent(value) + "&";
}
return queryString.slice(0, -1);
}
var baseUrl = "https://dbox.tools/";
var baseApiUrl = joinUrl(baseUrl, "api");
var dboxApi = {
/**
* @enum {string} DBox.API.System
* @readonly
*/
System: {
/** @type {string} Xbox */
xbox: "XBOX",
/** @type {string} Xbox 360 */
xbox360: "XBOX360",
/** @type {string} Xbox One */
xboxOne: "XBOXONE",
/** @type {string} Xbox Series X */
xboxSeriesX: "XBOXSERIESX",
/** @type {string} PC */
pc: "PC",
/** @type {string} PS3 */
mobile: "MOBILE"
},
Discs: {
/**
* Get and filter discs
* @param {string | null} name query
* @param {DBox.API.System} system query
* @param {integer | number} [limit] query, default: 100
* @param {integer | number} [offset] query, default: 0
* @returns {string} request URL, method: GET
*/
getDiscs: function(name, system, limit, offset) {
var params = {};
if (name) params.name = name;
if (system) params.system = system;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "discs") +
"?" + buildParams(params);
},
/**
* Get single disc by DBox ID
* @param {integer | number} discId path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscById: function(discId) {
return joinUrl(baseApiUrl, "discs/" + discId);
},
/**
* Get single disc by Redump ID
* @param {integer | number} redumpId path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByRedumpId: function(redumpId) {
return joinUrl(baseApiUrl, "discs/redump/" + redumpId);
},
/**
* Get single disc by its XMID (OG XBOX only)
* @param {string} xmid path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByXMId: function(xmid) {
return joinUrl(baseApiUrl, "discs/xmid/" + xmid);
},
/**
* Get single disc by its XeMID (XBOX 360 only)
* @param {string} xeMid path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByXeMId: function(xeMid) {
return joinUrl(baseApiUrl, "discs/xemid/" + xeMid);
},
/**
* Get single disc by its media ID. Media ID v1 (XBOX & XBOX 360) and Media ID v2 (XBOX One & XBOX Series) are combined in a single query
* @param {string} mediaId path, required
* @returns {string} request URL, method: GET
*/
getSingleDiscByMediaId: function(mediaId) {
return joinUrl(baseApiUrl, "discs/media_id/" + mediaId);
},
/**
* Get the dirs and files (if available) for a disc. Filetype indicates if it is a supported filetype that has further metadata available in the database.
* @param {integer | number} discId path, required
* @returns {string} request URL, method: GET
*/
getDiscFiles: function(discId) {
return joinUrl(baseApiUrl, "discs/" + discId + "/files");
}
},
Releases: {
/**
* Get and filter releases
* @param {string | null} name query
* @param {string | null} edition query
* @param {string | null} barcode query
* @param {DBox.API.System} system query
* @param {integer | number} limit query, default: 100
* @param {integer | number} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getReleases: function(name, edition, barcode, system, limit, offset) {
var params = {};
if (name) params.name = name;
if (edition) params.edition = edition;
if (barcode) params.barcode = barcode;
if (system) params.system = system;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "releases") +
"?" + buildParams(params);
},
/**
* Get single release by DBox ID
* @param {string} releaseId path, required
* @returns {string} request URL, method: GET
*/
getSingleRelease: function(releaseId) {
return joinUrl(baseApiUrl, "releases/" + releaseId);
}
},
Files: {
/**
* Get all discs that contain a particular file. NOTE: not all discs have been parsed on file-level yet
* @param {string} md5 path, required
* @returns {string} request URL, method: GET
*/
getFiles: function(md5) {
return joinUrl(baseApiUrl, "files/" + md5 + "/discs");
},
/**
* Filter all distinct xbe files. NOTE: not all discs have been parsed on file-level yet
* @param {string | null} titleId query
* @param {integer | null} allowrdMedia query
* @param {integer | null} region query, Available values: 1, 2, 3, 4, 7, 2147483648
* @param {integer | null} gameRating query
* @param {integer | null} discNumber query
* @param {integer | null} version query
* @param {integer | number} limit query, default: 100
* @param {integer | number} offset query, default: 0
* @returns
*/
getXbeFiles: function(titleId, allowrdMedia, region, gameRating, discNumber, version, limit, offset) {
if (region !== null && region !== undefined) {
var valid = [1, 2, 3, 4, 7, 2147483648];
if (!valid.includes(region)) throw new Error('Invalid region');
}
var params = {};
if (titleId) params.title_id = titleId;
if (allowrdMedia) params.allowrd_media = allowrdMedia;
if (region) params.region = region;
if (gameRating) params.game_rating = gameRating;
if (discNumber) params.disc_number = discNumber;
if (version) params.version = version;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "files/xbe") +
"?" + buildParams(params);
},
/**
* Get xbe file by md5. NOTE: not all discs have been parsed on file-level yet
* @param {string} md5 path, required
* @returns {string} request URL, method: GET
*/
getXbeFileByMd5: function(md5) {
return joinUrl(baseApiUrl, "files/xbe/" + md5);
},
/**
* Get stfs file by md5. NOTE: not all discs have been parsed on file-level yet
* @param {string} md5 path, required
* @returns {string} request URL, method: GET
*/
getStfsFileByMd5: function(md5) {
return joinUrl(baseApiUrl, "files/stfs/" + md5);
},
},
TitleIDs: {
/**
* Get and filter title IDs
* @param {string | (string | null)} name query
* @param {DBox.API.System | null} system query
* @param {string | (string | null)($uuid)} bingId query
* @param {string | (string | null)($uuid)} serviceConfigId query
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getTitleIds: function(name, system, bingId, serviceConfigId, limit, offset) {
var params = {};
if (name) params.name = name;
if (system) params.system = system;
if (bingId) params.bing_id = bingId;
if (serviceConfigId) params.service_config_id = serviceConfigId;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "title_ids") +
"?" + buildParams(params);
},
/**
* Get single title ID by its hexadecimal value
* @param {string} titleId path, required
* @returns {string} request URL, method: GET
*/
getSingleTitleId: function(titleId) {
return joinUrl(baseApiUrl, "title_ids/" + titleId);
}
},
Achievements: {
/**
* Get achievements for a title-id. Xbox 360/GFWL only
* @param {string} titleId path, required
* @returns {string} request URL, method: GET
*/
getAchievementsV1: function(titleId) {
return joinUrl(baseApiUrl, "achievements/v1/" + titleId);
},
/**
* Get achievements for a title-id. Xbox One/Series only
* @param {string} titleId path, required
* @returns {string} request URL, method: GET
*/
getAchievementsV2: function(titleId) {
return joinUrl(baseApiUrl, "achievements/v2/" + titleId);
},
},
Marketplace: {
/**
* Get and filter marketplace products
* @param {integer | (integer | null)} productType query
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getProducts: function(productType, limit, offset) {
var params = {};
if (productType) params.product_type = productType;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "marketplace/products") +
"?" + buildParams(params);
},
/**
* Get single marketplace product by marketplace product ID
* @param {string} productId path, required
* @returns {string} request URL, method: GET
*/
getSingleProduct: function(productId) {
return joinUrl(baseApiUrl, "marketplace/products/" + productId);
},
/**
* Get children of a marketplace product
* @param {string} productId path, required
* @param {integer | (integer | null)} productType query
* @returns {string} request URL, method: GET
*/
getProductChildren: function(productId, productType) {
var params = {};
if (productType) params.product_type = productType;
return joinUrl(baseApiUrl, "marketplace/products/" + productId + "/children") +
"?" + buildParams(params);
},
/**
* Get and filter marketplace product instances
* @param {string | (string | null)($uuid)} productId query
* @param {string | (string | null)} hexOfferId query
* @param {integer | (integer | null)} licenseTypeId query
* @param {integer | (integer | null)} packageType query
* @param {integer | (integer | null)} gameRegion query
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getProductInstances: function(productId, hexOfferId, licenseTypeId, packageType, gameRegion, limit, offset) {
var params = {};
if (productId) params.product_id = productId;
if (hexOfferId) params.hex_offer_id = hexOfferId;
if (licenseTypeId) params.license_type_id = licenseTypeId;
if (packageType) params.package_type = packageType;
if (gameRegion) params.game_region = gameRegion;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "marketplace/product-instances") +
"?" + buildParams(params);
},
/**
* Get single marketplace product instance by marketplace product instance ID
* @param {string} instanceId path, required
* @returns {string} request URL, method: GET
*/
getProductInstanceById: function(instanceId) {
return joinUrl(baseApiUrl, "marketplace/product-instances/" + instanceId);
},
/**
* Get all marketplace product types
* @returns {string} request URL, method: GET
*/
getProductTypes: function() {
return joinUrl(baseApiUrl, "marketplace/product-types");
},
/**
* Get single marketplace category
* @param {integer} productTypeId path, required
* @returns {string} request URL, method: GET
*/
getSingleProductType: function(productTypeId) {
return joinUrl(baseApiUrl, "marketplace/product-types/" + productTypeId);
},
/**
* Get and filter all marketplace categories
* @param {integer | (integer | null)} parent query
* @returns {string} request URL, method: GET
*/
getCategories: function(parent) {
var params = {};
if (parent) params.parent = parent;
return joinUrl(baseApiUrl, "marketplace/categories") +
"?" + buildParams(params);
},
/**
* Get single marketplace category
* @param {integer} categoryId path, required
* @returns {string} request URL, method: GET
*/
getSingleCategory: function(categoryId) {
return joinUrl(baseApiUrl, "marketplace/categories/" + categoryId);
},
/**
* Get all marketplace locales
* @returns {string} request URL, method: GET
*/
getLocales: function() {
return joinUrl(baseApiUrl, "marketplace/locales");
},
/**
* Get single marketplace locale by locale string
* @param {string} localeId path, required
* @returns {string} request URL, method: GET
*/
getSingleLocale: function(localeId) {
return joinUrl(baseApiUrl, "marketplace/locales/" + localeId);
},
},
/**
* Enum for product types, used in store API
* @enum {string} DBox.API.ProductType
*/
ProductType: {
application: "Application",
avatarItem: "AvatarItem",
consumable: "Consumable",
durable: "Durable",
game: "Game",
movie: "Movie",
pass: "PASS",
tvSeries: "TvSeries",
tvSeason: "TvSeason",
tvEpisode: "TVEpisode",
unmanagedConsumable: "UnmanagedConsumable",
},
/**
* Enum for product families, used in store API
* @enum {string} DBox.API.ProductFamily
*/
ProductFamily: {
apps: "Apps",
avatars: "Avatars",
games: "Games",
movies: "Movies",
passes: "Passes",
tv: "TV",
},
/**
* Enum for order by, used in store API
* @enum {string} DBox.API.OrderBy
*/
OrderBy: {
productId: "product_id",
titleId: "title_id",
revisionId: "revision_id",
/** such as "23654onetwoonestudio.cctv_kdpw61jgbrs34" */
packageFamilyName: "package_family_name",
/** such as "23654onetwoonestudio.cctv" */
packageIdentityName: "package_identity_name",
},
/**
* Enum for order direction, used in store API
* @enum {string} DBox.API.OrderDirection
*/
OrderDirection: {
/** @type {string} 升序 */
asc: "asc",
/** @type {string} 降序 */
desc: "desc",
},
Store: {
/**
* Get store products
* @param {string | (string | null)} category query
* @param {string | (string | null)} titleId query
* @param {DBox.API.ProductType | null} productType query
* @param {DBox.API.ProductFamily | null} productFamily query
* @param {DBox.API.OrderBy | null} orderBy query, default: productId
* @param {DBox.API.OrderDirection | null} orderDirection query, default: asc
* @param {integer} limit query, default: 100
* @param {integer} offset query, default: 0
* @returns {string} request URL, method: GET
*/
getProducts: function(category, titleId, productType, productFamily, orderBy, orderDirection, limit, offset) {
var params = {};
if (category) params.category = category;
if (titleId) params.title_id = titleId;
if (productType) params.product_type = productType;
if (productFamily) params.product_family = productFamily;
if (orderBy) params.order_by = orderBy;
if (orderDirection) params.order_direction = orderDirection;
if (limit) params.limit = limit;
if (offset) params.offset = offset;
return joinUrl(baseApiUrl, "store/products") +
"?" + buildParams(params);
},
/**
* Get single store product
* @param {string} productId path, required
* @returns {string} request URL, method: GET
*/
getSingleProduct: function(productId) {
return joinUrl(baseApiUrl, "store/products/" + productId);
},
/**
* Get all related products for a product id. Includes both child and parent relationships. Check the product-ids for relationship direction. The relationship_type is parent -> child direction. Same combinations can appear multiple times with different relationship types.
* @param {string} productId path, required
* @returns {string} request URL, method: GET
*/
getAllReleatedProducts: function(productId) {
return joinUrl(baseApiUrl, "store/products/" + productId + "/related");
},
/**
* Get single sku for store product
* @param {string} productId path, required
* @param {string} skuId path, required
* @returns {string} request URL, method: GET
*/
getSingleSkuFromProduct: function(productId, skuId) {
return joinUrl(baseApiUrl, "store/products/" + productId + "/sku/" + skuId);
}
}
};
function getXhr() {
var xmlhttp;
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
}
/**
* Send an HTTP request to the DBox API and return a Promise that resolves with the response.
* @param {string} api DBox API
* @param {string} method
* @param {[boolean]} isAsync default: true
* @param {[string]} username
* @param {[string]} pwd
* @returns {Promise <XMLHttpRequest>} A Promise that resolves with the response.
*/
var dboxXHR = function(api, method, isAsync, username, pwd) {
method = method || "GET";
if (typeof isAsync === "undefined" || isAsync === null) isAsync = true;
var xhr = getXhr();
if (username && pwd) {
try {
xhr.open(method, api, isAsync, username, pwd);
} catch (e) {
xhr.open(method, api, isAsync);
}
} else {
xhr.open(method, api, isAsync);
}
return new Promise(function(c, e) {
try {
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (c) c(xhr);
} else {
if (e) e(xhr.statusText + " (" + xhr.status + ")");
}
}
};
xhr.send("");
} catch (ex) {
if (e) e(ex);
}
});
};
/**
* Send an HTTP request to the DBox API and return a Promise that resolves with the response as JSON.
* @param {string} api DBox API
* @param {string} method
* @param {[boolean]} isAsync default: true
* @param {[string]} username
* @param {[string]} pwd
* @returns {Promise <object>} A Promise that resolves with the response as JSON.
*/
dboxXHR.parseJson = function(api, method, isAsync, username, pwd) {
return dboxXHR(api, method, isAsync, username, pwd).then(function(xhr) {
return JSON.parse(xhr.responseText);
});
}
/**
* DBox namespace
* @namespace {DBox}
*/
global.DBox = {
/**
* DBox API namespace
* @namespace {DBox.API}
*/
API: dboxApi,
/**
* @function {DBox.XHR}
*/
xhr: dboxXHR,
};
})(this);

206
shared/html/js/http.js Normal file
View File

@@ -0,0 +1,206 @@
(function(global) {
if (typeof global.Web === "undefined") global.Web = {};
function dataToString(obj) {
if (typeof obj === "string") return obj;
if (typeof obj === "object") return JSON.stringify(obj);
return String(obj);
}
global.Web.Http = {
Request: function() {
var inst = external.Web.Http.createHttpRequest();
this.open = function(method, url) { inst.open(method, url); };
this.getHeaders = function() { return JSON.parse(inst.getHeadersToJson()); };
this.getHeader = function(swName) { try { return inst.getHeader(swName); } catch (e) { return void 0; } };
this.setHeader = function(swName, swValue) { inst.setHeader(swName, swValue); };
this.removeHeader = function(swName) { inst.removeHeader(swName); };
this.clearHeader = function() { inst.clearHeader(); };
this.updateHeaders = function(headers) {
var keys = Object.keys(headers);
this.clearHeader();
for (var i = 0; i < keys.length; i++) {
this.setHeader(keys[i], dataToString(headers[keys[i]]));
}
};
this.setHeaders = function(headers) {
var keys = Object.keys(headers);
for (var i = 0; i < keys.length; i++) {
inst.setHeader(keys[i], dataToString(headers[keys[i]]));
}
};
this.send = function(body, encoding) { return inst.send(body, encoding); };
this.sendAsync = function(body, encoding) {
if (!encoding) encoding = "utf-8";
return new Promise(function(c, e) {
try {
inst.sendAsync(body, encoding, function(resp) {
if (c) c(resp);
});
} catch (ex) { if (e) e(ex); }
});
};
Object.defineProperty(this, "timeout", {
get: function() { return inst.timeout; },
set: function(value) { inst.timeout = value; }
});
Object.defineProperty(this, "readWriteTimeout", {
get: function() { return inst.readWriteTimeout; },
set: function(value) { inst.readWriteTimeout = value; }
});
Object.defineProperty(this, "allowAutoRedirect", {
get: function() { return inst.allowAutoRedirect; },
set: function(value) { inst.allowAutoRedirect = value; }
});
Object.defineProperty(this, "allowWriteStreamBuffering", {
get: function() { return inst.allowWriteStreamBuffering; },
set: function(value) { inst.allowWriteStreamBuffering = value; }
});
Object.defineProperty(this, "keepAlive", {
get: function() { return inst.keepAlive; },
set: function(value) { inst.keepAlive = value; }
});
Object.defineProperty(this, "maximumAutomaticRedirections", {
get: function() { return inst.maximumAutomaticRedirections; },
set: function(value) { inst.maximumAutomaticRedirections = value; }
});
Object.defineProperty(this, "userAgent", {
get: function() { return inst.userAgent; },
set: function(value) { inst.userAgent = value; }
});
Object.defineProperty(this, "referer", {
get: function() { return inst.referer; },
set: function(value) { inst.referer = value; }
});
Object.defineProperty(this, "contentType", {
get: function() { return inst.contentType; },
set: function(value) { inst.contentType = value; }
});
Object.defineProperty(this, "accept", {
get: function() { return inst.accept; },
set: function(value) { inst.accept = value; }
});
Object.defineProperty(this, "proxy", {
get: function() { return inst.proxy; },
set: function(value) { inst.proxy = value; }
});
Object.defineProperty(this, "cookieContainer", {
get: function() { return inst.cookieContainer; },
set: function(value) { inst.cookieContainer = value; }
});
Object.defineProperty(this, "protocolVersion", {
get: function() { return inst.protocolVersion; },
set: function(value) { inst.protocolVersion = value; }
});
Object.defineProperty(this, "preAuthenticate", {
get: function() { return inst.preAuthenticate; },
set: function(value) { inst.preAuthenticate = value; }
});
Object.defineProperty(this, "credentials", {
get: function() { return inst.credentials; },
set: function(value) { inst.credentials = value; }
});
Object.defineProperty(this, "automaticDecompression", {
get: function() { return inst.automaticDecompression; },
set: function(value) { inst.automaticDecompression = value; }
});
this.dispose = function() { inst.dispose(); };
},
Header: {
// 通用头(既可用于请求也可用于响应)
General: {
cacheControl: "Cache-Control",
connection: "Connection",
date: "Date",
pragma: "Pragma",
trailer: "Trailer",
transferEncoding: "Transfer-Encoding",
upgrade: "Upgrade",
via: "Via",
warning: "Warning"
},
// 请求头
Request: {
accept: "Accept",
acceptCharset: "Accept-Charset",
acceptEncoding: "Accept-Encoding",
acceptLanguage: "Accept-Language",
authorization: "Authorization",
expect: "Expect",
from: "From",
host: "Host",
ifMatch: "If-Match",
ifModifiedSince: "If-Modified-Since",
ifNoneMatch: "If-None-Match",
ifRange: "If-Range",
ifUnmodifiedSince: "If-Unmodified-Since",
maxForwards: "Max-Forwards",
proxyAuthorization: "Proxy-Authorization",
range: "Range",
referer: "Referer",
te: "TE",
userAgent: "User-Agent"
},
// 响应头
Response: {
acceptRanges: "Accept-Ranges",
age: "Age",
allow: "Allow",
contentEncoding: "Content-Encoding",
contentLanguage: "Content-Language",
contentLength: "Content-Length",
contentLocation: "Content-Location",
contentRange: "Content-Range",
contentType: "Content-Type",
etag: "ETag",
expires: "Expires",
lastModified: "Last-Modified",
location: "Location",
proxyAuthenticate: "Proxy-Authenticate",
retryAfter: "Retry-After",
server: "Server",
setCookie: "Set-Cookie",
vary: "Vary",
wwwAuthenticate: "WWW-Authenticate"
},
// CORS 相关头(常单独列出,也可归入请求/响应)
Cors: {
accessControlAllowOrigin: "Access-Control-Allow-Origin",
accessControlAllowCredentials: "Access-Control-Allow-Credentials",
accessControlAllowHeaders: "Access-Control-Allow-Headers",
accessControlAllowMethods: "Access-Control-Allow-Methods",
accessControlExposeHeaders: "Access-Control-Expose-Headers",
accessControlMaxAge: "Access-Control-Max-Age",
accessControlRequestHeaders: "Access-Control-Request-Headers",
accessControlRequestMethod: "Access-Control-Request-Method",
origin: "Origin"
},
// 安全/非标准常用头
Security: {
xFrameOptions: "X-Frame-Options",
xContentTypeOptions: "X-Content-Type-Options",
xXssProtection: "X-XSS-Protection",
strictTransportSecurity: "Strict-Transport-Security",
contentSecurityPolicy: "Content-Security-Policy",
referrerPolicy: "Referrer-Policy",
xRequestedWith: "X-Requested-With",
xForwardedFor: "X-Forwarded-For",
xForwardedProto: "X-Forwarded-Proto",
xRealIp: "X-Real-IP"
}
},
Method: {
get: "GET",
post: "POST",
put: "PUT",
delete: "DELETE",
head: "HEAD",
options: "OPTIONS",
trace: "TRACE",
connect: "CONNECT"
},
Version: {
v1_0: "1.0",
v1_1: "1.1",
}
}
})(this);

View File

@@ -162,15 +162,16 @@
}
oldTags.push(k);
promises.push(
anime.runAsync(p.page, [
anime.Keyframes.Opacity.hidden
]).then((function(page, key) {
return function() {
page.style.display = "none";
page.style.opacity = 0;
emit("unload", key);
};
})(p.page, k))
(function(page, key) {
// 返回 anime.runAsync 产生的 Promise
return anime.runAsync(page, [anime.Keyframes.Opacity.hidden])
.then(function() {
page.style.display = "none";
page.style.opacity = 0;
page.style.height = 0;
emit("unload", key);
});
})(p.page, k)
);
p.guide.classList.remove("selected");
}

View File

@@ -52,7 +52,13 @@
},
cancelAll: function() { external.Package.Reader.cancelAll(); },
addApplicationReadItem: function(swItemName) { return external.Package.Reader.addApplicationItem(swItemName); },
removeApplicationReadItem: function(swItemName) { return external.Package.Reader.removeApplicationItem(swItemName); }
removeApplicationReadItem: function(swItemName) { return external.Package.Reader.removeApplicationItem(swItemName); },
updateApplicationReadItems: function(aswArray) {
external.Package.Reader.updateApplicationItems(aswArray);
},
getApplicationReadItems: function() {
return JSON.parse(external.Package.Reader.getApplicationItemsToJson());
},
},
manager: {
add: function(swPkgPath, uOptions) {
@@ -169,5 +175,76 @@
cancelAll: function() { mgr.cancelAll(); },
active: function(swAppUserModelID, swArgs) { return mgr.activeApp(swAppUserModelID, swArgs || null); }
},
utils: (function() {
// 解析包全名,格式: <name>_<version>_<processorArchitecture>_<resourceId>_<publisherId>
// resourceId 可能为空(表现为两个连续下划线)
function parsePackageFullName(fullName) {
var parts = fullName.split('_');
// 按格式应有5部分: [name, version, architecture, resourceId, publisherId]
return {
name: parts[0],
version: parts[1],
architecture: parts[2],
resourceId: parts[3],
publisherId: parts[4]
};
}
// 解析包系列名,格式: <name>_<publisherId>
function parsePackageFamilyName(familyName) {
var underscoreIndex = familyName.indexOf('_');
if (underscoreIndex === -1) {
// 异常情况,按原字符串处理,但题目不会出现
return { name: familyName, publisherId: '' };
}
return {
name: familyName.substring(0, underscoreIndex),
publisherId: familyName.substring(underscoreIndex + 1)
};
}
// 将对象转换为包全名字符串
// 对象应包含 name, version, architecture, publisherId 字段resourceId 可选
function stringifyPackageFullName(pkg) {
var resourcePart = (pkg.resourceId === undefined || pkg.resourceId === null) ? '' : pkg.resourceId;
return pkg.name + '_' + pkg.version + '_' + pkg.architecture + '_' + resourcePart + '_' + pkg.publisherId;
}
// 将对象转换为包系列名字符串
// 对象应包含 name, publisherId 字段
function stringifyPackageFamilyName(pkgFamily) {
return pkgFamily.name + '_' + pkgFamily.publisherId;
}
return {
parsePackageFullName: parsePackageFullName,
parsePackageFamilyName: parsePackageFamilyName,
stringifyPackageFullName: stringifyPackageFullName,
stringifyPackageFamilyName: stringifyPackageFamilyName,
isDependency: function(swPkgIdentityName) {
var list = [
"Microsoft.Net.Native.Framework",
"Microsoft.Net.Native.Runtime",
"Microsoft.Net.CoreRuntime",
"WindowsPreview.Kinect",
"Microsoft.VCLibs",
"Microsoft.WinJS",
"Microsoft.UI.Xaml",
"Microsoft.WindowsAppRuntime",
"Microsoft.Advertising.XAML",
"Microsoft.Midi.Gmdls",
"Microsoft.Services.Store.Engagement",
"Microsoft.Media.PlayReadyClient"
];
for (var i = 0; i < list.length; i++) {
if (swPkgIdentityName.toLowerCase().indexOf(list[i].toLowerCase()) !== -1)
return true;
}
return false;
}
}
})()
};
global.Package.Reader = global.Package.reader;
global.Package.Manager = global.Package.manager;
global.Package.Utils = global.Package.utils;
})(this);

View File

@@ -530,3 +530,101 @@
global.Set = Set;
}
})(this);
(function(global) {
// 如果原生 WeakMap 已存在,则不覆盖
if (typeof global.WeakMap !== "undefined") {
return;
}
function WeakMap(iterable) {
// 必须使用 new 调用
if (!(this instanceof WeakMap)) {
throw new TypeError('Constructor WeakMap requires "new"');
}
// 私有存储:每个实例独立维护一个键值对数组
var entries = [];
// 验证 key 必须是对象或函数(不能是 null 或原始值)
function validateKey(key) {
if (key === null || (typeof key !== 'object' && typeof key !== 'function')) {
throw new TypeError('WeakMap key must be an object');
}
}
// 设置键值对
this.set = function(key, value) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
entries[i][1] = value;
return this;
}
}
entries.push([key, value]);
return this;
};
// 获取键对应的值
this.get = function(key) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
return entries[i][1];
}
}
return undefined;
};
// 判断是否存在指定键
this.has = function(key) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
return true;
}
}
return false;
};
// 删除指定键及其值
this.delete = function(key) {
validateKey(key);
for (var i = 0; i < entries.length; i++) {
if (entries[i][0] === key) {
entries.splice(i, 1);
return true;
}
}
return false;
};
// 处理可选的初始化参数iterable例如 [[key1, value1], [key2, value2]]
if (iterable !== null && iterable !== undefined) {
// 支持数组或类数组(具备 forEach 方法的对象)
if (typeof iterable.forEach === 'function') {
var self = this;
iterable.forEach(function(item) {
if (!Array.isArray(item) || item.length < 2) {
throw new TypeError('Iterator value is not an entry object');
}
self.set(item[0], item[1]);
});
} else if (typeof iterable.length === 'number') {
// 类数组对象(如 arguments
for (var i = 0; i < iterable.length; i++) {
var item = iterable[i];
if (!Array.isArray(item) || item.length < 2) {
throw new TypeError('Iterator value is not an entry object');
}
this.set(item[0], item[1]);
}
} else {
throw new TypeError('WeakMap iterable is not iterable');
}
}
}
// 挂载到全局对象
global.WeakMap = WeakMap;
})(this);

View File

@@ -502,6 +502,22 @@
if (!p.removeEventListener) p.removeEventListener = PromisePolyfill.removeEventListener;
if (!p.dispatchEvent) p.dispatchEvent = PromisePolyfill.dispatchEvent;
if (!p.onerror) p.onerror = null;
if (typeof p.prototype.then !== "function") {
p.prototype.then = function(onFulfilled, onRejected) {
return new p(function(resolve, reject) {
this.then(resolve, reject);
}).then(onFulfilled, onRejected);
};
}
if (typeof p.prototype.done !== "function") {
p.prototype.done = function(onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)["catch"](function(ex) {
setTimeout(function() {
throw ex;
}, 0);
});
};
}
}
if (typeof global.WinJS !== "undefined" && typeof global.WinJS.Promise !== "undefined") {
var wp = global.WinJS.Promise;

View File

@@ -25,6 +25,7 @@
});
var themeColor = Bridge.UI.themeColor;
pagemgr.register("reader", document.getElementById("tag-reader"), document.getElementById("page-reader"));
pagemgr.register("acquire", document.getElementById("tag-acquire"), document.getElementById("page-acquire"));
pagemgr.go("reader");
});
})(this);

1357
shared/html/js/storergapi.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -352,6 +352,13 @@ aside>nav ul li div[role=img] {
}
.ispage {
width: 100%;
height: auto;
box-sizing: border-box;
opacity: 1;
transition: all 0.4s cubic-bezier(0.1, 0.9, 0.2, 1);
}
.ispage.padding {
padding: 44px 60px;
}

View File

@@ -2,7 +2,7 @@
<html>
<head>
<title>Package Manager</title>
<title>Package Reader</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -32,6 +32,9 @@
<script type="text/javascript" src="libs/msgbox/msgbox.js"></script>
<script type="text/javascript" src="js/init.js"></script>
<script type="text/javascript" src="js/pkginfo.js"></script>
<script type="text/javascript" src="js/http.js"></script>
<script type="text/javascript" src="js/storergapi.js"></script>
<script type="text/javascript" src="js/dboxapi.js"></script>
<script type="text/javascript" src="js/datasrc.js"></script>
<script type="text/javascript" src="js/appbar.js"></script>
<script type="text/javascript" src="js/pagemgr.js"></script>
@@ -55,8 +58,8 @@
<body>
<div id="readerpage" class="pagecontainer full">
<div class="page full guide fold">
<main class="main padding">
<div id="page-reader" style="display: none;" class="ispage">
<main class="main">
<div id="page-reader" style="display: none;" class="ispage padding">
<h2>读取</h2>
<p>请选择一个包,获取其信息。</p>
<div>
@@ -365,6 +368,292 @@
})(this);
</script>
</div>
<div id="page-acquire" style="display: none;" class="ispage padding">
<h2>获取</h2>
<div id="acquire-forbidden" style="width: 100%;">
<p>由于来自 store.rg-adguard.net 的限制,现在暂时无法实现对包的获取。请自行打开下面 URL 进行访问。</p>
<a onclick="external.Process.open ('https://store.rg-adguard.net')">store.rg-adguard.net</a>
</div>
<div id="acquire-enable" style="width: 100%;">
<p>请在下面的输入框中输入要查询的内容,设置好参数后将进行查询。</p>
<div>
<div>
<input type="text" id="acquire-input">
<select id="acquire-valuetype" name="type">
<option value="url">分享链接</option>
<option value="ProductId">产品 ID</option>
<option value="PackageFamilyName">包系列名</option>
<option value="CategoryId">类别 ID</option>
</select>
<select id="acquire-channel" name="ring">
<option title="Windows Insider Fast" value="WIF">快速</option>
<option title="Windows Insider Slow" value="WIS">慢速</option>
<option title="Release Preview" value="RP" selected>发布预览</option>
<option title="Default OS" value="Retail">正式</option>
</select>
</div>
<div>
<div class="itemrow">
<input type="checkbox" id="acquire-smartquery" style="margin-left: 0px;">
<label for="acquire-smartquery">自动查询</label>
</div>
<button id="acquire-query">查询</button>
</div>
</div>
<style>
.acquire-item {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-content: flex-start;
justify-content: flex-start;
width: 100%;
margin: 5px 0;
box-sizing: border-box;
padding: 5px;
border: 1px solid #ccc;
-ms-user-select: element;
user-select: all;
}
.acquire-item #name {
font-size: 11pt;
width: 100%;
line-height: 1.3em;
overflow-x: hidden;
overflow-y: hidden;
text-overflow: ellipsis;
font-weight: normal;
}
.acquire-item .medium {
width: 100%;
font-size: 10pt;
line-height: 1.2em;
font-weight: normal;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: center;
justify-content: space-between;
align-items: center;
margin-top: 3px;
margin-bottom: 3px;
}
.acquire-item .medium div {
text-align: center;
flex: 0 0 auto;
}
.acquire-item .medium #ext {
text-align: left;
max-width: 100px;
/*min-width: 97px;*/
}
.acquire-item .medium div:nth-child(1),
.acquire-item .medium div:nth-child(3),
.acquire-item .medium div:nth-child(5) {
background-color: #e9fff5;
}
.acquire-item .medium div:nth-child(2),
.acquire-item .medium div:nth-child(4),
.acquire-item .medium div:nth-child(6) {
background-color: #ffffef;
}
.acquire-item .bottom {
font-weight: bold;
font-size: 9pt;
display: flex;
flex-direction: row;
align-content: center;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.acquire-item #hashpart {
flex: 1;
overflow-y: hidden;
overflow-x: hidden;
text-overflow: ellipsis;
}
.acquire-item:hover {
background-color: rgba(0, 0, 0, 0.122);
}
.acquire-item #download {
cursor: pointer;
font-size: 10pt;
}
</style>
<div id="acquire-template" class="acquire-item" style="display: none;">
<div id="name" class="top" title="Identity Name">Identity Name</div>
<div class="medium">
<div id="ext" title="File Type">Appx</div>
<div id="version" title="Version">1.0.0.0</div>
<div id="architecture" title="Processor Architecture">neutral</div>
<div id="publisherId" title="Identity Publisher Id"></div>
<div id="size" title="File Size"></div>
</div>
<div class="bottom">
<div id="hashpart"><span>SHA-1: </span><span id="hash"></span></div>
<div><a download id="download" href="">点击下载</a></div>
</div>
</div>
<div id="acquire-loading">
<br>
<div class="container itemrow">
<progress class="win-ring" style="margin-right: 10px;"></progress>
<span class="win-label title" data-res-resxml="MANAGER_APP_INSTALLEDAPPS_LOADING"></span>
<br>
</div>
</div>
<div id="acquire-result" style="width: 100%;">
<p style="font-weight: normal;" title="Category ID"><span>类别 ID</span>: <span id="acquire-categoryid" style="user-select: text; -ms-user-select: element;"></span></p>
<h3>以下可能为检索到的应用</h3>
<div id="acquire-list-app" style="width: 100%;"></div>
<h3>以下可能为检索到的依赖项</h3>
<div id="acquire-list-dep" style="width: 100%;"></div>
</div>
<script>
(function(global) {
var conf = external.Config.current;
var set = conf.getSection("Settings");
var isForbidden = !set.getKey("EnableAcquire").readBool(false);
var acquireInput = document.getElementById("acquire-input");
var acquireValuetype = document.getElementById("acquire-valuetype");
var acquireChannel = document.getElementById("acquire-channel");
var acquireSmartquery = document.getElementById("acquire-smartquery");
var acquireQuery = document.getElementById("acquire-query");
var acquireResult = document.getElementById("acquire-result");
var acquireListApp = document.getElementById("acquire-list-app");
var acquireListDep = document.getElementById("acquire-list-dep");
var acquireForbidden = document.getElementById("acquire-forbidden");
var acquireEnable = document.getElementById("acquire-enable");
var acquireCategoryid = document.getElementById("acquire-categoryid");
var acquireTemplate = document.getElementById("acquire-template");
var acquireLoading = document.getElementById("acquire-loading");
var acquireLoadingRing = acquireLoading.querySelector(".win-ring");
var acquireLoadingLabel = acquireLoading.querySelector(".win-label");
acquireForbidden.style.display = isForbidden ? "" : "none";
acquireEnable.style.display = isForbidden ? "none" : "";
acquireResult.style.display = isForbidden ? "none" : "none";
var dataSrc = {
apps: new DataView.DataSource(),
deps: new DataView.DataSource()
};
var putils = Package.Utils;
var templateFunc = function(item, index) {
var node = acquireTemplate.cloneNode(true);
node.style.display = "";
node.id = "";
var lastDotIndex = item.file.lastIndexOf(".");
var fileNamePart = lastDotIndex !== -1 ? item.file.substring(0, lastDotIndex) : item.file;
var fileExtension = lastDotIndex !== -1 ? item.file.substring(lastDotIndex + 1) : "";
var identityName = putils.parsePackageFullName(fileNamePart);
node.querySelector("#name").textContent = identityName.name;
node.querySelector("#ext").textContent = fileExtension;
node.querySelector("#version").textContent = identityName.version;
node.querySelector("#architecture").textContent = identityName.architecture;
node.querySelector("#publisherId").textContent = identityName.publisherId;
node.querySelector("#size").textContent = item.size;
node.querySelector("#hash").textContent = item.sha1;
node.title = item.file;
node.querySelector("#download").href = item.url;
node.querySelector("#download").title = item.url;
return node;
};
var listView = {
apps: new DataView.ListView(acquireListApp, templateFunc),
deps: new DataView.ListView(acquireListDep, templateFunc)
};
listView.apps.bind(dataSrc.apps);
listView.deps.bind(dataSrc.deps);
var keys = Object.keys(listView);
keys.forEach(function(k, i) {
var listview = listView[k];
var p = document.createElement("p");
p.textContent = "还没有内容...";
listview.emptyView = p;
});
acquireLoading.statusBar = new TransitionPanel(acquireLoading, {
axis: "y",
speed: 500,
});
acquireSmartquery.onchange = function() {
acquireValuetype.disabled =
acquireChannel.disabled = this.checked;
};
acquireSmartquery.checked = set.getKey("AcquireSmartQuery").readBool(false);
acquireValuetype.disabled =
acquireChannel.disabled = acquireSmartquery.checked;
acquireQuery.onclick = function() {
var self = this;
self.disabled = true;
acquireResult.style.display = "none";
acquireLoadingRing.style.display = "";
acquireLoading.statusBar.show();
var queryFunc = StoreRG.xhr.parse;
if (acquireSmartquery.checked) {
queryFunc = StoreRG.xhr.smartQuery;
}
acquireInput.disabled =
acquireSmartquery.disabled =
acquireValuetype.disabled =
acquireChannel.disabled = true;
queryFunc = StoreRG.test;
acquireLoadingLabel.textContent = "正在查询...";
queryFunc(acquireInput.value, acquireValuetype.value, acquireChannel.value).then(function(result) {
acquireCategoryid.textContent = result.categoryId;
var applist = [];
var deplist = [];
result.datas.forEach(function(item, index) {
if (item.file.indexOf(".BlockMap") >= 0) return;
if (putils.isDependency(item.file)) {
deplist.push(item);
} else {
applist.push(item);
}
});
dataSrc.apps.updateList(applist, function(item) {
return item.file;
});
listView.apps.refresh();
dataSrc.deps.updateList(deplist, function(item) {
return item.file;
});
listView.deps.refresh();
acquireLoadingLabel.textContent = "已获取到 {0} 个信息"
.replace("{0}", applist.length + deplist.length);
}, function(err) {
dataSrc.apps.clear();
dataSrc.deps.clear();
listView.apps.refresh();
listView.deps.refresh();
acquireLoadingLabel.textContent = err.message || err;
return Promise.wrap();
}).done(function() {
acquireResult.style.display = "";
setTimeout(function() {
acquireLoading.statusBar.hide();
}, 10000);
self.disabled = false;
acquireLoadingRing.style.display = "none";
acquireInput.disabled =
acquireSmartquery.disabled = false;
acquireValuetype.disabled =
acquireChannel.disabled = acquireSmartquery.checked;
});
}
})(this);
</script>
</div>
</div>
</main>
<aside class="win-ui-dark">
<nav class="container">
@@ -390,6 +679,10 @@
<div role="img">&#57650;</div>
<span class="win-type-base" data-res-resxml="MANAGER_MANAGE"></span>
</li>
<li id="tag-acquire">
<div role="img">&#57624;</div>
<span class="win-type-base" data-res-resxml="MANAGER_MANAGE"></span>
</li>
<li id="tag-settings">
<div role="img">&#57621;</div>
<span class="win-type-base" data-res-resxml="MANAGER_SETTINGS"></span>