mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 11:14:25 +00:00
873 lines
27 KiB
C#
873 lines
27 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Aaru.Checksums;
|
|
using Aaru.CommonTypes;
|
|
using Aaru.CommonTypes.AaruMetadata;
|
|
using Aaru.CommonTypes.Enums;
|
|
using Aaru.CommonTypes.Interfaces;
|
|
using Aaru.CommonTypes.Structs;
|
|
using Aaru.Database;
|
|
using Aaru.Database.Models;
|
|
using Aaru.Helpers;
|
|
using Aaru.Logging;
|
|
|
|
namespace Aaru.Images;
|
|
|
|
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
|
public class Nes : IByteAddressableImage
|
|
{
|
|
int _chrLen;
|
|
int _chrNvramLen;
|
|
int _chrRamLen;
|
|
byte[] _data;
|
|
Stream _dataStream;
|
|
ImageInfo _imageInfo;
|
|
int _instRomLen;
|
|
ushort _mapper;
|
|
bool _nes20;
|
|
NesHeaderInfo _nesHeaderInfo;
|
|
bool _opened;
|
|
int _prgLen;
|
|
int _prgNvramLen;
|
|
int _prgRamLen;
|
|
int _promLen;
|
|
byte _submapper;
|
|
bool _trainer;
|
|
|
|
#region IByteAddressableImage Members
|
|
|
|
/// <inheritdoc />
|
|
public string Author => Authors.NataliaPortillo;
|
|
|
|
/// <inheritdoc />
|
|
public Metadata AaruMetadata => null;
|
|
|
|
/// <inheritdoc />
|
|
public List<DumpHardware> DumpHardware => null;
|
|
|
|
/// <inheritdoc />
|
|
public string Format => _nes20 ? "NES 2.0" : "iNES";
|
|
|
|
/// <inheritdoc />
|
|
public Guid Id => new("D597A3F4-2B1C-441C-8487-0BCABC509302");
|
|
|
|
/// <inheritdoc />
|
|
|
|
// ReSharper disable once ConvertToAutoProperty
|
|
public ImageInfo Info => _imageInfo;
|
|
|
|
/// <inheritdoc />
|
|
public string Name => Localization.Nes_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 < 16) return false;
|
|
|
|
stream.Position = 0;
|
|
var magicBytes = new byte[4];
|
|
stream.EnsureRead(magicBytes, 0, 8);
|
|
var magic = BitConverter.ToUInt32(magicBytes, 0);
|
|
|
|
return magic == 0x1A53454E;
|
|
}
|
|
|
|
/// <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 < 16) return ErrorNumber.InvalidArgument;
|
|
|
|
stream.Position = 0;
|
|
var header = new byte[16];
|
|
stream.EnsureRead(header, 0, 8);
|
|
var magic = BitConverter.ToUInt32(header, 0);
|
|
|
|
if(magic != 0x1A53454E) return ErrorNumber.InvalidArgument;
|
|
|
|
if((header[7] & 0x0C) == 0x08) _nes20 = true;
|
|
|
|
_prgLen = header[4] * 16384;
|
|
_chrLen = header[5] * 8192;
|
|
_trainer = (header[6] & 0x4) != 0;
|
|
_instRomLen = 0;
|
|
_promLen = 0;
|
|
int trainerLen = _trainer ? 512 : 0;
|
|
|
|
_nesHeaderInfo = new NesHeaderInfo
|
|
{
|
|
NametableMirroring = (header[6] & 0x1) != 0,
|
|
BatteryPresent = (header[6] & 0x2) != 0,
|
|
FourScreenMode = (header[6] & 0x8) != 0,
|
|
Mapper = (ushort)(header[6] >> 4 | header[7] & 0xF0)
|
|
};
|
|
|
|
if((header[7] & 0x1) != 0)
|
|
_nesHeaderInfo.ConsoleType = NesConsoleType.Vs;
|
|
else if((header[7] & 0x2) != 0)
|
|
_nesHeaderInfo.ConsoleType = NesConsoleType.Playchoice;
|
|
else
|
|
_nesHeaderInfo.ConsoleType = NesConsoleType.Nes;
|
|
|
|
if(_nes20)
|
|
{
|
|
_nesHeaderInfo.ConsoleType = (NesConsoleType)(header[7] & 0x3);
|
|
_nesHeaderInfo.Mapper += (ushort)((header[8] & 0xF) << 8);
|
|
_nesHeaderInfo.Submapper = (byte)(header[8] >> 4);
|
|
|
|
if((header[9] & 0xF) == 0xF)
|
|
_prgLen = (1 << (header[4] >> 2)) * (header[4] & 0x3);
|
|
else
|
|
_prgLen += (header[9] & 0xF) * 16384;
|
|
|
|
if(header[9] >> 4 == 0xF)
|
|
_chrLen = (1 << (header[5] >> 2)) * (header[5] & 0x3);
|
|
else
|
|
_chrLen += (header[9] >> 4) * 8192;
|
|
|
|
if((header[10] & 0xF) > 0) _prgRamLen = 64 << (header[10] & 0xF);
|
|
|
|
if((header[10] & 0xF0) > 0) _prgNvramLen = 64 << ((header[10] & 0xF0) >> 4);
|
|
|
|
if((header[11] & 0xF) > 0) _chrRamLen = 64 << (header[11] & 0xF);
|
|
|
|
if((header[11] & 0xF0) > 0) _chrNvramLen = 64 << ((header[11] & 0xF0) >> 4);
|
|
|
|
_nesHeaderInfo.TimingMode = (NesTimingMode)(header[12] & 0x3);
|
|
|
|
switch(_nesHeaderInfo.ConsoleType)
|
|
{
|
|
case NesConsoleType.Vs:
|
|
_nesHeaderInfo.VsPpuType = (NesVsPpuType)(header[13] & 0xF);
|
|
_nesHeaderInfo.VsHardwareType = (NesVsHardwareType)(header[13] >> 4);
|
|
|
|
break;
|
|
case NesConsoleType.Extended:
|
|
_nesHeaderInfo.ExtendedConsoleType = (NesExtendedConsoleType)(header[13] & 0xF);
|
|
|
|
break;
|
|
}
|
|
|
|
_nesHeaderInfo.DefaultExpansionDevice = (NesDefaultExpansionDevice)(header[15] & 0x3F);
|
|
}
|
|
|
|
_data = new byte[imageFilter.DataForkLength - 16];
|
|
stream.Position = 16;
|
|
stream.EnsureRead(_data, 0, _data.Length);
|
|
|
|
_imageInfo = new ImageInfo
|
|
{
|
|
Application = "iNES",
|
|
CreationTime = imageFilter.CreationTime,
|
|
ImageSize = (ulong)imageFilter.DataForkLength,
|
|
LastModificationTime = imageFilter.LastWriteTime,
|
|
Sectors = (ulong)imageFilter.DataForkLength,
|
|
MetadataMediaType = MetadataMediaType.LinearMedia,
|
|
MediaType = MediaType.FamicomGamePak
|
|
};
|
|
|
|
StringBuilder sb = new();
|
|
|
|
sb.AppendFormat(Localization.PRG_ROM_size_0_bytes, _prgLen).AppendLine();
|
|
sb.AppendFormat(Localization.CHR_ROM_size_0_bytes, _chrLen).AppendLine();
|
|
sb.AppendFormat(Localization.Trainer_size_0_bytes, trainerLen).AppendLine();
|
|
sb.AppendFormat(Localization.Mapper_0, _nesHeaderInfo.Mapper).AppendLine();
|
|
|
|
if(_nesHeaderInfo.BatteryPresent) sb.AppendLine(Localization.Has_battery_backed_RAM);
|
|
|
|
if(_nesHeaderInfo.FourScreenMode)
|
|
sb.AppendLine(Localization.Uses_four_screen_VRAM);
|
|
else if(_nesHeaderInfo.NametableMirroring)
|
|
sb.AppendLine(Localization.Uses_vertical_mirroring);
|
|
else
|
|
sb.AppendLine(Localization.Uses_horizontal_mirroring);
|
|
|
|
switch(_nesHeaderInfo.ConsoleType)
|
|
{
|
|
// TODO: Proper media types
|
|
case NesConsoleType.Vs:
|
|
sb.AppendLine(Localization.VS_Unisystem_game);
|
|
|
|
break;
|
|
case NesConsoleType.Playchoice:
|
|
sb.AppendLine(Localization.PlayChoice_10_game);
|
|
sb.AppendFormat(Localization.INST_ROM_size_0_bytes, _instRomLen & 0xF).AppendLine();
|
|
sb.AppendFormat(Localization.PROM_size_0_bytes, _promLen).AppendLine();
|
|
|
|
break;
|
|
|
|
case NesConsoleType.Nes:
|
|
break;
|
|
case NesConsoleType.Extended:
|
|
switch(_nesHeaderInfo.ExtendedConsoleType)
|
|
{
|
|
case NesExtendedConsoleType.Vs:
|
|
sb.AppendLine(Localization.VS_Unisystem_game);
|
|
|
|
break;
|
|
|
|
case NesExtendedConsoleType.Normal:
|
|
break;
|
|
case NesExtendedConsoleType.Playchoice:
|
|
sb.AppendLine(Localization.PlayChoice_10_game);
|
|
sb.AppendFormat(Localization.INST_ROM_size_0_bytes, _instRomLen & 0xF).AppendLine();
|
|
sb.AppendFormat(Localization.PROM_size_0_bytes, _promLen).AppendLine();
|
|
|
|
break;
|
|
case NesExtendedConsoleType.VT01_Monochrome:
|
|
case NesExtendedConsoleType.VT01:
|
|
sb.AppendLine(Localization.VR_Technology_VT01);
|
|
|
|
break;
|
|
case NesExtendedConsoleType.VT02:
|
|
sb.AppendLine(Localization.VR_Technology_VT02);
|
|
|
|
break;
|
|
case NesExtendedConsoleType.VT03:
|
|
sb.AppendLine(Localization.VR_Technology_VT03);
|
|
|
|
break;
|
|
case NesExtendedConsoleType.VT09:
|
|
sb.AppendLine(Localization.VR_Technology_VT09);
|
|
|
|
break;
|
|
case NesExtendedConsoleType.VT32:
|
|
sb.AppendLine(Localization.VR_Technology_VT32);
|
|
|
|
break;
|
|
case NesExtendedConsoleType.VT369:
|
|
sb.AppendLine(Localization.VR_Technology_VT369);
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
_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 => [".nes"];
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<MediaTagType> SupportedMediaTags => [];
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<MediaType> SupportedMediaTypes => [MediaType.NESGamePak, MediaType.FamicomGamePak];
|
|
|
|
/// <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;
|
|
}
|
|
|
|
var header = new byte[16];
|
|
|
|
if(_nesHeaderInfo is null)
|
|
{
|
|
string hash = Sha256Context.Data(_data, out _).ToLowerInvariant();
|
|
using var ctx = AaruContext.Create(Settings.Settings.MainDbPath);
|
|
_nesHeaderInfo = ctx.NesHeaders.FirstOrDefault(hdr => hdr.Sha256 == hash);
|
|
}
|
|
|
|
_nesHeaderInfo ??= new NesHeaderInfo
|
|
{
|
|
Mapper = _mapper,
|
|
ConsoleType = _instRomLen > 0 ? NesConsoleType.Playchoice : NesConsoleType.Nes,
|
|
Submapper = _submapper
|
|
};
|
|
|
|
header[0] = 0x4E;
|
|
header[1] = 0x45;
|
|
header[2] = 0x53;
|
|
header[3] = 0x1A;
|
|
header[4] = (byte)(_prgLen / 16384 & 0xFF);
|
|
header[5] = (byte)(_chrLen / 8192 & 0xFF);
|
|
header[6] = (byte)((_nesHeaderInfo.Mapper & 0xF) << 4);
|
|
|
|
if(_nesHeaderInfo.FourScreenMode) header[6] |= 0x8;
|
|
|
|
if(_trainer) header[6] |= 0x4;
|
|
|
|
if(_nesHeaderInfo.BatteryPresent) header[6] |= 0x2;
|
|
|
|
if(_nesHeaderInfo.NametableMirroring) header[6] |= 0x1;
|
|
|
|
header[7] = (byte)(_mapper & 0xF0 | 0x8);
|
|
header[7] |= (byte)_nesHeaderInfo.ConsoleType;
|
|
|
|
header[8] = (byte)(_nesHeaderInfo.Submapper << 4 | (_nesHeaderInfo.Mapper & 0xF00) >> 4);
|
|
header[9] = (byte)(_prgLen / 16384 >> 8);
|
|
header[9] |= (byte)(_chrLen / 8192 >> 4 & 0xF);
|
|
|
|
// TODO: PRG-RAM, PRG-NVRAM, CHR-RAM and CHR-NVRAM sizes
|
|
|
|
header[12] = (byte)_nesHeaderInfo.TimingMode;
|
|
|
|
header[13] = _nesHeaderInfo.ConsoleType switch
|
|
{
|
|
NesConsoleType.Vs => (byte)((int)_nesHeaderInfo.VsHardwareType << 4 |
|
|
(int)_nesHeaderInfo.VsPpuType),
|
|
NesConsoleType.Extended => (byte)_nesHeaderInfo.ExtendedConsoleType,
|
|
_ => header[13]
|
|
};
|
|
|
|
header[14] = 0;
|
|
|
|
if(_instRomLen > 0) header[14]++;
|
|
|
|
if(_promLen > 0) header[14]++;
|
|
|
|
if(_nesHeaderInfo.ExtendedConsoleType == NesExtendedConsoleType.VT369) header[14]++;
|
|
|
|
switch(_nesHeaderInfo.Mapper)
|
|
{
|
|
case 86 when _nesHeaderInfo.Submapper == 1:
|
|
case 355:
|
|
header[14]++;
|
|
|
|
break;
|
|
}
|
|
|
|
header[15] = (byte)_nesHeaderInfo.DefaultExpansionDevice;
|
|
|
|
_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.FamicomGamePak && mediaType != MediaType.NESGamePak)
|
|
{
|
|
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];
|
|
_nes20 = true;
|
|
|
|
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;
|
|
}
|
|
|
|
List<LinearMemoryDevice> devices =
|
|
[
|
|
new()
|
|
{
|
|
Type = LinearMemoryType.ROM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_prgLen
|
|
}
|
|
}
|
|
];
|
|
|
|
if(_chrLen > 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.CharacterROM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_chrLen
|
|
}
|
|
});
|
|
}
|
|
|
|
if(_trainer)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.Trainer
|
|
});
|
|
}
|
|
|
|
if(_instRomLen > 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.ROM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_instRomLen
|
|
}
|
|
});
|
|
}
|
|
|
|
if(_promLen > 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.ROM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_promLen
|
|
}
|
|
});
|
|
}
|
|
|
|
if(_prgRamLen > 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.WorkRAM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_prgRamLen
|
|
}
|
|
});
|
|
}
|
|
|
|
if(_chrRamLen > 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.CharacterRAM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_chrRamLen
|
|
}
|
|
});
|
|
}
|
|
|
|
if(_prgNvramLen > 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.SaveRAM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_prgNvramLen
|
|
}
|
|
});
|
|
}
|
|
|
|
if(_chrNvramLen > 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.CharacterRAM,
|
|
PhysicalAddress = new LinearMemoryAddressing
|
|
{
|
|
Length = (ulong)_chrNvramLen
|
|
}
|
|
});
|
|
}
|
|
|
|
ushort mapper = _nesHeaderInfo?.Mapper ?? _mapper;
|
|
byte submapper = _nesHeaderInfo?.Submapper ?? _submapper;
|
|
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.Mapper,
|
|
Description = $"NES Mapper {mapper}"
|
|
});
|
|
|
|
if(submapper != 0)
|
|
{
|
|
devices.Add(new LinearMemoryDevice
|
|
{
|
|
Type = LinearMemoryType.Mapper,
|
|
Description = $"NES Submapper {submapper}"
|
|
});
|
|
}
|
|
|
|
mappings = new LinearMemoryMap
|
|
{
|
|
Devices = devices.ToArray()
|
|
};
|
|
|
|
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 foundChrRom = false;
|
|
var foundInstRom = false;
|
|
var foundProm = false;
|
|
var foundRam = false;
|
|
var foundChrRam = false;
|
|
var foundNvram = false;
|
|
var foundChrNvram = false;
|
|
var foundMapper = false;
|
|
var foundSubMapper = false;
|
|
|
|
// Sanitize
|
|
foreach(LinearMemoryDevice map in mappings.Devices)
|
|
{
|
|
Regex regex;
|
|
Match match;
|
|
|
|
switch(map.Type)
|
|
{
|
|
case LinearMemoryType.ROM when !foundRom:
|
|
_prgLen = (int)map.PhysicalAddress.Length;
|
|
foundRom = true;
|
|
|
|
break;
|
|
case LinearMemoryType.CharacterROM when !foundChrRom:
|
|
_chrLen = (int)map.PhysicalAddress.Length;
|
|
foundChrRom = true;
|
|
|
|
break;
|
|
case LinearMemoryType.Trainer when !_trainer:
|
|
_trainer = true;
|
|
|
|
break;
|
|
case LinearMemoryType.ROM when !foundInstRom:
|
|
_instRomLen = (int)map.PhysicalAddress.Length;
|
|
foundInstRom = true;
|
|
|
|
break;
|
|
case LinearMemoryType.ROM when !foundProm:
|
|
_promLen = (int)map.PhysicalAddress.Length;
|
|
foundProm = true;
|
|
|
|
break;
|
|
case LinearMemoryType.WorkRAM when !foundRam:
|
|
_prgRamLen = (int)map.PhysicalAddress.Length;
|
|
foundRam = true;
|
|
|
|
break;
|
|
case LinearMemoryType.CharacterRAM when !foundChrRam:
|
|
_chrRamLen = (int)map.PhysicalAddress.Length;
|
|
foundChrRam = true;
|
|
|
|
break;
|
|
case LinearMemoryType.SaveRAM when !foundNvram:
|
|
_prgNvramLen = (int)map.PhysicalAddress.Length;
|
|
foundNvram = true;
|
|
|
|
break;
|
|
case LinearMemoryType.CharacterRAM when !foundChrNvram:
|
|
_chrNvramLen = (int)map.PhysicalAddress.Length;
|
|
foundChrNvram = true;
|
|
|
|
break;
|
|
case LinearMemoryType.Mapper when !foundMapper:
|
|
regex = new Regex(@"NES Mapper ?(<mapper>\d+)");
|
|
match = regex.Match(map.Description);
|
|
|
|
if(match.Success)
|
|
{
|
|
if(ushort.TryParse(match.Groups["mapper"].Value, out ushort mapper))
|
|
{
|
|
if(_nesHeaderInfo is null)
|
|
_mapper = mapper;
|
|
else
|
|
_nesHeaderInfo.Mapper = mapper;
|
|
|
|
foundMapper = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case LinearMemoryType.Mapper when !foundSubMapper:
|
|
regex = new Regex(@"NES Sub-Mapper ?(<mapper>\d+)");
|
|
match = regex.Match(map.Description);
|
|
|
|
if(match.Success)
|
|
{
|
|
if(byte.TryParse(match.Groups["mapper"].Value, out byte mapper))
|
|
{
|
|
if(_nesHeaderInfo is null)
|
|
_submapper = mapper;
|
|
else
|
|
_nesHeaderInfo.Submapper = mapper;
|
|
|
|
foundSubMapper = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
default:
|
|
return ErrorNumber.InvalidArgument;
|
|
}
|
|
}
|
|
|
|
return foundRom && foundMapper ? 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
|
|
} |