Перейти к содержанию
  • записей
    70
  • комментариев
    49
  • просмотров
    4205

О блоге

 

 

Записи в этом блоге

 

[GHIDRA] Спустя 2 недели использования

Ну что ж, вот и прошло 2 недели с момента выхода GHIDRA и хотите верьте, хотите нет, но за все это время у меня не возникло желания вернуться в IDA (даже открыть). Т.е. весь функционал, который дает гидра из коробки в IDA реализован только в плагинах.   Производительность. После релиза я писал статью, где рассказывал о «баге» с производительностью, как мне потом объяснили, что такое возможно на свежеустановленной Java, т.е. после запуска какие-то данные кешируются на жесткий, чтобы при последующих запусках не выдирать их из файлов GHIDRA, так что сейчас с этим все отлично. Ах, да гибкие настройки автоанализа, которые позволяют выбрать что именно нужно анализировать, уменьшить количество потоков, анализирующих файл, чтобы уменьшить нагрузку на систему.   Декомпиляция. С этим что у Hex-Rays, что у GHIDRA все на одном уровне. Где-то вывод хуже в GHIDRA, где-то в IDA. Но для себя я выбрал декомпилятор гидры просто потому, что у меня на руках есть все правила декомпилятора и если, вдруг, что-то где-то выводится неправильно, то я могу поправить это за 5 минут, а не ждать год в лучшем случае, пока китайцы сольют новую версию IDA, где возможно, поправят этот баг. Временами вывод декомпилятора GHIDRA странный, если сравнивать с IDA, но если вдуматься, то он более логичный.   Документация. Еще гидра идет с подробнейшей документаций как по работе с GHIDRA, так и по работе с API. В менеджере скриптов, так же идут различные примеры работы с кодом. И переписывая плагин для ассоциации функций строками, наслаждался эти процессом, из-за того, что в документации написано «чтобы использовать эту апи, вот эту штуку засунь сюда и получишь вот это, с которой можно работать вот так»   Плагины. К моему глубочайшему удивлению за 2 недели комьюнити наделало огромное количество плагинов. Хочешь деобфускатор кода — пожалуйста, хочешь загрузчик switch, wasm, ps4, sega — без проблем, есть какая-то специфичная прошивка для специфичной железяки — читай документацию и без проблем сделаешь для него загрузчик и даже декомпилятор.    И в заключении, сегодня я удалил IDA из-за ненадобности. Да, она была хороша и удобна, когда не было альтернатив, но сейчас у IDA нет какой-то особенной фитчи, которая бы серьезно повлияла на выбор в её пользу.   

partoftheworlD

partoftheworlD

 

[Ghidra] Function String Associate релиз.

Переписал скрипт на гидролад.(я теперь знаю Java через Python 😄)
 Делает все тоже самое, рекурсивный поиск строк в функции и создание комментов.   Установка: Скачиваем файл из  репозитория Добавляем его в менеджер скриптов Ищем среди всех скриптов Запускаем     Надо будет еще по колдовать над форматированием строк, а так вроде все работает. Если что, какие-то баги, пишите сюда или создавайте репорт на гитхабе.

partoftheworlD

partoftheworlD

 

Импорт баз данных IDA в GHIDRA

Вместе с гидрой в папке ${GHIDRA}/Extensions/IDAPro/ идет плагин для IDA, для импорта и экспорт баз в виде XML.     Открываем плагин, выбираем, что нужно экспортировать и ждем завершения процесса.   На выходе получаем базу в виде XML файла(со всеми сохранными данными) и просто переносим его в окно GHIDRA. Происходит импорт.     ...        

partoftheworlD

partoftheworlD

 

[GHIDRA] Первые ощущения

