This repository was archived by the owner on Aug 2, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlove-pe.lua
802 lines (582 loc) · 22.3 KB
/
love-pe.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
--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 icodata = lovePE.extractIcon(exeString)
local success, newString = lovePE.replaceIcon(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 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 = {}
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] = 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)
end
exeFile:seek(ReturnOffset)
end
--print("---Directory end")
return Tree
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 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
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
--==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
for id, Directory in ipairs(DataDirectories) do
if Directory[1] > Sections[RSRC_ID].VirtualAddress then
Directory[1] = Directory[1] + 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
return icapi