diff --git a/TcBuild.Test/ParserTest.cs b/TcBuild.Test/ParserTest.cs index 91fb6db..34825ff 100644 --- a/TcBuild.Test/ParserTest.cs +++ b/TcBuild.Test/ParserTest.cs @@ -46,10 +46,12 @@ public void Test2() var MSBuildFrameworkToolsPath = @"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\"; var FrameworkSDKRoot = @"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\"; + var libPath = @"..\..\..\..\TcBuild\lib\"; var tools = new Tools( ilasmPath: Path.Combine(MSBuildFrameworkToolsPath, "ilasm.exe"), ildasmPath: new DirectoryInfo(FrameworkSDKRoot).GetFiles("ildasm.exe", SearchOption.AllDirectories).OrderByDescending(_ => _.DirectoryName).FirstOrDefault()?.FullName, + rcPath: Path.Combine(libPath, "RC.exe"), logger ); diff --git a/TcBuild.Test/TcBuildTask_Test.cs b/TcBuild.Test/TcBuildTask_Test.cs index 8b910e5..c78dfaf 100644 --- a/TcBuild.Test/TcBuildTask_Test.cs +++ b/TcBuild.Test/TcBuildTask_Test.cs @@ -49,6 +49,7 @@ public void Test_Run() MSBuildFrameworkToolsPath = @"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\", FrameworkSDKRoot = @"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\", CacheDir = cacheDir.FullName, + LibDirectory = @"..\..\..\..\TcBuild\lib\", //-- BuildEngine = new FakeBuildEngine(_output) }; diff --git a/TcBuild/Processor.cs b/TcBuild/Processor.cs index 004f08a..7e50fb5 100644 --- a/TcBuild/Processor.cs +++ b/TcBuild/Processor.cs @@ -62,14 +62,18 @@ public Task ExecuteAsync(CancellationToken token = default) token.ThrowIfCancellationRequested(); + // ico resource + FileInfo resFile = new FileInfo(Path.Combine(workDir.FullName, $"{AssemblyFile.Name}.res")); + _tools.TryCreateResFile(AssemblyFile, resFile); + // create: x86 - _tools.Assemble(wrapperSource, outFile, false, IsRelease); + _tools.Assemble(wrapperSource, outFile, resFile, false, IsRelease); _log.LogInfo($"{outFile.FullName}"); token.ThrowIfCancellationRequested(); // create: x64 - _tools.Assemble(wrapperSource, outFile64, true, IsRelease); + _tools.Assemble(wrapperSource, outFile64, resFile, true, IsRelease); _log.LogInfo($"{outFile64.FullName}"); token.ThrowIfCancellationRequested(); @@ -96,7 +100,8 @@ public Task ExecuteAsync(CancellationToken token = default) //.Where(_ => _.Name != "Microsoft.Build.Framework.dll") //.Where(_ => _.Name != "Microsoft.Build.Utilities.Core.dll") //.Where(_ => _.Name != "System.Collections.Immutable.dll") - ) + ), + GetSatelliteAssemblyFiles() ); if (!success) { _log.LogWarning("ZIP Archiver is not found - Installation Archive is not created."); @@ -114,6 +119,22 @@ public Task ExecuteAsync(CancellationToken token = default) } } + private IEnumerable GetSatelliteAssemblyFiles() + { + IEnumerable allAssemblyFiles = new[] { AssemblyFile }.Concat(ReferenceFiles).Where(f => f.Extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase)); + foreach (FileInfo assemblyFile in allAssemblyFiles) + { + foreach (FileInfo satelliteAssemblyFile in GetSatelliteAssemblyFiles(assemblyFile)) + { + yield return satelliteAssemblyFile; + } + } + } + + private IEnumerable GetSatelliteAssemblyFiles(FileInfo assemblyFile) + { + return assemblyFile.Directory.EnumerateFiles($"{Path.GetFileNameWithoutExtension(assemblyFile.Name)}.resources{assemblyFile.Extension}", SearchOption.AllDirectories); + } private void CreatePluginstFile(FileInfo iniFile, FileInfo outFile, PluginType pluginType) { diff --git a/TcBuild/TcBuild.csproj b/TcBuild/TcBuild.csproj index a0d5282..6aba575 100644 --- a/TcBuild/TcBuild.csproj +++ b/TcBuild/TcBuild.csproj @@ -32,6 +32,15 @@ true build + + + true + lib + + + true + lib + diff --git a/TcBuild/TcBuildTask.cs b/TcBuild/TcBuildTask.cs index c0da3bd..97bca2d 100644 --- a/TcBuild/TcBuildTask.cs +++ b/TcBuild/TcBuildTask.cs @@ -30,6 +30,8 @@ public class TcBuildTask : AppDomainIsolatedTask, ICancelableTask, ITask { [Required] public string CacheDir { get; set; } + internal string LibDirectory { get; set; } + //[Output] //public string TargetExt { get; private set; } @@ -47,6 +49,7 @@ public override bool Execute() var tools = new Tools( ilasmPath: Path.Combine(MSBuildFrameworkToolsPath, "ilasm.exe"), ildasmPath: new DirectoryInfo(FrameworkSDKRoot).GetFiles("ildasm.exe", SearchOption.AllDirectories).OrderByDescending(_ => _.DirectoryName).FirstOrDefault()?.FullName, + rcPath: Path.Combine(LibDirectory ?? new FileInfo(TcPluginBase).Directory.Parent.FullName, "RC.Exe"), _log ); diff --git a/TcBuild/lib/RC.Exe b/TcBuild/lib/RC.Exe new file mode 100644 index 0000000..c07b5de Binary files /dev/null and b/TcBuild/lib/RC.Exe differ diff --git a/TcBuild/lib/rcdll.dll b/TcBuild/lib/rcdll.dll new file mode 100644 index 0000000..43774f0 Binary files /dev/null and b/TcBuild/lib/rcdll.dll differ diff --git a/TcBuild/src/IconExtractor.cs b/TcBuild/src/IconExtractor.cs new file mode 100644 index 0000000..c1ff301 --- /dev/null +++ b/TcBuild/src/IconExtractor.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; + +namespace TcBuild +{ + internal static class IconExtractor + { + [UnmanagedFunctionPointer(CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private delegate bool ENUMRESNAMEPROC(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool FreeLibrary(IntPtr hModule); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr LockResource(IntPtr hResData); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern bool EnumResourceNames(IntPtr hModule, IntPtr lpszType, ENUMRESNAMEPROC lpEnumFunc, IntPtr lParam); + + + private const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002; + private readonly static IntPtr RT_ICON = (IntPtr)3; + private readonly static IntPtr RT_GROUP_ICON = (IntPtr)14; + + public static bool ExtractIconFromExecutable(FileInfo sourceFile, FileInfo targetFile) + { + IntPtr hModule = LoadLibraryEx(sourceFile.FullName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); + if (hModule == IntPtr.Zero) + throw new FileNotFoundException($"Library {sourceFile.Name} not found.", sourceFile.FullName); + + ENUMRESNAMEPROC callback = (h, t, name, l) => + { + var dir = GetDataFromResource(hModule, RT_GROUP_ICON, name); + + // Calculate the size of an entire .icon file. + + int count = BitConverter.ToUInt16(dir, 4); // GRPICONDIR.idCount + int len = 6 + 16 * count; // sizeof(ICONDIR) + sizeof(ICONDIRENTRY) * count + for (int i = 0; i < count; ++i) + len += BitConverter.ToInt32(dir, 6 + 14 * i + 8); // GRPICONDIRENTRY.dwBytesInRes + + using (FileStream targetStream = targetFile.Create()) + using (var dst = new BinaryWriter(targetStream)) + { + // Copy GRPICONDIR to ICONDIR. + + dst.Write(dir, 0, 6); + + int picOffset = 6 + 16 * count; // sizeof(ICONDIR) + sizeof(ICONDIRENTRY) * count + + for (int i = 0; i < count; ++i) + { + // Load the picture. + + ushort id = BitConverter.ToUInt16(dir, 6 + 14 * i + 12); // GRPICONDIRENTRY.nID + var pic = GetDataFromResource(hModule, RT_ICON, (IntPtr)id); + + // Copy GRPICONDIRENTRY to ICONDIRENTRY. + + dst.Seek(6 + 16 * i, 0); + + dst.Write(dir, 6 + 14 * i, 8); // First 8bytes are identical. + dst.Write(pic.Length); // ICONDIRENTRY.dwBytesInRes + dst.Write(picOffset); // ICONDIRENTRY.dwImageOffset + + // Copy a picture. + + dst.Seek(picOffset, 0); + dst.Write(pic, 0, pic.Length); + + picOffset += pic.Length; + } + } + return true; + }; + bool ok = EnumResourceNames(hModule, RT_GROUP_ICON, callback, IntPtr.Zero); + FreeLibrary(hModule); + return ok; + } + private static byte[] GetDataFromResource(IntPtr hModule, IntPtr type, IntPtr name) + { + // Load the binary data from the specified resource. + + IntPtr hResInfo = FindResource(hModule, name, type); + + IntPtr hResData = LoadResource(hModule, hResInfo); + + IntPtr pResData = LockResource(hResData); + + uint size = SizeofResource(hModule, hResInfo); + + byte[] buf = new byte[size]; + Marshal.Copy(pResData, buf, 0, buf.Length); + + return buf; + } + + } +} diff --git a/TcBuild/src/Tools.cs b/TcBuild/src/Tools.cs index 3cf9f26..db516c4 100644 --- a/TcBuild/src/Tools.cs +++ b/TcBuild/src/Tools.cs @@ -11,13 +11,15 @@ namespace TcBuild { public class Tools { private readonly string _ilasmPath; private readonly string _ildasmPath; + private readonly string _rcPath; private readonly ILogger _log; - public Tools(string ilasmPath, string ildasmPath, ILogger log) + public Tools(string ilasmPath, string ildasmPath, string rcPath, ILogger log) { _log = log; _ilasmPath = ilasmPath; _ildasmPath = ildasmPath; + _rcPath = rcPath; // MSBuildFrameworkToolsPath + ilasm.exe // FrameworkSDKRoot + ildasm.exe @@ -35,8 +37,14 @@ public Tools(string ilasmPath, string ildasmPath, ILogger log) throw new Exception("Cannot locate IL Disassembler ildasm.exe!"); } - _log.LogInfo($"IL Disassembler: '{_ildasmPath}'"); - _log.LogInfo($"IL Assembler : '{_ilasmPath}'"); + if (_rcPath != null && !File.Exists(_rcPath)) { + _log.LogError(_rcPath); + throw new Exception("Cannot locate Resource compiler rc.exe!"); + } + + _log.LogInfo($"IL Disassembler : '{_ildasmPath}'"); + _log.LogInfo($"IL Assembler : '{_ilasmPath}'"); + _log.LogInfo($"Resource compiler : '{_rcPath}'"); } @@ -62,7 +70,7 @@ public void Disassemble(FileInfo assemblyFile, FileInfo sourcePath, bool emitDeb } - public void Assemble(FileInfo inFile, FileInfo outFile, bool x64, bool release = false) + public void Assemble(FileInfo inFile, FileInfo outFile, FileInfo resFile, bool x64, bool release = false) { outFile.Delete(); @@ -72,9 +80,9 @@ public void Assemble(FileInfo inFile, FileInfo outFile, bool x64, bool release = args.Add($"/out:{Quote(outFile.FullName)}"); args.Add($"/dll"); - //if (resFile.Exists()) { - // args.Add($"/res:{Quote(resFile.FullName)}"); - //} + if (resFile?.Exists == true) { + args.Add($"/res:{Quote(resFile.FullName)}"); + } if (x64) { args.Add($"/x64"); @@ -92,7 +100,7 @@ public void Assemble(FileInfo inFile, FileInfo outFile, bool x64, bool release = } - public bool CreateZip(FileInfo zipFile, IEnumerable files) + public bool CreateZip(FileInfo zipFile, IEnumerable files, IEnumerable satelliteAssemblyFiles) { try { zipFile.Delete(); @@ -104,8 +112,13 @@ public bool CreateZip(FileInfo zipFile, IEnumerable files) fileContents.CopyTo(entry); } } + foreach (var file in satelliteAssemblyFiles.Where(_ => _.Exists)) { + using (var entry = zip.CreateEntry($"{file.Directory.Name}/{file.Name}").Open()) + using (var fileContents = file.OpenRead()) { + fileContents.CopyTo(entry); + } + } } - return true; } catch (Exception e) { @@ -114,6 +127,21 @@ public bool CreateZip(FileInfo zipFile, IEnumerable files) } } + public bool TryCreateResFile(FileInfo assemblyFile, FileInfo resFile) + { + if (_rcPath == null) + throw new InvalidOperationException("RC.exe path not specified."); + FileInfo icoFile = new FileInfo(Path.ChangeExtension(resFile.FullName, ".ico")); + FileInfo rcFile = new FileInfo(Path.ChangeExtension(resFile.FullName, ".rc")); + if (IconExtractor.ExtractIconFromExecutable(assemblyFile, icoFile)) + { + File.WriteAllText(rcFile.FullName, $"1 ICON \"{icoFile.Name}\""); + if (!TryRun(_rcPath, Quote(rcFile.FullName))) + throw new Exception($"RC.exe has failed create resource!\r\n{_rcPath} \"{rcFile.FullName}\""); + return true; + } + return false; + } private static string Quote(string arg) { diff --git a/WfxWrapper/FsWrapper.cs b/WfxWrapper/FsWrapper.cs index a61a6b6..be09a77 100644 --- a/WfxWrapper/FsWrapper.cs +++ b/WfxWrapper/FsWrapper.cs @@ -522,10 +522,11 @@ private static ExecResult ExecuteFileInternal(IntPtr mainWin, RemotePath remoteN var resStr = result.Type.ToString(); if (result.Type == ExecResult.ExecEnum.SymLink && result.SymlinkTarget.HasValue) { - resStr += " (" + result.SymlinkTarget + ")"; + resStr += " (" + result.SymlinkTarget.Path + ")"; } TraceCall(TraceLevel.Warning, resStr); + return result; } catch (Exception ex) { ProcessException(ex);