Ну что ж, все ощущения буду сравнивать с IDA. Дизассемблер рабочий, со странными "горячими" клавишами, сканирование проходит быстрее.     Декомпилятор работает на уровне с Hex-Ray, в некоторых моментах даже лучше. Автоматическое создание структур из коробки это действительно круто. Все что делается в IDA плагинами, в гидре есть из коробки, очень подробная документация к API, скрипты можно писать на Python и Java. О дааа, гидра умеет в различные типы, чего ида до сих пор не могет.     Сравнение декомпиляторов(низ ида, верх гидра)   Сверх удобная работа с сигнатурами функций   В ида это выглядит куда хуже.   Графическое представление функций, все же я выберу гидру, графики выглядят не так скомкано, временами это мешает сфокусироваться на отдельном блоке.   Так же у гидры удобное отображение функций при использовании классов. К примеру, в аргументах к функции используется созданная структура, гидра позволяет видеть в каких функциях она использована. Встроенный экспорт заголовков с созданными структурами, очень радует.   А вот так выглядит ручное создание классов. Создание классов так же поддерживает пространство имен, что удобно при реверсе, чтобы не создавать из одного класса помойку.   Одна из очушенных фитч, это сравнение функций, берем 2 или более функции и сравниваем, удобно бороться с самодельными пакерами имеющий ловушки. Так же поддерживается сравнение функций в разных файлах, что-то типа встроенного BinDiff.   Из минусов(временных) отсутствие поддержки темной темы и FLIRT анализа.   В заключение, мне кажется разработчикам иды придется очень-очень сильно постараться, либо снижать цены очень сильно, чтобы люди не уходили на бесплатный продукт с аналогичным функционалом. После переезда иды на новый API, ситуация с плагинами у неё не самая лучшая, но тут гидра выигрывает встроенным функционалом. Сейчас для меня гидра выигрывает почти по всем параметрам, пореверсим что-нибудь рабочее и посмотрим как себя поведет программа, какие проблемы могут быть и про это будет отдельная статья через 1-2 недели.  

partoftheworlD

partoftheworlD

 

Нахождение функции трассировки лучей

Начнем с теории, для того чтобы, что-то реверсить, надо знать как это работает, для этого стоит почитать вот эту книжку, а так же понять устройство функции выстрела.
Для этого нарисовал небольшую схему, описывающую в общих чертах, что происходит под капотом при нажатии ЛКМ в игре.     Как видим на схеме, необходимо проделать все тоже, что делали в прошлой статье по нахождению разброса, на данном этапе необходимо найти функцию рисующую дырки от пуль, по деловому она называется FX_FireBullet.   Если мы попали в функцию "Создания следов от выстрелов", остается только дебажить. В итоге внутри вложенной функции, внутри FX_FireBullet мы найдем такой код.  
  Он может отличаться, количество аргументов быть больше/меньше. Но вот какие аргументы принимала функция:   Координаты начальной позиции:     Координаты конечной позиции:   3-й аргумент маска. Маска задает магической функции создания луча, какие текстуры луч должен игнорировать, а с какими работать.
4-й аргумент фильтр трассировки 5-й аргумент булева переменная, указывающая, должна ли функция использовать группу коллизий. 6-й аргумент буфер, для вывода результата магии.   Вот какие значения получаем на выходе.     Красным выделено, то ради чего весь этот поиск затеян, это значение является чем-то типа процента отражения луча, обычно это значение от 0 до 1. На отражение так же влияет маска и если мы её подправим, отключив при трассировке все лишнее, оставив только модельку игрока, то возвращаемое значение fraction будет рано 0.95-1 если видим противника и можем попасть, и 0 если что-то мешает.     Ах да, совсем забыл, эта функция обычно находится в цикле for, просто для того, чтобы вызываться и рассчитывать разброс, дырки от пуль, трассировку каждый выстрел. В некоторых играх попадаются интересные реализации, т.е нет какой-то определенной функции работающей с трассировкой, но зато есть методы работающие с игровыми скриптами.     Этот метод ищет функцию по её названию и вызывает, а внутри этого метода находятся функции трассировки, но работают они по-разному в зависимости от передаваемого имени, а видимость задается 0 и 1, которое функция возвращает.  

