|
Перевод Spyro 3: Взлом и программы
|
|
| aleksusklim | Сообщение # 1 Воскресенье, 26.06.2011, 14:19 |
фдулыгылдшь
 Редактор
«1066»
Где: Не в городе Драконов
|
Работа с текстовой частью перевода введётся на code.google.com/p/spyro3-rus, обсуждение перевода (а не взлома) В темеПеревод Spyro 3: Текст В этой теме происходит обсуждение перевода, а также создание софта для потрошения игры. Важные сообщения: Структура WAD-файлов Спец. символы и File Paint 1 | Извлечение и добавление текста | О звуке | В продолжение о звуке | Вариант перевода Buzz's Dungeon и Midday Gardens
Сообщение отредактировал aleksusklim - Среда, 29.04.2015, 19:51 |
| |
| |
| aleksusklim | Сообщение # 391 Воскресенье, 07.07.2013, 18:57 |
фдулыгылдшь
 Редактор
«1066»
Где: Не в городе Драконов
|
Да! Я провёл огромнейший взлом игры, и теперь мы можем:
– Извлечь ВЕСЬ текст из игры разом; – Однозначно получить Numeration-сопоставление каждой строки конкретному STR-треку в автоматическом режиме; – Заменить звуковой трек любой реплики на любой другой существующий трек (например, большей длительности)
Качайте проги: http://klimaleksus2.ucoz.ru/Files/2/MegaTextRip.rar
Началось всё с того, что я решил найти наконец нормальный метод экстракции текста из уровней. Именно по «указателям» или ссылкам. А не просто парсить весь файл как неструктурированную свалку двоичного мусора.
Со времён Вихря я помню, как устроены четвёртые суб-субфайлы, а также оставшиеся подуровни (шестой, восьмой, десятый…) В самом конце – лист ссылок на экземпляры объектов. В каждом субфайле ссылки локальные, то есть дампить совсем не требуется, чтобы узнать, куда они ведут.
Итак, список ссылок, и все они указывают в этот же файл. Некоторые из них в «верхнюю» область, где виртуально размечаются блоки по 88 байт – полные начальные описания живых игровых объектов – их координаты, поворот, масштаб, анимация. Естественно тип и вид, некоторые дополнительные данные, которые уже зависят от самого рода объекта – разумно полагать, что персонаж с текстом должен значительно отличаться от простой бочки с алмазом.
Первые четыре из этих 88-ми байтов – прямой указатель. На некоторые дополнительные данные, которыми владеет этот экземпляр объекта. И вот их структура и тип уже конкретно сильно повязаны на роде объекта – тогда я сравнивал яйцо и Вихрь – последний хранил там свою толщину, высоту и позицию камеры, а яйцо описывало номер дракончика, который из него вылупляется. И данные эти занимают разные недетерминированный объём, который игра определяет, исходя из рода объекта. То есть его тип должен быть известен ещё по 88-ми основным байтам. И да, те прошлогодние исследования показали, что меня некий индекс в допустимых пределах можно превращать одни типы предметов в другие.
И всё бы оказалось очень просто, если бы абсолютно все ссылки из листа в конце вели строго на 88-ми байтовые блоки. Но ведь нет! Некоторые ведут не в верхушку, а в середину файла (где есть уже текст). Они тоже что-то показывают, но доподлинно лишь определено, что и там первые четыре байта – новый указатель на «что-то». И зачастую этими четырьмя байтами и оканчивается весь блок, ибо следующий из листа указывает на последующие же четыре байта, которые представляют собой ещё один указатель на «что-то другое».
Из этого следует, что игра различает 88 / ? / 4 блоки ещё до того, как перейдёт к ним. Либо по сличению адреса (блоки 88 располагаются значительно выше всего остального, причём «пустое пространство» в игре размечается на новые динамические 88-блоки…), либо каким-то другим флажком или списком, действующим не из листа (или есть закономерность, как зная тип всех предыдущий указателей, определить тип очередного). В любом случае, я пока не разгадал эту тайну, и «взлом» сводился к тому, чтобы научиться косвенно разделять указатели на истинно объектные «88» и всё остальное.
На самом деле меня волнуют не сами объекты, а лишь их дополнительные переменные, на которые указывают первые четыре байта (в файле, как я уже сказал, ссылки локальны, а уже в игре они заменяются на полные с «800хххххh»).
Основное наблюдение: все «говорящие» предметы – объекты 88. Ссылки ведут на область из восьми байт, после которых ВСЕГДА следует флаг «FF000000». А строго за ним располагается ссылка на ИМЯ этого персонажа.
Имя – просто текстовая строка с нулём на конце. И сама она идет немного ниже. После ссылки на имя расположенные четырёхбайтные ссылки на все его реплики. Чем последовательность ссылок заканчивается мне не совсем понятно, но можно проверять на нуль или на выход за границы файла, а также на неадекватность результирующей строки, но надо быть осторожным, ибо слишком суровое условие типа «char<32» может смести несколько экзотических строчек, но зато «char>127» работает всегда!
Кстати, строка «реплики» текста – совсем не такая простая, как имя персонажа. Выяснено наблюдениями: первый байт ВСЕГДА показывает, сколько начальных байт «лишних» включая его самого. Самый тривиальный случай: «01ssssss…», где «ss» – байты от строки. Возможны варианты вроде «022Assss…» или «032302ss…» – ну смысл ясен. Непонятно, чему максимально может равняться это число. Были и громадные «яяяя» типа «0901FFFFFFFFFFFFFFsssss…», так что рисково проверять «имя/реплика» через «char[0]<32». Фраза оканчивается нулём как обычная строка.
Что будет, если первый байт «00h» – не знаю, но ничего хорошего. Чтобы показать «пустую» строку используется «01000000h». Бывает и с флагом «FF000000h», это тоже надо отлавливать (а не прыгать чрез 254 символа вперёд…)
Я проводил эксперименты над байтами между первым (длина мусора) и начальным от строки – выяснил, что какая-то часть отвечает за позицию камеры при разговоре с персонажем на этой реплике. Можно было заставить её повернуться вообще в другую строну! Но большинство изменений игра не переживала, падая или зависая. Иногда портилась структура диалога, особенно вручение яиц или «(треугольник) TO TALK» – фразы путались, менялись местами или постоянно произносилась одна и та же.
Ну примерно понятно, там хранится поведение объекта на этой реплике, в частности, номер следующей и условия для её произношения. Если так вспомнить – различных действий может быть очень и очень много – от открытия порталов до появления мостов.
Также я проводил эксперименты над первыми восемью байтами «дополнительных переменных» объекта (после которых идёт «FF000000h» и ссылки на имя да на все реплики). Чего-то особо интересного и поддающегося простому изменению не выискал, но вот затирание первого байта (одного байта!) приводило к тому, что персонаж при разговоре произносил не свой текст из STR! То есть менялся его номер. Ну явно же одно байта мало, у нас ведь более 255 треков в Numeration! Значит, этот байт – индекс в каком-то списке, где треки распределяются уже более детально. Возможно, список должен был быть локален для уровня. Менялись сразу все его реплики, то есть этот байт отвечал за самого персонажа, а не за его текст. Где-то лежат списки, какие треки принадлежат каждому персонажу…
И тут я снова вспомнил про игровой пароль «квадрат-квадрат-круг-круг-квадрат-квадрат-круг-круг»! Который высвечивает диалоги уровня. Он разделяет их на «Type:» и «Msg:». Где фразы из одного «Type» произносит один персонаж. И да, наблюдения подтвердили, что первый байт дополнительных переменных – это и есть «Type». А все фразы персонажа располагаются именно в той последовательности, которую задаёт «Msg».
Меняя «тип», я мог заставить двух персонажей говорить одинаковые реплики, но не мог изменить сами треки по сути, то есть сопоставления «Type+Msg=?» у меня ещё не было.
Ударился в дизассемблирование. Надпись «Type: %d Msg: %d» лежит в SCUS. Нашёл её код, посмотрел где вызывается – лишь один раз в некой функции. Долгими замусориваниями я выяснил, что эта функция отвечает за «паузу» игры. Я посмотрел откуда она вызывается (а это была прикольная switch-select / case ассемблерная конструкция!), пробовал поменять вызов – дошло до того, что вместо меню паузы у меня отрисовывается весь VRAM, но игра на паузу-таки встаёт, и воспроизведение диалогов в чит-меню по-прежнему работает. Выходит, что «Type: %d Msg: %d» лишь рисует на экране тип и номер воспроизводимого трека, но совершенно не связано с его непосредственным воспроизведением.
Заодно проверил, действительно ли у них есть парсер «%d а-ля printf C++ стайл» – и да, оказывается в код игры встроен небольшой printf-щик, который заменяет %d на переданные аргументы. Вроде у него ещё «%08x» есть, я пока не исследовал подробно как он работает, но если удастся приручить эту функцию, то можно будет через неё выводить на экран содержимое интересующих нас регистров во время экспериментов с шифровкой-сжатием текста (только ещё предстоит найти «основной цикл» игры, где отрисовывается КАЖДЫЙ кадр); неплохо бы, а то я уж собирался свой конвертер из числа в строку на ассемблере писать…
Ладно, отвлеклись. Мне нужно было найти то место, откуда «Type: %d Msg: %d» берёт данные. Там постоянно происходила сверка с некой ячейкой, в которой хранился ноль. Другие ссылающиеся на эту ячейку тоже считывали этот ноль, а потом умножали (сдвигали) на большие степени. Я попробовал изменить на единицу или что повыше – все надписи на порталах, в меню, у персонажей, на летательных средствах и прочие – поменялись! Надписи из меню появились на порталах, названия сценок в меню, а некоторые и совсем пропали. Кажется, это какой-то адресный сдвиг. Но раз он нулевой, я мысленно подставил ноль в регистр, куда загружалось значение, и легко вычислил результирующий адрес (заодно и на экран его вывел….), куда обращалась «Type: %d Msg: %d» (по-идее сам ps2dis должен это знать, но из-за скрытого нуля он не мог вычислить выражение – засылающегося не считался).
По тому адресу располагались некоторые указатели – текущий и предыдущий – на данные, а все остальные – на функции. Я попробовал изменить текущий – и ура, «Type: %d Msg: %d» перестал видеть нормальные типы и номера треков. Но мало того, так ещё и все персонажи перестали устно говорить!
Супер. Перехожу по указателю и пытаюсь разгадать формат данных. А это было не сложно: первый байт – «Type», второй байт – «Msg», третий байт – номер XA канала в SPEECH.STR файле (которые у нас <32 в основных треках и >31 в Secret’STR, их ещё можно увидеть в «idx» файле, который создаёт jPSXdec, если открыть сохранённый индекс диска блокнотом), четвёртый байт игнорируется. Далее четыре байта – некая «длительность» трека, и ещё четыре байта – реальный адрес первого сектора данного трека в SPEECH.STR !
Это же то что доктор прописал! Через эту таблицу можно как узнать, так и изменить конкретный номер конкретного трека для конкретной фразы конкретного персонажа! http://klimaleksus2.ucoz.ru/Files/STARS/hack_0.png Итак ещё раз, 12 байт – номер персонажа, номер его реплики, канал XA, длительность в условных единицах и самое главное – LBA первый сектор трека в STR. По сути, нам нужен только он, но игра требует, чтобы номер XA канала совпадал с реальным.
LBA начала секторов конкретных треков тоже можно узнать в .idx сохранёнке jPSXdec, но придётся отнять «90000» от каждого номера – это LBA самого SPEECH.STR, а игра считает сектора по файлу, а не глобально по диску (разумно, если они захотят переместить файл на другой сектор, то придётся лишь где-то поменять смещение, а не в каждом уровне игры!)
Всё прямо-таки прекрасно, осталось узнать, где же лежит эта таблица треков на самом образе, а не в памяти. Нет, в четвёртом суб-субфайле её нет. Логично, ведь она едина на весь уровень, и можно прослушивать треки подуровней из основной локации, значит искать надо среди первых трёх субфайлов.
Ну в самом первом её точно нет, там ведь только звук и графика. Во втором тоже нет… Хорошо бы в третьем – будь я разработчиком, я бы поместил её в пустующую яичную зону! Но этой таблицы нет вообще в субфайле уровня…
Тогда может она в EXE? Нет, там бы это заняло кучу места, ведь на каждый уровень своя таблица, а она обычно не маленькая (кстати, окончанием служат «FFFF0000…», когда и Type и Msg равны 255)
Значит есть где-то субфайл, в котором располагаются таблицы сразу на все уровни – я провёл поиск по WAD, и ничего не нашёл… Да чего уж там, я много-много раз прошаривал весь ISO в надежде найти хоть кусочек это таблицы (вдруг из самого STR берётся?). Но не было ровным счетом ничего.
Значит, таблица генерируется динамически во время игры. Но чёрт, данные-то всё равно берутся откуда-то, не создаются ж из ничего! http://klimaleksus2.ucoz.ru/Files/STARS/hack_1.png Стал искать других ссылающихся на адрес (который указывает на Type=0 и Msg=0 – начало таблицы), но нашёл совсем немного – максимум лишь на чтение. Из-за того, что адресация прокачивается через «вечный ноль» и передаётся в функции, бедный дизассемблер не может обнаружить ссылку и не позволяет мне обойти по всем ссылающимся.
Тогда я стал копать соседние функции (несколько указателей, которые располагались после ссылки на таблицу). Сначала я по коду пытался понять, что они делают. Потом я просто попробовал найти их код – и хоп! Его снова нет…
Ни в SCUS ни в WAD. Ну не могут же ассемблерные функции появится сами собой!? Причём в каждом уровне это в общем случае ДРУГИЕ функции. Значит они откуда-то расшифровываются в память. Но простой поиск не давал ни одного хорошего совпадения.
Тогда я стал занулять вызовы функций или менять их местами. Некоторые вешали игру, но больше 80% проходили безболезненно. Я копал в SunriseSpring, и несколько первых функций отвечали за рождение яйца – если их нарушить, то оно либо не появлялось (от Хантера), либо не рождалось. Несколько следующих функций отвечали за спецэффекты. И вот это было интересно! Пыль, брызги, вспышки, звёзды, осколки, дымок – это такие небольшие спрайты, которые появляются и сразу исчезают. Так вот, после зануления функции они перестали исчезать! Они просто появлялись и оставались в 3D пространстве. Это было круто, никакими скриншотами не объяснить… После Зои, рождения яйца, разбивания банки или даже простого торможения остаются «спецэффекты». И они так и висят в воздухе застывшими как в желе! Можно даже выпускать пламя несколько раз – на концах остаются клубы дыма.
Мне сразу же стало любопытно, что будет, если ОЧЕНЬ много раз вызвать спецэффекты – заполонят ли они всё вокруг, или игра вылетит с нехваткой памяти. Оказалось всё гораздо умнее – когда спецэффектов становилось слишком много (причём независимо от типа – дым, звезда или искра), самые старые начинали исчезать. Но не сами по себе, а строго при появлении новых. Это неплохо раскрывает внутреннюю структуру их хранения. Скорее всего там определённый массив-очередь, и при добавлении новых смещается его индекс верхушки, при уничтожении – индексы высвобождаются. А если не удалять объекты, то индекс пройдёт кругом и начнёт затирать самые старые спецэффекты. Вернее даже не затирать, а лишь переопределять их.
Эффект не долговечен – до перезапуска уровня или выхода в другой, также имеет некоторые плохие побочные действия, но можно и как PEC код выложить…
Все оставшиеся последующие функции так или иначе были связаны с водой – плавание на поверхности, в глубине, поведение камеры в воде, скорость плавания, управление плаваньем, притяжение и столкновения – доходило до того, что прыгнув в ручей дракончик начинал «плавать» в его плоскости по воздуху не падая. И отменить было нельзя, прыжок тоже не работал. Была функция, отвечающая за нажатие треугольника.
Отдельного внимания заслуживает ещё одна функция, которая отвечала за всю живую природу вплоть до Спаркса. Стоило её отключить, как стрекоза замирала на месте и больше не следовала за дракончиком, но при этом оставалась живой и анимация воспроизводилось. Также не работали таблички, герои не разговаривали, надписи на порталах не возникали. Бочки оставались материальными, но не разбивались от огня; при бодании оставались на месте, но Спайро пробегал сквозь них. Овцы мирно щипали травку стоя на одном месте, из тоже нельзя было поразить. Камень, который надо разбить головой чтобы получить яйцо, больше головой не разбивался. Если же потом вернуть вызов функции на место, то все объекты снова оживали, Спаркс прилетал, а овцы разбегались.
Из всего этого я делаю заключение, что в эту область памяти помимо таблицы Type+Msg=STR выгружается ещё и сам код уровня! Конкретно поведение тех или иных объектов. Сами объекты отдельно, а их код здесь.
Поиск чего-либо по этим указателям по-прежнему ничего не давал, и я вдруг попробовал найти сами указатели. Мне казалось, что уж они-то точно должны на лету генерироваться…
Но я нашёл полное совпадение! И оно вело… на следующий субфайл! Все же помнят, что нумерация уровней идёт через один (98, 100, 102…). Так вот, соответствующий блок указателей хранится в самом начале следующего же субфайла (99,101,103…). Причём это начало полностью совпадает с тем, что находится в памяти. http://klimaleksus2.ucoz.ru/Files/STARS/hack_2.png И да, если компарить файл с памятью эмулятора, то «место, где должна быть таблица» оказывается в самом конце последующего субфайла. А при более детальном рассмотрении её даже видно! Однако данные совершено не те…
Это шифровка! Смотрите: http://klimaleksus2.ucoz.ru/Files/STARS/hack_3.png Ясно же, что за «00000000h» отвечает «CCC90880h». Я даже угадал способ шифрации. Сперва грешил на сложение и вычитание, но это оказался старый быстрый симметричный XOR ! все данные по 4 байта ксорились с неким ключом-паттерном, который записан… где бы вы думали? В заголовка файла! Начиная с четвёртого байта. А с двенадцатого байта идёт указатель на таблицу треков…
Мне нужна ссылка на начало шифрованной области. Её нигде не было, но исследовав несколько «следующих» субфайлов от разных уровней, я понял, что она мне и не требуется вовсе. Достаточно пропустить первые четыре байта, потом считать четырёхбайтный ключ, далее считывать по четыре байта и сверять с ключом – пока не совпадает – не трогать. Как только найдено полное совпадение (а это ВСЕГДА начало шифрованной области) – начиная со следующих же четырёх байт просто ксорить каждые считанные четыре байта с ключом. И так до конца файла!
Так мало того, ещё и зашифровывается обратно абсолютно так же – пропускаете декодированный файл через тот же механизм – он считает неизменный ключ и пойдёт его искать, а как найдёт (первое вхождение мы оставили нетронутым) – поксорит все нижеследующее с там же ключом, а в силу симметричности XOR, это восстановит оригинальные данные.
Ура! Теперь мы во-первых, знаем для чего нужные «следующие» субфайлы, а во-вторых, можем как считывать, так и менять сопоставление персонаж+фраза=озвучка. Остаётся открытым вопрос, зачем игра вообще что-то шифрует? Ей же гораздо проще вытащить неизменные данные, как начало этого же файла. Если же разработчики специально хотели скрыть эти данные от взломщиком/переводчиков, то могли бы придумать шифровку поизобретательнее чем банальный XOR, и ещё к примеру, закодировать сами указатели в заголовке, по которым я вычислил субфайл. Единственное, что я знаю, зачем применяют шифровку простым XOR – исключительно для блокирования автоматических попыток сличить данные между файлами. Например, чтобы встроить вирусный код в тело своей программы так, чтобы ни один антивирус его там не обнаружил. Почему XOR? Самый-самый быстрый. Почему не что-то помощнее? Нет смысла – машина и XOR не распознает, а от человека не спасёт и ничто другое…
Возвращаясь к сути полученных данных – для изменения номера STR на свой, нужно во-первых, найти Type персонажа, который его говорит, а также Msg номер реплики (всё это легче сделать в самой игре через тот же чит). Потом узнать начальный сектор целевого трека в SPEECH.STR, а также его XA канал. Остаётся загадочная «длительность» и единицы её измерения. Если поставить «0», то звука мы не услышим, а текст выведется на экран также быстро, как выводится текст от табличек. Если поставить большое число (стандартный разброс от «128» до «4512»), то текст будет выводиться по буковкам очень медленно, и продолжит делать это даже когда звук закончится. Что интересно – число никак не связано с длиной строки – одинаковая «длительность» заставит и длинную и короткую строки выводится на экран ровно нужное количество времени, в том смысле, что короткая строка будет выводиться дольше, а длинная наоборот, быстрее. А это означает, что игра ещё и вычисляет расчётную длину строки перед её выводом, умножает на нужный коэффициент и учитывает «длительность» из таблицы треков. А я-то думал, как все фразы на экране оканчиваются одновременно с голосом? Ну а для проекта перевода это означает, что НЕЛЬЗЯ в отличие от пиратов, завершать строки пробелами, даже если мы уместились в {Разницу} и наш текст оказался короче оригинального. Нужно оборвать строку нулём! Тогда её длина будет соответствовать скорости выведения на экран. А печатным строчкам в меню это хороший повод появиться ровно посередине экрана…
*Продолжение ниже*
Сообщение отредактировал aleksusklim - Среда, 10.07.2013, 01:40 |
| |
| |
| DrWho | Сообщение # 392 Воскресенье, 07.07.2013, 19:07 |
Мудрый Дракон
 Почетный Житель
