Click here to Skip to main content
15,881,599 members
Articles / Desktop Programming / Win32

Simple Version Resource Tool for Windows

Rate me:
Please Sign up or sign in to vote.
4.83/5 (65 votes)
3 Sep 2012CPOL9 min read 369.8K   27.4K   169  
A utility for creating version info on executable files without Resource Compiler
// relstamp.cpp
//
// pa02 cleanup. renamed to verpatch
// pa03 fixed up for VC2008 Express

#include "stdafx.h"
#include "relstamp.h"
#include "vs_version.h"
#pragma comment(lib, "version")

// Options
struct cmd_params {
	bool PatchMode;					// true: patch version descr. of the input file. false: create and replace.
	bool DbgImportOnly;				// debug: test import only, don't modify file
	bool DbgLoopbackTest;			// reparse generated blob
	// For import & patch mode
	bool DumpImportVerAsRC;			// dump input version info in RC format
	bool PreserveOriginalFilename;	// false: set to actual filename
	bool PreserveInternalFilename;	// false: set to actual filename
	bool PreserveStringVersionTail; // true: save appendix of fileversion, productversion strings

	bool fClearPdbPath;

	bool cmd_arg_parse( int argc, _TCHAR *argv[], PCTSTR *fname, struct file_ver_data_s *fvd ); 
//	cmd_params() {} // ctor
} g_params;

// Struct to hold a resource:
struct ResDesc 
{
	PUCHAR m_data;
	DWORD m_cbdata;
	ULONG_PTR m_type; // string ptr or numeric id
	ULONG_PTR m_name; // string ptr or numeric id
	WORD m_language;

	ResDesc() : m_data(0), m_cbdata(0) {}

	ResDesc( void *data, unsigned cbData, ULONG_PTR typeId, 
		ULONG_PTR name_id, WORD langId = LANG_NEUTRAL ) 
		: m_data((PUCHAR)data), m_cbdata(cbData), m_type(typeId), 
			m_name(name_id), m_language(langId)	{}

	friend bool addResourceFromFile( PCTSTR resfile, UINT32 id_flags );
};

static 
ResDesc *aRes[_A_MAX_N_RES + 1];

static
void addUpdRes( ResDesc *rd ) 
{
	for (int i = 0; i < ARRAYSIZE(aRes) - 1; i++) {
		if ( NULL == aRes[i] ) {
			aRes[i] = rd;
			return;
		}
	}
	dprint("ERROR: Too many resources added\n");
	throw ":TooManyRes";
	return;
}


BOOL fillCompanyInfo( __out file_ver_data_s *fvd )
{
	// default
	fvd->addTwostr( L"CompanyName", DEF_COMPANY_NAME );
	fvd->addTwostr( L"LegalCopyright", DEF_COPYRGT ); //"Copyright (c) 2009"
	return TRUE;
}


BOOL fillProductInfo( __out file_ver_data_s *fvd )
{
	// default
	fvd->addTwostr( L"ProductName", DEF_PRODUCT_NAME );
	fvd->pv_1 = 1;
	fvd->pv_2 = 0;
	fvd->pv_3 = 0;
	fvd->pv_4 = 0;
	return TRUE;
}


// callback class for ParseBinaryVersionResource()
class VerCallback1 : public IParseVerStrCallback
{
	file_ver_data_s *m_vd;
	int m_numcalled;
	public:
	VerCallback1(file_ver_data_s *fvd) : m_numcalled(0), m_vd(fvd) {};
	void callback( PCWSTR name, PCWSTR value ) override;
};

// Callback to import version strings
void VerCallback1::callback( PCWSTR name, PCWSTR value )
{
	d3print( "callback #%d [%ws]= [%ws]\n", m_numcalled++, name, value );
	++m_numcalled;

	// filter off FileVersion, Product Version. always take from FIXED_INFO.
	if ( 0 == _wcsicmp(name, L"FileVersion") ) {
		// Optional tail: "1.2.3.4 tail"
		PCTSTR tail = wcschr(value, _T(' '));
		if ( tail ) {
			while( *tail == _T(' ') ) tail++;
			if (*tail) m_vd->sFileVerTail = tail;
		}
		return;
	}

	if ( 0 == _wcsicmp(name, L"ProductVersion") ) {
		// Optional tail: "1.2.3.4 tail"
		PCTSTR tail = wcschr(value, _T(' '));
		if ( tail ) {
			while( *tail == _T(' ') ) tail++;
			if (*tail) m_vd->sProductVerTail = tail;
		}
		return;
	}

	m_vd->addTwostr( name, value );
}

