# Copyright 2004-2022 Tom Rothamel # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # http://www.csn.ul.ie/~caolan/publink/winresdump/winresdump/doc/pefile.html # Contains a reasonable description of the format. from __future__ import print_function from renpy.compat import bchr, chr, str, PY2 import struct import sys import array import pefile # This class performs various operations on memory-loaded binary files, # including modifications. class BinFile(object): def set_u32(self, addr, value): self.a[addr + 0] = (value >> 0) & 0xff self.a[addr + 1] = (value >> 8) & 0xff self.a[addr + 2] = (value >> 16) & 0xff self.a[addr + 3] = (value >> 24) & 0xff def u32(self): addr = self.addr rv = self.a[addr] rv |= self.a[addr + 1] << 8 rv |= self.a[addr + 2] << 16 rv |= self.a[addr + 3] << 24 self.addr += 4 return rv def u16(self): addr = self.addr rv = self.a[addr] rv |= self.a[addr + 1] << 8 self.addr += 2 return rv def u8(self): rv = self.a[self.addr] self.addr += 1 return rv def name(self): c = self.u16() rv = u"" for _i in range(c): rv += chr(self.u16()) return rv def seek(self, addr): self.addr = addr def tostring(self): if PY2: return self.a.tostring() # type: ignore else: return self.a.tobytes() def substring(self, start, len): # @ReservedAssignment if PY2: return self.a[start:start + len].tostring() # type: ignore else: return self.a[start:start + len].tobytes() def __init__(self, data): self.a = array.array('B') if PY2: self.a.fromstring(data) # type: ignore else: self.a.frombytes(data) ############################################################################## # These functions parse data out of the file. In these functions, offset is # relative to the start of the file. # The virtual address of the resource segment. resource_virtual = 0 # This parses a data block out of the resources. def parse_data(bf, offset): bf.seek(offset) data_offset = bf.u32() data_len = bf.u32() code_page = bf.u32() bf.u32() l = [ ] bf.seek(data_offset - resource_virtual) for _i in range(data_len): l.append(bchr(bf.u8())) return (code_page, b"".join(l)) # This parses a resource directory. def parse_directory(bf, offset): bf.seek(offset) char = bf.u32() # @UnusedVariable timedate = bf.u32() # @UnusedVariable major = bf.u16() # @UnusedVariable minor = bf.u16() # @UnusedVariable n_named = bf.u16() n_id = bf.u16() entries = [ ] for _i in range(n_named + n_id): entries.append((bf.u32(), bf.u32())) rv = { } for name, value in entries: if name & 0x80000000: bf.seek((name & 0x7fffffff)) name = bf.name() if value & 0x80000000: value = parse_directory(bf, value & 0x7fffffff) else: value = parse_data(bf, value) rv[name] = value return rv ############################################################################## # This utility function displays the tree of resources that have been loaded. def show_resources(d, prefix): if not isinstance(d, dict): print(prefix, "Codepage", d[0], "length", len(d[1])) return for k in d: print(prefix, k) show_resources(d[k], prefix + " ") ############################################################################## # These functions repack the resources into a new resource segment. Here, # the offset is relative to the start of the resource segment. class Packer(object): def pack(self, d): self.data = b"" self.data_offset = 0 self.entries = b"" self.entries_offset = 0 head = self.pack_dict(d, 0) self.data = b"" self.data_offset = len(head) + len(self.entries) self.entries = b"" self.entries_offset = len(head) return self.pack_dict(d, 0) + self.entries + self.data def pack_name(self, s): rv = self.data_offset + len(self.data) l = len(s) s = s.encode("utf-16le") self.data += struct.pack(" than this due to its alignment requirement (In memory size) # # the resource section should be padded out to meet the alignment requirement of SizeOfRawData = multiple of FileAlignment (So, in the file, since on disk) # ##### # RVA table virt address and size = that in the section header # ##### # SizeOfImage must be aligned to SectionAlignment (memory alignment) # There is NO padding needed, the image size number simply needs to be aligned to >= the memoryalignment = pe.OPTIONAL_HEADER.SectionAlignment filealignment = pe.OPTIONAL_HEADER.FileAlignment rsrc_section.Misc_VirtualSize = newExactSize rsrc_section.Misc_PhysicalAddress = newExactSize rsrc_section.Misc = newExactSize #Size of resource section (and pointer, but we're not changing anything 'above' resource) is the same as virtual address in the section table # https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only pe.OPTIONAL_HEADER.DATA_DIRECTORY[2].Size = rsrc_section.Misc_VirtualSize #Alignment for raw data if len(rsrc) % filealignment: pad = filealignment - (len(rsrc) % filealignment) padding = b"\0" * (pad) padding = padding[:pad] rsrc += padding rsrc_section.SizeOfRawData = len(rsrc) #Image size is the memory size of all sections + all headers, padded to memory alignment. #There's alread a header size, and this isn't changing headers, so just add. #imageSize = pe.OPTIONAL_HEADER.ImageBase + pe.OPTIONAL_HEADER.SizeOfHeaders imageSize = pe.OPTIONAL_HEADER.SizeOfHeaders for s in pe.sections: sectionSize = s.Misc_VirtualSize #Align every section, so add padding to size pad = 0 if sectionSize % memoryalignment: pad = memoryalignment - (sectionSize % memoryalignment) imageSize += (sectionSize + pad) pad = 0 if imageSize % memoryalignment: pad = memoryalignment - (imageSize % memoryalignment) pe.OPTIONAL_HEADER.SizeOfImage = imageSize + pad #The symbol table is simply left off. Its size DOES factor into ImageSize, but we didn't calculate it above, so fine #Correctly checksum the file. The entire file is involved in the calculation, so a new PE object must be generated for the calculation to work against newFile = pe.write()[:base] + rsrc newpe = pefile.PE(data=bytes(newFile)) newpe.OPTIONAL_HEADER.CheckSum = newpe.generate_checksum() return bytes(newpe.write()) if __name__ == "__main__": f = open(sys.argv[3], "wb") f.write(change_icons(sys.argv[1], sys.argv[2])) f.close()