Pull to refresh

Comments 8

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

Для использования нужно запускать в консоли команды вида: mutator in.nes out.nes keyword 100, где keyword используется как randseed (то есть для одного и того же слова будет один и тот же набор случайных мутаций), а число задаёт количество мутаций (конкретно сколько байт будет испорчено).

Программа для создания мутаций
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

class NesFile
{
	public string FileName;

	public byte[] Header;
	public byte[] Trainer;
	public List<byte[]> PRG;
	public List<byte[]> CHR;
	public byte[] Attach;

	public NesFile(string filename)
	{
		FileName = filename;

		var ROM = File.ReadAllBytes(FileName);
		if (ROM[0] != 'N' || ROM[1] != 'E' || ROM[2] != 'S' || ROM[3] != 0x1A)
		{
			throw new Exception("Invalid iNES header");
		}

		Header = new byte[16];
		Array.Copy(ROM, 0, Header, 0, 16);
		var offset = 16;

		if ((Header[6] & 0x04) != 0)
		{
			Trainer = new byte[512];
			Array.Copy(ROM, offset, Trainer, 0, Trainer.Length);
			offset += Trainer.Length;
		}

		PRG = new List<byte[]>();
		for (var i = 0; i < Header[4]; i++)
		{
			PRG.Add(new byte[16384]);
			Array.Copy(ROM, offset, PRG[i], 0, PRG[i].Length);
			offset += PRG[i].Length;
		}

		CHR = new List<byte[]>();
		for (var i = 0; i < Header[5]; i++)
		{
			CHR.Add(new byte[8192]);
			Array.Copy(ROM, offset, CHR[i], 0, CHR[i].Length);
			offset += CHR[i].Length;
		}

		if (ROM.Length > offset)
		{
			Attach = new byte[ROM.Length - offset];
			Array.Copy(ROM, offset, Attach, 0, Attach.Length);
		}
	}

	public void PrepareHeader()
	{
		Header[4] = (byte)PRG.Count;
		Header[5] = (byte)CHR.Count;
	}

	public void Save(string filename = null)
	{
		PrepareHeader();

		if (filename != null)
		{
			FileName = filename;
		}

		using (var fs = File.OpenWrite(FileName))
		{
			fs.Write(Header, 0, Header.Length);

			if (Trainer != null)
			{
				fs.Write(Trainer, 0, Trainer.Length);
			}

			for (var i = 0; i < PRG.Count; i++)
			{
				fs.Write(PRG[i], 0, PRG[0].Length);
			}

			for (var i = 0; i < CHR.Count; i++)
			{
				fs.Write(CHR[i], 0, CHR[0].Length);
			}

			if (Attach != null)
			{
				fs.Write(Attach, 0, Attach.Length);
			}
		}
	}
}

class Mutagen
{
	static void Main(string[] args)
	{
		if (args.Length < 4) return;
		var nes = new NesFile(args[0]);
		var rnd = new Random(args[2].GetHashCode());
		var crb = Int32.Parse(args[3]);
		for (int i = 0; i < crb; i++)
		{
			var prg = rnd.Next(nes.PRG.Count);
			var pos = rnd.Next(nes.PRG[prg].Length);
			var shift = rnd.Next(-2, +2);
			int value = nes.PRG[prg][pos] + shift;
			nes.PRG[prg][pos] = (byte)(value & 0xFF);
		}
		nes.Save(args[1]);
	}
}
UFO just landed and posted this here
Всё же «ампутация», на мой взгляд, больше подходит к отрезанию каких-то внешних вещей, не относящихся непосредственно к коду. В статье выше упоминается удаление некоторых тайлов в CHR ROM для того, чтобы сделать нарисованные ими элементы невидимыми.
Ежели у Вас селекция, то у нас — ампутация.
Не понял, почему вы говорите, что у меня селекция. Я сам занимался именно реверс-инжинирингом оригинальной менюшки, и в статье об этом сказано. Поскольку кода было немного (всего 16 килобайт), получилось разобраться в её работе на 100%, и на её основе сделать демку.
UFO just landed and posted this here
Как же теперь легко будет матери объяснить пользу ГМО. Спасибо.
Заходил несколько дней назад к вам на сайт, увидел обновление к Unchained Nostalgia, думаю, наверняка скоро и интересная статья появится, и вот она!

