Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
SlimeNull committed Mar 26, 2024
2 parents 1893f6f + f22e75e commit 678cc73
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>

<!-- 自动复制NuGet包到输出目录 -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<ItemGroup>
<Compile Include="SHLoadIndirectStringList.fs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\CurvaLauncher.Plugins\CurvaLauncher.Plugins.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="8.0.101" />
</ItemGroup>


<Target Name="CopyFSharpCore" AfterTargets="Build">
<Copy SourceFiles="$(OutDir)\FSharp.Core.dll" DestinationFiles="..\..\CurvaLauncher\bin\$(Configuration)\$(TargetFramework)\Libraries\FSharp.Core.dll"></Copy>
</Target>

<Target Name="CopyOutputDebug" AfterTargets="Build" Condition="'$(Configuration)'=='Debug'">
<Copy SourceFiles="$(OutDir)\$(MSBuildProjectName).dll" DestinationFiles="..\..\CurvaLauncher\bin\$(Configuration)\$(TargetFramework)\Plugins\$(MSBuildProjectName).dll"></Copy>
<Copy SourceFiles="$(OutDir)\$(MSBuildProjectName).pdb" DestinationFiles="..\..\CurvaLauncher\bin\$(Configuration)\$(TargetFramework)\Plugins\$(MSBuildProjectName).pdb"></Copy>
</Target>

<Target Name="CopyOutputRelease" AfterTargets="Build" Condition="'$(Configuration)'=='Release'">
<Copy SourceFiles="$(OutDir)\$(MSBuildProjectName).dll" DestinationFiles="..\..\CurvaLauncher\bin\$(Configuration)\$(TargetFramework)\AdditionalPlugins\$(MSBuildProjectName).dll"></Copy>
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -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<string> // 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
)

[<DllImport("shlwapi.dll", EntryPoint = "SHLoadIndirectString", CharSet = CharSet.Unicode, ExactSpelling = true)>]
static extern uint SHLoadIndirectString(string pszSource, char& pszOutBuf, int cchOutBuf, nativeint ppvReserved)

static let GetIndirectString (str: string) : ValueOption<string> =
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 []
9 changes: 9 additions & 0 deletions src/CurvaLauncher.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down

0 comments on commit 678cc73

Please sign in to comment.