diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml index 8dbdf50a4..02a4f96ad 100644 --- a/.github/workflows/build-ilspy.yml +++ b/.github/workflows/build-ilspy.yml @@ -350,6 +350,13 @@ jobs: --no-build --report-trx --results-directory test-results + - name: Execute ilspycmd tests + run: > + dotnet test --project ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj + --configuration Release + --no-build --report-trx + --results-directory test-results + - name: Execute BAML decompiler tests run: > dotnet test --project ILSpy.BamlDecompiler.Tests/ILSpy.BamlDecompiler.Tests.csproj diff --git a/ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj b/ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj new file mode 100644 index 000000000..6f9f4528b --- /dev/null +++ b/ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj @@ -0,0 +1,27 @@ + + + + + net11.0 + preview + + false + + true + ICSharpCode.ILSpyCmd.Tests.MicrosoftTestingPlatformEntryPoint + Exe + + + + + + + + + + + + + + + diff --git a/ICSharpCode.ILSpyCmd.Tests/MemberOptionTests.cs b/ICSharpCode.ILSpyCmd.Tests/MemberOptionTests.cs new file mode 100644 index 000000000..db083b6ef --- /dev/null +++ b/ICSharpCode.ILSpyCmd.Tests/MemberOptionTests.cs @@ -0,0 +1,156 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace ICSharpCode.ILSpyCmd.Tests +{ + [TestFixture] + public class ILSpyCmdMemberOptionTests + { + static readonly string testAssemblyPath = typeof(ILSpyCmdMemberOptionTests).Assembly.Location; + + static async Task<(int ExitCode, string Output, string Error)> RunAsync(params string[] args) + { + var originalOut = Console.Out; + var originalError = Console.Error; + var stdout = new StringWriter(); + var stderr = new StringWriter(); + try + { + Console.SetOut(stdout); + Console.SetError(stderr); + int exitCode = await ILSpyCmdProgram.Main(args); + return (exitCode, stdout.ToString(), stderr.ToString()); + } + finally + { + Console.SetOut(originalOut); + Console.SetError(originalError); + } + } + + [Test] + public async Task MethodByDocumentationIdString() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", + "-m", "M:ICSharpCode.ILSpyCmd.Tests.MemberOptionSample.Add(System.Int32,System.Int32)"); + + Assert.That(result.ExitCode, Is.EqualTo(0), result.Error); + Assert.That(result.Output, Does.Contain("int Add(int a, int b)")); + Assert.That(result.Output, Does.Not.Contain("Unrelated")); + } + + [Test] + public async Task PropertyByDocumentationIdString() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", + "-m", "P:ICSharpCode.ILSpyCmd.Tests.MemberOptionSample.Answer"); + + Assert.That(result.ExitCode, Is.EqualTo(0), result.Error); + Assert.That(result.Output, Does.Contain("Answer")); + Assert.That(result.Output, Does.Not.Contain("Add(int a, int b)")); + } + + [Test] + public async Task MethodByMetadataToken() + { + int token = typeof(MemberOptionSample).GetMethod(nameof(MemberOptionSample.Add))!.MetadataToken; + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", "-m", $"0x{token:x8}"); + + Assert.That(result.ExitCode, Is.EqualTo(0), result.Error); + Assert.That(result.Output, Does.Contain("int Add(int a, int b)")); + } + + [Test] + public async Task UnknownMemberReportsError() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", + "-m", "M:ICSharpCode.ILSpyCmd.Tests.MemberOptionSample.DoesNotExist"); + + Assert.That(result.ExitCode, Is.EqualTo(ProgramExitCodes.EX_DATAERR)); + Assert.That(result.Error, Does.Contain("DoesNotExist")); + } + + [Test] + public async Task MalformedTokenReportsTokenSpecificError() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", "-m", "0xZZ000001"); + + Assert.That(result.ExitCode, Is.EqualTo(ProgramExitCodes.EX_DATAERR)); + Assert.That(result.Error, Does.Contain("metadata token")); + Assert.That(result.Error, Does.Not.Contain("documentation id")); + } + + [Test] + public async Task MemberOfAnotherModuleReportsDistinctError() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", "-m", "M:System.Object.ToString"); + + Assert.That(result.ExitCode, Is.EqualTo(ProgramExitCodes.EX_DATAERR)); + Assert.That(result.Error, Does.Contain("defined in")); + } + + [Test] + public async Task SurroundingWhitespaceIsIgnored() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", + "-m", " M:ICSharpCode.ILSpyCmd.Tests.MemberOptionSample.Add(System.Int32,System.Int32) "); + + Assert.That(result.ExitCode, Is.EqualTo(0), result.Error); + Assert.That(result.Output, Does.Contain("int Add(int a, int b)")); + } + + [Test] + public async Task OutOfRangeTokenReportsError() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", "-m", "0x06ffffff"); + + Assert.That(result.ExitCode, Is.EqualTo(ProgramExitCodes.EX_DATAERR)); + Assert.That(result.Error, Does.Contain("0x06ffffff")); + } + + [Test] + public async Task TypeAndMemberOptionsAreMutuallyExclusive() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", + "-t", "ICSharpCode.ILSpyCmd.Tests.MemberOptionSample", + "-m", "P:ICSharpCode.ILSpyCmd.Tests.MemberOptionSample.Answer"); + + Assert.That(result.ExitCode, Is.EqualTo(ProgramExitCodes.EX_USAGE)); + } + } + + public class MemberOptionSample + { + public int Add(int a, int b) + { + return a + b; + } + + public string Answer => "42"; + + public void Unrelated() + { + } + } +} diff --git a/ICSharpCode.ILSpyCmd.Tests/packages.lock.json b/ICSharpCode.ILSpyCmd.Tests/packages.lock.json new file mode 100644 index 000000000..9d2d754cd --- /dev/null +++ b/ICSharpCode.ILSpyCmd.Tests/packages.lock.json @@ -0,0 +1,549 @@ +{ + "version": 2, + "dependencies": { + "net11.0": { + "Microsoft.Testing.Extensions.TrxReport": { + "type": "Direct", + "requested": "[2.1.0, )", + "resolved": "2.1.0", + "contentHash": "cXmP225WcMLLOSrW8xekaNhfzdBwXX3cbXbE5qSzmLbK0KZe3z8rAObKj70FWiPPPzm2W22x0ZW93gsmAfK6Mg==", + "dependencies": { + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.1.0", + "Microsoft.Testing.Platform": "2.1.0" + } + }, + "Microsoft.Testing.Extensions.VSTestBridge": { + "type": "Direct", + "requested": "[2.1.0, )", + "resolved": "2.1.0", + "contentHash": "bNRIEA2YoGr+Y+7LHdA7i1U80+7BAdf4K4Qh4Kx6eKkoBK/NV7QpoMg+GWPP0/eqAFzuUmUOIPVZ87Oo0Vyxmw==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.0.1", + "Microsoft.Testing.Extensions.Telemetry": "2.1.0", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.1.0", + "Microsoft.Testing.Platform": "2.1.0" + } + }, + "NUnit": { + "type": "Direct", + "requested": "[4.6.1, )", + "resolved": "4.6.1", + "contentHash": "xS4+YaBFUv1r8bcAbjitSfYaRZGfMwUiMdiaRziBXZpKgVxKDSHhjUn0mV5mObHGZRZq6eNa+WFWr3g8grj66A==" + }, + "NUnit3TestAdapter": { + "type": "Direct", + "requested": "[6.2.0, )", + "resolved": "6.2.0", + "contentHash": "8PMJB8za2u8S0ey/nEtvh9BHjLhv5yzUEMCmA0+VIPLKSeOAZ3TEHWDyAp0+iPQb3jnBH1o8k0PoU2FgzXLRnQ==", + "dependencies": { + "Microsoft.Extensions.DependencyModel": "8.0.2", + "Microsoft.Testing.Extensions.VSTestBridge": "2.1.0", + "Microsoft.Testing.Platform.MSBuild": "2.1.0" + } + }, + "TomsToolbox.Composition.Analyzer": { + "type": "Direct", + "requested": "[2.24.0, )", + "resolved": "2.24.0", + "contentHash": "dKHqW1MeAMnDIbtx8qDTsGwy/7LUiQ3ccdzHX0PzCh1r98Lgl/1deIky9+ojZO0K5HjeA7uE+eW9/h+v7EOIBA==" + }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.23.0", + "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==", + "dependencies": { + "System.Diagnostics.DiagnosticSource": "5.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "5Vnd2I75DmZCVEjSynIdJ/0EGafgnLQwgR3t2C2/fkjx/nRG+cLwxLLdInoHeCEpkD5K4Ov/g9ZCRYrl4TRsaA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "GqmN2o1CkJvk7uWp+p4CwBYW0w/zfoEbvsiFDbO2G8l1Uz+mrDAbAcZiXhU2lufKPby1cjAUdd5GTWpebYOkOA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "33cBeR2HRbzHUTtmcmLdNOApneNGcymwwL4arHuotgVK9Frba8kcDTrvVTj7cSCmF1R9OiSbZH0KxNOwab3HUg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "KRfFSSCV58vEdU7mPED/YMzeovIWF5P0g8s9K8n9HEfy0/WzMq37SrPdXdFN5/dFT/rPMHpF7AvpoXHckbcBFg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "ZOhZYwvbXGTgGVRwswIirofEMVHuWdxjdh0JeUZXwaF9cgcjXdz/t0ELtgaevw7ezTyv47yPNCgGreWtLkn3IQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Physical": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "1s1sKFTk/Foam64JY6+m/diH8drL3Wx6V3gtSd5v1IEZtszZYyc1pW8uRnMblzpNiR0l0t8gGk7tXj3xHzFgdg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Json": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Physical": "10.0.10" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "ANyvsgkNBRvcJh2XLgn8veGmajf+8m0AbKK+HPWdRL1yraSNVVSmQhFntLtdz/C795jxqqup+k05cs/3jZQPOA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "8.0.2", + "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "Kr/e7lUf4+N8tacbqJ2Ctwe/HarKdAc9ZkgKVVqvtJDBKbez+T/KnUwu82KSlnBp/SrpBcxc7u7xkE2oUZT/5Q==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.10", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.10" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "9uWiKpeOVac355STyChWR/pliFX/5CeLqChW9kKsaxyDH4EUTZxMkT4Jwp/J/peLm0GBFmSX5c0WCse3yCnq1Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "c5zqFCY9DiIpMovLd7/d/CTiEtrMOuQ639dhv3PABtKQIKNQikSHwQt8+N679uii9q+B55lgK28Uv64FOwEu8w==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "jhJAyo38kSrH3ARvWUk0h8itogVnQu2DCZuPo+s0Z+tXes0ugTxMPaHYzap85785eHQmPFqD9TYERqBbtGxn/w==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "jSOCVxEwCd4Aq925kJVz1kSO1EpX2OHYKL04qVREXkDU7Ce3pVDdHPYm+fEy8y/th2kJf/DAstRHpJAqoNWP8w==" + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "5LugpYGHk+mkn0a8IZgcyfBca8PCTAU9RQFoMrTdtOOidq88M2SI5f3px6ugnzgxC+eTkvYYJi8pzlUnG5xdAQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "Tf6z5HsL0VDYRTfvsoNrTGHGheCwkTsZBA2FFh5ATJUbkAwug+FFNISJK2gjpUNemlAOoWllAK52HOWCjto3EQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "cLrqxkuEfcilZ8SjK+9KAnpLk9lOoMPaOokF+wRUYie+iUEcdX4/p/+gJkt0BYgWLthjpBUCkVTBI6Kxg0nsOw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Binder": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "VIlNzPwPS0GeQVSmCqqo36ugryX3LpE9ul6gEkks5VLET3weH/XMLeWmclwfoGn4Nxi2mwVibB+OZBVJ9tDqvg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Configuration": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "8+TZBnV5fgBXoVNJ5ROSErUwYogk4hOgV7c2HWK1u5cqKGmiUTUn7+KqZ35iQu8e/B7Ykccyz5OTjdXcidNZ9g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "0RE4951AzQ+YD4gVrvbq0BhdsiBgSDo44yM7+QBZ2mrmMJeNjY+teCIYfUjqDPVYnKs0HR6SkkhgrX1YgXZq3Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "System.Diagnostics.EventLog": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "85SAPwXhJtdBInzN2k7SChiFiBGh3KOWay5AfoY+GREF6P7oZA98+ST2p7Z9384iLKYjkZSKIZ/FqIO5aojtNw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "srnhnk7nE8krBiIXp71LvBmKBtraBONWSRzdjJgRv1Ko9Mp8IVNqv4vIS9hGeVteBig8aQkva9ZG+sC+o5sVcA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "tnBmu/LwF25ZQK+HBNCu2xrwnkKoB/XEbJyooGGoYxHrhvxbSKi7eOFiJ4AXBy/QU4vtCvCJfoi8k9Ej72qzOQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Binder": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "5wu/GrYVd8mG2DVUw3vFJzF+O336TyTGg/Kmcgw9bfwYhCoFiV5lR5QeEmKecJyrW4W54nMfD3p3589E8a7czQ==" + }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "2.1.0", + "contentHash": "5TwgTx2u7k9Al/xbZ18QXq4Hdy2xewkVTI6K3sk+jY2ykqUkIKNuj7rFu3GOV5KnEUkevhw6eZcyZs77STHJIA==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "Microsoft.Testing.Platform": "2.1.0" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "2.1.0", + "contentHash": "D8xmIJYQFJ6D49Rx5/vPrkZZxb338Jkew+eSqZLBfBiWKw4QZKy3i1BOXiLfz0lOmaNErwDz/YWRojCdNl+B9Q==", + "dependencies": { + "Microsoft.Testing.Platform": "2.1.0" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "2.1.0", + "contentHash": "aHkjNTGIA+Zbdw6RJgSFrbDrCjO0CgqpElqYcvkRSeUhBv2bKarnvU3ep786U7UqrPlArT/B7VmImRibJD0Zrg==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "2.1.0", + "contentHash": "UpfPebXQtHGrWz21+YLHmJSm+5zsuPE9U9pfdCtoB+67g75fDmWlNgpkH2ZmdVhSwkjNIed9Icg8Iu63z2ei5Q==", + "dependencies": { + "Microsoft.Testing.Platform": "2.1.0" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.0.1", + "contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "NuGet.Common": { + "type": "Transitive", + "resolved": "7.6.0", + "contentHash": "uyXLqkbbZmkMvdHOR23l1EHW2hRmULtzoG3Ocj84VpGptnNfkODVboHGJfDfcn9Gi9oUNDs8/VjL4FZcST6zVg==", + "dependencies": { + "NuGet.Frameworks": "7.6.0" + } + }, + "NuGet.Configuration": { + "type": "Transitive", + "resolved": "7.6.0", + "contentHash": "+bNj+YneC5CNg1vR+WZjLAakscJlsi0KhADZUgIJPn4pwh8/jeCUMC5ik5cpET/i+QOplDy7aJVTGkpdfrlPww==", + "dependencies": { + "NuGet.Common": "7.6.0", + "System.Security.Cryptography.ProtectedData": "8.0.0" + } + }, + "NuGet.Packaging": { + "type": "Transitive", + "resolved": "7.6.0", + "contentHash": "TDp+qHzRBy1zjwiJGCbfpdO0jMG5hH/bk7p1EABLKv9p5SIykDPGnbuYXm2iZO0QJ/H+hOI/vo5LfqM17Q+G0w==", + "dependencies": { + "Newtonsoft.Json": "13.0.3", + "NuGet.Configuration": "7.6.0", + "NuGet.Versioning": "7.6.0", + "System.Security.Cryptography.Pkcs": "8.0.1" + } + }, + "NuGet.Versioning": { + "type": "Transitive", + "resolved": "7.6.0", + "contentHash": "TpZxfOoQBQk/0r/2uc1A1qNYIKHkJGgOrWP+ax3nsNAUN/1BOQMDrgmGADogSA4hOXH1ZJiyeYg4Ca+vUW0sEg==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "OvGz3PrzuAI/Sj7LTcXcCe3FClRI1IyRMZjNONcZtFh+Ww7nAtSh4kh08r8KVe/xxkXJPjR0Y1jF7H+N42d4xQ==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" + }, + "icsharpcode.bamldecompiler": { + "type": "Project", + "dependencies": { + "ICSharpCode.Decompiler": "[8.0.0-noversion, )" + } + }, + "icsharpcode.decompiler": { + "type": "Project", + "dependencies": { + "System.Collections.Immutable": "[9.0.0, )", + "System.Reflection.Metadata": "[9.0.0, )" + } + }, + "icsharpcode.ilspyx": { + "type": "Project", + "dependencies": { + "ICSharpCode.Decompiler": "[8.0.0-noversion, )", + "K4os.Compression.LZ4": "[1.3.8, )", + "Mono.Cecil": "[0.11.6, )", + "System.Composition.AttributedModel": "[10.0.10, )", + "System.Reflection.Metadata": "[10.0.10, )", + "System.Runtime.CompilerServices.Unsafe": "[6.1.2, )" + } + }, + "ilspycmd": { + "type": "Project", + "dependencies": { + "ICSharpCode.BamlDecompiler": "[10.0.0-noversion, )", + "ICSharpCode.Decompiler": "[8.0.0-noversion, )", + "ICSharpCode.ILSpyX": "[8.0.0-noversion, )", + "McMaster.Extensions.Hosting.CommandLine": "[5.1.0, )", + "Microsoft.Extensions.Hosting": "[10.0.10, )", + "NuGet.Protocol": "[7.6.0, )", + "System.Security.Cryptography.Pkcs": "[10.0.10, )" + } + }, + "K4os.Compression.LZ4": { + "type": "CentralTransitive", + "requested": "[1.3.8, )", + "resolved": "1.3.8", + "contentHash": "LhwlPa7c1zs1OV2XadMtAWdImjLIsqFJPoRcIWAadSRn0Ri1DepK65UbWLPmt4riLqx2d40xjXRk0ogpqNtK7g==" + }, + "McMaster.Extensions.CommandLineUtils": { + "type": "CentralTransitive", + "requested": "[5.1.0, )", + "resolved": "5.1.0", + "contentHash": "YceiKmxdsBrlkAJpmJ2N8XkLyAH01xLsm4VLfjNp5MvtAom2wflZtEBz3CLMQret6Z7jSh3oBO1ULF1Is0tNxA==" + }, + "McMaster.Extensions.Hosting.CommandLine": { + "type": "CentralTransitive", + "requested": "[5.1.0, )", + "resolved": "5.1.0", + "contentHash": "XN6s/86/bP0PSsP5p2eX49ZKmWrURSNg7nvrG5vws3abm7Py1A4bv3x3Pdvg7FqR8mLgEE0gNTI1qDtlYQIQnw==", + "dependencies": { + "McMaster.Extensions.CommandLineUtils": "5.1.0", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.5", + "Microsoft.Extensions.Logging.Abstractions": "10.0.5" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "plJWK2zpWuuyxI8F8s2scx6Je7N1Ajjs6HvYUGKwRnDMWIVIz9FHwAkiT7ASgrvAOd10T0FPVlh9BzAJJME+jg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "uvJ6sHwjgrkMEJOgiC76G0mcZGXerwyyWkwX34EOjCbxKG6TCtfAoqDKAMsCvEBf9HxjlGQEgqsSMOGCmGBf+A==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA==" + }, + "Microsoft.Extensions.Hosting": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "tL9FkfV64GPUDSPvwrgyw42LVzsnVAnyrqJEuZVJbODgrQ3eL63zmzEcVWoCHzfgqUhWggzbgAyUCnz/zfI3Pg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Binder": "10.0.10", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.10", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.10", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.10", + "Microsoft.Extensions.Configuration.Json": "10.0.10", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.10", + "Microsoft.Extensions.DependencyInjection": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Diagnostics": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Physical": "10.0.10", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Configuration": "10.0.10", + "Microsoft.Extensions.Logging.Console": "10.0.10", + "Microsoft.Extensions.Logging.Debug": "10.0.10", + "Microsoft.Extensions.Logging.EventLog": "10.0.10", + "Microsoft.Extensions.Logging.EventSource": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Mono.Cecil": { + "type": "CentralTransitive", + "requested": "[0.11.6, )", + "resolved": "0.11.6", + "contentHash": "f33RkDtZO8VlGXCtmQIviOtxgnUdym9xx/b1p9h91CRGOsJFxCFOFK1FDbVt1OCf1aWwYejUFa2MOQyFWTFjbA==" + }, + "NuGet.Frameworks": { + "type": "CentralTransitive", + "requested": "[7.6.0, )", + "resolved": "7.6.0", + "contentHash": "rJ7QtKN45XzLXCrMATve6eFLiUyUGEkA1rFSb6U6Fw6laM4hEAcKOrcdbgWlcFUlCK2158qP1LF00hg/ivF3nw==" + }, + "NuGet.Protocol": { + "type": "CentralTransitive", + "requested": "[7.6.0, )", + "resolved": "7.6.0", + "contentHash": "Ccb9dJG9hW0FdHFjXoHmhJBBJRYSCeSJArLdZjyZj6/FEAIKezLn75KFQNrTPE5UiAp4HVrjBizufZ/0IXhfKQ==", + "dependencies": { + "NuGet.Packaging": "7.6.0" + } + }, + "System.Collections.Immutable": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "Ih5zrydoDc1H5I0eNP6f4Lzw3cjsPMN4Nikd86kbyi66y0flkM9GfjVo62aUbcxeTbypzBCFAFQ+/6RF2jRORg==" + }, + "System.Composition.AttributedModel": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "alK+Ew7XBsxHgwmSmiZzg7x4sLpNIaQ3QVJUkJ/issHZY0L+RHceqO27pDp0J0sDnSf8g6yJNpDnWSKL5sAa/g==" + }, + "System.Reflection.Metadata": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "NZIS6/IUBh70nJfVvE577oDc81y5GfYRgjWssC8AGpSlVzSu5YfbHXEtHXyLeEY7gqqbAaASwJEP0Oy3Q54Jfw==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "CentralTransitive", + "requested": "[6.1.2, )", + "resolved": "6.1.2", + "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" + }, + "System.Security.Cryptography.Pkcs": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "KG8t5RHczdgB3IWvRndpI0nE1DSo7ikDYSDbITxfgRFyIvnYiKzuPjaZOsPnwFQ1aOGUl9sEulJul9wlHLEUoA==" + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj index 405d9e687..8a70111f4 100644 --- a/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj +++ b/ICSharpCode.ILSpyCmd/ICSharpCode.ILSpyCmd.csproj @@ -71,4 +71,8 @@ + + + + diff --git a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs index 1d4489fc5..5968c22d4 100644 --- a/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs +++ b/ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs @@ -23,7 +23,9 @@ using System.IO; using System.IO.Compression; using System.IO.MemoryMappedFiles; using System.Linq; +using System.Globalization; using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Threading; using System.Threading.Tasks; @@ -33,6 +35,7 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.DebugInfo; +using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Solution; @@ -105,6 +108,9 @@ Examples: [Option("-t|--type ", "The fully qualified name of the type to decompile.", CommandOptionType.SingleValue)] public string TypeName { get; } + [Option("-m|--member ", "The single member to decompile: an XML documentation id string (e.g. \"M:System.String.Concat(System.String,System.String)\", as also accepted by the UI's --navigateto option) or a metadata token (e.g. 0x06000005).", CommandOptionType.SingleValue)] + public string MemberIdString { get; } + [Option("-il|--ilcode", "Show IL code.", CommandOptionType.NoValue)] public bool ShowILCodeFlag { get; } @@ -359,6 +365,12 @@ Examples: } else { + if (MemberIdString != null && TypeName != null) + { + app.Error.WriteLine("The --type and --member options are mutually exclusive."); + return ProgramExitCodes.EX_USAGE; + } + if (outputDirectory != null) { string outputName = Path.GetFileNameWithoutExtension(fileName); @@ -366,6 +378,11 @@ Examples: (string.IsNullOrEmpty(TypeName) ? outputName : TypeName) + ".decompiled.cs")); } + if (MemberIdString != null) + { + return DecompileMember(fileName, output, MemberIdString); + } + return Decompile(fileName, output, TypeName); } } @@ -619,6 +636,84 @@ Examples: return 0; } + int DecompileMember(string assemblyFileName, TextWriter output, string idOrToken) + { + CSharpDecompiler decompiler = GetDecompiler(assemblyFileName); + + if (!TryResolveMember(decompiler.TypeSystem, idOrToken, out EntityHandle handle, out string error)) + { + Console.Error.WriteLine(error); + return ProgramExitCodes.EX_DATAERR; + } + + output.Write(decompiler.DecompileAsString(handle)); + return 0; + } + + /// + /// Resolves a member reference supplied on the command line: either an XML + /// documentation id string ("M:...", "P:...", "F:...", "E:..." or "T:...") or a + /// metadata token in hexadecimal "0x06000005" form. Documentation id strings use + /// the same syntax as the compiler-generated documentation files and the UI's + /// --navigateto option. + /// + static bool TryResolveMember(IDecompilerTypeSystem typeSystem, string idOrToken, out EntityHandle handle, out string error) + { + handle = default; + error = null; + string trimmed = idOrToken.Trim(); + + if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + if (!int.TryParse(trimmed.AsSpan(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int tokenValue)) + { + error = $"'{trimmed}' is not a valid metadata token; expected a hexadecimal value like 0x06000005."; + return false; + } + var metadata = typeSystem.MainModule.MetadataFile.Metadata; + var candidate = MetadataTokens.EntityHandle(tokenValue); + int rowNumber = tokenValue & 0x00ffffff; + int rowCount = candidate.Kind switch { + HandleKind.TypeDefinition => metadata.TypeDefinitions.Count, + HandleKind.FieldDefinition => metadata.FieldDefinitions.Count, + HandleKind.MethodDefinition => metadata.MethodDefinitions.Count, + HandleKind.PropertyDefinition => metadata.PropertyDefinitions.Count, + HandleKind.EventDefinition => metadata.EventDefinitions.Count, + _ => 0, + }; + if (rowNumber < 1 || rowNumber > rowCount) + { + error = $"Metadata token {trimmed} does not reference a type or member of this module."; + return false; + } + handle = candidate; + return true; + } + + IEntity entity; + try + { + entity = IdStringProvider.FindEntity(trimmed, new SimpleTypeResolveContext(typeSystem.MainModule)); + } + catch (ReflectionNameParseException ex) + { + error = $"'{trimmed}' is not a valid documentation id string: {ex.Message}"; + return false; + } + if (entity == null || entity.MetadataToken.IsNil) + { + error = $"Member '{trimmed}' was not found in this module. Expected an XML documentation id string (e.g. \"M:System.String.Concat(System.String,System.String)\") or a metadata token (e.g. 0x06000005)."; + return false; + } + if (entity.ParentModule != typeSystem.MainModule) + { + error = $"Member '{trimmed}' is defined in '{entity.ParentModule?.AssemblyName}', not in this module."; + return false; + } + handle = entity.MetadataToken; + return true; + } + /// /// Resolves a type name supplied on the command line to a single type definition. /// diff --git a/ILSpy.Desktop.slnf b/ILSpy.Desktop.slnf index 1317367df..906305e90 100644 --- a/ILSpy.Desktop.slnf +++ b/ILSpy.Desktop.slnf @@ -5,6 +5,7 @@ "ICSharpCode.BamlDecompiler\\ICSharpCode.BamlDecompiler.csproj", "ICSharpCode.Decompiler.TestRunner\\ICSharpCode.Decompiler.TestRunner.csproj", "ICSharpCode.Decompiler.Tests\\ICSharpCode.Decompiler.Tests.csproj", + "ICSharpCode.ILSpyCmd.Tests\\ICSharpCode.ILSpyCmd.Tests.csproj", "ICSharpCode.Decompiler\\ICSharpCode.Decompiler.csproj", "ICSharpCode.ILSpyX\\ICSharpCode.ILSpyX.csproj", "ILSpy.BamlDecompiler.Tests\\ILSpy.BamlDecompiler.Tests.csproj", diff --git a/ILSpy.XPlat.slnf b/ILSpy.XPlat.slnf index ca432d29c..42d3936e9 100644 --- a/ILSpy.XPlat.slnf +++ b/ILSpy.XPlat.slnf @@ -3,6 +3,7 @@ "path": "ILSpy.sln", "projects": [ "ICSharpCode.ILSpyCmd\\ICSharpCode.ILSpyCmd.csproj", + "ICSharpCode.ILSpyCmd.Tests\\ICSharpCode.ILSpyCmd.Tests.csproj", "ICSharpCode.Decompiler.PowerShell\\ICSharpCode.Decompiler.PowerShell.csproj", "ICSharpCode.Decompiler.TestRunner\\ICSharpCode.Decompiler.TestRunner.csproj", "ICSharpCode.Decompiler.Tests\\ICSharpCode.Decompiler.Tests.csproj", diff --git a/ILSpy.sln b/ILSpy.sln index 2de1bce33..b75eea7a1 100644 --- a/ILSpy.sln +++ b/ILSpy.sln @@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy.BamlDecompiler.Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy.BamlDecompiler.Tests.Windows", "ILSpy.BamlDecompiler.Tests.Windows\ILSpy.BamlDecompiler.Tests.Windows.csproj", "{7A5C3CEF-0744-4643-BA1E-7AE6610FF269}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.ILSpyCmd.Tests", "ICSharpCode.ILSpyCmd.Tests\ICSharpCode.ILSpyCmd.Tests.csproj", "{042FE5F2-3C0A-4B99-A268-D1917CC67B47}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -239,6 +241,18 @@ Global {7A5C3CEF-0744-4643-BA1E-7AE6610FF269}.Release|x64.Build.0 = Release|Any CPU {7A5C3CEF-0744-4643-BA1E-7AE6610FF269}.Release|x86.ActiveCfg = Release|Any CPU {7A5C3CEF-0744-4643-BA1E-7AE6610FF269}.Release|x86.Build.0 = Release|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Debug|x64.ActiveCfg = Debug|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Debug|x64.Build.0 = Debug|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Debug|x86.ActiveCfg = Debug|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Debug|x86.Build.0 = Debug|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Release|Any CPU.Build.0 = Release|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Release|x64.ActiveCfg = Release|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Release|x64.Build.0 = Release|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Release|x86.ActiveCfg = Release|Any CPU + {042FE5F2-3C0A-4B99-A268-D1917CC67B47}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE