Основное отличие PSX от приставок предыдущего поколения, разумеется, является наличие CDROM, которое, в общих чертах, имеет преимущество по сравнению с другими носителями при чтении длинных потоков данных на постоянной скорости. И проигрывает с технической точки зрения во всём остальном (например, в скорости поиска, разгона/остановки).
Изначально компакт-диск PSX имеет одновременно треки цифрового аудио и данных. Он имеет файловую систему ISO 9660 для треков данных, описание которой нас мало интересует, кроме нескольких важных фактов, влияющие в дальнейшем на методики чтения секторов данных.
Таким образом, и всего сказанного следует, что процессы, связанных с поиском/чтением на диске являются наиболее критичными с точки зрения скорости загрузки, а, кроме того, не стоит забывать, что перемещение головки – это не только лишнее время, но и лишний шум/износ механических частей CDROM.
Эти процессы – первые кандидаты на оптимизацию, для которой есть два пути:
Наконец, мы подбираемся к двум основным особенностям, присущим дискам PSX в частности, дамми файлы и архивы.
Pi*d
). Поэтому при равной плотности файлов, головка будет меньше перемещаться в радиальном направлении, если все файлы будут расположены как можно ближе к краю диска. Кроме того, чтение данных с диска происходит при одинаковой линейной скорости, поэтому частота оборотов шпинделя изменяется в зависимости от положения головки: 500 об/мин (8.3 об/c) при чтении с внутренних областей и 200 об/мин (3.3 об/c) при чтении с наружных дорожек. А значит, допускаемое количество секторов, на котором могут находиться файлы, чтобы не перемещать головку линейно на внутреннем диаметре равно 128*75/8.3 = ±1156 секторов, а на наружном: 128*75/3.3 = ±2909 секторов. Таким образом, с целью снижения времени чтения с диска, файлы лучше располагать на внешней стороне диска.Итак, на сегодня теории достаточно. Пора пощупать какой-нибудь простенький архив отладчиком. Я взял архив в игре Spider Man, который состоит из двух файлов CD.HED (расширение намекает на то, что это файл заголовка) и CD.WAD – сам архив. Формат архива настолько прост, что можно сразу открыть CD.HED в хексредакторе и раскопать его сходу, но наша задача найти код и понять, как архив обрабатывается. План действий, как всегда, прост до безобразия – отыскать код, который читает данные из файла CD.HED, понять, как эти данные используются, рассмотрев код в отладчике, либо в дизассемблере. Искать место, где читаются данные, мы будем из отладчика. Для PSX их не так уж и много, я пользовался PCSX 1.5 от zHAOsILi, модифицированным Zidane и Horror’ом. У отладчика есть возможность установки точек останова на чтение определенного сектора. Открыв образ игры, например в UltraISO легко отыскать LBA файла. Для CD.HED это 25 или 0х19. Запускаем игру в отладчике, сразу после начала жмём F11 и в окне ‘CD-ROM Read on sector’ вбиваем 19. Жмём Set и Run и вываливаемся на команде по адресу 0x8008d8d8 в памяти.
В код вокруг этой команды можно особо не вглядываться. Это библиотечная функция CD_getsector, вызываемая CdRead(). Главное, что мы получили здесь, это адрес RAM, куда копируется содержимое считанного сектора. Снимаем галки с останова при чтении сектора и ставим останов на чтение из RAM по адресу 0х800B9E68. Опять жмём Set и Run и на сей раз вываливаемся прямо в функции чтения содержимого заголовочного файла:
80064BA8 lbu $v1, 0($s0)
Теперь можно исследовать код прямо в окне отладчика, пробегая по нему, а можно проанализировать в дизассемблере. Я воспользуюсь вторым вариантом и загружу исполняемый файл ‘SLUS_008.75’ в IDA. Этот дизассемблер сам разберет файл, проанализирует и даже переименует библиотечные функции при наличии PsyQ FLIRT сигнатур (с версии 3.85 IDA они входят в стандартную поставку). Перейдём по нашему адресу 0x80064BA8 и натолкнемся на вполне заурядную процедуру анализа содержимого заголовочного файла.
В общих чертах, эта процедура изначально подсказала мне, что в структуре заголовочного файла есть некая нуль-терминированная строка, длина которой выравнена на четыре, и есть два 32-хбитных значения, которые читаются, если строка в заголовочном файле и, находящаяся в памяти равны. Очевидно эта строка – имя файла, а два значения смещение и размер файла в архиве CD.WAD, которые обрабатываются уже вне этой процедуры. Всё. Я же сказал, что архив будет простеньким. Чтобы полноценно работать с архивом, можно написать программку, распаковывающую все файлы из архива и собирающие его обратно из списка файлов. Небольшой нюанс заключается в необходимости соблюдения исходного порядка файлов, так как, очевидно, разработчики этой игры помнили о необходимости хранения часто используемых файлов как можно ближе друг к другу.
Или можно воспользоваться возможностями Total Commander и собственноручно написать архиваторный плагин под такой тип архива, что добавит удобства и простоты в обращении с архивом. Я пойду более элегантным вторым путём, так как, если вы поняли всё, что я написал до этого, о том, как накрапать простенькую утилиту в 100 строк вам рассказывать не нужно.
Итак, архиваторный плагин под Total Commander имеет расширение .wcx и является ничем иным, как dll с несколькими функциями, вызываемыми самим TCmd. Близким нашему случаю примером такого плагина служит ISO.wcx, который позволяет работать с файлами .iso образов как с обычной директорией TCmd. Плагины пишутся на Delphi или C++, я буду проще и возьму в руки Delphi.
Вообще говоря, в сети существует несколько документов для написания архиваторных плагинов, в том числе и довольно подробные от Motorocker. Основной официальный документ – ‘WCX Writer’s Reference’. В рамках этой статьи я собираюсь кратко описать, что я делаю в каждой из функций, реализованных плагином, потому что конкретную реализацию любой желающий может увидеть в исходнике. Файл wcxhead.pas – заголовочный файл, поставляемый с WCX Writer’s Reference, содержащий коды ошибок и другие числовые константы, которые можно использовать в коде. wad.dpr – код самого плагина. Как и в любой dll исходный код в конце имеет секцию
exports
OpenArchive,
ReadHeader,
ProcessFile,
SetChangeVolProc,
SetProcessDataProc,
GetPackerCaps,
CloseArchive,
PackFiles;
Плагин умеет открывать архив, распаковывать любой переданный в TCmd файлы, то есть работать с файлами прямо в архиве и умеет собирать файлы, переданные в TCmd в архив. Можно дополнительно добавить возможность добавлять и удалять файлы из архива, но это требует введения какого-нибудь связанного списка, чего мне, для плагина, которым я воспользуюсь пару раз, было лень.
OpenArchive(Var ArchiveData: TOpenArchiveData): HANDLE; stdcall;
Открывает архив и должна, как минимум, вернуть уникальный хэндл открытого архива. Я дополнительно проверяю здесь все ли файлы архива в наличии и если всё успешно, создаю файловые потоки файла заголовка и файла данных, которые в дальнейшем освобождаются в функции CloseArchive(hArcData: HANDLE): Integer
ReadHeader(hArcData: HANDLE; Var HeaderData: THeaderData): Integer; stdcall;
Заполняет массив записей HeaderData и возвращает ноль в случае успеха. В моем случае заполняется имя файла и его размер. Дополнительно ставятся даты создания файлов с тем, чтобы каждый следующий файл имел большую дату создания. Это необходимо для сохранения порядка файлов при последующей пересборке архива. Достаточно лишь отсортировать файлы по дате создания. В архиве игры дат файлов в любом случае нет.
Function ProcessFile(hArcData: HANDLE; Operation: Integer;
DestPath: PChar; DestName: PChar): Integer; stdcall;
Копируем файл из архива по следующему смещению в файл по переданному имени. Здесь же обрабатываем нажатие кнопки «Отмена» в процессе распаковки.
Function PackFiles(PackedFile: PChar; SubPath: PChar; SrcPath: PChar;
AddList: PChar; Flags: Integer): Integer; stdcall;
Упаковываем переданный список файлов в один архив. Никаких хитростей, разве что нужно обратить внимание, что каждый новый файл начинается с нового сектора, поэтому предусмотрено выравнивание адреса начала каждого файла на 0х800 и дополнительно предусмотрена передача имени упаковываемого в данный момент файла в TCmd для отображения в окошке Progress Bar’а.
Отладка такая же, как и любого dll - прописываем в Delphi в качестве host application исполняемый файл Total Commander, закрываем запущенный TCmd, если вы им пользуетесь и запускаем его из-под Delphi. Ставим точки останова для отладки и производим действия с архивом, которые должны вызвать исполнение нужного нам кода плагина.
Похоже, что всё. Для автоматической установки плагина в TCmd добавляем в архив с откомпилированным .wcx файлом ini файл pluginst и спокойно работаем с архивом.