BOOL fillFileInfo( __out file_ver_data_s *fvd, PCTSTR fpath )
{
	UINT xname, xdot_ext;
	if ( !fileGetNameExtFromPath( fpath, &xname, &xdot_ext) ) {
		dtprint(_T("Error parsing the file name\n") );
		return FALSE;
	}

	PCTSTR pfilename = (PCTSTR)( (PUCHAR)fpath + xname );
	PCTSTR pdot_ext  = (PCTSTR)( (PUCHAR)fpath + xdot_ext );

	if ( g_params.PatchMode ) 
	{
		// Import version blob from the file
		PUCHAR verinfo = (PUCHAR)calloc( _MAX_VERS_SIZE_CB, 1 );
		ASSERT(verinfo);
		if ( !fileReadVersionInfo( fpath, verinfo, _MAX_VERS_SIZE_CB ) ) {
			dprint("error reading version info from the file, err=%d\n", GetLastError());
			if (ERROR_RESOURCE_TYPE_NOT_FOUND == GetLastError() )
				dprint("The file does not have a version resource\n");
			if (ERROR_RESOURCE_DATA_NOT_FOUND == GetLastError() )
				dprint("The file could not be found or is not executable/dll\n");
			free(verinfo);
			return FALSE;
		}

		VS_FIXEDFILEINFO *fxi = NULL;
		VerCallback1 mycb(fvd);
		if ( !ParseBinaryVersionResource( verinfo, _MAX_VERS_SIZE_CB, 
			&fxi, &mycb, g_params.DumpImportVerAsRC ) ) {
			dprint("error parsing version info from the file\n");
			free(verinfo);
			return FALSE;
		}

		fvd->v_1 = HIWORD(fxi->dwFileVersionMS);
		fvd->v_2 = LOWORD(fxi->dwFileVersionMS);
		fvd->v_3 = HIWORD(fxi->dwFileVersionLS); 
		fvd->v_4 = LOWORD(fxi->dwFileVersionLS);

		fvd->pv_1 = HIWORD(fxi->dwProductVersionMS);
		fvd->pv_2 = LOWORD(fxi->dwProductVersionMS);
		fvd->pv_3 = HIWORD(fxi->dwProductVersionLS);
		fvd->pv_4 = LOWORD(fxi->dwProductVersionLS);

		fvd->dwFileFlags = (fxi->dwFileFlags & fxi->dwFileFlagsMask);
		fvd->dwFileType = fxi->dwFileType;
		fvd->dwFileSubType = fxi->dwFileSubtype;

		if (fxi->dwFileOS != VOS_NT_WINDOWS32 && fxi->dwFileOS != VOS__WINDOWS32 ) {
			dprint( "warning: imported dwFileOS not WINDOWS32 (=%#x)\n", fxi->dwFileOS);
		}

		free(verinfo);
	} // patch mode

	if ( !g_params.PreserveOriginalFilename ) {
		fvd->addTwostr(L"OriginalFilename", pfilename);
	}

	if ( !g_params.PreserveInternalFilename ) {
		fvd->addTwostr(L"InternalName", pfilename);
	}

	// Fill in per file info:
	if ( VFT_UNKNOWN == fvd->dwFileType ) {
		d3tprint(_T("file ext=%s\n"), pdot_ext);
		if ( _tcsicmp( pdot_ext, _T(".exe") ))
			fvd->dwFileType = VFT_APP;
		if ( _tcsicmp( pdot_ext, _T(".sys") ))
			fvd->dwFileType = VFT_DRV;
		if ( _tcsicmp( pdot_ext, _T(".dll") ))
			fvd->dwFileType = VFT_DLL;
	}

	if ( !fvd->getValStr(L"FileDescription") ) {
		// make up FileDescription:
		fvd->addTwostr( L"FileDescription", pfilename );
	}

	return TRUE;
}

