mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 08:45:33 +00:00
212 lines
6.8 KiB
GDScript
212 lines
6.8 KiB
GDScript
class_name ReplaceIcon
|
|
extends SceneTree
|
|
|
|
const ICON_SIZE := 359559
|
|
|
|
var error_callable: Callable
|
|
|
|
|
|
func _init() -> void:
|
|
var arguments = OS.get_cmdline_args()
|
|
if arguments.size() != 4:
|
|
print(
|
|
"Usage:\n",
|
|
" godot -s ReplaceIcon.gd icon name\n",
|
|
"\n",
|
|
"Replaces ico file in windows PE32+ executable.\n",
|
|
"Add --headless to hide Godot console.\n",
|
|
"\n",
|
|
"Arguments:\n",
|
|
" godot path to Godot 4 beta2+ executable\n",
|
|
" icon path to new icon\n",
|
|
" name path to modified PE32+ executable\n"
|
|
)
|
|
quit()
|
|
return
|
|
replace_icon(arguments[3], arguments[2])
|
|
quit()
|
|
|
|
|
|
func replace_icon(executable_path: String, icon_path: String) -> void:
|
|
var icon_replacer := IconReplacer.new()
|
|
|
|
var images := get_images(icon_path)
|
|
|
|
var executable_file := FileAccess.open(executable_path, FileAccess.READ_WRITE)
|
|
if not executable_file:
|
|
print_error("Could not open executable file!")
|
|
return
|
|
var headers := executable_file.get_buffer(2048)
|
|
var resources_section_entry := icon_replacer.find_resources_section_entry(headers)
|
|
if not resources_section_entry:
|
|
return
|
|
if resources_section_entry.size_of_raw_data < 359559:
|
|
print_error("Could not find icons in executable. Wrong template?")
|
|
return
|
|
|
|
executable_file.seek(resources_section_entry.pointer_to_raw_data)
|
|
var resources := executable_file.get_buffer(resources_section_entry.size_of_raw_data)
|
|
|
|
resources = icon_replacer.replace_icons(resources, resources_section_entry.virtual_address, images)
|
|
if not resources.is_empty():
|
|
executable_file.seek(resources_section_entry.pointer_to_raw_data)
|
|
executable_file.store_buffer(resources)
|
|
|
|
|
|
func get_images(icon_path: String) -> Dictionary:
|
|
var file := FileAccess.open(icon_path, FileAccess.READ)
|
|
if not file:
|
|
print_error(str("Could not open icon file!\n", FileAccess.get_open_error()))
|
|
return {}
|
|
return Icon.new(file.get_buffer(ICON_SIZE)).images
|
|
|
|
|
|
func print_error(error_message: String) -> void:
|
|
printerr(error_message)
|
|
if error_callable:
|
|
error_callable.call(error_message)
|
|
|
|
|
|
class IconReplacer:
|
|
enum ImageType {PE32 = 0x10b, PE32_PLUS = 0x20b}
|
|
|
|
const PE_HEADER_ADDRESS_OFFSET := 0x3c
|
|
const NUMBER_OF_SECTIONS_OFFSET := 0x6
|
|
const SIZE_OF_OPTIONAL_HEADER_OFFSET := 0x14
|
|
const MAGIC_OFFSET := 0x18
|
|
const COFF_HEADER_SIZE := 24
|
|
const SECTION_SIZE := 40
|
|
const SIZE_OF_RAW_DATA_OFFSET := 0x10
|
|
const POINTER_TO_RAW_DATA_OFFSET := 0x14
|
|
const DATA_ENTRY_SIZE := 16
|
|
|
|
var error_callable: Callable
|
|
|
|
|
|
func replace_icons(resources: PackedByteArray, rva_offset: int, images: Dictionary) -> PackedByteArray:
|
|
var data_entries := find_data_entries(resources)
|
|
for data_size in images.keys():
|
|
var icon_offset := find_icon_offset(data_entries, data_size, rva_offset)
|
|
if resources.slice(icon_offset + 1, icon_offset + 4).get_string_from_ascii() != "PNG":
|
|
print_error("Wrong icon type, PNG signature missing")
|
|
return PackedByteArray()
|
|
resources = IconReplacer.replace(resources, images[data_size], icon_offset)
|
|
return resources
|
|
|
|
|
|
func find_icon_offset(data_entries: Array, data_size: int, rva_offset: int) -> int:
|
|
for data_entry in data_entries:
|
|
if data_entry.size == data_size:
|
|
return data_entry.rva - rva_offset
|
|
return -1
|
|
|
|
|
|
func find_resources_section_entry(headers: PackedByteArray) -> SectionEntry:
|
|
var header_offset := IconReplacer.lsb_first(headers, PE_HEADER_ADDRESS_OFFSET, 2)
|
|
var image_type := IconReplacer.lsb_first(headers, header_offset + MAGIC_OFFSET, 2)
|
|
if not image_type == ImageType.PE32_PLUS:
|
|
print_error("Only PE32+ executables are handled.")
|
|
return null
|
|
var sections_size := IconReplacer.lsb_first(headers, header_offset + NUMBER_OF_SECTIONS_OFFSET, 2)
|
|
var size_of_optional_header := IconReplacer.lsb_first(headers, header_offset + SIZE_OF_OPTIONAL_HEADER_OFFSET, 2)
|
|
var sections_offset := header_offset + COFF_HEADER_SIZE + size_of_optional_header
|
|
for _i in range(sections_size):
|
|
var section_name = headers.slice(sections_offset, sections_offset + 8).get_string_from_ascii()
|
|
if section_name == ".rsrc":
|
|
return SectionEntry.new(headers.slice(sections_offset, sections_offset + SECTION_SIZE))
|
|
sections_offset += SECTION_SIZE
|
|
return null
|
|
|
|
|
|
func find_data_entries(resources: PackedByteArray) -> Array:
|
|
var result := []
|
|
parse_table(resources, 0, result)
|
|
return result
|
|
|
|
|
|
func parse_table(resources: PackedByteArray, offset: int, data_entries: Array) -> void:
|
|
var entry_count := IconReplacer.lsb_first(resources, offset + 14, 2)
|
|
offset += 16
|
|
for _i in range(entry_count):
|
|
parse_entry(resources, offset, data_entries)
|
|
offset += 8
|
|
|
|
|
|
func parse_entry(resources: PackedByteArray, offset: int, data_entries: Array) -> void:
|
|
var entry_offset := IconReplacer.lsb_first(resources, offset + 4)
|
|
if entry_offset & 0x80000000:
|
|
parse_table(resources, entry_offset & 0x7fffffff, data_entries)
|
|
else:
|
|
parse_data_entry(resources, entry_offset, data_entries)
|
|
|
|
|
|
func parse_data_entry(resources: PackedByteArray, offset: int, data_entries: Array) -> void:
|
|
data_entries.append(DataEntry.new(resources.slice(offset, offset + DATA_ENTRY_SIZE)))
|
|
|
|
|
|
func print_error(error_message: String) -> void:
|
|
printerr(error_message)
|
|
if error_callable:
|
|
error_callable.call(error_message)
|
|
|
|
|
|
static func lsb_first(bytes: PackedByteArray, offset: int, byte_count = 4) -> int:
|
|
var result := 0
|
|
for i in range(byte_count, 0, -1):
|
|
result = (result << 8) + bytes[offset + i - 1]
|
|
return result
|
|
|
|
|
|
static func replace(bytes: PackedByteArray, replacement: PackedByteArray, index: int) -> PackedByteArray:
|
|
for i in range(replacement.size()):
|
|
bytes.set(index + i, replacement[i])
|
|
return bytes
|
|
|
|
|
|
class SectionEntry:
|
|
const VIRTUAL_ADDRESS_OFFSET := 0x0c
|
|
const SIZE_OF_RAW_DATA_OFFSET = 0x10
|
|
const POINTER_TO_RAW_DATA_OFFSET = 0x14
|
|
|
|
var virtual_address: int
|
|
var pointer_to_raw_data: int
|
|
var size_of_raw_data: int
|
|
|
|
|
|
func _init(bytes: PackedByteArray) -> void:
|
|
virtual_address = IconReplacer.lsb_first(bytes, VIRTUAL_ADDRESS_OFFSET)
|
|
size_of_raw_data = IconReplacer.lsb_first(bytes, SIZE_OF_RAW_DATA_OFFSET)
|
|
pointer_to_raw_data = IconReplacer.lsb_first(bytes, POINTER_TO_RAW_DATA_OFFSET)
|
|
|
|
|
|
class DataEntry:
|
|
const RVA_OFFSET := 0
|
|
const SIZE_OFFSET := 4
|
|
|
|
var rva: int
|
|
var size: int
|
|
|
|
|
|
func _init(bytes: PackedByteArray) -> void:
|
|
rva = IconReplacer.lsb_first(bytes, RVA_OFFSET)
|
|
size = IconReplacer.lsb_first(bytes, SIZE_OFFSET)
|
|
|
|
|
|
class Icon:
|
|
const IMAGE_COUNT_OFFSET := 0x4
|
|
const IMAGES_OFFSET := 0x6
|
|
const ICON_ENTRY_SIZE := 16
|
|
const SIZE_OFFSET := 0x8
|
|
const DATA_OFFSET := 0xc
|
|
|
|
var images := {}
|
|
|
|
|
|
func _init(bytes: PackedByteArray) -> void:
|
|
var image_count := IconReplacer.lsb_first(bytes, IMAGE_COUNT_OFFSET, 2)
|
|
var offset := IMAGES_OFFSET
|
|
for i in image_count:
|
|
var size := IconReplacer.lsb_first(bytes, offset + SIZE_OFFSET)
|
|
var data_offset := IconReplacer.lsb_first(bytes, offset + DATA_OFFSET)
|
|
images[size] = bytes.slice(data_offset, data_offset + size)
|
|
offset += ICON_ENTRY_SIZE
|