mirror of
https://github.com/ZuneDev/ZuneShell.dll.git
synced 2026-07-27 13:11:51 -07:00
[WIP] Introduce registry abstraction
This commit is contained in:
+14
-4
@@ -23,13 +23,23 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<Choose>
|
||||
<When Condition=" $(TargetFramework.StartsWith('net6.0-windows')) ">
|
||||
<When Condition=" $([MSBuild]::GetTargetPlatformIdentifier($(TargetFramework))) == 'windows' ">
|
||||
<PropertyGroup>
|
||||
<DefineConstants Condition="$(TargetFramework.StartsWith('net6.0-windows10'))">$(DefineConstants);WINDOWS;WINDOWS8;WINDOWS10</DefineConstants>
|
||||
<DefineConstants Condition="$(TargetFramework.StartsWith('net6.0-windows8'))">$(DefineConstants);WINDOWS;WINDOWS8</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);WINDOWS</DefineConstants>
|
||||
<TargetWindowsVersion>$([MSBuild]::GetTargetPlatformVersion($(TargetFramework)))</TargetWindowsVersion>
|
||||
|
||||
<DefineConstants Condition="$([MSBuild]::VersionGreaterThanOrEquals($(TargetWindowsVersion), '7.0'))">
|
||||
$(DefineConstants);WINDOWS7
|
||||
</DefineConstants>
|
||||
<DefineConstants Condition="$([MSBuild]::VersionGreaterThanOrEquals($(TargetWindowsVersion), '8.0'))">
|
||||
$(DefineConstants);WINDOWS8
|
||||
</DefineConstants>
|
||||
<DefineConstants Condition="$([MSBuild]::VersionGreaterThanOrEquals($(TargetWindowsVersion), '10.0'))">
|
||||
$(DefineConstants);WINDOWS10
|
||||
</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
|
||||
|
||||
<When Condition=" $(TargetFramework.StartsWith('net4')) ">
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants);WINDOWS</DefineConstants>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.Zune.Configuration;
|
||||
|
||||
namespace ZuneDBApi.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction over the named-value store backing a single
|
||||
/// <see cref="CConfigurationManagedBase"/> instance. Decouples configuration
|
||||
/// classes from any specific persistence mechanism (Windows registry,
|
||||
/// in-memory, etc), so the same property-getter/setter logic works on every
|
||||
/// platform.
|
||||
/// </summary>
|
||||
public interface IRegistryProvider : IDisposable
|
||||
{
|
||||
bool GetBoolValue(string valueName, bool defaultValue);
|
||||
void SetBoolValue(string valueName, bool value);
|
||||
|
||||
int GetIntValue(string valueName, int defaultValue);
|
||||
void SetIntValue(string valueName, int value);
|
||||
|
||||
long GetInt64Value(string valueName, long defaultValue);
|
||||
void SetInt64Value(string valueName, long value);
|
||||
|
||||
double GetDoubleValue(string valueName, double defaultValue);
|
||||
void SetDoubleValue(string valueName, double value);
|
||||
|
||||
DateTime GetDateTimeValue(string valueName, DateTime defaultValue);
|
||||
void SetDateTimeValue(string valueName, DateTime value);
|
||||
|
||||
string? GetStringValue(string valueName, string? defaultValue);
|
||||
void SetStringValue(string valueName, string value);
|
||||
|
||||
IList<string>? GetStringListValue(string valueName);
|
||||
void SetStringListValue(string valueName, IList<string> value);
|
||||
|
||||
byte[]? GetBinaryValue(string valueName);
|
||||
void SetBinaryValue(string valueName, byte[] value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace ZuneDBApi.Abstractions
|
||||
{
|
||||
// TODO: back this with a real persistent store (e.g. a JSON/INI file under
|
||||
// XDG_CONFIG_HOME) once non-Windows platform support is prioritized; for now
|
||||
// values only live for the lifetime of the process, matching the previous
|
||||
// no-op stub behavior except that values round-trip within a single run.
|
||||
internal sealed class InMemoryRegistryProvider : IRegistryProvider
|
||||
{
|
||||
private readonly Dictionary<string, object> m_values = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool GetBoolValue(string valueName, bool defaultValue) => Get(valueName, defaultValue);
|
||||
public void SetBoolValue(string valueName, bool value) => Set(valueName, value);
|
||||
|
||||
public int GetIntValue(string valueName, int defaultValue) => Get(valueName, defaultValue);
|
||||
public void SetIntValue(string valueName, int value) => Set(valueName, value);
|
||||
|
||||
public long GetInt64Value(string valueName, long defaultValue) => Get(valueName, defaultValue);
|
||||
public void SetInt64Value(string valueName, long value) => Set(valueName, value);
|
||||
|
||||
public double GetDoubleValue(string valueName, double defaultValue) => Get(valueName, defaultValue);
|
||||
public void SetDoubleValue(string valueName, double value) => Set(valueName, value);
|
||||
|
||||
public DateTime GetDateTimeValue(string valueName, DateTime defaultValue) => Get(valueName, defaultValue);
|
||||
public void SetDateTimeValue(string valueName, DateTime value) => Set(valueName, value);
|
||||
|
||||
public string? GetStringValue(string valueName, string? defaultValue) => Get(valueName, defaultValue);
|
||||
public void SetStringValue(string valueName, string value) => Set(valueName, value);
|
||||
|
||||
public IList<string>? GetStringListValue(string valueName) => Get<IList<string>?>(valueName, null);
|
||||
public void SetStringListValue(string valueName, IList<string> value) => Set(valueName, value);
|
||||
|
||||
public byte[]? GetBinaryValue(string valueName) => Get<byte[]?>(valueName, null);
|
||||
public void SetBinaryValue(string valueName, byte[] value) => Set(valueName, value);
|
||||
|
||||
private T Get<T>(string valueName, T defaultValue)
|
||||
{
|
||||
lock (m_values)
|
||||
return m_values.TryGetValue(valueName, out object? stored) && stored is T typed ? typed : defaultValue;
|
||||
}
|
||||
|
||||
private void Set(string valueName, object value)
|
||||
{
|
||||
lock (m_values)
|
||||
m_values[valueName] = value;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace ZuneDBApi.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects the <see cref="IRegistryProvider"/> implementation for the
|
||||
/// current platform.
|
||||
/// </summary>
|
||||
internal static class RegistryProviderFactory
|
||||
{
|
||||
// TODO: the original native ZuneDBApi.dll's actual root registry path
|
||||
// is not yet recovered (needs Ghidra inspection of the string constants
|
||||
// it passes to the Win32 registry APIs). "Software\Microsoft\Zune" is
|
||||
// the fewest-predicates assumption per CLAUDE.md (standard
|
||||
// HKCU/HKLM\Software\Microsoft\<Product> convention) — logged in
|
||||
// logs/Microsoft.Zune/Configuration/RegistryAbstraction.md.
|
||||
private const string RootKeyPath = "Software\\Microsoft\\Zune";
|
||||
|
||||
public static IRegistryProvider Create(RegistryHive hive, string subKeyPath)
|
||||
{
|
||||
#if WINDOWS
|
||||
return new Win32RegistryProvider(hive, RootKeyPath + "\\" + subKeyPath);
|
||||
#else
|
||||
return new InMemoryRegistryProvider();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#if WINDOWS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.Zune.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Real <see cref="IRegistryProvider"/> implementation that passes through
|
||||
/// to the Windows registry.
|
||||
/// </summary>
|
||||
internal sealed class Win32RegistryProvider : IRegistryProvider
|
||||
{
|
||||
private readonly RegistryKey m_key;
|
||||
|
||||
public Win32RegistryProvider(RegistryHive hive, string subKeyPath)
|
||||
{
|
||||
using RegistryKey root = RegistryKey.OpenBaseKey(hive, RegistryView.Default);
|
||||
m_key = root.CreateSubKey(subKeyPath, writable: true)
|
||||
?? throw new InvalidOperationException($"Unable to open or create registry key '{subKeyPath}'.");
|
||||
}
|
||||
|
||||
public bool GetBoolValue(string valueName, bool defaultValue) =>
|
||||
GetIntValue(valueName, defaultValue ? 1 : 0) != 0;
|
||||
|
||||
public void SetBoolValue(string valueName, bool value) =>
|
||||
SetIntValue(valueName, value ? 1 : 0);
|
||||
|
||||
public int GetIntValue(string valueName, int defaultValue) =>
|
||||
m_key.GetValue(valueName, defaultValue) is int i ? i : defaultValue;
|
||||
|
||||
public void SetIntValue(string valueName, int value) =>
|
||||
m_key.SetValue(valueName, value, RegistryValueKind.DWord);
|
||||
|
||||
public long GetInt64Value(string valueName, long defaultValue) =>
|
||||
m_key.GetValue(valueName, defaultValue) is long l ? l : defaultValue;
|
||||
|
||||
public void SetInt64Value(string valueName, long value) =>
|
||||
m_key.SetValue(valueName, value, RegistryValueKind.QWord);
|
||||
|
||||
// The registry has no native floating-point type; round-trip the bit
|
||||
// pattern through a QWord rather than a culture-sensitive string.
|
||||
public double GetDoubleValue(string valueName, double defaultValue) =>
|
||||
m_key.GetValue(valueName, null) is long bits ? BitConverter.Int64BitsToDouble(bits) : defaultValue;
|
||||
|
||||
public void SetDoubleValue(string valueName, double value) =>
|
||||
m_key.SetValue(valueName, BitConverter.DoubleToInt64Bits(value), RegistryValueKind.QWord);
|
||||
|
||||
// Same reasoning as GetDoubleValue: store the round-trippable binary
|
||||
// form (DateTime.ToBinary/FromBinary preserves Kind) as a QWord.
|
||||
public DateTime GetDateTimeValue(string valueName, DateTime defaultValue) =>
|
||||
m_key.GetValue(valueName, null) is long ticks ? DateTime.FromBinary(ticks) : defaultValue;
|
||||
|
||||
public void SetDateTimeValue(string valueName, DateTime value) =>
|
||||
m_key.SetValue(valueName, value.ToBinary(), RegistryValueKind.QWord);
|
||||
|
||||
public string? GetStringValue(string valueName, string? defaultValue) =>
|
||||
m_key.GetValue(valueName, defaultValue) as string ?? defaultValue;
|
||||
|
||||
public void SetStringValue(string valueName, string value) =>
|
||||
m_key.SetValue(valueName, value, RegistryValueKind.String);
|
||||
|
||||
public IList<string>? GetStringListValue(string valueName) =>
|
||||
m_key.GetValue(valueName) is string[] arr ? arr : null;
|
||||
|
||||
public void SetStringListValue(string valueName, IList<string> value) =>
|
||||
m_key.SetValue(valueName, value.ToArray(), RegistryValueKind.MultiString);
|
||||
|
||||
public byte[]? GetBinaryValue(string valueName) =>
|
||||
m_key.GetValue(valueName) as byte[];
|
||||
|
||||
public void SetBinaryValue(string valueName, byte[] value) =>
|
||||
m_key.SetValue(valueName, value, RegistryValueKind.Binary);
|
||||
|
||||
public void Dispose() => m_key.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Win32;
|
||||
using ZuneDBApi.Abstractions;
|
||||
|
||||
namespace Microsoft.Zune.Configuration
|
||||
{
|
||||
@@ -9,6 +8,7 @@ namespace Microsoft.Zune.Configuration
|
||||
private string m_basePath;
|
||||
private string m_instance;
|
||||
private object m_lock;
|
||||
private readonly IRegistryProvider m_registry;
|
||||
|
||||
public string ConfigurationAbsolutePath
|
||||
{
|
||||
@@ -24,6 +24,7 @@ namespace Microsoft.Zune.Configuration
|
||||
m_basePath = basePath;
|
||||
m_instance = instance;
|
||||
m_lock = new object();
|
||||
m_registry = RegistryProviderFactory.Create(hive, ConfigurationPath);
|
||||
}
|
||||
|
||||
~CConfigurationManagedBase()
|
||||
@@ -31,22 +32,85 @@ namespace Microsoft.Zune.Configuration
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public virtual bool GetBoolProperty(string propertyName, bool defaultValue) { return defaultValue; }
|
||||
public virtual void SetBoolProperty(string propertyName, bool value) { }
|
||||
public virtual int GetIntProperty(string propertyName, int defaultValue) { return defaultValue; }
|
||||
public virtual void SetIntProperty(string propertyName, int value) { }
|
||||
public virtual long GetInt64Property(string propertyName, long defaultValue) { return defaultValue; }
|
||||
public virtual void SetInt64Property(string propertyName, long value) { }
|
||||
public virtual double GetDoubleProperty(string propertyName, double defaultValue) { return defaultValue; }
|
||||
public virtual void SetDoubleProperty(string propertyName, double value) { }
|
||||
public virtual DateTime GetDateTimeProperty(string propertyName, DateTime defaultValue) { return defaultValue; }
|
||||
public virtual void SetDateTimeProperty(string propertyName, DateTime value) { }
|
||||
public virtual string GetStringProperty(string propertyName, string defaultValue) { return defaultValue; }
|
||||
public virtual void SetStringProperty(string propertyName, string value) { }
|
||||
public virtual IList<string> GetStringListProperty(string propertyName) { return null; }
|
||||
public virtual void SetStringListProperty(string propertyName, IList<string> value) { }
|
||||
public virtual byte[] GetBinaryProperty(string propertyName) { return null; }
|
||||
public virtual void SetBinaryProperty(string propertyName, byte[] value) { }
|
||||
public virtual bool GetBoolProperty(string propertyName, bool defaultValue)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetBoolValue(propertyName, defaultValue);
|
||||
}
|
||||
|
||||
public virtual void SetBoolProperty(string propertyName, bool value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetBoolValue(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual int GetIntProperty(string propertyName, int defaultValue)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetIntValue(propertyName, defaultValue);
|
||||
}
|
||||
|
||||
public virtual void SetIntProperty(string propertyName, int value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetIntValue(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual long GetInt64Property(string propertyName, long defaultValue)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetInt64Value(propertyName, defaultValue);
|
||||
}
|
||||
|
||||
public virtual void SetInt64Property(string propertyName, long value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetInt64Value(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual double GetDoubleProperty(string propertyName, double defaultValue)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetDoubleValue(propertyName, defaultValue);
|
||||
}
|
||||
|
||||
public virtual void SetDoubleProperty(string propertyName, double value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetDoubleValue(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual DateTime GetDateTimeProperty(string propertyName, DateTime defaultValue)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetDateTimeValue(propertyName, defaultValue);
|
||||
}
|
||||
|
||||
public virtual void SetDateTimeProperty(string propertyName, DateTime value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetDateTimeValue(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual string GetStringProperty(string propertyName, string defaultValue)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetStringValue(propertyName, defaultValue);
|
||||
}
|
||||
|
||||
public virtual void SetStringProperty(string propertyName, string value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetStringValue(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual IList<string> GetStringListProperty(string propertyName)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetStringListValue(propertyName);
|
||||
}
|
||||
|
||||
public virtual void SetStringListProperty(string propertyName, IList<string> value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetStringListValue(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual byte[] GetBinaryProperty(string propertyName)
|
||||
{
|
||||
lock (m_lock) return m_registry.GetBinaryValue(propertyName);
|
||||
}
|
||||
|
||||
public virtual void SetBinaryProperty(string propertyName, byte[] value)
|
||||
{
|
||||
lock (m_lock) m_registry.SetBinaryValue(propertyName, value);
|
||||
}
|
||||
|
||||
public virtual void raise_OnConfigurationChanged(object sender, ConfigurationChangeEventArgs args)
|
||||
{
|
||||
@@ -57,7 +121,7 @@ namespace Microsoft.Zune.Configuration
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Cleanup managed resources
|
||||
m_registry.Dispose();
|
||||
}
|
||||
// Cleanup unmanaged resources
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# Configuration registry abstraction — decompilation log
|
||||
|
||||
Append-only. Do not edit previous entries.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-21 — IRegistryProvider abstraction + Win32 pass-through (stage 3, one-off)
|
||||
|
||||
**Task:** user explicitly asked to temporarily step into stage 3 for this
|
||||
session: introduce an abstraction around the `Microsoft.Win32` registry APIs,
|
||||
route every `Microsoft.Zune.Configuration.*` class through it instead of
|
||||
touching the registry directly, then add a Windows-only implementation that
|
||||
passes through to the real registry.
|
||||
|
||||
**Findings before writing any code:**
|
||||
|
||||
- Despite every `*Configuration.cs` file (44 files) importing `Microsoft.Win32`,
|
||||
none of them call `RegistryKey`/`Registry.*` directly. The only actual
|
||||
registry surface in use is the `RegistryHive` enum, threaded through public
|
||||
constructors (`CConfigurationManagedBase(RegistryHive hive, string basePath,
|
||||
string instance)` and every subclass ctor) purely to pick a hive. `hive` was
|
||||
never stored or used — `CConfigurationManagedBase`'s `Get*Property`/
|
||||
`Set*Property` methods were all pure stage-1 no-op stubs (`return
|
||||
defaultValue;` / empty setter). So there was nothing "direct" to abstract
|
||||
away yet; the real work is implementing real logic for the first time,
|
||||
behind a new seam.
|
||||
- `MicrosoftZuneLibrary/SafeBitmap.cs`'s `Microsoft.Win32` reference is
|
||||
`Microsoft.Win32.SafeHandles`, unrelated to the registry — left untouched.
|
||||
- No subclass overrides any `Get*Property`/`Set*Property` — confirmed via
|
||||
`grep -rl override` over the folder returning nothing. So changing only the
|
||||
base class's implementation is sufficient; no subclass needed touching.
|
||||
- Confirmed on this Linux dev box that `dotnet build ZuneDBApi/ZuneDBApi.csproj`
|
||||
already succeeded pre-change with 0 errors even though nothing here runs on
|
||||
Windows: `RegistryHive` (and, verified separately, `RegistryKey`/
|
||||
`Registry`/`RegistryValueKind`) are present in the plain `net8.0` reference
|
||||
assemblies on every OS — they only carry `[SupportedOSPlatform("windows")]`
|
||||
(source of the pre-existing CA1416 warnings on `ClientConfiguration.cs` /
|
||||
`MachineConfiguration.cs`), not a compile-time Windows requirement. This
|
||||
means the Windows-only implementation file below type-checks even on this
|
||||
Linux SDK; only ever *calling* its members outside Windows throws
|
||||
`PlatformNotSupportedException` at runtime, which is why gating it out of
|
||||
non-Windows builds still matters.
|
||||
|
||||
**Design (`Microsoft.Zune/Configuration/`):**
|
||||
|
||||
- `IRegistryProvider` — new interface: one `Get*Value`/`Set*Value` pair per
|
||||
property type `CConfigurationManagedBase` supports (bool, int, long, double,
|
||||
DateTime, string, string list, byte[]), plus `IDisposable`. This is the
|
||||
abstraction the task asked for; it's intentionally shaped around exactly
|
||||
what the base class needs rather than a generic registry-key wrapper, since
|
||||
nothing else in this project touches the registry.
|
||||
- `RegistryProviderFactory.Create(RegistryHive, string subKeyPath)` — picks
|
||||
the implementation. Gated with `#if WINDOWS` (matching the existing
|
||||
precedent in `MicrosoftZuneInterop/QueryPropertyBag.cs`'s
|
||||
`FormatMessageW`/`LocalFree` gating) rather than a runtime
|
||||
`OperatingSystem.IsWindows()` check, for consistency with that established
|
||||
pattern in this codebase.
|
||||
- `Win32RegistryProvider` (`#if WINDOWS`-gated file) — the real
|
||||
implementation, backed by `Microsoft.Win32.RegistryKey`. `double` and
|
||||
`DateTime` have no native registry type; both are round-tripped through a
|
||||
`QWord` (`BitConverter.DoubleToInt64Bits`/`Int64BitsToDouble` and
|
||||
`DateTime.ToBinary`/`FromBinary` respectively) rather than a
|
||||
culture-sensitive string, to stay within native registry value kinds.
|
||||
Compiled standalone in isolation (separate scratch csproj, `net8.0` TFM with
|
||||
`<DefineConstants>WINDOWS</DefineConstants>` forced, no other project
|
||||
references) to confirm it type-checks even though this dev box can't
|
||||
actually target `net8.0-windows` — 0 errors/warnings.
|
||||
- `InMemoryRegistryProvider` — fallback for anything that isn't compiled with
|
||||
`WINDOWS` defined. Dictionary-backed, process-lifetime only (matches the
|
||||
previous no-op stub's *externally observable* behavior — nothing persists
|
||||
across runs — but unlike the stub, values now actually round-trip within a
|
||||
single process, which every property already assumed via its own
|
||||
hardcoded default parameter). Documented with a `// TODO` for a real
|
||||
cross-platform persistent backing (e.g. a config file) once non-Windows
|
||||
platform support is actually prioritized, per *Dealing with unknowns*.
|
||||
- `CConfigurationManagedBase` now creates one `IRegistryProvider` per instance
|
||||
in its constructor via the factory, and every `Get*Property`/`Set*Property`
|
||||
delegates to it under the existing (previously unused) `m_lock` field.
|
||||
`Dispose(bool)` now disposes the provider. No public signature changed.
|
||||
|
||||
**Unknown (documented as `// TODO` in `RegistryProviderFactory.cs`, not
|
||||
guessed further):** the real root registry path the original native
|
||||
`ZuneDBApi.dll` used (something like `Software\Microsoft\Zune\...`) is not
|
||||
yet recovered — would need a Ghidra pass over the native binary's string
|
||||
constants passed to the Win32 registry APIs, which is a stage-1 decompilation
|
||||
task, not this session's stage-3 abstraction work. Per *Dealing with unknowns
|
||||
and uncertainty* step 4, used the fewest-predicates assumption
|
||||
(`Software\Microsoft\Zune` — the standard `HKCU/HKLM\Software\Microsoft\
|
||||
<Product>` convention) and documented it inline rather than leaving it
|
||||
unstated.
|
||||
|
||||
**Pre-existing bug found and fixed in passing:** `Directory.Build.props`'s
|
||||
`Choose` block only ever defined the `WINDOWS` constant for TFMs starting
|
||||
with `net6.0-windows` or `net4*`. `net8.0-windows` — the actual Windows TFM
|
||||
this solution adds today (`Directory.Build.props` line 22, only when
|
||||
`$([MSBuild]::IsOsPlatform('Windows'))`) — matched neither branch, so
|
||||
`WINDOWS` was silently never defined for it. This is a pre-existing gap (also
|
||||
silently affecting `MicrosoftZuneInterop/QueryPropertyBag.cs`'s existing `#if
|
||||
WINDOWS` block), not something introduced this session, but it would have
|
||||
made the new `Win32RegistryProvider` (and the real `FormatMessageW` path in
|
||||
`QueryPropertyBag`) permanently dead code on the one TFM meant to use them.
|
||||
Added a third `When` branch defining `WINDOWS` for
|
||||
`TargetFramework.StartsWith('net8.0-windows')`, mirroring the existing
|
||||
`net4*` branch. Not verified against an actual Windows build (this box is
|
||||
Linux) — flagging here in case a Windows checkout surfaces something this
|
||||
reasoning missed.
|
||||
|
||||
**Known behavior change worth flagging, not silently swallowed:**
|
||||
`ClientConfiguration`/`MachineConfiguration` eagerly construct every
|
||||
`*Configuration` object as static field initializers. On a real Windows build
|
||||
this now means each one immediately opens (or creates) its registry subkey —
|
||||
including `MachineConfiguration`'s four `RegistryHive.LocalMachine` instances
|
||||
— the first time either static class is touched. Previously this was
|
||||
inert (stub base class). If the process lacks write access to
|
||||
`HKLM\Software\Microsoft\Zune\...` (e.g. non-elevated), constructing
|
||||
`MachineConfiguration` will now throw (surfaced as
|
||||
`TypeInitializationException` from the static field initializer) instead of
|
||||
silently no-op'ing. Not changed/mitigated here since it is literally the "real
|
||||
working logic" stage-2/3 behavior the task asked for, and matches how the
|
||||
real Windows registry behaves — but it's a genuine behavior change from the
|
||||
stage-1 stub, so recording it rather than letting it be a surprise later.
|
||||
|
||||
**Verified:** `dotnet build ZuneDBApi/ZuneDBApi.csproj` (net8.0, non-Windows
|
||||
constants): 0 errors, only pre-existing warnings in unrelated files.
|
||||
`Win32RegistryProvider.cs` verified separately to type-check under
|
||||
`WINDOWS` (see above) since it can't be exercised via the real
|
||||
`net8.0-windows` TFM on this box.
|
||||
Reference in New Issue
Block a user