BOOL parseFileVer( __out file_ver_data_s *fvd, PCTSTR arg )
{
	unsigned n1=0,n2=0,n3=0,n4=0;
	int nf = _stscanf_s( arg, _T("%d.%d.%d.%d"), &n1, &n2, &n3, &n4);

	// if less than 4 numbers given, they are minor
	switch( nf ) {
		case 1:
			fvd->v_4 = n1;
			break;
		case 2:
			fvd->v_3 = n1; fvd->v_4 = n2;
			break;
		case 3:
			fvd->v_2 = n1; fvd->v_3 = n2; fvd->v_4 = n3; 
			break;
		case 4:
			fvd->v_1 = n1; fvd->v_2 = n2; fvd->v_3 = n3; fvd->v_4 = n4;
			break;
		default:
			dprint("error parsing version arg\n");
			return FALSE;
	}

	// Optional tail: "1.2.3.4 tail"
	// If no tail found, don't replace existing.
	PCTSTR tail = _tcschr(arg, _T(' '));
	if ( tail ) {
		while( *tail == _T(' ') ) tail++;
		fvd->sFileVerTail = (*tail) ? strUnEscape(tail) : NULL;
	}

	return TRUE;
}

BOOL parseProductVer( __out file_ver_data_s *fvd, PCTSTR arg )
{
	unsigned n1=0,n2=0,n3=0,n4=0;
	int nf = _stscanf_s( arg, _T("%d.%d.%d.%d"), &n1, &n2, &n3, &n4);

	// if less than 4 numbers given, they are minor
	switch( nf ) {
		case 1:
			fvd->pv_4 = n1;
			break;
		case 2:
			fvd->pv_3 = n1; fvd->pv_4 = n2;
			break;
		case 3:
			fvd->pv_2 = n1; fvd->pv_3 = n2; fvd->pv_4 = n3; 
			break;
		case 4:
			fvd->pv_1 = n1; fvd->pv_2 = n2; fvd->pv_3 = n3; fvd->pv_4 = n4;
			break;
		default:
			dprint("error parsing version arg\n");
			return FALSE;
	}

	// Optional tail: "1.2.3.4 tail"
	// If no tail found, don't replace existing.
	PCTSTR tail = _tcschr(arg, _T(' '));
	if ( tail ) {
		while( *tail == _T(' ') ) tail++;
		fvd->sProductVerTail = (*tail) ? strUnEscape(tail) : NULL;
	}

	return TRUE;
}


BOOL updFileResources( LPCTSTR fname, __in ResDesc *ard[] )
{
	if ( !ard[0] ) {
		d2print("No resources to update\n");
		return TRUE;
	}

	// open file, start update
	BOOL ok = FALSE;
	int n_updated = 0;
	HANDLE rhandle = ::BeginUpdateResource( fname, FALSE );
	if ( !rhandle ) {
		dprint( "Error opening file for update resources, err=%d\n", GetLastError() );
		return FALSE;
	}

	for (int i = 0; ; i++) {
		ResDesc *rd = ard[i];
		if ( !rd )
			break;

		ok = ::UpdateResource(
			rhandle,
			(LPCTSTR)rd->m_type,
			(LPCTSTR)rd->m_name,
			rd->m_language,
			(LPVOID)rd->m_data,
			rd->m_cbdata
			);

		if (!ok) {
			dprint("UpdateResource #%d err=%d\n", i,  GetLastError());
			break;
		}
		n_updated++;
	}

	d2print("Resources updated: %d\n", n_updated);

	BOOL r2 = ::EndUpdateResource( rhandle, !ok );
	if (!r2) {
		dprint("EndUpdateResource err=%d\n", GetLastError());
		ok = FALSE;
	}

	return ok;
}


