Посібник розробника плагінів
Мова: 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 із трьома перевірками, що вже проходять (плагін завантажується, зразок переживає завантаження і збереження, валідатор проходить). Застосунок одразу показує новий плагін у списку.
Далі в такому порядку:
- Розбір:
load_data_from_json_obj/save_data_to_json_objуrules.py. Перш ніж чіпати щось інше, покладіть справжній зразок уSAMPLEу згенерованому тесті. - Теги:
tag_manager.py. - Ширини:
fonts/*.json,font_map.json, ліміти вconfig.json. - Промпти:
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 та значки списку використовують шість необов'язкових хуків. Кожен хук має типову поведінку, тож плагін без них працює: майстер тоді бере роль і платформу від користувача, файли використовуються як є, а кнопки лишаються вимкненими.
get_source_roles()перелічує ролі файлів проєкту, наприклад основна гра, оновлення, DLC і диск 2. Перша роль стає типовою. Хост завжди додає після них Base game, Update і DLC, щоб користувач міг додати всі файли гри; DLC може повторюватися, іunpack_sourcesтоді отримує ключіdlc,dlc_2,dlc_3... (plugin_facts.paths_of_role(sources, "dlc")їх перелічує). Файл, якийidentify_sourceне розпізнав, отримує роль і платформу з власного заголовка (ID тайтлів Switch і 3DS).identify_source(path)читає один доданий файл за його вмістом і повертає його роль і платформу. Для файлу, що не з цієї гри, повертаєNone.get_platforms()перелічує платформи, на яких виходить гра. Майстер показує їх у списку платформ і бере їх, колиidentify_sourceне назвав платформу; гра на одній платформі дає її кожному файлу. Кожен плагін називає хоча б одну (tests/test_architecture/test_plugin_platforms_declared.py). Типово хук читає"platforms": ["3ds", "switch"]зconfig.jsonплагіна; ідентифікатори та їхні назви —plugin_facts.PLATFORM_LABELS(gb, gbc, gba, nds, 3ds, snes, n64, gc, wii, wiiu, switch, psx, ps2, psp, saturn, gdrom, pc, android, other).unpack_sources(sources, target_dir, progress, is_cancelled)розпаковує файли ролей у порожню теку, яку створив хост. Повертає цю теку абоNone, коли файли не треба розпаковувати.get_source_subfolder()називає теку розпакованого дерева, яка є коренем вихідних файлів проєкту, з роздільниками/, наприкладfiles/res/Msgus. За замовчуваннямNone: вихідні файли — уся розпакована тека. Гра, чиї платформи розкладають файли по-різному, повертає список таких шляхів: джерелом стає перша наявна тека, так само в розпакованій теці, яку додає користувач. Якщо жодної немає, хост завершує розпакування з повідомленням. Використовуйте, коли розпаковане дерево містить більше за текст гри.unpack_key_consoles()перелічує консолі, файли ключів яких читає розпакування: будь-які з"3ds","switch","wii". Типово: жодної. Майстер просить файл ключів консолі лише тоді, коли вибраний файл зашифрований і гра перелічує цю консоль. Гра, яка не розпаковує зашифровані образи, повертає порожній список.- Зашифровані образи консолей:
core/containers/console_crypto.py. Усерединіunpack_sourceskey_path(console)повертає файл ключів, який користувач вибрав у майстрі для цієї консолі, абоNone; читачі вcore/project_library/images.pyсамі читають файл ключів. Читач викликаєencrypted_error(console, label), якщо немає файлу ключів або пакета pycryptodome. Плагін сам не відкриває файл ключів, і шлях до нього ніколи не записується в проєкт. get_build_targets()перелічує, що гра може зібрати. Кожна ціль маєid,label,platforms(порожньо означає всі) іkind:files,patchабоlayeredfs.build_target(target_id, source_dir, translation_dir, output_dir, progress, is_cancelled)записує одну ціль у порожню теку виводу і повертає шлях результату.project_icon(project_dir)повертає зображення гри (байти PNG або JPEG) для рядка проєкту абоNone. Хост масштабує його до 128 px і кешує. Виконується в процесі скану, тож може читати файли, але не створює віджетів. Файлicon.pngкористувача поруч із файлом проєкту має перевагу.plugins/plain_text/rules.pyповертаєbanner.pngпоруч із файлом проєкту як приклад.classify_resource(path)повертає вид одного файлу вихідної папки для підрахунку файлів у Менеджері проєктів:"text","font","texture"або"other". ПовернітьNone, щоб лишилося правило розширення файлу (типове). Менеджер викликає його для кожного файлу вихідної папки, коли рядок відкривається вперше; він виконується на worker-потоці і може читати файл. Помилка в хуку повертає правило розширення.
Ці хуки виконуються у робочому потоці. Вони не мають створювати віджети або читати 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).
Чого не робити
- Не кладіть дампи гри, архіви чи ресурси видавця в плагін, який комітите.
- Не імпортуйте PyQt5; застосунок працює на PyQt6.
- Не пишіть специфічну для гри поведінку в
plugins/common/чи в рушій. Типи вікон, ідентифікатори предметів і назви ролей належать плагіну. - Не перевіряйте в коді плагіна об'єкти
Mock. - Не повертайте метадані, які неможливо серіалізувати: вони потрапляють у файл сесії.
- Не викидайте й не переставляйте невідомі поля формату файлу під час збереження.
- Не копіюйте таблиці
zelda_bmgу плагін іншої гри. - Не забувайте
config.json— без нього плагін ніколи не з'явиться.