Skip to content

Commit eaa652c

Browse files
🔧 Fix(PluginImport): 修复导入逻辑与旧版一致:从kxp文件头中获取信息而不是从解压出的文件中获取。
1 parent fd3ebec commit eaa652c

1 file changed

Lines changed: 104 additions & 116 deletions

File tree

KitX Clients/KitX Core/KitX.Core/Plugin/PluginsManager.cs

Lines changed: 104 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,44 @@ public async Task<bool> ImportPluginAsync(string kxpFilePath)
257257
try
258258
{
259259
var decoder = new FileFormats.CSharp.ExtensionsPackage.Decoder(kxpFilePath);
260+
261+
// Read structs from KXP binary header (preferred path)
262+
var (loaderStructHeader, pluginStructHeader) = decoder.GetLoaderAndPluginInfo();
263+
260264
decoder.Decode(pluginDir);
261265
Log.Information($"Decoded KXP file to: {pluginDir}");
266+
267+
// Parse LoaderStruct from header, fall back to extracted file
268+
LoaderInfo? loaderInfo = ParseLoaderStructFromJson(loaderStructHeader);
269+
if (loaderInfo is null)
270+
{
271+
var loaderStructPath = Path.Combine(pluginDir, "LoaderStruct.json");
272+
if (File.Exists(loaderStructPath))
273+
{
274+
var loaderStructJson = await File.ReadAllTextAsync(loaderStructPath);
275+
loaderInfo = ParseLoaderStructFromJson(loaderStructJson);
276+
}
277+
}
278+
279+
// Parse PluginStruct from header, fall back to extracted file
280+
PluginInfo? pluginInfo = ParsePluginStructFromJson(pluginStructHeader);
281+
if (pluginInfo is null)
282+
{
283+
var pluginStructPath = Path.Combine(pluginDir, "PluginStruct.json");
284+
if (File.Exists(pluginStructPath))
285+
{
286+
var pluginStructJson = await File.ReadAllTextAsync(pluginStructPath);
287+
pluginInfo = ParsePluginStructFromJson(pluginStructJson);
288+
}
289+
}
290+
291+
return await FinalizeImport(pluginDir, kxpFilePath, loaderInfo, pluginInfo);
262292
}
263293
catch (Exception ex)
264294
{
265295
Log.Warning(ex, "Failed to decode KXP file, trying as direct files...");
266296

267297
// If KXP decode fails, assume it's a directory with files already extracted
268-
// Just copy the source directory contents
269298
var sourceDir = Path.GetDirectoryName(kxpFilePath);
270299
if (sourceDir != null && Directory.Exists(sourceDir))
271300
{
@@ -278,140 +307,100 @@ public async Task<bool> ImportPluginAsync(string kxpFilePath)
278307
}
279308
}
280309
}
310+
311+
return await FinalizeImport(pluginDir, kxpFilePath, null, null);
281312
}
313+
}
314+
catch (Exception ex)
315+
{
316+
Log.Error(ex, $"In {location}: Error importing plugin {kxpFilePath}: {ex.Message}");
317+
return false;
318+
}
319+
}
282320

283-
// Look for LoaderStruct.json and PluginStruct.json in the extracted files
284-
var loaderStructPath = Path.Combine(pluginDir, "LoaderStruct.json");
285-
var pluginStructPath = Path.Combine(pluginDir, "PluginStruct.json");
321+
private static LoaderInfo? ParseLoaderStructFromJson(string loaderStructJson)
322+
{
323+
try
324+
{
325+
return System.Text.Json.JsonSerializer.Deserialize<LoaderInfo>(loaderStructJson,
326+
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true });
327+
}
328+
catch (Exception ex)
329+
{
330+
Log.Warning(ex, "Failed to parse LoaderStruct JSON");
331+
return null;
332+
}
333+
}
286334

287-
PluginInfo? pluginInfo = null;
288-
LoaderInfo? loaderInfo = null;
335+
private static PluginInfo? ParsePluginStructFromJson(string pluginStructJson)
336+
{
337+
try
338+
{
339+
return System.Text.Json.JsonSerializer.Deserialize<PluginInfo>(pluginStructJson,
340+
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true, IncludeFields = true });
341+
}
342+
catch (Exception ex)
343+
{
344+
Log.Warning(ex, "Failed to parse PluginStruct JSON");
345+
return null;
346+
}
347+
}
289348