////////////////////////////////////////////////////////////////////////
// main
////////////////////////////////////////////////////////////////////////
int _tmain(int argc, _TCHAR* argv[])
{
	static file_ver_data_s file_ver_data;
	PCTSTR fname;
	BOOL r;

	SetErrorMode( SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX );
	r = SetThreadLocale( LOCALE_INVARIANT );

	if ( !g_params.cmd_arg_parse( argc, argv, &fname, NULL ) ) {
		return 1;
	}

	d2tprint(_T("relstamp file=[%s]\n"), fname );

	r &= fillFileInfo( &file_ver_data, fname );

	if ( !g_params.PatchMode ) { // if not patch mode, read from INI
		r &= fillCompanyInfo( &file_ver_data );
		r &= fillProductInfo( &file_ver_data );
	}

	// Parse again, put the args into the data
	if ( !g_params.cmd_arg_parse( argc, argv, &fname, &file_ver_data ) ) {
		dprint("Error parsing command args 2 pass!\n");
		r = FALSE;
	}

	if ( !r ) {
		dprint("Some of actions failed, exiting\n");
		return 1;
	}

	PUCHAR verdata = NULL;
	r = makeVersionResource( &file_ver_data, &verdata );
	if ( !r ) return 1;

	if ( g_params.DbgLoopbackTest )
	{	// loopback test for vers res parser
		VS_FIXEDFILEINFO *fxi = NULL;
		VerCallback1 mycb(&file_ver_data);
		dprint("dbg: begin reparse\n");
		r = ParseBinaryVersionResource( verdata, _MAX_VERS_SIZE_CB, &fxi, &mycb, g_params.DumpImportVerAsRC );
		dprint("dbg: end reparse\n");
	}

	if( g_params.DbgImportOnly ) {
		dprint("read only, exiting\n");
		return 0;
	}

	// Add the version to resources update:
	addUpdRes( 
		new ResDesc( 
			(PVOID)verdata, 
			*((PWORD)verdata), 
			(ULONG_PTR)RT_VERSION, 
			(ULONG_PTR)MAKEINTRESOURCE(VS_VERSION_INFO), 
			MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)
			)
	);

	r = updFileResources( fname, &aRes[0] );
	if ( !r ) {
		dprint("Update file resources failed, the file may be damaged\n");
		return 2;
	}

	// Re-checksum
	r = updFileChecksum( fname, g_params.fClearPdbPath );
	if ( !r ) {
		dprint("Update file checksum failed, the file may be damaged\n");
		return 3;
	}

	d2print("ok\n");
	return 0;
}


// Check if the string key name is alias, return the correct invariant key name:
// See ver-res.txt
LPCTSTR aliasToStringKey( LPCTSTR key )
{
	if ( 0 == _tcsicmp(key, _T("comment"))) return _T("Comments");
	if ( 0 == _tcsicmp(key, _T("company"))) return _T("CompanyName");
	if ( 0 == _tcsicmp(key, _T("description"))) return _T("FileDescription");
	if ( 0 == _tcsicmp(key, _T("desc"))) return _T("FileDescription");
	if ( 0 == _tcsicmp(key, _T("title"))) return _T("InternalName");
	if ( 0 == _tcsicmp(key, _T("copyright"))) return _T("LegalCopyright");
	if ( 0 == _tcsicmp(key, _T("(c)"))) return _T("LegalCopyright");
	if ( 0 == _tcsicmp(key, _T("trademarks"))) return _T("LegalTrademarks");
	if ( 0 == _tcsicmp(key, _T("(tm)"))) return _T("LegalTrademarks");
	if ( 0 == _tcsicmp(key, _T("tm"))) return _T("LegalTrademarks");
	if ( 0 == _tcsicmp(key, _T("product"))) return _T("ProductName");
	if ( 0 == _tcsicmp(key, _T("productversion"))) return _T("ProductVersion");
	if ( 0 == _tcsicmp(key, _T("productver")))	 return _T("ProductVersion");
	if ( 0 == _tcsicmp(key, _T("prodver"))) return _T("ProductVersion");
	if ( 0 == _tcsicmp(key, _T("pb"))) return _T("PrivateBuild");
	if ( 0 == _tcsicmp(key, _T("sb"))) return _T("SpecialBuild");
	if ( 0 == _tcsicmp(key, _T("build"))) return _T("SpecialBuild");

	if ( 0 == _tcsicmp(key, _T("fileversion"))) {
		dprint("do NOT use FileVersion with /s option, see usage!\n");
	}

	if ( 0 == _tcsicmp(key, _T("language"))) {
		dprint("do NOT use Language with /s option, this won't work.\n");
	}

	//d3tprint(_T("alias not found %s\n"), key);
	return key;
}


