using System; using System.IO; using System.Collections; using System.Text; using System.Diagnostics; namespace IDE.util { class ResourceGen { [CRepr] struct IconDir { public uint16 mReserved; public uint16 mType; public uint16 mCount; } [CRepr] struct IconDirectoryEntry { public uint8 mWidth; public uint8 mHeight; public uint8 mColorCount; public uint8 mReserved; public uint16 mPlanes; public uint16 mBitCount; public uint32 mBytesInRes; public uint32 mImageOffset; } [CRepr] struct ResourceHeader { public uint32 mDataSize; public uint32 mHeaderSize; public uint32 mType; public uint32 mName; public uint32 mDataVersion; public uint16 mMemoryFlags; public uint16 mLanguageId; public uint32 mVersion; public uint32 mCharacteristics; } [CRepr] struct VsFixedFileInfo { public uint32 mSignature; public uint32 mStrucVersion; public uint32 mFileVersionMS; public uint32 mFileVersionLS; public uint32 mProductVersionMS; public uint32 mProductVersionLS; public uint32 mFileFlagsMask; public uint32 mFileFlags; public uint32 mFileOS; public uint32 mFileType; public uint32 mFileSubtype; public uint32 mFileDateMS; public uint32 mFileDateLS; }; FileStream mOutStream = new FileStream() ~ delete _; public Result AddIcon(String iconFile) { if (!iconFile.EndsWith(".ico", .OrdinalIgnoreCase)) { gApp.OutputErrorLine("Invalid icon file: {0}. Expected '.ico' file.", iconFile); return .Err; } FileStream stream = scope FileStream(); if (stream.Open(iconFile, .Read) case .Err) { gApp.OutputErrorLine("Failed to open icon file: {0}", iconFile); return .Err; } let iconDir = Try!(stream.Read()); if ((iconDir.mReserved != 0) || (iconDir.mType != 1) || (iconDir.mCount > 0x100)) { gApp.OutputErrorLine("Invalid file format: {0}", iconFile); return .Err; } var entries = scope List(); for (int idx < iconDir.mCount) { entries.Add(Try!(stream.Read())); } for (int idx < iconDir.mCount) { let iconEntry = ref entries[idx]; Try!(stream.Seek(iconEntry.mImageOffset)); if (iconEntry.mBytesInRes > 1024*1024) { gApp.OutputErrorLine("Invalid icon entry in: {0}", iconFile); return .Err; } uint8* data = new:ScopedAlloc! uint8[iconEntry.mBytesInRes]*; Try!(stream.TryRead(.(data, iconEntry.mBytesInRes))); ResourceHeader res; res.mDataSize = iconEntry.mBytesInRes; res.mHeaderSize = 32; res.mType = 0xFFFF | (3 << 16); res.mName = 0xFFFF | ((uint32)(idx + 1) << 16); res.mDataVersion = 0; res.mMemoryFlags = 0x1010; res.mLanguageId = 1033; res.mVersion = 0; res.mCharacteristics = 0; Try!(mOutStream.Write(res)); Try!(mOutStream.TryWrite(Span(data, iconEntry.mBytesInRes))); mOutStream.Align(4); } ResourceHeader res; res.mDataSize = 6 + 14 * iconDir.mCount; res.mHeaderSize = 32; res.mType = 0xFFFF | (14 << 16); res.mName = 0xFFFF | (101 << 16); res.mDataVersion = 0; res.mMemoryFlags = 0x1030; res.mLanguageId = 1033; res.mVersion = 0; res.mCharacteristics = 0; Try!(mOutStream.Write(res)); Try!(mOutStream.Write(iconDir)); for (int idx < iconDir.mCount) { var iconEntry = ref entries[idx]; // Write entry without mImageOffset Try!(mOutStream.TryWrite(Span((uint8*)&iconEntry, 12))); Try!(mOutStream.Write((int16)idx + 1)); } mOutStream.Align(4); return .Ok; } public Result Start(String resFileName) { if (mOutStream.Create(resFileName) case .Err) { return .Err; } ResourceHeader res; res.mDataSize = 0; res.mHeaderSize = 32; res.mType = 0xFFFF; res.mName = 0xFFFF; res.mDataVersion = 0; res.mMemoryFlags = 0; res.mLanguageId = 0; res.mVersion = 0; res.mCharacteristics = 0; Try!(mOutStream.Write(res)); return .Ok; } public Result AddVersion(String description, String comments, String company, String product, String copyright, String fileVersion, String productVersion, String originalFileName) { MemoryStream strStream = scope MemoryStream(); void AddString(String key, String value) { if (value.IsEmpty) return; let key16 = scope EncodedString(key, Encoding.UTF16); let value16 = scope EncodedString(value, Encoding.UTF16); int keyPadding = ((key16.Size / 2) & 1) + 1; int valuePadding = 2 - ((value16.Size / 2) & 1); if (value16.Size != 0) { strStream.Write((uint16)(key16.Size + keyPadding*2 + value16.Size + 8)); //wLength strStream.Write((uint16)(value16.Size / 2) + 1); //wValueLength } else { strStream.Write((uint16)(key16.Size + keyPadding*2 + value16.Size + 6)); //wLength valuePadding = 0; strStream.Write((uint16)0); //wValueLength } strStream.Write((uint16)1); //wType strStream.TryWrite(.(key16.Ptr, key16.Size)); for (int pad < keyPadding) strStream.Write((char16)0); strStream.TryWrite(.(value16.Ptr, value16.Size)); for (int pad < valuePadding) strStream.Write((char16)0); } void WriteStr16(Stream stream, String str, int length) { for (var c in str.RawChars) stream.Write((char16)c); for (int i = str.Length; i < length; i++) stream.Write((char16)0); } let ext = scope String(); Path.GetExtension(originalFileName, ext); let internalName = scope String(); Path.GetFileNameWithoutExtension(originalFileName, internalName); AddString("CompanyName", company); AddString("FileDescription", description); AddString("Comments", comments); AddString("FileVersion", fileVersion); AddString("InternalName", internalName); AddString("LegalCopyright", copyright); AddString("OriginalFilename", originalFileName); AddString("ProductName", product); AddString("ProductVersion", productVersion); int verSectionCount = 0; uint64 version = 0; for (let verSection in fileVersion.Split('.')) { int32 verSect = 0; bool endNow = false; switch (int32.Parse(verSection)) { case .Ok(out verSect): case .Err(.InvalidChar(out verSect)): endNow = true; default: } version <<= 16; version |= (uint64)verSect; if (++verSectionCount == 4) break; if (endNow) break; } if (verSectionCount < 4) version <<= (int)(16 * (4 - verSectionCount)); int strTableSize = strStream.Length; ResourceHeader res; res.mDataSize = (uint32)strTableSize + 0xDC; res.mHeaderSize = 32; res.mType = 0xFFFF | (16 << 16); res.mName = 0xFFFF | (1 << 16); res.mDataVersion = 0; res.mMemoryFlags = 0x30; res.mLanguageId = 1033; res.mVersion = 0; res.mCharacteristics = 0; Try!(mOutStream.Write(res)); Try!(mOutStream.Write((uint16)(strTableSize + 0xDC))); Try!(mOutStream.Write((uint16)0x34)); Try!(mOutStream.Write((uint16)0)); // wType = binary WriteStr16(mOutStream, "VS_VERSION_INFO", 17); VsFixedFileInfo ffi; ffi.mSignature = 0xFEEF04BD; ffi.mStrucVersion = 0x10000; ffi.mFileVersionMS = (uint32)(version >> 32); ffi.mFileVersionLS = (uint32)(version); ffi.mProductVersionMS = (uint32)(version >> 32); ffi.mProductVersionLS = (uint32)(version); ffi.mFileFlagsMask = 0x3F; ffi.mFileFlags = 0; ffi.mFileOS = 0x40004; if (ext.Equals(".DLL", .OrdinalIgnoreCase)) ffi.mFileType = 2; //DLL else ffi.mFileType = 1; //APP ffi.mFileSubtype = 0; ffi.mFileDateMS = 0; ffi.mFileDateLS = 0; Try!(mOutStream.Write(ffi)); // StringFileInfo Try!(mOutStream.Write((uint16)(strTableSize + 0x3A))); Try!(mOutStream.Write((uint16)0)); // wValueLength Try!(mOutStream.Write((uint16)1)); // wType WriteStr16(mOutStream, "StringFileInfo", 15); // StringTable Try!(mOutStream.Write((uint16)(strTableSize + 0x16))); // wLength Try!(mOutStream.Write((uint16)0)); // mValueLength Try!(mOutStream.Write((uint16)1)); // mType WriteStr16(mOutStream, "040904b0", 9); // szKey strStream.Position = 0; strStream.CopyTo(mOutStream); // StringTable mOutStream.Write((uint16)0x44); mOutStream.Write((uint16)0x0); mOutStream.Write((uint16)0x1); WriteStr16(mOutStream, "VarFileInfo", 13); mOutStream.Write((uint16)0x24); mOutStream.Write((uint16)0x4); mOutStream.Write((uint16)0x0); WriteStr16(mOutStream, "Translation", 13); mOutStream.Write((uint32)0x04B00409); return .Ok; } public Result AddManifest(String manifestPath) { String manifestText = scope String(); if (File.ReadAllText(manifestPath, manifestText) case .Err) { gApp.OutputLine("Failed to open manifest file: {0}", manifestPath); return .Err; } ResourceHeader res; res.mDataSize = (uint32)manifestText.Length + 3; res.mHeaderSize = 32; res.mType = 0xFFFF | (0x18 << 16); res.mName = 0xFFFF | (1 << 16); res.mDataVersion = 0; res.mMemoryFlags = 0x1030; res.mLanguageId = 1033; res.mVersion = 0; res.mCharacteristics = 0; Try!(mOutStream.Write(res)); Try!(mOutStream.Write(uint8[3] (0xEF, 0xBB, 0xBF))); // UTF8 BOM if (manifestText.Length > 0) Try!(mOutStream.TryWrite(Span((uint8*)manifestText.Ptr, manifestText.Length))); mOutStream.Align(4); return .Ok; } public Result Finish() { return .Ok; } } }