«1371»
Где: Не в городе Драконов
|
Продолжение ниже.
Сообщение отредактировал DrWho - Среда, 10.07.2013, 01:33 |
| |
| |
| aleksusklim | Сообщение # 393 Понедельник, 08.07.2013, 09:22 |
фдулыгылдшь
 Редактор
«1066»
Где: Не в городе Драконов
|
*Начало выше*
Вспомним, с чего мы начали, а именно – поиск и извлечение строк из данных для объектов. Замечательно, первый байт показывает «Type» героя, потом семь байт неизвестно что, затем «FF000000h», а далее сразу указатель на его имя, за ним – перечисление указателей на реплики.
Чтобы добраться до «Type» из нижнего списка, нужно дважды разыменовать указатели: из листа на 88, оттуда по четырём первым на «Type», и если через семь байт расположен «FF000000h», то скорее всего дальше следует имя и тексты. Однако «FF000000» встречается в объектных (4,6,8,10…) суб-субфайлах очень часто, и велика вероятность холостого срабатывания. Можно ещё и парсить строку по критериям вроде минимальной длины, допустимых символов, чтобы начиналась с заглавных букв.. но всё это уже почти перебор, а я ищу более строгий однозначных метод.
Оказалось, что после указателя на 88-байтовый объект с текстом, через несколько строк в листе обязательно будет указатель на 4 байта, которые ссылаются на ИМЯ этого персонажа! А потом и далее через четыре байта по всем его репликам. Получается, что текст это как бы тоже объект…
Легко запутаться. Суть в том, что на каждую фразу ровно один указатель, он находится выше неё. Рядом с ним есть указатели на имя и остальные фразы. До них идёт «FF000000h» а ещё левее – дополнительные переменные объекта. Основная ссылка в листе ведёт далеко вверх на 88. Оттуда по указателю – на «Type». Если отойти на 12 байт правее, то встретим указатель на имя, НО туда же ведёт одна из последующих ссылок в листе. Для этого нам даже не надо знать объект – разыменовываем первую – попадаем на 4 байта, разыменовываем их – вот и имя персонажа. А если также поступить с несколькими следующими строчками листа, то выйдем на все тексты. Получается, что можно либо найти объект и идти вправо в поисках текстов, либо найти тексты и щупать слева их объект…
Я объединил оба метода! Я храню массив «потенциальных» ссылок на имена. Если разыменовывая предполагаемый объект я обнаруживаю флаг «FF000000h», то помечаю «номером Type» место справа, где должна быть ссылка на имя. И если (и только если) это действительно ссылка на имя, то разыменовывая некоторый следующий объект я обязательно наткнусь на ячейку массива, в которой что-то записано (это номер Type потенциального объекта), а значит, смогу тут же считать и имя, и все реплики (даже не дожидаясь указателей на них, просто помечу как «использованные» на всякий случай), и при этом буду знать Type!
Всю эту систему я отлаживал довольно долго, пока она не стала работать ровно так, как должна – вытаскивать абсолютно все тексты с сохранением Numeration. Оказалось, что иногда ссылки на реплики ведут в пустые ячейки, но их нельзя игнорировать, ибо «Msg» повязан на порядковом номере, и если пустую строку выкинуть, то все последующие номера реплик этого персонажа будут сбиты. «Скачок» Msg наблюдается и в чит-меню в самой игре, так что несуществующие строки (коих например полно на табличках Шейлы начиная со второго мира– они определяют текст, который должен быть показан, если кенгуру «ещё» не спасена, что в принципе невозможно при легальном прохождении; мы хотели туда свой рекламный текст вставить… думаю, что теперь мы сможем вставить туда ещё и свою озвучку – да, кажется таблички тоже могут «говорить»…) я решил заменять на прочерки «-», как и «несуществующие имена» (тех же самых табличек например).
Осталось всего ничего – вытащить LBA начальные сектора всех треков из STR, чтобы сверять с ними содержимое из таблицы сопоставления «Type+Msg». Нет, я больше не хочу доверять ни jPSXdec, ни три часа рипать образ через RipBin. У меня уже есть утилитка для замены треков в STR, я перечитал её код, и написал такую прогу, которая сможет считать XA каналы и LBA начала секторов напрямую из ISO образа! Реально, мне нужно по одному байту с каждого сектора, зачем для этого извлекать весь SPEECH.STR ? Зато нужно знать как структуру формата образа (24/2048/280), так и размер + LBA самого STR файла в нём. Это уже эмулирование и WinHex… Зачем? Все нужные данные лежат в корневом секторе, это номер 22. Там таблица размещения файлов. Вообще документацию ISO9660 надо бы почитать, а не как я – посмотрел и взломал формат самого образа.
Короче, шняга такая. В 22-ом секторе находится описание корневой папки. Первые два байта – длина структуры. За ней следует очередная структура, где первые два байта снова показывает длину. Если длина равна нулю, то список закончился. Структуры описывают либо расположение файла, либо папки, либо текущий/родительский каталог. Четыре байта (после размера структуры) – LBA первый сектор объекта. Потом четыре байта с чем-то непонятным. За ними четыре байта – размер объекта. Ещё 18 байт непонятного назначения, и один байт – длина имени объекта. Следом за ним – само имя, терминированное нулём. После него идёт что-то ещё, но оно мне уже не нужно. «Объект» может быть либо файлом, либо папкой, либо сигнатурой каталога. У последней нулевое имя, так что её отличить легко (она показывает текущий LBA той папки, в которой вы находитесь). Файл от папки отличается чем-то внутренним, во что я не стал вникать, а решил просто сканировать имя – если оно заканчивается на «;1» (вернее просто предпоследний символ – точка с запятой), то это файл, а иначе – папка.
Находя папки нужно рекурсивно перемещаться в их сектора и снова считывать структуры. Поскольку записи каталогов всегда первые две (свой и корневой), то вместо них можно просто по желанию выводить путь текущей папки, а ещё возможно опционально отключать рекурсию или не выводить полные пути.
Правда я не тестил свою программу на образах с каталогами, которые занимают более одного сектора (2048 байт), скорее всего, она не сработает, но вряд ли для PlayStation есть диски, на которых будет ТАК много файлов в одной папке, и уж тем более, в Spyro.
Вроде и всё, но для полной картины я решил придумать способ, как вытащить названия миров из SCUS. Поскольку указатели глобальные, то надеется на них бессмысленно (я не хочу ни хардкорить, ни опционально задавать адрес размещения EXE в памяти игры, ибо у GH он один, а у обычной версии совсем другой). Взял навскидку адрес «4096» и незатейливыми свериваниями (пока четвёртый байт каждого квартета равен 00h или 80h – чистой воды указатели) продвигаюсь к «Pete the Mountain Goat», который пропускаю просто по известной длине, а уж потом считываю список разделённых нулям строк.
Такой же шаткий парсинг пришлось применить и для нахождения таблицы TypeMsg в раскодированном «следующем» субфайле (указатель я опять не мог использовать по тем же причинам) – шёл снизу вверх от Type=255 и Msg=255 пока не встречу Type=0 и Msg=0; и даже для считывания листа ссылок на объекты и строки в объектных суб-субфайлах – не просто снизу вверх игнорить пустоту: в некоторых уровнях там располагались громоздкие указатели на память, так что сначала искал «не ноль», потом что-то «меньше размера файла но больше нуля», а затем уже «ноль», разворачивался и шёл вниз.
Теория закончилась, начинается практика. Я ведь написал аж 10 программ на Delphi ! Некоторые общего назначения, другие чисто под Spyro. Одни могут применяться независимо, а некоторые лишь в связке с остальными.
Общее управление проходит через «.bat» сценарии, и как обычно нужно «перетаскивать».
Файл «Namespace.bat» определяет имена программ, сценариев и рабочих файлов, его вызывает почти каждый скрипт.
Кстати, способ извлечения файлов напрямую из образа тоже пригодился, и он работает не только на BIN/ISO, но и на MDF, надо лишь выставить «24/2048/376». А также, для пиратских образов нужно знать имена файлов «WAD.WAD», «SCUS_944.67» и «SPEECH.STR» (если есть).
Поэтому первый сценарий задаёт параметры для образа. Например для GH это Для моего пиратского сборника (он у меня в MDF)
Основную подготовку выполняет автоматический скрипт «Kernel.bat» Он извлекает таблицу размещения файлов с образа, потом пытается найти в ней «SPEECH.STR», и если это удаётся, то запускает программу, которая извлечёт из образа номера секторов для всех треков. Но этого мало, затем другая программа перестраивает список так, чтобы он соответствовал нашему Numaration (если XA канал ниже 32, то нумеровать с нуля, а если нет, то с тысячи) Далее в той же файловой таблице обнаруживаются SCUS и WAD, а потом извлекаются в рабочую папку (убедитесь, что на жёстком диске достаточно места!)
Остаётся три этапа – работа со SCUS, работа с WAD и сбор общего списка. Их выполняют разные скрипты.
«ScusWork.bat» извлекает из EXE названия всех уровней, а потом пропарсивает их так, чтобы номер «98» стал «098».
«AllLevelsWork.bat» работает с WAD куда комплексное. Он поочерёдно извлекает по два субфайла от 98 до 170 (171) и передаёт их для «OneLevelWork.bat». Тот сначала расшифровывает второй субфайл, потом вытаскивает из него таблицу треков, затем начинает поочередно извлекать из первого субфайла подуровни от основного четвёртого до «пока не перестанет извлекаться», ищет описанным способом все тексты подуровня и вызывает основную программу, которая сольёт их вместе с номерами Str в один большой документ (который каждый раз дополняется, а не перезаписывается).
Остаётся лишь финально пропарсить этот документ, чтобы вставить туда названия уровней и выдать читабельные версии. Сейчас на выходе есть один общий красивый документ с текстами в стиле Хранилища, один простой текстовый список, табличный формат для Numeration, файл для моей базы данных и простой список всех персонажей, разделённый по уровням, а глобальные герои, появляющиеся более чем в двух уровнях, вынесены в его начало.
Там же определяется длительность (для Numeration) каждого трека в STR исходя из количества XA секторов, занимаемых дорожкой. Сразу же пришла простая формула, связывающая размер XA с «длительностью», записанную с таблицу TypeMsg: это количество целы занятых секторов, умноженное на 32. Например, самые короткие реплики занимают пять секторов, значит «длительность» равна «(5-1)*32» = 128; Самая большая фраза Бианки имеет длительность 4512, значит «(4512/32)+1» = 142. Примерная реальная продолжительность трека в секундах вычисляется делением количества секторов на коэффициент «4,6875», погрешность составляет менее двух десятых секунды («с запасом») для первой реплики Бианки и чуть больше 0,7 для самых коротких треков.
*Окончание ниже*
Сообщение отредактировал aleksusklim - Среда, 10.07.2013, 01:34 |
| |
| |
| DrWho | Сообщение # 394 Среда, 10.07.2013, 01:33 |
Мудрый Дракон
 Почетный Житель
