diff --git a/src/CurvaLauncher.Common/CurvaLauncher.Common.csproj b/src/CurvaLauncher.Common/CurvaLauncher.Common.csproj
index be1f7b8..6e5b9d2 100644
--- a/src/CurvaLauncher.Common/CurvaLauncher.Common.csproj
+++ b/src/CurvaLauncher.Common/CurvaLauncher.Common.csproj
@@ -1,5 +1,7 @@
+
+
net8.0-windows
enable
@@ -11,6 +13,18 @@
true
CurvaLauncher
true
+ Common library of CurvaLauncher
+ git
+ https://github.com/OrgEleCho/CurvaLauncher
+ Icon128.png
+ MIT
+
+
+ True
+ \
+
+
+
diff --git a/src/CurvaLauncher.Plugins/CurvaLauncher.Plugins.csproj b/src/CurvaLauncher.Plugins/CurvaLauncher.Plugins.csproj
index fd26206..54821ee 100644
--- a/src/CurvaLauncher.Plugins/CurvaLauncher.Plugins.csproj
+++ b/src/CurvaLauncher.Plugins/CurvaLauncher.Plugins.csproj
@@ -1,14 +1,30 @@
-
+
+
+
net8.0-windows
enable
enable
- true
CurvaLauncher
+
+ true
true
+
+ Plugin library of CurvaLauncher
+ git
+ https://github.com/OrgEleCho/CurvaLauncher
+ Icon128.png
+ MIT
+
+
+ True
+ \
+
+
+
diff --git a/src/CurvaLauncher.props b/src/CurvaLauncher.props
new file mode 100644
index 0000000..97d1ea8
--- /dev/null
+++ b/src/CurvaLauncher.props
@@ -0,0 +1,5 @@
+
+
+ 0.7.1-beta
+
+
\ No newline at end of file
diff --git a/src/CurvaLauncher.sln b/src/CurvaLauncher.sln
index deac774..fcc589e 100644
--- a/src/CurvaLauncher.sln
+++ b/src/CurvaLauncher.sln
@@ -37,6 +37,15 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "CurvaLauncher.Plugins.SHLoa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CurvaLauncher.Plugins.Everything", "Plugins\CurvaLauncher.Plugins.Everything\CurvaLauncher.Plugins.Everything.csproj", "{C7AC6C75-89C6-42E5-8D2A-D06994949F05}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F36EDE5B-9C67-44F4-87A9-792390FECF85}"
+ ProjectSection(SolutionItems) = preProject
+ CurvaLauncher.props = CurvaLauncher.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Templates", "Templates", "{45D0660D-436E-4419-AEB9-B6ED5BC3E0ED}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurvaLauncher.PluginTemplate", "Templates\CurvaLauncher.PluginTemplate\CurvaLauncher.PluginTemplate.csproj", "{702E5FF3-99D0-4FD3-86E2-04C0A2E82560}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -165,6 +174,14 @@ Global
{C7AC6C75-89C6-42E5-8D2A-D06994949F05}.Release|Any CPU.Build.0 = Release|Any CPU
{C7AC6C75-89C6-42E5-8D2A-D06994949F05}.Release|x64.ActiveCfg = Release|x64
{C7AC6C75-89C6-42E5-8D2A-D06994949F05}.Release|x64.Build.0 = Release|x64
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Debug|x64.Build.0 = Debug|Any CPU
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Release|Any CPU.Build.0 = Release|Any CPU
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Release|x64.ActiveCfg = Release|Any CPU
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -181,6 +198,7 @@ Global
{F3F6F783-4636-457F-80E1-CC489F524B43} = {BAACD50D-2F94-4A65-8B13-49031D617CAC}
{8CFC1C29-51AA-45ED-A91F-01F513182002} = {4A86F98E-B276-4F75-9847-8D0E4280D887}
{C7AC6C75-89C6-42E5-8D2A-D06994949F05} = {BAACD50D-2F94-4A65-8B13-49031D617CAC}
+ {702E5FF3-99D0-4FD3-86E2-04C0A2E82560} = {45D0660D-436E-4419-AEB9-B6ED5BC3E0ED}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3FC4E11A-3D67-43DE-84D8-DCA1841F0D71}
diff --git a/src/CurvaLauncher/CurvaLauncher.csproj b/src/CurvaLauncher/CurvaLauncher.csproj
index cb1ae56..916982a 100644
--- a/src/CurvaLauncher/CurvaLauncher.csproj
+++ b/src/CurvaLauncher/CurvaLauncher.csproj
@@ -1,5 +1,7 @@
+
+
WinExe
net8.0-windows
@@ -12,8 +14,6 @@
app.manifest
Assets\Icon.ico
- 0.7.1-beta
-
true
true
diff --git a/src/CurvaLauncher/Services/PluginService.cs b/src/CurvaLauncher/Services/PluginService.cs
index 408ed99..616ff50 100644
--- a/src/CurvaLauncher/Services/PluginService.cs
+++ b/src/CurvaLauncher/Services/PluginService.cs
@@ -14,6 +14,8 @@
using CurvaLauncher.Plugins;
using CurvaLauncher.PluginInteraction;
using System.Diagnostics;
+using System.IO.Compression;
+using System.Runtime.Loader;
namespace CurvaLauncher.Services;
@@ -50,65 +52,123 @@ private void CoreLoadPlugins(out List plugins)
plugins = new List();
var dir = EnsurePluginDirectory();
- var dllFiles = dir.GetFiles("*.dll");
AppConfig config = _configService.Config;
- foreach (FileInfo dllFile in dllFiles)
- if (CoreLoadPlugin(config, dllFile.FullName, out PluginInstance? plugin))
+ foreach (var dllFile in dir.GetFiles("*.dll"))
+ {
+ if (CoreLoadDllPlugin(config, dllFile.FullName, out PluginInstance? plugin))
+ {
+ plugins.Add(plugin);
+ }
+ }
+
+ foreach (var zipFile in dir.GetFiles("*.zip"))
+ {
+ if (CoreLoadZipPlugin(config, zipFile.FullName, out PluginInstance? plugin))
{
plugins.Add(plugin);
}
+ }
}
- private bool CoreLoadPlugin(AppConfig config, string dllFilePath, [NotNullWhen(true)] out PluginInstance? pluginInstance)
+ private bool CoreLoadPluginFromAssembly(AppConfig config, Assembly assembly, [NotNullWhen(true)] out PluginInstance? pluginInstance)
{
pluginInstance = null;
- try
- {
- var assembly = Assembly.LoadFile(dllFilePath);
-
- Type? pluginType = assembly.ExportedTypes
+ Type? pluginType = assembly.ExportedTypes
.Where(type => type.IsAssignableTo(typeof(ISyncPlugin)) || type.IsAssignableTo(typeof(IAsyncPlugin)))
.FirstOrDefault();
- if (pluginType == null)
- return false;
+ if (pluginType == null)
+ return false;
- if (!PluginInstance.TryCreate(pluginType, out pluginInstance))
- return false;
+ if (!PluginInstance.TryCreate(pluginType, out pluginInstance))
+ return false;
- var typeName = pluginType.FullName!;
+ var typeName = pluginType.FullName!;
- if (config.Plugins.TryGetValue(typeName, out var pluginConfig))
- {
- var props = pluginInstance.Plugin.GetType().GetProperties()
+ if (config.Plugins.TryGetValue(typeName, out var pluginConfig))
+ {
+ var props = pluginInstance.Plugin.GetType().GetProperties()
.Where(p => p.GetCustomAttribute() is not null
|| p.GetCustomAttribute() is not null);
- if (pluginConfig.Options != null)
+ if (pluginConfig.Options != null)
+ {
+ foreach (var property in props)
{
- foreach (var property in props)
+ if (pluginConfig.Options.TryGetPropertyValue(property.Name, out var value))
{
- if (pluginConfig.Options.TryGetPropertyValue(property.Name, out var value))
- {
- var type = property.PropertyType;
- var val = JsonSerializer.Deserialize(value, type);
- property.SetValue(pluginInstance.Plugin, val);
- }
+ var type = property.PropertyType;
+ var val = JsonSerializer.Deserialize(value, type);
+ property.SetValue(pluginInstance.Plugin, val);
}
}
-
- pluginInstance.IsEnabled = pluginConfig.IsEnabled;
- pluginInstance.Weight = pluginConfig.Weight;
}
- else
+
+ pluginInstance.IsEnabled = pluginConfig.IsEnabled;
+ pluginInstance.Weight = pluginConfig.Weight;
+ }
+ else
+ {
+ pluginInstance.IsEnabled = true;
+ }
+
+ return true;
+ }
+
+ private bool CoreLoadDllPlugin(AppConfig config, string dllFilePath, [NotNullWhen(true)] out PluginInstance? pluginInstance)
+ {
+ pluginInstance = null;
+
+ try
+ {
+ var assembly = Assembly.LoadFile(dllFilePath);
+ return CoreLoadPluginFromAssembly(config, assembly, out pluginInstance);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Plugin load failed, {ex}");
+ return false;
+ }
+ }
+
+ private bool CoreLoadZipPlugin(AppConfig config, string zipFilePath, [NotNullWhen(true)] out PluginInstance? pluginInstance)
+ {
+ pluginInstance = null;
+
+ try
+ {
+ using var zipFile = File.OpenRead(zipFilePath);
+ using var zipArchive = new ZipArchive(zipFile, ZipArchiveMode.Read);
+ var assemblyLoadContext = new AssemblyLoadContext(null, false);
+
+ foreach (var entry in zipArchive.Entries)
{
- pluginInstance.IsEnabled = true;
+ if (!entry.FullName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ using var entryStream = entry.Open();
+
+ try
+ {
+ var assembly = assemblyLoadContext.LoadFromStream(entryStream);
+
+ if (pluginInstance is null)
+ {
+ CoreLoadPluginFromAssembly(config, assembly, out pluginInstance);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"DLL load failed, {ex}");
+ }
}
- return true;
+ return pluginInstance is not null;
}
catch (Exception ex)
{
diff --git a/src/Templates/CurvaLauncher.PluginTemplate/.template.config/template.json b/src/Templates/CurvaLauncher.PluginTemplate/.template.config/template.json
new file mode 100644
index 0000000..21072b2
--- /dev/null
+++ b/src/Templates/CurvaLauncher.PluginTemplate/.template.config/template.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "EleCho",
+ "identity": "CurvaLauncher.PluginTemplate",
+ "classifications": [
+ "CurvaLauncher",
+ "Library"
+ ],
+ "name": "CurvaLauncher Plugin",
+ "description": "Creates a CurvaLauncher plugin",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "shortName": "clp",
+ "sourceName": "CurvaLauncher.PluginTemplate",
+ "symbols": {
+ "EnableI18n": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "true"
+ }
+ },
+ "sources": [
+ {
+ "modifiers": [
+ {
+ "condition": "(!EnableI18n)",
+ "exclude": [
+ "I18n/EnUs.xaml",
+ "I18n/ZhHans.xaml"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Templates/CurvaLauncher.PluginTemplate/CurvaLauncher.PluginTemplate.csproj b/src/Templates/CurvaLauncher.PluginTemplate/CurvaLauncher.PluginTemplate.csproj
new file mode 100644
index 0000000..b3183c3
--- /dev/null
+++ b/src/Templates/CurvaLauncher.PluginTemplate/CurvaLauncher.PluginTemplate.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net8.0-windows
+ enable
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
diff --git a/src/Templates/CurvaLauncher.PluginTemplate/I18n/EnUs.xaml b/src/Templates/CurvaLauncher.PluginTemplate/I18n/EnUs.xaml
new file mode 100644
index 0000000..a069afb
--- /dev/null
+++ b/src/Templates/CurvaLauncher.PluginTemplate/I18n/EnUs.xaml
@@ -0,0 +1,9 @@
+
+ Sample Plugin
+ This is a plugin with i18n support
+
+ Test Plugin
+ Your plugin is running well
+
\ No newline at end of file
diff --git a/src/Templates/CurvaLauncher.PluginTemplate/I18n/ZhHans.xaml b/src/Templates/CurvaLauncher.PluginTemplate/I18n/ZhHans.xaml
new file mode 100644
index 0000000..4bc7010
--- /dev/null
+++ b/src/Templates/CurvaLauncher.PluginTemplate/I18n/ZhHans.xaml
@@ -0,0 +1,9 @@
+
+ 示例插件
+ 这是一个支持本地化的插件
+
+ 测试插件
+ 你的插件运行良好
+
\ No newline at end of file
diff --git a/src/Templates/CurvaLauncher.PluginTemplate/SomePlugin.cs b/src/Templates/CurvaLauncher.PluginTemplate/SomePlugin.cs
new file mode 100644
index 0000000..bfee57a
--- /dev/null
+++ b/src/Templates/CurvaLauncher.PluginTemplate/SomePlugin.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using CurvaLauncher;
+using CurvaLauncher.Plugins;
+
+namespace CurvaLauncher.PluginTemplate
+{
+#if (!EnableI18n)
+ public class SomePlugin : Plugin, ISyncPlugin
+ {
+ public override string Name => "Sample Plugin"; // Plugin Name
+ public override string Description => "Plugin Description"; // Plugin Description
+
+ public override ImageSource Icon { get; }
+ = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgra32, null); // Empty Icon
+
+ public SomePlugin(CurvaLauncherContext context) : base(context)
+ {
+ // With CurvaLauncherContext, you can use some APIs
+ // provided by CurvaLauncher
+
+ }
+
+ public IEnumerable Query(string query)
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ yield break;
+ }
+
+ yield return new EmptyQueryResult("Test Plugin OK", $"Your plugin {Name} is running well", 1, null);
+ }
+ }
+#else
+ public class SomePlugin : SyncI18nPlugin
+ {
+ private readonly CurvaLauncherContext _context;
+ private readonly Assembly _currentAssembly;
+
+ public override object NameKey => "StrPluginName"; // Plugin name resource key
+ public override object DescriptionKey => "StrPluginDescription"; // Plugin description resource key
+
+ public override ImageSource Icon { get; }
+ = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgra32, null); // Empty Icon
+
+ public SomePlugin(CurvaLauncherContext context) : base(context)
+ {
+ _context = context;
+ _currentAssembly = Assembly.GetExecutingAssembly();
+
+ // With CurvaLauncherContext, you can use some APIs
+ // provided by CurvaLauncher
+
+ }
+
+ public override IEnumerable GetI18nResourceDictionaries()
+ {
+ yield return I18nResourceDictionary.Create(new CultureInfo("en-US"), "I18n/EnUs.xaml");
+ yield return I18nResourceDictionary.Create(new CultureInfo("zh-Hans"), "I18n/ZhHans.xaml");
+ }
+
+ public override IEnumerable Query(string query)
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ yield break;
+ }
+
+ yield return new EmptyQueryResult(
+ _context.GetI18nResourceString(_currentAssembly, "StrTestResultTitle")!,
+ _context.GetI18nResourceString(_currentAssembly, "StrTestResultDescription")!,
+ 1,
+ null);
+ }
+ }
+#endif
+}
diff --git a/src/Templates/Templates.sln b/src/Templates/Templates.sln
new file mode 100644
index 0000000..47b5534
--- /dev/null
+++ b/src/Templates/Templates.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CurvaLauncher.I18nPluginTemplate", "CurvaLauncher.I18nPluginTemplate\CurvaLauncher.I18nPluginTemplate.csproj", "{6DE03DDB-7AD5-49B0-B212-07C15D8E1F8C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CurvaLauncher.PluginTemplate", "CurvaLauncher.PluginTemplate\CurvaLauncher.PluginTemplate.csproj", "{709E311C-73D1-438E-B77A-5919CEFDCD72}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6DE03DDB-7AD5-49B0-B212-07C15D8E1F8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6DE03DDB-7AD5-49B0-B212-07C15D8E1F8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6DE03DDB-7AD5-49B0-B212-07C15D8E1F8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6DE03DDB-7AD5-49B0-B212-07C15D8E1F8C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {709E311C-73D1-438E-B77A-5919CEFDCD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {709E311C-73D1-438E-B77A-5919CEFDCD72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {709E311C-73D1-438E-B77A-5919CEFDCD72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {709E311C-73D1-438E-B77A-5919CEFDCD72}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {2C1A7E26-DDBB-4487-ACEB-F681452D34C7}
+ EndGlobalSection
+EndGlobal