EnglishУкраїнськаGitHub

Посібник розробника плагінів

Мова: English · Українська

Плагін навчає Picoripi одної гри або текстового формату: як читати й записувати її файли, які в неї теги, якою може бути ширина рядка і — за бажанням — що власні дані гри кажуть про кожен рядок. Ця сторінка — єдиний посібник із плагінів. Перелік усіх гачків генерується з коду: docs/PLUGIN_CONTRACT.md.

Джерело істини: plugins/base_game_rules.py (гачки та їхні типові реалізації), plugins/spec.py (контракт як дані), ui/main_window/main_window_plugin_handler.py (завантаження), ui/settings/logging_mixin.py → find_plugins (пошук плагінів).


Початок плагіна

.\venv\Scripts\python.exe tools/new_plugin.py <id> "<Display Name>" --prefix XX

<id> — назва теки в plugins/; --prefix — короткий префікс великими літерами для ідентифікаторів проблем плагіна (XX_WIDTH_EXCEEDED). Команда копіює plugins/default_plugin, перейменовує префікс і відображувану назву та створює tests/test_plugins/test_<id>/test_rules.py із трьома перевірками, що вже проходять (плагін завантажується, зразок переживає завантаження і збереження, валідатор проходить). Застосунок одразу показує новий плагін у списку.

