Разработка и ромхакинг > Инструменты
[DC][PS1][PS2][GC] Плагин к 7-zip для распаковки CHD, RVZ и AFS
(1/1)
cptPauer:
Я доделал наконец свой плагин к 7-zip.
https://github.com/Conkwer/tres7z/releases/tag/v2.3.0
Распаковка CHD, AFS, RVZ.

В чём суть?

Он распаковывает CHD (Dreamcast, PSX, PS2) и RVZ (GameCube).
Плагин обычно ложится в c:\Program Files\7-Zip\Formats\ (64 бит)

Бывает такое, берешь образ GameCube, а он в RVZ и что делать? Качаешь Долфин, распаковываешь.
Очень бесячая тема.

Та же ерунда с CHD v5 и особенно которые с ZSTD компрессией, новые.
У меня все образы PlayStation1 в нём для Анберника.
Тут бывает надо ISO достать и поиграть на старом ePSXe, тут начинается гемморой, впоминай как, качай МАМЕ, опять бери chdman и извлекай.

Ну и для Dreamcast так же. Хочешь закинуть игру на GD-emu, а она в CHD.

С плагином же вообще удобно. Просто как обычный архив открываешь, по-человечески наконец.
Всё равно 7-Zip уже установлен для 7z.
В смысле, из CHD можно достать ISO или GDI. Из RVZ -> ISO. Из AFS что угодно.
Без потерь в качестве, как было запаковано так и достанется. Запаковку не делал обратно в CHD, только распаковка.

Плагин сложно было сделать, цените. У 7-zip система сборки упоротая, не говоря про плагины. Там даже с нейронкой его собрать, можно быстрее состарится.
Наверное, поэтому никто и не делает такие плагины. Я тоже бы не делал, просто научился их делать раньше, для CDI.

Плагин поддерживает еще CDI, да. Но не полноценно, CDDA надо доделывать. Зато он показывает IP.BIN как bootsector.bin.

Вообще, для 7-zip существовал плагин iso7z. Вот мой с ним конфликтует, кстати.
В том плагине нету нормальной поддержки CHD v5 ZSTD, он только четвертой открывает. И там есть поддержка CDI, но не показывает бутсектор.

Еще мой плагин открывает MTF для Darkstone (с компрессией!) и несколько других форматов, но это не актуально на эмуленде.

Что актуально, это то что он открывает AFS! Те, которые повсеместно на Dreamcast. С поддержкой запаковки. Вообще, сложности распаковать/запаковать AFS небыло никогда, но это было всегда неудобно. И Puyo Puyo Tools багованная.

А тут можно сделать нормальный индекс и вынести это в комментарии, у 7-zip есть такая фича.
И автоопределение ADX музыки в таких архивах, как у AFS Explorer. Поддержка AFS подогнана как у AFS Explorer.
Это должно быть лучше Puyo Puyo Tools и AFS Explorer. Но, только для архивов первой версии, которые на Dreamcast. Более новые (которые встречаются на PS2 и GC) меня не интересовали, я их не добавлял.

Код, понятно что, не с нуля написан. Собрал из готового. Все кудосы соответствующим проектам и спекам: MAME, Долфину и т.п.

Времязатраты: 2 дня на каждый формат, кроме RVZ. Где можно было ускорится нейронкой и готовым кодом, ускорялся (я же не садомазохист). Там и так гемморно, что бы ещё кодить всё лично.
Roosya:

--- Цитата: cptPauer от 01 Сентября 2026, 03:09:04 ---из CHD можно достать ISO или GDI. Из RVZ -> ISO. Из AFS что угодно.
--- Конец цитаты ---
Хорошее дело, а то для пс1 держал в chd только из-за многофайловой структуры куебин. А для пс2 мог бы и в исо. А тут поголовно куча форматов ужато в этот новый. Старые эмули с ним не работают. Хотя вот epsxe читал вроде обновили. но в анберниках и ретроидах его нет.
e2e41:
This plugin can be used for Magic the Gathering DC???
там вреде deflate, хз не работает вроде
cptPauer:

--- Цитата: e2e41 от 01 Сентября 2026, 16:51:44 ---This plugin can be used for Magic the Gathering DC???
там вреде deflate, хз не работает вроде

