From f22e75e2d13df9a9590a666b820a037af1650065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E4=BB=99=E5=A5=B3?= Date: Fri, 26 Jan 2024 19:45:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=96=B0=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96SHLoadIndirectStringList=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...er.Plugins.SHLoadIndirectStringList.fsproj | 37 +++++ .../SHLoadIndirectStringList.fs | 155 ++++++++++++++++++ src/CurvaLauncher.sln | 9 + 3 files changed, 201 insertions(+) create mode 100644 src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/CurvaLauncher.Plugins.SHLoadIndirectStringList.fsproj create mode 100644 src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/SHLoadIndirectStringList.fs diff --git a/src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/CurvaLauncher.Plugins.SHLoadIndirectStringList.fsproj b/src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/CurvaLauncher.Plugins.SHLoadIndirectStringList.fsproj new file mode 100644 index 0000000..d0be99b --- /dev/null +++ b/src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/CurvaLauncher.Plugins.SHLoadIndirectStringList.fsproj @@ -0,0 +1,37 @@ + + + + Library + net8.0-windows + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/SHLoadIndirectStringList.fs b/src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/SHLoadIndirectStringList.fs new file mode 100644 index 0000000..c543c9e --- /dev/null +++ b/src/AdditionalPlugins/CurvaLauncher.Plugins.SHLoadIndirectStringList/SHLoadIndirectStringList.fs @@ -0,0 +1,155 @@ +namespace CurvaLauncher.Plugins.SHLoadIndirectStringList +open CurvaLauncher.Plugins +open CurvaLauncher +open System.Runtime.InteropServices +open System +open Microsoft.FSharp.NativeInterop +open System.Reflection +open System.IO +open System.Linq +open Microsoft.Win32 + +#nowarn "9" // NativePtr + +// 资源项 +type ResourceItem = + { + PackageName: string // Registry SubKey + IdentityName: Option // Package/Identity/@Name + ResourceNames: {| Name:string; FullName:string |} array // + } + +// 查询结果 +type public StringQueryResult(title, desc, weight) = + interface IQueryResult with + member this.Title: string = title + member this.Description: string = desc + member this.Weight: float32 = weight + member this.Icon: Windows.Media.ImageSource = null + +// 插件 +type public SHLoadIndirectStringPlugin(context: CurvaLauncherContext) = + inherit SyncPlugin(context) + + static do + AppDomain.CurrentDomain.add_AssemblyResolve(fun _ args -> + let asmName = new AssemblyName(args.Name) + let dllName = "FSharp.Core.dll" + if asmName.Name = "FSharp.Core" then + [| + (Path.Combine(AppContext.BaseDirectory, dllName)) + (Path.Combine(AppContext.BaseDirectory, "Libraries", dllName)); + (Path.Combine(Directory.GetCurrentDirectory(), dllName)) + |].Where(File.Exists).First() |> Assembly.LoadFrom + else + null + ) + + [] + static extern uint SHLoadIndirectString(string pszSource, char& pszOutBuf, int cchOutBuf, nativeint ppvReserved) + + static let GetIndirectString (str: string) : ValueOption = + let trim = str.AsSpan().Trim() + if trim.StartsWith("@{") && trim.EndsWith("}") + then + let ptr = NativePtr.stackalloc 1024; + let buf = NativePtr.toByRef ptr; + if SHLoadIndirectString(new string(trim), &buf, 1024, 0) = 0u + then ValueSome(new string(ptr)) + else ValueNone + else + ValueNone + + static let GetUwpResourceStrings() = + use packagesKey = Registry.ClassesRoot.OpenSubKey("Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages") + let names = packagesKey.GetSubKeyNames() + seq { + for packageName in names do + let manifestFile = Path.Combine(string (packagesKey.OpenSubKey(packageName).GetValue("PackageRootFolder")), "AppxManifest.xml") + let def() = { PackageName = packageName; ResourceNames = Array.empty; IdentityName = None } + if File.Exists(manifestFile) then + try + let xml = Xml.XmlDocument() + xml.Load(manifestFile) + let ns = Xml.XmlNamespaceManager(xml.NameTable) + ns.AddNamespace("ns", "http://schemas.microsoft.com/appx/manifest/foundation/windows10") + ns.AddNamespace("uap", "http://schemas.microsoft.com/appx/manifest/uap/windows10") + + let properties = xml.SelectSingleNode("/ns:Package/ns:Properties", ns) + let identity = xml.SelectSingleNode("/ns:Package/ns:Identity", ns) + let identityName = identity.Attributes["Name"].Value + let start = "ms-resource:" + + let r = seq { + let resources = + seq { + properties.SelectSingleNode("ns:DisplayName/text()", ns) + properties.SelectSingleNode("ns:Description/text()", ns) + properties.SelectSingleNode("ns:PublisherDisplayName/text()", ns) + } + |> Seq.map (fun node -> ValueOption.ofObj node) + |> Seq.map (fun nodeOpt -> nodeOpt |> ValueOption.bind (fun v -> (ValueOption.ofObj v.Value))) + |> Seq.where _.IsSome + |> Seq.distinct + + for node in resources do + match node with + | ValueSome res when res.StartsWith(start) -> + let fullName = + if node.Value.StartsWith("ms-resource://") then node.Value + else sprintf "@{%s?ms-resource://%s/Resources/%O}" packageName identityName (res.AsMemory start.Length) + {| Name = node.Value; FullName = fullName |} + | _ -> () + } + yield { PackageName = packageName; ResourceNames = r |> Seq.toArray; IdentityName = Some(identityName) } + with + | _ -> yield def() + else + yield def() + } |> Seq.toArray + + let mutable cache : ResourceItem[] = [||] + + let GetCompletion (str: string) = + seq { + for res in cache do + let mutable flag = false + for item in res.ResourceNames do + if item.FullName.StartsWith(str) then + flag <- true + yield item.FullName + + if flag = false then + if res.PackageName.AsSpan().StartsWith(str.AsSpan(2)) + then yield res.PackageName |> sprintf "@{%s" + else () + } + + override this.get_Name() = "IndirectString查询" + override this.get_Description() = "查询SHLoadIndirectString的资源字符串" + override this.get_Icon() = null + + override this.Initialize() = + base.Initialize() + let resources = GetUwpResourceStrings() + cache <- resources + #if DEBUG + for res in resources do + let reses = res.ResourceNames |> Seq.map _.FullName + sprintf "%s -> %s" res.PackageName (String.Join(" | ", reses)) + |> System.Diagnostics.Debug.WriteLine + #endif + + override this.Finish() = + base.Finish() + + override this.Query(query: string) : IQueryResult seq = + let trim = query.AsSpan().Trim() + if trim.StartsWith("@{") + then + seq { + match GetIndirectString(query) with + | ValueSome str -> yield StringQueryResult(query, str.Trim(), 1.0f) + | _ -> yield! GetCompletion(query) |> Seq.map (fun v -> StringQueryResult(v, "", 0.9f) :> IQueryResult) + } + else [] diff --git a/src/CurvaLauncher.sln b/src/CurvaLauncher.sln index 762de65..1cd010b 100644 --- a/src/CurvaLauncher.sln +++ b/src/CurvaLauncher.sln @@ -31,6 +31,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CurvaLauncher.Plugins.ZXing EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CurvaLauncher.Plugins.Test", "Plugins\CurvaLauncher.Plugins.Test\CurvaLauncher.Plugins.Test.csproj", "{F3F6F783-4636-457F-80E1-CC489F524B43}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdditionalPlugins", "AdditionalPlugins", "{4A86F98E-B276-4F75-9847-8D0E4280D887}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "CurvaLauncher.Plugins.SHLoadIndirectStringList", "AdditionalPlugins\CurvaLauncher.Plugins.SHLoadIndirectStringList\CurvaLauncher.Plugins.SHLoadIndirectStringList.fsproj", "{8CFC1C29-51AA-45ED-A91F-01F513182002}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +93,10 @@ Global {F3F6F783-4636-457F-80E1-CC489F524B43}.Debug|Any CPU.Build.0 = Debug|Any CPU {F3F6F783-4636-457F-80E1-CC489F524B43}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3F6F783-4636-457F-80E1-CC489F524B43}.Release|Any CPU.Build.0 = Release|Any CPU + {8CFC1C29-51AA-45ED-A91F-01F513182002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CFC1C29-51AA-45ED-A91F-01F513182002}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CFC1C29-51AA-45ED-A91F-01F513182002}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CFC1C29-51AA-45ED-A91F-01F513182002}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -103,6 +111,7 @@ Global {2782D69A-3CE8-4222-83BE-50C6DBC4571F} = {BAACD50D-2F94-4A65-8B13-49031D617CAC} {3781D17E-F339-4933-85BC-D821C5FCC131} = {BAACD50D-2F94-4A65-8B13-49031D617CAC} {F3F6F783-4636-457F-80E1-CC489F524B43} = {BAACD50D-2F94-4A65-8B13-49031D617CAC} + {8CFC1C29-51AA-45ED-A91F-01F513182002} = {4A86F98E-B276-4F75-9847-8D0E4280D887} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FC4E11A-3D67-43DE-84D8-DCA1841F0D71}