Далі в такому порядку:

  1. Розбір: load_data_from_json_obj / save_data_to_json_obj у rules.py. Перш ніж чіпати щось інше, покладіть справжній зразок у SAMPLE у згенерованому тесті.
  2. Теги: tag_manager.py.
  3. Ширини: fonts/*.json, font_map.json, ліміти в config.json.
  4. Промпти: translation_prompts/prompts.json — наостанок.

Перевірка і тести:

$env:PYTHONPATH = "."; .\venv\Scripts\python.exe -m plugins.validate <id>
$env:PYTHONPATH = "."; .\venv\Scripts\python.exe -m pytest -n auto tests/test_plugins/test_<id>/

python -m plugins.validate викликає кожен гачок із тестовими аргументами і повідомляє про гачок, що кидає виняток, про хибний тип результату, сигнатуру, яку хост не може викликати, зламаний JSON-файл, сесійні ключі в config.json і (попередженнями) про атрибути головного вікна поза дозволеним списком.

Що зібрати до написання коду

Точний формат файлу зі зразками вихідних і перекладених файлів та очікуваним результатом збереження; максимальну ширину й кількість рядків на сторінку; кожен тег і керівний код — які з них нульової ширини і яка ширина видимих; звідки беруться метрики шрифту; як розбиваються сторінки; метадані мовців або розділів; чи мають невідомі поля проходити збереження без змін. plugins/default_plugin/AI_PLUGIN_ASSISTANT_PROMPT.md ставить ці питання у формі, яку можна вставити AI-помічникові.


Пошук і завантаження

Пошук (New Project і Settings → Global → Active Game Plugin): кожна тека в plugins/, що містить config.json, окрім import_plugins. display_name у цьому файлі — підпис у списку, він має бути унікальним; назва теки — ідентифікатор плагіна.

Завантаження: importlib.import_module(f"plugins.{id}.rules"). Модуль має визначати GameRules — підклас BaseGameRules, який створюється як GameRules(main_window_ref=self.mw). Він також має створюватися без головного вікна (GameRules()): так роблять тести й валідатор. Жодної роботи з мережею, важким диском чи архівами в __init__.

Якщо імпорт не вдався, користувач бачить Plugin Load Error, а застосунок повертається до BaseGameRules. Перемикання плагінів вивантажує всі завантажені модулі плагіна, тож кеші на рівні модулів починаються порожніми.

Аліаси: після завантаження в default_tag_mappings підмішуються plugins/<id>/aliases.json (постачені типові значення, лише для читання), а потім власний aliases.json користувача з теки плагіна в профілі користувача. Застосунок зберігає аліаси лише в теку користувача.

Силові аліаси та імена, які гра малює кольором: аліас, назва якого починається з F: ("{F:Link}": "{Player}"), доходить до моделі як звичайне слово, яке вона відмінює; у перекладі лишається це слово, а не тег, тож перейменований герой однаково «Лінк». Якщо гра сама малює це ім'я кольором, поверніть теги кольору з get_force_alias_wrapping() ({"{F:Link}": ("{Color:Green}", "{Color:White}")} у Minish Cap): слово надсилається обгорнутим, і відмінювана форма повертається в тому самому кольорі; попередження про теги не рахує ці теги зайвими. Для кожної гри треба з'ясувати, чи рушій фарбує ім'я: Twilight Princess, The Wind Waker і Tears of the Kingdom — ні (TotK: жоден із 76–99 тегів {playerName} у кожній мові не обгорнутий тегом кольору; англійська пише «Link» звичайним текстом; Wind Waker HD: 326 з 359 тегів [Name] взагалі не мають кольору; Skyward Sword HD: 626 з 631 {heroName}), Minish Cap — так.

Дії меню: get_plugin_actions() може додавати QAction (name, text, tooltip, shortcut, handler, menu, toolbar). Дії AI-перекладу належать застосунку і є для кожної гри.

Голосові фрагменти: get_voice_clips(project_dir) повертає фрагменти (id, title, disc: .bin, з якого читається оригінал, voice_dir: де зберігається перекладений WAV). export_voice_clip, import_voice_clip і apply_voice_clips виконують роботу; дія меню плагіна відкриває діалог, а кожен експорт, імпорт або запис іде у воркері. Гра без голосових фрагментів лишає типові значення (порожній список).


Файли плагіна

plugins/<id>/
  config.json                         # обов'язково: щонайменше "display_name"
  rules.py                            # обов'язково: class GameRules(BaseGameRules)
  config.py                           # ідентифікатори проблем і типові налаштування виявлення / автовиправлення
  tag_manager.py                      # теги гри та їхнє підсвічування
  font_map.json, fonts/*.json         # ширини
  font_sources.json                   # шрифти гри для редактора шрифтів (необов'язково)
  texture_sources.json                # текстури гри з текстом для вікна «Текстури» (необов'язково)
  translation_prompts/prompts.json    # розділи промптів, які гра змінює

config.json — типові значення для нового проєкту: display_name, game_dialog_max_width_pixels, line_width_warning_threshold_pixels, width_tolerance_percent (на скільки відсотків рядок може вийти за цільову ширину, перш ніж його треба скоротити; береться, коли тип вікна дає лише одну ширину), lines_per_page, default_font_file, detection_enabled, autofix_enabled, кольори та прапорці перенесення. Сесійні ключі (шляхи відкритих файлів, вибір, позиції прокрутки, історія пошуку) валідатор відхиляє; застосунок зберігає їх у project_settings.json.

config.py — PROBLEM_DEFINITIONS, DEFAULT_DETECTION_SETTINGS, DEFAULT_AUTOFIX_SETTINGS = generate_base_config(PREFIX, overrides=…, custom_problems=…) із plugins/common/config_factory.py.

rules.py — GameRules описує свої частини атрибутами класу, а BaseGameRules збирає їх:

class GameRules(BaseGameRules):
    problem_prefix = "XX"
    problem_definitions = PROBLEM_DEFINITIONS
    tag_manager_class = TagManager        # типово: GenericTagManager
    tag_style = "curly"                   # "square", якщо теги виглядають [ось так]
    analyze_whole_string_first = True

З цими атрибутами базовий клас створює менеджер тегів, аналізатор проблем і виправляч тексту та сам відповідає на гачки, які лише передають виклик далі (визначення проблем, підсвічування, аналіз, автовиправлення, короткі назви проблем). problem_analyzer_class / text_fixer_class вказуйте лише для специфічних перевірок чи виправлень гри (підкласи GenericProblemAnalyzer / GenericTextFixer із plugins/common/). problem_ids = problem_ids(DEFINITIONS, PREFIX, without=(…)) прибирає стандартні перевірки, яких гра не використовує.

Контекст рядка для перевірки. Поки програма перевіряє один рядок, атрибути аналізатора _current_scan_block_idx і _current_scan_string_idx містять номер блока та номер рядка цього тексту. Програма задає їх навколо кожного аналізу і після виклику повертає попередні значення. Значення зберігаються окремо для кожного потоку: потік бачить лише ті значення, які задав сам, тож два потоки, що перевіряють рядки одним аналізатором, не змішують їх. Поза аналізом рядка обидва дорівнюють None; читайте їх через getattr(self, "_current_scan_block_idx", None) і обробляйте None. Не зберігайте їх після виклику. Щоб показати деталь під назвою проблеми в підказці редактора, реалізуйте get_problem_detail(block_idx, string_idx, problem_id). Поверніть короткий текст або "", якщо деталі немає.

font_map.json і fonts/*.json — fonts/*.json є картами шрифтів ({символ: {"width": N}}). Кореневий font_map.json перевизначає ширини видимих тегів та іконок: {"[A]": {"width": 16}, "{COLOR_RED}": {"width": 0}}. Видимий тег-іконка не має нульової ширини. Ширини, які користувач задає в програмі (діалог ширини тегу, Settings → карта шрифту), зберігаються в ~/.picoripi/plugins/<плагін>/font_map.json, і цей файл має перевагу над файлом плагіна; так само translation_map.json, який редактор шрифтів пише без проєкту, і window_layouts.json, який пише таблиця лімітів за типом вікна в Settings. Програма ніколи не пише в plugins/.

font_sources.json (або хук get_font_sources()) — власні растрові шрифти гри, щоб редактор шрифтів показав їх для проєкту й зберіг назад. Кожен запис: label, format (bfn, n64, g1t, g1n, bffnt (Switch), bcfnt (3DS BCFNT і 3DS BFFNT; шрифт із кодуванням Shift-JIS у FINF відображає коди через core/font_formats/sjis.py, де вісім українських літер мають вільні коди 0x8492–0x8499), bffnt_wiiu (Wii U BFFNT), brfnt (Wii RFNT і архівний RFNA, той самий читач), fe13_bfnt (.bfnt Fire Emblem Awakening: атлас гліфів на аркушах A4 / A8 з відсортованою таблицею гліфів; одна клітинка на гліф, перемальований гліф повертається у свою рамку, нова літера займає вільну смугу рядків або рамку японського гліфа), jfnt (One Piece: Unlimited World Red 3DS: Ganbarion JFNT з картинкою jIMG A8, клітинки гліфів у сітці, байти ширини редагуються), nlg_font (Luigi's Mansion: Dark Moon: опис гліфів і сторінки ETC1A4 пакета Next Level Games; params.record вибирає один шрифт; пікселі й числа ширини можна редагувати, нових гліфів — ні), cop_fnt (растрові шрифти FD Agatsuma з Code of Princess), l5_fnt (растрові шрифти Level-5 FNT з Fantasy Life: список кодів і один 4-бітний растр на гліф), pw_xpr (шрифти Peace Walker *_FONT.xpr: пакунок XPR2 з лінійним 8-бітним атласом і таблицею кодів), ubiart_tfn (таблиця гліфів UbiArt .tfn.ckd з атласом .tga.ckd як companion; нова літера в порожній клітинці стає записом таблиці), asw_jfnt (шрифт-атлас Arc System Works jFNT з Dragon Ball Fusions: таблиця діапазонів, метрики та вбудований атлас jIMG; картинки гліфів редагуються на місці), cfna (архівні шрифти 3DS CFNA з NintendoWare ArchiveFont: листи стиснуті по одному, з таблицями символів), layton_nftr (NFTR Professor Layton у BIOS-стисненні), layton_gfnt (шрифти GFNT Professor Layton), layton_hd (атлас шрифта порту HD Professor Layton), gen4_glyphs (растрові шрифти DS Gen 4: Diamond / Pearl, Platinum, HeartGold), laft (шрифти Monolith Soft LAFT .wifnt ігор Xenoblade для Switch: клітинки сталого розміру в одному атласі MIBL, кожен гліф зі своїм кодом і лівим та правим краями), phyre_bmf (растрові шрифти PhyreEngine .fgen.NXGL.phyre Dragon Quest Builders 1: записи символів із прямокутником в атласі поля відстаней, повернуті гліфи лежать на боці; одна клітинка на символ, перемальована клітинка повертається у свій прямокутник), qbf або gzf (Grezzo, OoT3D / MM3D), bfotf (масштабований шрифт OpenType/CFF зі Switch: ширини зберігаються як ширини гліфів, перемальований гліф — як обведений контур; params.size — розмір у пікселях), mgs (пропорційний 2bpp-шрифт рушія MGS2, The Twin Snakes; params height, first_code, upper_codepage), texture_grid (сітка гліфів в одному зображенні текстури, клітинки якої розставляє код гри, напр. атласи HUD і меню Twin Snakes у паках текстур stage.dat або сирі аркуші тайлів SNES у Super Metroid; params texture (формат текстури, типово tpl), image, cell [w, h], chars (рядок або список, де запис не з одного символу — токен гри, "" для вільної клітинки — лишає клітинку без символу; так само запис "\0") або first_code, необов’язковий columns; аркуш — саме зображення у власних кольорах, назад кодується у формат текстури), eternal_darkness (Eternal Darkness: сітка 16 x 16 гліфів у зображенні TPL, збереженому догори дриґом і заповненому стовпцями, показана рядками й у правильному положенні; ширини — таблиця в пакеті-companion EBootPak.bin; параметри image, width_table (запис пакета-компаньйона), необов’язкові grid, flip, encoding (cp1251: коди 0x80-0xFF показано кирилицею)), mgs1 (font.res Metal Gear Solid з PlayStation: 96 пропорційних 2bpp-гліфів по 12 рядків і символи zenkaku 12x12; гліф може змінити ширину), mgs1_hd (копія цього шрифту від M2 у 4x у Metal Gear Solid Master Collection: 2bpp-гліфи 48x48, записи по 578 байтів із шириною і зсувом рядка, або 48 символів zenkaku), mgs_nht (текстовий шрифт MGS2 / MGS3 Master Collection MGS_Font_nht.raw: 8-бітні гліфи зі зсувами й шириною; вільні клітинки беруть нові символи), m2 (PSB-шрифт M2: рамка кожного символу на сторінці A8 / A8L8 / RGBA8; гліфи перемальовуються в межах своїх рамок, рамки й метрики лишаються), cv_gba (1-бітні гліфи GBA-ігор Castlevania від Konami: 2-байтовий ключ і байт на рядок пікселів; glyphs, record, rows, keep_mask для бітів, що не є пікселями, widths, коли таблиця ширин іде файлом companion, chars = символ -> номер гліфа), gba_tiles (плитки GBA 8x8 4bpp в LZ77-блоці стиснутої LZ77 multiboot-програми, Tingle Tuner; params program_offset, program_address, tail_pointers, tiles_address, tiles_room, glyph_offset, glyph_count, advance, chars (код -> символ) і необов’язкові slots (коди з акцентом, які під час збереження вказують на вільні плитки)), nftr (NFTR з Nintendo DS, Four Swords Anniversary Edition; params columns — стовпці сітки редактора, spare — порожні клітинки для нових символів), ffta2 (Final Fantasy Tactics A2: UseMoji_image.bin з таблицею ширин FontWidthTable.bin як companion; 2-бітні гліфи в клітинках 16x16, гліф у вільній клітинці додається до коду тексту 0xBF), twewy (The World Ends with You: один із чотирьох шрифтів файлу pack Grp_Font.bin; параметри pages і widths (члени архіву), cell, columns, chars (main або big)), cv_nds (DS Castlevania: записи з 2-байтового ключа й 1-бітних рядків; параметри cell, advance, columns, chars), vagrant (файл шрифту 12x12 4bpp VSFONT.FNT Vagrant Story, який робоча тека складає з текстури гліфів і таблиці ширин; param set: 0 звичайний, 1 курсив; layout: "credits" відкриває шрифти титрів у ENDING.PRG: клітинки 16x16, зсуви texture і widths, set 0 курсив, 1 жирний), retro_font_gx (Metroid Prime Trilogy: FONT Retro разом зі своєю текстурою C4, як їх розпаковує робоча тека; гліфи — бітові поля 4-бітного індексу текстури, змінюються на місці, нових не додати), retro_font_gc (той самий файл для GameCube Metroid Prime: FONT версій 0–2 з 46-байтовими записами гліфів і будь-яким форматом текстури GX; прямокутник кожного гліфа редагується як розкодований RGBA, назад пишуться лише змінені клітинки; файли версії 4 йдуть у retro_font_gx), policenauts (Policenauts: пропорційний 2-бітний шрифт висотою 12 пікселів на початку KANJIFNT.* / KPRFONT.*; ширина гліфа — його крок, гліфи не можуть вийти за місце шрифту), retro_font (Metroid Prime 4 і Prime Remastered: FONT Retro, за яким ідуть його сторінки TXTR R8 з полем відстаней, як їх розпаковує робоча тека; params.page вибирає мовну множину, 0 — латиниця; гліфи правляться на місці, нові не додаються), pgf (PSP PGF, ревізія 2: формат системного шрифту, який читає sceFont; змінений гліф кодується заново з нового чорнила, дані гліфів розкладаються заново; коди — Unicode), lunar (1-бітна таблиця гліфів 16x16 усередині програми, Lunar: Silver Star Story Complete; params offset, count, chars або first_code; гра міряє ширину за чорнилом, тож зберігаються лише пікселі; Lunar 2 додає rows (15), row_order (little) і widths_offset / widths_first для своєї 4-бітної таблиці ширин, яка тоді теж зберігається), mof_font (Castlevania: Mirror of Fate на 3DS, .bfont: атлас CGFX з таблицею MST рамок гліфів; код — 0x1F + номер гліфа), mt_font (шрифти Capcom MT Framework на 3DS, Monster Hunter: таблиця рамок гліфів GFD або lfd з однією сторінкою MT TEX як companion і params.page; нова літера дістає власний запис і рамку у вільному місці сторінки), fragile_dreams (FONT із Fragile Dreams: 256 однобайтових кодів, кожен — рамка гліфа на сторінці TPL; більший гліф іде у вільне місце сторінки), xf (3DS-шрифт Level-5: XPCK з текстури IMGC із трьома шарами гліфів і FNT.bin; Yo-kai Watch: нові символи йдуть у вільне місце текстури, а повна текстура росте до висоти степеня двійки, до якої її й так доповнює GPU), is_obd (Infinite Space: шрифт діалогів 8x16, пари OBJ-тайлів у спрайтовому файлі .obd; params.first, count, chars), dq9 (Dragon Quest IX: список гліфів fi_*.bin зі смугою пікселів fd_*.bin як companion; гліфи шукаються за назвою, тож літера, введена в одну з порожніх клітинок params.spare, стає новим гліфом із назвою з її байтів UTF-8), zelda3 (шрифт діалогів PC-порту A Link to the Past snesrev/zelda3: файл font.png, який пише його restool.py, гліфи 8x16 2bpp рядами по 16 (128, або 256 в українському шрифті робочої теки) з мітками ширини; param chars: абетка гри в порядку гліфів, записи довші за один символ — іконки), pmd_font (шрифти Spike Chunsoft на 3DS в іграх Mystery Dungeon: таблиця гліфів .dic з атласом .img як companion; символ, введений у вільну клітинку, отримує нове місце у вільній частині атласу), cells (клітинки гліфів сталого розміру одна за одною в діапазоні байтів файлу, шрифти в стилі DS у font_data.bin The Tale of Two Towns: params offset, count, cell [w, h], bpp (4), columns, chars (символи клітинок по порядку), advance, tiles (клітинка 16x16 як чотири плитки 8x8; "columns": ліва верхня, ліва нижня, права верхня, права нижня), widths (зсув у байтах таблиці ширин, байт на клітинку, зберігається назад)), jade (Rayman Raving Rabbids 1/2: файл .jfnt робочої папки — FONTDESC рушія Jade, 4/8-бітна текстура і палітра; гліфи перемальовуються всередині своїх прямокутників, нові не додаються), brfna (архівний шрифт Wii RFNA: сторінки стиснені CX Huffman; змінюються пікселі й ширини, нові символи — ні), swf_font (векторний шрифт DefineFont3 ролика Flash / Scaleform, params.font — назва; гліфи малюються в клітинки, змінена клітинка зберігається прямокутниками, нові гліфи не додаються), rrr_errfont (шрифт дискових помилок Rabbids 2 err_msg.fnt з err_msg_8bits.tex як companion), fntg (EA FntG для Wii: таблиця гліфів і сторінка C4 SHPG; гліфи змінюються на місці, гліфу можна дати інший символ), g4font (шрифт G4 Level-5 для Switch: font.cfg.bin з текстурою RGBA8 font.g4tx як companion, чотири шари гліфів; params.font вибирає основний шрифт 0, шрифт фуріґани 1 або другий стиль 2/3; нові символи йдуть у вільне місце текстури), tmp_sdf (шрифт Unity TextMesh Pro SDF, як його розпаковує робоча тека: дерево типів у JSON з атласом PNG як companion; клітинка — прямокутник гліфа в атласі разом із полем, перемальована клітинка повертається в цей прямокутник, ширина — горизонтальний крок; нових символів не додає — Pokémon Brilliant Diamond / Shining Pearl). bfotf також відкриває звичайний файл .otf / .ttf (дані шрифту Unity); ttf — та сама модель під власною назвою для .ttf / .otf гри на ПК, і символ, якого немає, може вказувати на наявний схожий гліф і зберігає його без шифрування), path (шлях або glob відносно теки джерела проєкту, або список варіантів — перемагає перший, що знайшовся; ../ дозволено; в однофайловому проєкті береться сам файл проєкту), необов’язковий member (glob файлів усередині архіву за path або шлях крізь архіви в ньому: dat/file_select.arc/font/*.bfn), необов'язковий companion (другий файл, потрібний шрифту, шлях від теки source — текстура шрифту G4; обидва читаються й зберігаються разом), font_map (карта ширин, яку живить шрифт; редактор при збереженні пише її в <проєкт>/font_maps/, а перевірки ширини її читають) і params (константи гри для формату: сітка клітинок і кодова сторінка атласу G1T, а коли гра тримає ширини у виконуваному файлі — widths (values: таблиця гри, scale: одиниць гри на піксель атласу, patches: {"exefs/<build id>.ips": "<адреса таблиці в цій збірці>"} — збереження пише по одному патчу IPS32 на збірку в теку перекладу), ідентифікатор ROM, файли dmadata і зсув таблиці ширин шрифту N64, який розмір файлу G1N; BFN, BFFNT і формати 3DS описують себе самі, але джерело BFFNT може задати min_sheets: шрифт відкривається з порожніми аркушами до цієї кількості, а аркуш, на якому з’явилися гліфи, зберігається як новий шар текстури, і його символи додаються до мапи символів шрифту — для шрифту, де немає місця для нової абетки). Код форматів у core/font_formats/ не містить констант ігор. Файл, у якому і текст, і шрифт (ROM N64), зберігає правки шрифту при збереженні тексту: хост переносить їх із копії перекладу в перебудований файл. translation_map.json у теці плагіна ({"Ж": "Æ"}: перекладену літеру малює гліф цього слота) стає початковою мапою редактора для проєкту, у якого своєї немає; шрифти з Unicode-мапою символів (bffnt, bcfnt, qbf, gzf, g1n) натомість беруть справжні літери.

texture_sources.json (або хук get_texture_sources()) — текстури гри, на яких є текст (заставки, написи меню, логотипи), щоб вікно «Текстури» (Інструменти → Текстури…) показало їх для проєкту, експортувало в PNG і записало перемальований PNG назад у копію перекладу. Кожен запис: label, kind (title_screen, area_card, menu_label, …), format (bti і tpl (GameCube/Wii), tpl_ctr (TPL порту Xenoblade Chronicles 3D у little-endian: кожна картинка — текстура PICA, RGBA4 / RGB565 / LA8 / ETC1 / A8, нижній рядок перший), bflim (3DS BFLIM/BCLIM і Wii U BFLIM; малюнок читається як збережений, верхній рядок перший, без параметра; params.flip перевертає його після обрізання, і жоден плагін його не задає), ctpk (3DS), fr3 (файли рівнів Jak and Daxter: записи RGBA8, BC1 і BC3), xenos (плиткові картинки Xbox 360, Hard Corps: Uprising), layton_bg, layton_ani, layton_arj, layton_cani, layton_gfsp, layton_limg (картинки та атласи анімацій Professor Layton DS), bch (3DS BCH з Ace Attorney Trilogy від Capcom: текстури — записи регістрів PICA в блоці команд GPU, ETC1 / ETC1A4 / RGB565 / RGBA8, пишуться на місці), rtex (RTEX Agatsuma з Code of Princess: 3DS PICA RGBA4 / ETC1A4 знизу вгору, Switch — дані zlib RZ з RGBA8 / RGB8 / LA8 або плитки PICA; змінена текстура Switch стискається знову), jimg (One Piece: Unlimited World Red 3DS: Ganbarion jIMG / .jtex, ETC1, ETC1A4 або A8, одна картинка, без mip-рівнів), nlg_tex (Luigi's Mansion: Dark Moon: таблиця текстур Powe і піксельний елемент пакета Next Level Games .dict / .data, ETC1 / ETC1A4, mip-рівні перебудовуються), gmio (текстури GMIO Digimon World: Re:Digitize Decode: одна картинка PICA за заголовком 0x80 байт), ztex (картинки Level-5 ztex з Fantasy Life: текстури PICA з іменами файлів, окремо або в UI-контейнері), sh2tex (картинки Silent Hill 2 PC .tex / .tbn2: 8-бітні індекси в 256 кольорів BGRA), tga (нестиснений TGA 24 або 32 біт, атлас шрифту Silent Hill 2 Enhanced Edition; заголовок зберігається при записі), pw_txp (пакунки Peace Walker *.txp з DDS-текстурами, лінійні DXT1 / DXT5), ubiart_tex (текстури UbiArt .tga.ckd / .png.ckd 3DS-збірок: PICA ETC1 / ETC1A4, нижній рядок першим), cgfx (3DS CGFX .bcres/.bctex і телопи .r Fire Emblem Awakening: текстури TXOB блоку DATA, усі формати PICA; рівень 0 пишеться на місці, рівні mip робляться з нього наново), dmp (Dragon Quest VII 3DS: 16-байтовий заголовок і одна поверхня PICA), ctxb (Grezzo 3DS: файли CTXB і текстури всередині моделей CMB), stex (Atlus 3DS STEX, Shin Megami Tensei IV: одна текстура PICA на файл; пара GL type / format задає формат пікселів), bntx (Switch), g1t (Koei Tecmo), txtr_gx (розпакований TXTR Retro з ігор Metroid Prime для Wii: усі формати GX, палітра лишається, коли кольори в неї вміщаються), j3d (текстури TEX1 моделі GameCube/Wii .bmd/.bdl, напр. логотип заставки Twilight Princess; params.texture вибирає їх за назвою), gba (тайли GBA 4bpp у блоках LZ77 стиснутої multiboot-програми: ключі програми як у gba_tiles і textures: {name, block, room, offset, tiles, columns, palette, bank}), gim (PSP GIM, окремо або як члени паку FCHN: index4/index8 з палітрою, RGBA5650/5551/4444/8888, свізл PSP; картинка з палітрою зберігає палітру, якщо в ній є всі нові кольори), txtr (TXTR Retro з Metroid Prime 4 і Prime Remastered, як його розпаковує робоча тека: R8, RGBA8, BC1–BC5 і BC7 записуються на місці; ASTC читається, а змінена ASTC-текстура зберігається як RGBA8), imgc (Level-5 3DS .xi, окремо або як файл у пакеті XPCK .xa/.xc), gtx (текстури Wii U GX2, рівні mip малюються наново; також усередині стиснутого gzip пакета TMPK, як X.pack.gz у Twilight Princess HD), tim (PlayStation TIM, 4/8 біт із палітрою або 15 біт, одна або кілька картинок одна за одною, як .DIS Vagrant Story; params.clut_row вибирає рядок палітри, TIM без палітри показано відтінками сірого; змінений піксель бере найближчий колір палітри), tim2 (картинки TIM2 PS2 / PSP, 4/8 біт із палітрою або 16 біт, окремо або всередині іншого файлу — кожен знайдений у ньому TIM2, напр. у програмі чи файлі субтитрів; порядок палітри й діапазон альфи PS2 враховано; змінений піксель бере найближчий колір палітри), policenauts_pak (пакунки картинок Policenauts: картинки з RLE по рядках, 15-бітний колір або палітра з 16 кольорів; змінена картинка кодується заново, пакунок перебудовується), png (PNG, який збірка сама перетворює на дані гри: джерела графіки декомпіляції (декомпіляція pret через rgbgfx) або текстури, які робоча тека розпаковує з бандлів Unity через UnityPy; зберігається того ж виду: 1-бітна картинка лишається 1-бітною, сіра — сірою, кожен піксель стає найближчим рівнем сірого її бітової глибини або одним із params.levels відтінків (4 для Game Boy), картинка з палітрою зберігає палітру, решта — RGBA; з texture_grid це й сітка шрифту), pcx (PCX Metal Gear Solid з PlayStation, 4-бітний планарний або 8-бітний; відредаговане зображення зводиться до власної палітри текстури й стискається знову, тож розмір файлу може змінитися; звичайні ZSoft PCX 8-, 4- і 24-бітні теж читаються), m2 (усі текстури PSB-файлу M2: RGBA8 з байтами B, G, R, A, A8L8, A8, L8; запис на місці), g4tx (набір текстур G4TX Level-5 для Switch: текстури NXTCH, RGBA8, BC1, BC3, BC7), shpg (фігури EA SHPG для Wii: C4/C8 з палітрою, RGB5A3, RGBA8, CMPR), bfres (текстури FTEX файла Wii U BFRES, напр. картинки інтерфейсу Paper Mario: Color Splash; кубічні карти й текстури SNORM не показуються), mibl (текстури Monolith Soft MIBL ігор Xenoblade для Switch, .witex), ctrt (малюнки .ctt Kingdom Hearts 3D: заголовок 0x80 байт і одна текстура PICA), l2d (малюнки CTRT усередині макета .l2d Kingdom Hearts 3D), wilay (текстури MIBL макета .wilay Xenoblade, записуються на місці, і JPEG макета навчальної картинки), ctxr (.ctxr MGS2 / MGS3 Master Collection: текстура NVN зі Switch і PC TXTR версії 7), phyre (файл PhyreEngine .NXGL.phyre з однією текстурою, Dragon Quest Builders 1: ARGB8, ARGB4444, DXT1 або DXT5, рядки знизу вгору, mip-рівні пишуться знову; пакунок багатьох текстур, спрайти uitex/uiicon Builders 1, теж читається: формат кожної текстури дають використані імена форматів і групи таблиць виправлень), is_tex (3D-текстури .tex Infinite Space з їхньою палітрою), dq9_spr (картинки .spr Dragon Quest IX), jade (текстури рушія Jade Rayman Raving Rabbids 1/2 як .jtex: 4/8 біт з палітрою, TGA RGBA8, DDS DXT1/3/5; змінена 4/8-бітна картинка бере найближчі кольори палітри), jade_jtx (текстури JTX TV Party як .jtx: DXT1, DXT1 + альфа, C4/C8 з палітрою, BGRA8; міпи пишуться наново), raw (пікселі без заголовка за відомими зсувами, напр. текстури N64: params.pixel_format на кшталт n64:IA8 або snes:2bpp / snes:3bpp / snes:4bpp (планарні плитки SNES 8x8, індекси кольорів показано сірим; 3bpp — розкладка A Link to the Past), width, height і offset або список textures з {name, offset}; n64:CI4/CI8 і psx:CI4/CI8 беруть палітру з tlut_offset, psx:RGB555 — 16-бітні кольори PlayStation, psx:4bpp/8bpp — індекси без CLUT, saturn:1bpp_12x12 — 1-бітні гліфи 12x12 один за одним), vs_gim, vs_hf1, vs_rle (екрани з тайлів, спрайти довідки (params.sprites) і стиснений RLE титульний екран Vagrant Story (offset, end, width, height, прямокутники textures); картинку, що вже не стискається у своє місце, не записано), sotn_blocks / sotn_cmp (картинки Symphony of the Night для PlayStation: блоки тайлів VRAM 128x128 у файлах F_*.BIN, bpp, blocks, clut або palette; картинка, стиснена півбайтовим кодером гри, за offset, width, height, room, при збереженні стискається знову, а якщо вже не вміщається — не записується), sh3_tex (текстури pic.arc Silent Hill 3 PC: PS2 GS 8-біт з палітрою і 32-біт RGBA; інші заголовки ще не читаються), mt_tex (TEX Capcom MT Framework на 3DS: одна картинка PICA з мип-рівнями, Monster Hunter), pmd_img (картинки .img Spike Chunsoft на 3DS у Pokémon Mystery Dungeon: Gates to Infinity і Super Mystery Dungeon: 128-байтовий заголовок \0cte і одна поверхня PICA, записана знизу вгору; RGB8, RGBA8, RGBA4, ETC1, ETC1A4, A8), ffta2_efx (3D-текстури DS у файлах ефектів *.efx Final Fantasy Tactics A2: A3I5 / A5I3 з власною палітрою; змінений тексель бере найближчий колір палітри), tiles (тайли 8x8 GBA / DS / Saturn з 1-, 4- або 8-бітними індексами палітри, показані відтінками сірого: NCGR бере розмір із заголовка; тайли без заголовка — bpp, offset, tiles, клітинки спрайтів cell [w, h] у тайлах і per_row; кольори: palette (байти BGR555 гри в hex), bank і banks (одна hex-цифра на тайл, . = bank); nibble: "high": перший піксель 4-бітного байта — його старший півбайт (Saturn); 1-бітний тайл — 8 байтів, перший піксель у старшому біті; linear: звичайне растрове зображення по per_row x 8 пікселів у рядку замість тайлів, імпортований колір поза палітрою стає найближчим кольором банку тайла), tilemap (карта імен над банком клітинок 8x8 з 4- або 8-бітними індексами, одна картинка на карту: екрани VDP2 Saturn, як заставка й меню Symphony of the Night; map_offset, cols, rows, cells_offset, bpp, nibble, unit (байтів на номер клітинки), name_mask, pal_shift / pal_mask (номер палітри в імені), hflip_bit / vflip_bit, blank, palette, max_tiles і textures для кількох екранів над одним банком; перемальований блок бере однакову клітинку, якщо вона є, малюється на місці, коли клітинку вживає лише цей запис карти, інакше бере вільну клітинку або розширює банк до max_tiles)), path (як у font_sources.json; можна {a,b}; glob може бути й у частині з папками, data/map/*/*_en.xc), необов’язковий member (glob усередині архіву за path; архіви в архівах обходяться — Title.szs/timg/*.bflim; файли dmadata ROM N64 звуться #<індекс>; архіви ZAR/GAR Grezzo — теж архіви) і params: compression члена (або файлу, коли члена немає: zlib, gzip, yar; Yaz0, zstd і LzS Grezzo розпізнаються за сигнатурою), file_offset + file_size (файл текстури — цей діапазон байтів, для паків з індексом деінде) і texture (glob назв текстур у файлі, де їх кілька). Піксельний формат береться з самого файлу; код у core/texture_formats/ не містить констант ігор. Текстура зберігає розмір; заново кодуються лише змінені блоки 4x4 чи тайли, тож невідредагована текстура записується байт у байт. Файл, що містить і текст, і картинки (програма з титульним екраном), зберігає відредаговані картинки, коли зберігається текст: хост переносить їх із копії перекладу в перебудований файл.

translation_prompts/prompts.json — може містити лише розділи, які гра змінює (зазвичай translation); решта підмішується по ключах із plugins/common/defaults/prompts.json. Ніколи не додавайте інструкцію, що дозволяє змінювати теги.


Формати файлів і стан, що їх супроводжує

get_file_formats() повертає елементи core.formats.FileFormat(extensions, mode, label). mode — у якому вигляді вміст файлу потрапляє в load_data_from_json_obj і виходить із save_data_to_json_obj: "json" (розібраний), "text" (рядок) або "bytes". Типово це .json + .txt. Гра з власною таблицею повертає [FileFormat((".tbl",), "bytes", "Text tables"), *DEFAULT_FORMATS] і сама розбирає байти; імпорт проєкту, завантаження, збереження й діалоги відкриття/збереження працюють без змін у хості. Архівний формат додається через ContainerManager.register(MyContainer, extensions=(".pak",)).

includes_source_file(rel_path) вирішує, чи стає вихідний файл блоком. rel_path відносний до папки джерела і має роздільники /. Типово це True. Гра, чиє розпаковане дерево містить той самий текст багатьма мовами, повертає True лише для мови, яку перекладають. Інші файли лишаються в папці джерела: збірка далі їх читає, а load_multi_reference може показати їх як довідкові колонки. Блок, чий файл хук відхиляє, зникає зі списку проєкту під час наступної синхронізації; його файл перекладу лишається на диску.

unpack_reference(path, project_dir, progress, is_cancelled) читає довідку, яку користувач обрав файлом (образ гри іншого регіону). Хост викликає його у фоновому потоці. Плагін пише те, що потрібно load_multi_reference, у папку проєкту й повертає цю папку; хост зберігає її як шлях довідки. Поверніть None, щоб шлях лишився як є (типово). Пишіть у тимчасову сусідню папку й перейменовуйте її на місце, щоб скасування лишило старішу папку. Потім load_multi_reference читає лише цю папку: він працює в потоці інтерфейсу й має бути швидким. Приклад: plugins/super_mario_sunshine/reference.py пише лише файли BMG європейського диска.

reference_worker_rules() (необов'язковий метод) дозволяє хосту виконати load_multi_reference у робочому потоці. Він повертає (rules, window), створені в потоці вікна: rules — об'єкт тієї самої гри, що містить стан, який читає розбір (копія, взята зараз); window — копія з власним відображенням тегів, яке розбір заповнює (хост копіює його нові аліаси у відображення редактора в потоці вікна), або None. Дефолт None (або відсутній метод): розбір виконується в потоці вікна. Текстові вікна оновлюються, коли розбір завершується. Twilight Princess повертає копію з власним відображенням тегів. system_3ds і manual_3ds повертають об'єкт із файлами або мануалами проєкту, взятими в цей момент. Сесія без стану плагіна читає свої файли знову, а потім розбирає довідку знову (manual_3ds: англійська відповідає прочитаним мануалам).

default_reference_path() може надати джерело референсу, якщо користувач не задав шлях. Повертайте лише метадані, без доступу до диска. Використовуйте reference_worker_rules() для автоматичного пошуку, щоб читати індекси архівів і таблиці поза потоком інтерфейсу. Типове None зберігає явний вибір референсу. Автоматичний шлях не записується як налаштування користувача. Yo-kai Watch читає розпаковані таблиці й наявні мови ARC0 через окрему копію шляхів джерела. Порожні рядки та рядки без відповідності не створюють вкладку.

Усередині застосунку текст — це List[List[str]], блоки рядків, плюс назви блоків. load_data_from_json_obj повертає (blocks, block_names); save_data_to_json_obj(blocks, block_names) повертає те, що йде назад у файл. Невідомі поля формату мають пережити цей шлях туди й назад.

Якщо для збереження потрібне щось, чого немає в рядках (ключі таблиці, розібраний бінарний файл), тримайте це в плагіні й реалізуйте export_runtime_state() / restore_runtime_state(state) / reset_runtime_state() — прості JSON-дані, які хост зберігає між перезавантаженнями, відкатами й сесіями, — та prepare_save_context(context), що викликається перед побудовою кожного файлу проєкту (context.block_indices, context.runtime_state, context.existing_versions() видає поточні байти файлу, спершу переклад). Сесія, записана до того, як плагін почав зберігати стан, не відновлює жодного: тоді хост один раз читає файли проєкту у фоновому потоці лише заради стану (відновлені рядки й правки лишаються) і знову сканує проблеми. Хост ніколи не читає атрибути плагіна. Приклади: plugins/pokemon_fr/rules.py (ключі), plugins/manual_3ds/rules.py (один посібник на блок), plugins/zelda_bmg/rules.py (бінарний файл, що латається під час збереження).

Сесія зберігає рядки повідомлень, тож гра, чиї рядки змінюють значення, оголошує версію: session_data_layout() повертає число, а менеджер сесій ставить його на кожну збережену сесію. Сесія з іншим числом або без нього проходить через migrate_session_snapshot(snapshot). Хук відображає рядки й повертає снімок з новою позначкою; без позначки сесію відхилено, і проєкт відкривається з файлів. Типове значення (None) не додає ні позначки, ні міграції.

Хост розбирає файл оригіналу й файл перекладу тим самим load_data_from_json_obj. Якщо один файл може містити і оригінал, і переклад (кілька мов в одному файлі, який читається на місці), реалізуйте set_load_role(role): хост викликає його з "translation" перед розбором файлу перекладу (завантаження проєкту, файл змін, повторний розбір після збереження чи відкату, підрахунок рядків у Менеджері проєктів) і з "source" після. Приклад: plugins/manual_3ds/rules.py (en — оригінал, ua — переклад).


Можливості та власні дані гри

Усе вище — обов'язковий мінімум. Понад нього плагін може добувати власні дані гри — атрибути повідомлень, графи діалогів, таблиці сцен — або зовнішнє джерело відомостей про світ гри і передавати це в AI-переклад, побудову глосарію та Story Timeline. Усе це за бажанням; кожен гачок має безпечну типову реалізацію.

Якщо у вас є Що стає можливим
Декомпіляція або сирці гри типи вікон, потік діалогів, таблиці сцен, розміщення акторів
Сирі файли гри (архіви повідомлень, дані рівнів) те саме, через розбір бінарних файлів
Вікі спільноти пошук відомостей для описів у глосарії
Фанатський сценарій або проходження структура сцен і мовців для MemPalace
Лише витягнутий текст нічого з цього не потрібно — прохід по тексту все одно збудує глосарій

get_capabilities() -> Set[str] каже Localization Pipeline, які кроки пропонувати. Не оголошувати нічого — повноцінна відповідь.

Назва Гачок, який вона обіцяє Що з'являється
glossary_seed get_glossary_seed_entries() Терміни глосарію з даних гри без AI-запиту (term, description, section, icon, source_ref, blocks). Хук читає лише self.mw.project_manager: безголовий запуск засівання (scripts/seed_series_glossary.py) передає лише цей атрибут.
external_lore get_external_lore(term) Зовнішні відомості для описів у глосарії
speaker_attribution get_speaker_for_string() Крок Name the speakers і поле Speaker
message_window_preview get_preview_window_style() та гачки get_window_* Ігрові вікна повідомлень у попередньому перегляді, ліміти за типом вікна в Settings
strict_tags — Відповідь ШІ, що загубила, додала чи змінила {тег} джерела, відхиляється й запитується знову (для ігор, де важить кожен керівний код)

Теги серії (get_series_tag_map(), не здібність). Плагін, що повертає TagMap (plugins/common/series_tags.py), задає вид кожному своєму керуючому тегу: розрив рядка, сторінка, прокрутка, пауза, колір, ім'я, що вставляє гра, число, граматична форма або вид, що називає сам тег. Коли дві гри однієї серії мають однакові види в тому самому порядку, переклад, збережений в одній, переноситься в іншу, а його теги записуються синтаксисом іншої гри. Тег без виду блокує перенесення для цього тексту, а смуга пам'яті пояснює причину. Без цього хука рядок серії збігається лише з тим самим текстом.

Не оголошуйте можливість, якої плагін не може забезпечити.

Слоти, а не значення. get_translation_context_for_string повертає метадані під ключами, які публікує рушій: window_type, content_role, role_instruction, has_speaker, glossary_section, force_glossary, layout_class ("prose": Picoripi сам переносить рядки кожної сторінки; "lines": один рядок оригіналу — один рядок перекладу: меню, назви, вибір; пропущено: вгадується з тексту оригіналу). Значення належать плагіну: content_role може бути "BossName" чи будь-чим, що потрібно грі, а role_instruction — речення, яке пояснює моделі, що ця роль означає. Рушій ніколи не порівнює значення з конкретною грою, і код рушія не повинен його знати.

Мовці. get_speaker_for_string повертає ідентифікатор, записаний у даних гри; рушій заповнює рядки, яких користувач не задав, і ніколи не перезаписує вибір користувача. is_placeholder_speaker(name) каже, чи є цей ідентифікатор внутрішнім кодом, який крок злиття зі сценарієм може замінити справжнім іменем (типово — так).

Теки в дереві блоків. get_virtual_folders(block_idx, text_of) повертає теки, що показуються під фізичним блоком у дереві. Кожен вузол — {"title", "rows", "children"}: вузол із children бере рядки з них, листок перелічує рядки (блок, рядок), а рядок може бути в кількох теках. Вибір теки заповнює список рядків її рядками в заданому порядку. text_of(рядок) повертає поточний текст рядка блоку або оригінал, коли переклад порожній, тож заголовок, який перекладач змінює, з’являється після перебудови дерева (збереження або перебудова дерева). Вузли будуйте з даних у пам’яті: виклик іде в UI-потоці. Зразок — plugins/manual_3ds/folders.py.

Вікна повідомлень. Із message_window_preview: get_preview_window_style(block_idx, string_idx) дає стиль повідомлення; get_window_presets() / get_window_preset_label() / get_window_style_for_preset() живлять панель примусового вибору типу вікна; get_window_frame(style) повертає геометрію й картинку з файлів гри; get_window_item_icon() і get_window_text_offset_y() відповідають за слот предмета й вертикальне центрування гри; get_window_layout_groups() / get_window_layouts_document() / save_window_layouts_document() керують таблицею лімітів за типом вікна в Settings. get_string_layout(block, string) дає ширину, шрифт і кількість рядків на сторінку для окремого рядка (пріоритет: перевизначення рядка > цей гачок > загальні налаштування). fit_text_to_window(text, block, string) повертає переклад, перенесений власними кодами рядка й сторінки гри (None — загальне перенесення); ШІ-переклад викликає його, коли застосовує відповідь, а плагін може викликати його зі свого Autofix. Джерело шрифту з "preview": true читається з файлів самого проєкту: хост міряє ним ширину (під назвою font_map, якщо редактор шрифтів ще не записав свою) і малює ним попередній перегляд — для будь-якого формату з core/font_formats.

Повторювані тексти (D46). Пакетне злиття повторів ключує повторюваний текст за його вікном і контекстом перекладу. get_duplicate_context_for_string(block, string) може повернути словник, серіалізований у JSON: він замінює ці два значення в ключі, а мовець, адресат і перевизначення користувача лишаються. Повертайте той самий словник для кожного місця, яке може ділити переклад (посібники 3DS повертають сталий, тож абзац у двох посібниках перекладається один раз). None дає ключ за замовчуванням. Перед записом повторного, пам'яті або серійного перекладу в місце get_text_fit_problems_for_string(block, string, text) перелічує проблеми розкладки, через які текст не вміщається в це місце (ідентифікатори обирає плагін, наприклад WORD або LINES); порожній список означає, що вміщається. Сюди належить лише розкладка: перевірка, що потребує відмальовування сторінки або поточних текстів редактора, лишається з скануванням проблем. Місце, яке відхилено, після прогону лишається неперекладеним. Рядок пам'яті, збережений до появи словника, працює на своєму місці.

Власний попередній перегляд. get_custom_preview_widget(main_window) повертає QWidget, який малює поточний текст у власному вигляді гри замість попереднього перегляду BFN; None лишає попередній перегляд BFN. Хост вставляє віджет у колонку попереднього перегляду, ховає BFN, викликає refresh() після кожної зміни вибору й тексту та shutdown() перед видаленням. Проєкт читається через main_window; віджет нічого в ньому не записує. Еталон — plugins/manual_3ds/preview_panel.py.

Назви тегів MSBT з MSBT Editor. plugins/common/gcf.py читає конфіги ігор редактора MSBT Editor (AeonSake, .gcf) і накладає їхні назви, описи й назви значень на каталог plugins.common.lms_tags (gcf.overlay); розкладка аргументів лишається тією, що перевірена на тексті гри, а перейменовані теги зберігають старі назви як псевдоніми (TagCodec(..., aliases=...)).

Зразкова реалізація. plugins/zelda_bmg/ (Twilight Princess) читає файли гри та декомпіляцію: атрибути повідомлень, що визначають вікно, а отже й роль повідомлення (window_kinds.py), графи діалогів (msg_flow.py), таблиці сцен (stage_data.py), розмітку за типом вікна, попередній перегляд із кольорами, масштабом та іконками, рамки вікон із локального дампа (window_frame_loader.py). Копіюйте підхід — знайдіть у даних гри поле, яке вже кодує, чим є повідомлення, і віддайте його через гачок, — а не таблиці.


Менеджер проєктів: ролі, розпакування і цілі збірки

Майстер нового проєкту в Менеджері проєктів, кнопки Build, Export і Run та значки списку використовують шість необов'язкових хуків. Кожен хук має типову поведінку, тож плагін без них працює: майстер тоді бере роль і платформу від користувача, файли використовуються як є, а кнопки лишаються вимкненими.

Ці хуки виконуються у робочому потоці. Вони не мають створювати віджети або читати self.mw. Хост передає progress і is_cancelled; перевіряйте is_cancelled() у довгих циклах. Хост збирає у тимчасову теку і перейменовує її після успіху. Export копіює зібрану теку в теку, яку вибирає користувач. Run запускає команду емулятора, яку користувач задав у Менеджері проєктів, і передає шлях збірки аргументом.

Еталон: plugins/plain_text/rules.py. Його єдина ціль копіює файли перекладу як є.

Образи гри: core/project_library/images.py читає образи дисків GameCube (і диски NKit GameCube) та картриджі Nintendo DS за заголовком. Перед розпакуванням образу гри хост перевіряє, чи є на цільовому диску місце для нього; простий файл, наприклад ROM GBA, використовується як є. У identify_source викликайте identify або base_identity(path, game_codes), а в unpack_sources — extract(path, target_dir, progress, is_cancelled, select=...). unpack.py гри перелічує файли, які вона залишає, як це робив її скрипт zt; plugins/zelda_fsae/ — короткий приклад. ImageError означає формат, який помічник не читає; він має дійти до хоста, що показує повідомлення.

Сім'ї версій (family_plan)

Гра, що вийшла в кількох версіях (наприклад GameCube, Wii і Wii U HD), може мати один переклад. family_plan(sources, project_dir) отримує теки або файли, які дав користувач, і повертає core.family_projects.model.FamilyPlan, або None, якщо сім'ї немає (типово). Плагін розпізнає кожне джерело за вмістом, а не за назвою теки, яку дав користувач: для Twilight Princess кількість повідомлень у файлах є відбитком (plugins/zelda_bmg/family.py). План перелічує учасників (FamilyMember(key, label, path, base, parents)), базову версію і read(member_key), який повертає пари (unit_id, text).

Ядро (core/family_projects/) зв'язує одиниці кожного учасника з одиницями його батьків за однаковим ключем збігу (типово текст), зберігає один переклад на спільну одиницю, тримає відмінні тексти як перевизначення для учасника і розсилає тексти назад. Учасник може зв'язуватися з кількома батьками по черзі: якщо він має більшість тексту спільною з другим учасником, він спершу зв'язується з ним. Менше джерел, ніж учасників, є припустимим. Побудовник проєкту Twilight Princess (plugins/zelda_bmg/family_project.py) записує один проєкт. Кожне дерево одиниць (одне повідомлення і його зв'язані копії) — одне повідомлення проєкту, у теці "Shared" (дерево з базовою версією і більш ніж однією версією), "Shared HD Msguk" (те саме для дерева з коренем у HD Msguk) або "<мітка> only" (дерево однієї версії). FamilyPlan.shared_files називає файли, які учасник бере з базової версії побайтно (Wii бере Fontus/fontres.arc); family.py shared їх копіює. Сімейство Wind Waker (BMG GameCube і MSBT HD, plugins/zelda_ww/family.py) відрізняється в одному: теги записано двома синтаксисами. family_tags.py зіставляє їх за таблицею family_tag_map.json, яку виведено з файлів гри (python -m plugins.zelda_ww.family_tags derive <тека bmg> <тека msbt>). Ключ збігу читає обидві сторони в синтаксисі HD і прибирає колір. Сім'я задає exact_keys, тож рушій пов'язує кожен ключ з його копіями за ключем, а не за порядком (align_by_key). Проєкт відкривається плагіном GameCube (zelda_tww), який читає блоки MSBT за їхнім початком; його підсвічування тегів ще використовує синтаксис GameCube. Командний рядок: python -m plugins.zelda_ww.family plan|project|push|verify <family.json>.

Тести плагіна

Згенерований файл тестів — це початок. Додайте на справжніх даних: зразок, що розбирається в очікувані блоки; збереження туди й назад, яке зберігає кожне поле; правильні й неправильні теги; попередження про ширину на навмисно довгому рядку; автовиправлення, що повертає (str, bool) і не псує теги. Спільні перевірки — у plugins/testing.py (check_loads, check_round_trip, check_validator).


Чого не робити