BizHawk/BizHawk.Client.Common/RomGame.cs

217 lines
5.9 KiB
C#
Raw Normal View History

2013-10-25 00:59:34 +00:00
using System;
using System.Globalization;
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Common;
2013-10-25 00:59:34 +00:00
namespace BizHawk.Client.Common
2013-10-25 00:59:34 +00:00
{
public class RomGame
{
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;
public RomGame()
{
}
2013-10-25 00:59:34 +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;
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);
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
{
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;
}
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
// .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;
}
}
}
catch (Exception)
{
// No need for errors in patching to propagate.
}
2013-10-25 00:59:34 +00:00
}
}
}