partoftheworlD

partoftheworlD

 

Отключение разброса на уровне рандомизации значений

Обнуление прицела это конечно хорошо и просто, но часто не работает с оружием у которого нет прицела. Для начала необходимо найти адрес работающий с перекрестием. Бряк на доступ. Делаем выстрел.     1 выстрел 1 срабатывание инструкции, то что нужно. Открываем дизассемблер и разбираемся что тут происходит. Я уже все декомпилировал.     Готово, после реверса становится ясно, что тут у нас создаются значения, которые задают разброс оружию.   rng_init возвращает значение размером в 2 байта, мне кажется это seed (число используемое для инициализации генератора псевдослучайных чисел)   rng_flt возвращает значение float, первым аргументом задается seed, вторым минимально возможное значение, третьим максимально возможное.     В принципе тут все, чтобы разброса не было, можно либо отключить проверку, либо, при условии запрета на инжект перехватывать seed, для генерации в собственном чите, чтобы компенсировать разброс.      Или ноускопить как млг про     Но на этом не все, прелесть данного способа в том, что, если мы нашли функцию инициализирующую генератор псевдослучайных чисел, мы можем управлять всем, что только работает с псевдорандомом, например выпадение лута, движение ветра, шанс крафта, успешность взлома и т.д. Только посмотрите, сколько здесь возможностей для взлома, используя псевдорандом.  

partoftheworlD

partoftheworlD

 

CrySearch <3

Сегодня поговорим про CrySearch и причины по которым я его выбрал.   1. Чистый код. Если сравнить репозитории CE и CrySearch вы поймете про что я.   2. Скорость сканирования. После того как я впервые воспользовался CrySearch для поиска неизвестного, я удивился и не мог поверить, что поиск может занимать так мало времени.   3. Встроенные защиты от анти-читов. Рандомные заголовки, разные методы внедрения, чтения, записи в память, открытия потока, запрет на чтение физически не поддерживаемой памяти.
  4. Все под рукой.   Посмотрите на интерфейс, в нем нет ничего лишнего, а если что-то мешает, всегда можно отключить.     5. Интересный TODO лист   Много чего интересного ожидается, т.е автор собирается добавлять действительно нужные плюшки для взлома, а не делать из программы для сканирования памяти комбайн, который вроде бы и работает, но часть функционала сделана слишком не удобно для использования.        

partoftheworlD

partoftheworlD

 

Создание Manjaro base образа для Docker

Для начала необходимо запустить демон докера: systemctl start docker  Создаем rootfs mkdir ~/Desktop/rootfs && cd ~/Desktop/ Если это делается на рабочей системе, а не с live cd, то необходимо установить pacstrap pacman -S arch-install-scripts   pacstrap -cdGM rootfs filesystem pacman Делаем различные настройки, типа cp /etc/pacman-mirrors.conf ~/Desktop/rootfs/etc/pacman-mirrors.conf  Импортируем созданный образ в докер.  tar -C rootfs -c . | docker import - manjaro_docker   Запускаем   docker run -it manjaro_docker /bin/bash   Необходимо будет установить ключи для пакмана  pacman-key –-init pacman-key --populate archlinux manjaro  И зеркала:  pacman-mirrors -c Russia Выходим.  Принимаем изменения docker commit ${containerid} manjaro_dk И используем образ в Dockerfile FROM manjaro_dk  RUN pacman -Syyuu --noconfirm base-devel RUN pacman -Scc –noconfirm …     Экспорт: docker export NAME | gzip > NAME.gz  

partoftheworlD

partoftheworlD

 

Определение signed и unsigned модификаторов переменных в дизассемблере

