frist commit

This commit is contained in:
2026-05-29 23:58:49 +08:00
commit 5c1aa53058
22 changed files with 835 additions and 0 deletions

34
AGENTS.md Normal file
View File

@@ -0,0 +1,34 @@
# Repository Guidelines
## Project Structure & Module Organization
This repository contains a small .NET CLI for patching the parent path stored in classic VHD differencing disks.
- `src/VHDPatchFix/` contains the CLI, VHD footer/header parsing, checksum logic, and patch operation.
- `VHDPatchFix.sln` is the canonical solution file for build and test commands.
Keep VHD binary parsing code isolated in small classes. Prefer adding focused helpers under `src/VHDPatchFix/` instead of expanding `Program.cs`.
## Build, and Development Commands
- `dotnet build VHDPatchFix.sln -v minimal` restores and builds the CLI and tests.
- `dotnet run --project src\VHDPatchFix\VHDPatchFix.csproj -- --help` shows CLI usage.
- `dotnet run --project src\VHDPatchFix\VHDPatchFix.csproj -- --vhd "I:\internal_1.vhd" --parent "\Device\FscryptDisk_APP_0\internal_0.vhd" --dry-run` validates a target without writing.
Use `--dry-run` before patching real disks. Ensure the child VHD is detached before writing.
## Coding Style & Naming Conventions
Use C# nullable reference types and implicit usings as configured in the project files. Use four-space indentation, PascalCase for public types and methods, and camelCase for locals and parameters.
Keep binary offsets explicit and close to the relevant VHD structure parser. Avoid unrelated formatting-only edits in functional changes.
## Commit & Pull Request Guidelines
This checkout has no Git history, so no repository-specific convention is established. Use short imperative subjects such as `Add VHD parent locator patcher`.
Pull requests should include the target scenario, before/after command output when relevant, and test results from `dotnet build` plus the test runner.
## Security & Data Safety
Do not patch attached VHDs. Keep backup creation enabled by default for real disks. Avoid logging full local paths unless needed for troubleshooting, and never validate parent existence as a requirement because NT device paths may only exist on another machine.

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
# VHDPatchFix
`vhdfix` patches the parent path stored inside a classic VHD differencing disk. It is intended for cases where the parent VHD was moved and PowerShell `Set-VHD` cannot update the stored parent locator.
## Usage
Preview a change without writing:
```powershell
dotnet run --project src\VHDPatchFix\VHDPatchFix.csproj -- --vhd "I:\B.vhd" --parent "I:\A.vhd" --dry-run
```
Patch the VHD and create the default `.bak` backup:
```powershell
dotnet run --project src\VHDPatchFix\VHDPatchFix.csproj -- --vhd "I:\B.vhd" --parent "I:\A.vhd"
```
Verify after writing:
```powershell
Get-VHD -Path "I:\A.vhd"
```
## Safety Notes
- Only classic `.vhd` differencing disks are supported.
- `.vhdx` is not supported.
- The child VHD should be detached before patching.
- The tool does not require the new parent path to exist on the current computer.
## Development
```powershell
dotnet build VHDPatchFix.sln -v minimal
dotnet run --project src\VHDPatchFix\VHDPatchFix.csproj -- --help
```

22
VHDPatchFix.sln Normal file
View File