--- Конец цитаты ---
Да. Почему не работает? Fully supported, блин.
На распаковку и запаковку. Хорошо работает. Что нужно делать, что бы оно не работало?
Открываю pac, извлекает и запаковывает. Можно вообще любые файлы паковать в pac.
Автоматически показывает pvr текстуры и str музыку в этих pac по заголовкам.











Кстати, если кто-то хочет помочь это перевести на русский, то помогайте. Я сам не осилю это доперевести. Больше чем два года прошло.

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

Статут проекта: я сделал русскую озвучку Яндексом. Как по мне, то достаточно нормально перевелось. Lin нарисовал пробный шрифт, но шрифт кривоватый, доделывать шрифт некому.
Есть проект для Круптара. Лично мне в Круптаре неудобно, но Круптар пересчитывает поинтеры и можно текст любого размера писать. Весь текст можно редактировать свободно, тот который не текстурами.

Туторы я перевёл (обучающие ролики), но озвучки для них не делал на русский. На английский есть..

Сейчас вроде бы перевели игру на английский, но я не смотрел что там, этот перевод был начат раньше и с японского могу перевести сам. Я переводил без нейронок, для этого я сделал на английский eTypist OCR и им распознавал текстуры.

Патч с переводом для eTypist я выкладывал. eTypist забытая, но очень мощная японская OCR. Изначатально интерфейс там на японском полностью. Часто распознаёт лучше нейронок, но они уже близко подобрались.

-----

Можно попробовать натравить нейронку перевести на русский текст. Но, это тоже не так просто как кажется и не бесплатно. Я бы даже сказал, что описания карт полюбому нужно переводить нейронкой, их там вроде около 300. Но делать вычитку и меню надёжнее переводить вручную.

-----

Я делал на два языка. На английский там относительно просто, потому что описания карт уже есть на английском (не активно), ролики есть на английском (но субтитры к ним японские, на английском голосом, но сабы я сделал), шрифт английский уже есть. Там только меню на японском, текстуры (магазин). В целом, тоже достаточно много текстур. Картинки карт на японском, но они продублированы текстом и на таком разрешении их всё равно толком не видно. Там не рассчитано, что с карт будут читать текст.

Игра планировалась на два языка, наверное. Там английский есть, но не до конца.

Да, Deflate без заголовка. Я оговорился, наверное. Алгоритм не мой, я просто портировал c Python тот скрипт, с которым со мной поделились.

--- Код: ---import sys
import zlib
from pathlib import Path
from struct import unpack, pack
import argparse
import json

def read_u32le(f):
    return unpack("<I", f.read(4))[0]

def pack_u32le(u32):
    return pack("<I", u32)

