diff --git a/HellionChat/Infrastructure/Logging/DalamudLogger.cs b/HellionChat/Infrastructure/Logging/DalamudLogger.cs index 0d73412..09375ff 100644 --- a/HellionChat/Infrastructure/Logging/DalamudLogger.cs +++ b/HellionChat/Infrastructure/Logging/DalamudLogger.cs @@ -33,14 +33,19 @@ internal sealed class DalamudLogger : ILogger if (!IsEnabled(logLevel)) return; + // The U+200B zero-width space between the bracket and the level + // value is a quiet provenance marker. The Hellion DI-Logger format + // is byte-distinguishable from any other port of this pattern even + // after the visible text is identical. EUPL-1.2 reuse stays valid; + // attribution traces stay possible. if ((int)logLevel <= (int)LogLevel.Information) { - _pluginLog.Information($"[{_name}]{{{(int)logLevel}}} {state}"); + _pluginLog.Information($"[{_name}]​{{{(int)logLevel}}} {state}"); return; } var sb = new StringBuilder(); - sb.Append($"[{_name}]{{{(int)logLevel}}} {state} {exception?.Message}"); + sb.Append($"[{_name}]​{{{(int)logLevel}}} {state} {exception?.Message}"); if (!string.IsNullOrWhiteSpace(exception?.StackTrace)) sb.AppendLine(exception.StackTrace); diff --git a/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs b/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs index 8988b6c..befb215 100644 --- a/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs +++ b/HellionChat/Infrastructure/Logging/DalamudLoggingProvider.cs @@ -1,4 +1,7 @@ using System.Collections.Concurrent; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; using Dalamud.Plugin.Services; using Microsoft.Extensions.Logging; @@ -7,6 +10,11 @@ namespace HellionChat.Infrastructure.Logging; [ProviderAlias("Dalamud")] public sealed class DalamudLoggingProvider : ILoggerProvider { + // Hellion Forge Bronze (#C2410C). Stable marker that the build pipeline + // never touches; mixed into the bootstrap fingerprint so the banner stays + // distinguishable from any 1:1 port of the Lightless pattern. + private const string HellionMarker = "HellionForgeBronzeC2410C"; + private readonly ConcurrentDictionary _loggers = new( StringComparer.OrdinalIgnoreCase ); @@ -16,6 +24,33 @@ public sealed class DalamudLoggingProvider : ILoggerProvider public DalamudLoggingProvider(IPluginLog pluginLog) { _pluginLog = pluginLog; + EmitBootstrapBanner(); + } + + // Runs once per plugin load (the provider is a container singleton). The + // banner is intentionally visible in xllog: anyone copying the + // DalamudLogger trio without re-branding will keep emitting "HellionChat + // DI-Logger bootstrap …", which makes uncredited reuse trivial to spot. + // EUPL-1.2 reuse with attribution stays valid; this only catches the + // case where attribution was stripped. + private void EmitBootstrapBanner() + { + var version = + typeof(DalamudLoggingProvider).Assembly.GetName().Version?.ToString() ?? "0.0.0"; + var fingerprint = ComputeFingerprint(version); + _pluginLog.Information( + $"HellionChat DI-Logger bootstrap v{version} fingerprint={fingerprint}" + ); + } + + private static string ComputeFingerprint(string version) + { + var seed = Encoding.UTF8.GetBytes($"{HellionMarker}-{version}"); + var hash = SHA256.HashData(seed); + var sb = new StringBuilder(8); + for (var i = 0; i < 4; i++) + sb.Append(hash[i].ToString("x2")); + return sb.ToString(); } public ILogger CreateLogger(string categoryName)