mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 11:14:25 +00:00
1067 lines
38 KiB
C#
1067 lines
38 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using Aaru.CommonTypes;
|
|
using Aaru.CommonTypes.AaruMetadata;
|
|
using Aaru.CommonTypes.Attributes;
|
|
using Aaru.CommonTypes.Enums;
|
|
using Aaru.CommonTypes.Interfaces;
|
|
using Aaru.CommonTypes.Structs;
|
|
using Aaru.Helpers;
|
|
using Aaru.Logging;
|
|
using Marshal = Aaru.Helpers.Marshal;
|
|
|
|
namespace Aaru.Images;
|
|
|
|
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
|
public partial class GameBoy : IByteAddressableImage
|
|
{
|
|
byte[] _data;
|
|
Stream _dataStream;
|
|
ImageInfo _imageInfo;
|
|
bool _opened;
|
|
|
|
#region IByteAddressableImage Members
|
|
|
|
/// <inheritdoc />
|
|
public string Author => Authors.NataliaPortillo;
|
|
|
|
/// <inheritdoc />
|
|
public Metadata AaruMetadata => null;
|
|
|
|
/// <inheritdoc />
|
|
public List<DumpHardware> DumpHardware => null;
|
|
|
|
/// <inheritdoc />
|
|
public string Format => "Nintendo Game Boy cartridge dump";
|
|
|
|
/// <inheritdoc />
|
|
public Guid Id => new("04AFDB93-587E-413B-9B52-10D4A92966CF");
|
|
|
|
/// <inheritdoc />
|
|
|
|
// ReSharper disable once ConvertToAutoProperty
|
|
public ImageInfo Info => _imageInfo;
|
|
|
|
/// <inheritdoc />
|
|
public string Name => Localization.GameBoy_Name;
|
|
|
|
/// <inheritdoc />
|
|
public bool Identify(IFilter imageFilter)
|
|
{
|
|
if(imageFilter == null) return false;
|
|
|
|
Stream stream = imageFilter.GetDataForkStream();
|
|
|
|
// Not sure but seems to be a multiple of at least this
|
|
if(stream.Length % 32768 != 0) return false;
|
|
|
|
stream.Position = 0x104;
|
|
var magicBytes = new byte[8];
|
|
stream.EnsureRead(magicBytes, 0, 8);
|
|
var magic = BitConverter.ToUInt64(magicBytes, 0);
|
|
|
|
return magic == 0x0B000DCC6666EDCE;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber Open(IFilter imageFilter)
|
|
{
|
|
if(imageFilter == null) return ErrorNumber.NoSuchFile;
|
|
|
|
Stream stream = imageFilter.GetDataForkStream();
|
|
|
|
// Not sure but seems to be a multiple of at least this, maybe more
|
|
if(stream.Length % 512 != 0) return ErrorNumber.InvalidArgument;
|
|
|
|
stream.Position = 0x104;
|
|
var magicBytes = new byte[8];
|
|
stream.EnsureRead(magicBytes, 0, 8);
|
|
var magic = BitConverter.ToUInt64(magicBytes, 0);
|
|
|
|
if(magic != 0x0B000DCC6666EDCE) return ErrorNumber.InvalidArgument;
|
|
|
|
_data = new byte[imageFilter.DataForkLength];
|
|
stream.Position = 0;
|
|
stream.EnsureRead(_data, 0, (int)imageFilter.DataForkLength);
|
|
|
|
_imageInfo = new ImageInfo
|
|
{
|
|
Application = "Multi Game Doctor 2",
|
|
CreationTime = imageFilter.CreationTime,
|
|
ImageSize = (ulong)imageFilter.DataForkLength,
|
|
MediaType = MediaType.GameBoyGamePak,
|
|
LastModificationTime = imageFilter.LastWriteTime,
|
|
Sectors = (ulong)imageFilter.DataForkLength,
|
|
MetadataMediaType = MetadataMediaType.LinearMedia
|
|
};
|
|
|
|
Header header = Marshal.ByteArrayToStructureBigEndian<Header>(_data, 0x100, Marshal.SizeOf<Header>());
|
|
|
|
var name = new byte[(header.Name[^1] & 0x80) == 0x80 ? 15 : 16];
|
|
Array.Copy(header.Name, 0, name, 0, name.Length);
|
|
|
|
_imageInfo.MediaTitle = StringHandlers.CToString(name);
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
sb.AppendFormat(Localization.Name_0, _imageInfo.MediaTitle).AppendLine();
|
|
|
|
if((header.Name[^1] & 0xC0) == 0xC0)
|
|
sb.AppendLine(Localization.Requires_Game_Boy_Color);
|
|
else
|
|
{
|
|
if((header.Name[^1] & 0xC0) == 0xC0) sb.AppendLine(Localization.Contains_features_for_Game_Boy_Color);
|
|
|
|
if(header.Sgb == 0x03) sb.AppendLine(Localization.Contains_features_for_Super_Game_Boy);
|
|
}
|
|
|
|
sb.AppendFormat(Localization.Region_0, header.Country == 0 ? Localization.Japan : Localization.World)
|
|
.AppendLine();
|
|
|
|
sb.AppendFormat(Localization.Cartridge_type_0, DecodeCartridgeType(header.RomType)).AppendLine();
|
|
sb.AppendFormat(Localization.ROM_size_0_bytes, DecodeRomSize(header.RomSize)).AppendLine();
|
|
|
|
if(header.SramSize > 0)
|
|
sb.AppendFormat(Localization.Save_RAM_size_0_bytes, DecodeSaveRamSize(header.SramSize)).AppendLine();
|
|
|
|
sb.AppendFormat(Localization.Licensee_0, DecodeLicensee(header.Licensee, header.LicenseeNew)).AppendLine();
|
|
sb.AppendFormat(Localization.Revision_0, header.Revision).AppendLine();
|
|
sb.AppendFormat(Localization.Header_checksum_0, header.HeaderChecksum).AppendLine();
|
|
sb.AppendFormat(Localization.Checksum_0_X4, header.Checksum).AppendLine();
|
|
|
|
_imageInfo.Comments = sb.ToString();
|
|
_opened = true;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string ErrorMessage { get; private set; }
|
|
|
|
/// <inheritdoc />
|
|
public bool IsWriting { get; private set; }
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<string> KnownExtensions => [".gb", ".gbc", ".sgb"];
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<MediaTagType> SupportedMediaTags => [];
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<MediaType> SupportedMediaTypes => [MediaType.GameBoyGamePak];
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<(string name, Type type, string description, object @default)> SupportedOptions => [];
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<SectorTagType> SupportedSectorTags => [];
|
|
|
|
/// <inheritdoc />
|
|
public bool Create(string path, MediaType mediaType, Dictionary<string, string> options, ulong sectors,
|
|
uint negativeSectors, uint overflowSectors, uint sectorSize) =>
|
|
Create(path, mediaType, options, (long)sectors) == ErrorNumber.NoError;
|
|
|
|
/// <inheritdoc />
|
|
public bool Close()
|
|
{
|
|
if(!_opened)
|
|
{
|
|
ErrorMessage = Localization.No_image_has_been_opened;
|
|
|
|
return false;
|
|
}
|
|
|
|
if(!IsWriting)
|
|
{
|
|
ErrorMessage = Localization.Image_is_not_opened_for_writing;
|
|
|
|
return false;
|
|
}
|
|
|
|
_dataStream.Position = 0;
|
|
_dataStream.Write(_data, 0, _data.Length);
|
|
_dataStream.Close();
|
|
|
|
IsWriting = false;
|
|
_opened = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool SetMetadata(Metadata metadata) => false;
|
|
|
|
/// <inheritdoc />
|
|
public bool SetDumpHardware(List<DumpHardware> dumpHardware) => false;
|
|
|
|
/// <inheritdoc />
|
|
public bool SetImageInfo(ImageInfo imageInfo) => true;
|
|
|
|
/// <inheritdoc />
|
|
public long Position { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber Create(string path, MediaType mediaType, Dictionary<string, string> options, long maximumSize)
|
|
{
|
|
if(_opened)
|
|
{
|
|
ErrorMessage = Localization.Cannot_create_an_opened_image;
|
|
|
|
return ErrorNumber.InvalidArgument;
|
|
}
|
|
|
|
if(mediaType != MediaType.GameBoyGamePak)
|
|
{
|
|
ErrorMessage = string.Format(Localization.Unsupported_media_format_0, mediaType);
|
|
|
|
return ErrorNumber.NotSupported;
|
|
}
|
|
|
|
_imageInfo = new ImageInfo
|
|
{
|
|
MediaType = mediaType,
|
|
Sectors = (ulong)maximumSize
|
|
};
|
|
|
|
try
|
|
{
|
|
_dataStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
|
}
|
|
catch(IOException ex)
|
|
{
|
|
ErrorMessage = string.Format(Localization.Could_not_create_new_image_file_exception_0, ex.Message);
|
|
AaruLogging.Exception(ex, Localization.Could_not_create_new_image_file_exception_0, ex.Message);
|
|
|
|
return ErrorNumber.InOutError;
|
|
}
|
|
|
|
_imageInfo.MediaType = mediaType;
|
|
IsWriting = true;
|
|
_opened = true;
|
|
_data = new byte[maximumSize];
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber GetMappings(out LinearMemoryMap mappings)
|
|
{
|
|
mappings = new LinearMemoryMap();
|
|
|
|
if(!_opened)
|
|
{
|
|
ErrorMessage = Localization.No_image_has_been_opened;
|
|
|
|
return ErrorNumber.NotOpened;
|
|
}
|
|
|
|
Header header = Marshal.ByteArrayToStructureBigEndian<Header>(_data, 0x100, Marshal.SizeOf<Header>());
|
|
|
|
var hasMapper = false;
|
|
var hasSaveRam = false;
|
|
string mapperManufacturer = null;
|
|
string mapperName = null;
|
|
|
|
switch(header.RomType)
|
|
{
|
|
case 0x01:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC1";
|
|
|
|
break;
|
|
case 0x02:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC1";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x03:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC1";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x05:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC2";
|
|
|
|
break;
|
|
case 0x06:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC2";
|
|
|
|
break;
|
|
case 0x08:
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x09:
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x0B:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MMM01";
|
|
|
|
break;
|
|
case 0x0C:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MMM01";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x0D:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MMM01";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x0F:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC3";
|
|
|
|
break;
|
|
case 0x10:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC3";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x11:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC3";
|
|
|
|
break;
|
|
case 0x12:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC3";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x13:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC3";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x19:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC5";
|
|
|
|
break;
|
|
case 0x1A:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC5";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x1B:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC5";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x1C:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC5";
|
|
|
|
break;
|
|
case 0x1D:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC5";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x1E:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC5";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0x20:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC6";
|
|
|
|
break;
|
|
case 0x22:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Nintendo";
|
|
mapperName = "MBC7";
|
|
hasSaveRam = true;
|
|
|
|
break;
|
|
case 0xFC: // Pocket Camera
|
|
mappings = new LinearMemoryMap
|
|
{
|
|
Devices =
|
|
[
|
|
new LinearMemoryDevice
|
|
{
|
|
Location = "U1",
|
|
Manufacturer = "Nintendo",
|
|
Model = "MAC-GBD",
|
|
Package = "LQFP-100",
|
|
Type = LinearMemoryType.Processor
|
|
},
|
|
new LinearMemoryDevice
|
|
{
|
|
Location = "U2",
|
|
Manufacturer = "Nintendo",
|
|
Model = "GBD-PCAX-0",
|
|
Package = "TSOP-32",
|
|
Type = LinearMemoryType.ROM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Start = 0,
|
|
Length = DecodeRomSize(header.RomSize)
|
|
}
|
|
},
|
|
new LinearMemoryDevice
|
|
{
|
|
Location = "U3",
|
|
Manufacturer = "Sharp",
|
|
Model = "52CV1000SF85LL",
|
|
Package = "TSOP-32",
|
|
Type = LinearMemoryType.SaveRAM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Start = DecodeRomSize(header.RomSize),
|
|
Length = DecodeSaveRamSize(header.SramSize)
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
return ErrorNumber.NoError;
|
|
case 0xFD:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Bandai";
|
|
mapperName = "TAMA5";
|
|
|
|
break;
|
|
case 0xFE:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Hudson";
|
|
mapperName = "HuC-3";
|
|
|
|
break;
|
|
case 0xFF:
|
|
hasMapper = true;
|
|
mapperManufacturer = "Hudson";
|
|
mapperName = "HuC-1";
|
|
|
|
break;
|
|
}
|
|
|
|
mappings = new LinearMemoryMap();
|
|
|
|
if(header.SramSize > 0) hasSaveRam = true;
|
|
|
|
var devices = 1;
|
|
|
|
if(hasSaveRam) devices++;
|
|
|
|
if(hasMapper) devices++;
|
|
|
|
mappings.Devices = new LinearMemoryDevice[devices];
|
|
|
|
mappings.Devices[0].Type = LinearMemoryType.ROM;
|
|
|
|
mappings.Devices[0].PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Start = 0,
|
|
Length = DecodeRomSize(header.RomSize)
|
|
};
|
|
|
|
if(hasSaveRam)
|
|
{
|
|
mappings.Devices[1] = new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.SaveRAM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Start = mappings.Devices[0].PhysicalAddress.Length,
|
|
Length = DecodeSaveRamSize(header.SramSize)
|
|
}
|
|
};
|
|
}
|
|
|
|
if(hasMapper)
|
|
{
|
|
mappings.Devices[^1] = new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.Mapper,
|
|
Manufacturer = mapperManufacturer,
|
|
Model = mapperName
|
|
};
|
|
}
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadByte(out byte b, bool advance = true) => ReadByteAt(Position, out b, advance);
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadByteAt(long position, out byte b, bool advance = true)
|
|
{
|
|
b = 0;
|
|
|
|
if(!_opened)
|
|
{
|
|
ErrorMessage = Localization.No_image_has_been_opened;
|
|
|
|
return ErrorNumber.NotOpened;
|
|
}
|
|
|
|
if(position >= _data.Length)
|
|
{
|
|
ErrorMessage = Localization.The_requested_position_is_out_of_range;
|
|
|
|
return ErrorNumber.OutOfRange;
|
|
}
|
|
|
|
b = _data[position];
|
|
|
|
if(advance) Position = position + 1;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadBytes(byte[] buffer, int offset, int bytesToRead, out int bytesRead, bool advance = true) =>
|
|
ReadBytesAt(Position, buffer, offset, bytesToRead, out bytesRead, advance);
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber ReadBytesAt(long position, byte[] buffer, int offset, int bytesToRead, out int bytesRead,
|
|
bool advance = true)
|
|
{
|
|
bytesRead = 0;
|
|
|
|
if(!_opened)
|
|
{
|
|
ErrorMessage = Localization.No_image_has_been_opened;
|
|
|
|
return ErrorNumber.NotOpened;
|
|
}
|
|
|
|
if(position >= _data.Length)
|
|
{
|
|
ErrorMessage = Localization.The_requested_position_is_out_of_range;
|
|
|
|
return ErrorNumber.OutOfRange;
|
|
}
|
|
|
|
if(buffer is null)
|
|
{
|
|
ErrorMessage = Localization.Buffer_must_not_be_null;
|
|
|
|
return ErrorNumber.InvalidArgument;
|
|
}
|
|
|
|
if(offset + bytesToRead > buffer.Length) bytesRead = buffer.Length - offset;
|
|
|
|
if(position + bytesToRead > _data.Length) bytesToRead = (int)(_data.Length - position);
|
|
|
|
Array.Copy(_data, position, buffer, offset, bytesToRead);
|
|
|
|
if(advance) Position = position + bytesToRead;
|
|
|
|
bytesRead = bytesToRead;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber SetMappings(LinearMemoryMap mappings)
|
|
{
|
|
if(!_opened)
|
|
{
|
|
ErrorMessage = Localization.No_image_has_been_opened;
|
|
|
|
return ErrorNumber.NotOpened;
|
|
}
|
|
|
|
if(!IsWriting)
|
|
{
|
|
ErrorMessage = Localization.Image_is_not_opened_for_writing;
|
|
|
|
return ErrorNumber.ReadOnly;
|
|
}
|
|
|
|
var foundRom = false;
|
|
var foundSaveRam = false;
|
|
var foundMapper = false;
|
|
|
|
// Sanitize
|
|
foreach(LinearMemoryDevice map in mappings.Devices)
|
|
{
|
|
switch(map.Type)
|
|
{
|
|
case LinearMemoryType.ROM when !foundRom:
|
|
foundRom = true;
|
|
|
|
break;
|
|
case LinearMemoryType.SaveRAM when !foundSaveRam:
|
|
foundSaveRam = true;
|
|
|
|
break;
|
|
case LinearMemoryType.Mapper when !foundMapper:
|
|
foundMapper = true;
|
|
|
|
break;
|
|
default:
|
|
return ErrorNumber.InvalidArgument;
|
|
}
|
|
}
|
|
|
|
// Cannot save in this image format anyway
|
|
return foundRom ? ErrorNumber.NoError : ErrorNumber.InvalidArgument;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber WriteByte(byte b, bool advance = true) => WriteByteAt(Position, b, advance);
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber WriteByteAt(long position, byte b, bool advance = true)
|
|
{
|
|
if(!_opened)
|
|
{
|
|
ErrorMessage = Localization.No_image_has_been_opened;
|
|
|
|
return ErrorNumber.NotOpened;
|
|
}
|
|
|
|
if(!IsWriting)
|
|
{
|
|
ErrorMessage = Localization.Image_is_not_opened_for_writing;
|
|
|
|
return ErrorNumber.ReadOnly;
|
|
}
|
|
|
|
if(position >= _data.Length)
|
|
{
|
|
ErrorMessage = Localization.The_requested_position_is_out_of_range;
|
|
|
|
return ErrorNumber.OutOfRange;
|
|
}
|
|
|
|
_data[position] = b;
|
|
|
|
if(advance) Position = position + 1;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber WriteBytes(byte[] buffer, int offset, int bytesToWrite, out int bytesWritten,
|
|
bool advance = true) =>
|
|
WriteBytesAt(Position, buffer, offset, bytesToWrite, out bytesWritten, advance);
|
|
|
|
/// <inheritdoc />
|
|
public ErrorNumber WriteBytesAt(long position, byte[] buffer, int offset, int bytesToWrite, out int bytesWritten,
|
|
bool advance = true)
|
|
{
|
|
bytesWritten = 0;
|
|
|
|
if(!_opened)
|
|
{
|
|
ErrorMessage = Localization.No_image_has_been_opened;
|
|
|
|
return ErrorNumber.NotOpened;
|
|
}
|
|
|
|
if(!IsWriting)
|
|
{
|
|
ErrorMessage = Localization.Image_is_not_opened_for_writing;
|
|
|
|
return ErrorNumber.ReadOnly;
|
|
}
|
|
|
|
if(position >= _data.Length)
|
|
{
|
|
ErrorMessage = Localization.The_requested_position_is_out_of_range;
|
|
|
|
return ErrorNumber.OutOfRange;
|
|
}
|
|
|
|
if(buffer is null)
|
|
{
|
|
ErrorMessage = Localization.Buffer_must_not_be_null;
|
|
|
|
return ErrorNumber.InvalidArgument;
|
|
}
|
|
|
|
if(offset + bytesToWrite > buffer.Length) bytesToWrite = buffer.Length - offset;
|
|
|
|
if(position + bytesToWrite > _data.Length) bytesToWrite = (int)(_data.Length - position);
|
|
|
|
Array.Copy(buffer, offset, _data, position, bytesToWrite);
|
|
|
|
if(advance) Position = position + bytesToWrite;
|
|
|
|
bytesWritten = bytesToWrite;
|
|
|
|
return ErrorNumber.NoError;
|
|
}
|
|
|
|
#endregion
|
|
|
|
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
|
static string DecodeLicensee(byte headerLicensee, byte[] headerLicenseeNew)
|
|
{
|
|
if(headerLicensee != 0x33)
|
|
{
|
|
return headerLicensee switch
|
|
{
|
|
0x00 => Localization.none_licensee,
|
|
0x01 => "nintendo",
|
|
0x08 => "capcom",
|
|
0x09 => "hot-b",
|
|
0x0A => "jaleco",
|
|
0x0B => "coconuts",
|
|
0x0C => "elite systems",
|
|
0x13 => "electronic arts",
|
|
0x18 => "hudsonsoft",
|
|
0x19 => "itc entertainment",
|
|
0x1A => "yanoman",
|
|
0x1D => "clary",
|
|
0x1F => "virgin",
|
|
0x20 => "KSS",
|
|
0x24 => "pcm complete",
|
|
0x25 => "san-x",
|
|
0x28 => "kotobuki systems",
|
|
0x29 => "seta",
|
|
0x30 => "infogrames",
|
|
0x31 => "nintendo",
|
|
0x32 => "bandai",
|
|
0x33 => Localization.GBC_see_above,
|
|
0x34 => "konami",
|
|
0x35 => "hector",
|
|
0x38 => "Capcom",
|
|
0x39 => "Banpresto",
|
|
0x3C => "*entertainment i",
|
|
0x3E => "gremlin",
|
|
0x41 => "Ubisoft",
|
|
0x42 => "Atlus",
|
|
0x44 => "Malibu",
|
|
0x46 => "angel",
|
|
0x47 => "spectrum holoby",
|
|
0x49 => "irem",
|
|
0x4A => "virgin",
|
|
0x4D => "malibu",
|
|
0x4F => "u.s. gold",
|
|
0x50 => "absolute",
|
|
0x51 => "acclaim",
|
|
0x52 => "activision",
|
|
0x53 => "american sammy",
|
|
0x54 => "gametek",
|
|
0x55 => "park place",
|
|
0x56 => "ljn",
|
|
0x57 => "matchbox",
|
|
0x59 => "milton bradley",
|
|
0x5A => "mindscape",
|
|
0x5B => "romstar",
|
|
0x5C => "naxat soft",
|
|
0x5D => "tradewest",
|
|
0x60 => "titus",
|
|
0x61 => "virgin",
|
|
0x67 => "ocean",
|
|
0x69 => "electronic arts",
|
|
0x6E => "elite systems",
|
|
0x6F => "electro brain",
|
|
0x70 => "Infogrammes",
|
|
0x71 => "Interplay",
|
|
0x72 => "broderbund",
|
|
0x73 => "sculptered soft",
|
|
0x75 => "the sales curve",
|
|
0x78 => "t*hq",
|
|
0x79 => "accolade",
|
|
0x7A => "triffix entertainment",
|
|
0x7C => "microprose",
|
|
0x7F => "kemco",
|
|
0x80 => "misawa entertainment",
|
|
0x83 => "lozc",
|
|
0x86 => "tokuma shoten intermedia",
|
|
0x8B => "bullet-proof software",
|
|
0x8C => "vic tokai",
|
|
0x8E => "ape",
|
|
0x8F => "i'max",
|
|
0x91 => "chun soft",
|
|
0x92 => "video system",
|
|
0x93 => "tsuburava",
|
|
0x95 => "varie",
|
|
0x96 => "yonezawa/s'pal",
|
|
0x97 => "kaneko",
|
|
0x99 => "arc",
|
|
0x9A => "nihon bussan",
|
|
0x9B => "Tecmo",
|
|
0x9C => "imagineer",
|
|
0x9D => "Banpresto",
|
|
0x9F => "nova",
|
|
0xA1 => "Hori electric",
|
|
0xA2 => "Bandai",
|
|
0xA4 => "Konami",
|
|
0xA6 => "kawada",
|
|
0xA7 => "takara",
|
|
0xA9 => "technos japan",
|
|
0xAA => "broderbund",
|
|
0xAC => "Toei animation",
|
|
0xAD => "toho",
|
|
0xAF => "Namco",
|
|
0xB0 => "Acclaim",
|
|
0xB1 => "ascii or nexoft",
|
|
0xB2 => "Bandai",
|
|
0xB4 => "Enix",
|
|
0xB6 => "HAL",
|
|
0xB7 => "SNK",
|
|
0xB9 => "pony canyon",
|
|
0xBA => "*culture brain o",
|
|
0xBB => "Sunsoft",
|
|
0xBD => "Sony imagesoft",
|
|
0xBF => "sammy",
|
|
0xC0 => "Taito",
|
|
0xC2 => "Kemco",
|
|
0xC3 => "Squaresoft",
|
|
0xC4 => "tokuma shoten intermedia",
|
|
0xC5 => "data east",
|
|
0xC6 => "tonkin house",
|
|
0xC8 => "koei",
|
|
0xC9 => "ufl",
|
|
0xCA => "ultra",
|
|
0xCB => "vap",
|
|
0xCC => "use",
|
|
0xCD => "meldac",
|
|
0xCE => "*pony canyon or",
|
|
0xCF => "angel",
|
|
0xD0 => "Taito",
|
|
0xD1 => "sofel",
|
|
0xD2 => "quest",
|
|
0xD3 => "sigma enterprises",
|
|
0xD4 => "ask kodansha",
|
|
0xD6 => "naxat soft",
|
|
0xD7 => "copya systems",
|
|
0xD9 => "Banpresto",
|
|
0xDA => "tomy",
|
|
0xDB => "ljn",
|
|
0xDD => "ncs",
|
|
0xDE => "human",
|
|
0xDF => "altron",
|
|
0xE0 => "jaleco",
|
|
0xE1 => "towachiki",
|
|
0xE2 => "uutaka",
|
|
0xE3 => "varie",
|
|
0xE5 => "epoch",
|
|
0xE7 => "athena",
|
|
0xE8 => "asmik",
|
|
0xE9 => "natsume",
|
|
0xEA => "king records",
|
|
0xEB => "atlus",
|
|
0xEC => "Epic/Sony records",
|
|
0xEE => "igs",
|
|
0xF0 => "a wave",
|
|
0xF3 => "extreme entertainment",
|
|
0xFF => "ljn",
|
|
_ => Localization.Unknown_licensee
|
|
};
|
|
}
|
|
|
|
string licenseeNew = StringHandlers.CToString(headerLicenseeNew);
|
|
|
|
return licenseeNew switch
|
|
{
|
|
"00" => Localization.none_licensee,
|
|
"01" => "Nintendo R&D1",
|
|
"08" => "Capcom",
|
|
"13" => "Electronic Arts",
|
|
"18" => "Hudson Soft",
|
|
"19" => "b-ai",
|
|
"20" => "kss",
|
|
"22" => "pow",
|
|
"24" => "PCM Complete",
|
|
"25" => "san-x",
|
|
"28" => "Kemco Japan",
|
|
"29" => "seta",
|
|
"30" => "Viacom",
|
|
"31" => "Nintendo",
|
|
"32" => "Bandai",
|
|
"33" => "Ocean / Acclaim",
|
|
"34" => "Konami",
|
|
"35" => "Hector",
|
|
"37" => "Taito",
|
|
"38" => "Hudson",
|
|
"39" => "Banpresto",
|
|
"41" => "Ubi Soft",
|
|
"42" => "Atlus",
|
|
"44" => "Malibu",
|
|
"46" => "angel",
|
|
"47" => "Bullet -Proof",
|
|
"49" => "irem",
|
|
"50" => "Absolute",
|
|
"51" => "Acclaim",
|
|
"52" => "Activision",
|
|
"53" => "American sammy",
|
|
"54" => "Konami",
|
|
"55" => "Hi tech entertainment",
|
|
"56" => "LJN",
|
|
"57" => "Matchbox",
|
|
"58" => "Mattel",
|
|
"59" => "Milton Bradley",
|
|
"60" => "Titus",
|
|
"61" => "Virgin",
|
|
"64" => "LucasArts",
|
|
"67" => "Ocean",
|
|
"69" => "Electronic Arts",
|
|
"70" => "Infogrames",
|
|
"71" => "Interplay",
|
|
"72" => "Brøderbund",
|
|
"73" => "sculptured",
|
|
"75" => "sci",
|
|
"78" => "THQ",
|
|
"79" => "Accolade",
|
|
"80" => "misawa",
|
|
"83" => "lozc",
|
|
"86" => "tokuma shoten i",
|
|
"87" => "tsukuda ori",
|
|
"91" => "Chunsoft",
|
|
"92" => "Video system",
|
|
"93" => "Ocean / Acclaim",
|
|
"95" => "Varie",
|
|
"96" => "Yonezawa / s'pal",
|
|
"97" => "Kaneko",
|
|
"99" => "Pack in soft",
|
|
"A4" => "Konami",
|
|
_ => Localization.Unknown_licensee
|
|
};
|
|
}
|
|
|
|
static uint DecodeRomSize(byte headerRomType) => headerRomType switch
|
|
{
|
|
0 => 32768,
|
|
1 => 65536,
|
|
2 => 131072,
|
|
3 => 262144,
|
|
4 => 524288,
|
|
5 => 1048576,
|
|
6 => 2097152,
|
|
7 => 4194304,
|
|
8 => 8388608,
|
|
0x52 => 1179648,
|
|
0x53 => 1310720,
|
|
0x54 => 1572864,
|
|
_ => 0
|
|
};
|
|
|
|
static uint DecodeSaveRamSize(byte headerRamType) => headerRamType switch
|
|
{
|
|
0 => 0,
|
|
1 => 2048,
|
|
2 => 8192,
|
|
3 => 32768,
|
|
4 => 131072,
|
|
5 => 65536,
|
|
_ => 0
|
|
};
|
|
|
|
static string DecodeCartridgeType(byte headerRomType) => headerRomType switch
|
|
{
|
|
0x00 => Localization.ROM_only,
|
|
0x01 => Localization.ROM_and_MBC1,
|
|
0x02 => Localization.ROM_MBC1_and_RAM,
|
|
0x03 => Localization.ROM_MBC1_RAM_and_battery,
|
|
0x05 => Localization.ROM_and_MBC2,
|
|
0x06 => Localization.ROM_MBC2_and_battery,
|
|
0x08 => Localization.ROM_and_RAM,
|
|
0x09 => Localization.ROM_RAM_and_battery,
|
|
0x0B => Localization.ROM_and_MMM01,
|
|
0x0C => Localization.ROM_MMM01_and_RAM,
|
|
0x0D => Localization.ROM_MMM01_RAM_and_battery,
|
|
0x0F => Localization.ROM_MBC3_timer_and_battery,
|
|
0x10 => Localization.ROM_MBC3_RAM_timer_and_battery,
|
|
0x11 => Localization.ROM_and_MBC3,
|
|
0x12 => Localization.ROM_MBC3_and_RAM,
|
|
0x13 => Localization.ROM_MBC3_RAM_and_battery,
|
|
0x19 => Localization.ROM_and_MBC5,
|
|
0x1A => Localization.ROM_MBC5_and_RAM,
|
|
0x1B => Localization.ROM_MBC5_RAM_and_battery,
|
|
0x1C => Localization.ROM_MBC5_and_vibration_motor,
|
|
0x1D => Localization.ROM_MBC5_RAM_and_vibration_motor,
|
|
0x1E => Localization
|
|
.ROM_MBC5_RAM_battery_and_vibration_motor,
|
|
0x20 => Localization.ROM_and_MBC6,
|
|
0x22 => Localization
|
|
.ROM_MBC7_RAM_battery_light_sensor_and_vibration_motor,
|
|
0xFC => Localization.Pocket_Camera,
|
|
0xFD => Localization.ROM_and_TAMA5,
|
|
0xFE => Localization.ROM_and_HuC_3,
|
|
0xFF => Localization.ROM_and_HuC_1,
|
|
_ => Localization.Unknown_cartridge_type
|
|
};
|
|
|
|
#region Nested type: Header
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
[SwapEndian]
|
|
partial struct Header
|
|
{
|
|
/// <summary>Usually 0x00 (NOP)</summary>
|
|
public byte Opcode1;
|
|
/// <summary>Usually 0xC3 (JP)</summary>
|
|
public byte Opcode2;
|
|
/// <summary>Jump destination</summary>
|
|
public ushort Start;
|
|
/// <summary>Boot logo, checked by boot ROM</summary>
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
|
|
public byte[] Logo;
|
|
/// <summary>Game name, ASCIIZ, if last byte has 7-bit set, that byte is Game Boy Color type</summary>
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
|
public byte[] Name;
|
|
/// <summary>New licensee code</summary>
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
|
public byte[] LicenseeNew;
|
|
/// <summary>Super Game Boy Flag</summary>
|
|
public byte Sgb;
|
|
/// <summary>ROM type</summary>
|
|
public byte RomType;
|
|
/// <summary>ROM size</summary>
|
|
public byte RomSize;
|
|
/// <summary>SRAM size</summary>
|
|
public byte SramSize;
|
|
/// <summary>Country code</summary>
|
|
public byte Country;
|
|
/// <summary>Licensee code</summary>
|
|
public byte Licensee;
|
|
/// <summary>Game revision</summary>
|
|
public byte Revision;
|
|
/// <summary>Header checksum</summary>
|
|
public byte HeaderChecksum;
|
|
/// <summary>Cartridge checksum</summary>
|
|
public ushort Checksum;
|
|
}
|
|
|
|
#endregion
|
|
} |