@@ -0,0 +1,22 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VHDPatchFix", "src\VHDPatchFix\VHDPatchFix.csproj", "{A6DA9E21-3118-4CC2-9E96-6C623F4E3C7E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A6DA9E21-3118-4CC2-9E96-6C623F4E3C7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6DA9E21-3118-4CC2-9E96-6C623F4E3C7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6DA9E21-3118-4CC2-9E96-6C623F4E3C7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6DA9E21-3118-4CC2-9E96-6C623F4E3C7E}.Release|Any CPU.Build.0 = Release|Any CPU
{2A3A1C8D-4613-4C43-9D73-EB6965E375E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A3A1C8D-4613-4C43-9D73-EB6965E375E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A3A1C8D-4613-4C43-9D73-EB6965E375E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A3A1C8D-4613-4C43-9D73-EB6965E375E2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,15 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# Rider 忽略的文件
/contentModel.xml
/.idea.VHDPatchFix.iml
/projectSettingsUpdater.xml
/modules.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,90 @@
using System.Text;
namespace VHDPatchFix;
public sealed class DynamicHeader
{
public DynamicHeader(long offset, byte[] bytes)
{
Offset = offset;
Bytes = bytes;
ParentLocators = Enumerable.Range(0, 8).Select(index => ParentLocator.Read(bytes, index)).ToArray();
}
public long Offset { get; }
public byte[] Bytes { get; }
public ParentLocator[] ParentLocators { get; private set; }
public string ParentUnicodeName
{
get
{
byte[] nameBytes = Bytes.AsSpan(64, 512).ToArray();
string value = Encoding.BigEndianUnicode.GetString(nameBytes);
return value.TrimEnd('\0');
}
}
public static DynamicHeader Read(Stream stream, ulong offset)
{
if (offset > long.MaxValue || offset + VhdConstants.DynamicHeaderSize > (ulong)stream.Length)
{
throw new InvalidDataException("The VHD dynamic header offset is outside the file.");
}
byte[] bytes = new byte[VhdConstants.DynamicHeaderSize];
stream.Position = (long)offset;
ReadExactly(stream, bytes);
string cookie = Encoding.ASCII.GetString(bytes, 0, 8);
if (cookie != VhdConstants.DynamicHeaderCookie)
{
throw new InvalidDataException("The VHD dynamic header cookie is invalid.");
}
uint storedChecksum = Endian.ReadUInt32(bytes, 36);
Endian.WriteUInt32(bytes, 36, 0);
uint computedChecksum = VhdChecksum.Compute(bytes);
Endian.WriteUInt32(bytes, 36, storedChecksum);
if (storedChecksum != computedChecksum)
{
throw new InvalidDataException("The VHD dynamic header checksum is invalid.");
}
return new DynamicHeader((long)offset, bytes);
}
public void SetParentUnicodeName(string parentPath)
{
Bytes.AsSpan(64, 512).Clear();
string value = parentPath.Length > 255 ? parentPath[..255] : parentPath;
Encoding.BigEndianUnicode.GetBytes(value, Bytes.AsSpan(64, 512));
}
public void SetLocator(ParentLocator locator)
{
locator.Write(Bytes);
ParentLocators[locator.Index] = locator;
}
public void UpdateChecksum()
{
Endian.WriteUInt32(Bytes, 36, 0);
Endian.WriteUInt32(Bytes, 36, VhdChecksum.Compute(Bytes));
}
private static void ReadExactly(Stream stream, byte[] buffer)
{
int read = 0;
while (read < buffer.Length)
{
int count = stream.Read(buffer, read, buffer.Length - read);
if (count == 0)
{
throw new EndOfStreamException();
}
read += count;
}
}
}

26
src/VHDPatchFix/Endian.cs Normal file
View File

@@ -0,0 +1,26 @@
using System.Buffers.Binary;
namespace VHDPatchFix;
public static class Endian
{
public static uint ReadUInt32(ReadOnlySpan<byte> bytes, int offset)
{
return BinaryPrimitives.ReadUInt32BigEndian(bytes.Slice(offset, 4));
}
public static ulong ReadUInt64(ReadOnlySpan<byte> bytes, int offset)
{
return BinaryPrimitives.ReadUInt64BigEndian(bytes.Slice(offset, 8));
}
public static void WriteUInt32(Span<byte> bytes, int offset, uint value)
{
BinaryPrimitives.WriteUInt32BigEndian(bytes.Slice(offset, 4), value);
}
public static void WriteUInt64(Span<byte> bytes, int offset, ulong value)
{
BinaryPrimitives.WriteUInt64BigEndian(bytes.Slice(offset, 8), value);
}
}

View File

@@ -0,0 +1,38 @@
using System.Text;
namespace VHDPatchFix;
public sealed record ParentLocator(int Index, string PlatformCode, uint PlatformDataSpace, uint PlatformDataLength, ulong PlatformDataOffset)
{
private static readonly HashSet<string> WindowsCodes = new(StringComparer.Ordinal)
{
"W2ku",
"W2ru"
};
public bool IsPresent => PlatformCode.Trim('\0') != string.Empty && PlatformDataSpace > 0 && PlatformDataOffset > 0;
public bool IsWindowsUnicode => WindowsCodes.Contains(PlatformCode);
public static ParentLocator Read(ReadOnlySpan<byte> header, int index)
{
int offset = 576 + index * 24;
string platformCode = Encoding.ASCII.GetString(header.Slice(offset, 4));
return new ParentLocator(
index,
platformCode,
Endian.ReadUInt32(header, offset + 4),
Endian.ReadUInt32(header, offset + 8),
Endian.ReadUInt64(header, offset + 16));
}
public void Write(Span<byte> header)
{
int offset = 576 + Index * 24;
Encoding.ASCII.GetBytes(PlatformCode, header.Slice(offset, 4));
Endian.WriteUInt32(header, offset + 4, PlatformDataSpace);
Endian.WriteUInt32(header, offset + 8, PlatformDataLength);
Endian.WriteUInt32(header, offset + 12, 0);
Endian.WriteUInt64(header, offset + 16, PlatformDataOffset);
}
}

View File

@@ -0,0 +1,9 @@
namespace VHDPatchFix;
public sealed record PatchOptions(
string VhdPath,
string ParentPath,
bool DryRun,
bool NoBackup,
string? BackupPath,
bool Verbose);

View File

@@ -0,0 +1,10 @@
namespace VHDPatchFix;
public sealed record PatchResult(
string VhdPath,
string NewParentPath,
string? OldParentPath,
string? BackupPath,
bool DryRun,
bool UsedRelocation,
int UpdatedLocatorCount);

169
src/VHDPatchFix/Program.cs Normal file
View File

@@ -0,0 +1,169 @@
using System.Diagnostics;
using VHDPatchFix;
return ProgramMain.Run(args);
public static class ProgramMain
{
public static int Run(string[] args)
{
try
{
if (args.Length == 0 || args.Contains("--help") || args.Contains("-h"))
{
PrintUsage();
return args.Length == 0 ? 1 : 0;
}
PatchOptions options = ParseArgs(args);
PatchResult result = VhdParentPathPatcher.Patch(options);
PrintResult(result);
if (options.Verbose && !options.DryRun)
{
TryRunGetVhd(options.VhdPath);
}
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
return 2;
}
}
private static PatchOptions ParseArgs(string[] args)
{
string? vhdPath = null;
string? parentPath = null;
string? backupPath = null;
bool dryRun = false;
bool noBackup = false;
bool verbose = false;
for (int index = 0; index < args.Length; index++)
{
string arg = args[index];
switch (arg)
{
case "--vhd":
vhdPath = ReadValue(args, ref index, arg);
break;
case "--parent":
parentPath = ReadValue(args, ref index, arg);
break;
case "--backup-path":
backupPath = ReadValue(args, ref index, arg);
break;
case "--dry-run":
dryRun = true;
break;
case "--no-backup":
noBackup = true;
break;
case "--verbose":
verbose = true;
break;
default:
throw new ArgumentException($"Unknown argument: {arg}");
}
}
if (string.IsNullOrWhiteSpace(vhdPath))
{
throw new ArgumentException("Missing required argument: --vhd <path>");
}
if (string.IsNullOrWhiteSpace(parentPath))
{
throw new ArgumentException("Missing required argument: --parent <path>");
}
if (noBackup && backupPath is not null)
{
throw new ArgumentException("--no-backup cannot be used with --backup-path.");
}
return new PatchOptions(vhdPath, parentPath, dryRun, noBackup, backupPath, verbose);
}
private static string ReadValue(string[] args, ref int index, string name)
{
if (index + 1 >= args.Length)
{
throw new ArgumentException($"Missing value for {name}.");
}
index++;
return args[index];
}
private static void PrintUsage()
{
Console.WriteLine("Usage:");
Console.WriteLine(" vhdfix --vhd <child.vhd> --parent <new-parent-path> [--dry-run] [--no-backup] [--backup-path <path>] [--verbose]");
Console.WriteLine();
Console.WriteLine("Example:");
Console.WriteLine(@" vhdfix --vhd ""I:\internal_1.vhd"" --parent ""\Device\FscryptDisk_APP_0\internal_0.vhd""");
}
private static void PrintResult(PatchResult result)
{
Console.WriteLine(result.DryRun ? "Dry run completed." : "VHD parent path updated.");
Console.WriteLine($"VHD: {result.VhdPath}");
Console.WriteLine($"Old parent: {result.OldParentPath ?? "(not readable)"}");
Console.WriteLine($"New parent: {result.NewParentPath}");
Console.WriteLine($"Updated locators: {result.UpdatedLocatorCount}");
Console.WriteLine($"Relocation needed: {result.UsedRelocation}");
if (!result.DryRun)
{
if (!string.IsNullOrEmpty(result.BackupPath))
{
Console.WriteLine($"Backup: {result.BackupPath}");
}
Console.WriteLine($@"Verify with: Get-VHD -Path ""{result.VhdPath}""");
}
}
private static void TryRunGetVhd(string vhdPath)
{
try
{
ProcessStartInfo startInfo = new()
{
FileName = "powershell.exe",
ArgumentList = { "-NoProfile", "-Command", $"Get-VHD -Path '{vhdPath.Replace("'", "''")}' | Select-Object -ExpandProperty ParentPath" },
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using Process? process = Process.Start(startInfo);
if (process is null)
{
return;
}
string output = process.StandardOutput.ReadToEnd().Trim();
string error = process.StandardError.ReadToEnd().Trim();
process.WaitForExit(5000);
if (process.ExitCode == 0 && output.Length > 0)
{
Console.WriteLine($"Get-VHD ParentPath: {output}");
}
else if (error.Length > 0)
{
Console.WriteLine($"Get-VHD verification skipped: {error}");
}
}
catch
{
Console.WriteLine("Get-VHD verification skipped: PowerShell or Hyper-V cmdlets are unavailable.");
}
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>vhdfix</AssemblyName>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,15 @@
namespace VHDPatchFix;
public static class VhdChecksum
{
public static uint Compute(ReadOnlySpan<byte> bytes)
{
uint sum = 0;
foreach (byte value in bytes)
{
sum += value;
}
return ~sum;
}
}

View File

@@ -0,0 +1,11 @@
namespace VHDPatchFix;
public static class VhdConstants
{
public const int SectorSize = 512;
public const int FooterSize = 512;
public const int DynamicHeaderSize = 1024;
public const uint DifferencingDiskType = 4;
public const string FooterCookie = "conectix";
public const string DynamicHeaderCookie = "cxsparse";
}

View File

@@ -0,0 +1,50 @@
using System.Text;
namespace VHDPatchFix;
public sealed record VhdFooter(byte[] Bytes, ulong DataOffset, uint DiskType)
{
public static VhdFooter Read(Stream stream)
{
if (stream.Length < VhdConstants.FooterSize)
{
throw new InvalidDataException("File is too small to contain a VHD footer.");
}
byte[] bytes = new byte[VhdConstants.FooterSize];
stream.Position = stream.Length - VhdConstants.FooterSize;
ReadExactly(stream, bytes);
string cookie = Encoding.ASCII.GetString(bytes, 0, 8);
if (cookie != VhdConstants.FooterCookie)
{
throw new InvalidDataException("The file does not end with a classic VHD footer.");
}
uint storedChecksum = Endian.ReadUInt32(bytes, 64);
Endian.WriteUInt32(bytes, 64, 0);
uint computedChecksum = VhdChecksum.Compute(bytes);
Endian.WriteUInt32(bytes, 64, storedChecksum);
if (storedChecksum != computedChecksum)
{
throw new InvalidDataException("The VHD footer checksum is invalid.");
}
return new VhdFooter(bytes, Endian.ReadUInt64(bytes, 16), Endian.ReadUInt32(bytes, 60));
}
private static void ReadExactly(Stream stream, byte[] buffer)
{
int read = 0;
while (read < buffer.Length)
{
int count = stream.Read(buffer, read, buffer.Length - read);
if (count == 0)
{
throw new EndOfStreamException();
}
read += count;
}
}
}

View File

@@ -0,0 +1,141 @@
namespace VHDPatchFix;
public static class VhdParentPathPatcher
{
public static PatchResult Patch(PatchOptions options)
{
if (!File.Exists(options.VhdPath))
{
throw new FileNotFoundException("VHD file was not found.", options.VhdPath);
}
using FileStream stream = new(options.VhdPath, options.DryRun ? FileMode.Open : FileMode.Open, options.DryRun ? FileAccess.Read : FileAccess.ReadWrite, FileShare.Read);
VhdFooter footer = VhdFooter.Read(stream);
if (footer.DiskType != VhdConstants.DifferencingDiskType)
{
throw new InvalidDataException("Only classic VHD differencing disks are supported.");
}
DynamicHeader header = DynamicHeader.Read(stream, footer.DataOffset);
ParentLocator[] locators = header.ParentLocators.Where(locator => locator.IsPresent && locator.IsWindowsUnicode).ToArray();
if (locators.Length == 0)
{
throw new InvalidDataException("No Windows unicode parent locator was found in the VHD dynamic header.");
}
string? oldParentPath = locators
.Select(locator => VhdParentPathReader.ReadLocatorPath(stream, locator))
.FirstOrDefault(path => !string.IsNullOrWhiteSpace(path)) ?? header.ParentUnicodeName;
byte[] newLocatorData = VhdParentPathReader.EncodeWindowsLocatorPath(options.ParentPath);
bool needsRelocation = locators.Any(locator => newLocatorData.Length > CheckedCapacity(locator));
if (options.DryRun)
{
return new PatchResult(options.VhdPath, options.ParentPath, oldParentPath, null, true, needsRelocation, locators.Length);
}
string backupPath = CreateBackup(options);
header.SetParentUnicodeName(options.ParentPath);
foreach (ParentLocator locator in locators)
{
ParentLocator updated = newLocatorData.Length <= CheckedCapacity(locator)
? WriteInPlace(stream, locator, newLocatorData)
: WriteRelocated(stream, footer, locator, newLocatorData);
header.SetLocator(updated);
}
header.UpdateChecksum();
stream.Position = header.Offset;
stream.Write(header.Bytes);
stream.Flush(true);
stream.Position = 0;
VhdFooter verifyFooter = VhdFooter.Read(stream);
DynamicHeader verifyHeader = DynamicHeader.Read(stream, verifyFooter.DataOffset);
ParentLocator? verifyLocator = verifyHeader.ParentLocators.FirstOrDefault(locator => locator.IsPresent && locator.IsWindowsUnicode);
string? verifiedPath = verifyLocator is null ? null : VhdParentPathReader.ReadLocatorPath(stream, verifyLocator);
if (!string.Equals(verifiedPath, options.ParentPath, StringComparison.Ordinal))
{
throw new InvalidDataException("The VHD was written, but verification did not read back the requested parent path.");
}
return new PatchResult(options.VhdPath, options.ParentPath, oldParentPath, backupPath, false, needsRelocation, locators.Length);
}
private static string CreateBackup(PatchOptions options)
{
if (options.NoBackup)
{
return string.Empty;
}
string backupPath = options.BackupPath ?? options.VhdPath + ".bak";
if (File.Exists(backupPath))
{
throw new IOException($"Backup path already exists: {backupPath}");
}
File.Copy(options.VhdPath, backupPath);
return backupPath;
}
private static ParentLocator WriteInPlace(Stream stream, ParentLocator locator, byte[] data)
{
uint capacity = CheckedCapacity(locator);
stream.Position = (long)locator.PlatformDataOffset;
stream.Write(data);
WriteZeros(stream, checked((int)(capacity - data.Length)));
return locator with { PlatformDataLength = (uint)data.Length };
}
private static ParentLocator WriteRelocated(Stream stream, VhdFooter footer, ParentLocator locator, byte[] data)
{
long footerOffset = stream.Length - VhdConstants.FooterSize;
stream.SetLength(footerOffset);
long dataOffset = Align(stream.Length, VhdConstants.SectorSize);
stream.Position = stream.Length;
WriteZeros(stream, checked((int)(dataOffset - stream.Length)));
stream.Position = dataOffset;
stream.Write(data);
uint dataSpace = checked((uint)Align(data.Length, VhdConstants.SectorSize));
long paddedEnd = dataOffset + dataSpace;
WriteZeros(stream, checked((int)(paddedEnd - stream.Position)));
stream.Write(footer.Bytes);
return locator with
{
PlatformDataOffset = (ulong)dataOffset,
PlatformDataSpace = dataSpace,
PlatformDataLength = (uint)data.Length
};
}
private static uint CheckedCapacity(ParentLocator locator)
{
return locator.PlatformDataSpace;
}
private static long Align(long value, int alignment)
{
long remainder = value % alignment;
return remainder == 0 ? value : value + alignment - remainder;
}
private static void WriteZeros(Stream stream, int count)
{
if (count <= 0)
{
return;
}
byte[] zeros = new byte[Math.Min(count, 8192)];
while (count > 0)
{
int toWrite = Math.Min(count, zeros.Length);
stream.Write(zeros, 0, toWrite);
count -= toWrite;
}
}
}

View File

@@ -0,0 +1,91 @@
using System.Text;
namespace VHDPatchFix;
public static class VhdParentPathReader
{
public static string? ReadLocatorPath(Stream stream, ParentLocator locator)
{
if (!locator.IsPresent || locator.PlatformDataLength == 0)
{
return null;
}
if (locator.PlatformDataOffset > long.MaxValue ||
locator.PlatformDataOffset + locator.PlatformDataLength > (ulong)stream.Length)
{
return null;
}
byte[] bytes = new byte[locator.PlatformDataLength];
stream.Position = (long)locator.PlatformDataOffset;
ReadExactly(stream, bytes);
return DecodeLocatorPath(bytes);
}
public static string DecodeLocatorPath(ReadOnlySpan<byte> bytes)
{
ReadOnlySpan<byte> trimmed = TrimTrailingZeros(bytes);
string littleEndian = Encoding.Unicode.GetString(trimmed);
if (LooksLikePath(littleEndian))
{
return littleEndian.TrimEnd('\0');
}
string bigEndian = Encoding.BigEndianUnicode.GetString(trimmed);
if (LooksLikePath(bigEndian))
{
return bigEndian.TrimEnd('\0');
}
return Encoding.UTF8.GetString(trimmed).TrimEnd('\0');
}
public static byte[] EncodeWindowsLocatorPath(string parentPath)
{
return Encoding.Unicode.GetBytes(parentPath);
}
private static ReadOnlySpan<byte> TrimTrailingZeros(ReadOnlySpan<byte> bytes)
{
int length = bytes.Length;
while (length > 0 && bytes[length - 1] == 0)
{
length--;
}
if (length % 2 != 0 && length < bytes.Length)
{
length++;
}
return bytes.Slice(0, length);
}
private static bool LooksLikePath(string value)
{
string trimmed = value.TrimEnd('\0');
if (trimmed.Length == 0)
{
return false;
}
int controlChars = trimmed.Count(char.IsControl);
return controlChars == 0 && (trimmed.Contains('\\') || trimmed.Contains('/') || trimmed.Contains(':'));
}
private static void ReadExactly(Stream stream, byte[] buffer)
{
int read = 0;
while (read < buffer.Length)
{
int count = stream.Read(buffer, read, buffer.Length - read);
if (count == 0)
{
throw new EndOfStreamException();
}
read += count;
}
}
}

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("vhdfix")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("vhdfix")]
[assembly: System.Reflection.AssemblyTitleAttribute("vhdfix")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.

View File

@@ -0,0 +1,8 @@
// <auto-generated/>
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Threading;
global using System.Threading.Tasks;

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v10.0", FrameworkDisplayName = ".NET 10.0")]

View File

@@ -0,0 +1,22 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("vhdfix")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("vhdfix")]
[assembly: System.Reflection.AssemblyTitleAttribute("vhdfix")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.

View File

@@ -0,0 +1,8 @@
// <auto-generated/>
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Net.Http;
global using System.Threading;
global using System.Threading.Tasks;