Есть какие-то предположения, сколько потребовалось проб и ошибок для создания такого хака? Это все, полагаю, вручную проверялось?
Сколько именно времени ушло на создание хака — не знаю. Но его автор выкладывает и другие подобные хаки. Например, вот пачка странных хаков игры Battle City. На мой вопрос автор этого хака ответил лаконично:
Как делал? Просто, но долго. Корруптором.
Потом в процессе разговора он уточнил суть используемого им метода:
Но не всегда случайные и не всегда случайным образом. Можно определённые на определённые. Но если ты не знаешь АСМ, дебаггеры и т.д., то только так.
Видимо, «случайные» правки всё же как-то были приправлены интуицией. Как видно, использованная программа позволяет задавать некоторые правила для «мутаций»:
image

Я сам в детстве (15 лет назад) просто исправляя в HEX-редакторе байты, которые мне просто «не нравились», умудрился снять требование регистрации в одной из вариаций игры «О, счастливчик!» (я писал свой вариант этой игры, и взламывал «конкурента», который тоже был написан школьником). Просто очень сильно повезло. Плюс много терпения и сотни перезапусков с разными исправлениями :)

Сделал подобную программу для игр Sega Mega Drive, инвертирующую инструкции условного перехода (равно <---> не равно). Из-за того, что процессор Сеги обращается к памяти только по чётным адресам, портится меньше областей, не относящихся к инструкциям.

Нужная инструкция находится посекторной порчей: портим половину рома, восстанавливаем и портим вторую половину. Потом первую половину первой половины, вторую половину первой половины и так до конца.

При каждой порче пользователь проверяет наличие нужного ему изменения в эмуляторе и выбирает один из трёх пунктов:

1 баги мешают увидеть есть ли нужное изменение - область остаётся на будущее изучение

2 баги не мешают увидеть, что нужного изменения нет - эта область рома больше не трогается

3 баги не мешают увидеть, что нужное изменение есть. В этой области начинается двоичный поиск инструкции

На это могло уходить два часа, поэтому искал способы уменьшить поломки игры.

Оказалось, что достаточно портить инструкции в случайном порядке. В итоге теперь получается найти нужное за 20-30 минут.

Примеры

(в скобках - количество шагов)

Ultimate Mortal Kombat 3 (USA)
Чтобы секретные меню были изначально, без ввода комбинации. Оказалось, что инструкций понадобится две - одна отображает эти меню (56), другая даёт по ним ходить (34). Потом обнаружилась ещё одна альтернативная, активирующая эти меню при нажатии "вниз" (127), будто была набрана полная комбинация

Streets of Rage (W) (REV00) [!]
Секретное меню выбора уровня (19)
Выбор одинаковых персонажей (48)

Streets of Rage 2 (USA)
Секретное меню выбора уровня, на этот раз понадобились три инструкции: отображение пункта меню (83), доступ к нему сверху (156) и доступ снизу (45). Можно было ограничиться только одной стороной или вообще без отображения, но хотелось сделать красиво

Streets of Rage 3 (USA)
На этот раз аж 5 инструкций: доступ сверху (60), доступ снизу (43), а отображение - уже комбинация инструкций: нет номера уровня и пункта Exit (18) + есть номер и Exit (46) + ещё одна инструкция. Последнюю нашёл только предварительно изменив первые 2 инструкции в роме, т.к. она сама по себе, без сочетания с остальными, не находима - визуально ничего не меняет в игре

Contra - Hard Corps (USA, Korea) (En)
Выбор одинаковых персонажей - 2 инструкции: для нажатия "вверх" или "влево" (114) и "вниз" или "вправо" (217)

Shining Force II (U)
Секретное меню (26)
Режим управление врагами (185)

Sign up to leave a comment.

Articles