Знание модификатора переменной на этапе реверса поможет найти проблемы в коде или новый функционал для читов.  Для примера, если программист не указал модификатор переменной, то компилятор решит использоваться signed по стандарту, это может привести к проблемам, например к целочисленному переполнению, если в коде, не будет соответствующей проверки. Для начала посмотрим на таблицу, чтобы понять в чем разница.     Как вы видите, разница в диапазонах. А теперь посмотрим на примере одного и того же кода на c++, различие в инструкциях signed и unsigned модификаторов.   Signed | Unsigned push rbp | push rbp mov rbp,rsp | mov rbp,rsp mov DWORD PTR [rbp-0x4],edi | mov DWORD PTR [rbp-0x4],edi cmp DWORD PTR [rbp-0x4],0x62 | cmp DWORD PTR [rbp-0x4],0x62 jg 4004cc <test_signed(int)+0x1a> | ja 4004c9 <test_signed(unsigned int)+0x17> mov eax,DWORD PTR [rbp-0x4] | mov eax,DWORD PTR [rbp-0x4] lea edx,[rax+0x32] | add eax,0x19 mov eax,DWORD PTR [rbp-0x4] | add eax,eax add eax,edx | jmp 4004c9 <test_signed(unsigned int)+0x17> jmp 4004cc <test_signed(int)+0x1a> | pop rbp pop rbp | ret ret | Первое это конечно же условные прыжки: jg, jge, jl, jle = прыжки основанные на signed сравнении (Они проверяют SF флаг) ja, jae, jb, jbe = прыжки основанные на unsigned сравнении (Они проверяют CF флаг) Второе инструкции( на примере умножения и деления)   imul + idiv = signed mul + div = unsigned Это примеры для самых основных команд, которые встречаются чаще всего, хотите больше? Добро пожаловать в документацию .   На практике это дает возможность, делать отрицательное/огромное значение, там где не должно их быть. И в случае невнимательности программистов, что-то пойдет не так, но будет весело.

partoftheworlD

partoftheworlD

 

Обход проверки целостности (программный метод)

Однажды я уже рассказывал как обойти проверку целостности руками, но это долго, нудно, да и кому вообще надо?!
Для начала необходимые утилиты: x64dbg, CrySearch/CE, любой компилятор.   Нашли адрес отвечающий за патроны, ставим на него бряк на запись, выходи на пишущую инструкцию, ставим бряк на доступ размером 1 байт в памяти на эту инструкцию и ждем срабатывания. После срабатывания в заголовке x64dbg будет написан ID потока.     Переходим во вкладку "Потоки", здесь нам необходим только адрес начала:     Теперь переходим к автоматизации, вся суть этого обхода в получении всех потоков и сравнении адреса начала, т.е если адрес начала равен 0x7FF730E2449D, то убиваем поток. Вот и весь обход.   if (reinterpret_cast<DWORD64>(thread->Win32StartAddress) == (reinterpret_cast<DWORD64>(mainModule) + 0x4490)) { HANDLE hThread = OpenThread(THREAD_TERMINATE, FALSE, threadId); TerminateThread(hThread, 1); CloseHandle(hThread); }   0x4490 это относительный виртуальный адрес адреса начала потока.      

partoftheworlD

partoftheworlD

 

[Star Wars: Battlefront] ESP правкой пары байт.

Ну что ж, я отлично отдохнул и самое время для статьи.   Для начала нам необходимо создать локальный матч, обязательно экипировать бинокль(он как раз и создает эту обводку) и можно приступать. Ищем все как по учебнику - неизвестное 4 байта.  Находим адрес со значением имеющим F1, это обозначает красный цвет обводки:       Теперь нам необходимо восстановить указатель, чтобы после обнов не обновлять чит каждый раз.   ====================================================================================   Получаем указатель вида CWorld->pGameContext ->Entity List + id * 8] -> pSolderVehicleVisuals -> 0x04B5. Пишем перебор всех игроков, проверку по номеру команды и добавляем подсветку.  В итоге, получаем чит в 9 строк.   И конечно же, результат.    

