Files
love-pe/love-pe.lua
2018-05-16 22:22:02 +03:00

928 lines
26 KiB
Lua

--love-pe library by RamiLego4Game (Rami Sabbagh)
--[[
- Usage:
local lovePE = require("love-pe")
local icodata = lovePE.extractIcon(exeFile)
local success = lovePE.replaceIcon(exeFile,icoFile,newFile)
local success = lovePE.patchIcon(exeFile,icoFile,newFile)
local icodata = lovePE.extractIcon(exeString)
local success, newString = lovePE.replaceIcon(exeString,icoString)
local success, newString = lovePE.patchIcon(exeString,icoString)
- Arguments:
exeFile -> A LÖVE File object open in read mode and seaked at 0, The source exe file.
icoFile -> A LÖVE File object open in read mode and seaked at 0, The new ico file.
newFile -> A LÖVE File object open in write mode and seaked at 0, The new patched exe file.
exeString -> The source exe data as a string.
icoString -> The new ico data as a string.
newString -> The new patched exe data as a string.
- Reference:
Version File Resource: https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
Icons:
https://msdn.microsoft.com/en-us/library/ms997538.aspx
]]
local bit = require("bit")
local utf8 = require("utf8")
local bor,band,lshift,rshift,tohex = bit.bor,bit.band,bit.lshift,bit.rshift,bit.tohex
local resourcesTypes = {
"CURSOR",
"BITMAP",
"ICON",
"MENU",
"DIALOG",
"STRING_TABLE",
"FONT_DIRECTORY",
"FONT",
"ACCELERATORS",
"UNFORMATTED_RESOURCE_DATA",
"MESSAGE_TABLE",
"GROUP_CURSOR",
13,
"GROUP_ICON",
15,
"VERSION_INFORMATION",
17,18,19,20,21,22,23,
"MANIFEST"
}
for k,v in ipairs(resourcesTypes) do
resourcesTypes[v] = k
end
--==Internal Functions==--
local function newStringFile(data)
local str = data or ""
local file = {}
local pos = 0
function file:getSize() return #str end
function file:seek(p) pos = p end
function file:tell() return pos end
function file:read(bytes)
if bytes then
if pos+bytes > #str then bytes = #str-pos end
local substr = str:sub(pos+1,pos+bytes)
pos = pos + bytes
return substr, bytes
else
return str
end
end
function file:write(d,s)
if s then d = d:sub(1,s) end
if pos+#d > #str then d = d:sub(1,#str-pos) end
str = str:sub(1,pos)..d..str:sub(pos+#d+1,-1)
pos = pos + #d
return #d
end
function file:flush() end
function file:close() end
return file
end
local function decodeNumber(str,littleEndian)
local num = 0
if littleEndian then str = str:reverse() end
for char in string.gmatch(str,".") do
local byte = string.byte(char)
num = lshift(num,8)
num = bor(num, byte)
end
return num
end
local function encodeNumber(num,len,bigEndian)
local chars = {}
for i=1,len do
chars[i] = string.char(band(num,255))
num = rshift(num,8)
end
chars = table.concat(chars)
if bigEndian then chars = chars:reverse() end
return chars
end
local function decodeUTF16(str16)
local giter = string.gmatch(str16,"..")
local iter = function()
local short = giter()
if short then
return decodeNumber(short,true)
end
end
local nstr = {}
local unicode = iter()
while unicode do
--Surrogate pairs
if unicode >= 0xD800 and unicode <= 0xDBFF then
local lowPair = iter()
if lowPair and lowPair >= 0xDC00 and lowPair <= 0xDFFF then
unicode = lshift(unicode-0xD800,10) + (lowPair-0xDC00) + 0x01000
nstr[#nstr+1] = utf8.char(unicode)
unicode = iter()
else --Unpaired surrogate
nstr[#nstr+1] = utf8.char(unicode)
unicode = lowPair
end
else
nstr[#nstr+1] = utf8.char(unicode)
unicode = iter()
end
end
return table.concat(nstr)
end
local function encodeUTF16(str8)
local nstr ={}
for pos, unicode in utf8.codes(str8) do
if unicode >= 0x10000 then --Encode as surrogate pair
unicode = unicode - 0x01000
nstr[#nstr+1] = encodeNumber(rshift(unicode,10)+0xD800,2,false)
nstr[#nstr+1] = encodeNumber(band(unicode,0x3FF)+0xDC00,2,false)
else
nstr[#nstr+1] = encodeNumber(unicode,2,false)
end
end
return table.concat(nstr)
end
local function convertRVA2Offset(RVA,Sections)
for id, Section in ipairs(Sections) do
if (Section.VirtualAddress <= RVA) and (RVA < (Section.VirtualAddress + Section.VirtualSize)) then
return Section.PointerToRawData + (RVA - Section.VirtualAddress)
end
end
error("FAILED "..tohex(RVA))
end
local function readResourceDirectoryTable(exeFile,Sections,RootOffset,Level)
local Tree, TreeOffsets = {}, {}
local Characteristics = decodeNumber(exeFile:read(4),true)
local TimeDateStamp = decodeNumber(exeFile:read(4),true)
local MajorVersion = decodeNumber(exeFile:read(2),true)
local MinorVersion = decodeNumber(exeFile:read(2),true)
local NumberOfNameEntries = decodeNumber(exeFile:read(2),true)
local NumberOfIDEntries = decodeNumber(exeFile:read(2),true)
--print("--readResourceDirectoryTable",RootOffset,Level,MajorVersion,MinorVersion,TimeDateStamp,NumberOfNameEntries,NumberOfIDEntries)
--Parse Entries
for i=1,NumberOfNameEntries+NumberOfIDEntries do
local Name = decodeNumber(exeFile:read(4),true)
local Offset = decodeNumber(exeFile:read(4),true)
local ReturnOffset = exeFile:tell()
--Parse name/id for entry
if band(Name,0x80000000) ~= 0 then
--Name is a string RVA
local NameOffset = convertRVA2Offset(band(Name,0x7FFFFFFF), Sections)
exeFile:seek(NameOffset)
local NameLength = decodeNumber(exeFile:read(2),true)
--Decode UTF-16LE string
Name = decodeUTF16(exeFile:read(NameLength*2))
--print("Name Entry",Name)
else
--Name is an ID
Name = band(Name,0xFFFF)
if Level == 0 then
if resourcesTypes[Name] then
Name = resourcesTypes[Name]
end
end
--print("ID Entry",Name)
end
if band(Offset,0x80000000) ~= 0 then
--Another directory
exeFile:seek(RootOffset + band(Offset,0x7FFFFFFF))
--print("Another Directory")
Tree[Name], TreeOffsets[Name] = readResourceDirectoryTable(exeFile,Sections,RootOffset,Level+1)
else
--Data offset
exeFile:seek(RootOffset + band(Offset,0x7FFFFFFF))
local DataRVA = decodeNumber(exeFile:read(4),true)
local DataSize = decodeNumber(exeFile:read(4),true)
local DataCodepage = decodeNumber(exeFile:read(4),true)
--print("DATA",DataRVA,DataSize,DataCodepage)
if DataCodepage ~= 0 then print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") end
local DataOffset = convertRVA2Offset(DataRVA,Sections)
exeFile:seek(DataOffset)
Tree[Name] = exeFile:read(DataSize)
TreeOffsets[Name] = RootOffset + band(Offset,0x7FFFFFFF)
end
exeFile:seek(ReturnOffset)
end
--print("---Directory end")
return Tree, TreeOffsets
end
local function buildResourcesDirectoryTable(ResourcesTree,VirtualAddress)
local Data = {}
local Offset = 0
local Level = 0
local function writeDirectory(Directory)
local NameEntries, IDEntries = {}, {}
Level = Level + 1
for k,v in pairs(Directory) do
if type(k) == "string" then
if Level == 1 and resourcesTypes[k] then
IDEntries[#IDEntries+1] = {resourcesTypes[k],v}
else
NameEntries[#NameEntries+1] = {k,v}
end
elseif type(k) == "number" then
IDEntries[#IDEntries+1] = {k,v}
end
end
table.sort(IDEntries,function(a,b) return a[1] < b[1] end)
--Write the resource directory table
Data[#Data+1] = encodeNumber(0,4,false) Offset = Offset + 4 --Characteristics
Data[#Data+1] = encodeNumber(0,4,false) Offset = Offset + 4 --Time/Date Stamp
Data[#Data+1] = encodeNumber(0,2,false) Offset = Offset + 2 --Major Version
Data[#Data+1] = encodeNumber(0,2,false) Offset = Offset + 2 --Minor Version
Data[#Data+1] = encodeNumber(#NameEntries,2,false) Offset = Offset + 2 --Number of name entries
Data[#Data+1] = encodeNumber(#IDEntries,2,false) Offset = Offset + 2 --Number of ID entries
local EntriesID = #Data --Where the entries data start
--Pre-Allocate the place for the entries
for i=1,#NameEntries+#IDEntries do
Data[#Data+1] = ""
Data[#Data+1] = ""
Offset = Offset + 8
end
for _, Entry in ipairs(NameEntries) do
--Write resource directory string
local StringRVA = VirtualAddress+Offset
local String = encodeUTF16(Entry[1])
Data[#Data+1] = encodeNumber(#String/2,2,false) Offset = Offset + 2 --String Length
Data[#Data+1] = String; Offset = Offset + #String --Unicode String
Entry[3] = StringRVA + 0x80000000 --A string name
Entry[4] = Offset
if type(Entry[2]) == "table" then --Sub-directory
Entry[4] = Entry[4] + 0x80000000 --Set sub-directory flag
writeDirectory(Entry[2])
else --Data
Data[#Data+1] = encodeNumber(VirtualAddress+Offset+16,4,false) Offset = Offset + 4 --Predict the DataRVA
Data[#Data+1] = encodeNumber(#Entry[2],4,false) Offset = Offset + 4 --Size
Data[#Data+1] = encodeNumber(0,4,false) Offset = Offset + 4 --Codepoint
Data[#Data+1] = encodeNumber(0,4,false) Offset = Offset + 4 --Reserved
Data[#Data+1] = Entry[2]; Offset = Offset + #Entry[2] --The actual data
end
end
for _, Entry in ipairs(IDEntries) do
Entry[3] = Entry[1] --The entry id itself
Entry[4] = Offset
if type(Entry[2]) == "table" then --Sub-directory
Entry[4] = Entry[4] + 0x80000000 --Set sub-directory flag
writeDirectory(Entry[2])
else --Data
Data[#Data+1] = encodeNumber(VirtualAddress+Offset+16,4,false) Offset = Offset + 4 --Predict the DataRVA
Data[#Data+1] = encodeNumber(#Entry[2],4,false) Offset = Offset + 4 --Size
Data[#Data+1] = encodeNumber(0,4,false) Offset = Offset + 4 --Codepoint
Data[#Data+1] = encodeNumber(0,4,false) Offset = Offset + 4 --Reserved
Data[#Data+1] = Entry[2]; Offset = Offset + #Entry[2] --The actual data
end
end
for _, Entry in ipairs(NameEntries) do
Data[EntriesID+1] = encodeNumber(Entry[3],4,false); EntriesID = EntriesID + 1
Data[EntriesID+1] = encodeNumber(Entry[4],4,false); EntriesID = EntriesID + 1
end
for _, Entry in ipairs(IDEntries) do
Data[EntriesID+1] = encodeNumber(Entry[3],4,false); EntriesID = EntriesID + 1
Data[EntriesID+1] = encodeNumber(Entry[4],4,false); EntriesID = EntriesID + 1
end
Level = Level - 1
end
writeDirectory(ResourcesTree)
return table.concat(Data)
end
local function getAnyKey(t)
for k,v in pairs(t) do
return k
end
end
local function getAnyValue(t)
for k,v in pairs(t) do
return v
end
end
local function extractGroupIcon(ResourcesTree,GroupID)
--Icon extraction process
local IconGroup = getAnyValue(ResourcesTree["GROUP_ICON"][GroupID])
local Icons = {""}
local o = 5 --String Offset
--Read the icon header
local Count = decodeNumber(IconGroup:sub(o,o+1),true)
o = o+2
local DataOffset = 6 + 16*Count
for i=1,Count do
o = o+12
local IcoID = decodeNumber(IconGroup:sub(o,o+1),true)
Icons[#Icons+1] = getAnyValue(ResourcesTree["ICON"][IcoID])
local Length = #Icons[#Icons]
IconGroup = IconGroup:sub(1,o-1) .. encodeNumber(DataOffset,4,false) .. IconGroup:sub(o+2,-1)
o = o + 4
DataOffset = DataOffset + Length
end
Icons[1] = IconGroup
return table.concat(Icons)
end
local function removeGroupIcon(ResourcesTree,GroupID)
local IconGroup = getAnyValue(ResourcesTree["GROUP_ICON"][GroupID])
ResourcesTree["GROUP_ICON"][GroupID] = nil --Delete the group icon
local o = 5 --String Offset
--Read the icon header
local Count = decodeNumber(IconGroup:sub(o,o+1),true)
o = o+2
for i=1,Count do
o = o+12
local IcoID = decodeNumber(IconGroup:sub(o,o+1),true)
ResourcesTree["ICON"][IcoID] = nil
o = o + 2
end
end
local function addGroupIcon(ResourcesTree,GroupID,icoFile)
local IconGroup = {}
local Icons = {}
local NextIconID = 1
IconGroup[#IconGroup+1] = icoFile:read(4)
local Count = decodeNumber(icoFile:read(2),true)
IconGroup[#IconGroup+1] = encodeNumber(Count,2,false)
for i=1,Count do
IconGroup[#IconGroup+1] = icoFile:read(8)
local IcoSize = decodeNumber(icoFile:read(4),true)
local IcoOffset = decodeNumber(icoFile:read(4),true)
IconGroup[#IconGroup+1] = encodeNumber(IcoSize,4,false)
--Find an empty slot for the icon data
while ResourcesTree["ICON"][NextIconID] do
NextIconID = NextIconID + 1
end
IconGroup[#IconGroup+1] = encodeNumber(NextIconID,2,false)
local ReturnOffset = icoFile:tell()
icoFile:seek(IcoOffset)
ResourcesTree["ICON"][NextIconID] = {[1033] = icoFile:read(IcoSize)}
icoFile:seek(ReturnOffset)
NextIconID = NextIconID + 1
end
icoFile:seek(0)
IconGroup = table.concat(IconGroup)
ResourcesTree["GROUP_ICON"][GroupID] = {[1033] = IconGroup}
end
local function patchGroupIcon(ResourcesTree, ResourcesOffsets, Sections, icoFile, exeFile, newFile, GroupID)
--Read resources group icon
local IconGroup = getAnyValue(ResourcesTree["GROUP_ICON"][GroupID])
local IconGroupFile = newStringFile(IconGroup)
IconGroupFile:read(4) --Skip 4 bytes
local ExeImagesCount = decodeNumber(IconGroupFile:read(2),true)
local ExeImagesDimensions = {}
for i=1,ExeImagesCount do
local ImgWidth = decodeNumber(IconGroupFile:read(1),true)
local ImgHeight = decodeNumber(IconGroupFile:read(1),true)
if ImgWidth == 0 then ImgWidth = 256 end
if ImgHeight == 0 then ImgHeight = 256 end
IconGroupFile:read(10) --Skip 10 bytes
local ImgID = decodeNumber(IconGroupFile:read(2),true)
ExeImagesDimensions[string.format("%dx%d",ImgWidth,ImgHeight)] = ImgID
end
--Read ico
icoFile:read(4) --Skip 4 bytes
local icoImagesCount = decodeNumber(icoFile:read(2),true)
local NewImages = {}
for i=1, icoImagesCount do
local ImgWidth = decodeNumber(icoFile:read(1),true)
local ImgHeight = decodeNumber(icoFile:read(1),true)
if ImgWidth == 0 then ImgWidth = 256 end
if ImgHeight == 0 then ImgHeight = 256 end
icoFile:read(6) --Skip 6 bytes
local ImgSize = decodeNumber(icoFile:read(4),true)
local ImgOffset = decodeNumber(icoFile:read(4),true)
local ReturnOffset = icoFile:tell()
icoFile:seek(ImgOffset)
local ImgData = icoFile:read(ImgSize)
icoFile:seek(ReturnOffset)
local IDStr = string.format("%dx%d",ImgWidth,ImgHeight)
if ExeImagesDimensions[IDStr] then
NewImages[ExeImagesDimensions[IDStr]] = ImgData
end
end
--Replace icons
for id, imgData in pairs(NewImages) do
local EntryOffset = ResourcesOffsets["ICON"][id][1033]
exeFile:seek(EntryOffset)
local DataRVA = decodeNumber(exeFile:read(4),true)
newFile:seek(exeFile:tell())
newFile:write(encodeNumber(#imgData,4,false)) --Write the new icon image size
local DataOffset = convertRVA2Offset(DataRVA,Sections)
newFile:seek(DataOffset)
newFile:write(imgData) --Write the new image data
end
end
local function skipDOSHeader(exeFile)
if exeFile:read(2) ~= "MZ" then error("This is not an executable file !",3) end
exeFile:read(58) --Skip 58 bytes
local PEHeaderOffset = decodeNumber(exeFile:read(4),true) --Offset to the 'PE\0\0' signature relative to the beginning of the file
exeFile:seek(PEHeaderOffset) --Seek into the PE Header
end
local function skipPEHeader(exeFile)
if exeFile:read(4) ~= "PE\0\0" then error("Corrupted executable file !",3) end
end
local function parseCOFFHeader(exeFile)
--Corrently only parses the NumberOfSections value, and skips the rest.
local values = {}
exeFile:read(2) --Skip Machine.
values.NumberOfSections = decodeNumber(exeFile:read(2),true)
exeFile:read(16) --Skip 3 long values (12 bytes) and 2 short values (4 bytes).
return values
end
local function parsePEOptHeader(exeFile)
local values = {}
local PEOptionalHeaderSignature = decodeNumber(exeFile:read(2),true)
values.x86, values.x64 = false, false --Executable arch
if PEOptionalHeaderSignature == 267 then --It's x86
values.x86 = true
elseif PEOptionalHeaderSignature == 523 then --It's x64
values.x64 = true
else
error("ROM images are not supported !",3)
end
exeFile:read(32-2) --Skip 30 bytes
values.SectionAlignment = decodeNumber(exeFile:read(4),true)
values.FileAlignment = decodeNumber(exeFile:read(4),true)
exeFile:read(values.x64 and 108-2-38 or 92-2-38) --Skip 106 bytes for x64, and 90 bytes for x86
values.NumberOfRvaAndSizes = decodeNumber(exeFile:read(4),true)
return values
end
local function parseDataTables(exeFile,NumberOfRvaAndSizes)
local DataDirectories = {}
for i=1, NumberOfRvaAndSizes do
DataDirectories[i] = {decodeNumber(exeFile:read(4),true), decodeNumber(exeFile:read(4),true)}
--print("DataDirectory #"..i,DataDirectories[i][1],DataDirectories[i][2])
end
return DataDirectories
end
local function writeDataDirectories(exeFile, DataDirectories)
for i, Directory in ipairs(DataDirectories) do
exeFile:write(encodeNumber(Directory[1],4,false))
exeFile:write(encodeNumber(Directory[2],4,false))
end
end
local function parseSectionsTable(exeFile,NumberOfSections)
local Sections = {}
for i=1, NumberOfSections do
--print("\n------=Section=------",i)
local Section = {}
Section.Name = ""
for i=1,8 do
local char = exeFile:read(1)
if char ~= "\0" then
Section.Name = Section.Name .. char
end
end
Section.VirtualSize = decodeNumber(exeFile:read(4),true)
Section.VirtualAddress = decodeNumber(exeFile:read(4),true)
Section.SizeOfRawData = decodeNumber(exeFile:read(4),true)
Section.PointerToRawData = decodeNumber(exeFile:read(4),true)
Section.PointerToRelocations = decodeNumber(exeFile:read(4),true)
Section.PointerToLinenumbers = decodeNumber(exeFile:read(4),true)
Section.NumberOfRelocations = decodeNumber(exeFile:read(2),true)
Section.NumberOfLinenumbers = decodeNumber(exeFile:read(2),true)
Section.Characteristics = decodeNumber(exeFile:read(4),true)
for k,v in pairs(Section) do
--print(k,v)
end
Sections[i] = Section
end
return Sections
end
local function writeSectionsTable(exeFile,Sections)
for id, Section in ipairs(Sections) do
exeFile:write(Section.Name..string.rep("\0",8-#Section.Name))
exeFile:write(encodeNumber(Section.VirtualSize,4,false))
exeFile:write(encodeNumber(Section.VirtualAddress,4,false))
exeFile:write(encodeNumber(Section.SizeOfRawData,4,false))
exeFile:write(encodeNumber(Section.PointerToRawData,4,false))
exeFile:write(encodeNumber(Section.PointerToRelocations,4,false))
exeFile:write(encodeNumber(Section.PointerToLinenumbers,4,false))
exeFile:write(encodeNumber(Section.NumberOfRelocations,2,false))
exeFile:write(encodeNumber(Section.NumberOfLinenumbers,2,false))
exeFile:write(encodeNumber(Section.Characteristics,4,false))
end
end
local function readSections(exeFile,Sections)
local SectionsData = {}
for id, Section in ipairs(Sections) do
exeFile:seek(Section.PointerToRawData)
SectionsData[id] = exeFile:read(Section.SizeOfRawData)
end
return SectionsData
end
local function writeSections(exeFile,Sections,SectionsData)
for id, Section in ipairs(Sections) do
exeFile:seek(Section.PointerToRawData)
exeFile:write(SectionsData[id])
end
end
local function readTrailData(exeFile)
local currentPos = exeFile:tell()
local size = exeFile:getSize()
return exeFile:read(size-currentPos+1)
end
local function writeTree(tree,path)
for k,v in pairs(tree) do
if type(v) == "table" then
love.filesystem.createDirectory(path..k)
writeTree(v,path..k.."/")
else
love.filesystem.write(path..k,v)
end
end
end
--==User API==--
local icapi = {}
function icapi.extractIcon(exeFile)
if type(exeFile) == "string" then exeFile = newStringFile(exeFile) end
--DOS Header
skipDOSHeader(exeFile)
--PE Header
skipPEHeader(exeFile)
--COFF Header
local NumberOfSections = parseCOFFHeader(exeFile).NumberOfSections
--PE Optional Header
local NumberOfRvaAndSizes = parsePEOptHeader(exeFile).NumberOfRvaAndSizes
local DataDirectories = parseDataTables(exeFile,NumberOfRvaAndSizes)
--Sections Table
local Sections = parseSectionsTable(exeFile,NumberOfSections)
--Calculate the file offset to the resources data directory
local ResourcesOffset = convertRVA2Offset(DataDirectories[3][1],Sections)
--Seek into the resources data !
exeFile:seek(ResourcesOffset)
local ResourcesTree = readResourceDirectoryTable(exeFile,Sections,ResourcesOffset,0)
local IconKeys,FirstIcon = {}
for k,v in pairs(ResourcesTree["GROUP_ICON"]) do
IconKeys[#IconKeys+1] = k
ResourcesTree["GROUP_ICON"][k] = extractGroupIcon(ResourcesTree,k)
if not FirstIcon then FirstIcon = ResourcesTree["GROUP_ICON"][k] end
end
for k,v in pairs(IconKeys) do
ResourcesTree["GROUP_ICON"][v..".ico"] = ResourcesTree["GROUP_ICON"][v]
ResourcesTree["GROUP_ICON"][v] = nil
end
--writeTree(ResourcesTree,"/")
return FirstIcon
end
function icapi.replaceIcon(exeFile,icoFile,newFile)
local newFile = newFile
if type(exeFile) == "string" then exeFile = newStringFile(exeFile) end
if type(icoFile) == "string" then icoFile = newStringFile(icoFile) end
if type(newFile) == "string" or not newFile then newFile = newStringFile(newFile) end
--DOS Header
skipDOSHeader(exeFile)
--PE Header
skipPEHeader(exeFile)
--COFF Header
local NumberOfSections = parseCOFFHeader(exeFile).NumberOfSections
--PE Optional Header
local PEOptHeader = parsePEOptHeader(exeFile)
local NumberOfRvaAndSizes = PEOptHeader.NumberOfRvaAndSizes
local DataDirectoriesOffset = exeFile:tell() --Where the DataDirectories are stored
local DataDirectories = parseDataTables(exeFile,NumberOfRvaAndSizes)
--Sections Table
local SectionsOffset = exeFile:tell() --Where the sections tables start
local Sections = parseSectionsTable(exeFile,NumberOfSections)
local SectionsData = readSections(exeFile,Sections)
--Trail data
local TrailData = readTrailData(exeFile)
--Calculate the file offset to the resources data directory
local ResourcesOffset = convertRVA2Offset(DataDirectories[3][1],Sections)
--Seek into the resources data !
exeFile:seek(ResourcesOffset)
--Parse the resources data
local ResourcesTree = readResourceDirectoryTable(exeFile,Sections,ResourcesOffset,0)
--writeTree(ResourcesTree,"/!NEW RES/")
local GroupID = getAnyKey(ResourcesTree["GROUP_ICON"])
removeGroupIcon(ResourcesTree,GroupID)
addGroupIcon(ResourcesTree,GroupID,icoFile)
local RSRC_ID = 0
for k,Section in ipairs(Sections) do
if Section.Name == ".rsrc" then
RSRC_ID = k
break
end
end
SectionsData[RSRC_ID] = buildResourcesDirectoryTable(ResourcesTree,Sections[RSRC_ID].VirtualAddress)
local function Align(value,file)
local aligner = PEOptHeader[file and "FileAlignment" or "SectionAlignment"]
return math.ceil(value/aligner)*aligner
end
SectionsData[RSRC_ID] = SectionsData[RSRC_ID] .. string.rep("\0",Align(#SectionsData[RSRC_ID],true)-#SectionsData[RSRC_ID])
local NewRSRCSize = Align(#SectionsData[RSRC_ID],true)
local OldRSRCSize = DataDirectories[3][2]
local ShiftOffset = NewRSRCSize - OldRSRCSize
DataDirectories[3][2] = NewRSRCSize
Sections[RSRC_ID].SizeOfRawData = Sections[RSRC_ID].SizeOfRawData + ShiftOffset
local RSRC_Pointer = Sections[RSRC_ID].PointerToRawData
for id, Section in ipairs(Sections) do
if Sections[id].PointerToRawData > RSRC_Pointer then
Sections[id].PointerToRawData = Sections[id].PointerToRawData + ShiftOffset
end
if Sections[id].PointerToRelocations > RSRC_Pointer then
Sections[id].PointerToRelocations = Sections[id].PointerToRelocations + ShiftOffset
end
if Sections[id].PointerToLinenumbers > RSRC_Pointer then
Sections[id].PointerToLinenumbers = Sections[id].PointerToLinenumbers + ShiftOffset
end
end
--Copy the DOS,PE,COFF and PEOpt headers
exeFile:seek(0)
newFile:write(exeFile:read(DataDirectoriesOffset))
writeDataDirectories(newFile,DataDirectories)
writeSectionsTable(newFile,Sections)
writeSections(newFile,Sections,SectionsData)
newFile:write(TrailData)
newFile:seek(0)
return true, newFile:read()
end
function icapi.patchIcon(exeFile, icoFile, newFile)
local newFile = newFile
if type(exeFile) == "string" then exeFile = newStringFile(exeFile) end
if type(icoFile) == "string" then icoFile = newStringFile(icoFile) end
if type(newFile) == "string" or not newFile then newFile = newStringFile(newFile) end
--DOS Header
skipDOSHeader(exeFile)
--PE Header
skipPEHeader(exeFile)
--COFF Header
local NumberOfSections = parseCOFFHeader(exeFile).NumberOfSections
--PE Optional Header
local PEOptHeader = parsePEOptHeader(exeFile)
local NumberOfRvaAndSizes = PEOptHeader.NumberOfRvaAndSizes
local DataDirectoriesOffset = exeFile:tell() --Where the DataDirectories are stored
local DataDirectories = parseDataTables(exeFile,NumberOfRvaAndSizes)
--Sections Table
local SectionsOffset = exeFile:tell() --Where the sections tables start
local Sections = parseSectionsTable(exeFile,NumberOfSections)
local SectionsData = readSections(exeFile,Sections)
--Calculate the file offset to the resources data directory
local ResourcesOffset = convertRVA2Offset(DataDirectories[3][1],Sections)
--Seek into the resources data !
exeFile:seek(ResourcesOffset)
--Parse the resources data
local ResourcesTree, ResourcesOffsets = readResourceDirectoryTable(exeFile,Sections,ResourcesOffset,0)
local GroupID = getAnyKey(ResourcesTree["GROUP_ICON"])
exeFile:seek(0)
newFile:write(exeFile:read())
newFile:seek(0)
patchGroupIcon(ResourcesTree,ResourcesOffsets,Sections,icoFile,exeFile,newFile,GroupID)
return true, newFile:read()
end
return icapi