290-
// Parse LoaderStruct.json if exists
291-
if (File.Exists(loaderStructPath))
292-
{
293-
try
294-
{
295-
var loaderStructJson = await File.ReadAllTextAsync(loaderStructPath);
296-
var loaderStruct = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(loaderStructJson);
349+
private async Task<bool> FinalizeImport(
350+
string pluginDir, string kxpFilePath,
351+
LoaderInfo? loaderInfo, PluginInfo? pluginInfo)
352+
{
353+
const string location = $"{nameof(PluginsManager)}.{nameof(FinalizeImport)}";
297354

298-
loaderInfo = new LoaderInfo
299-
{
300-
LoaderName = loaderStruct.TryGetProperty("LoaderName", out var name) ? name.GetString() ?? "Unknown" : "Unknown",
301-
LoaderVersion = loaderStruct.TryGetProperty("LoaderVersion", out var version) ? version.GetString() ?? "1.0.0" : "1.0.0",
302-
LoaderLanguage = loaderStruct.TryGetProperty("LoaderLanguage", out var lang) ? lang.GetString() ?? "Unknown" : "Unknown",
303-
LoaderFramework = loaderStruct.TryGetProperty("LoaderFramework", out var fw) ? fw.GetString() ?? "Unknown" : "Unknown",
304-
SelfLoad = loaderStruct.TryGetProperty("SelfLoad", out var selfLoad) && selfLoad.GetBoolean(),
305-
Tags = new Dictionary<string, string>()
306-
};
355+
try
356+
{
357+
var pluginFileName = Path.GetFileNameWithoutExtension(kxpFilePath);
307358

308-
// Copy loader struct to LoaderInfo.json (different format)
309-
var loaderInfoJson = System.Text.Json.JsonSerializer.Serialize(loaderInfo, new JsonSerializerOptions { WriteIndented = true });
310-
await File.WriteAllTextAsync(Path.Combine(pluginDir, "LoaderInfo.json"), loaderInfoJson);
359+
if (pluginInfo == null)
360+
{
361+
// If no plugin info from KXP, look for LoaderStruct/PluginStruct files
362+
var loaderStructPath = Path.Combine(pluginDir, "LoaderStruct.json");
363+
var pluginStructPath = Path.Combine(pluginDir, "PluginStruct.json");
311364

312-
Log.Information($"Parsed LoaderStruct: {loaderInfo.LoaderName} v{loaderInfo.LoaderVersion}");
313-
}
314-
catch (Exception ex)
365+
if (File.Exists(loaderStructPath))
315366
{
316-
Log.Warning(ex, "Failed to parse LoaderStruct.json");
367+
var json = await File.ReadAllTextAsync(loaderStructPath);
368+
loaderInfo ??= ParseLoaderStructFromJson(json);
317369
}
318-
}
319370

320-
// Parse PluginStruct.json if exists (or look for embedded plugin info)
321-
if (File.Exists(pluginStructPath))
322-
{
323-
try
371+
if (File.Exists(pluginStructPath))
324372
{
325-
var pluginStructJson = await File.ReadAllTextAsync(pluginStructPath);
326-
var pluginStruct = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(pluginStructJson);
373+
var json = await File.ReadAllTextAsync(pluginStructPath);
374+
pluginInfo = ParsePluginStructFromJson(json);
375+
}
327376

377+
if (pluginInfo == null)
378+
{
328379
pluginInfo = new PluginInfo
329380
{
330-
Name = pluginStruct.TryGetProperty("Name", out var name) ? name.GetString() ?? "Unknown" : "Unknown",
331-
Version = pluginStruct.TryGetProperty("Version", out var ver) ? ver.GetString() ?? "1.0.0" : "1.0.0",
332-
DisplayName = new Dictionary<string, string>(),
333-
SimpleDescription = new Dictionary<string, string>(),
334-
ComplexDescription = new Dictionary<string, string>(),
381+
Name = pluginFileName,
382+
Version = "1.0.0",
383+
PublisherName = "Unknown",
384+
AuthorName = "Unknown",
385+
DisplayName = new Dictionary<string, string> { { "en-us", pluginFileName } },
386+
SimpleDescription = new Dictionary<string, string> { { "en-us", "Imported plugin" } },
387+
ComplexDescription = new Dictionary<string, string> { { "en-us", "Imported plugin" } },
335388
TotalDescriptionInMarkdown = new Dictionary<string, string>(),
336389
Tags = new Dictionary<string, string>(),
337390
Functions = new List<Function>()
338391
};
339-
340-
// Parse DisplayName
341-
if (pluginStruct.TryGetProperty("DisplayName", out var displayName))
342-
{
343-
foreach (var prop in displayName.EnumerateObject())
344-
{
345-
pluginInfo.DisplayName[prop.Name] = prop.Value.GetString() ?? "";
346-
}
347-
}
348-
349-
// Parse SimpleDescription
350-
if (pluginStruct.TryGetProperty("SimpleDescription", out var simpleDesc))
351-
{
352-
foreach (var prop in simpleDesc.EnumerateObject())
353-
{
354-
pluginInfo.SimpleDescription[prop.Name] = prop.Value.GetString() ?? "";
355-
}
356-
}
357-
358-
// Parse ComplexDescription
359-
if (pluginStruct.TryGetProperty("ComplexDescription", out var complexDesc))
360-
{
361-
foreach (var prop in complexDesc.EnumerateObject())
362-
{
363-
pluginInfo.ComplexDescription[prop.Name] = prop.Value.GetString() ?? "";
364-
}
365-
}
366-
367-
// Parse other fields
368-
pluginInfo.AuthorName = pluginStruct.TryGetProperty("AuthorName", out var author) ? author.GetString() ?? "Unknown" : "Unknown";
369-
pluginInfo.AuthorLink = pluginStruct.TryGetProperty("AuthorLink", out var authorLink) ? authorLink.GetString() ?? "" : "";
370-
pluginInfo.PublisherName = pluginStruct.TryGetProperty("PublisherName", out var publisher) ? publisher.GetString() ?? "Unknown" : "Unknown";
371-
pluginInfo.PublisherLink = pluginStruct.TryGetProperty("PublisherLink", out var publisherLink) ? publisherLink.GetString() ?? "" : "";
372-
pluginInfo.IconInBase64 = pluginStruct.TryGetProperty("IconInBase64", out var icon) ? icon.GetString() ?? "" : "";
373-
pluginInfo.IsMarketVersion = pluginStruct.TryGetProperty("IsMarketVersion", out var marketVer) && marketVer.GetBoolean();
374-
pluginInfo.RootStartupFileName = pluginStruct.TryGetProperty("RootStartupFileName", out var rootFile) ? rootFile.GetString() ?? "" : "";
375-
376-
if (pluginStruct.TryGetProperty("PublishDate", out var publishDate))
377-
{
378-
if (DateTime.TryParse(publishDate.GetString(), out var pd))
379-
pluginInfo.PublishDate = pd;
380-
}
381-
382-
if (pluginStruct.TryGetProperty("LastUpdateDate", out var lastUpdate))
383-
{
384-
if (DateTime.TryParse(lastUpdate.GetString(), out var lud))
385-
pluginInfo.LastUpdateDate = lud;
386-
}
387-
388-
Log.Information($"Parsed PluginStruct: {pluginInfo.Name} v{pluginInfo.Version}");
389-
}
390-
catch (Exception ex)
391-
{
392-
Log.Warning(ex, "Failed to parse PluginStruct.json");
393392
}
394393
}
395394

396-
// If no plugin info from KXP, create basic info from filename
397-
if (pluginInfo == null)
395+
// Write LoaderInfo.json
396+
if (loaderInfo != null)
398397
{
399-
pluginInfo = new PluginInfo
400-
{
401-
Name = pluginFileName,
402-
Version = "1.0.0",
403-
PublisherName = "Unknown",
404-
AuthorName = "Unknown",
405-
DisplayName = new Dictionary<string, string> { { "en-us", pluginFileName } },
406-
SimpleDescription = new Dictionary<string, string> { { "en-us", "Imported plugin" } },
407-
ComplexDescription = new Dictionary<string, string> { { "en-us", "Imported plugin" } },
408-
TotalDescriptionInMarkdown = new Dictionary<string, string>(),
409-
Tags = new Dictionary<string, string>(),
410-
Functions = new List<Function>()
411-
};
398+
var loaderInfoJson = System.Text.Json.JsonSerializer.Serialize(loaderInfo,
399+
new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
400+
await File.WriteAllTextAsync(Path.Combine(pluginDir, "LoaderInfo.json"), loaderInfoJson);
412401
}
413402

414-
// Validate RootStartupFileName is specified and exists
403+
// Validate RootStartupFileName
415404
if (string.IsNullOrEmpty(pluginInfo.RootStartupFileName))
416405
{
417406
Log.Error($"Plugin import failed: RootStartupFileName is not specified in plugin {pluginInfo.Name}. Please ensure the plugin package includes this field.");
@@ -422,13 +411,13 @@ public async Task<bool> ImportPluginAsync(string kxpFilePath)
422411
if (!File.Exists(pluginFilePath))
423412
{
424413
Log.Error($"Plugin import failed: RootStartupFileName '{pluginInfo.RootStartupFileName}' points to a non-existent file in plugin {pluginInfo.Name}. File not found at: {pluginFilePath}");
425-
// Clean up the created directory
426414
try { Directory.Delete(pluginDir, true); } catch { }
427415
return false;
428416
}
429417

430-
// Create PluginInfo.json
431-
var pluginInfoJson = System.Text.Json.JsonSerializer.Serialize(pluginInfo, new JsonSerializerOptions { WriteIndented = true });
418+
// Write PluginInfo.json
419+
var pluginInfoJson = System.Text.Json.JsonSerializer.Serialize(pluginInfo,
420+
new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
432421
await File.WriteAllTextAsync(Path.Combine(pluginDir, "PluginInfo.json"), pluginInfoJson);
433422

434423
// Create installation record
@@ -445,7 +434,6 @@ public async Task<bool> ImportPluginAsync(string kxpFilePath)
445434

446435
Log.Information($"Imported plugin: {pluginInfo.Name} (v{pluginInfo.Version}) to {pluginDir}");
447436

448-
// Raise plugin status changed event
449437
PluginStatusChanged?.Invoke(this, new PluginStatusChangedEventArgs
450438
{
451439
PluginId = installation.Id,
@@ -458,7 +446,7 @@ public async Task<bool> ImportPluginAsync(string kxpFilePath)
458446
}
459447
catch (Exception ex)
460448
{
461-
Log.Error(ex, $"In {location}: Error importing plugin {kxpFilePath}: {ex.Message}");
449+
Log.Error(ex, $"In {location}: {ex.Message}");
462450
return false;
463451
}
464452
}

0 commit comments

Comments
 (0)