partoftheworlD

partoftheworlD

 

Боль и ненависть восстановления винды

В общем, сегодня расскажу историю о том как я восстанавливал систему имея на руках только загрузчик образов Acronis и старый телефон.   Представьте страшный сон сис.админа, когда уничтожается весь жесткий диск с бекапами и образами систем. В итоге компьютер становится гробом собирающим пыль.     Первое, что пришло в голову это скачать tib образ из облака, включив торрент на телефоне и скачав 4 гб бекапа из 8 начали появляться ошибки, потом вспомнил, что все MicroSD карты форматрованны в FAT, где размер 1 файла может быть не больше 4 гб. Искал бекапы под w10/8.1/7 и подошли по размеру только бекапы win хр, самые свежие за 15 год, из 30 бекапов получилось скачать только 2, никто не раздаёт, а те, которые получилось скачать невозможно было запустить, в системе было все на столько сильно вырезано, что отваливалось абсолютно все. Так я промучился около 1.5 суток. Единственное решение, которое было возможно сделать - отключить все драйвера и запустить систему в безопасном режиме, но тогда я не знал, работают ли вообще программы в этом режиме. После того как удалось успешно запустить WIndows XP, начал качать образ ubuntu и UltraISO, но после записи обнаружилось, что записывая загрузочные флешки Linux , через эту программу, флешки не будут запускаться, а других программ для создания загрузочных флешек я не нашел. А дальше, старый смартфон на андройде, плохой интернет, загрузка x32 образа винды, чтобы после получить доступ к современным программам и записать x64 винду и наконец-то восстановить систему.   В общем, флешек много не бывает, как и внешних жестких дисков. Лучше раскидать флешки по углам, чем потом придумывать такие извращения.

partoftheworlD

partoftheworlD

 

[x64dbg] Снятие дампа с инструкции для самых маленьких

Итак, для дампа будем использовать точку останова с условием, с выводом определенных типов для нужных нам регистров.     Условие остановки: представляет из себя условие при котором будет срабатывать бряк, так как мы не знаем количество, функций которые имеет игра, то установим чуть меньше максимального количества функций в IDA. Текст журнала: Это то, что будет выводится в журнале во время остановки. Формат вывода такой {тип: регистр}. Весь список типов и пример использования:
    Условие добавление в журнал: Говорит само за себя. Текст команды: Это то, что будет выполняться после остановки бряка, на скрине указана команда go - тоже самое что нажать F9. Так же команд может быть несколько, каждая последующая добавляется со знаком точка с запятой. (go; StepOut) Условие для команды: Условие для выполнения команды. Счетчик остановок: Просто счетчик, к значению которого можно обратиться через $breakpointcounter   Демо:   Ссылка на док: https://x64dbg.readthedocs.io/en/latest/introduction/ConditionalBreakpoint.html

partoftheworlD

partoftheworlD

 

[Reverse Thread] Metal Gear Solid V: The Phantom Pain

Думаю, стоит начать создавать подобные темы на ковыряемые игры участниками форума, чтобы каждый мог внести вклад в будущее разработки трейнеров/читов конкретной игры, объединив усилия при поиске и реверсе, и конечно же, узнать что-то новое,а так же воспользоваться этим функционалом. Так как решил сделать Silent Aim для этой игры, начнем пожалуй с неё. Тема будет обновляться, если есть желание помочь, пишите комментарии с найденными плюшками, они так же появятся в этой теме.   Base_ptr : "\xE8\x00\x00\x00\x00\x48\x8B\x05\x00\x00\x00\x00\x48\x85\xC0\x48\x0F\x44\xC3", "x????xxx????xxxxxxx" + 8 CWeapon : [Base_ptr] CWeapon_vftable : [Base_ptr] + 0x330     Определение адресов интерфейсов игры: Скрипт использует адреса перекрестных ссылок функции CreateInterface, чтобы получить все функции, которые создают интерфейсы, после скрипт получает адрес функции, декомпилирует, регулярками чистим вывод декомплиятора, передаем имя интерфейса и его адрес функции переименовывающей адреса. (в скрипте перекрестные ссылки в виде массива, просто потому что они статичные)   И список всех интерфейсов:   Игровые функции и lua методы (дамп):   Reclass: metal_gear.rcnet Игровые скрипты: Scripts.7z (позже будет отформатированная версия)  

