Короткий познавательный пост, основанный на моей больной фантазии. Все совпадения случайны.
Обратился как-то один человек на профильном форуме с вопросом, как запустить (не сказать чтобы старую) игру на Windows 10. Игра (хотя я бы назвал это технодемо) была разработана на легендарном XNA 4.0 (земля пухом) и планировалась к выпуску через Steam Greenlight (и ему земля пухом) еще в далеком 2012, после чего заброшена и убрана со всех магазинов.
Игра была куплена тем человеком еще в тот момент, когда она легально продавалась. А спустя годы она просто перестала подавать признаки жизни. $5 за полную версию уплочены, а поиграть нельзя. Обидно.
Так как типовые решения по типу установки XNA Redistributable не помогли, то был предложен единственно возможный выход из ситуации - отладка через dnSpy.
Внимание: обратная разработка хоть прямо и не запрещена законодательно, но разрешенные случаи весьма ограниченны. В других странах разрешен явный запрет любых манипуляций через лицензию, превращающий это занятие во вполне наказуемое. Не повторяйте дома.
Так как ПО было приобретено легально и оно не работает, то этот теоретический случай допустим в соответствии со статьей 1280 гражданского кодекса.
Демонстрировать буду на бесплатной демоверсии, скачать можно здесь.
dnSpy
Театр начинается с вешалки, а программа на C# с атрибутов сборки. Они нам сейчас не сильно интересны, поэтому пытаемся сразу запускать отладку. С неизвестными программами лучше использовать виртуальную машину, так как может быть встроена какая-нибудь пакость для кулхацкеров, но мне можно и так.
Ошибка. Открываем стек вызовов и ищем место, где произошел провальный запрос в сеть.
А вот и виновник торжества. По сообщению из исключения можно сделать вполне однозначный вывод, что проблемой является скупердяйство и непредусмотрительность автора, который решил перестать платить за домен и хостинг, а вместе с этим случайно (а может и намеренно) сделал запуск игры невозможным без интернета. Так как на сайте заявлено отсутствие DRM, то это явно недосмотр.
Что тут происходит?
Если почитать документацию на XNA, то этот метод предназначен для загрузки контента и вызывается автоматически при запуске игры. Первым делом выполняется инициализация некоторых вещей, после чего открывается файл настроек (options.xml) и начинает последовательно считываться. Как только считываются параметры аккаунта для таблицы рекордов, то происходит отправка логина+пароля на официальный сайт и попытка получить ID. Если сервер возвращает Failure, то открывается диалог входа, который можно пропустить. Если сервер возвращает число, то игра запускается. А вот если сервер недоступен, то вылетает исключение, которое никак не обрабатывается и приводит к молчаливому вылету.
Диалог входа официально можно пропустить.
На этом этапе есть 2 варианта действий:
Вручную отредактировать настройки и убрать пункт об аккаунте. Тогда игра будет всегда запускаться без входа.
Физически убрать запрос на несуществующий сайт.
Нормальные герои всегда идут в обход, так что переходим к написанию небольшого патча. (На самом деле, о первом варианте я додумался не сразу. Более того, полная версия зависает при убирании этого пункта из настроек).
Выделяем строчку с запросом и нажимает "Изменить инструкции IL". Открывается окно с заголовком "Изменение тела метода".
Это - язык MSIL. Он очень прост и сильно напоминает Forth, на самом деле, но новичка может отпугнуть.
Ключевым элементом является стек. Что такое стек я уже когда-то рассказывал. Это некоторое место, на вершину которого можно последовательно добавлять значения (числа, строки и прочие объекты), а так же снимать их в обратном порядке.
Не эти стеки, но суть та же. Можно добавлять сверху новые фишки и снимать с вершины старые.
ldloc.2: Значение локальной переменной под номером 2 (с типом строки)
ldstr: Строку "&p=".
ldloc.3: Значение локальной переменной под номером 3 (тоже строка).
Потом мы вызываем String::Concat(), принимающий в себя 4 строки и соединяющий их вместе. При вызове метода будут сняты 4 верхние строки со стека и добавлена новая строка.
Вызов следующего метода (WebClient::DownloadString()) снимает строку, полученную от String::Concat(), и веб-клиент. В результате должна остаться только 1 строка, возвращенная WebClient::DownloadString().
Следующим этапом мы должны снять эту строку со стека и сохранить её в локальную переменную 1, но этого не произойдет, так как WebClient::DownloadString() всегда будет проваливаться и выбрасывать исключение. Исключение, если его не обработать сразу, будет раз за разом проваливаться глубже по стеку вызовов, пока не достигнет обработчика или дна. Так как никаких обработчиков по пути нет, то исключение достигает дна и приводит к вылету.
Чтобы избежать получения исключения, мы можем убрать всё, что предназначено для запроса в сеть. Примерно вот так:
8 инструкций превратились в 2: загрузка строки с числом (которое может быть любым) и сохранение в локальную переменную под номером 1.
Сохраняем и проверяем результат.
Было.
Стало.
Из декомпилированного кода полностью исчез кусок с запросом. Осталась лишь загрузка константной строки и сравнение с "Failure", которое всегда будет успешным.
Сохраняем модуль и пытаемся запускать опять.
Жопиздан!
С этой задачей успешно справился человек, который не смыслит в программировании вообще ничего. По его словам, он джва года ждал эту игру!
Но не всё так просто. Если попытаться пройти любой уровень, то вылетит то же самое исключение.
Для исправления точно так же выделяем строку и опять редактируем инструкции в теле метода. Но так как отправка статистики нонче ни на что не влияет, то просто удаляем выделенные инструкции без остатка.
А для надежности выделяем WebClient, нажимаем "Анализировать" и смотрим, где еще может ждать нас подлянка.
В Finish.Update() мы ошибку только что исправили, в Game1.LoadContent() исправили в самом начале, а Login.button1_Click(), Program.Main() и Title.Update() нас не интересуют, так как достаточно просто не делать того, что приведет к обращению в сеть (а конкретно: не открывать диалог входа, не указывать никакие аргументы при запуске и не пытаться авторизоваться через главное меню (вроде бы не реализовано)).
В прошлом году были в Москве (привет @jewellerpotato). Впечатлений и фотографий было много, всё сразу и не расскажешь. И вот появился повод показать музей космонавтики.
Приехали мы на метро и сразу увидели этот грандиозный памятник. Никогда не видел его вживую. Масштабы и размер впечатляют! А ещё впечатляет то, что он полностью отделан из титана!
А чтобы поняли масштаб сбоку постамента вот такой барельеф:
Так вот стоя на ступеньке (слева внизу фото) возле Юрия Гагарина можно дотянуться рукой до большого пальца руки. Настолько высокий барельеф.
Грандиозный монумент
Вот несколько фотографии из самого музея.
1 из 10
Земля в иллюминаторе!
Красивая инсталляция лунной миссии:
1 из 2
А тут космонавты вернулись на землю:
Настоящие метеориты. Можно потрогать и зарядиться космической энергией:
Рекомендую музей к посещению. Очень понравилось. Хочется, чтобы космос покоряли и дальше.
Два организованных разнонаправленных потока толпы разделили улицу на две равные части. Служители закона, клерки, студенты и простые работяги, все в одинаковых светло-серых костюмах, с одинаковыми улыбками на, конечно же, разных лицах.
Все они были счастливы! Ведь иначе и быть не могло, директива 13-71 кодекса продуктивного сообщества прямо гласила: «Счастливый гражданин на 30% эффективнее несчастливого и на 75% эффективнее несчастного. Каждый добропорядочный гражданин обязан быть счастливым!». Счастливым был и Никон. Этим светлым солнечным утром он передвигался в общем потоке в сторону редакции газеты «Июльский вестник». Парадоксально, но выходила газета регулярно и в другие месяцы. Просто дирекция решила, что такое название даёт позитивный настрой даже самым неприятным новостям.
Никон прошёл через проходную, посмотрел на большое информационное табло с дежурной надписью «Средний уровень счастья граждан равен 114 единицам». Хороший показатель. Значит и новости сегодня можно писать нейтральные, возможно даже скучноватые, дабы не приедались хорошие. Их редакция приберегала на случаи, когда показатель падал ниже сотни.
Стоило Никону подойти к своему рабочему столу, его наручный монитор взволнованно завибрировал о неприемлемом повышении уровня стресса. Никон даже не сразу понял почему, но потом осознал, что смотрит на жёлтый бумажный ярлычок, приклеенный к центру стола. Вызов «на ковёр». Редко, когда это делалось от хорошей жизни. Никон положил свой кожаный портфель на стул, и сразу направился на управленческий этаж.
– Присаживайтесь, – даже не взглянув на вошедшего Никона бросил старший управляющий. Низкий, толстый, лысеющий и всегда с печальным лицом. По документам его уровень счастья никогда не снижался до критической отметки, хотя, мешки под глазами и полное отсутствие даже намёка на улыбку говорили об обратном.
– Доброе утро, Синеж Евменьевич! – голос Никона слегка дрожал. Он сел на стул перед своим начальником и смиренно сложил руки на колени.
– Доброе. Последнее время ваша эффективность значительно снизилась. Вы принимаете фелицитин?
– Нет, не было необходимости.
– С сегодняшнего дня пропейте курс. И завтра проснитесь в хорошем настроении!
– Слушаюсь!
Никон встал и молча вышел из кабинета управляющего и вернулся к своему рабочему месту. Он вынул из портфеля индивидуальный фелицитаметер. Трясущимися руками извлёк тест полоску, проткнул палец ланцетом и выдавил каплю крови. Десять секунд долгого ожидания, пока прибор проводил свои измерения, и вот, на экране отобразился результат: семьдесят восемь единиц. На десять пунктов ниже критической отметки! Сердце начало колотиться как бешеное, словно испуганный заяц, попавший в капкан, пыталось вырваться из грудной клетки.
Никон срочно достал из портфеля небольшую стеклянную баночку с таблетками и заглотил сразу две. Через минуту сердцебиение выровнялось, нахлынувший адреналин отпустил свою холодную хватку и Никон, сделав несколько глубоких вдохов, снова воспользовался фелицитаметром: восемьдесят девять единиц. «Что же, хорошо. Так лучше», – пробормотал он себе под нос, и принялся, наконец-то, за работу.
Вечером пришлось немного задержаться, чтобы нагнать отставание в планах по статьям, и возвращался Никон уже не в организованном потоке сограждан, а в одиночестве. Накрапывал слабый дождь, приятно охлаждая разогретые щёки.
Никон сунул руку в карман пиджака и наткнулся на флакон с фелицитином. О чём-то задумавшись, он споткнулся о небольшой камень и выронил пузырёк. А тот, с радостным звоном ударился об асфальт и тут же укатился в ливнёвку вместе с потоком дождевой воды.
«Да что ж такое!», – Никон тут же бросился на землю, сунул руку в отверстие слива, но сколько он не шарился в темноте и пустоте, ничего, кроме мусора и грязной воды нащупать так и не смог.
Утро ударило по голове ужасный грохочущим звуком будильника. Синеж Евменьевич дал чёткое и понятное распоряжение: проснуться в хорошем настроении. Задание было провалено. Фелицитаметр показал шестьдесят пять единиц. Руки слегка тряслись, но сделать ничего с этим сейчас было нельзя. Последний пузырёк фелицитина исчез в канализации прошлым вечером. «Зайду в аптеку по дороге на работу», – пробормотал Никон себе под нос и встал с кровати.
Пополнить запасы фелицитина, правда, ему так и не пришлось. Стоило выйти на улицу и повернуться в сторону работы, к нему подошёл мужчина в тумно-сером костюме. Незнакомец молча положил Никону руку на плечо и жестом указал на припаркованную неподалёку машину.
Нерадивого нарушителя показателя счастья увезли в сторону, противоположную от редакции «Июльского вестника».
Информационное табло показывало средний уровень счастья в одну сотню и двадцать пять единиц. Высокий показатель. Значит, все граждане счастливы! Партия на правильном пути!
Туманность Розетка — область активного звездообразования в созвездии Единорога, где гигантские облака водорода и пыли под действием гравитации сжимаются, формируя новые светила.
ааа... ну тогда да. либо новым постом надо было пилить продолжение. либо всувать в пост, пока есть возможность.
даж хз как лучше, а как правильнее.
ах если бы)
когда-то в сети тож чел один сказал шо его город чёт с птицами связан
я не знала о таком городе, но чот по рофлу спизданула мол "хы, ПТИЧИНСК...БЁРДСК!"
и угадала. он ахуел ибо подумал, что я знаю его и...