///////////////////////////////////////////////////////////////////////
// Command args
//
//TODO: handle escapes in strings passed on command line
//      mode without version res: only bin patches 
// 
///////////////////////////////////////////////////////////////////////
bool cmd_params::cmd_arg_parse( int argc, _TCHAR *argv[], PCTSTR *fname, 
	__out_opt struct file_ver_data_s *fvd ) 
{
	bool firstPass = false;
	static file_ver_data_s my_fvd;
	if ( !fvd ) {
		firstPass = true;
		fvd = &my_fvd;
	}

	int pos_args = 0;
	int patch_actions = 0;

	PatchMode = true; //default mode -pa04

	for( int i = 1; i < argc; i++ ) {
		PCTSTR ap = argv[i];
		if (!ap) 
			break; //done

		if ( *ap == _T('/') || *ap == _T('-') ) {
			ap++;

			if ( argmatch(_T("?"), ap ) )
				{ showUsage(); return false; }
			else if ( argmatch(_T("fn"), ap) )  // keep original filename
				PreserveInternalFilename = PreserveOriginalFilename = true;
			else if ( argmatch(_T("vo"), ap) ) // dump input res. desc. as RC source
				DumpImportVerAsRC = true;
			else if ( argmatch(_T("xi"), ap) ) {  // read only, don't patch
				DbgImportOnly = true;	// dbg
			}
			else if ( argmatch(_T("xlb"), ap) )  // reparse created res.desc. (self test)
				DbgLoopbackTest = true;  //dbg 
			else if ( argmatch(_T("sc"), ap) ) { // /sc "comment"
				ap = argv[++i];	ASSERT(ap);
				patch_actions++;
				if( !fvd->addTwostr( _T("Comments"), strUnEscape(ap) ) ) {
					dtprint(_T("Error adding string:[%s]\n"), ap);
					return false;
				}
			}
			else if ( argmatch(_T("s"), ap) ) { 
				//Add a string to version res: /s name "comment"
				PCTSTR ns = argv[++i];	ASSERT(ns);
				ap = argv[++i];	ASSERT(ap);
				patch_actions++;

				if( !fvd->addTwostr( aliasToStringKey(ns), strUnEscape(ap) ) ) {
					dtprint(_T("Error adding string:[%s]\n"), ap);
					return false;
				}
			}
			else if( argmatch(_T("pv"), ap) ) {
				// product version string has same form as the file version arg (positional)
				ap = argv[++i];	ASSERT(ap);
				if ( !parseProductVer( fvd, ap ) ) {
					dprint("bad product version arg, see usage (/?)\n");
					return false;
				}
				patch_actions++;
			}
			else if (argmatch(_T("vft2"), ap) ) { // version subtype
				ap = argv[++i];	ASSERT(ap);
				long n = _tcstol( ap, NULL, 16 );
				if ( !(n >= 0 && n <= 0xFFFF) ) {
					dtprint(_T("Bad subtype \"%s\". For usage: /?\n"), ap);
					return false;
				}
				fvd->dwFileSubType = (USHORT)n;
				patch_actions++;
			}
			else if ( argmatch(_T("rpdb"), ap) ) {
				g_params.fClearPdbPath = true;
				patch_actions++;
			}
			else if ( argmatch(_T("va"), ap) ) { // auto generate version desc.
				PatchMode = false;
				patch_actions++;
			}
			else if ( argmatch(_T("rf"), ap) ) {
				// Add a resource from file: /rf #<number> filename
				// For now, use numeric ids only. High 16 bits can be used for type, etc.
				ULONG res_id = 0;
				ap = argv[++i];	ASSERT(ap);
				if ( *ap++ != _T('#') || 0 == (res_id = _tcstol( ap, NULL, 16 )) ) {
					dtprint(_T("Resource id must be #hex_number\n"), ap);
					return false;
				}

				ap = argv[++i];	ASSERT( ap && *ap != _T('/') && *ap != _T('-') );
				// only during 2nd pass:
				if ( !firstPass && !addResourceFromFile( ap, res_id ) ) {
					dtprint(_T("Error adding resource file [%s] id=%#X\n"), ap, res_id);
					return false;
				}
			}
			else {
				dtprint(_T("Unknown option \"%s\". For usage: /?\n"), ap);
				return false;
			}
		} else {
			// position args:
			switch(pos_args++) {
				case 0:
					*fname = ap; //ex d:/stuff/foo.sys
					break; 
				case 1:
					if ( parseFileVer( fvd, ap ) ) {
						patch_actions++;
						break;
					}
					dprint("bad version arg, see usage (/?)\n");
					return false;
				default:
					dprint("Too many args, see usage (/?)\n");
					return false;
			}
		}
	}

	if ( 0 == pos_args ) {
		showUsage();
		return false;
	}

	if ( 0 == patch_actions )
		DbgImportOnly = true; //dbg

	// flags: SpecialBuild fVerBeta fVerDebug?

	return true;
}


