Source

dfrus / dfrus034.exw

-- Версия патча для DF 0.34.*

include std/console.e
include std/filesys.e
include std/search.e
include std/get.e
include std/sequence.e
include std/map.e
include std/convert.e

-- with trace
include patcher.e
include pe.e
include disasm.e

global integer debug = 0
include patchdf.e

sequence path = ""
sequence cmd = command_line()

debug = find("debug",cmd)
if debug then
    cmd = remove(cmd,debug)
    debug = 1
end if

if length(cmd)>2 then
    path = cmd[3]
end if

constant df1 = path & "Dwarf Fortress.exe"
constant df2 = path & "Dwarf Fortress Rus.exe"
atom fn, pe_header

-----------------------------------------------------------------------------
constant DF_0_34_01_SDL_stamp = #4F391A33 -- Value got from http://dwarffortresswiki.org/index.php/DF2012:Memory_hacking
fn = open_pe(df1)
if fn < 0 then
    printf(1,"\nUnable to open \"%s\"\n",{df1})
    any_key()
    abort(1)
end if

puts(1,"Checking timedate stamp... ")
pe_header = check_pe(fn)
constant timedate = fpeek4u(fn, pe_header+PE_TIMEDATE_STAMP)
close(fn)
if timedate < DF_0_34_01_SDL_stamp then
    puts(1,"\nWrong timedate stamp. The patcher works with DF 0.34.1 SDL and later versions.\n")
    any_key()
    abort(1)
else
    puts(1,"OK\n")
end if

-- @todo: Добавить проверку присутствия SDL.dll в импорте

-----------------------------------------------------------------------------
puts(1,"Loading translation file...\n")

constant trans_filename = "trans.txt"
object trans = load_trans_file_to_map(trans_filename)
if atom(trans) and trans=-1 then
    printf(1,"Failed.\n%s file not found.\n",{trans_filename})
    any_key()
    abort(-1)
else
    printf(1,"%d strings loaded.\n", map:size(trans))
end if

-----------------------------------------------------------------------------
printf(1,"Copying \"%s\"\nTo \"%s\"...\n", {df1,df2})

if not copy_file(df1,df2,1) then
    puts(1,"Failed.\n")
    any_key()
    abort(1)
else
    puts(1,"Success.\n")
end if

-----------------------------------------------------------------------------
puts(1,"Finding cross-referencess...\n")

fn = open_pe(df2)
if fn < 0 then
    printf(1,"Failed to open \"%s\"", {df2})
    delete_file(df2)
    any_key()
    abort(1)
end if

pe_header = check_pe(fn)
if pe_header < 0 then
    printf(1,"Failed. \"%s\" is not an executable file.")
    close(fn)
    delete_file(df2)
    any_key()
    abort(1)
end if

constant
    image_base = fpeek4u(fn, pe_header+PE_IMAGE_BASE),
    sections = get_section_table(fn, pe_header)

-- Получаем адреса всех перемещаемых элементов:
sequence
    relocs = get_relocations(fn,sections),
-- Получаем перекрёстные ссылки:
    xref_table = get_cross_references(fn,relocs,sections,image_base), -- @todo: заменить на get_cross_references_to_map()
    objs  = xref_table[1],
    xrefs = xref_table[2]

-----------------------------------------------------------------------------
puts(1,"Enabling the cyrillic alphabet...\n")

