{{notification.text}}

MirGames

Псевдоархивер — это программа (или ее часть) служащая для следующих целей:

  1. Упаковка файловых пакетов в один файл (псевдоархив).
  2. Шифровка файлов.
  3. (Иногда) сжатие.

На первый взгляд, возможно, непонятно, зачем все это нужно. Ну ладно, со сжатием и шифрованием все понятно. А вот зачем первый пункт?

Пример

Допустим, пишем мы игру. Одна из проблем разработчиков игр – читерство. Особенно если в игре предусмотрен мультиплеер.

Хитрый читер, купив себе, скажем, дешевый меч, заменяет файл с этим объектом файлом наикрутейшего артефактного мечища.

Ничего хорошего, не правда ли? А если вся база вещей находится в одном псевдоархиве (тем более зашифрованном)? Ничего у этого подлеца не выйдет! Есть и другие плюсы такого способа хранения данных.

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

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

Что ж, теперь понятно, зачем нам это надо. Приступим к реализации.

Псевдоархивер изнутри

Логично писать псевдоархивер как отдельный модуль программы (или как динамически-подключаемую библиотеку).

Тогда этот модуль потом можно использовать и в редакторе ресурсов и в т.п. вещах. К тому же, это значительно повышает читабельность кода, а в случае написания в виде библиотеки позже легко можно выпускать патчи на эту часть программы.

В любом случае – это ваше дело. А сейчас, собственно, устройство простого псевдоархивера.

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

2 вида заголовков
Первый способ задания заголовка.
<Кол-во подфайлов><[Длина имени 1-го подфайла]><Имя 1-го подфайла><«Адрес» 1-го подфайла>…<Байты 1-го подфайла>…

Первые 2(4) байта псевдоархива отводятся для хранения количества подфайлов в архиве.

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

Далее следует непрерывная цепочка байт всех подфайлов псевдоархива.

При обращении к подфайлу псевдоархива (возможно по порядковому номеру или имени фала) псевдоархивер читает заранее расшифрованный или незашифрованный вообще заголовок псевдоархива и, найдя необходимый подфайл, читает его «адрес» и «адрес» следующего подфала псевдоархива. Затем производится чтение байт псевдоархива, начиная с адреса самого подфайла до адреса следующего.

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

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

Второй способ задания заголовка.
<[Кол-во подфайлов]><[Длина имени 1-го подфайла]><Имя 1-го подфайла><Размер 1-го подфайла><Байты 1-го подфайла>…

Количество подфайлов псевдоархива можно и не записывать, ориентируясь по размеру всего псевдоархива при чтении/записи подфайлов. А можно и записать, считая кол-во уже обработанных подфайлов. Затем идут имена подфайлов (их длина указывается перед именем файла или постоянна). После имени идет беззнаковое целое число, равное размеру данного подфайла в байтах и далее – байты самого подфайла.

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

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

Перезапись, удаление, извлечение и запись подфайлов псевдоархива

Удаление одного из подфайлов псевдоархива (или перезапись одного из подфайлов, которая сводится к удалению старой версии подфайла из псевдоархива и дозаписи новой) – это те действия, которые требуют перемещения байт других подфайлов или заголовков на новые места. То же самое необходимо и при дозаписи нового подфайла в псевдоархив с первым типом заголовка.

Нельзя забывать, что каждый такой подфайл может занимать не одну сотню мегабайт (например, псевдоархив с видео-роликами), и поэтому перенести файл целиком (т.е. загрузить в память а потом записать на новое место) не удастся. Если же переписывать по одному байту, получается ооочень медленно. На помощь приходит буферизация – копирование пакетами, скажем, по одному килобайту.

То же самое касается и извлечения и записи подфайлов – буферизация необходима и тут.

Шифрование

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

Чтобы этот ключ было трудно вычислить можно паковать исполняемый файл программы различными exepacker'ами. Можно шифровать или не шифровать заголовки псевдоархива.

Идеи и советы

Несколько полезных советов:

  1. Сжатие псевдоархивов полезно при отсутствии динамической подгрузки данных в игре (т.е. все карты, модели, звук и другие данные загружаются при загрузке игры или уровня, а не в процессе самой игры (например, при входе в здание)). Иначе процесс игры будет
    постоянно прерываться паузами на время разжатия псевдоархивов.
  2. Удобно расшифровывать заголовок псевдоархива при первом обращении к этому псевдоархиву. Также можно расшифровать и все байты подфайлов (если предполагается использовать многие из подфайлов данного псевдоархива). Зашифровывать же псевдоархивы желательно только тогда, когда предполагается, что данный псевдоархив больше использоваться не будет. А чтобы подлый читер не смог прочитать расшифрованный псевдоархив в это время, можно открыть псевдоархив на чтение-запись (что часто итак необходимо). Все это касается и сжатия.
  3. На случай аварийного завершения программы стоит предусмотреть создание back-up файла для каждого открытого для работы псевдоархива. В этом файле может храниться, например, количество уже расшифрованных/зашифрованных байт заголовка псевдоархива или подфайлов.
    Это, хотя и сделает псевдоархив частично расшифрованным, не даст ему испортиться (так как при следующем запуске программа сможет завершить начатое действие). Вообще, следует расшифровывать небольшие подфайлы во временные папки, а не прямо в псевдоархиве.
    Тогда с псевдоархивами ничего не случится.
01.11.04 19:42