; ================================================================================
; 								ResCompile
; ================================================================================
;
; 	By fat115
; 	License : GNU/LGPL v2.1
; 	Cette bibliothèque contient des fonctions permettant de créer un fichier 
;	ressource VersionInfo pouvant ensuite être utilisé pour patcher un fichier binaire.
; 	This library contains some functions allowing to create a VersionInfo resource
;	file wich can be further used to patch a binary file.

; 	Compiler: AutoHotkey (http://www.autohotkey.com/).
; 	$id=ResCompile.ahk $date=2011-08-28
;
; --------------------------------------------------------------------------------
;						Copyright (C) 2010-2011 Framakey

;						Website: http://www.framakey.org

;	This library is free software; you can redistribute it and/or
;	modify it under the terms of the GNU Lesser General Public
;	License as published by the Free Software Foundation; either
;	version 2.1 of the License, or (at your option) any later version.

;	This library is distributed in the hope that it will be useful,
;	but WITHOUT ANY WARRANTY; without even the implied warranty of
;	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;	Lesser General Public License for more details.

;	You should have received a copy of the GNU Lesser General Public
;	License along with this library; if not, write to the Free Software
;	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

; --------------------------------------------------------------------------------

/*
*********************** Changelog ***********************
2011-08-28 :
	Updated to AutoHotkey_L 1.1 +
	added CreateRESString as function returning an hexa string, receives an object as argument
	added ResWrite function to write CreateRESString value to a binary file
	removed iif function (replaced by ternary operator)
	updated TextToHex function

2010-02-21 :
	Initial release
*/

/*
******************************************************************************
|
***************************** Creation Functions *****************************
|
|	- CreateRESString:		returns a "binary" string which need to be written
|							as a binary file to create the resource file.
|							This can be done with BinWrite function from :
|								http://www.autohotkey.com/forum/topic4546.html
|
|	- CreateVar:			create the Var Structure (no need to call this)
|	- CreateVarFileInfo:	create the VarFileInfo Structure (using CreateVar)
|	- CreateString:			create a String Structure, child of StringTable
|	- CreateStringTable:	create StringTable Structure using all previous
|							String Structure children
|	- CreateStringFileInfo:	create StringFileInfo Structure using previous
|							StringTable
|	- CreateVSFixed:		create VS_FIXEDFILEINFO (see TODO)
|	- CreateVSVersion:		create complete VS_VERSIONINFO Structure
|
******************************************************************************
*/

CreateRESString(_verinfoobj)
{
; _verinfoobj properties
; .CommaFileVersion : file version formatted as X,X,X,X
; .CommaProductVersion : product version formatted as X,X,X,X
; .LangCode : ex 0x040C for French or 0x0000 for Neutral (see below), set to 0x0000 if empty
; .CharSet : ex 0x04B0 for Unicode (most common), set to 0x04B0 if empty

; the two next properties are available only if you want to make stupid things :
; i.e. setting languages different in VarFileInfo and StringFileInfo IS stupid !
; .VarFileInfoLangCode : 
; .VarFileInfoCharSet : 

; below are the StringFileInfo properties supported
; .Comments : 
; .LegalCopyright : 
; .CompanyName : 
; .FileDescription : 
; .FileVersion : file version formatted as X.X.X.X
; .ProductVersion : product version formatted as X.X.X.X
; .InternalName : 
; .LegalTrademarks : 
; .LegalTrademarks1 : Use for Office Apps only ?
; .LegalTrademarks2 : Use for Office Apps only ?
; .OriginalFilename : 
; .ProductName : 
; .PrivateBuild : 
; .SpecialBuild : 
; .BuildID : 

; FileType infos (hexadecimal, 0x prefix isn't mandatory)
; FileOS : default to 0x4 (Windows 32-bit)
; FileType : default to 0x1 (application)
; FileSubType : default to 0x0 (undefined)

	If !_verinfoobj.CommaFileVersion
		_verinfoobj.CommaFileVersion := "1,0,0,0"
	If !_verinfoobj.CommaProductVersion
		_verinfoobj.CommaProductVersion := "1,0,0,0"
	If !_verinfoobj.LangCode
		_verinfoobj.LangCode := "0x0000"
	If !_verinfoobj.VarFileInfoLangCode
		 _verinfoobj.VarFileInfoLangCode := _verinfoobj.LangCode
	If !_verinfoobj.CharSet
		_verinfoobj.CharSet := "0x04B0"
	If !_verinfoobj.VarFileInfoCharSet
		 _verinfoobj.VarFileInfoCharSet := _verinfoobj.CharSet
	If !_verinfoobj.FileOS
		_verinfoobj.FileOS := "0x4"
	If !_verinfoobj.FileType
		{
		_verinfoobj.FileType := "0x1"
		_verinfoobj.FileSubType := "0x0"
		}

	_VarFileInfo := CreateVarFileInfo(_verinfoobj.VarFileInfoLangCode, _verinfoobj.VarFileInfoCharSet)
	_StringList := ""
	For k, v in _verinfoobj
		{
		If k in CommaFileVersion,CommaProductVersion,LangCode,CharSet,VarFileInfoLangCode,VarFileInfoCharSet,FileOS,FileType,FileSubType
			Continue
		_StringList .= CreateString(k,v)
		}
	_StringTable := CreateStringTable(_verinfoobj.LangCode, _verinfoobj.CharSet, _StringList)
	_StringFileInfo := CreateStringFileInfo(_StringTable)
	_VSFixed := CreateVSFixed(_verinfoobj)
	_VSVersion := CreateVSVersion(_VSFixed, _StringFileInfo, _VarFileInfo)

Return _VSVersion
}


CreateVar(h_LangCode, h_CharSet, i_Type = 0)
{
; Var Structure / Element Reference
;	WORD   wLength;			/ Elem1
;	WORD   wValueLength;	/ Elem2
;	WORD   wType;			/ Elem3 = i_Type
;	WCHAR  szKey[];			/ Elem4 = Translation
;	WORD   Padding[];		/ Elem5
;	WORD   Value[];			/ Elem6 = h_LangCode + h_CharSet

/*
Language codes
	0000 Neutral
	0401 Arabic
	0402 Bulgarian
	0403 Catalan
	0404 Traditional Chinese
	0405 Czech
	0406 Danish
	0407 German
	0408 Greek
	0409 U.S. English
	040A Castilian Spanish
	040B Finnish
	040C French
	040D Hebrew
	040E Hungarian
	040F Icelandic
	0410 Italian
	0411 Japanese
	0412 Korean
	0413 Dutch
	0414 Norwegian - Bokml
	0810 Swiss Italian
	0813 Belgian Dutch (Cyrillic)
	0814 Norwegian - Nynorsk
	0415 Polish
	0416 Brazilian Portuguese
	0417 Rhaeto-Romanic
	0418 Romanian
	0419 Russian
	041A Croato-Serbian (Latin)
	041B Slovak
	041C Albanian
	041D Swedish
	041E Thai
	041F Turkish
	0420 Urdu
	0421 Bahasa
	0804 Simplified Chinese
	0807 Swiss German
	0809 U.K. English
	080A Mexican Spanish
	080C Belgian French
	0C0C Canadian French
	100C Swiss French
	0816 Portuguese
	081A Serbo-Croatian (Cyrillic)

Character Set
	0000  7-bit ASCII
	03A4  Windows, Japan (Shift - JIS X-0208)
	03B5  Windows, Korea (Shift - KSC 5601)
	03B6  Windows, Taiwan (GB5)
	04B0  Unicode
	04E2  Windows, Latin-2 (Eastern European)
	04E3  Windows, Cyrillic
	04E4  Windows, Multilingual
	04E5  Windows, Greek
	04E6  Windows, Turkish
	04E7  Windows, Hebrew
	04E8  Windows, Arabic
*/

	Elem3 := IntToWord(i_Type)
	Elem4 := IsszKey(TextToHex("Translation"))
	Elem5 := Mod(CountWords(Elem4),2) ? "" : "0000"
	Elem6 := HexToWord(h_LangCode) . HexToWord(h_CharSet)
	Elem2 := IntToWord(CountWords(Elem6)*2)
	Elem1 := IntToWord((1+CountWords(Elem2 . Elem3 . Elem4 . Elem5 . Elem6))*2)
	s_Element := Elem1 . Elem2 . Elem3 . Elem4 . Elem5 . Elem6
	s_Padding := Mod(CountWords(s_Element),2) ? "0000" : ""
	s_Element .= s_Padding
Return s_Element
}

CreateVarFileInfo(h_LangCode, h_CharSet, i_Type = 1, i_VarType = 0)
{
; VarFileInfo Structure / Element Reference
;	WORD   wLength;			/ Elem1
;	WORD   wValueLength;	/ Elem2
;	WORD   wType;			/ Elem3
;	WCHAR  szKey[];			/ Elem4
;	WORD   Padding[];		/ Elem5
;	Var    Children[];		/ Elem6

	Elem3 := IntToWord(i_Type)
	Elem4 := IsszKey(TextToHex("VarFileInfo"))
	Elem5 := Mod(CountWords(Elem4),2) ? "" : "0000"
	Elem6 := CreateVar(h_LangCode, h_CharSet, i_VarType)
	Elem2 := IntToWord(0)
	Elem1 := IntToWord((1+CountWords(Elem2 . Elem3 . Elem4 . Elem5 . Elem6))*2)
	s_Element := Elem1 . Elem2 . Elem3 . Elem4 . Elem5 . Elem6
	s_Padding := Mod(CountWords(s_Element),2) ? "0000" : ""
	s_Element .= s_Padding
Return s_Element
}

CreateString(s_szKey, s_Value, i_Type = 1)
{
; String Structure / Element Reference
;	WORD   wLength;			/ Elem1
;	WORD   wValueLength;	/ Elem2
;	WORD   wType;			/ Elem3
;	WCHAR  szKey[];			/ Elem4
;	WORD   Padding[];		/ Elem5
;	WORD   Value[];			/ Elem6

/*
Guidelines values for szKey :
	Comments
	CompanyName
	FileDescription
	FileVersion
	InternalName
	LegalCopyright
	LegalTrademarks
	OriginalFilename
	PrivateBuild
	ProductName
	ProductVersion
	BuilID
	SpecialBuild
*/

	Elem3 := IntToWord(i_Type)
	Elem4 := IsszKey(TextToHex(s_szKey))
	Elem5 := Mod(CountWords(Elem4),2) ? "" : "0000"
	Elem6 := IsszKey(TextToHex(s_Value))
	Elem2 := IntToWord(CountWords(Elem6))
	Elem1 := IntToWord((1+CountWords(Elem2 . Elem3 . Elem4 . Elem5 . Elem6))*2)
	s_Element := Elem1 . Elem2 . Elem3 . Elem4 . Elem5 . Elem6
	s_Padding := Mod(CountWords(s_Element),2) ? "0000" : ""
	s_Element .= s_Padding
Return s_Element
}

CreateStringTable(h_LangCode, h_CharSet, s_StringList, i_Type = 1)
{
; StringTable Structure / Element Reference
;	WORD   wLength;			/ Elem1
;	WORD   wValueLength;	/ Elem2
;	WORD   wType;			/ Elem3
;	WCHAR  szKey[];			/ Elem4
;	WORD   Padding[];		/ Elem5
;	String    Children[];	/ Elem6

	StringReplace, h_LangCode, h_LangCode, 0x
	StringReplace, h_CharSet, h_CharSet, 0x
	Elem3 := IntToWord(i_Type)
	Elem4 := IsszKey(TextToHex(h_LangCode) . TextToHex(h_CharSet))
	Elem5 := Mod(CountWords(Elem4),2) ? "" : "0000"
	Elem6 := s_StringList
	Elem2 := IntToWord(0)
	Elem1 := IntToWord((1+CountWords(Elem2 . Elem3 . Elem4 . Elem5 . Elem6))*2-(SubStr(s_StringList, -7)="00000000" ? 2 : 0))
	s_Element := Elem1 . Elem2 . Elem3 . Elem4 . Elem5 . Elem6
	s_Padding := Mod(CountWords(s_Element),2) ? "0000" : ""
	s_Element .= s_Padding
Return s_Element
}

CreateStringFileInfo(s_StringTable, i_Type = 1)
{
; StringFileInfo Structure / Element Reference
;	WORD   wLength;			/ Elem1
;	WORD   wValueLength;	/ Elem2
;	WORD   wType;			/ Elem3
;	WCHAR  szKey[];			/ Elem4
;	WORD   Padding[];		/ Elem5
;	StringTable Children[];	/ Elem6

	Elem3 := IntToWord(i_Type)
	Elem4 := IsszKey(TextToHex("StringFileInfo"))
	Elem5 := Mod(CountWords(Elem4),2) ? "" : "0000"
	Elem6 := s_StringTable
	Elem2 := IntToWord(0)
	Elem1 := IntToWord((1+CountWords(Elem2 . Elem3 . Elem4 . Elem5 . Elem6))*2-(SubStr(s_StringTable, -7)="00000000" ? 2 : 0))
	s_Element := Elem1 . Elem2 . Elem3 . Elem4 . Elem5 . Elem6
	s_Padding := Mod(CountWords(s_Element),2) ? "0000" : ""
	s_Element .= s_Padding
Return s_Element
}

CreateVSFixed(_object, dwSignature= "0xFEEF04BD", dwStrucVersion= "0x10000", dwFileFlagsMask= "0x3f", dwFileFlags= "0x0", dwFileDateMS= "0x0", dwFileDateLS= "0x0")
{
; VX_FIXEDFILEINFO Structure
;	DWORD dwSignature; always 0xFEEF04BD
;	DWORD dwStrucVersion;
;	DWORD dwFileVersionMS; represented by QWord dwFileVersion
;	DWORD dwFileVersionLS;
;	DWORD dwProductVersionMS; represented by QWord dwProductVersion
;	DWORD dwProductVersionLS;
;	DWORD dwFileFlagsMask; always 0x3F
;	DWORD dwFileFlags; see http://msdn.microsoft.com/en-us/library/ms646997%28v=vs.85%29.aspx for details
;	DWORD dwFileOS;
;	DWORD dwFileType;
;	DWORD dwFileSubtype;
;	DWORD dwFileDateMS;
;	DWORD dwFileDateLS;

; TODO : improve support for StrucVersion, FileFlags, FileSubType and FileDate

	s_Element := HexToDWord(dwSignature)
				. HexToDWord(dwStrucVersion)
				. VerToQWord(_object.CommaFileVersion)
				. VerToQWord(_object.CommaProductVersion)
				. HexToDWord(dwFileFlagsMask)
				. HexToDWord(dwFileFlags)
				. HexToDWord(_object.FileOS)
				. HexToDWord(_object.FileType)
				. HexToDWord(_object.FileSubType)
				. HexToDWord(dwFileDateMS)
				. HexToDWord(dwFileDateLS)
Return s_Element
}

CreateVSVersion(s_VSFixed, s_StringFileInfo, s_VarFileInfo, i_Type = 0)
{
; StringFileInfo Structure / Element Reference
;	WORD   wLength;			/ Elem1
;	WORD   wValueLength;	/ Elem2
;	WORD   wType;			/ Elem3
;	WCHAR  szKey[];			/ Elem4
;	WORD   Padding[];		/ Elem5
;	VS_FIXEDFILEINFO  Value	/ Elem6
;	WORD   Padding[];		/ Optional
;	WORD   Children[];		/ Elem7

	Elem3 := IntToWord(i_Type)
	Elem4 := IsszKey(TextToHex("VS_VERSION_INFO"))
	Elem5 := Mod(CountWords(Elem4),2) ? "" : "0000"
	Elem6 := s_VSFixed
	Elem7 := s_StringFileInfo . s_VarFileInfo
	Elem2 := IntToWord(CountWords(s_VSFixed)*2)
	Elem1 := IntToWord((1+CountWords(Elem2 . Elem3 . Elem4 . Elem5 . Elem6 . Elem7))*2)
	s_Element := Elem1 . Elem2 . Elem3 . Elem4 . Elem5 . Elem6 . Elem7
Return s_Element
}

/*
******************************************************************************
|
**************************** Conversion Functions ****************************
|
|	- IntToWord :	convert an integer value to a little-endian Word
|	- HexToWord :	convert an hexadecimal value (0xFFFF or FFFF supported) to
|					a little-endian Word
|	- HexToDWord :	same as above but to a little endian DWord
|	- VerToQWord :	convert a version string (ie : 1,2,3,4) to a little-endian
|					QWord. Use commas not dots !
|	- TextToHex :	convert a string (Unicode) to hexadecimal
|					(each char = one word, UTF-16 Little-Endian)
|
******************************************************************************
*/

IntToWord(i_entier)
{
	SetFormat Integer, H
	; conversion to hex value
	i_entier += 0
	SetFormat Integer, D
	; conversion to Word (4 hexa digits)
	StringTrimLeft, i_entier, i_entier, 2
	i_entier := "0000" . i_entier
	StringRight, i_entier, i_entier, 4
	w_mot := SubStr(i_entier, 3, 2) . SubStr(i_entier, 1, 2)
Return w_mot
}

HexToWord(h_hexa)
{
	StringReplace, h_hexa, h_hexa, 0x
	h_hexa := "0000" . h_hexa
	StringRight, h_hexa, h_hexa, 4
	w_mot := SubStr(h_hexa, 3, 2) . SubStr(h_hexa, 1, 2)
Return w_mot
}

HexToDWord(h_hexa)
{
	StringReplace, h_hexa, h_hexa, 0x
	h_hexa := "00000000" . h_hexa
	StringRight, h_hexa, h_hexa, 8
	dw_mot := HexToWord(SubStr(h_hexa, 5, 4)) . HexToWord(SubStr(h_hexa, 1, 4))
Return dw_mot
}

VerToQWord(s_chaine)
{
	qw_Result =
	StringSplit, Num, s_chaine, `,
	Loop, 4
		Num%A_Index% := IntToWord(Num%A_Index%)
	qw_Result := NUm2 . Num1 . Num4 . Num3
Return qw_Result
}

TextToHex(ByRef s_chaine)
{
	s_Result := ""
	SetFormat, IntegerFast, H
	Loop, % StrLen(s_chaine)
		{
		_ms := _ls := ""
		_ls .= SubStr(NumGet(s_chaine,(A_Index-1)*2, "UChar"), 3)
		_ms .= SubStr(NumGet(s_chaine,(A_Index-1)*2+1, "UChar"), 3)
		s_Result .= ((StrLen(_ls) = 1) ? "0" _ls : _ls) ((StrLen(_ms) = 1) ? "0" _ms : _ms)
		}
	SetFormat, IntegerFast, D
Return s_Result
}



/*
******************************************************************************
|
************************** Miscellaneous Functions ***************************
|
|	- IsszKey :		add NULL Word to terminate szKey
|	- CountWords :	count the number of Words in the string (integer result)
|	- ResWrite : 	write data to binary file
|
******************************************************************************
*/

IsszKey(s_chaine)
{
Return s_chaine . "0000"
}

CountWords(s_chaine)
{
	i_NbWords := StrLen(s_chaine)//4
Return i_NbWords
}

ResWrite(file, data)
{
	f := FileOpen(file, "w")
	_nbBytes := StrLen(data) // 2
	If StrLen(data) != (_nbBytes * 2)
		Return False
	Loop, % _nbBytes
	{
		_value := "0x" . SubStr(data, (A_Index * 2) -1, 2)
		f.WriteUChar(_value)
	}
	f.Close()
	Return _nbBytes
}