partoftheworlD

partoftheworlD

 

[Разное] Почитать на вечер.

Пока искал информацию о переполнении кучи, нашел серию статей рассказывающую о проблемах, которые могут быть на уровне приложений. Статьи не большие, но с достаточно подробным описанием и примерами.

http://etutorials.org/Networking/network+security+assessment/Chapter+13.+Application-Level+Risks/

partoftheworlD

partoftheworlD

 

[Pwnie Island: Pwn Adventure 3] Pirate's Treasure

Это было великолепное задание, я прям удовлетворен. Советую всем попробовать эту игру, это отличная практика, где можно попробовать свои силы в разных сферах. От поиска и изменения значений до понимания работы прокси сервера, перехвата пакетов, модификации, генерации ключей и целочисленного переполнения.    

partoftheworlD

partoftheworlD

 

[IDA PRO 7] Расширенная фильтрация инструкций по стеку вызовов

Ничего нового, просто из-за ненадобности решил поделится скриптом, который поможет многим в анализе стека вызовов. Кому хочется искать где пишется и используется какой-либо адрес или переменная. Достаточно сделать бряк, скопировать адреса функций из стека вызовов и указать необходимую инструкцию.    Например, нам необходимо узнать что происходить с ebp+arg_0 по ходу выполнения кода десятка  функций по сотне инструкций в каждой.   Вставляем в скрипт адреса из стека вызовов:     Вставляем то, что нужно найти: ebp+arg_0   И в выводе получаем:   Addr | Instruction | ---------------------------------------------- ---------------------------------------------- 0x88B59 -- mov eax, [ebp+arg_0] ---------------------------------------------- 0x2DCA16 -- mov ecx, [ebp+arg_0] 0x2DCA59 -- push [ebp+arg_0]; void * 0x2DCA71 -- movss xmm0, [ebp+arg_4] 0x2DCA9E -- movss xmm0, [ebp+arg_4] ---------------------------------------------- 0x2DC8BC -- push [ebp+arg_0] 0x2DC8DB -- mov ebx, [ebp+arg_0] ---------------------------------------------- 0x1D6D3A -- mov esi, [ebp+arg_0] 0x1D6ED6 -- mov esi, [ebp+arg_0] ---------------------------------------------- 0x1D6F2A -- mov ebx, [ebp+arg_0] 0x1D6F5F -- cmp [ebp+arg_4], 0 0x1D6F87 -- mov eax, [ebp+arg_4] 0x1D717C -- mov byte ptr [ebp+arg_4+3], cl 0x1D71CA -- cmp byte ptr [ebp+arg_4+3], 22h ---------------------------------------------- или например при поиске скорострельности, ставим бряк на адрес инструкции отнимающей патроны, вставляем стек вызовов и фильтруем:   Addr | Instruction | ---------------------------------------------- 0x21068F -- ucomisd xmm1, xmm0 0x21073A -- comiss xmm0, xmm1 0x210792 -- comisd xmm1, xmm0 0x2108EF -- comisd xmm5, xmm0 0x210B4F -- comiss xmm0, ds:flt_4DC2A8 0x211366 -- comisd xmm1, xmm0 0x211478 -- comiss xmm0, xmm1 0x2114FC -- comisd xmm0, [ebp+var_50] 0x211885 -- comiss xmm0, xmm1 0x211915 -- comisd xmm0, ds:dbl_4DC5E8 ---------------------------------------------- 0x222437 -- comiss xmm0, xmm1 0x222452 -- comiss xmm1, xmm0 0x22247E -- comiss xmm1, xmm0 ---------------------------------------------- 0x277321 -- comiss xmm0, ds:dword_4DBFC8 0x2773A5 -- comisd xmm1, qword_895F18 0x277422 -- comiss xmm1, xmm0 0x277446 -- comiss xmm0, xmm1 ---------------------------------------------- Бам, как 2 пальца почесать.   Ссылка на скрипт: https://github.com/partoftheworlD/IDA7_IntructionFilter   В планах написать интерфейс, а то без него как-то не круто. Так же будет подобный плагин для radare2, но позже.