def ceil4(n):
    return ((n + 3) // 4) * 4

def unpack_pac(input_file, output_dir):
    p_in = Path(input_file).resolve()
    if output_dir:
        p_out = Path(output_dir).resolve() / p_in.stem
    else:
        p_out = p_in.parent / p_in.stem
    p_out.mkdir(parents=True, exist_ok=True)

    file_info = []

    with open(p_in, "rb") as f:
        magic = f.read(4)
        if magic != b"PAC\x00":
            print("magic != PAC\x00")
            sys.exit(1)
        file_count = read_u32le(f)
        for i in range(file_count):
            off = 8 + i * 4
            f.seek(off)
            file_off = read_u32le(f)
            file_off_end = read_u32le(f)
            file_size = file_off_end - file_off
            f.seek(file_off)
            data = f.read(file_size)
            file_entry = {"index": i, "size": file_size}
            file_name = f"{i:08d}"
            if data[:4] == b"NZIP":
                try:
                    u = zlib.decompress(data[8:], wbits=-15)
                    if u[:4] == b"GBIX":
                        p = p_out / (file_name + ".pvr")
                        p.write_bytes(u)
                        file_entry["type"] = "pvr"
                    elif u[:4] == b"SDCP":
                        p = p_out / (file_name + ".sdcp")
                        p.write_bytes(u)
                        file_entry["type"] = "sdcp"
                    else:
                        p = p_out / (file_name + ".bin")
                        p.write_bytes(u)
                        file_entry["type"] = "bin"
                except zlib.error:
                    p = p_out / (file_name + ".nzip")
                    p.write_bytes(data)
                    file_entry["type"] = "nzip"
            elif data[:4] == b"SDCP":
                p = p_out / (file_name + ".sdcp")
                p.write_bytes(data)
                file_entry["type"] = "sdcp"
            else:
                p = p_out / (file_name + ".bin")
                p.write_bytes(data)
                file_entry["type"] = "bin"
            file_info.append(file_entry)

    info_json_path = p_out / "info.json"
    with info_json_path.open("w") as info_json_file:
        json.dump(file_info, info_json_file, indent=4)

def pack_pac(input_folder, output_file):
    input_folder = Path(input_folder)
    output_file = Path(output_file)

    info_json_path = input_folder / "info.json"
    if info_json_path.exists():
        with info_json_path.open("r") as info_json_file:
            file_info = json.load(info_json_file)
    else:
        print("No info.json found. Processing files as usual.")
        file_info = None

    files = []
    if file_info:
        for entry in file_info:
            file_path = input_folder / f"{entry['index']:08d}.{entry['type']}"
            if file_path.exists():
                data = file_path.read_bytes()
                if entry['type'] == 'pvr':
                    c = zlib.compress(data, level=9, wbits=-15)
                    c = b"NZIP" + pack_u32le(len(data)) + c
                    files.append(c)
                elif entry['type'] == 'nzip':
                    u = file_path.read_bytes()
                    c = zlib.compress(u, level=9, wbits=-15)
                    c = b"NZIP" + pack_u32le(len(u)) + c
                    files.append(c)
                else:
                    files.append(data)
            else:
                print(f"File {file_path} not found. Skipping.")
    else:
        file_paths = sorted(input_folder.glob('*'))
        for file_path in file_paths:
            if file_path.suffix in ['.pvr', '.nzip', '.bin', '.sdcp']:
                data = file_path.read_bytes()
                if file_path.suffix == '.pvr':
                    c = zlib.compress(data, level=9, wbits=-15)
                    c = b"NZIP" + pack_u32le(len(data)) + c
                    files.append(c)
                elif file_path.suffix == '.nzip':
                    u = file_path.read_bytes()
                    c = zlib.compress(u, level=9, wbits=-15)
                    c = b"NZIP" + pack_u32le(len(u)) + c
                    files.append(c)
                else:
                    files.append(data)
            else:
                print(f"Skipping {file_path.name}")

    with open(output_file, "wb") as f:
        f.write(b"PAC\x00")
        f.write(pack_u32le(len(files)))
        off = 8 + len(files) * 4 + 4
        for i in range(len(files)):
            f.write(pack_u32le(off))
            off += ceil4(len(files[i]))
        f.write(pack_u32le(off))
        for i in range(len(files)):
            padded = files[i].ljust(ceil4(len(files[i])), b"\x00")
            f.write(padded)

def main():
    parser = argparse.ArgumentParser(
        description="Unpack or pack a PAC file.",
        epilog="Examples:\n"
               "  python script.py unpack example.pac -d ./uncompressed\n"
               "  python script.py unpack example.pac\n"
               "  python script.py pack ./uncompressed/example example.new.pac",
        formatter_class=argparse.RawTextHelpFormatter
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    unpack_parser = subparsers.add_parser("unpack", help="Unpack a PAC file.")
    unpack_parser.add_argument("input_file", type=str, help="The input PAC file to unpack.")
    unpack_parser.add_argument("-d", "--directory", type=str, default=None, help="The output directory for unpacked files.")

    pack_parser = subparsers.add_parser("pack", help="Pack files into a PAC file.")
    pack_parser.add_argument("input_folder", type=str, help="The input folder containing files to pack.")
    pack_parser.add_argument("output_file", type=str, help="The output PAC file.")

    args = parser.parse_args()

    if args.command == "unpack":
        unpack_pac(args.input_file, args.directory)
    elif args.command == "pack":
        pack_pac(args.input_folder, args.output_file)

if __name__ == "__main__":
    main()
--- Конец кода ---

Автор один из местных, но я не уверен кто.
Сделано было до нейронок, несколько лет назад.

Это MTF был в LZSS. Просто MTF запарил меня, теперь оговариваюсь. Там тоже запаковка поддерживается. И инфы как сделать правильно небыло, данные смещались и игра крашилась.
Поэтому LZSS застрял у меня в голове, оговариваюсь, моральная травма)). А в Магике был Deflate.