////////////////////////////////////////////////////////////////////////
// misc. utils
////////////////////////////////////////////////////////////////////////


LPWSTR stralloc( __in PCSTR s )
{
	ASSERT(s);
	size_t n = strlen(s);
	ASSERT( n < (256));
	LPWSTR p = (LPWSTR)malloc( (n + 1) * sizeof(WCHAR) );
	ASSERT( p );
	for ( size_t i = 0; i <= n; i++ )
		p[i] = s[i];
	return p;
}

LPWSTR stralloc( __in PCWSTR s )
{
	ASSERT(s);
	PWSTR p = _wcsdup(s);
	ASSERT(p);
	return p;
}

// return byte offset to name and .ext parts of filename
BOOL fileGetNameExtFromPath( __in PCTSTR path, __out PUINT pname, __out PUINT pext )
{
	ASSERT(path);
	PTSTR name = (PTSTR)calloc( 2*MAX_PATH, sizeof(TCHAR));
	ASSERT(name);
	PTSTR ext = name + MAX_PATH;

	errno_t e;
	e = _tsplitpath_s(path, NULL, 0 , NULL, 0, name, MAX_PATH, ext, 10);
	if ( e == ERROR_SUCCESS ) {
		size_t lname = _tcslen(name);
		size_t lext = _tcslen(ext);
		*pname = (UINT)(_tcslen(path) - lname - lext) * sizeof(TCHAR);
		*pext = *pname + (lname * sizeof(TCHAR));
	} else {
		dtprint(_T("Error parsing filename: err=%d path=[%s]\n"), e, path);
	}

	if ( name ) free( name );

	return e == ERROR_SUCCESS;
}

// Read VS_VERSION resource blob from a file by name
BOOL fileReadVersionInfo( __in PCTSTR fname, __out PUCHAR buf, __in unsigned size)
{
	BOOL r;
	r = GetFileVersionInfo(  fname, NULL /*reserved*/, (DWORD)size, (LPVOID)buf );
	return r;
}

// Format a string escaped for RC: quotes, symbols (R) (C) and so on
PCWSTR strEscape( __in PCWSTR ws )
{
	if ( !ws || !*ws ) {
		return (PCWSTR)L"\\0";
	}
	return ws; //$$$ TODO. for now, unescaped text will be printed, edit manually
}

// Unescape a string escaped for RC: quotes, symbols (R) (C) and so on
PCWSTR strUnEscape( __in PCWSTR ws )
{
	return ws; //$$$ TODO
}

bool argmatch(PCTSTR sw, PCTSTR cmp )
{
	if ( 0 == _tcsicmp(sw, cmp) ) return true;
	return false;
}