partoftheworlD

partoftheworlD

 

[Alpha protocol] Управление скоростью показа диалогов. Часть 2. Исследование+

Статьи придется растянуть ещё на 1-2 т.к. я свалился с температурой. Но сегодня не об этом, для начала нам необходимо вспомнить структуру построения диалогов.   [B01_P_INTRO_01_000 SoundNodeWave] SpokenText="Ты очнулся, очень хорошо - я не знала, сколько ты проваляешься. Эти транквилизаторы в конце концов перестали действовать." Comment="NOTES[], TRIGGER[], GLOSSARY[]" bMature=False Subtitles[0]=(Text="Ты очнулся, очень хорошо - я не знала, сколько ты проваляешься. Эти транквилизаторы в конце концов перестали действовать.") Необходимо обратить внимание на идентификатор, а точнее на SoundNodeWave, в прошлой статье я забыл указать, что это класс UE3, по сути это звуковой узел, работающий с локализацией, который содержит данные об воспроизводимом файле, а также его характеристиках.   Но для нашей цели, а именно увеличения времени показа субтитров, надо обратить внимание на массив Subtitles, а дальше просто, посмотреть в документацию.     смотрим на структуру array<SubtitleCue>     Линия текста субтитров и время, которое субтитры должны отображаться. Из примера выше:   Text="Ты очнулся, очень хорошо - я не знала, сколько ты проваляешься. Эти транквилизаторы в конце концов перестали действовать."   Вот мы и нашли способ для увеличения времени показа субтитров, имея на руках только гугл и файлы локализации. Да, он относительно долгий (нам надо будет переписать все субтитры, а их там 598 файлов). Можно конечно скрипт написать, который подправит все, но должен же быть способ проще. Но насколько я понял по локализации, то проблема в быстрых субтитрах именно в ней, в нормальной английской версии, субтитры разделены на группы, а в русской все в одной строке.   Eng: [B01_P_INTRO_01_000 SoundNodeWave] SpokenText="Good, you're awake - I wasn't sure how long you'd be under. Those tranquilizers wore off fast." Subtitles[0]=(Text="Good, you're awake - ") Subtitles[1]=(Text="I wasn't sure how long you'd be under. ") Subtitles[2]=(Text="Those tranquilizers wore off fast.") bManualWordWrap=True Comment="NOTES[], TRIGGER[], GLOSSARY[]" bMature=False Rus: [B01_P_INTRO_01_000 SoundNodeWave] SpokenText="Ты очнулся, очень хорошо - я не знала, сколько ты проваляешься. Эти транквилизаторы в конце концов перестали действовать." Comment="NOTES[], TRIGGER[], GLOSSARY[]" bMature=False Subtitles[0]=(Text="Ты очнулся, очень хорошо - я не знала, сколько ты проваляешься. Эти транквилизаторы в конце концов перестали действовать.") В следующей статье, мы будет находить объект SoundNodeWave, реверсить его структуру, восстанавливать в ReClass и писать скрипт в CE, который будет подсовывать в массив SubtitleCue указанное нами время. Или оставить это моим читателям как домашнее задание?

partoftheworlD

partoftheworlD

×

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.