-- Первые несколько записей в таблице перкодировки:
constant unicode_table_start = { #20, #263A, #263B, #2665, #2666, #2663, #2660, #2022 }

-- Находим таблицу перекодировки:
atom off = 0
sequence buf
for i = 1 to length(objs) do
    buf = fpeek4u(fn, {objs[i],length(unicode_table_start)})
    if equal(buf, unicode_table_start) then
        off = objs[i]
        exit
    end if
end for

if not off then
    close(fn)
    puts(1,"Unicode table not found.\n")
    any_key()
    abort(-1)
end if

patch_unicode_table(fn, off)

-----------------------------------------------------------------------------
-- puts(1,"Preparing additional data section...\n")
-- Подготовка дополнительной секции данных

constant
    file_alignment = fpeek4u(fn,pe_header+PE_FILE_ALIGNMENT),
    section_alignment = fpeek4u(fn,pe_header+PE_SECTION_ALIGNMENT)

-- "Прототип" новой секции:
sequence new_section = {
    ".rus",
    0, -- Virtual Size -- виртуальный размер пока не известен
    align(sections[$][SECTION_RVA]+sections[$][SECTION_VSIZE],
        section_alignment), -- RVA -- выровнять под #1000
    0, -- Phisical Size -- физический размер пока не известен
    align(sections[$][SECTION_POFFSET] + sections[$][SECTION_PSIZE],
        file_alignment), -- Phisical Offset -- Выровнять под #200
    0,0,0,0, -- reserved
    IMAGE_SCN_CNT_INITIALIZED_DATA + IMAGE_SCN_MEM_READ + IMAGE_SCN_MEM_EXECUTE -- readable code/data
}

atom new_sect_off = new_section[SECTION_POFFSET] -- Смещение в новой секции
integer aligned = 0

-----------------------------------------------------------------------------
puts(1,"Translating...\n")

function second(sequence s)
    return s[2]
end function

sequence strings = extract_strings(fn, objs)
if debug then
    printf(1,"%d strings extracted.\n", length(strings))
    if length(cmd)>=5 then
        integer lo=second(value(cmd[4])), hi=second(value(cmd[5]))
        if lo <= 0 or lo > length(strings) then
            lo = 1
        end if
        if hi <= 0 or hi > length(strings) then
            hi = length(strings)
        end if
        strings = strings[lo..hi]
        printf(1,"%d %d %d\n",{lo,floor((lo+hi)/2),hi})
        printf(1,"From %x to %x total %d\n",{strings[1][1], strings[$][1], length(strings)})
    end if
end if

for i = 1 to length(strings) do
    object translation = map:get(trans, strings[i][2])
    if atom(translation) then
        -- if debug then
            -- printf(1,"Translation not found for \"%s\".\n", {strings[i][2]})
        -- end if
        continue -- перевод не найден
    end if
    if equal(strings[i][2], translation) then
        continue -- перевод равен оригиналу
    end if
    -- Поиск строки в таблице объектов:
    integer k = binary_search(strings[i][1], objs)
    sequence s = xrefs[k] -- список ссылок на данную строку
    
    -- Находим ссылки на середину строки
    integer l = 1
    while objs[k+l]-strings[i][1] < length(strings[i][2])+1 do
        for j = 1 to length(s) do
            integer delta =  s[j] - xrefs[k+l][1]
            if length(xrefs[k+l]) = 1 and delta>0 and delta<=6 then
                s[j] = xrefs[k+l][1]
            end if
        end for
        l += 1
    end while
    
    integer aligned_len = align(length(strings[i][2])+1) -- Максимальная длина "короткой" строки
    integer long = aligned_len<length(translation)+1
    integer str_off
    if not long then
        -- Если строка достаточно короткая, то записать ее поверх старой:
        fpoke(fn, strings[i][1], pad_tail(translation & 0, aligned_len, 0)) -- назадействованные байты заполнить нулями
        aligned = 0
    else
        -- Если строка длинная, то записать ее в специально отведенную для этого секцию:
        aligned = align(length(translation)+1)
        fpoke(fn, new_sect_off, pad_tail(translation, aligned, 0))
        str_off = new_sect_off
        new_sect_off += aligned
    end if
    
    -- Исправляем длину строк по каждой из ссылок на нее
    for j = 1 to length(s) do
        -- Исправить длину строки:
        object fix = fix_len(fn, s[j], length(strings[i][2]), length(translation))
        if sequence(fix) then -- Функция наткнулась на jmp и вернула машинный код указания длины и адрес перехода
            -- integer disp = 
            
        elsif fix != 0 then -- Удалось исправить длину, или исправление не требуется
            if fix = -2 and debug then -- не удалось исправить длину, но скорее всего нужно
                printf(1,"|%s|%s| <- %x (%x)\n",
                    {strings[i][2], translation} & s[j] & ( off_to_rva_ex(s[j], sections[1])+image_base ) )
                puts(1, "SUSPICIOUS: Failed to fix length.\n")
            end if
            if long then -- Если строка длинная, то исправить ссылку на строку в коде:
                fpoke4(fn, s[j], off_to_rva_ex(str_off, new_section)+image_base)
            end if
        elsif long then -- Если не удалось исправить длину:
            -- Считываем 3 байта перед ссылкой для нахождения начала копирующего кода
            sequence pre = fpeek(fn, {s[j]-3, 3})
            -- Находим начало копирующего кода:
            integer start = s[j]-get_start(pre)
            -- Получаем длину кода, копирующего строку:
            object x = get_length( fpeek(fn, {start, 100}), length(strings[i][2])+1)
            
            if sequence(x) then
                -- Получаем машинный код, копирующий строку:
                sequence mach = mach_memcpy(off_to_rva_ex(str_off, new_section)+image_base, x[2],
                                                                            length(translation)+1)
                integer new_ref_off = mach[$]
                mach = mach[1..$-1]
                -- Добавляем в машинный код затираемое lea, т.к. значение может использоваться ниже по коду:
                if sequence(x[4]) then
                    mach &= lea(x[4][1],x[4][2..3])
                end if
                integer start_rva = off_to_rva_ex(start, sections[1]) -- RVA начала изменяемого кода
                integer new_ref -- Адрес новой ссылки
                if length(mach) > x[1] then
                    -- Полученный машинный код не умещается на первоначальное место
                    -- Прописываем его в отдельную секцию
                    -- mach &= RET_NEAR -- Превращаем сгенерированный код в процедуру
                    -- fpoke(fn, new_sect_off, mach)
                    -- aligned = align(length(mach))
                    -- -- По первоначальному местоположению копирующего кода пропишем вызов процедуры
                    -- mach = CALL_NEAR & int_to_bytes(new_ref_off-(start+5))
                    -- new_ref = off_to_rva_ex(new_sect_off, new_section)+new_ref_off
                    -- new_sect_off += aligned
                    if debug then
                        printf(1,"|%s|%s| <- %x (%x)\n",
                            {strings[i][2], translation} & s[j] & ( off_to_rva_ex(s[j], sections[1])+image_base ) )
                        printf(1, "Mach code is too long (%d against %d).\n",{length(mach),x[1]})
                    end if
                    continue -- полученный машинный код не уместился
                else
                    new_ref = start_rva+new_ref_off
                end if
                mach = pad_tail(mach, x[1], NOP) -- добиваем освободившиеся байты пустыми командами
                fpoke(fn, start, mach) -- патчим код!
                -- Исправляем битые релокации --
                x[3] += start_rva-1 -- получаем адреса удаленных ссылок
                integer mod_reloc = modify_relocations(fn, sections, (-x[3]) & new_ref)
                if mod_reloc != 0 and debug then
                    printf(1,"|%s|%s| <- %x (%x)\n",
                        {strings[i][2], translation} & s[j] & ( off_to_rva_ex(s[j], sections[1])+image_base ) )
                    printf(1, "Failed to fix relocations (Error code %d).\n",mod_reloc)
                end if
            elsif debug then
                printf(1,"|%s|%s| <- %x (%x)\n",
                    {strings[i][2], translation} & s[j] & ( off_to_rva_ex(s[j], sections[1])+image_base ) )
                printf(1,"Failed to obtain copying code length (Error code %d).\n",x)
            end if
        end if
    end for
end for

-----------------------------------------------------------------------------
-- Физическое добавление новой секции

-- Создавать новую секцию только при ненулевом ее размере:
if new_sect_off > new_section[SECTION_POFFSET] then
    -- Выровнять физический размер секции и файла под #200
    atom file_size = align(new_sect_off, file_alignment)
    -- Указываем физический размер секции:
    new_section[SECTION_PSIZE] = file_size - new_section[SECTION_POFFSET]
    
    puts(1,"Adding new data section...\n")
    
    -- Выровнять размер файла:
    seek(fn,file_size-1)
    puts(fn,0)

    -- Виртуальный размер секции, выравнивать не нужно:
    new_section[SECTION_VSIZE] = new_sect_off - new_section[SECTION_POFFSET] 

    -- Записать информацию о секции: 
    put_section_info(fn,
        pe_header + SIZEOF_PE_HEADER + length(sections)*SIZEOF_IMAGE_SECTION_HEADER,
        new_section)

    -- Исправить значение поля количества секций:
    fpoke2(fn, pe_header + PE_NUMBER_OF_SECTIONS, length(sections)+1)

    -- Изменить поле ImageSize PE-заголовка с учетом новой секции и выровнять под #1000:
    fpoke4(fn, pe_header + PE_SIZE_OF_IMAGE,
        align(new_section[SECTION_RVA] + new_section[SECTION_VSIZE],
            section_alignment))
end if

close(fn)

puts(1,"Done.\n")
any_key()