//////////////////////////////////////////////////////////////////////////
// Add raw binary resource from file.
// Low 16 bit of id_flags = resource ID. Bitmask FF0000 = type (0=RCDATA). High byte reserved.
//////////////////////////////////////////////////////////////////////////
bool addResourceFromFile( PCTSTR resfile, UINT32 id_flags )
{
	UINT64 xFileSize;
	HANDLE fh = CreateFile(resfile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, NULL);

	if (INVALID_HANDLE_VALUE == fh ) {
		dtprint(_T("Error opening res. file %s err=%d\n"), resfile, GetLastError());
		return false;
	}

	if ( !GetFileSizeEx( fh, (PLARGE_INTEGER)&xFileSize ) ) {
		dtprint(_T("Error get file size %s\n"), resfile);
		CloseHandle(fh);
		return false;
	}

	if ( xFileSize > _A_MAX_RES_CB ) {
		dtprint(_T("Error: file size too large %s %I64d K\n"), resfile, (xFileSize/1024));
		CloseHandle(fh);
		return false;
	}

	DWORD dwFileSize = (DWORD)xFileSize;
	PUCHAR dp = (PUCHAR)calloc( dwFileSize + 4, 1 ); // round up to 4 bytes
	ASSERT(dp);
	DWORD cbread;
	if ( !ReadFile( fh, (LPVOID)dp, dwFileSize, &cbread, NULL ) ) {
		dtprint(_T("Error reading file %s\n"), resfile);
		CloseHandle(fh);
		free(dp);
		return false;
	}

	CloseHandle(fh);

	// round up to 4 bytes
	dwFileSize = (dwFileSize + 3) & ~3; 

	ULONG restype = (id_flags >> 16) & 0xFF;
	if ( 0 == restype ) restype = (ULONG)RT_RCDATA;

	addUpdRes( new ResDesc( dp, dwFileSize, restype, id_flags & 0xFFFF ) );

	return true;	
}


#if 1
// Get a resource (pointer and size)
// hm: module handle. 0 is the exe file
bool getResource( HMODULE hm, DWORD rtype, DWORD rid, __out LPCVOID *p, __out PDWORD size )
{
	HRSRC hrs = FindResourceEx( hm, (LPCTSTR)rtype, (LPCTSTR)rid, 0 /*MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)*/ );
	if ( !hrs ) {
		d2print("cannot find res. %#x, err=%d\n", rid, GetLastError());
		return false;
	}

	DWORD rsize = SizeofResource(hm, hrs);
	if ( rsize < sizeof (DWORD) ) {
		d2print("res. size = 0??\n");
		return false;
	}

	HGLOBAL hg = LoadResource( hm, hrs );
	if ( !hg ) {
		d2print("err LoadResource %d\n", GetLastError() );
		return false;
	}
	
	*p = LockResource( hg );
	*size = rsize;
	return !!(*p) ;
}
#endif

////////////////////////////////////////////////////////////////////////////////
// USAGE
// How to add the help text:
// - Put the text in a file (ex. usage.txt)
// - make a copy of this exe file
// - use /rf #64 <file.txt> to attach this text file to the copy of the program
// See ver-self.cmd for a working example (this is also a kind of unit test :)
////////////////////////////////////////////////////////////////////////////////
void showUsage()
{
#if 1 
	const ULONG usage_txt_id = 0x64; //100
	const ULONG usage_txt_type = (ULONG)(ULONG_PTR)RT_RCDATA;
	const void *p;
	DWORD len;
	if ( !getResource( 0, usage_txt_type, usage_txt_id, &p, &len ) ) {
		dprint("No usage text! See readme.txt for instructions how to add it.\n");
		return;
	}
	fwrite(p, len, 1, stderr);
	return;

#else 
	dprint("verpatch r2 (2009/05/31)\n");
	dprint("Usage: verpatch filename [version] [/options]\n");
	dprint("\nOptions:\n");
	dprint(" /sc \"comment\"\t- add Comments string\n"); // todo: escapes
	dprint(" /s name \"value\"\t- add/replace any version resource string\n"); // todo: escapes
	dprint(" /va\t- create a default version resource\n");
	dprint(" /vft2 num\t- specify driver type (VFT2_xxx, see winver.h)\n");
	dprint(" /fn\t- preserve Original filename, Internal name in the file version info\n");
	dprint(" /vo\t- output the file version info in RC format\n");
	dprint(" /xi\t- test mode, do not patch the file\n");
	dprint(" /rpdb\t- remove path to .pdb in debug information\n");
	dprint(" /rf #hex-id file - add a resource from file (see readme)\n");
	dprint("\n\nExamples:\n");
	dprint("  verpatch d:\\foo.dll 1.2.33.44 /sc \"holy cow, it works!\"\n");
	dprint("  verpatch d:\\foo.sys \"33.44 release\" /fn\n");
	dprint("  verpatch d:\\foo.exe 1.2.3.4 /rf #9 driver.sys\n");
#endif
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions