Skip to content

Commit

Permalink
Add customize command
Browse files Browse the repository at this point in the history
  • Loading branch information
nxtn committed Aug 5, 2020
1 parent 7e644f7 commit 2a576e1
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 14 deletions.
38 changes: 38 additions & 0 deletions src/WinSW.Core/Native/ResourceApis.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma warning disable SA1310 // Field names should not contain underscore

using System;
using System.Runtime.InteropServices;

namespace WinSW.Native
{
internal static class ResourceApis
{
internal static readonly IntPtr VS_VERSION_INFO = new IntPtr(1);

internal static readonly IntPtr RT_VERSION = new IntPtr(16);

[DllImport(Libraries.Kernel32, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr BeginUpdateResourceW(string fileName, bool deleteExistingResources);

[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern bool EndUpdateResourceW(IntPtr update, bool discard);

[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern IntPtr FindResourceW(IntPtr module, IntPtr name, IntPtr type);

[DllImport(Libraries.Kernel32)]
internal static extern bool FreeLibrary(IntPtr libModule);

[DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode)]
internal static extern IntPtr LoadLibraryW(string libFileName);

[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern IntPtr LoadResource(IntPtr module, IntPtr resInfo);

[DllImport(Libraries.Kernel32)]
internal static extern IntPtr LockResource(IntPtr resData);

[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern bool UpdateResourceW(IntPtr update, IntPtr type, IntPtr name, ushort language, IntPtr data, int size);
}
}
170 changes: 170 additions & 0 deletions src/WinSW.Core/Native/Resources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using static WinSW.Native.ResourceApis;

namespace WinSW.Native
{
internal static class Resources
{
/// <exception cref="CommandException" />
internal static unsafe bool UpdateCompanyName(string path, string outputPath, string companyName)
{
IntPtr module = LoadLibraryW(path);
try
{
IntPtr verInfo = FindResourceW(module, VS_VERSION_INFO, RT_VERSION);
if (verInfo == IntPtr.Zero)
{
Exit();
}

IntPtr resData = LoadResource(module, verInfo);
if (resData == IntPtr.Zero)
{
Exit();
}

IntPtr resAddr = LockResource(resData);

IntPtr address = resAddr;
int offset = 0;

short length = ((short*)address)[0];
short valueLength = ReadHeaderAndAdvance();
string key = ReadKeyAndAdvance();
Debug.Assert(key == "VS_VERSION_INFO");
offset += valueLength;
Align();

valueLength = ReadHeaderAndAdvance();
key = ReadKeyAndAdvance();
Debug.Assert(key == "VarFileInfo");
offset += valueLength;
Align();

valueLength = ReadHeaderAndAdvance();
key = ReadKeyAndAdvance();
Debug.Assert(key == "Translation");
ushort language = ((ushort*)address)[0];
ushort codePage = ((ushort*)address)[1];
offset += valueLength;
address = resAddr + offset;

valueLength = ReadHeaderAndAdvance();
key = ReadKeyAndAdvance();
Debug.Assert(key == "StringFileInfo");
offset += valueLength;
Align();

short stringTableLength = ((short*)address)[0];
int stringTableEndOffset = offset + stringTableLength;
valueLength = ReadHeaderAndAdvance();
key = ReadKeyAndAdvance();
Debug.Assert(key == $"{language:x4}{codePage:x4}");

do
{
int valueLengthOffset = offset + sizeof(short);
valueLength = ReadHeaderAndAdvance();
key = ReadKeyAndAdvance();

if (key != "CompanyName")
{
offset += sizeof(short) * valueLength;
Align(); // ?
continue;
}

// int oldLength = "CloudBees, Inc.".Length + 1; // 16
int newLength = companyName.Length + 1;
Debug.Assert(newLength > 12 && newLength <= 16);

IntPtr newAddress = Marshal.AllocHGlobal(length);
try
{
Buffer.MemoryCopy((void*)resAddr, (void*)newAddress, length, length);

*(short*)(newAddress + valueLengthOffset) = (short)newLength;
fixed (char* ptr = companyName)
{
Buffer.MemoryCopy(ptr, (void*)(newAddress + offset), newLength * sizeof(char), newLength * sizeof(char));
}

File.Copy(path, outputPath, true);

IntPtr update = BeginUpdateResourceW(outputPath, false);
if (update == IntPtr.Zero)
{
Exit();
}

try
{
if (!UpdateResourceW(update, RT_VERSION, VS_VERSION_INFO, language, newAddress, length))
{
Exit();
}

if (!EndUpdateResourceW(update, false))
{
Exit();
}

return true;
}
catch
{
_ = EndUpdateResourceW(update, true);
throw;
}
}
finally
{
Marshal.FreeHGlobal(newAddress);
}
}
while (offset < stringTableEndOffset);

return false;

static void Exit()
{
Throw.Command.Win32Exception();
}

void Align()
{
if ((offset & 3) != 0)
{
offset &= ~3;
offset += 4;
}

address = resAddr + offset;
}

short ReadHeaderAndAdvance()
{
valueLength = ((short*)address)[1];
offset += sizeof(short) * 3;
address = resAddr + offset;
return valueLength;
}

string ReadKeyAndAdvance()
{
string key = Marshal.PtrToStringUni(address)!;
offset += sizeof(char) * (key.Length + 1);
Align();
return key;
}
}
finally
{
_ = FreeLibrary(module);
}
}
}
}
9 changes: 9 additions & 0 deletions src/WinSW.Core/Native/Throw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ internal static void Win32Exception(int error, string message)
/// <exception cref="CommandException" />
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void Win32Exception()
{
Win32Exception inner = new Win32Exception();
Debug.Assert(inner.NativeErrorCode != 0);
throw new CommandException(inner);
}

/// <exception cref="CommandException" />
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void Win32Exception(string message)
{
Win32Exception inner = new Win32Exception();
Expand Down
12 changes: 1 addition & 11 deletions src/WinSW.Tests/Configuration/ExamplesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,7 @@ public void MinimalConfigShouldDeclareDefaults()

private static XmlServiceConfig Load(string exampleName)
{
string directory = Environment.CurrentDirectory;
while (true)
{
if (File.Exists(Path.Combine(directory, ".gitignore")))
{
break;
}

directory = Path.GetDirectoryName(directory);
Assert.NotNull(directory);
}
string directory = Layout.RepositoryRoot;

string path = Path.Combine(directory, $@"samples\sample-{exampleName}.xml");
Assert.True(File.Exists(path));
Expand Down
32 changes: 32 additions & 0 deletions src/WinSW.Tests/MainTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Diagnostics;
using System.IO;
using System.ServiceProcess;
using WinSW.Tests.Util;
using Xunit;
Expand Down Expand Up @@ -47,5 +49,35 @@ public void ShouldNotPrintLogsForStatusCommand()
string cliOut = CommandLineTestHelper.Test(new[] { "status" });
Assert.Equal("NonExistent" + Environment.NewLine, cliOut);
}

#if NET461
[Fact]
public void Customize()
{
const string OldCompanyName = "CloudBees, Inc.";
const string NewCompanyName = "CLOUDBEES, INC.";

string inputPath = Path.Combine(Layout.ArtifactsDirectory, "WinSW.NET461.exe");

Assert.Equal(OldCompanyName, FileVersionInfo.GetVersionInfo(inputPath).CompanyName);

// deny write access
using FileStream file = File.OpenRead(inputPath);

string outputPath = Path.GetTempFileName();
Program.TestExecutablePath = inputPath;
try
{
_ = CommandLineTestHelper.Test(new[] { "customize", "-o", outputPath, "--manufacturer", NewCompanyName });

Assert.Equal(NewCompanyName, FileVersionInfo.GetVersionInfo(outputPath).CompanyName);
}
finally
{
Program.TestExecutablePath = null;
File.Delete(outputPath);
}
}
#endif
}
}
39 changes: 39 additions & 0 deletions src/WinSW.Tests/Util/Layout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.IO;
using Xunit;

namespace WinSW.Tests.Util
{
internal static class Layout
{
private static string repositoryRoot;
private static string artifactsDirectory;

internal static string RepositoryRoot
{
get
{
if (repositoryRoot != null)
{
return repositoryRoot;
}

string directory = Environment.CurrentDirectory;
while (true)
{
if (File.Exists(Path.Combine(directory, ".gitignore")))
{
break;
}

directory = Path.GetDirectoryName(directory);
Assert.NotNull(directory);
}

return repositoryRoot = directory;
}
}

internal static string ArtifactsDirectory => artifactsDirectory ??= Path.Combine(RepositoryRoot, "artifacts");
}
}
Loading

0 comments on commit 2a576e1

Please sign in to comment.