Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
dconstruct — Дисассемблер и экспериментальный декомпилятор для скриптов TLOU2 DC. | Kitploit
Инструменты/GitHubGitHub/deepquantum/dconstruct
Статический анализОбратная инженерияОтладчикиАнализ Бинарных ФайловАнализ Прошивок
GitHubdeepquantum/dconstruct

dconstruct

Дисассемблер и экспериментальный декомпилятор для скриптов TLOU2 DC.

Репозиторий
23241 месяц назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Version License: CC BY-NC-ND 4.0

dconstruct

Анимация полной дизассембляции и декомпиляции

Открыть исходный MP4

dconstruct — это инструмент реверс-инжиниринга для файлов DC-Script, используемых в The Last of Us Part II. Он включает дизассемблер и декомпилятор.

Он выводит файлы .asm, содержащие дизассемблированные структуры и байт-код, а также файлы .dcpl (DC Pseudo Language) с C-подобным псевдокодом.

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

Основные возможности

[]= ``` - ``: Адрес памяти структуры, которую вы хотите отредактировать (в шестнадцатеричном формате, начиная с `0x`. Проще всего скопировать его из дизассемблированной версии файла, который вы хотите отредактировать). - ``: Индекс переменной-элемента внутри структуры. Равен числу, которое вы видите слева от элемента. - ``: Новое значение, которое будет присвоено этому элементу. Должно быть того же размера. (целые числа и числа с плавающей запятой имеют размер 4, sids/структуры — размер 8). Программа НЕ будет проверять, что структуры относятся к одному типу.
  • Оптимизирован для скорости. Дизассемблирование и декомпиляция файлов выполняются молниеносно.
  • Точная реконструкция исходного кода, особенно контроль потока выполнения.
  • Точная автоматическая интерпретация всех структур в дизассемблере.
  • Одновременное дизассемблирование и декомпиляция нескольких файлов. Декомпиляция каждого отдельного файла .bin в игре занимает всего несколько секунд.
  • Внесение изменений с помощью флага -e, создание новых файлов для использования в модах.
  • Загрузка пользовательских sidbas'ов для использования в дизассемблировании.
  • Как использовать

    Сначала рекомендуется переместить распакованную директорию dconstruct в безопасное место, например C:\Program Files.

    Чтобы сделать dconstruct максимально удобным в использовании, рекомендуется добавить каталог .\bin внутри папки dconstruct в ваш PATH. Подробнее можно узнать здесь или выполнить следующие быстрые шаги:

    • Зайдите в строку поиска Windows и введите «переменные среды», вы должны получить вариант «Изменение системных переменных среды».
    • После выбора этого варианта должно появиться окно с заголовком «Свойства системы». Над кнопками «ОК», «Отмена» и «Применить» вы увидите кнопку «Переменные среды...».
    • После нажатия этой кнопки появится ещё одно окно. Здесь перейдите во вторую таблицу (озаглавленную «Системные переменные») и найдите запись с именем переменной «Path». Дважды щёлкните по ней.
    • В этом диалоге выберите опцию «Создать» справа. Теперь вставьте путь к каталогу .\bin. Он должен выглядеть примерно так:

    a

    • Убедитесь, что ваш путь заканчивается на "\bin", а не на "\dconstruct".

    • Нажмите «ОК» во всех открытых диалогах.

    • Чтобы проверить, что всё сработало, откройте новую командную строку и введите dconstruct --about. Вы должны увидеть вывод программы и никаких сообщений об ошибках.

    Выполните в командной строке команду, подобную этой, чтобы сгенерировать первый дизассемблированный файл:```shell dconstruct my_bin_file.bin

    root@kitploit:~
    Затем это создаст файл с именем `my_bin_file.bin.asm` в той же директории, что и ваш входной файл. Вы можете открыть этот файл с помощью текстового/кодового редактора. Я рекомендую использовать что-то вроде VSCode, который предлагает расширенные возможности поиска и хорошо справляется с большими файлами. Стандартный блокнот Windows не рекомендуется.
    
    Чтобы декомпилировать файл, добавьте флаг `--decompile` при выполнении команды.
    
    # Аргументы командной строки
    
    - `-i` - входной файл или папка. Может быть опущен, если передать путь к входным данным в качестве первого аргумента.
    
    - `-o` - путь вывода. Если ваш входной путь - это папка, то этот параметр не может быть файлом. Если вывод не указан, файл .txt будет помещен рядом с входным файлом. Если входной файл является папкой и вывод не указан, программа создаст директорию 'output' в текущей рабочей директории и поместит туда все файлы.
    
    - sidbase загружается из `sidbase.bin`, расположенного рядом с исполняемым файлом.
    
    - `--no_decompile` - не создавать декомпилированный псевдокод в файл .dcpl. Файл будет помещен рядом с файлом .asm. По умолчанию этот параметр равен false.
    
    - `--no_optimize` - не оптимизировать и не чистить код dcpl. Включает в себя встраивание вызовов функций, удаление неиспользуемых переменных, преобразование совместимых циклов for в циклы foreach и преобразование некоторых цепочек if-else в выражения match.
    
    - `--pascal_case` - преобразовывать имена функций игры в Pascal Case в выводе dcpl, например, get-boolean -> GetBoolean.
    
    - `--graphs` - создавать файлы .svg, содержащие графы потоков управления для всех декомпилированных функций. Каждый файл .bin получает свою собственную папку, содержащую все его графы. Это **значительно** замедляет скорость декомпиляции, поэтому не рекомендуется при декомпиляции большого количества файлов одновременно.
    
    - `--emit_once` - запрещает повторное испускание одной и той же структуры в дизассемблированном коде. Если структура встречается несколько раз, только первый экземпляр будет полностью испущен, а все остальные вхождения будут заменены тегом `ALREADY_EMITTED`. Это может значительно уменьшить размер файла.
    
    - `-e` - внести правку. Подробнее в разделе ниже.
    
    - `--edit_file` - указать файл правок. Файл правок содержит одну правку на строку. Он использует тот же синтаксис, что и флаг -e.
    
    # Что такое дизассемблер?
    
    [Дизассемблер](https://en.wikipedia.org/wiki/Disassembler) — это инструмент, который читает бинарные инструкции (также известные как [байт-код](https://en.wikipedia.org/wiki/Bytecode) или [машинный код](https://en.wikipedia.org/wiki/Machine_code)) и переводит каждую из них в читаемую человеком версию, называемую [мнемоникой](https://en.wikipedia.org/wiki/Assembly_language#Mnemonics). Дизассемблеры обычно не пытаются интерпретировать смысл этих инструкций, а просто преобразуют их 1:1 в читаемые версии. Например, инструкции:```arm
    15 00 00 00
    4A 01 01 00
    43 31 01 00
    1C 00 00 01
    

    разбираются на следующие удобочитаемые версии:```arm LookupPointer r0, 0 LoadStaticU64Imm r1, 1 Move r49, r1 CallFf r0, r0, 1

    root@kitploit:~
    Все числа в байт-коде записаны в [шестнадцатеричной системе](https://en.wikipedia.org/wiki/hexadecimal). Первый столбец в каждой строке представляет `opcode`, или тип выполняемой инструкции. Следующий столбец — регистр назначения, куда будет сохранён результат операции. Последние два столбца — операнды 1 и 2, которые являются либо регистрами, либо литеральными числами, над которыми выполняется операция. Не все инструкции используют все 4 байта; например, первая инструкция `LookupPointer` требует только одного операнда.
    
    Дисассемблер dconstruct также добавляет некоторую дополнительную информацию, призванную немного упростить чтение инструкций. Он также вставляет метки (например, `L_0`), чтобы облегчить отслеживание ветвлений в коде.```arm
    15 00 00 00   LookupPointer        r0, 0         r0 = ST[0] -> <is-player-abby?>
    4A 01 01 00   LoadStaticU64Imm     r1, 1         r1 = ST[1] -> <player>
    43 31 01 00   Move                 r49, r1       r49 = player
    1C 00 00 01   CallFf               r0, r0, 1     r0 = is-player-abby?(player)
    2F 0D 00 00   BranchIfNot          r0, 0xD       IF NOT r0 => L_0
    

    Это полезно, когда вы хотите просмотреть необработанное содержимое файла, не позволяя программе делать слишком много предположений. Но это может быть трудночитаемо для больших блоков кода, так как отсутствует какая-либо структура. Здесь на помощь приходит декомпилятор.

    Что такое декомпилятор?

    Декомпилятор — это обратная сторона компилятора. Компилятор — это программа, которая берет написанный человеком код (например, C, Java, C++, ...) и создает машинные инструкции. В случае TLOU2 и многих других игр ND используемый язык сценариев называется 'DC', который по сути является версией языка программирования Racket>), а "машиной" является сама игра, которая выполняет инструкции во время работы. По сути, программисты пишут на DC и используют компилятор для преобразования этого кода в файлы .bin, поставляемые с игрой.

    Декомпилятор берет дизассемблированный код, полученный выше, и создает так называемый псевдокод. Псевдокод — это попытка восстановить исходный исходный код, который использовался для создания необработанных инструкций. Это призвано значительно упростить понимание кода, однако процесс генерации псевдокода довольно сложен, так как существует множество различных версий исходного кода, которые могут сгенерировать конечный байт-код, а также оптимизации, происходящие во время компиляции.

    В настоящее время вывод декомпилятора dconstruct не синтаксически похож на оригинальный DC. DC (т.е. Racket) — это функциональный язык программирования с уникальным синтаксисом, который чертовски нечитаем для программистов, не привыкших к нему. По этой причине я решил сделать псевдокод более похожим на C, что должно быть проще для чтения большинству людей. Однако в планах создание большего количества синтаксисов, включая Racket, а также версию на Python.

    Примеры

    Функция sqrt-sign()

    Исходные байты```arm

    43 00 31 00 15 01 00 00 43 02 00 00 43 31 02 00 1B 01 01 01 43 02 00 00 40 03 01 00 24 02 02 03 2F 0B 02 00 40 02 02 00 2D 0C 00 00 40 02 03 00 43 03 02 00 15 04 04 00 43 05 01 00 43 31 05 00 1C 04 04 01 07 03 03 04 43 01 03 00 00 01 01 00

    root@kitploit:~
    Исходя из этого, практически невозможно понять, что делает код.
    
    ## Дизассемблированный код с метками и таблицей символов```arm
    sqrt-sign = script-lambda [0x9A8D8] {
        [1 args]
        0000   0x09A928   43 00 31 00   Move                 r0, r49       r0 = arg_0
        0001   0x09A930   15 01 00 00   LookupPointer        r1, 0         r1 = ST[0] -> <absf>
        0002   0x09A938   43 02 00 00   Move                 r2, r0        r2 = arg_0
        0003   0x09A940   43 31 02 00   Move                 r49, r2       r49 = arg_0
        0004   0x09A948   1B 01 01 01   Call                 r1, r1, 1     r1 = absf(arg_0)
        0005   0x09A950   43 02 00 00   Move                 r2, r0        r2 = arg_0
        0006   0x09A958   40 03 01 00   LoadStaticFloatImm   r3, 1         r3 = ST[1] -> <0.000000>
        0007   0x09A960   24 02 02 03   FGreaterThanEqual    r2, r2, r3    r2 = r2 >= r3
        0008   0x09A968   2F 0B 02 00   BranchIfNot          r2, 0xB       IF NOT r2 => L_0
        0009   0x09A970   40 02 02 00   LoadStaticFloatImm   r2, 2         r2 = ST[2] -> <1.000000>
        000A   0x09A978   2D 0C 00 00   Branch               0xC           GOTO => L_1
      L_0:
        000B   0x09A980   40 02 03 00   LoadStaticFloatImm   r2, 3         r2 = ST[3] -> <-1.000000>
      L_1:
        000C   0x09A988   43 03 02 00   Move                 r3, r2        r3 = -1.000000
        000D   0x09A990   15 04 04 00   LookupPointer        r4, 4         r4 = ST[4] -> <sqrt>
        000E   0x09A998   43 05 01 00   Move                 r5, r1        r5 = RET_absf
        000F   0x09A9A0   43 31 05 00   Move                 r49, r5       r49 = RET_absf
        0010   0x09A9A8   1C 04 04 01   CallFf               r4, r4, 1     r4 = sqrt(RET_absf)
        0011   0x09A9B0   07 03 03 04   FMul                 r3, r3, r4    -1.000000 = -1.000000 * RET_sqrt
        0012   0x09A9B8   43 01 03 00   Move                 r1, r3        r1 = -1.000000
        0013   0x09A9C0   00 01 01 00   Return               r1            Return
    
        SYMBOL TABLE:
        0000   0x09A9C8    function: absf
        0001   0x09A9D0    float: 0.000000
        0002   0x09A9D8    float: 1.000000
        0003   0x09A9E0    float: -1.000000
        0004   0x09A9E8    function: sqrt
    }
    

    Теперь код стал гораздо более читаемым, но даже одна ветвь раздражает при чтении, если вы не привыкли читать ассемблер.

    Граф потока управления

    Не вдаваясь в подробности, граф потока управления (CFG) разбивает ассемблерный код на «узлы» по различным инструкциям ветвления. Это важно при анализе кода, чтобы выяснить, где «поток» программы может расходиться на разные пути, что может потребовать от нас создания переменных, операторов if, циклов for и т.д. Эти графы необходимо генерировать в фоновом режиме, но вы можете вывести их в виде изображений с помощью флага программы --graphs.

    Финальный псевдокод```rust

    u64? sqrt-sign(f32 arg_0) { f32 var_1; if (arg_0 >= 0.00) { var_1 = 1.00; } else { var_1 = -1.00; } return var_1 * sqrt(absf(arg_0)); }

    root@kitploit:~
    Теперь назначение функции совершенно ясно: мы берем абсолютное значение аргумента, извлекаем квадратный корень из этого значения, а затем умножаем его на исходный знак аргумента. Например, `sqrt-sign(-9) = -3`.
    
    ## Optimization passes
    
    dconstruct автоматически применяет оптимизационные проходы к псевдокоду. Вот несколько примеров:
    
    ### Function call inlining
    
    #### Before```c
    u64? set-arrow-explosive-handle-rootvars() {
        u64? var_0 = get-uint64(fx-handle, self);
        u64? var_1 = get-float(kill, self);
        set-effect-float(var_0, killradius, var_1);
        u64? var_2 = get-uint64(fx-handle, self);
        u64? var_3 = get-float(strong, self);
        set-effect-float(var_2, strongradius, var_3);
        u64? var_4 = get-uint64(fx-handle, self);
        u64? var_5 = get-float(weak, self);
        u64? var_6 = set-effect-float(var_4, weakradius, var_5);
        return var_6;
    }
    

    После```c

    u64? set-arrow-explosive-handle-rootvars() { set-effect-float(get-uint64(fx-handle, self), killradius, get-float(kill, self)); set-effect-float(get-uint64(fx-handle, self), strongradius, get-float(strong, self)); return set-effect-float(get-uint64(fx-handle, self), weakradius, get-float(weak, self)); }

    root@kitploit:~
    ### Foreach loops
    
    ### До```c#
    u64? bmm-deactivate-all(u64? arg_0) {
        u64? var_0 = darray-count(arg_0);
        begin-foreach();
        for (u64 i = 0; i < var_0; i++) {
            u64? var_1 = darray-at(arg_0, i);
            u16 var_2;
            if (var_1 && *(u16*)(var_1 + 12) == 7) {
                var_2 = *(u64*)var_1;
            } else if (var_1 && *(u16*)(var_1 + 12) == 5) {
                var_2 = *(u64*)var_1;
            } else if (var_1 && *(u16*)(var_1 + 12) == 4) {
                var_2 = *(u64*)var_1;
            } else {
                var_2 = 0;
            }
            net-send-event-all(deactivate, var_2);
        }
        u64? var_3 = end-foreach();
        return var_3;
    }
    

    После```c#

    u64? bmm-deactivate-all(u64? arg_0) { foreach (u64? var_1 : arg_0) { u16 var_2; if (var_1 && (u16)(var_1 + 12) == 7) { var_2 = (u64)var_1; } else if (var_1 && (u16)(var_1 + 12) == 5) { var_2 = (u64)var_1; } else if (var_1 && (u16)(var_1 + 12) == 4) { var_2 = (u64)var_1; } else { var_2 = 0; } net-send-event-all(deactivate, var_2); } }

    root@kitploit:~
    ### Выражения соответствия
    
    ### Перед```scala
    string #C57EE0A64537AE8F(u16 arg_0) {
        string var_0;
        if (arg_0 == 0) {
            var_0 = "Militia";
        } else if (arg_0 == 1) {
            var_0 = "Scars";
        } else if (arg_0 == 2) {
            var_0 = "Rattlers";
        } else if (arg_0 == 3) {
            var_0 = "Infected";
        } else if (arg_0 == 4) {
            var_0 = "Max Num Factions";
        } else {
            var_0 = "Invalid";
        }
        return var_0;
    }
    

    После```scala

    string #C57EE0A64537AE8F(u16 arg_0) { return match (arg_0) { 0 -> "Militia" 1 -> "Scars" 2 -> "Rattlers" 3 -> "Infected" 4 -> "Max Num Factions" else -> "Invalid" }; }

    root@kitploit:~
    ## Пример дизассемблированной структуры```c
    *ellie-weapons* = symbol-array [0x00190] {
      [0] int: 6
      [1] int: 0
      [2] array [0x198] {size: 6} {
        [0] anonymous struct [0x780] {
          [0] sid: pistol-beretta
        }
        [1] anonymous struct [0x788] {
          [0] sid: pistol-revolver-taurus
        }
        [2] anonymous struct [0x790] {
          [0] sid: rifle-remington-bolt
        }
        [3] anonymous struct [0x798] {
          [0] sid: bow-ellie
        }
        [4] anonymous struct [0x7a0] {
          [0] sid: shotgun-remington-pump
        }
        [5] anonymous struct [0x7a8] {
          [0] sid: rifle-mpx5
        }
      }
    }
    

    Редактирование

    Редактирование DC-файлов с помощью флага -e

    Вы можете использовать флаг -e для применения изменений к DC-файлам. Эти изменения сохраняются в новой копии исходного файла, оставляя оригинал нетронутым. Можно указать несколько флагов -e одновременно, чтобы внести несколько изменений за раз.

    Кроме того, вы можете указать программе путь к файлу правок. Файл правок содержит по одной правке на строку. Он использует тот же синтаксис, что и флаг -e, но может быть немного удобнее, если вы хотите применить несколько правок одновременно.

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

    Синтаксис правок

    Каждая правка следует следующему синтаксису:```xml

    Пример

    Предположим, у вас есть структура следующего вида:```c++ [4] firearm-gameplay-def [0x11C28] { [0] float 0.7 // might represent the rate of fire, so i want to lower it for my mod ... }

    root@kitploit:~
    Чтобы заменить первую переменную-член (индекс 0) значением с плавающей точкой 0.5, команда редактирования будет такой:
    
    `-e 0x11C28[0]=0.5`
    
    Структура, которую мы хотим отредактировать, находится по адресу `0x11C28`, и нам нужна первая переменная-член (0 слева от числа с плавающей точкой). Затем мы указываем новое значение после `=`, в данном случае 0.5. Если редактирование прошло успешно, программа покажет сообщение о том, что значение изменено с `0.7->0.5`.
    
    Для файла редактирования просто уберите `-e` и поместите по одному редактированию на строку:
    
    ### edit_file.txt
    
    0x11C28[0]=0.5
    
    0x11C28[1]=0.2
    
    ...
    
    ## Типы переменных-членов
    
    Структуры могут содержать переменные-члены разных типов:
    
    - `float` — указывайте десятичные значения с точкой (например, 0.5).
    
    - `int` — указывайте целые значения без точки (например, 42).
    
    - `sid` (строковый идентификатор) — (подробнее ниже)
    
    - `string` — в настоящее время не поддерживается для замены
    
    - `structure` — путём замены указателя (подробнее ниже)
    
    ### Замена sid по имени:
    
    `-e 0xABC[5]=ellie`
    
    Команда ищет значение "ellie" в текущей базе sidbase. Если его нет, выдаётся предупреждение, и редактирование не применяется. Если значение найдено, фактическое хеш-значение (большое число) заменит текущее значение переменной-члена.
    
    ### Замена sid прямым ручным переопределением хеша:
    
    `-e 0xABC[5]=#XXXXXXXXXXXXXXXX`
    
    Символ # указывает на необработанное хеш-значение, которое будет применено напрямую без поиска.
    
    ### Замена структур-членов
    
    Если структура содержит другую структуру в качестве члена, можно заменить всю структуру-член, присвоив ей адрес другой структуры.
    
    Например, предположим, у вас есть следующее:```c++
    [4] weapon-gameplay-def [0x0C523] {
        ...
        [7] firearm-gameplay-def [0x11C28] {
            ...
        }
    }
    

    Итак, weapon-gameplay-def содержит firearm-gameplay-def. Чтобы заменить firearm-gameplay-def внутри weapon-gameplay-def на другой firearm-gameplay-def, находящийся по адресу 0x0ABC, правка будет:

    -e 0x11C28[7]=0x0ABC

    Расширение языка DCPL для VS Code

    VS Code поддерживает создание пользовательских расширений для добавления подсветки синтаксиса в пользовательские языки. dconstruct поставляется с файлом .vsix, который добавляет эту поддержку для расширения файлов .dcpl. Поскольку это одноразовый язык, не предназначенный для повседневного программирования, я не загружаю расширение в marketplace, а вместо этого поставляю его в виде сырого файла .vsix. Чтобы установить это расширение в вашем VS Code, выполните следующую команду:```shell code --install-extension <path/to/extension/dcpl-lint-0.0.1.vsix>

    root@kitploit:~
    или откройте палитру команд с помощью CTRL+SHIFT+P и введите "Install extension via VSIX", затем выберите файл .vsix.
    
    После этого ваш код .dcpl должен выглядеть примерно так:
    
    ![dcpl_code](https://assets.kitploit.com/production/public/readmes/13048/8b4ab26ab7952a91a015d1376d3101bcad17c65602ed9c2b5c1587cb1d1a9f3c.png)
    
    # Известные проблемы
    
    Декомпилятор в настоящее время не завершён на 100%, поэтому он находится в «экспериментальном» состоянии. Если во время декомпиляции вы получаете предупреждения, не беспокойтесь: эти функции пока не поддерживаются, но, надеюсь, будут поддерживаться в будущем. В дополнение к этому есть и другие известные на данный момент проблемы:
    
    - функции с одним выражением и интенсивным использованием сокращённых вычислений (особенно внутри структур) пока не реализованы. Я начал работу над алгоритмом для них, но не знаю, сколько времени это займёт, хотя эта задача имеет высокий приоритет
    - некоторые типы указаны неверно, особенно типы аргументов
    - пустые блоки if в конструкциях if/else могут приводить к странным отступам. Это опять же не всегда моя вина, поскольку существуют ветви, которые на самом деле не выполняют никакой работы, и это сложно обнаружить
    
    Вместе с этим есть несколько проблем, которые, вероятно, не будут исправлены:
    
    - возврат мусора, когда неизвестно, является ли функция void или нет
    - тонны избыточного кода
    
    # Планируемые функции
    
    - полный формат вывода Racket и Python
    
    # Особая благодарность
    
    - **icemesh** – за предоставление базовых [структур для DC-файлов](https://github.com/icemesh/dc/tree/main/t2) и [его дизассемблера](https://github.com/icemesh/t2-dc-disasm), которые во многом послужили вдохновением.
    
    - **Specilizer** – за его DC-Tool, также послуживший вдохновением для этой программы.
    
    - **uxh** – за знания в области скриптования.
    
    - **bigdragon** и **Wedge** – за бета-тестирование
    
    - Всё сообщество моддинга Discord – за дружелюбие и помощь.
    
    ## Поддержка
    
    Все мои инструменты и моды всегда будут на 100% бесплатными, но такие программы требуют много работы.
    
    Если вы хотите поддержать меня, вы можете посетить мой Ko-fi:
    
    [![Ko-fi](https://img.shields.io/badge/Donate-Ko--fi-ff5f5f.svg)](https://ko-fi.com/deepquantum)
    
    ## Лицензия
    
    Файлы, созданные вами с помощью этого мода, полностью ваши, и вы можете делать с ними всё, что захотите. Упоминание авторства приветствуется, но не является обязательным.
    
    Сама программа лицензирована в соответствии с
    [Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License](https://creativecommons.org/licenses/by-nc-nd/4.0/).
    
    Это означает, что вы можете делиться программой с другими, указывая авторство, но в настоящее время вам не разрешается изменять её или использовать в коммерческих целях.
    
    Скачать инструмент