«1371»
Где: Не в городе Драконов
|
Продолжение ниже.
Добавлено (10.07.2013, 01:31) --------------------------------------------- Ну и чтоб пост не пустовал, отпишусь: Цитата (steeldragon) Во всех субфайлах хранится информация и помимо этой. (Окружение уровня, текстуры, и так далее. Подробнее: Энциклопедия - Структура субфайлов WAD). А в котором можно шрифт извлечь и вставить в GH? Я хочу просто потестировать этот момент в атласе, меню пауза? субтитрах и катсценах. И чем, я пробовал тайловым (как-то там) штукой сделать - но там даже эксешника нет - а расписанный метод я не понял совсем.
Добавлено (10.07.2013, 01:33) --------------------------------------------- Ну и вопрос на засыпку: чем можно поковырять STR файл? Хочу попробовать озвучку перенести в STR от GH.
Сообщение отредактировал DrWho - Среда, 10.07.2013, 01:36 |
| |
| |
| aleksusklim | Сообщение # 395 Среда, 10.07.2013, 01:34 |
фдулыгылдшь
 Редактор
«1066»
Где: Не в городе Драконов
|
*Начало выше*
Ну что ж, осталось лишь описать интерфейсы всех своих программ и скриптов…
«FastExportWad.exe» – клон «FastWad», который может только извлекать субфайлы. Плюс, меньше пишет на консоль. Принимает путь к WAD, имя куда извлечь и номер субфайла.
«SpyroLabels.exe» – вытаскивает названия уровней из EXE. Принимает путь к SCUS, имя для сохранения текстового документа, номер последнего уровня и шаг нумерации. Поскольку располагаются они в обратном порядке, последние два аргумента будут «170 -2» Формат выходного файла: первая строка – номер уровня, следующая строка – полученное название. И так до конца файла (пустая строка).
«SpyroObject.exe» – основной алгоритм поиска объектов и их текста в субфайлах подуровней. Принимает извлечённый четвёртый (шестой, восьмой…) суб-субфайл, имя для выходного документа и максимальную длину строки, которую я определяю как 1024 символа. Формат выходного файла: строгий. 4 символа – разделитель «----» или число – длина строки (десятичный формат с начальными пробелами, не более «9999»). Затем 1 символ табулятора. Далее если это строка с разделителем «----», то три символа – десятичное число – «Type» индекс объекта (не более «255») и два символа переноса строки; если строка начиналась с числа-длины, то после табулятора идут столько (длина) символов – текстовая строка, оканчивающаяся двумя символами переноса строки. Если предыдущая строка была с разделителем и числом «Type», то текущая строка отражает имя объекта. Все следующие строки построены по принципу этой, но в них в корректной последовательности «Msg» указаны все реплики персонажа. До тех пор, пока вместо длины не появится очередной разделитель «----». Файл начинается с разделителя и заканчивается разделителем и табулятором.
«SpyroCodeCrypt.exe» – декодирует и шифрует обратно «следующий» субфайл уровня. Если передан один аргумент – имя субфайла – программа модифицирует его самого, если два аргумента, то первый считывается, а во второй сохраняется результат.
«SpyroCodeCrypt.exe» – Распечатывает таблицу «TypeMsg» из расшифрованного «следующего» субсубфайла. Принимает декодированный субфайл и имя документа для сохранения. Формат выходного файла: строгий, но читаемый. По строкам расписаны значения «Type», «Msg», «Канал XA», «длительность» и «LBA начальный сектор». Потом пустая строка. И так до тех пор, пока не выйдет «255/255/0/0/0» (всегда в конце). Каждая строка после нескольких табуляторов выносит «в центр» экрана те же самые данные, но уже с подписями. Программа также способна принимать свой же изменённый документ, чтобы модифицировать исходный декодированный субфайл. Для этого передайте ей любой третий аргумент (из второго данные пишутся в первый). Внимание, используются только значения из левого столбца в документе! И да, лучше ничего не добавлять и не забирать, а лишь изменять цифры. В конце всегда должен быть «255/255/0/0/0», иначе игре же хуже; а первым должен быть «0/0/…», иначе второй раз не откроется. В теории можно расширить список, но на практике я это не проверял, скорее всего общий размер фигурирует где-то в другом месте.
«CdSector.exe» – десять аргументов… Строит список файлов на образе диска. Первый – путь до образа, второй – имя для сохранения документа. Третий – начальный сектор с корневой папкой (обычно «22»), четвёртый-пятый-шестой – настройки «заголовок-тело-хвост» формата образа. Для ISO это «24», «2048», «280»; для MDF это «24», «2048», «376». Что обозначают эти числа я писал ещё в японской теме для RipBin. Здесь же более общий случай, поэтому «тело» обычно всегда будет «2048». Последние четыре аргумента – опции: «0» отключено, «1» (или что угодно) – включено. Первая – обрабатывать ли все каталоги рекурсивно? (Да – выведет и из всех дочерних, Нет – только из указанного сектора, если третий аргумент «22», то лишь из корневого). Вторая – Показывать ли найденные папки? (Да – обнаруженные папку будут выведены наравне с файлами, Нет – папки может и будут обработаны рекурсивно (предыдущая опция), но не выведутся в документ). Третья – выводить ли имя текущего каталога? (Да – в каждом каталоге перед списком окажется его имя и адрес, Нет – список будет содержать только найденные папки (если предыдущая опция) и файлы). Четвёртая – Отображать ли полные пути? (Да – перед именем каждого файла и папки будет выведен путь до него, начинающийся с «\», Нет – имена будут выводиться так, как они записаны в структуре каталога) Формат выходного файла: зависит от настроек, но принцип такой: первая строка – два числа и символ (через пробелы) : LBA первый сектор объекта, размер объекта в байтах и флаг: «P» для текущего каталога, «F» для найденного файла, «D» для найденной папки. Следующая строка – имя объекта. В конце списка пара чисел «сектор размер» будет «0 0». Рекурсия каталогов проходит только после полной распечатки текущего, то есть сначала перечисляется содержимое имеющейся папки, а за ней программа последовательно переходит по папкам сверху вниз. Файлы имеют в конце имени «;» и цифру, папки ничего, а информация о текущем каталоге оканчивается на «\».
«GetFromDisc.exe» – извлекает файл из образа диска напрямую. Аргументы: путь к образу, имя для сохранения, LBA первый сектор файла, размер файла в байтах, и троица настроек формата образа «24», «2048», «280» (или «24», «2048», «376» для MDF). По сути, извлекать файлы можно с любого типа образов, если они устроены одинаково с ISO/BIN/MDF, если рассмотреть неизвестный формат получше и подобрать эти числа. У программы нет функции засовывания файла обратно в образ, ибо ни к чему хорошему это не приведёт (сгорают контрольные суммы), зато её код до смешного простой.
«StrDump.exe» – распечатывает начала треков и их каналы из STR файла на образе. Аргументы один-в-один такие же, как у предыдущей программы (GetFromDisc). Формат выходного файла: список. Четыре числа в каждой строке: LBA первый сектор трека, его размер в секторах, номер XA канала, «Sector stride» – число, показывающее через сколько других треков перемешены нужные (jPSXdec кстати, это тоже запоминает в свой «idx»). Завершается флагом «0 0 0 0».
«SpeechList.exe» – Считывает выходной документ от предыдущей программы (StrDump) и строит на его основе наш Numeration список. Два аргумента (угадайте какие?) Формат выходного файла: тоже список из четырёх чисел: STR номер трека (в нашем смысле), LBA первый сектор, количество секторов трека, и его XA канал. Кончается ничем.
«TextMerge.exe» – сливает все необходимые данные в единый текстовый документ. Первый аргумент – выходной файл от «SpyroObject.exe»; второй аргумент – выходной файл от «SpyroStrInfo.exe»; третий – выходной файл от «SpeechList.exe»; четвёртый – документ для сохранения: если существует, то происходит дозапись, если нету – создаётся новый; пятый аргумент опциональный – номер уровня WAD (строка) : если передан, то сразу записывается в выходной файл со звёздочки («*098»), если не передан – допись просто происходит через несколько строк – это необходимо чтобы отличать уровни от подуровней – при обработке уровня надо передать его номер и он впишется, а подуровни просто потом допишутся через пары пустых строчек. Формат выходного файла: огромный список! Строки, начинающиеся со звёздочки – начала уровней., они отделяются пустыми строками с обеих сторон (первая строка по идее должна сразу начинаться со звёздочки). Очередная непустая строка – имя персонажа. За ним перечисляются реплики до тех пор, пока не встреться пустая строка; каждая реплика занимает две строчки: на первой четыре числа – наш номер STR, LBA первый сектор трека, размер трека в секторах, и «длительность» фразы из игры (номер канала XA я посчитал лишним и вообще ненужным); а на следующей строке уже сам текст реплики. После пустой строки снова ожидается имя персонажа и его реплики, но: если пустых строк две или более – это разделитель подуровней; если встретилась звёздочка – на этой же строке имя нового уровня. Если фраза не наёдена в STR и не может быть сопоставлена в номером для Numeration – вместо четырёх чисел выводится «0 0 0 0»
«FinalOutput.exe» – Последний монстр, принимает семь аргументов. Первый: имя выхода от предыдущей программы (TextMerge), ещё пять файлов являются выходными: нормальный, текстовый, табличный, список персонажей, код базы данных. Последний седьмой аргумент – текстовый документ из-под «SpyroLabels.exe» (дополнительно прошедший через «FixNames.bat») с названиями уровней, сопоставляемыми с их номерами. Форматы выходных файлов: Нормальный: разделитель уровней «~~~~~~~~» за ним после переноса номер WAD, двоеточие и после пробела название уровня; разделитель подуровней «~~» отдельным абзацем; имена персонажей в круглых скобках с двумя пробелами в начале, за ними строки, начинающиеся с номера STR в квадратных скобках с табулятором на конце, и на этой же строке сам текст реплики. Пустые строки отделяют персонажей с их текстом и разделители. Текстовый формат – простой список: номер STR, номер уровня WAD, имя персонажа, двоеточие с пробелом и текст реплики. Цифры выровнены пробелами, а после них вставлены табуляторы. Табличный – почти как текстовый, но везде стоят «||» для наших wiki-страниц. Под номером уровня WAD через «<br / >» ещё и вставлена приблизительная длительность трека в секундах (формулу я приводил выше). Список героев – всё просто: сначала «глобальные», потом через разделители «~~~ WAD ~~~» локальные (если есть). Формат базы данных пока описывать не буду, расскажу о нём когда про эту самую базу буду говорить.
Ну и все сценарии:
«FinalWork.cmd» – запускает «FinalOutput.exe» и сортирует текстовый и табличный вывод через «Sorting.bat».
«AllLevelsWork.bat» – принимает имя WAD-файла и делает кучу действий, основное – вызов «OneLevelWork.bat» в цикле.
«FixNames.bat» – принимает вывод от «SpyroLabels.exe» и добавляет двузначным цифрам начальный ноль, перезаписывает исходный файл.
«ISO_rip.bat» – принимает ISO образ GH и производит все действия над ним.
«Kernel.bat» – внутренне использование, принимает образ от «ISO_rip.bat».
«MDF_rip.bat» – клон «ISO_rip.bat», настроенный на ParaDoX образ MDF (и там нет SPEECH.STR). Можете заменить переменные в файле под себя.
«Namespace.bat» – вызывается всеми, закрепляет имена файлов, можете отредактировать его.
«OneLevelWork.bat» – принимает три аргумента: первый – субфайл уровня, второй – следующий субфайл, третий – номер основного уровня (ноль в начале для «98» добавляется внутри). Сливает текст из одного уровня куда следует.
«ParseLba.bat» – принимает вывод от «CdSector.exe» и имя интересующего файла (без «;1» на конце). Записывает в переменные %t_lba% и %t_size% сектор и размер если нашёл, и нули, если нет.
«ScusWork.bat» – принимает SCUS и вытаскивает оттуда названия уровней. Сам вызывает «FixNames.bat».
«Sorting.bat» – использует «sort», чтобы отсортировать переданный файл. Перезаписывает его.
Есть ещё несколько самостоятельных батников: (все они принимают один файл и выводят результат по тому же имени с новым добавленным расширением, просто наблюдайте появление нового файла рядом с исходным)
«CdSector_ISO.bat» – перетащите на него образ и он построит список файлов на нём. «CdSector_MDF.bat» – клон для MDF.
«Code_crypt.bat» – перетащите на него «следующий» субфайл, и он его раскодирует/закодирует.
«Get_levels.bat» – перетащите на него SCUS и он вытащит оттуда имена файлов.
«Get_SCUS.bat» – перетащите на него образ GH, и он вытащит оттуда SCUS. «Get_WAD.bat» – клон для WAD.
«Spyro_text.bat» – перетащите на него объектный субфайл, и он вытащит вам текст оттуда.
«Str_export.bat» – перетащите раскодированный «следующий» субфайл и получите TypeMsg таблицу треков. «Str_import.bat» – если изменили вывод предыдущего то можете всавить его обратно – перетаскивать надо по-прежнему за раскодированный «следующий» субфайл.
«StrDump_GH.bat» – перетащите на него образ GH, и он вытащит вам информацию о секторах.
Кстати, многие сценарии ломаются, если не передать какой-то параметр или сделать это неправильно. Никаких звёздочек не пишите, а том там есть команды удаления файлов, будет плохо если попадёт del /f /q * А все программы вообще не запустятся если не соблюдать количество аргументов. Всё что они пишут на консоль – название и статус: «OK !» – доработала до конца, «ERROR !» – возникла ошибка. Никакой справки по аргументам в них нет, лучше читайте вызовы из bat-фалов.
Последнее, что нужно сказать. Я понял, откуда у «панд» так много текста! Помните подуровень, где надо было на лодке защищать от носорогов целую кучу панд? Так разработчики тупо копирнули код одной из «говорящих» панд более десяти раз. При этом в игре текста нет, а в файле тонны его копий… (забавно, мои пираты его даже по-разному переводили!) В подуровне MoltenCrater где надо было прикрутить головы человечкам – всех их зовут «Rocky». Текстов нет, а имена есть. У всех трёх козлов одинаковое имя, текст и номера STR. Но имя меняется во время игры, они берётся из EXE. В MidnightMountain Бианка скопирована дважды, ибо она появляется как в начале уровня, так и возле ворот Колдуньи. И весь её текст тоже сдублирован дважды, со ссылкой на те же треки из STR (если найти где концы какой реплики, то в ненужный текст преспокойно влезут все данные для моего Вихря!) Такое встречается много где, например у Шерифа. А у Зои на табличках для Спаркса есть «Yes.» да «No.» и как ответа на вопросы и как отдельные реплики.
И вообще, надо новый Нумерейшн делать…
Вот весь вывод для Greatest Hits: http://klimaleksus2.ucoz.ru/Files/2/GhText.rar
А вот инвалидский без STR (GH’овские не подошли по LBA секторов) от Парадокса: http://klimaleksus2.ucoz.ru/Files/2/ParaDoX.rar
Ещё раз ссылка на мой архив: http://klimaleksus2.ucoz.ru/Files/2/MegaTextRip.rar
but nobody came
Сообщение отредактировал aleksusklim - Среда, 10.07.2013, 01:37 |
| |
| |
| steeldragon | Сообщение # 396 Среда, 10.07.2013, 10:42 |
Старейшина Драконов
 Редактор
«422»
Где: Не в городе Драконов
|
Мощно. Надо будет посмотреть, особенно SpyroObject...
Цитата (aleksusklim) Ясно же, что за «00000000h» отвечает «CCC90880h». Я даже угадал способ шифрации. Сперва грешил на сложение и вычитание, но это оказался старый быстрый симметричный XOR ! все данные по 4 байта ксорились с неким ключом-паттерном, который записан… где бы вы думали? В заголовка файла! Цитата Остаётся открытым вопрос, зачем игра вообще что-то шифрует? Насколько я помню, именно таким (или почти таким) способом шифровки пользуется libcrypt: Цитата (consolecopyworld.com) The routine that performs the check on the disk uses the hardware registers of the CD-ROM(1F80180X) directly and it memorizes the temporary data in the scratchpad memory (1F800000-1F8003FF). It then calculates a 16bit number with a recursive algorithm. (Magic Word!) this magic word is then used as a parameter for the subroutines, the value is stored in some register of COP0 (coprocessor of system), leaving it in the low part of the BPC register until the program finishes. ... The second routine, that that checks the presence of the MagicWord in the BPC, is implemented in different ways in the various games that use it: some perform the check immediately (FF8 for example), others wait until a certain level (Spyro2)...
|
| |
| |
| aleksusklim | Сообщение # 397 Четверг, 11.07.2013, 16:51 |
фдулыгылдшь
 Редактор
«1066»
Где: Не в городе Драконов
|
Цитата (steeldragon) Насколько я помню, именно таким (или почти таким) способом шифровки пользуется libcrypt
А можно полную ссылку на источник? По тексту я нашёл лишь: http://badtaste.free.fr/psx/outils/cours.html
А вообще да, это отголоски libcrypt.
Я провёл эксперименты над Платиной, причём у меня откуда-не-помню есть «.sub» файл, который если положить рядом с образом, то Epsxe эмулирует его как оригинальный диск, и в Cloud Spires не вылетает ошибка. Следовательно я имел возможность дампить память после ошибки защиты и без неё, чтобы найти отличия.
И да, во всём виноват XOR. Но не совсем тот, который расшифровывает «следующий» субфайл. Я поковырялся в коде и понял, что такое «ключ» для расшифровки. Оказывается это просто адрес, на котором нужно перестать ксорить! Система загружает «следующий» субфайл в память по статичному адресу, потом считывает ключ с четвёртого байта, потом прыгает вперёд ровно на «1000h» (4096 байт) и начинает производить XOR всех данных с ключом до тех пор, пока текущий адрес не станет больше или равен значению ключа. По сути наш ключ – это просто размер «следующего» субфайла плюс его адрес загрузки минус количество нулей на конце.
При правильной эмуляции с субканалом я сдампил память и сравнил её с файлом из WAD. Естественно, не совпадало. Тогда я расшифровал его своей программой (можно выпустить новую версию, раз теперь известно назначение ключа) и сравнил ещё раз: основная часть совпала, а небольшой участок остался шифрованным, но не так, как в остальном (WAD и дамп). С этими тремя я сравнил дамп при ошибке защиты, и в нём этот же участок выглядел опять неправильно. Через дизассемблер я нашёл функцию, которая им занимается, и там снова был XOR. Но сама функция находилась в этом же «следующем» субфайле, то есть была расшифрована первым XOR (который с ключом).
Выходит, что система защиты влияет на вторичный ключ, которым уже расшифрованный «следующий» субфайл расшифровывает небольшую часть себя. Откуда он берёт адрес, размер и ключ – не совсем понятно, но скорее всего, они просто вшиты в его тело (которое изначально шифровано первым XOR).
Второе наблюдение: если сохранить savestate при загрузке с субканалом, то потом даже если поменять образ на «нелегальный» и загрузить savestate, то больше ошибок не случится. Из чего я делаю вывод, что игра ОДИН раз сканирует легальность диска, а потом просто обращается к результатам проверки.
В описании libcrypt сказано, что хранит магическое слово игра чёрти где, и просто так в дампе его не будет. Но где-то в эмуляторе оно же есть! Плюс известно, как именно libcrypt сканирует диск – обращаясь к ячейкам-регистрам дисковода, их можно подсветить в ps2dis если загрузить ps1.map через «label definition file» в меню.
В итоге есть три способа обойти libcrypt на Платине: 1) Найти и уничтожить функцию, которая сканирует диск на легальность. Вместо неё написать код, который даст такой результат, будто прежняя функция подтверждает легальность любого диска. 2) Найти место, куда сохраняется магическое слово и само это слово (например, сравнивая память эмулятора в ArtMoney). Потом просто всунуть куда-нибудь функцию, которая положит магическое слово в нужную ячейку или регистр. Либо понять, откуда шифрованная функция в «следующих» субфайлах получает правильный или неправильный ключ, и сделать так, чтобы он всегда был правильным. 3) Полностью разрушить все XOR-шифрования, а в WAD поместить заранее расшифрованные (сдампенные) «следующие» субфайлы – игра будет пытаться их неправильно расшифровать, но мы занулим саму шифровку (поэтому независимо от правильности ключа, функция всё равно ничего не изменит), а поскольку файлы будут загружены уже расшифрованные нами, то всё будет «просто работать». Альтернатива – найти ключи-смещения-размеры для каждого уровня, которые использует вторичный XOR, и «перешифровать» части файлов так, чтобы неверный ключ наоборот становился верным и выдавал корректные данные (это легче чем кажется, XOR же симметричный!)
Результаты испытаний: 1) Найти первую функцию тестирования диска оказалось слишком сложно… Ссылок на аппаратные регистры дисковода очень много – я пытался занулять вызывающие их функции, но игра либо не загружалась совсем, либо наоборот работала абсолютно правильно (я пытался испортить алгоритм сканирования диска, чтобы на «легальном» образе происходила ошибка при входе в Cloud Spires) 2) Я искал через ArtMoney различия в памяти эмулятора при загрузках верного и неверного образов, а также после savestate на легальных и нелегальных запусках. То что нашёл не сработало… Пробовал сверять память, куда частенько идёт обращение в областях возле XOR, но полная её подмена на данные с «нелегального» запуска ничуть не помешали игре зайти в уровень корректно. 3) Предварительная расшифровка WAD может сработать, но есть две проблемы: нужно дампить и дизассемблировать КАЖДЫЙ уровень, что отнимет очень много времени… А во-вторых, ещё не факт что мы вот так просто найдём ВСЕ места, где игра обращается к системе защиты. Может она не только запуски уровней трассирует? (вылетание при касании воды, лавы или проигрышах (Buzz’s Dungeon) – это всё равно пока «следующий» субфайл, ибо там и описано поведение воды, камеры и спецэффектов. Но я имею в виду что-то совершенно другое, например появлении «Зои» понятно где…)
Что хочу сказать – Платину можно взломать. Но я пока не стану этим заниматься. Когда дойдём до перевода Платины – вернусь к взлому. Либо если кому интересно, можете сами попробовать снять libcrypt, или хотя бы проверить гипотезу о полном удалении ксора отовсюду. Насчёт GH – у нас всё в порядке. Даже если за первым ксором скрывается ещё один, то в обычном режиме он нормально расшифровывает критические данные, и игра всегда работает. Откуда берётся Зоя при загрузке подставной сохранёнки с карты памяти? Ещё не знаю, но во-первых я не уверен что это вообще связано с libcrypt, а во-вторых я не буду убирать Зою, мы же хотим в ней послание оставить!
but nobody came
Сообщение отредактировал aleksusklim - Четверг, 11.07.2013, 17:29 |
| |
| |
| DrWho | Сообщение # 398 Четверг, 11.07.2013, 18:31 |
Мудрый Дракон
 Почетный Житель
«1371»
Где: Не в городе Драконов
|
Цитата (aleksusklim) Что хочу сказать – Платину можно взломать. Но я пока не стану этим заниматься. Когда дойдём до перевода Платины – вернусь к взлому. Либо если кому интересно, можете сами попробовать снять libcrypt, или хотя бы проверить гипотезу о полном удалении ксора отовсюду. Взломать можно, но уже смысла нет, ибо выпилена эта самая проверка со всеми последствиями и теперь платиновая версия проходима, хоть конвертируй в ISO или в MDF/MDS. Кто выпилил - сами знаете/узнаете кто.
Цитата (aleksusklim) Откуда берётся Зоя при загрузке подставной сохранёнки с карты памяти? Ещё не знаю, но во-первых я не уверен что это вообще связано с libcrypt, а во-вторых я не буду убирать Зою, мы же хотим в ней послание оставить! Зоя, которая говорит, что у тебя неправильно-хакнутая версия?
Продублирую два вопроса, что выше оставил:
Цитата (steeldragon) Во всех субфайлах хранится информация и помимо этой. (Окружение уровня, текстуры, и так далее. Подробнее: Энциклопедия - Структура субфайлов WAD). А в котором можно шрифт извлечь и вставить в GH? Я хочу просто потестировать этот момент в атласе, меню пауза? субтитрах и катсценах. И чем, я пробовал тайловым (как-то там) штукой сделать - но там даже эксешника нет - а расписанный метод я не понял совсем. Ну и вопрос на засыпку: чем можно поковырять STR файл? Хочу попробовать озвучку перенести от Вектора в STR от GH.
|
| |
| |
| steeldragon | Сообщение # 399 Четверг, 11.07.2013, 19:14 |
Старейшина Драконов
 Редактор
«422»
Где: Не в городе Драконов
|
Вообще-то, в заголовке цитаты есть ссылка на сайт... Раз уж спросили, всё, что мне известно по данному вопросу: http://consolecopyworld.com/psx/psx_libcrypt_tutorial.shtml http://redump.org/guide/libcrypt/ ... Цитата (aleksusklim) В итоге есть три способа обойти libcrypt на Платине: Libcrypt в Платине выпилил brill. Вот SCUS, с оригиналом в архиве: https://dl.dropboxusercontent.com/u....z Могу выложить остальное, если надо. ... Цитата DrWhoА в котором можно шрифт извлечь и вставить в GH?В любом_001_субфайле_субфайла_уровня.wad. Шрифт Атласа в 180 субфайле основного WAD. ... Цитата Ну и вопрос на засыпку: чем можно поковырять STR файл? Вилкой Тут где-то рядом есть алгоритмы и программы для извлечения/записи звука. В Энциклопедии, например. А в недавнем мега-тексте - программа для нахождения треков.
|
| |
| |
| DrWho | Сообщение # 400 Четверг, 11.07.2013, 19:26 |
Мудрый Дракон
 Почетный Житель
«1371»
Где: Не в городе Драконов
|
Цитата (steeldragon) В любом_001_субфайле_субфайла_уровня.wad. Шрифт Атласа в 180 субфайле основного WAD. Ок, только чем извлекать и заменять? Цитата (DrWho) я пробовал тайловым (как-то там) штукой сделать - но там даже эксешника нет - а расписанный там метод я не понял совсем.
|
| |
| |
| steeldragon | Сообщение # 401 Четверг, 11.07.2013, 19:35 |
Старейшина Драконов
 Редактор
«422»
Где: Не в городе Драконов
|
Цитата (DrWho) Ок, только чем извлекать и заменять? PGG2. Ссылка - https://dl.dropboxusercontent.com/u....GG2.rar Инструкция где-то рядом...
|
| |
| |
| DrWho | Сообщение # 402 Четверг, 11.07.2013, 22:11 |
Мудрый Дракон
 Почетный Житель
«1371»
Где: Не в городе Драконов
|
Буду пробовать. Благодарю за ссылку.
Цитата (steeldragon) Инструкция где-то рядом... Глянем.
Сообщение отредактировал DrWho - Четверг, 11.07.2013, 22:13 |
| |
| |
| aleksusklim | Сообщение # 403 Суббота, 13.07.2013, 19:23 |
фдулыгылдшь
 Редактор
«1066»
Где: Не в городе Драконов
|
Цитата (DrWho) Взломать можно, но уже смысла нет, ибо выпилена эта самая проверка со всеми последствиями и теперь платиновая версия проходима, хоть конвертируй в ISO или в MDF/MDS.
Неужели!? Что ж, я этого не знал. Сам Yams просил меня когда-нибудь её взломать…
Цитата (DrWho) Зоя, которая говорит, что у тебя неправильно-хакнутая версия?
Да, система защиты отключена, но не удалена из GH, поэтому её можно заставить включится, причём для этого не нужно воздействие извне – достаточно загрузить игру с особой сохранёнки с карты памяти через игровое меню. В чём «особенность» сохранёнки я пока не выяснил, но у меня таковая есть, и глюков она плодит сразу массу, исключая лишь псевдоуровни четвёртого мира, вечнозелёного Спаркса и главный вылет на Колдунье. Зойка + нелепый Толстосум продающий несуществующих животных + пустые таблички на порталах присутствуют!
Цитата (DrWho) А в котором можно шрифт извлечь и вставить в GH? Я хочу просто потестировать этот момент в атласе, меню пауза? субтитрах и катсценах.
Не путайте игровой шрифт и шрифт в атласе. Второй пока не взломан, и лично я его не исследовал и в глаза через PVV не видал…
Цитата (DrWho) И чем, я пробовал тайловым (как-то там) штукой сделать - но там даже эксешника нет - а расписанный метод я не понял совсем.
Ну чувак…
Это JAVA, а её можно двойным щелчком запускать!
Попробуйте либо этот BAT либо этот EXE: http://klimaleksus2.ucoz.ru/Files/2/TmExe.rar Основной JAR файл переименуйте в «tm.jar»
Цитата (DrWho) Ну и вопрос на засыпку: чем можно поковырять STR файл? Хочу попробовать озвучку перенести от Вектора в STR от GH.
Если LBA не совпадают, то вам придётся долго мучиться…
В любом случае, НЕ конвертируйте в XA те MP3, который вам выдал jPSXdec.
Цитата (steeldragon) Вообще-то, в заголовке цитаты есть ссылка на сайт...
Поэтому я и предусмотрительно сказал «А можно полную ссылку на источник?»
Цитата (steeldragon) Раз уж спросили, всё, что мне известно по данному вопросу:
Да, всё то же, ничего нового. Но по второй ссылке советуют-таки эмулировать субканал, а не взламывать код.
Цитата (steeldragon) Libcrypt в Платине выпилил brill.
А он рассказал, КАК это сделал? Наверное нет – вполне в его стиле – ни тебе исходного кода, ни описаний…
Хм… А на кой он ужал его через UPX !? Типа чтоб никто не докопался до самих изменений?
Цитата (steeldragon) Могу выложить остальное, если надо.
Хе-хе, нет уж, это я выложу остальное.
Итак, вначале я решил сэмулировать фикс и посмотреть, реально ли он работает. Да! Тогда я сдампил память и восстановил из него несжатый EXE. Поскольку тот забит мусором, я сравнивал его с оригинальным и подчистил структуру. Даже заголовок оставил: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_01.png
Файл по сути отличался лишь в двух местах, и вот второе: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_02.png
Я потестил и оказалось, что даже без этих изменений защита не активируется. Тогда я срезал и копирайт из заголовка, и вот эту ерунду в конце. Осталось всего 24 различных байта…
Дизассемблировал и посмотрел, что же там за код: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_03.png
На самом деле это шифровка! На скриншоте показан алгоритм, который её расшифровывает. Видите огромные синие цифры выше строки «80067b3c»? Это не код, это полный бред. Код начинается ниже, и в нём:
В «t0» загружается указатель на начало (точку входа) шифрованной функции. В «v0» загружается ключ для шифрования. В «v1» загружается некое число, служащее вторым ключом. В «t6» загружается адрес, с которого нужно расшифровывать. В «t7» заносится размер шифрованной области в квартетах (размер в байтах делённый на 4).
Далее (выделено красным слева) начинается процедура расшифровки: В «t2» считывается квартет по «t6». Из «t7» вычитается единица. «t2» ксорится с ключом «v0» (t2=t2 xor v0) «t2» сохраняется по «t6». К «t6» прибавляется четыре (один квартет). (Далее две строки меняются местами из-за запоздалого прыжка) «v0» приравнивается разности «v1» и «v0» (v0=v1-v0), то есть изменяется сам ключ, вычитаясь из неизменного вторичного ключа. Если «t7» не равно нулю, то процедура расшифровки повторяется. Иначе происходит прыжок в «t0».
Чтобы увидеть, что на самом деле скрывается в шифрованной функции и что там изменил brill, мне пришлось дословно повторить процедуру расшифровки функции:
Получается, что XOR шифрует сразу двумя ключами, которые через раз чередуются. Но в итоге я получил расшифрованную функцию: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_04.png
…Знаете, это самый красивый ассемблер, который я видел.
Что делается в самом верху не так важно. Видите выделенную строку? Это прыжок обратно в функцию расшифровки!! Перед ним лишь меняется значение «t0» – прыжка выхода.
Другими словами, после расшифровки кода система прыгает в только что раскодированную функцию, исполняет её, потом выбирает дальнейший адрес выхода (а это регистр «ra», скорее всего в нём значение PC, которое было перед первым прыжком, как если бы вместо «jr t0» было «jr ra») и ещё раз исполняет функцию расшифровки! Которая теперь, естественно, закодирует секретную функцию обратно. И как будто ничего не было!
…Это просто гениально. Секретная функция невидима, не дизассемблируема, и даже если вы сдампите память игры в любой момент – вы её всё равно не найдёте и не дизассемблируете. Потому что она расшифровывается, выполняется и тут же зашифровывается обратно…
Вот так в двоичном виде выглядит то, что поменял brill: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_05.png
Бред? Конечно, она же зашифрована. Но если раскодировать, то: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_06.png
Ха, видали? Банальное зануление… Но давайте выясним, что там был за код: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_07.png
Три прыжка после выделенной строки. Все ведут на нижнюю сроку с меткой. Все три ветки перепрыгивают через такой код: Считать один байт в «a0» по адресу «a1». Загрузить в верхнюю часть «a2» константу «1f00». Сложить «a2» с «a0» (хоть там и «or», это просто сложение).
А вот что brill сделал с кодом: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_08.png
Прыжков нет! «a0» всегда загружается из «a1», но и эту строчку можно было удалить, ибо значение «a0» просто игнорируется – он явно прописал в «a2» не просто «1f000000 + xxxx», а константу «1f00dd11». Видимо в ней и кроется результат проверки диска libCrypt, и это число обозначает что диск лицензионный.
Другой же вопрос, как он к этому пришёл и как выудил это значение… А что сам код – так его можно было переписать тысячью способов, например не удалять прыжки, а просто направить их ближе или вообще загружать магическую константу ниже по коду.
В самих прыжках сравниваются некоторые биты в нескольких регистрах, определённые должны быть единичными, другие нулевыми. Без тотального изучения этой функции, а также всех, что вызывает она и вызывают её, я не могу сказать ничего больше, и главное – как же именно работает LibCrypt.
Но функция явно очень странная: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_09.png
Она вызывает некую процедуру «60698» подряд несколько раз с разными аргументами. Причём возврат из неё происходит не через «ra» (возможно чтобы не использовать стек вовсе), а явно через другой регистр, обозначающий точку входа в продолжение этой же функции (выделен красным).
И да, здесь есть загадочная команда «mtc0», о которой говорилось в статье, которую показал steeldragon: http://klimaleksus2.ucoz.ru/Files/REPEAT/lib_10.png
Ну почему я никак не могу найти справку по всем ключевым словам дизассемблера!? Я же вообще не в курсе, что дает «mtc0 a1, EntryLo1» или «mfc0 a0,Count» – я даже не соображу, это присваивание регистров или их выгрузка …
Ну всё, анализ закончен. Не дало пока ничего практического, то есть я ещё не смогу сходу взломать LibCrypt любой другой игры… Единственное, что я пока понял – надо всегда искать подозрительный XOR !
Выкладываю все версии файлов, которые я получил: http://klimaleksus2.ucoz.ru/Files/2/xor.rar
Там в «SCES_028.35\» : «Original» – оригинальный файл; «Brill» – тот, который вы мне дали (был ужат через UPX); «Decoded» – он же, но уже распакованный и подогнанный под оригинал, но ещё с копирайтом хакера и непонятной модификацией в конце. «Clean» – оригинальный файл, в который встроены только нужные изменения 24 байт шифрованной функции. Именно это главное – его надо прожигать! «Original_xor» – оригинальный файл с расшифрованной функцией (для исследований, прожигать бесполезно!) «Clean_xor» – изменённый файл с расшифрованной функцией.
В папке «xor\» есть программа «platinum.exe» – перетащите на неё оригинальный или изменённый (но не ужатый UPX !) файл, и получите вместо него версию с расшифрованной функцией (*_xor). Перетащите ещё раз, и она закодирует её обратно. Все параметры (ключи, смещения и алгоритм) я захардкорил, так что ни для чего другого её использовать не получится, но зато рядом есть исходный код!
Эй, а полноценный патч нужен? И не так как с Вихрем, а чтобы все контрольные суммы соблюдались! Так я же могу сделать: http://klimaleksus2.ucoz.ru/Files/2/LibCrack.rar
Там всё, инструкция внутри. Quick&Smart патчеры + возможность патчить только SCES. Сделано для конечного юзера, полученный образ можно без ошибок эмулировать на виртуальных дисководах или даже прожечь на PlayStation!
Надеюсь это не точная копия моего архива? А то ведь ещё заплатка нужна…
Цитата (steeldragon) Инструкция где-то рядом...
Ух-ты, может в перевод и это вставим? Поближе к Агенту9 или Хантеру с овцами на НЛО ! «Яйца где-то рядом…» – ну не круто ли?
but nobody came
|
| |
| |
| steeldragon | Сообщение # 404 Суббота, 13.07.2013, 19:54 |
Старейшина Драконов
 Редактор
«422»
Где: Не в городе Драконов
|
Цитата (aleksusklim) главный вылет на Колдунье. Который срабатывает, когда я роюсь в WAD 001 (конечно, не заметить потерю 50 килобайтов из 100-килобайтного файла сложно...)
Цитата (aleksusklim) А он рассказал, КАК это сделал? Наверное нет – вполне в его стиле – ни тебе исходного кода, ни описаний… В точку.
Цитата (aleksusklim) загадочная команда «mtc0» Цитата (aleksusklim) Ну почему я никак не могу найти справку по всем ключевым словам дизассемблера!? Ну не знаю... Отсюда: http://vhouten.home.xs4all.nl/mipsel/r3000-isa.html "mfc0 - Move from coprocessor register" #EDIT: Извиняюсь, нужна была информация про "mtc0"... #EDIT2: Теперь отсюда: http://cgi.cse.unsw.edu.au/~cs3231/10s1/os161/man/sys161/mips.html "The MIPS-161 has 10 supervisor registers in coprocessor 0. These may be accessed with the MFC0 (move from coprocessor 0) and MTC0 instruction, as follows: mfc0 $4, $12loads the contents of supervisor register 12 (STATUS) into general-purpose register 4 (a0), and mtc0 $4, $12 does the reverse." По-простому, mtc0 загружает содержимое основного регистра в регистр сопроцессора, а mfc0, с которым я это ключевое слово перепутал - наоборот, из регистра сопроцессора в основной. Цитата Надеюсь это не точная копия моего архива? А то ведь ещё заплатка нужна… Заплатка в архиве.
Сообщение отредактировал steeldragon - Суббота, 13.07.2013, 21:04 |
| |
| |
| DrWho | Сообщение # 405 Воскресенье, 14.07.2013, 12:34 |
Мудрый Дракон
 Почетный Житель
«1371»
Где: Не в городе Драконов
|
Цитата (aleksusklim) Эй, а полноценный патч нужен? И не так как с Вихрем, а чтобы все контрольные суммы соблюдались! Конечно нужен, вихрь так будет не только эксклюзивом GH, но и платиновой версии.
Цитата (aleksusklim) Если LBA не совпадают, то вам придётся долго мучиться… Они вряд ли совпадают там, просто хотел поэксперементировать и реплики от Вектора в GH засунуть попробовать. Жаль все-таки никто из пиратов за GH не брался, потому что если бы хоть кто-то из студий выпустили перевод полноценный в GH, вот тогда проблем меньше было бы. И можно было бы задействовать вихрь уже в русской версии.
Цитата (aleksusklim) Неужели!? Что ж, я этого не знал. Сам Yams просил меня когда-нибудь её взломать… Ну как, я сначала попросил brill'а выпилить защиту, он отказался. Потом ему SquareFun объяснил ситуацию и в итоге он убрал защиту из игры.Добавлено (14.07.2013, 12:34) ---------------------------------------------
Цитата (aleksusklim) Зойка + нелепый Толстосум продающий несуществующих животных + пустые таблички на порталах присутствуют! Крайне желательно оставить только Зою, ибо есть вероятность случайно задеть яйца, которые начнут пропадать, в том числе и недобор камней драгоценных. Вы когда-то пытались с вихрем эксперимент провести, а можно сделать клон Зои и ее расположить ближе к началу уровня (только не там, где стоит зоя, говорящая об управлении)?
Сообщение отредактировал DrWho - Суббота, 13.07.2013, 20:01 |
| |
| |