Я проверял на игре, работало. Но, если какие-то косяки, то скрипт на Python тогда наверняка работает, проверено.


--- Цитата: Roosya от 01 Сентября 2026, 08:54:41 ---Хорошее дело, а то для пс1 держал в chd только из-за многофайловой структуры куебин. А для пс2 мог бы и в исо. А тут поголовно куча форматов ужато в этот новый. Старые эмули с ним не работают. Хотя вот epsxe читал вроде обновили. но в анберниках и ретроидах его нет.

--- Конец цитаты ---

Кстати, можно было сделать для PS1, что бы из CHD обратно получался многобин. Но, сейчас там извлекается одним файлом + cue.

Поэтому, если игру PS1 из Редампа запаковать в CHD, а этим плагином извлечь, то она из многобина  редампа превратится в однобин. Сама игра останется точно такой же, но формат изменится.


--- Цитата ---Хотя вот epsxe читал вроде обновили. но в анберниках и ретроидах его нет.
--- Конец цитаты ---
Кстати, в epsxe наверное без zstd, не уверен.

Раньше же был просто пятой версии chd, хотя часто встречались четвёртой для первой соньки.
А теперь переходят на zstd компрессию и такие вообще мало где открываются, эмуляторы не поддерживают.
Redream не поддерживает, например.
e2e41:
шрифт разве не в DPFONT? если там то кирилица уже вшита должна быть, жалко что все заглохло...
в текущих реалиях текст перевести не состовляет труда

7zG.exe не работает
7zFM.exe работает
cptPauer:

--- Цитата: e2e41 от 02 Сентября 2026, 17:02:28 ---видимо я что то делаю не так просто закинул плагин в корень с 7zip...

--- Конец цитаты ---
Плагины ложатся не в корень! У тебя не подключен, открывает как без плагина.
Ложится в подпапку "Formats" или "Сodecs".
Например: "c:\Program Files\7-Zip\Formats\tres7z.64.dll" (раньше dream7z.64.dll, название самой dll роли не играет).

Разрядность должна совпадать. Если у тебя 64-битная версия, то плагин должен быть 64-битный.
32-битного там пока нету.

Кроме того, там есть архив "всё включено". Архиватор вместе с плагином (7-Zip-Zstandard-x64.zip).
Старая версия не лучший вариант, но проверить работу плагина можно.

Плагин подходит как к официальному 7-zip (https://www.7-zip.org/) так и форкам (https://github.com/mcmilk/7-Zip-zstd) разных версий.
cl:
я не проверял абсолюно все форматы но это работает, я даже 1 звезду поставил
cptPauer:
А чего в отдельную тему перенесли из "Рус. переводы и ромхакинг игр на Dreamcast".

Обсуждалось как раз ромхакинг и переводы: а) перевод Magic the Gathering (Dreamcast) на русский язык. Ромхакинг чистой воды, скрины с Круптара, алгоритмы компрессии и разное.

б) распаковка pac (это формат архивов с кастомным заголовком) это ромхакинг, пример распаковки на Python.

в) распаковка AFS это близко к ромхакингу, половина игр на Dreamcast использует AFS v1. Раньше для этого использовали AFS Exporer и Puyo Puyo Tools.

И я даже не успел проги остальные выложить. Там еще плеер был yamaha apdcm, который удобнее vgmstream.

Небыло оффтопа. Всё про ромхакинг и касается переводов. Для Dreamcast в основном было сделано. И плагин позволяет из CDI доставать IP.BIN. Это часто нужно для правильной пересборки образа.

CDI, CHD, AFS v1, PAC = Dreamcast

