Skip to content

Instantly share code, notes, and snippets.

@jazzdelightsme
Created December 30, 2016 07:39
Show Gist options
  • Save jazzdelightsme/844a5e6b97a58148767e01eb6dac5c9b to your computer and use it in GitHub Desktop.
Save jazzdelightsme/844a5e6b97a58148767e01eb6dac5c9b to your computer and use it in GitHub Desktop.
C# code that can emit a .res file with native version info (VS_FIXEDFILEINFO et al)
// This class is used to generate a compiled win32 resource (.res) file containing version
// information for use during compilation. (it's sort of a substitute for rc.exe and a .rc
// file)
static class ResourceEmitter
{
/// <summary>
/// Similar to the Version class, but uses ushorts instead of ints (because the
/// full version needs to be able to fit into 64 bits).
/// </summary>
public class ResourceVersion : IComparable< ResourceVersion >,
IEquatable< ResourceVersion >
{
public readonly ushort Major;
public readonly ushort Minor;
public readonly ushort Build; // Sometimes this field is known as "dot", and
public readonly ushort Revision; // this one as "build".
#region Constructors
public ResourceVersion( ushort major, ushort minor )
: this( major, minor, ushort.MaxValue, ushort.MaxValue )
{
}
public ResourceVersion( ushort major, ushort minor, ushort build )
: this( major, minor, build, ushort.MaxValue )
{
}
public ResourceVersion( ushort major, ushort minor, ushort build, ushort revision )
{
// Should we allow a /completely/ unspecified version? It would compare as
// less than anything else.
if( major == ushort.MaxValue )
throw new ArgumentException( "You must pass at least one non-max version component." );
bool sawMax = false;
if( minor == ushort.MaxValue )
sawMax = true;
if( build == ushort.MaxValue )
sawMax = true;
else if( sawMax )
throw new ArgumentException( "You can't have non-max values after max values." );
if( revision == ushort.MaxValue )
sawMax = true;
else if( sawMax )
throw new ArgumentException( "You can't have non-max values after max values." );
Major = major;
Minor = minor;
Build = build;
Revision = revision;
} // end constructor
public ResourceVersion( string versionString )
{
var tmp = Parse( versionString );
Major = tmp.Major;
Minor = tmp.Minor;
Build = tmp.Build;
Revision = tmp.Revision;
} // end constructor
public ResourceVersion( ResourceVersion other )
{
Major = other.Major;
Minor = other.Minor;
Build = other.Build;
Revision = other.Revision;
}
#endregion
public void ToInts( out int mostSignificant, out int leastSignificant )
{
mostSignificant = (((int) (uint) Major) << 16) | ((int) (uint) Minor);
leastSignificant = (((int) (uint) Build) << 16) | ((int) (uint) Revision);
}
public long ToLong()
{
int ms, ls;
ToInts( out ms, out ls );
return (((long) (ulong) ms) << 32) | ((long) (ulong) (uint) ls);
}
/// <summary>
/// Like System.Version, omits unspecified portions of the version. (i.e. "new
/// ResourceVersion( "2.0" ).ToString()" yields "2.0", not "2.0.0.0")
/// </summary>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append( Major );
if( ushort.MaxValue != Minor )
{
sb.Append( '.' );
sb.Append( Minor );
if( ushort.MaxValue != Build )
{
sb.Append( '.' );
sb.Append( Build );
if( ushort.MaxValue != Revision )
{
sb.Append( '.' );
sb.Append( Revision );
}
}
}
return sb.ToString();
} // end ToString()
#region Parsing
private static bool _TryParse( string s, out ResourceVersion version, out string reason )
{
version = null;
reason = null;
if( null == s )
throw new ArgumentNullException( "s" );
string[] parts = s.Split( '.' );
if( 0 == parts.Length )
{
reason = "cannot convert an empty string.";
return false;
}
// System.Version requires at least two parts. Do we want to be the same?
if( parts.Length > 4 )
{
reason = "too many parts.";
return false;
}
ushort[] parsed = new ushort[ 4 ] { UInt16.MaxValue,
UInt16.MaxValue,
UInt16.MaxValue,
UInt16.MaxValue };
for( int i = 0; i < parts.Length; i++ )
{
if( 0 == parts[ i ].Length )
{
reason = String.Format( "part {0} was empty.", i );
return false;
}
if( !UInt16.TryParse( parts[ i ], out parsed[ i ] ) )
{
reason = String.Format( "part {0} was not a valid UInt16.", i );
return false;
}
}
version = new ResourceVersion( parsed[ 0 ],
parsed[ 1 ],
parsed[ 2 ],
parsed[ 3 ] );
return true;
} // end TryParse()
public static bool TryParse( string verionString, out ResourceVersion version )
{
string dontCare;
return _TryParse( verionString, out version, out dontCare );
}
public static ResourceVersion Parse( string versionString )
{
string reason;
ResourceVersion v;
if( !_TryParse( versionString, out v, out reason ) )
{
throw new ArgumentException( String.Format( "Could not parse '{0}' as a version: {1}", versionString, reason ), "versionString" );
}
return v;
}
#endregion
#region Value-type stuff
public int CompareTo( ResourceVersion other )
{
if( Object.ReferenceEquals( other, null ) )
return int.MaxValue;
//
// N.B. ushort.MaxValue is treated specially (as less than everything else)
//
// The consequence of this is that a [a ResourceVersion object constructed
// from] "2.0" compares as less than [a ResourceVersion object constructed
// from] "2.0.0".
//
if( Major != other.Major )
{
if( Major == ushort.MaxValue )
return -1;
else if( other.Major == ushort.MaxValue )
return 1;
else
return Major.CompareTo( other.Major );
}
if( Minor != other.Minor )
{
if( Minor == ushort.MaxValue )
return -1;
else if( other.Minor == ushort.MaxValue )
return 1;
else
return Minor.CompareTo( other.Major );
}
if( Build != other.Build )
{
if( Build == ushort.MaxValue )
return -1;
else if( other.Build == ushort.MaxValue )
return 1;
else
return Build.CompareTo( other.Major );
}
if( Revision != other.Revision )
{
if( Revision == ushort.MaxValue )
return -1;
else if( other.Revision == ushort.MaxValue )
return 1;
else
return Revision.CompareTo( other.Major );
}
return 0;
} // end CompareTo()
public bool Equals( ResourceVersion other )
{
if( Object.ReferenceEquals( other, null ) )
return false;
return 0 == CompareTo( other );
}
public override bool Equals( object obj )
{
return Equals( obj as ResourceVersion );
}
public override int GetHashCode()
{
// Alternative: return ToLong().GetHashCode();
return Major.GetHashCode() +
(Minor.GetHashCode() << 6) +
(Build.GetHashCode() << 12) +
(Revision.GetHashCode() << 18);
}
public static bool operator ==( ResourceVersion v1, ResourceVersion v2 )
{
if( Object.ReferenceEquals( null, v1 ) )
return Object.ReferenceEquals( null, v2 );
return v1.Equals( v2 );
}
public static bool operator !=( ResourceVersion v1, ResourceVersion v2 )
{
return !(v1 == v2);
}
private static int _Compare( ResourceVersion v1, ResourceVersion v2 )
{
if( Object.ReferenceEquals( null, v1 ) )
{
if( Object.ReferenceEquals( null, v2 ) )
return 0; // null == null
else
return Int32.MinValue; // null < X
}
else if( Object.ReferenceEquals( null, v2 ) )
{
return Int32.MaxValue; // X > null
}
return v1.CompareTo( v2 );
}
public static bool operator <( ResourceVersion v1, ResourceVersion v2 )
{
return _Compare( v1, v2 ) < 0;
}
public static bool operator >( ResourceVersion v1, ResourceVersion v2 )
{
return _Compare( v1, v2 ) > 0;
}
public static bool operator <=( ResourceVersion v1, ResourceVersion v2 )
{
return _Compare( v1, v2 ) <= 0;
}
public static bool operator >=( ResourceVersion v1, ResourceVersion v2 )
{
return _Compare( v1, v2 ) >= 0;
}
#endregion
public static void SelfTest()
{
//
// Verify that 1 < 1.0 < 1.0.0 < 1.0.0.0
//
var v1 = new ResourceVersion( "1" );
var v10 = new ResourceVersion( "1.0" );
var v100 = new ResourceVersion( "1.0.0" );
var v1000 = new ResourceVersion( "1.0.0.0" );
if( !(v1 < v10) )
throw new Exception( "bug" );
if( !(v10 < v100) )
throw new Exception( "bug" );
if( !(v100 < v1000) )
throw new Exception( "bug" );
if( !(v1 < v100) )
throw new Exception( "bug" );
if( !(v1 < v1000) )
throw new Exception( "bug" );
if( !(v10 < v1000) )
throw new Exception( "bug" );
//
// Verify some equality.
//
#pragma warning disable 1718
if( v1 != v1 )
throw new Exception( "bug" );
if( v10 != v10 )
throw new Exception( "bug" );
if( v100 != v100 )
throw new Exception( "bug" );
if( v1000 != v1000 )
throw new Exception( "bug" );
#pragma warning restore 1718
if( v1 != new ResourceVersion( "1" ) )
throw new Exception( "bug" );
if( v10 != new ResourceVersion( "1.0" ) )
throw new Exception( "bug" );
if( v100 != new ResourceVersion( "1.0.0" ) )
throw new Exception( "bug" );
if( v1000 != new ResourceVersion( "1.0.0.0" ) )
throw new Exception( "bug" );
if( v1 == new ResourceVersion( "2" ) )
throw new Exception( "bug" );
//
// Verify that unspecified parts are ushort.MaxValue.
//
if( v1.Minor != ushort.MaxValue )
throw new Exception( "bug" );
if( v1.Build != ushort.MaxValue )
throw new Exception( "bug" );
if( v1.Revision != ushort.MaxValue )
throw new Exception( "bug" );
//
// Verify that you can't create them with unspecified parts in the middle.
//
try
{
new ResourceVersion( 1, ushort.MaxValue, 1 );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
//
// Verify some other illegal construction scenarios
//
try
{
new ResourceVersion( (string) null );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
try
{
new ResourceVersion( "" );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
try
{
new ResourceVersion( "." );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
try
{
new ResourceVersion( "1." );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
try
{
new ResourceVersion( ".1" );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
try
{
new ResourceVersion( "1.1." );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
try
{
new ResourceVersion( ".1.1." );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
try
{
new ResourceVersion( "1.1.1.65536" );
throw new Exception( "bug" );
}
catch( ArgumentException )
{
// expected
}
//
// Verify some other comparisons.
//
if( !(new ResourceVersion( 1, 0, 0, 0) < new ResourceVersion( 2, 0, 0, 0)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 0, 0, 0) < new ResourceVersion( 2, 0 )) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 10, 0, 0) < new ResourceVersion( 2, 0 )) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 0, 0, 0) < new ResourceVersion( 1, 0, 0, 1)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 0, 0, 0) < new ResourceVersion( 1, 0, 1, 0)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 0, 0, 0) < new ResourceVersion( 1, 1, 0, 0)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 0, 0, 0) < new ResourceVersion( 1, 1)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 0, 0, 0) < new ResourceVersion( 1, 1, 0, 1)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 1, 0, 0) < new ResourceVersion( 1, 1, 0, 1)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 1, 0, 1) < new ResourceVersion( 1, 1, 1, 0)) )
throw new Exception( "bug" );
if( !(new ResourceVersion( 1, 1, 1, 0) < new ResourceVersion( 1, 1, 1, 1)) )
throw new Exception( "bug" );
//
// Verify ToInts and ToLong.
//
int ms, ls;
long l;
ResourceVersion v0000 = new ResourceVersion( 0, 0, 0, 0 );
v0000.ToInts( out ms, out ls );
if( 0 != ms )
throw new Exception( "bug" );
if( 0 != ls )
throw new Exception( "bug" );
l = v0000.ToLong();
if( 0 != l )
throw new Exception( "bug" );
v1.ToInts( out ms, out ls );
if( 0x0001FFFF != ms )
throw new Exception( "bug" );
if( -1 != ls )
throw new Exception( "bug" );
l = v1.ToLong();
if( 0x0001FFFFFFFFFFFF != l )
throw new Exception( "bug" );
v10.ToInts( out ms, out ls );
if( 0x00010000 != ms )
throw new Exception( "bug" );
if( -1 != ls )
throw new Exception( "bug" );
l = v10.ToLong();
if( 0x00010000FFFFFFFF != l )
throw new Exception( "bug" );
v100.ToInts( out ms, out ls );
if( 0x00010000 != ms )
throw new Exception( "bug" );
if( 0x0000ffff != ls )
throw new Exception( "bug" );
l = v100.ToLong();
if( 0x000100000000FFFF != l )
throw new Exception( "bug" );
v1000.ToInts( out ms, out ls );
if( 0x00010000 != ms )
throw new Exception( "bug" );
if( 0x00000000 != ls )
throw new Exception( "bug" );
l = v1000.ToLong();
if( 0x0001000000000000 != l )
throw new Exception( "bug" );
var v1001 = new ResourceVersion( "1.0.0.1" );
v1001.ToInts( out ms, out ls );
if( 0x00010000 != ms )
throw new Exception( "bug" );
if( 0x00000001 != ls )
throw new Exception( "bug" );
l = v1001.ToLong();
if( 0x0001000000000001 != l )
throw new Exception( "bug" );
var v4444 = new ResourceVersion( "4.4.4.4" );
v4444.ToInts( out ms, out ls );
if( 0x00040004 != ms )
throw new Exception( "bug" );
if( 0x00040004 != ls )
throw new Exception( "bug" );
l = v4444.ToLong();
if( 0x0004000400040004 != l )
throw new Exception( "bug" );
var vTopBits = new ResourceVersion( "4096.4096.4096.4096" );
vTopBits.ToInts( out ms, out ls );
if( 0x10001000 != ms )
throw new Exception( "bug" );
if( 0x10001000 != ls )
throw new Exception( "bug" );
l = vTopBits.ToLong();
if( 0x1000100010001000 != l )
throw new Exception( "bug" );
} // end SelfTest()
} // end class ResourceVersion
// Here is how the arguments to WriteResource correspond to the output of "filever /v":
//
// fileType isDebug
// v v
// --a-- W32i APP ENU 1.0.19.482 shp 529,408 12-22-2016 snippet.exe
// Language 0x0409 (English (United States))
// CharSet 0x04b0 Unicode
// OleSelfRegister Disabled
// CompanyName my company <= companyName
// FileDescription Snippet Application <= description
// InternalName Snippet.exe <= internalName
// OriginalFilename Snippet.exe <= originalFilename
// ProductName Snippet.exe <= productName
// ProductVersion 1.0.19.482 <= productVersion + additionalProductVersionText
// FileVersion 1.0.19.482 (built by danthom on REDMOND\DANTHOM-LAP) <= fileVersion + additionalFileVersionText
// LegalCopyright Copyright (C) 2014 <= copyright
// PrivateBuild private build string <= privateBuild
// SpecialBuild special build string <= specialBuild
// Comments these are the comments <= comments
// VS_FIXEDFILEINFO:
// Signature: feef04bd
// Struc Ver: 00010000
// FileVer: 00010000:001301e2 (1.0:19.482) <= fileVersion
// ProdVer: 00010000:001301e2 (1.0:19.482) <= productVersion
// FlagMask: 0000003f
// Flags: 00000028 private special <= isDebug, isPreRelease, [non-null privateBuild], [non-null specialBuild]
// OS: 00000004 Win32
// FileType: 00000001 App <= fileType
// SubType: 00000000
// FileDate: 00000000:00000000
//
public static void WriteResource( Stream stream,
VFT fileType,
ResourceVersion fileVersion,
string additionalFileVersionText,
ResourceVersion productVersion,
string additionalProductVersionText,
bool isDebug,
bool isPreRelease,
string privateBuild,
string specialBuild,
string description,
string internalName,
string copyright,
string originalFilename,
string productName,
string companyName,
string comments )
{
_AssertDWordAligned( stream );
//ResourceVersion.SelfTest();
bool isPrivateBuild = !String.IsNullOrEmpty( privateBuild );
bool isSpecialBuild = !String.IsNullOrEmpty( specialBuild );
// I don't know why rc.exe spits out a .res that starts with an empty header...
// but it does, so we will too.
BinaryWriter bw = new BinaryWriter( stream );
foreach( int i in sm_empty_RESOURCE_HEADER )
{
bw.Write( i );
}
// The DataSize field in the RESOURCEHEADER structure indicates the number of
// bytes written /after/ the RESOURCEHEADER, so we'll need to remember where the
// beginning of the RESOURCEHEADER is so that we can come back later and write
// that value.
long resHeaderPos = stream.Position;
// <RESOURCEHEADER> see http://msdn.microsoft.com/en-us/library/windows/desktop/ms648027(v=vs.85).aspx
bw.Write( 0 ); // DataSize--we'll have to come back later to fill this in.
bw.Write( 0x20 ); // HeaderSize. This could be variable if we were writing strings for the name or type.
bw.Write( 0x0010ffff ); // Type. Bottom word indicates binary, 0x0010 indicates RT_VERSION.
bw.Write( 0x0001ffff ); // Name. Bottom word indicates binary, 0x0001 indicates ????.
bw.Write( 0 ); // DataVersion.
bw.Write( 0x04090030 ); // LanguageId and MemoryFlags.
bw.Write( 0 ); // Version (of the resource... not important).
bw.Write( 0 ); // Characteristics.
// </RESOURCEHEADER>
CountingWriter writer = null;
using( writer = new CountingWriter( stream ) )
{
// <VS_VERSIONINFO> see http://msdn.microsoft.com/en-us/library/windows/desktop/ms647001(v=vs.85).aspx
using( CountingWriter w2 = new CountingWriter( stream ) )
{
w2.Write( (ushort) 0 ); // wLength--w2 will overwrite this with the correct value when it is disposed.
w2.Write( (ushort) Marshal.SizeOf( typeof( VS_FIXEDFILEINFO ) ) ); // wValueLength.
Debug.Assert( 0x34 == Marshal.SizeOf( typeof( VS_FIXEDFILEINFO ) ) );
w2.Write( (ushort) 0 ); // wType (0 for binary)
w2.Write( Encoding.Unicode.GetBytes( "VS_VERSION_INFO" ) ); // szKey
w2.Write( 0 ); // null terminator for szKey, plus padding to get things DWORD-aligned.
new VS_FIXEDFILEINFO( fileVersion,
productVersion,
isDebug,
isPreRelease,
isPrivateBuild,
isSpecialBuild,
fileType ).Write( w2 );
}
_AssertDWordAligned( stream );
// "other stuff"
// <StringFileInfo> see http://msdn.microsoft.com/en-us/library/windows/desktop/ms646989(v=vs.85).aspx
using( CountingWriter w2 = new CountingWriter( stream ) )
{
writer.Write( (ushort) 0 ); // wLength--w2 will fill this in when it is disposed.
writer.Write( (ushort) 0 ); // wValueLength--always 0 for StringFileInfo.
writer.Write( (ushort) 1 ); // wType--1 for text data.
writer.Write( Encoding.Unicode.GetBytes( "StringFileInfo" ) );
writer.Write( (ushort) 0 ); // null terminator. No padding needed; we're already 32-bit aligned at this point.
_AssertDWordAligned( stream );
// <StringTable> see http://msdn.microsoft.com/en-us/library/windows/desktop/ms646992(v=vs.85).aspx
using( CountingWriter w3 = new CountingWriter( stream ) )
{
writer.Write( (ushort) 0 ); // wLength--w3 will fill it in when it gets disposed.
writer.Write( (ushort) 0 ); // wValueLength--always 0 for StringTable.
writer.Write( (ushort) 1 ); // wType--1 for text data.
writer.Write( Encoding.Unicode.GetBytes( "040904b0" ) );
writer.Write( (ushort) 0 ); // null terminator. No padding needed; we're already 32-bit aligned at this point.
_AssertDWordAligned( stream );
// <String> (multiple) see http://msdn.microsoft.com/en-us/library/windows/desktop/ms646987(v=vs.85).aspx
if( !String.IsNullOrEmpty( comments ) )
{
_WriteStringStructure( writer.Stream, "Comments", comments );
}
if( !String.IsNullOrEmpty( companyName ) )
{
_WriteStringStructure( writer.Stream, "CompanyName", companyName );
}
if( !String.IsNullOrEmpty( description ) )
{
_WriteStringStructure( writer.Stream, "FileDescription", description );
}
string versionString = null;
// Sending it through Version handles dealing with unspecified parts: 2, 0, -1, -1 --> "2.0".
string baseVersionString = fileVersion.ToString();
if( String.IsNullOrEmpty( additionalFileVersionText ) )
{
versionString = baseVersionString;
}
else
{
versionString = baseVersionString + " " + additionalFileVersionText;
}
_WriteStringStructure( writer.Stream, "FileVersion", versionString );
if( !String.IsNullOrEmpty( internalName ) )
{
_WriteStringStructure( writer.Stream, "InternalName", internalName );
}
if( !String.IsNullOrEmpty( copyright ) )
{
_WriteStringStructure( writer.Stream, "LegalCopyright", copyright );
}
if( !String.IsNullOrEmpty( originalFilename ) )
{
_WriteStringStructure( writer.Stream, "OriginalFilename", originalFilename );
}
if( isPrivateBuild )
{
_WriteStringStructure( writer.Stream, "PrivateBuild", privateBuild );
}
if( !String.IsNullOrEmpty( productName ) )
{
_WriteStringStructure( writer.Stream, "ProductName", productName );
}
baseVersionString = productVersion.ToString();
if( String.IsNullOrEmpty( additionalProductVersionText ) )
{
versionString = baseVersionString;
}
else
{
versionString = baseVersionString + " " + additionalProductVersionText;
}
_WriteStringStructure( writer.Stream, "ProductVersion", versionString );
if( isSpecialBuild )
{
_WriteStringStructure( writer.Stream, "SpecialBuild", specialBuild );
}
// </String> (multiple)
}
// </StringTable>
}
// </StringFileInfo>
_AssertDWordAligned( stream );
// <VarFileInfo> see http://msdn.microsoft.com/en-us/library/windows/desktop/ms646995(v=vs.85).aspx
using( CountingWriter w2 = new CountingWriter( stream ) )
{
writer.Write( (ushort) 0 ); // wLength--w2 will fill this in when it is disposed.
writer.Write( (ushort) 0 ); // wValueLength--always 0 for VarFileInfo.
writer.Write( (ushort) 1 ); // wType--1 for text data.
writer.Write( Encoding.Unicode.GetBytes( "VarFileInfo" ) );
writer.Write( 0 ); // null terminator plus padding
_AssertDWordAligned( stream );
// <Var> see http://msdn.microsoft.com/en-us/library/windows/desktop/ms646994(v=vs.85).aspx
using( CountingWriter w3 = new CountingWriter( stream ) )
{
writer.Write( (ushort) 0 ); // wLength--w3 will fill it in when it is disposed.
writer.Write( (ushort) 4 ); // wValueLength
writer.Write( (ushort) 0 ); // wType--0 for binary data.
writer.Write( Encoding.Unicode.GetBytes( "Translation" ) );
writer.Write( 0 ); // null terminator plus padding
writer.Write( 0x04b00409 ); // Value. High word is IBM code page, low word is MS language ID. (Unicode and English)
}
// </Var>
}
// </VarFileInfo>
}
_AssertDWordAligned( stream );
long curPos = stream.Position;
stream.Seek( resHeaderPos, SeekOrigin.Begin );
bw.Write( (int) writer.Count );
stream.Seek( curPos, SeekOrigin.Begin );
} // end WriteResource
// [Conditional( "DEBUG" )]
// private static void _AssertDWordAligned( Stream s )
// {
// Debug.Assert( 0 == (((int) s.Position) & 0x03), "Stream is not DWORD-aligned." );
// } // end _AssertDWordAligned()
private static void _AssertDWordAligned( Stream s )
{
if( 0 != (((int) s.Position) & 0x03) )
{
throw new Exception( "Stream is not DWORD-aligned." );
}
} // end _AssertDWordAligned()
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms648027(v=vs.85).aspx
// for the structure definition. I don't know why rc.exe spits out a .res that starts
// with an empty header... but it does.
static int[] sm_empty_RESOURCE_HEADER =
{
0x00000000, // data size
0x00000020, // header size
0x0000ffff, // type (nothing)
0x0000ffff, // name (nothing)
0x00000000, // DataVersion
0x00000000, // memory flags (not important) and LanguageId
0x00000000, // version (not important)
0x00000000, // characteristics (not important)
};
public enum VFT
{
APP = 0x01,
DLL = 0x02
// others not supported
}
[StructLayout( LayoutKind.Sequential )]
class VS_FIXEDFILEINFO
{
public int dwSignature = unchecked( (int) 0xfeef04bd );
public int dwStrucVersion = 0x00010000;
public int dwFileVersionMS;
public int dwFileVersionLS;
public int dwProductVersionMS;
public int dwProductVersionLS;
public int dwFileFlagsMask;
public int dwFileFlags;
public int dwFileOS = 0x00000004; // windows32
public int dwFileType;
public int dwFileSubtype;
public int dwFileDateMS;
public int dwFileDateLS;
public VS_FIXEDFILEINFO( ResourceVersion version,
bool isDebug,
bool isPreRelease,
bool isPrivateBuild,
bool isSpecialBuild,
VFT fileType )
: this( version,
version, // < We just set the product version == file version
isDebug,
isPreRelease,
isPrivateBuild,
isSpecialBuild,
fileType )
{
}
public VS_FIXEDFILEINFO( ResourceVersion version,
ResourceVersion prodVersion,
bool isDebug,
bool isPreRelease,
bool isPrivateBuild,
bool isSpecialBuild,
VFT fileType )
{
version.ToInts( out dwFileVersionMS, out dwFileVersionLS );
prodVersion.ToInts( out dwProductVersionMS, out dwProductVersionLS );
dwFileFlagsMask = 0x03f;
dwFileFlags = 0;
if( isDebug )
dwFileFlags |= 0x01;
if( isPreRelease )
dwFileFlags |= 0x02;
if( isPrivateBuild )
dwFileFlags |= 0x08;
if( isSpecialBuild )
dwFileFlags |= 0x20;
long timestamp = DateTime.Now.ToFileTime();
// rc.exe doesn't seem to fill this in...
//dwFileDateMS = (int) ((timestamp & unchecked( (long) 0xffffffff00000000 )) >> 32);
//dwFileDateLS = (int) (timestamp & 0x00000000ffffffff);
dwFileDateMS = 0;
dwFileDateLS = 0;
dwFileType = (int) fileType;
} // end constructor
public void Write( CountingWriter writer )
{
writer.Write( dwSignature );
writer.Write( dwStrucVersion );
writer.Write( dwFileVersionMS );
writer.Write( dwFileVersionLS );
writer.Write( dwProductVersionMS );
writer.Write( dwProductVersionLS );
writer.Write( dwFileFlagsMask );
writer.Write( dwFileFlags );
writer.Write( dwFileOS );
writer.Write( dwFileType );
writer.Write( dwFileSubtype );
writer.Write( dwFileDateMS );
writer.Write( dwFileDateLS );
} // end Write()
} // end class VS_FIXEDFILEINFO
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms646987(v=vs.85).aspx
private static void _WriteStringStructure( Stream stream, string key, string val )
{
_AssertDWordAligned( stream );
int szKeyWordsWritten;
using( CountingWriter writer = new CountingWriter( stream ) )
{
writer.Write( (ushort) 0 ); // wLength--writer will fill this in when it is disposed.
writer.Write( (ushort) (val.Length + 1) ); // wValueLength (in words, counting null terminator)
writer.Write( (ushort) 1 ); // wType--1 for text data.
writer.Write( Encoding.Unicode.GetBytes( key ) ); // szKey
writer.Write( (ushort) 0 ); // null terminator
// We started on an odd word boundary. Need to null-pad to an even word boundary
// so that we are DWORD-aligned.
szKeyWordsWritten = key.Length + 1;
if( 0x00 == (szKeyWordsWritten & 0x01) )
{
writer.Write( (ushort) 0 );
}
_AssertDWordAligned( stream );
writer.Write( Encoding.Unicode.GetBytes( val ) ); // Value
writer.Write( (ushort) 0 ); // null terminator
} // end using( CountingWriter )
// rc.exe does not count this final padding in the wLength field... I
// theorize it should not matter, but if I ever have to debug something and am
// comparing against a "real" .res file, I don't want to have to filter it out of
// the diff, so I won't count it either.
// We started on an even word boundary. Need to null-pad to an even word boundary
// so that we are DWORD-aligned.
szKeyWordsWritten = val.Length + 1;
if( 0x01 == (szKeyWordsWritten & 0x01) )
{
stream.Write( new byte[] { 0, 0 }, 0, 2 );
}
_AssertDWordAligned( stream );
} // end _WriteStringStructure
// When you dispose of the CountingWriter, it will write the count of bytes written since
// it was created to the stream back where the stream was when the CountingWriter was
// first created. It writes the count as a two-byte value (a WORD).
public class CountingWriter : IDisposable
{
private Stream m_stream;
private long m_startPosition;
private long m_endPosition = -1;
private byte[] m_buf = new byte[ 0x10 ];
public CountingWriter( Stream destStream )
{
if( null == destStream )
throw new ArgumentNullException( "destStream" );
if( !destStream.CanSeek )
throw new ArgumentException( "The destination stream must be able to seek.", "destStream" );
m_stream = destStream;
m_startPosition = m_stream.Position;
} // end constructor
private void _CheckDisposed()
{
if( -1 != m_endPosition )
throw new ObjectDisposedException( GetType().Name );
}
public void Write( ushort word )
{
_CheckDisposed();
m_buf[ 0 ] = (byte) word;
m_buf[ 1 ] = (byte) (word >> 8);
m_stream.Write( m_buf, 0, 2 );
}
public void Write( int dword )
{
_CheckDisposed();
m_buf[ 0 ] = (byte) dword;
m_buf[ 1 ] = (byte) (dword >> 8);
m_buf[ 2 ] = (byte) (dword >> 16);
m_buf[ 3 ] = (byte) (dword >> 24);
m_stream.Write( m_buf, 0, 4 );
}
public void Write( byte[] buf )
{
_CheckDisposed();
m_stream.Write( buf, 0, buf.Length );
}
public void Dispose()
{
if( -1 != m_endPosition )
{
// "Helpful" assert:
Debug.Fail( "Why dispose me more than once?" );
return;
}
long endPosition = m_stream.Position;
long bytesWritten = endPosition - m_startPosition;
if( 0 == bytesWritten )
return;
if( bytesWritten < 2 )
throw new InvalidOperationException( "Only one byte written to a CountingWriter." );
m_stream.Seek( m_startPosition, SeekOrigin.Begin );
ushort count = (ushort) bytesWritten;
Write( count );
m_stream.Seek( endPosition, SeekOrigin.Begin );
m_endPosition = endPosition;
}
// Safe to access after disposal.
public long Count
{
get
{
if( -1 == m_endPosition )
return m_stream.Position - m_startPosition;
else
return m_endPosition - m_startPosition;
}
}
public Stream Stream { get { return m_stream; } }
} // end class CountingWriter
} // end class ResourceEmitter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment