[r][i][color=gray][url=http://klimaleksus.narod2.ru/Other/realms/170.txt]#170[/url], 26.08.12[/color][/i][/r] А вот как я взломал японскую камеру. Ссылка на патч: http://klimaleksus.narod2.ru/Files/1/Japancam.rar Я супер-крутой хакер. Но раньше я действовал по-мелкому, находя адреса памяти и меняя их значения. Или же ковыряя файл через WinHex, изменял указатели, которые представляли собой открытые четырёхбайтовые числа. Но таким способом невозможно взломать «функцию», то есть мгновенное действие, например поведение игры после нажатия на клавишу. Я могу отловить и изменить только то, что «живёт» хотя бы несколько кадров, например последствия вызова той же функции (если она что-то куда-то сохраняет), но сам факт запуска функции мне неподвластен, то есть я не могу заставить игру перестать её обрабатывать (если там нет готового выхода типа «if x=0 then exit») или вызвать функцию принудительно по своему желанию. Когда я создавал Вихрь на Полуночной Горе, я просто модифицировал область переменных, заменив номер одного объекта на другой, а также его координаты и прочие переменные. Но вот изменить физическое поведение вихря так не будет возможно. Потому что весь «код» лежит либо в EXE, либо где-то в WAD как локальный. Хотя скорее всего, в WAD двоичного кода ассемблера вообще нет, зато EXE отвечает абсолютно за всё. Однако когда я брался за взлом японской камеры, я и не собирался переписывать код, а хотел сделать так, чтобы американская камера всегда была вместо японской, особенно при загрузке сохранёнки, где при нажатии L1+R1 активируется американская камера. А я хотел поменять их между собой, чтобы при L1+R1 наоборот, применялась японская, в то время как американская работала бы при обычном включении. Взялся за взлом. Через ArtMoney начал проверять, есть ли в памяти где-то ячейка, отвечающая за режим камеры. Оказалось, что есть. Это для эмулятора «9CB4D0», а для игры – «8007F4B0». Если там находится «115» (75h), то камера японская, а если «83» (55h), то американская. Но это ещё не всё! Меняется странная ячейка «9CB3B0» (для игры – «8007F390»), как видите, расположена она недалеко. В ней записан «0» у японской камеры, и «1» у американской. Но её действие совсем другое… Эй! Почему никто не сказал и не заметил, что в японском Spyro1 нельзя прыгать на бегу? А я-то ещё думаю, почему управление такое странное… Когда держишь квадрат, то нажатие креста не имеет эффекта. До тех пор, пока не включён РежиссёрскийРежим… С американской камерой прыгать на бегу получается. Выходит, что виновница плохой игры не сама камера, а отсутствие такого необходимого прыжка на бегу! Вот флаг наличия прыжка «1» и записывается в эту ячейку. Результат таков: при обычной игре ячейка «8007F4B0» заполняется числом «115», а «8007F390» – нулём. А с R1+L1 – в «8007F4B0» будет «83», а в «8007F390» – единица! Проверяю возможность изменения этого на лету в ОЗУ во время игры. Да! Как прыжок можно отключить и включить, так и режим камеры очень легко поддаётся изменению. Плюс, появляются ещё два новых состояния: американская камера без прыжка и японская камера с прыжком. Теперь давайте определим цель… Раз уж можно включать и отключать прыжок, то имеет ли смысл ремонтирование камеры? Может, реализовать вечный прыжок на бегу, а камера пусть остаётся как L1+R1? Или наоборот, починить камеру, а пусть L1+R1 даёт только наличие прыжка. Но в любом случае мне ещё нужно заставить игру вписывать в эти ячейки те данные, которые захочу я, уже без ArtMoney. Для этого сначала проделаю изменения в ОЗУ, потому определю, откуда оно было загружено, и через WinHex изменю там. Ведь я так сделал Вихрь… Но камера вообще отказалась мне подчиняться. Сперва я решил узнать, что будет, если для прыжка записать не «0» и не «1», а для камеры – не «115» и не «85». С прыжком всё понятно сразу: если он нулевой, то прыжка нет. Иначе он всегда есть, хоть там «1» или «255» или даже «4294967295» (FFFFFFFFh). Камера же гораздо необычнее. Она работает при любом раскладе, будь там «0» или «255», но становится странная и дёрганая. Например при нуле она резко отлетает вверх, если развернуть Спайро лицом «сюда», а при «255» больше похожа на японскую, но она часто пытается развернуться как «активная», но почему-то застревает на полпути. В принципе, любое число так или иначе влияет на поведение камеры, но отличаются они незначительно. Поскольку я не собираюсь проводить 256 экспериментов, чтобы найти наилучшую камеру (ведь и «0» и «255» имеют ужасные глюки и отвратительные рывки при быстром движении), придётся явно сохранять константы «83» и «115». Открываю начальный экран загрузки игры с карты памяти, чтобы найти момент, когда числа в ячейках меняются. Нет, ни при удержании L1+R1. А именно после нажатия креста и потухания экрана. Осталось определить субфайл, в котором прописан код загрузчика карты памяти. Это субфайлы с невысокими номерами и небольшими размерами. Теперь я должен найти «источник» этих чисел «83» и «115», то есть именно то место в памяти (файлах), откуда они копируются в нужную область RAM. Плохо, что числа маленькие… если просто их искать, то я получу тонну совпавших данных… А если же.. о! Числа «155» и «83» на самом деле четырёхбайтовые, то есть если я изначально (а там пока нули) до нажатия креста заполню область рядом с «8007F4B0» (дюжину байт туда-сюда) максимальным значением в «255» (FFh), и нажимаю на крест – ячейка камеры превращается из 255 в 115 (ну или 83, если я удержал R1+L1), а следующие три байта становятся нулевыми! А это означает, что числа хранятся как «73000000h» и «53000000h», ну а их уже очень легко отыскать. …Легко, но что-то не получается… Вижу кучи других значений, изменяю, запускаю – там снова заводское. Не, ну ладно… Может там какой-то расчёт происходит, ведь не просто так выбраны 83 и 155! Скорее всего биты разворачиваются, и каждый их них за что-то отвечает (например один за высоту, другой за скорость реакции камеры и.т.д). Возможно, разработчики оставили формулы «создания» этих констант вместо того, чтобы явно внести их в файлы. Но ведь флаг прыжка не должен рассчитываться! Он либо ноль, либо один, причём тоже 32 бита. Нужно искать число «01000000» в памяти. Ух… Да, их много. План такой – изменяю несколько на что угодно (лучше просто на 2, что терпимо для игры; ведь при 255 Epsxe часто начинает вылетать… к чему бы это?), нажимаю L1+R2+крест и смотрю в ОЗУ – если там остался «1», то я изменил мусор, значит эти значения можно спокойно удалить, а эмулятор через savestate загрузить обратно на момент поиска значений. Если же после одновременного изменения нескольких значений эмулятор завис или вылетел, то его необходимо перезагрузить, чтобы на этот раз поменять меньше ячеек. Ну я например, беру по сотне за раз, и если происходит ошибка (нашла коса на камень…), то разгребаю проблемный участок десятками; если и там ошибка, то уже определяю по одному – а потом снова вперёд сотнями! А ещё иногда в таблице сразу видны значения, которые постоянно меняются – их можно удалить сразу, даже не проверяя. Я протрассировал так абсолютно все значения четырёхбайтовых единиц. Ничего! Я не смог найти место, откуда игра «достаёт» единицу, чтобы записать её по нужному адресу! О, кстати, а может поискать сам этот адрес? Нет, увы: он тоже не мелькает в дампах памяти, и совершенно неясно, как же игра его узнаёт. Стоп, а может при нажатии креста система подгружает новый субфайл, а механизм расчёта находится в нём? Тогда я переименовал свой savestate в «DEMO_999.99.000» и загрузил его через F3 после «Run BIOS». Это гарантированно предотвратит появление новых субфайлов. Опять неудача – значения и прыжка и камеры стабильно появляются в нужных ячейках… А где эти самые области дублированы в файлах? Я начинаю шарить по ОЗУ через ArtMoney, пытаясь найти что-то особенное. Но там все так странно – немного ниже надпись «BISCPS-10083SPYRO» (которая есть и в EXE и в субфайлах), а прокрутив повыше обнаруживаю «Library Programs (c) 1993-1997 Sony Computer Entertainment Inc., All Rights Reserved», что уже явно выдрано из «SCPS_100.83» (тот же EXE) Короче – блок до и блок после есть в разных файлах в разных местах на диске. А вот именно это пространство словно само собой создаётся! Я пытаюсь найти указатель уже не на один адрес, а на диапазон адресов, сначала на небольшие участки, а потом уже от отчаянья до ±1024! И опять ничего… Уф, что же мне делать? Есть другой вариант – найти участок в игре, где какие-то данные записываются по какому-то адресу. Подменить как адрес так и данные, чтобы в этот момент превращать камеру! Придумал два варианта: либо через меню, либо через L1, R1 в игре (перевороты через бок). А что перевороты? Они разве нужны для игры? Я, например, по назначению так ни разу ими и не пользовался. Вот я и подумал, что нажатие L1 / R1 заносит в какую-то ячейку информацию о том, что для дракона пора воспроизводить анимацию переката (и совершать все связанные с этим действия) или даже проще – ставил флаг, в процессе ли сейчас кувырок или нет. Однако существует опасность, что ВСЕ кнопки управления могут брать этот адрес из одного и того же места, что не позволит его безболезненно изменить. Э-э, в любом случае, пока не взломаю – не узнаю! Вот я и приступил к отсеиваниям. Стою > «искать» (кодированное значение). Пробежал, остановился > «не изменилось». Ушёл в другой уровень > «не изменилось». Тут быстро жму R1 и Esc, чтобы остановить эмуляцию > «изменилось». Восстанавливаю Epsxe, чуть жду > «изменилось», а потом сразу же > «не изменилось». Нашёл несколько значений. Теперь при заморозке я должен впасть в бесконечный кувырок. Нет… Не вышло. Единственная ячейка, которая что-то делает – «9CB240» (для игры – «8007F220»), но это просто угол зрения камеры. Если увеличить, то она как бы приближается, но видно становится меньше. А если уменьшить (например до «-10», то есть «4294967286»), края раздвигаются, а изображение суживается. Становится видимо даже то, что находится почти сзади. И вот оно сохранение быстродействия – прекрасно понятно, что все модели и текстуры (да что уж! Даже полигоны неба) не отрисовываются там, где камера не должна их видеть. Скриншоты: http://klimaleksus.narod2.ru/Files/1/cam_1.jpg (жаль, что скрин не передаёт динамичности бега…) http://klimaleksus.narod2.ru/Files/1/cam_2.jpg (хотите знать, что это так ярко светит жёлтым? У Спаркса спросите…) http://klimaleksus.narod2.ru/Files/1/cam_3.jpg (посмотрите на синий портал вдали) http://klimaleksus.narod2.ru/Files/1/cam_4.jpg (а как он вам вблизи?) http://klimaleksus.narod2.ru/Files/1/cam_5.jpg (и так во всей игре) Ещё один весёлый адрес – «A3C7AA» (в игре – «800F078A»). Его я обнаружил ещё при взламывании экрана загрузки. Он отвечает за визуальную составляющую объекта «Спайро», который сперва прилетает по миру на площадку, а потом стоит на ней. Его можно превратить в одного зелёного монстра (которого он сшибает при приземлении), в другого такого же (который иногда пробегает на переднем плане, причём модель анимируется только если этот самый оригинал пробегает в этот момент), или в табличку «Insomniac Games» (особенно весело при полёте!) Присваивать значение от 1 до 4. Любые другие числа (по крайней мере из тех, что я попробовал) вызывают немедленный сбой и завершение работы эмулятора. http://klimaleksus.narod2.ru/Files/1/cam_6.jpg (смир-но!) http://klimaleksus.narod2.ru/Files/1/cam_7.jpg (как видно, центр таблички не совпадает с центром дракона…) http://klimaleksus.narod2.ru/Files/1/cam_8.jpg (близнецы!) Вернёмся ко взломам. Итак, идея с перекатами провалилась. Остаётся единственная надежда на меню. Открываю его и хочу понять, за что отвечает та самая строчка. Нахожу тут переводы меню («МТйД»), по таблице соответствия сопоставляю иероглифы (ага, закинул в Word и всё на глаз сравнивал, хотя можно было и в поиск дать…), наконец, определяю что это Стерео/Моно, нахожу его в EXE. Ищу указатель – фиг там! Ни на одну запись в EXE игра не держит указателей! Как такое возможно? Как система находит данные, не зная нужного отсчёта? А раньше ведь у меня получалось… Ну ладно. Попробую оттолкнуться не от самого слова, а от того действия, которое производит изменение стерео на моно. Ломаю «кодированное значение» и отыскиваю всякие закономерности. Результат – пять адресов. Но скорее только два: «9CBE10» (в игре «8007FDF0») – принимает «0» для стерео по умолчанию, и заменяется единицей «1» для режима монофонического звука. Причём я над ним немножко поиздевался и попробовал написать туда «2». Тогда он и три следующих байта вдруг стали равны «255» (FFh)! Это число «4294967295» или «-1». При следующем изменении оно превратилось обратно в «2». Выходит, что значение не явно присваивается, а [i]вычитается из единицы[/i]. Следовательно, искать его бесполезно. От «9CD2F0» (в игре «800812D0») до «009CD2F3» («800812D3») – то есть 4 байта. Однако его значения скорее напоминают четыре однобайтовых числа, чем одно четырёхбайтовое. Для стерео четвёрка равна «127=0=127=0» (шестнадцатеричные: «7F=0=7F=0»), а для моно – «63=63=63=63» (hex: «3F=3F=3F=3F»). Странно, не правда ли? Пусть так. Значит эти числа («8323199» и «1061109567») откуда-то копируются! Надо найти как их, так и место, где записан пункт назначения конечный адрес «2148012755» (800812D3h). И .. что? Ничего. Опять… Да что Spyro1 возомнил о себе!? Блин, мне бы просто посмотреть, отследить поведение системы в момент переключения настройки… Откуда берутся адреса и значения, да как именно копируются. Эх, я бы в код заглянул… Ну хоть в какой-нибудь, хоть в ассемблер! Вспоминаю про «эмулятор с отладчиком», но к огромному сожалению он не собирается запускать японский Spyro1 (Хей, почему!? А на самой, реальной приставке Sony PlayStation, этот прожжённый на CD-R образ сможет ли нормально работать?) Ну а даже б если запустил? Что я в нём могу? Не, вот даже при старте BIOS без диска… это ж ужас. Одно неловкое движение в меню, и вылетает необработанное исключение. А у него, походу, аллергия на эти самые исключения, так что он то и дело падает. Логирование. Не работает!! Вызовы процедур не трассируются, а сектора диска не мониторятся. В самом отладчике – просмотр дорожек диска (с наитупейшей в мире навигацией), который я запросто могу и в WinHex делать, потом граббер GPU, который НЕРЕАЛЬНО виснет… Стек вызовов процедур, который пуст при работе, а увидеть его можно только остановив действо. Просмотр состояний регистров, но на кой он сдался, если читабельного кода нет. Как же нет, есть! Там куча (с такой же идиотской навигацией) мнемонических команд, добрая половина которых нераспознана отладчиком. О, её можно запускать по шагам! А нука-сь… О-хо, эдак я год буду ждать завершения отрисовки одного кадра… Что-что ещё? «Выполнить до курсора»? Ага, осталось «угадать», на какой адрес поставить сам курсор… Последний писк – распечатка дизассемблерического листинга в файл, но всё что я смог оттуда получить – тонну надписей «illegal». Но вдруг мне вспомнился один такой ps2dis, то есть, дизассемблер PlayStation 2. Я его скачал когда не помню и откуда не знаю. И куда положил тоже пришлось долго определять. На тот момент, я экспериментально кастовал на него различные PS-EXE'шки, но дальше горы мнемонических кодов не продвинулся и забросил это дело до лучшего применения… …Которое наконец, настало! Вот сама прога: http://klimaleksus.narod2.ru/Files/1/ps2dis.rar Штучка отпадная. Очень, очень мощная! Но всё по порядку. Во-первых, никакой инструкции там и в помине нет, так что мне пришлось как обычно, изучать программу в процессе. Во-вторых, она сразу распаковывает EXE по нужным адресам так, что все данные располагаются словно в эмуляторе – как если бы я дампил память. Кстати, я где-то читал, что дизассемблировать можно и дампы памяти, поэтому был готов если что, выдернуть парочку копий всех адресов с включённым на экране меню, чтобы отыскать в них нужную функцию. Но пока довольствовался EXE. Что интересно, ps2dis умеет распознавать английский текст «ENTERING GNASTY'S WORLD» (что он там делает!?), но все «японские» кириллические байты распознаёт как реальные инструкции, так что процесс обещает быть долгим. Не буду много говорить о том, как я изучал ps2dis, но навигация там простая, кнопкой «вправо» можно перейти по ссылке на адрес, кнопкой «пробел» пометить оператор, а «F3» и «Shift+F3» найти все те адреса, операторы которых ссылаются на помеченную строку (типа, навороченный поиск адреса). Ну вот я и взял адресок «8007F4B0» (камера), перешёл на него клавишей «G», пометил строку и поискал ссылающихся. Вау! Да их же целая толпа! Хотя сам адрес пуст (ps2dis работает в полном пространстве от «00000000» до «FFFFFFFF», хотя ОЗУ располагается по крайней мере выше «80000000»). Поменьше рефереров было у адреса прыжка («8007F390»). Также без ссылок не остался и стерео-переключатель «8007FDF0», ну и его флаги «800812D0 – 800812D3». Что дальше? Я вижу код, который явно что-то делает, но понятия не имею, как его читать. Я не знаю смысла мнемоники, хотя общая концепция программирования на ассемблере мне известна. …Это ещё со времён «Sinclair ZX-Spectrum на основе *БЕЙСИК* микрон с микропроцессором Z80» (тот что 1990 года выпуска с 40Кб оперативки, 16Кб ПЗУ и 2Кб видеопамяти экрана 256 на 176 точек… Стоп, минуточку! *заглядывает в инструкцию* Да я его недооценивал! Системные характеристики: http://klimaleksus.narod2.ru/Files/1/cam_10.jpg ). Короче, там программы (в большинстве своём, игровые) делились на две категории – те, что написаны непосредственно на Бейсике, и на «программы в машинных кодах» (которые грузились всё равно-таки через ядро бейсика RANDOMIZE USR xxx), то есть на ассемблере. В инструкции сказано, что они создавались благодаря компиляции с Паскаля или «C» (который в те времена был ещё без плюс-плюс). Ну а я писал только на бейсике, исключительно на бейсике. Поэтому такие понятия как адреса и регистры меня напрямую не касались. Я как-то пытался что-то написать на самом ассемблере, но при любой ошибке этот (с позволения сказать) компьютер просто перезагружался (а вот бейсик был куда гуманнее, тихо выводя сообщения, и перезагружаясь только если свет моргнёт…) Вот я и почитал тогда справочку по устройству машинных кодов, знания которых пригодились мне.. вот лишь сейчас. [c][font=Courier][size=22] ↓ ↓ ↓ ↓ ↓ [/size][/font][/c] [c][font=Courier][size=22] ↑ ↑ ↑ ↑ ↑ [/size][/font][/c] Итак, ассемблер. Он работает только в заданных адресах. То есть если нелинейный (ветвящийся) код скопировать и вставить в другое место, то ссылки будут показывать на прежнее. Или придётся вручную отыскивать и менять все адреса (может автоматически, если код был получен компиляцией, а не ручным вводом). Сама программа представляет из себя набор команд. Они исполняются очень быстро подряд. Но команды не масштабируемы, то есть это не как обычный код, мол «X=sqrt(2+2*2+1*3)», а команда=операция, только что-то одно. Результаты промежуточных операций надо где-то хранить, для этого существуют регистры. Это, типа, такие переменные. Они бывают разного размера и типа (ну в Синклере были маленькие восьмибитные (1 байт) регистры, но они соединялись в пары, получая шестнадцатибитные уже по два байта). Поэтому, чтобы что-то сложить, нужно, грубо говоря, сначала занести первое слагаемое в один регистр, потом занести второе слагаемое в другой регистр, а затем приравнять третий регистр сумме первых двух, и скорее всего, поместить результат в какую-то ячейку памяти, адрес которой должен храниться в четвёртом регистре… Чтобы не растранжирить так сразу все регистры несколькими действиями, был изобретён глобальный стек. Это как банка, в которую можно засовывать значения изменяемых регистров. А извлекаться они будут в обратной последовательности, то есть первый выйдет последним. В стек сперва помещают значения всех тех регистров, которые нужны для текущего действия (в задуманном порядке), потом производят само действие с этими регистрами, а уж затем восстанавливают из стека все былые значения, что могли быть стёрты при выполнении. Даже если одной из промежуточных операций будет вызов функции (переход исполнителя по другому адресу и возврат оттуда), которая захочет использовать те же самые регистры, то она тоже просто временно положит их текущие значения в глобальный стек. Главное тут не забыть вытащить из стека всё, что было в него внесено, ведь иначе рано или поздно произойдёт переполнение (хотя с большей вероятностью система просто накроется, когда в регистр не вернётся нужное значение) Команды, понятные для человека, записывались мнемоникой, например «NOP» означает «ничего не делать», а «MOV» – занести что-то в регистр. Аргументы и имена регистров пушится через запятую после команды (видимо AutoIt ниже третьей версии выглядел примерно так же). Стоит ли говорить, что регистр мог хранить ТОЛЬКО числа. А не строки. Строка (скорее всего) задавалась адресом первого символа (в регистре) и нулевым байтом на конце (или длиной, записанной либо в другом регистре, либо в ячейке, адрес которой всё равно лежит в каком-то регистре). В общем, концепция программирования чудовищно отличается от привычной. Ну совершенно! (Это даже хуже, чем DOS). Где-то не так давно на компе читал CHM-справку какой-то программы или эмулятора чего-либо, и посмотрел, как в нём работают стандартные функции… Ужас! (Хоть бы больше такого нигде не было…) Функции там имеют номер и подномер. Они принимают аргументы и возвращают результаты. Так вот, чтобы вызвать функцию, нужно было занести в один регистр номер нужного раздела функций, в другой регистр – подномер уже самой функции, в ещё кучу других регистров – аргументы. Затем как-то воплотить её исполнение. После чего выкопировать себе результаты из других регистров. Ассемблер-то быстрый, но если его ТАК гонять… Ну я отвлёкся. Итак, ps2dis. Мне была срочно нужна хоть какая-нибудь справка по командам, чтобы прочитать код Spyro. Забурился в интернет, нашёл страничку: http://gamehacking.org/faqs/1UpPS2Dis.html (Кстати говоря, там нет ни одной команды для работы со стеком! Неужели PlayStation обходится без него?) Очень странно, но других нормальных описаний не было. Да и это не ахти как профессионально сделано (да что они там в самом деле? Туториалы, видео-уроки, пошаговые инструкции… идиоты! Дайте справку по всем командам как htmlbook.ru даёт по тегам!!) Рассказывается не всё, чертовски не всё! Как, например, загрузить данные с диска в ОЗУ? А как заставить GPU использовать выбранный графический ресурс? А как воспроизвести звук или считать нажатия кнопок на джойстике? Здесь даны чисто математические функции, немного работы с памятью и операторы языка. А вот какое-то действие выполнить с ними я не смогу, до тех пор, пока не узнаю, как «вынуждать» систему действовать (ну, скорее всего, придётся занести в регистр адрес начала данных, в другой регистр номер требуемого действия и написать правильную команду…) Но мне и математики хватит, по крайней мере я смогу изменить адреса и значения нужных ячеек, а игра сделает остальное. Попробовал использовать тот набор С++ разработчика для PS, который мелькнул от [url=http://www.spyro-realms.com/forum/48-11180-154704-16-1338837995]steeldragon[/url] (нь..ет, я не.. игнорирую, я просто… уф, я отвечу там, точно отвечу!) Скомпилировал один демо EXE и дизассемблировал его. Там такая хренотень в кодах, что дольше десяти минут я не продержался… Они ведь тоже какой-то движок использовали, у которого куча функций в арсенале. Ещё по ps2dis: Кто-то пишет что-то умное, но словно для кого-то глупого: http://www.oocities.org/techni_slave/hack.html Очень хорошее описание всех горячих клавиш: http://www.oocities.org/siliconvalley/station/8269/ps2dis/ Всё, пора начинать мой взлом! Запускаю ps2dis (шрифт – Courier New 12 смотрится неплохо), открываю в нём EXE и перехожу по адресу «8007FDF0» – это ячейка, отвечающая за моно/стерео настройку в меню. Вижу пустоту, как и ожидалось. Включаю анализатор (помечает все линии, на которые есть ссылки): http://klimaleksus.narod2.ru/Files/1/dis_1.png Space+F3 – ищу ссылающихся. Первый: http://klimaleksus.narod2.ru/Files/1/dis_2.png Так-с, «LW» что означает «Load Word», то есть, код загружает значение из этой ячейки в регистр V0, а чуть ниже сравнивает его с нулём. Это точно не для меню (там должно быть сохранение) Идём дальше, F3: http://klimaleksus.narod2.ru/Files/1/dis_3.png Интересно, да? «LI», которого нет в описании. Смею предположить, что это «Load Integer (или Immediate)», расшифровывающееся как «addiu a0, a0, $fdf0», что видно из строки состояния. Тут меня осенило, почему я не мог найти адреса в ОЗУ. Да потому что адрес разбивается на две части – на верхнюю половину «8007» и нижнюю – «FDF0», и грузятся они в два действия! В предыдущей строке стоит «lui a0, $8008». Зачем он превращается в «8007» я точно не знаю, но так происходит везде. Код загрузки адреса такой: lui a0, $8008 addiu a0, a0, $fdf0 , что приравнивает регистр «A0» (смаю предположить, что с «А» начинаются специальные регистры, предназначенные именно для хранения адресов памяти, а не просто так) значению «8007FDF0». Кстати, видите строчку выше «mov a3, zero»? Команда MOV тоже не описана. Потому что это лишь упрощение реальной команды «addu a3, zero, zero», чтобы обнулить регистр. Хм, кажется, строка «lui a0, $0000» приведёт к тому же самому результату! Да в прочем, какая разница, написать «X=0» или «X=X-X» или даже «X=A*0», если результат всё равно нулевой? Остаётся лишь заметить, что слово «zero» означает РЕГИСТР, в котором хранится вечный ноль, а само число «0» записывалось бы «$0000», но далеко не все команды принимают числовые значения, поэтому «zero» используется очень часто. Ладно, давайте посмотрим, что Спайро делает с этим адресом, который сейчас сохранён в A0. Дальше по коду видны сразу две ссылки (ps2dis подсвечивает A0). На F3 они реагируют точно так же, вот послеследующий: http://klimaleksus.narod2.ru/Files/1/dis_4.png Ха, «SW»! Это и есть код изменения пункта меню. Но пока пройдёмся по другим ссылкам: http://klimaleksus.narod2.ru/Files/1/dis_5.png Тут (заметьте, очень далеко внизу!) по нашему адресу записывается «zero», то есть ячейка обнуляется. Это уж точно мне не мешает, иду ниже: http://klimaleksus.narod2.ru/Files/1/dis_6.png Здесь тоже загружают значение и сравнивают его с нулём. А тут же присутствует «jr ra», то есть возврат из функции. Между какие-то данные что-то считывают и записывают… http://klimaleksus.narod2.ru/Files/1/dis_7.png SW, интересный блок. Регистр «AT» подсвечен, и он постоянно приравнивается в «8008», то есть в него грузится верхняя часть того адреса, который используется последующей строкой. Резонный вопрос, зачем повторять одно и то же, если «at» вообще не изменяется на протяжении всего кода? Мне кажется так сделал тот компилятор, на исходнике к которому писали Spyro. Ну ведь не на ассемблере же! Машинный код создан другой программой. А у неё, кажется, любой адрес задаётся двумя строчками ПОДРЯД. Система как бы переопределяет число полностью. Словно не знает, что его верхняя половина одинакова. Грубо говоря, конструкция: A=8008 X1=A+0001 X2=A+0002 X3=A+0003 Исполнена как: A=8008 X1=A+0001 A=8008 X2=A+0002 A=8008 X3=A+0003 Расточительно, зато если бы вдруг пришлось кардинально поменять один из адресов, это на остальные это не произвело бы влияния. Их движок как бы подходит к каждому присваиванию заново, даже не глядя на то, что AT уже хранит как раз именно то число, которое ему присваивают ещё раз. Код не оптимизирован. Но мне это без разницы, иду к последнему ссылающемуся: http://klimaleksus.narod2.ru/Files/1/dis_8.png А этот код словно обратен предыдущему, теперь значения из той части памяти копируются в эту. (Забегая вперёд скажу, что я на 95% уверен, что это коды сохранения и загрузки игры на карту памяти) В результате возвращаюсь в область третьего ссылающегося, где лежит основная функция изменения режима стерео в меню: http://klimaleksus.narod2.ru/Files/1/dis_9.png Вот я так сидел и смотрел на этот код. Я пытался прочитать его, перепроверял MIPS (мнемонику) и думал над ним. Это первый в моей жизни раз, когда я читал код Ассемблера и понимал, что он означает… О, как я был близок к изменению! Но я очень боялся притронуться к нему. Как? Разве можно просто так переписывать код без всяких тренировок и инструкций? Лишь очень размыто представляя себе дальнейшую работу? Я на краю утёса… Я вор, который мечтал добраться до самого сложного сейфа в самом охраняемом банке, и который сделал это и стоит перед ним… Я космонавт, годами тренировавшийся к полётам, и вот вставший перед трапом к кабине корабля, готовящегося к старту… Короче, я минут сорок просто смотрел на код. Уж не знаю, думал ли я над этим кодом, или просто завис в переживаниях, но через какое-то время мне надоело ничего не делать, и я взялся за перепись. Определил много нового в ассемблере (особенно касательно прыжков), чего раньше не знал. Потом ещё поискал в интернете и убедился, что сделал правильные выводы. Написал свой код. Сохранил (хи, в ps2dis интересная система сохранения области, а я как дурак на глаз в WinHex списывал…). Проверил. Не работает, вернее, не полностью. Изменил код, перепроверил. Уже по-другому, но неправильно. Подумал ещё, изменил и проверил. О, наконец! Жизнеспособная версия. Но глюки при входе в игру с L1+R1 (я не сменил стерео-адрес, но сравнивал адрес прыжка, что приводило к неработоспособности настройки: нужно было дважды щёлкнуть её в меню, чтобы отключить и снова включить). Потом значение неверно сохранялось и загружалось на карту памяти… А как выяснилось, за стерео звучание отвечает именно ноль в «8007FDF0», а не «127=0=127=0» в «800812D0»! Пришлось ещё раз переписать весь код этого блока, и, вроде, всё заработало. Потом я раскрасил надписи и закончил. Хей, не кажется ли вам, что я замял конец рассказа? Начинал так медленно, а финишировал одним абзацем? Сейчас распишу подробнее… Просто я уже и не вспомню всех промежуточных тестирований нерабочего кода, потому что я их не сохранял, а даже если бы сохранял, то не запомнил бы назначение и ход моих мыслей в тот момент. Так что я просто распишу логику оригинального кода и изменённого кода, но не неудачные попытки его создания. Но поверьте на слово, перепечатывал байты из ps2dis в WinHex я раз восемь. И простите, если вдруг стану немножко повторяться… Итак, оригинальный блок. Я пронумеровал строчки справа: http://klimaleksus.narod2.ru/Files/1/dis_10.png Смотрим-с. Вот сам код: 0 ) 8002d704: jal $80059f90 1 ) 8002d708: addu a3, zero, zero 2 ) 8002d70c: lui a0, $8008 3 ) 8002d710: addiu a0, a0, $fdf0 4 ) 8002d714: lw v1, $0000(a0) 5 ) 8002d718: addiu v0, zero, $0001 6 ) 8002d71c: subu v0, v0, v1 7 ) 8002d720: beq v0, zero, $8002d754 8 ) 8002d724: sw v0, $0000(a0) 9 ) 8002d728: addiu v0, zero, $003f 10) 8002d72c: lui at, $8008 11) 8002d730: sb v0, $12d3(at) 12) 8002d734: lui at, $8008 13) 8002d738: sb v0, $12d2(at) 14) 8002d73c: lui at, $8008 15) 8002d740: sb v0, $12d1(at) 16) 8002d744: lui at, $8008 17) 8002d748: sb v0, $12d0(at) 18) 8002d74c: j $8002d778 19) 8002d750: nop 20) 8002d754: addiu v0, zero, $007f 21) 8002d758: lui at, $8008 22) 8002d75c: sb v0, $12d2(at) 23) 8002d760: lui at, $8008 24) 8002d764: sb v0, $12d0(at) 25) 8002d768: lui at, $8008 26) 8002d76c: sb zero, $12d3(at) 27) 8002d770: lui at, $8008 28) 8002d774: sb zero, $12d1(at) 29) 8002d778: lui a0, $8008 30) 8002d77c: addiu a0, a0, $12d0 31) 8002d780: jal $80068eb0 32) 8002d784: nop Он сильно отличается от того, что видно на скриншоте, потому что ps2dis сам определяет и показывает вычисленные адреса известных регистров, а в распечатке выдаёт настоящий голый код. Руководствоваться лучше скриншотом, заглядывая в сам код лишь для ясности. Поехали, сперва объясню каждую строчку, а логику поведаю после: 0 ) Вызвать функцию (с возвратом) по адресу «80059F90» (что на 45603 строки впереди) после исполнения следующего оператора. 1 ) Приравнять регистр «a3» нулю. 2 ) Записать в регистр «a0» число «80080000» (здесь и далее всё в шестнадцатеричной) 3 ) Изменить значение регистра «a0» на «8007FDF0». 4 ) Считать 4 байта по адресу «0000+a0» (т.е. «8007FDF0») и сохранить их в «v1». 5 ) Приравнять регистр «v0» числу «0001». 6 ) Вычесть из регистра «v0» (т.е. из единицы) содержимое регистра «v1» и сохранить обратно в регистр «v0». 7 ) Выполнить следующий оператор, затем сравнить содержимое «v0» с нулём; если не равно, то продолжать, а иначе перейти к адресу «8002d754» (что на 13 строк впереди, т.е. к строке № 20) 8 ) Сохранить в 4 байта по адресу «0000+a0» (т.е. «8007FDF0») содержимое регистра «v0». 9 ) Приравнять регистр «v0» числу «003F». 10) Записать в регистр «at» число «80080000». 11) Сохранить 1 байт по адресу «12D3+at» (т.е. «800812D3») из регистра «v0» (т.е. число «3F») 12) Записать в регистр «at» число «80080000». 13) Сохранить 1 байт по адресу «12D2+at» (т.е. «800812D2») из регистра «v0» (т.е. число «3F») 14) Записать в регистр «at» число «80080000». 15) Сохранить 1 байт по адресу «12D1+at» (т.е. «800812D1») из регистра «v0» (т.е. число «3F») 16) Записать в регистр «at» число «80080000». 17) Сохранить 1 байт по адресу «12D0+at» (т.е. «800812D0») из регистра «v0» (т.е. число «3F») 18) Безвозвратно перейти по адресу «8002D778» (что на 11 строчек впереди, т.е. к строке № 29) после исполнения следующего оператора. 19) Ничего не делать. 20) Приравнять регистр «v0» числу «007F». 21) Записать в регистр «at» число «80080000». 22) Сохранить 1 байт по адресу «12D2+at» (т.е. «800812D2») из регистра «v0» (т.е. число «7F»). 23) Записать в регистр «at» число «80080000». 24) Сохранить 1 байт по адресу «12D0+at» (т.е. «800812D0») из регистра «v0» (т.е. число «7F»). 25) Записать в регистр «at» число «80080000». 26) Сохранить нулевой байт по адресу «12D3+at» (т.е. «800812D3»). 27) Записать в регистр «at» число «80080000». 28) Сохранить нулевой байт по адресу «12D1+at» (т.е. «800812D1»). 29) Записать в регистр «a0» число «80080000». 30) Изменить значение регистра «a0» на «800812D0». 31) Вызвать функцию (с возвратом) по адресу «80068EB0» (что на 60876 строк впереди) после исполнения следующего оператора. 32) Ничего не делать. [c][font=Courier][size=22] ↓ ↓ ↓ ↓ ↓ [/size][/font][/c] [c][font=Courier][size=22] ↑ ↑ ↑ ↑ ↑ [/size][/font][/c] Довольно интересные можно сделать выводы! Начнём: 0) Поскольку функция вызывается очень издалека, отслеживать её нет необходимости, поэтому не обращаем внимания на эту строку. 1) Обнуление «a3» происходит ДО предыдущего прыжка. А знаете, почему? Почему строка после инструкции перехода выполняется до самого перехода? Потому что ассемблер очень быстрый. Он реактивный, он сверхзвуковой! Процессор несётся по строчкам с небольшим опережением, поэтому, когда он получает очередную строку для обработки, он заранее готовится взять новую следующим шагом. То есть невозможно моментально изменить номер текущей строки. Это происходит с задержкой на полстроки, а именно: система принимает инструкцию прыжка (j*, b*), затем выполняет следующий оператор, и в это же время начинает готовиться брать наконец ту строку, на которую указала инструкция перехода. Короче, два выхода: либо мысленно поменять прыжок со следующей строкой (если там не ещё один прыжок, хотя на практике такого не делают…), либо ставить NOP после каждого прыжка (ведь он всё равно пропускается) 2) Дополнение для следующей строки, чтобы нормально загрузить адрес «8007FDF0». Пока что грузится лишь «80080000» в «a0». 3) Прибавление к «80080000» окончания «FDF0» даёт в сумме адрес «8007FDF0». Странно? Даже очень, если рассмотреть строки 29 и 30, где при той же технике число «10000» не отнимается из результата. Там, оказывается, есть объяснение… Когда прибавляется значение, не превосходящее «7FFF», то оно просто складывается; а если это число больше или равно «8000», то из результата сперва вычитается «10000»! Зачем не знаю, но так везде – если правая часть адреса (или любого регистра) не меньше 8000, то левую надо увеличить на «0001» (00010000) предыдущим шагом. 4) Получение значения настройки Стерео/Моно («8007FDF0») и запись его в «v1». Как мы знаем, настройка обозначает «00000000» для стерео и «00000001» для моно. 5) Записывается в «v0» простая единичка («00000001»). Сейчас узнаем, зачем… 6) Вычитание! Самое оно (про которое я упомянул, когда в ArtMoney внёс в ячейку число «2»)! Из единицы вычитается флаг стерео, который либо 0, либо 1. Таким образом он меняет свое состояние, превращаясь из нуля в единицу и из единицы в ноль. Именно это действие можно назвать самым центром настройки стереозвука в меню. Эта строчка меняет стерео на моно и моно на стерео. 7) Прыжок. Если «v0» (результат вычитания), т.е. новое состояние переключателя, равно нулю (переход с моно на стерео), то прыгнуть к строке № 20, а иначе (переключение со стерео на моно) – продолжить исполнение кода дальше. 8) Действие, обратное строке № 4, которое сохраняет значение регистра «v0» (новое состояние настройки) по тому же самому адресу, по которому оно было взято ранее, а именно «8007FDF0», ведь регистр «a0» с тех пор не изменился. Вспомним, что это действие выполняется ПЕРЕД прыжком, в независимости от того, будет ли он совершён в предыдущей строке. 9) Число «3F» (т.е. «63» в десятичной) заносится в регистр «v0». Это не адрес (как предполагает ps2dis, создавая метку), а то число, которое должно оказаться в каждом из четырёх байт моно-режима. Ведь этот код выполняется для преобразования в моно, так что байты должны задаваться тут. 10) Верхняя часть адреса сохраняется в «at». Поскольку все четыре конечных адреса 12d0, 12d1, 12d2, 12d3 не превосходят 8000, в верхний разряд идёт «8008» как полагается. 11) Байт «3F» (63) сохраняется в «800812D3». Он должен быть записан в три другие ячейки без изменений. 12,13,14,15,16,17) Сохранение, почему-то, происходит в обратном порядке, то есть сначала в 800812D3 (предыдущий шаг), потом в 800812D2, затем в 800812D1 и наконец, в 800812D0. Всё это делается однообразным кодом, в котором «at» постоянно принимает значение «80080000» перед непосредственной записью. 18) Прыжок к строке № 29, ведь иначе ветка для стерео тоже исполнится. Для ветвлений подобного типа всегда требуется два прыжка: один по условию, который отодвинет вперёд одну из веток; а другой прямо перед началом этой отодвинутой ветки, чтобы исполнитель, завершив ближнюю ветку, сразу прыгнул за конец дальней (хм, подобный приём я часто использовал на том самом Бейсике, ведь области ELSE там не было и в помине… Мне приходилось ставить на IF оператор GOTO, направив настолько вперёд, сколько хочу отдать под мнимый ELSE, а перед оператором назначения поставить ещё один GOTO, направленный после конца мнимого THEN… большие трудности произойдут, если я захочу немного дописать в ту или иную ветку… Благо, разрешено по 255 операторов в строке и между соседними строками особая нумерация (10,20,30,40…) позволяла вставить по 9 других… но если уж совсем невмоготу, то выручал GOSUB, направленный куда-нибудь далеко-далеко на 9000, где были поля свободного места, главное не забыть RETURN в конце… А вообще, знаете. Тот Синклер и всё связанное с ним очень поощряло ко всякого рода взломам! Вот что написано в одном абзаце _официальной_ инструкции: http://klimaleksus.narod2.ru/Files/1/cam_9.jpg ! Особенно меня поражает строчка «Например, взломаем загрузчик игры…») 19) Пустая команда, чтобы предшествующий прыжок прошёл без дополнительных нагрузок. 20) Как строка № 9, здесь «v0» получает число «7F» (т.е. 127 для стерео), который должен быть занесён уже не во все четыре, а только в две ячейки. 21) Опять пишем в «at» верхнюю часть «80080000», чтобы корректно определить адрес назначения. 22) Аналог № 11, но уже число «7F» (127) сохраняется в «800812D2». Да, тут уж совсем не по порядку, смотрите далее. 23) Ещё раз обновляем «at» (совершенно бесполезное действие, но зато даёт потенциальное место для собственных строчек кода!) 24) Пишем «7F» по «800812D0». А вот в остальные два уже нужны нули. 25) Всё тот же «at»… 26) Вот! Глядите, как интересно: ноль не прописывается явно в «v0», как можно было бы ожидать, а вместо «v0» применяется «zero», в котором всегда вечный ноль! Так что адрес «800812D3» с лёгкостью обнуляется. 27) «at»! 28) Повтор операции нулевого регистра, теперь для последнего адреса «800812D1». Это завершающая строка всего ветвления, дальше код исполняется и для моно и для стерео. 29) Регистр «a0» вновь принимает «80080000» для новых манипуляций. 30) Его превращают в «800812D0», я уже не знаю для чего, ведь интересующий меня код закончился. 31) Функция! Не знаю, зачем она нужна, да и знать не хочу. 32) Пустота для упрощения предыдущего вызова. Ну как вам кодик? (Вообще говоря, он мог быть построен десятком различных способов, например запомнить 63, 127 и 0 в разных регистрах, выбрать один из них по ветке, а код записи сделать единственным, или вообще грузить все 4 байта как одно word, как значение всего регистра через SW) Вот я стал планировать, какую его часть предстоит менять. Строки 0 и 1 к делу отношения не имеют, так что я их не трону. Строчки от 20 до 32 формируют такой набор байт, который необходим для правильного стерео режима. А поскольку я задумал всегда иметь бесконечное стерео, то этот код, в принципе, можно исполнять постоянно при смене режима камеры, чтобы стерео уж точно никуда не делось. Поэтому с 20 до 32 я тоже не изменю, а ветку направлю так, чтобы код исполнялся подряд, начиная с 20 и дальше, то есть стерео будет переопределяться постоянно. А как менять? Сначала я просто заменял адреса. Ну и регистры и числа. А потом смекнул, что значение переключателя моно-стерео «8007FDF0» на самом деле и определяет моно да стерео! Никакие 127=0=127=0, ни 63=63=63=63! Это бесполезно, вообще не знаю, для чего они. За стерео отвечает один байт «8007FDF0» – ноль = стерео, а единица (или что угодно) – моно. Если я на основе оригинала построю на нём переключатель камеры, то одновременно и режим звука будет меняться. (Как я это узнал? Вставил в уши наушники, запустил новую игру (прямо по курсу первый замороженный дракон), отвёл Спайро правее и ниже. Теперь кристаллический звук статуи слышен только в левом наушнике, а овцы, враги и девчачьи вздохи Спайро – как в правом, так и в обоих. При изменении в ArtMoney все звуки резко начинали проигрываться в обоих наушниках; обратно – снова раздельно. Кстати, музыку лучше отключить в меню, ибо она всегда играет одинаково, хотя может и нет, просто мало отличается…) Мне так не катит. Ещё раз, если я сохраню логику оригинальной ветки, проверяя «8007FDF0», и в зависимости от этого выставляя «8007F4B0» и «8007F390» (ну прыжок же тоже менять?), то моно и стерео у меня станут изменяться, как говорится, as well. Поэтому от «8007FDF0» вообще нельзя отталкиваться, его следует хранить нулевым (вечное стерео). Но блин, при этом само меню перестаёт работать, потому что (похоже) механизм отрисовки японского текста сам и проверяет адрес «8007FDF0» ! Чтобы избавится от такого, мне придётся найти и изменить его тоже. Вторая проблема – сохранение на карту памяти. Как мы знаем, камера НЕ МОЖЕТ быть сохранена на карту ни в каком виде, потому что превращается в японскую (по умолчанию) ПОСЛЕ загрузки игры из сейва. А вот флаг моно-стерео загружается. И сохраняется! Поэтому его следует постоянно приравнивать нулю, как при изменении режима камеры (т.е. даже если вы загрузитесь из моно, то оно станет стерео при первом же пользовании моей опцией), так и при сохранении на карту (даже если не менять камеру, моно станет стерео после следующей загрузки новой сохранёнки), но при самой загрузке трогать что-то большого смысла не имеет, можно даже не тратить время. Что ж, я меняю код! Жёстко меняю, словно я ас в ассемблере. Скриншот: http://klimaleksus.narod2.ru/Files/1/dis_11.png Ну как? Заметна разница? Распечатка: 2) 8002d70c: addu v1, zero, zero 3) 8002d710: lui a0, $8008 4) 8002d714: addiu a0, a0, $f390 5) 8002d718: lw v1, $0000(a0) 6) 8002d71c: beq v1, zero, $8002d734 7) 8002d720: nop 8) 8002d724: addiu v0, zero, $0073 9) 8002d728: addiu v1, zero, $0000 10)8002d72c: j $8002d740 11)8002d730: nop 12)8002d734: addiu v0, zero, $0053 13)8002d738: addiu v1, zero, $0001 14)8002d73c: nop 15)8002d740: sw v1, $0000(a0) 16)8002d744: sw zero, $0a60(a0) 17)8002d748: sw v0, $0120(a0) 18)8002d74c: nop 19)8002d750: nop А разница очень большая. Сперва расшифровка, затем логика: 2) Приравнять регистр «v1» нулю. 3) Записать в регистр «a0» число «80080000». 4) Изменить значение регистра «a0» на «8007F390». 5) Считать 4 байта по адресу «0000+a0» (т.е. «8007F390») и сохранить их в «v1». 6)Выполнить следующий оператор, затем сравнить содержимое «v1» с нулём; если не равно, то продолжать, а иначе перейти к адресу «8002D734» (что на 6 строк впереди, т.е. к строке № 12) 7) Ничего не делать. 8) Приравнять регистр «v0» числу «0073». 9) Приравнять регистр «v1» числу «0000». 10) Безвозвратно перейти по адресу «8002D740» (что на 5 строчек впереди, т.е. к строке № 15) после исполнения следующего оператора. 11) Ничего не делать. 12) Приравнять регистр «v0» числу «0053». 13) Приравнять регистр «v1» числу «0001». 14) Ничего не делать. 15) Сохранить в 4 байта по адресу «0000+a0» (т.е. «8007F390») содержимое регистра «v1». 16) Сохранить в 4 байта по адресу «0A60+a0» (т.е. «8007FDF0») нулевой регистр. 17) Сохранить в 4 байта по адресу «0120+a0» (т.е. «8007F4B0») содержимое регистра «v0». 18) Ничего не делать. 19) Ничего не делать. !! Ясно? За переключатель я взял сам флаг прыжка на бегу! Ноль (по умолчанию) – японская камера, прыжка нет, псевдостерео. Один – прыжок есть (по факту изменения ячейки), камера американская, псевдомоно. Логика: 2) Обнуляю «v1» на крайний пожарный, чтоб уж точно загрузилось верное значение. В принципе, этого можно и не делать. 3,4) Копия оригинальных строк 2 и 3, с той лишь разницей, что адрес уже не «8007FDF0» (стереомоно), а «8007F390» – прыжок на бегу. 5) Считываю из «8007F390» в регистр «v1». Его и буду проверять… 6) Ветвление. Не такое большое как в оригинале, мне ведь всего два регистра сменить… Сейчас проверяется не новое значение (не как в оригинале!) а ещё старое. То есть ноль означает переход со стерео на моно, а единица – с моно на стерео. Прыжок на строку 12 происходит при переходе на моно. Продолжение при стерео. ВНИМАНИЕ: на самом деле это уже никакие не «стерео» и не «моно»! Но ведь менюшка-то будет озаглавлена по-старому… Вот я и решил зафиксировать за японским словом «СТЕРЕО» режим японской камеры без прыжка, а под словом «МОНО» – американский с прыжком. И отношение «8007FDF0» с «8007F390» одинаково – стерео = нет прыжка = по умолчанию; моно = прыжок = по требованию. 7) Пустота, я не хочу путаться в операторах. 8) Японская ветка. Заношу в «v0» число «115», свойство японской камеры. 9) А в регистр «v1» – число «0», новое значение прыжка на бегу. (Хей! Вы заметили, что я избавился от вычитания? Не, реально, зачем оно нужно? Я лучше явно выберу значение в самой ветке. Кстати, поэтому я взял «$0000», а не «zero» – у американцев ведь единица будет…) 10) Прыжок на строку 15, чтобы код американской камеры не исполнился сейчас. 11) Пустота, для облегчения этого самого прыжка. 12) Американская ветка. Загружу в «v0» число 83, свойство американской камеры. 13) А в регистр «v1» – единицу, активирующую прыжок на бегу (и одновременно являющуюся флагом самого переключателя псеводостерео/псевдомоно). 14) Пустота. Ничем не оправданная, просто визуально отделяет окончание ветвления. Я ведь не ограничен количеством строк! При всём желании я даже мог бы поставить JAL и свалить хоть сотню строчек куда-нибудь в свободное место, если бы мне не хватило имеющегося здесь пространства. 15) Общая часть. Сохраняю «v1» по тому же самому адресу, по какому взял его (8007F390), но теперь его значение сменилось с 1 на 0 или с 0 на 1. По совместительству, прыжок на бегу. 16) Обнуляю адрес для режима звука, то есть «8007FDF0». А вы заметили, каким образом я это сделал? Я не стал по сто раз грузить «8008» в адресные регистры! Я немного посчитал… Мой адрес прыжка – «8007F390». Это меньше, чем «8007FDF0». И даже меньше «8007F4B0», адреса режима камеры! Выходит, что я могу просто плюсовать небольшие числа (а именно «0», «A60» и «120») при непосредственное записи, не меняя значения самого адресного регистра. 17) Тем же путём сохраняю «v0», свойство камеры, по вычисленному адресу «8007F4B0». Кстати, ps2dis тут сам запутался, и не может показать результирующий адрес, как он делал это в оригинале. Печальная новость: это означает, что не во всех случаях можно легко определить ссылающиеся ячейки… Значит мне с таким удачным взломом просто повезло. 18) Пустота. А действительно, мне больше нечего писать, я закончил! 19) Пустота, бывшая здесь и в оригинале. Знаменует окончание изменений, ведь к последующему коду я не прикасался. Думаете, взлом закончен? Не-а! Если оставить всё как есть, то меню не сможет переключать настройку, вернее, этого не будет видно. Потому что графическая часть всё ещё проверяет адрес «8007FDF0», значение режима звука. Цель: найти его и изменить на «8007F390»! А ведь поиск я уже производил – это первоначальная прогулка с F3, видная на ранних скриншотах выше в тексте. Перечислю-ка их ещё раз: http://klimaleksus.narod2.ru/Files/1/dis_2.png Вероятно. Ведь он – единственный, а следующим идёт тот блок, который я только что поменял. Оставшиеся: http://klimaleksus.narod2.ru/Files/1/dis_5.png Но тут сохраняется ноль. Возможно, это блок при загрузке игры, ведь стартовый экран проходит как стерео. http://klimaleksus.narod2.ru/Files/1/dis_6.png Функция, в которой считывается это значение. Возможно, что это нужная область… http://klimaleksus.narod2.ru/Files/1/dis_7.png http://klimaleksus.narod2.ru/Files/1/dis_8.png Сохранение и загрузка. Предполагаю, что на карту памяти… Понять, чему равен «s0» мне так и не удалось! Остаётся два варианта, либо адрес «80019F34» (http://klimaleksus.narod2.ru/Files/1/dis_2.png ), либо «8005B31C» (http://klimaleksus.narod2.ru/Files/1/dis_6.png ). Но следует учесть, что один из них должен использоваться самой игрой для воспроизведения стерео или моно звука. Что ж, раз второй лежит внутри функции, да ещё и очень далеко внизу, а первый почти перед кодом изменения меню, то я предполагаю, что менять следует именно адрес с первого из этих двух скриншотов. И я не ошибся! Новый код: http://klimaleksus.narod2.ru/Files/1/dis_12.png Незатейливое изменение строки 80019F34: с lw v0, $fdf0(v0) на lw v0, $f390(v0) Отныне менюшка полностью рабоатает! Остаётся сохранение на карту… По скриншоту http://klimaleksus.narod2.ru/Files/1/dis_7.png : Меняю регистр «v0» на вечно пустой «zero»: http://klimaleksus.narod2.ru/Files/1/dis_13.png То есть код 8005DD38: с sw v0, $fdf0(at) на sw zero, $fdf0(at) Готово! Под завершение, последний шртих: http://klimaleksus.narod2.ru/Files/1/dis_14.png Как же мне повезло! Длина надписей ровно 4 символа! А это значит, что для сохранения черырёхбайтовой чётности (это связано с тем, что JUMP инструкцией невозможно прыгнуть на строки, не кратные четырём… нужно же было запихать в 32 бита не только сам адрес, но и сигнатуру оператора!) системе пришлось добавить ещё четыре пустых байта. Мне столько даже и не нужно, меняю цвет: стерео – красный (японская камера), а моно – зелёный (американская камера): http://klimaleksus.narod2.ru/Files/1/dis_15.png Всё становится понятно с первого взгляда. Если интересно, как ps2dis смотрит на это, то вот: http://klimaleksus.narod2.ru/Files/1/dis_16.png Хи, он-то думает, что это какой-то супершифрованный нераспознающийся код! Таблица для ArtMoney (прилагается к патчу): http://klimaleksus.narod2.ru/Files/1/dis_17.png 1) «Прыжок 1/0» – если «0», то прыжка нет; иначе есть. 2) «Кам 83/115» – если «115», то камера японская, если «83» – то американская; иначе – гибрид. 3) «заставка 1-4» – управление объектом Спайро на начальном экране. «1» – Спайро, «2» – первый сбитый монстрик, «3» – табличка Бессонных, «4» – пробегающий монстрик; иначе – крах эмулятора. Актуально ТОЛЬКО при начальном меню, в остальной игре не действует. 4) «поле зрения 1» – угол обзора камеры. «1» – стандарт. Увеличение – приближение, сокращение обзора. Уменьшение в отрицательную строну – отдаление, повышение обзора. Сильно отрицательные значения при выравнивании камеры приводят в вылету из эмулятора. 5) «Стерео 0/1» – если «0», то стерео; иначе – моно. 6,7,8,9) «127/63» или «0/63» – байты, принимающие «127» или «0» для стерео, и «63» для моно. Назначение неизвестно. Мой патч официально закончен (в бета-версии обнаружилась ошибка документа, отменяющего патч для SCPS). Однако, я рассчитывал, что nihonjin подскажет мне правильные японские слова, которые следует поставить вместо «стерео» и «моно» в меню… Хотя, в русской версии будет по-другому, по-нашему. А вот японскую версию с исправленной камерой мне бы хотелось «раскрутить» не слабее Вихря[url=http://forum.darkspyro.net/spyro/viewposts.php?topic=52149]![/url] Дайте правильные слова, а я ещё и доработаю патч так, чтобы он выставлял верные CRC суммы проверки ошибок в BIN, чтобы образ не портился после применения патча, и чтобы его можно было прожечь на реальный диск для самой приставки без опасений (а то мне кажется, что ни фига не будет пахать на соньке, ведь образ выйдет запоротый…) А ещё, по аналогии с Вихрем, я сделал неплохое многозначительное стихотворение-пародию (угадайте, на что?) по поводу взлома японской камеры: [c][b]Камера[/b] Зима недовольства заменится летом, Когда завладею восточным секретом И камеру странную восстановлю, Ей пунктик особый добавлю в меню; Удобно раскрашу, Усердно отлажу, На критику миру отдам! Чтоб странствия наши Привычней и краше, Доступнее сделались вам. Для этого первым обыденным шагом Значения в памяти перемешаю, Проверю, замечу, мозги шевельну, И даже прыжок при атаке верну! А в этом поможет мне сяо-Ассемблер: Исправлю часть кода, да в патч сохраню, И образ отныне навек изменю – Пусть будут таблички отыщены всеми, Пусть тайны игры постигают усердней… Держите! Всё вам безвозмездно дарю.[/c] [r][i](Кстати, к слову: кто отважится назвать единственные восемь книг, которые читали Insomniac Games?)[/i][/r] Копия ссылки на патч: http://klimaleksus.narod2.ru/Files/1/Japancam.rar [hr] Эх… Три мультипоста, а я снова ничего не ответил по всем предыдущим сообщениям… Стоило ли мне вообще тратить время на описание этапов взлома? Не лучше ли было вместо этого сделать что-нибудь полезное? Ну, к примеру, я мог бы ответить во всех необходимых темах, да ещё и закрыть долги по ЛС (сколько ж я их накопил…). Но в темах я и так рано или поздно ответил бы. Или я мог провести два других игровых взлома, чтобы создать что-то новое. Либо, например, сделать одно большое исследование звукового формата или объёмных моделей. Тогда бы я создал что-то ещё. …И только лишь. Вместо этого я написал свой комментарий, руководствуясь которым, кто-то другой мог бы на миг превратиться в меня и тоже взломать и улучшить что-то, может даже не один раз. А это бессмертно. Что перспективнее: десяток взломов, или ещё один живой хакер? А я делаю хакера. В надежде, что кто-то прочитав пост, сможет сотворить столько же, или даже ещё больше, чем я. Пустой тратой времени это не назвать.