Сонька 1-2 просто тоже использует CHD, так исторически сложилось, но на Dreamcast раньше начали. RVZ просто бонус.
Теперь чёрта с два найдешь, так всё в одном месте было. И где проги и переводы теперь обсуждать?
e2e41:
Просто линк оставить в профильной на эту тему, откровенно плагин удобный для некоторых моментов.
Беларус:
cptPauer, я планирую ту тему распотрошыть на разные темы, т.к. там слишком много чево. Значит, буду советоватса с тобой перед этим.
Этот плагин для слишком многих платформ, поэтому отделил. Согласен с e2e41, просто оставь там ссылку на сюда.
Partsigah:
На PCE CD тожэ работает, но распаковывает так жэ, как и chdman: т.е. если до упаковки в chd было cue + несколько bin, то после распаковки будет останется только 1 cue и 1 bin.

Добавлено позже:
Это я к тому, что если есть патч для образа, то он не редко рассчитан на структуру cue + несколько bin, где патчить надо конкретный bin. Но при переконвертации все эти bin-ы сливаются в один, и образ ужэ не пропатчить.
cl:

--- Цитата: Partsigah от 05 Сентября 2026, 10:16:50 ---Но при переконвертации все эти bin-ы сливаются в один, и образ ужэ не пропатчить.

--- Конец цитаты ---
есть обратная конвертация

--- Цитата ---If you want to reverse the process and create split files again, binmerge supports that. But do note, for some games, the Redump cuesheet contains information that binmerge cannot reasonably preserve when merging. If you're trying to get back to a pristine Redump set, you may want to download the freely available cuesheets available on Redump's web site.
--- Конец цитаты ---
cptPauer:

--- Цитата: Беларус от 04 Сентября 2026, 03:03:15 ---Согласен с e2e41, просто оставь там ссылку на сюда.
--- Конец цитаты ---
Понятно. Да в принципе не важно, можно и просто ссылку.
Я сначала подумал, что сообщения вообще пропали.


--- Цитата: Partsigah от 05 Сентября 2026, 10:16:50 ---На PCE CD тожэ работает, но распаковывает так жэ, как и chdman: т.е. если до упаковки в chd было cue + несколько bin, то после распаковки будет останется только 1 cue и 1 bin.
--- Конец цитаты ---
Технически, сделать нарезку (split) как у binmerge можно в 7-Zip. Но просто слитый (merged) bin+cue это самое удобное и chdman тоже не поддерживает разрезанные (split). Слитные оно как-то надежнее и проще.

Ну и такое дело, что у PCE CD бывают патчи под самые разные ромсеты и угодить всем нельзя.
Иногда берут образы, которые делает TrueRIP и там меняют аудио. Там вообще c toc и wav, а у редампа там raw и какую-то свою инфу могут в cue записывать.

Причём, прегапы не всегда соблюдают и не на всех эмуляторах даже работает нормально. Пример: Valis IV с английской озвучкой, пришлось исправлять что бы поиграть на Mednafen. От переводчика зависит, какой будет патч.
Такое редко надо, обычно ведь качают готовый "ром". А когда патчить самому, то это всегда геммор. Иногда переводы выкладывают патчерами, которые на новых ОС тупо не запускаются.

Чисто технически, в плагин нарезку (split) добавить не сложно. Но тогда мы потеряем слитный (merged). Варианты решения:
a) Самое очевидное, это сделать виртуальные папки merged и split и там отображать образ разрезанным и слитным, два образа будет. Но, тогда когда на архиве жмёшь "извлечь" то оно извлечёт две копии, split и merged. Мне неудобно так.

б) Можно изменить сам 7-Zip, его интерфейс. Добавить кнопку с переключателем split/merged. Но тогда это уже будет не плагин а форк 7-Zip заточенный под ромы. А это не хочется делать, потому что 7-Zip обновляется часто и форк быстро устареет. Удобнее, когда оно отдельным кодеком.

в) Можно добавить консольный параметр, но это не работает в интерфейсе. А какой тогда смысл, оно нужнее всего для GUI.

г) Сделать два плагина, один будет открывать как merged, другой как split. Но потом неудобно будет билдить
То есть, оставить слитный (merged) это лучший вариант, как и сделали разработчики самого chd формата.
Если надо разрезать, то использовать тулзы типо binmerge, CHDlite, truerip, да.

Кроме того, у меня есть тулкит конвертить игры PCE CD под PSP и он плохо дружит с разрезанными образами.
Навигация
Главная страница сообщений

Перейти к полной версии