2013-10-25 00:59:34 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
|
2013-10-27 22:07:40 +00:00
|
|
|
|
using BizHawk.Common;
|
2014-07-03 15:35:50 +00:00
|
|
|
|
using BizHawk.Common.NumberExtensions;
|
2013-11-04 01:06:36 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
2013-11-03 03:54:37 +00:00
|
|
|
|
namespace BizHawk.Client.Common
|
2013-10-25 00:59:34 +00:00
|
|
|
|
{
|
|
|
|
|
public class RomGame
|
|
|
|
|
{
|
2017-04-14 19:59:01 +00:00
|
|
|
|
public byte[] RomData { get; }
|
|
|
|
|
public byte[] FileData { get; }
|
|
|
|
|
public GameInfo GameInfo { get; }
|
|
|
|
|
public string Extension { get; }
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
private const int BankSize = 1024;
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
public RomGame()
|
|
|
|
|
{
|
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
public RomGame(HawkFile file)
|
|
|
|
|
: this(file, null)
|
|
|
|
|
{
|
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
public RomGame(HawkFile file, string patch)
|
|
|
|
|
{
|
|
|
|
|
if (!file.Exists)
|
2014-01-08 03:53:53 +00:00
|
|
|
|
{
|
2013-10-25 00:59:34 +00:00
|
|
|
|
throw new Exception("The file needs to exist, yo.");
|
2014-01-08 03:53:53 +00:00
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
Extension = file.Extension;
|
|
|
|
|
|
|
|
|
|
var stream = file.GetStream();
|
|
|
|
|
int fileLength = (int)stream.Length;
|
|
|
|
|
|
2014-01-08 03:53:53 +00:00
|
|
|
|
// read the entire contents of the file into memory.
|
|
|
|
|
// unfortunate in the case of large files, but thats what we've got to work with for now.
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
// if we're offset exactly 512 bytes from a 1024-byte boundary,
|
|
|
|
|
// assume we have a header of that size. Otherwise, assume it's just all rom.
|
|
|
|
|
// Other 'recognized' header sizes may need to be added.
|
|
|
|
|
int headerOffset = fileLength % BankSize;
|
2017-07-23 14:26:08 +00:00
|
|
|
|
if (headerOffset.In(0, 128, 512) == false)
|
2013-10-25 00:59:34 +00:00
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("ROM was not a multiple of 1024 bytes, and not a recognized header size: {0}. Assume it's purely ROM data.", headerOffset);
|
|
|
|
|
headerOffset = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (headerOffset > 0)
|
2014-01-08 03:53:53 +00:00
|
|
|
|
{
|
2013-10-25 00:59:34 +00:00
|
|
|
|
Console.WriteLine("Assuming header of {0} bytes.", headerOffset);
|
2014-01-08 03:53:53 +00:00
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
2014-01-08 03:53:53 +00:00
|
|
|
|
// read the entire file into FileData.
|
2013-10-25 00:59:34 +00:00
|
|
|
|
FileData = new byte[fileLength];
|
2015-04-21 23:50:15 +00:00
|
|
|
|
stream.Position = 0;
|
2013-10-25 00:59:34 +00:00
|
|
|
|
stream.Read(FileData, 0, fileLength);
|
|
|
|
|
|
2014-01-08 03:53:53 +00:00
|
|
|
|
// if there was no header offset, RomData is equivalent to FileData
|
|
|
|
|
// (except in cases where the original interleaved file data is necessary.. in that case we'll have problems..
|
|
|
|
|
// but this whole architecture is not going to withstand every peculiarity and be fast as well.
|
2013-10-25 00:59:34 +00:00
|
|
|
|
if (headerOffset == 0)
|
|
|
|
|
{
|
|
|
|
|
RomData = FileData;
|
|
|
|
|
}
|
2019-11-04 01:55:38 +00:00
|
|
|
|
else if (file.Extension == ".DSK" || file.Extension == ".TAP" || file.Extension == ".TZX" ||
|
|
|
|
|
file.Extension == ".PZX" || file.Extension == ".CSW" || file.Extension == ".WAV" || file.Extension == ".CDT")
|
|
|
|
|
{
|
|
|
|
|
// these are not roms. unforunately if treated as such there are certain edge-cases
|
|
|
|
|
// where a header offset is detected. This should mitigate this issue until a cleaner solution is found
|
|
|
|
|
// (-Asnivor)
|
|
|
|
|
RomData = FileData;
|
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
2014-01-08 03:53:53 +00:00
|
|
|
|
// if there was a header offset, read the whole file into FileData and then copy it into RomData (this is unfortunate, in case RomData isnt needed)
|
2013-10-25 00:59:34 +00:00
|
|
|
|
int romLength = fileLength - headerOffset;
|
|
|
|
|
RomData = new byte[romLength];
|
|
|
|
|
Buffer.BlockCopy(FileData, headerOffset, RomData, 0, romLength);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (file.Extension == ".SMD")
|
2014-01-08 03:53:53 +00:00
|
|
|
|
{
|
2013-10-25 00:59:34 +00:00
|
|
|
|
RomData = DeInterleaveSMD(RomData);
|
2014-01-08 03:53:53 +00:00
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
if (file.Extension == ".Z64" || file.Extension == ".N64" || file.Extension == ".V64")
|
2014-01-08 03:53:53 +00:00
|
|
|
|
{
|
2013-10-25 00:59:34 +00:00
|
|
|
|
RomData = MutateSwapN64(RomData);
|
2014-01-08 03:53:53 +00:00
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
2014-01-08 03:53:53 +00:00
|
|
|
|
// note: this will be taking several hashes, of a potentially large amount of data.. yikes!
|
2013-10-25 00:59:34 +00:00
|
|
|
|
GameInfo = Database.GetGameInfo(RomData, file.Name);
|
2017-07-23 14:26:08 +00:00
|
|
|
|
|
|
|
|
|
if (GameInfo.NotInDatabase && headerOffset==128 && file.Extension == ".A78")
|
|
|
|
|
{
|
|
|
|
|
// if the game is not in the DB, add the header back in so the core can use it
|
|
|
|
|
// for now only .A78 games, but probably should be for other systems as well
|
|
|
|
|
RomData = FileData;
|
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
CheckForPatchOptions();
|
|
|
|
|
|
|
|
|
|
if (patch != null)
|
|
|
|
|
{
|
|
|
|
|
using (var patchFile = new HawkFile(patch))
|
|
|
|
|
{
|
|
|
|
|
patchFile.BindFirstOf("IPS");
|
|
|
|
|
if (patchFile.IsBound)
|
2014-01-08 03:53:53 +00:00
|
|
|
|
{
|
2015-04-19 13:55:47 +00:00
|
|
|
|
RomData = IPS.Patch(RomData, patchFile.GetStream());
|
2014-01-08 03:53:53 +00:00
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static byte[] DeInterleaveSMD(byte[] source)
|
|
|
|
|
{
|
|
|
|
|
// SMD files are interleaved in pages of 16k, with the first 8k containing all
|
|
|
|
|
// odd bytes and the second 8k containing all even bytes.
|
|
|
|
|
int size = source.Length;
|
2014-01-08 03:53:53 +00:00
|
|
|
|
if (size > 0x400000)
|
|
|
|
|
{
|
|
|
|
|
size = 0x400000;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-25 00:59:34 +00:00
|
|
|
|
int pages = size / 0x4000;
|
2014-01-08 03:53:53 +00:00
|
|
|
|
var output = new byte[size];
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
for (int page = 0; page < pages; page++)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < 0x2000; i++)
|
|
|
|
|
{
|
|
|
|
|
output[(page * 0x4000) + (i * 2) + 0] = source[(page * 0x4000) + 0x2000 + i];
|
|
|
|
|
output[(page * 0x4000) + (i * 2) + 1] = source[(page * 0x4000) + 0x0000 + i];
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-17 18:18:26 +00:00
|
|
|
|
|
2013-10-25 00:59:34 +00:00
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-14 19:59:01 +00:00
|
|
|
|
private static unsafe byte[] MutateSwapN64(byte[] source)
|
2013-10-25 00:59:34 +00:00
|
|
|
|
{
|
|
|
|
|
// N64 roms are in one of the following formats:
|
|
|
|
|
// .Z64 = No swapping
|
|
|
|
|
// .N64 = Word Swapped
|
2014-12-03 06:14:59 +00:00
|
|
|
|
// .V64 = Byte Swapped
|
2013-10-25 00:59:34 +00:00
|
|
|
|
|
|
|
|
|
// File extension does not always match the format
|
|
|
|
|
int size = source.Length;
|
|
|
|
|
|
|
|
|
|
// V64 format
|
|
|
|
|
fixed (byte* pSource = &source[0])
|
|
|
|
|
{
|
|
|
|
|
if (pSource[0] == 0x37)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < size; i += 2)
|
|
|
|
|
{
|
|
|
|
|
byte temp = pSource[i];
|
|
|
|
|
pSource[i] = pSource[i + 1];
|
|
|
|
|
pSource[i + 1] = temp;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-01-08 03:53:53 +00:00
|
|
|
|
|
2013-10-25 00:59:34 +00:00
|
|
|
|
// N64 format
|
|
|
|
|
else if (pSource[0] == 0x40)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < size; i += 4)
|
|
|
|
|
{
|
2014-01-08 03:53:53 +00:00
|
|
|
|
// output[i] = source[i + 3];
|
|
|
|
|
// output[i + 3] = source[i];
|
|
|
|
|
// output[i + 1] = source[i + 2];
|
|
|
|
|
// output[i + 2] = source[i + 1];
|
2013-10-25 00:59:34 +00:00
|
|
|
|
byte temp = pSource[i];
|
|
|
|
|
pSource[i] = source[i + 3];
|
|
|
|
|
pSource[i + 3] = temp;
|
|
|
|
|
|
|
|
|
|
temp = pSource[i + 1];
|
|
|
|
|
pSource[i + 1] = pSource[i + 2];
|
|
|
|
|
pSource[i + 2] = temp;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-01-08 03:53:53 +00:00
|
|
|
|
else // Z64 format (or some other unknown format)
|
2013-10-25 00:59:34 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return source;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CheckForPatchOptions()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (GameInfo["PatchBytes"])
|
|
|
|
|
{
|
2014-01-08 03:53:53 +00:00
|
|
|
|
var args = GameInfo.OptionValue("PatchBytes");
|
2013-10-25 00:59:34 +00:00
|
|
|
|
foreach (var val in args.Split(','))
|
|
|
|
|
{
|
|
|
|
|
var split = val.Split(':');
|
|
|
|
|
int offset = int.Parse(split[0], NumberStyles.HexNumber);
|
|
|
|
|
byte value = byte.Parse(split[1], NumberStyles.HexNumber);
|
|
|
|
|
RomData[offset] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-17 16:16:55 +00:00
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
// No need for errors in patching to propagate.
|
|
|
|
|
}
|
2013-10-25 00:59:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|