using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace com.clusterrr.Famicom.Containers
{
///
/// Disk info FDS block (block type 1)
///
[StructLayout(LayoutKind.Sequential, Size = 56, Pack = 1)]
public class FdsBlockDiskInfo : IFdsBlock, IEquatable
{
///
/// Disk side
///
public enum DiskSides
{
///
/// Side A
///
A = 0,
///
/// Side B
///
B = 1,
}
///
/// Disk type
///
public enum DiskTypes
{
///
/// Normal
///
FMS = 0,
///
/// With shutter
///
FSC = 1,
}
///
/// Country
///
public enum Country
{
///
/// Japan
///
Japan = 0x49,
}
///
/// Company name, source: https://www.nesdev.org/wiki/Licensee_codes
///
public enum Company
{
///
/// Nintendo
///
Nintendo = 0x01,
///
/// Nomura Securities? (unverified)
///
NomuraSecurities = 0x07,
///
/// Capcom
///
Capcom = 0x08,
///
/// Hot-B
///
HotB = 0x09,
///
/// Jaleco
///
Jaleco = 0x0A,
///
/// Coconuts Japan Entertainment
///
CoconutsJapanEntertainment = 0x0B,
///
/// Electronic Arts (Japan)
///
ElectronicArtsJap = 0x13,
///
/// Hudson Soft
///
HudsonSoft = 0x18,
///
/// Tokai Engineering
///
TokaiEngineering = 0x21,
///
/// Kemco (Japan)
///
KemcoJap = 0x28,
///
/// SETA (Japan)
///
SetaJap = 0x29,
///
/// Tamtex
///
Tamtex = 0x2B,
///
/// Hector Playing Interface (Hect)
///
HectorPlayingInterface = 0x35,
///
/// Loriciel
///
Loriciel = 0x3D,
///
/// Gremlin
///
Gremlin = 0x3E,
///
/// Seika Corporation
///
SeikaCorporation = 0x40,
///
/// Ubisoft
///
Ubisoft = 0x41,
///
/// System 3
///
System3 = 0x46,
///
/// Irem
///
Irem = 0x49,
///
/// Gakken
///
Gakken = 0x4A,
///
/// Absolute
///
Absolute = 0x50,
///
/// Acclaim (NA)
///
AcclaimNA = 0x51,
///
/// Activision
///
Activision = 0x52,
///
/// American Sammy
///
AmericanSammy = 0x53,
///
/// GameTek
///
Gametek = 0x54,
///
/// Hi Tech Expressions
///
HITechExpressions = 0x55,
///
/// LJN
///
Ljn = 0x56,
///
/// Matchbox Toys
///
MatchboxToys = 0x57,
///
/// Mattel
///
Mattel = 0x58,
///
/// Milton Bradley
///
MiltonBradley = 0x59,
///
/// Mindscape / Software Toolworks
///
MindscapeSoftwareToolworks = 0x5A,
///
/// SETA (NA)
///
SetaNA = 0x5B,
///
/// Taxan
///
Taxan = 0x5C,
///
/// Tradewest
///
Tradewest = 0x5D,
///
/// INTV Corporation
///
IntvCorporation = 0x5E,
///
/// Titus
///
Titus = 0x60,
///
/// Virgin Games
///
VirginGames = 0x61,
///
/// Ocean
///
Ocean = 0x67,
///
/// Electronic Arts (NA)
///
ElectronicArtsNA = 0x69,
///
/// Beam Software
///
BeamSoftware = 0x6B,
///
/// Elite Systems
///
EliteSystems = 0x6E,
///
/// Electro Brain
///
ElectroBrain = 0x6F,
///
/// Infogrames
///
Infogrames = 0x70,
///
/// JVC
///
Jvc = 0x72,
///
/// Parker Brothers
///
ParkerBrothers = 0x73,
///
/// The Sales Curve / SCi
///
TheSalesCurveSci = 0x75,
///
/// THQ
///
Thq = 0x78,
///
/// Accolade
///
Accolade = 0x79,
///
/// Triffix
///
Triffix = 0x7A,
///
/// Microprose Software
///
MicroproseSoftware = 0x7C,
///
/// Kemco (NA)
///
KemcoNA = 0x7F,
///
/// Misawa Entertainment
///
MisawaEntertainment = 0x80,
///
/// G. Amusements Co.
///
GAmusementsCO = 0x83,
///
/// G.O 1
///
GO1 = 0x85,
///
/// Tokuma Shoten Intermedia
///
TokumaShotenIntermedia = 0x86,
///
/// Nihon Maicom Kaihatsu (NMK)
///
NihonMaicomKaihatsu = 0x89,
///
/// BulletProof Software (BPS)
///
BulletproofSoftware = 0x8B,
///
/// VIC Tokai
///
VicTokai = 0x8C,
///
/// Sanritsu
///
Sanritsu = 0x8D,
///
/// Character Soft
///
CharacterSoft = 0x8E,
///
/// I'Max
///
IMax = 0x8F,
///
/// Toaplan
///
Toaplan = 0x94,
///
/// Varie
///
Varie = 0x95,
///
/// Yonezawa Party Room 21 / S'Pal
///
YonezawaPartyRoom21SPal = 0x96,
///
/// Pack-In-Video
///
PackINVideo = 0x99,
///
/// Nihon Bussan
///
NihonBussan = 0x9A,
///
/// Tecmo
///
Tecmo = 0x9B,
///
/// Imagineer
///
Imagineer = 0x9C,
///
/// Face
///
Face = 0x9E,
///
/// Scorpion Soft
///
ScorpionSoft = 0xA2,
///
/// Broderbund
///
Broderbund = 0xA3,
///
/// Konami
///
Konami = 0xA4,
///
/// K. Amusement Leasing Co. (KAC)
///
KAmusementLeasingCO = 0xA5,
///
/// Kawada Co., Ltd.
///
KawadaCOLtd = 0xA6,
///
/// Takara
///
Takara = 0xA7,
///
/// Royal Industries
///
RoyalIndustries = 0xA8,
///
/// Tecnos
///
Tecnos = 0xA9,
///
/// Victor Musical Industries
///
VictorMusicalIndustries = 0xAA,
///
/// Hi-Score Media Work
///
HIScoreMediaWork = 0xAB,
///
/// Toei Animation
///
ToeiAnimation = 0xAC,
///
/// Toho (Japan)
///
TohoJap = 0xAD,
///
/// TSS
///
Tss = 0xAE,
///
/// Namco
///
Namco = 0xAF,
///
/// Acclaim (Japan)
///
AcclaimJap = 0xB0,
///
/// ASCII Corporation / Nexoft
///
AsciiCorporationNexoft = 0xB1,
///
/// Bandai
///
Bandai = 0xB2,
///
/// Soft Pro Inc.
///
SoftProInc = 0xB3,
///
/// Enix
///
Enix = 0xB4,
///
/// dB-SOFT
///
DBSoft = 0xB5,
///
/// HAL Laboratory
///
HalLaboratory = 0xB6,
///
/// SNK
///
Snk = 0xB7,
///
/// Pony Canyon
///
PonyCanyon = 0xB9,
///
/// Culture Brain
///
CultureBrain = 0xBA,
///
/// Sunsoft
///
Sunsoft = 0xBB,
///
/// Toshiba EMI
///
ToshibaEmi = 0xBC,
///
/// CBS/Sony Group
///
CbsSonyGroup = 0xBD,
///
/// Sammy Corporation
///
SammyCorporation = 0xBF,
///
/// Taito
///
Taito = 0xC0,
///
/// Sunsoft / Ask Co., Ltd.
///
SunsoftAskCOLtd = 0xC1,
///
/// Kemco
///
Kemco = 0xC2,
///
/// Square / Disk Original Group (DOG)
///
SquareDiskOriginalGroup = 0xC3,
///
/// Tokuma Shoten
///
TokumaShoten = 0xC4,
///
/// Data East
///
DataEast = 0xC5,
///
/// Tonkin House / Tokyo Shoseki
///
TonkinHouseTokyoShoseki = 0xC6,
///
/// East Cube / Toho (NA)
///
EastCubeTohoNA = 0xC7,
///
/// Koei
///
Koei = 0xC8,
///
/// UPL
///
Upl = 0xC9,
///
/// Konami / Ultra / Palcom
///
KonamiUltraPalcom = 0xCA,
///
/// NTVIC / VAP
///
NtvicVap = 0xCB,
///
/// Use Co., Ltd.
///
UseCOLtd = 0xCC,
///
/// Meldac
///
Meldac = 0xCD,
///
/// Pony Canyon / FCI
///
PonyCanyonFci = 0xCE,
///
/// Angel
///
Angel = 0xCF,
///
/// Disco
///
Disco = 0xD0,
///
/// Sofel
///
Sofel = 0xD1,
///
/// Bothtec, Inc. / Quest
///
BothtecIncQuest = 0xD2,
///
/// Sigma Enterprises
///
SigmaEnterprises = 0xD3,
///
/// Ask Corp.
///
AskCorp = 0xD4,
///
/// Kyugo Trading Co.
///
KyugoTradingCO = 0xD5,
///
/// Naxat Soft / Kaga Tech
///
NaxatSoftKagaTech = 0xD6,
///
/// Status
///
Status = 0xD8,
///
/// Banpresto
///
Banpresto = 0xD9,
///
/// Tomy
///
Tomy = 0xDA,
///
/// Hiro Co., Ltd.
///
HiroCOLtd = 0xDB,
///
/// Nippon Computer Systems (NCS) / Masaya Games
///
NipponComputerSystemsMasayaGames = 0xDD,
///
/// Human Creative
///
HumanCreative = 0xDE,
///
/// Altron
///
Altron = 0xDF,
///
/// K.K. DCE
///
KKDce = 0xE0,
///
/// Towa Chiki
///
TowaChiki = 0xE1,
///
/// Yutaka
///
Yutaka = 0xE2,
///
/// Kaken Corporation
///
KakenCorporation = 0xE3,
///
/// Epoch
///
Epoch = 0xE5,
///
/// Athena
///
Athena = 0xE7,
///
/// Asmik
///
Asmik = 0xE8,
///
/// Natsume
///
Natsume = 0xE9,
///
/// King Records
///
KingRecords = 0xEA,
///
/// Atlus
///
Atlus = 0xEB,
///
/// Sony Music Entertainment
///
SonyMusicEntertainment = 0xEC,
///
/// Pixel Corporation
///
PixelCorporation = 0xED,
///
/// Information Global Service (IGS)
///
InformationGlobalService = 0xEE,
///
/// Fujimic
///
Fujimic = 0xEF,
///
/// A-Wave
///
AWave = 0xF0,
}
[MarshalAs(UnmanagedType.U1)]
// Raw byte: 0x01
private readonly byte blockType = 1;
///
/// Valid block type ID
///
public byte ValidTypeID { get => 1; }
///
/// True if block type ID is valid
///
public bool IsValid { get => blockType == ValidTypeID; }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)]
readonly byte[] diskVerification = Encoding.ASCII.GetBytes("*NINTENDO-HVC*");
///
/// Literal ASCII string: *NINTENDO-HVC*
///
public string DiskVerification => Encoding.ASCII.GetString(diskVerification).Trim(new char[] { '\0', ' ' }); /*set => diskVerification = value.PadRight(14).ToCharArray(0, value.Length > 14 ? 14 : value.Length);*/
[MarshalAs(UnmanagedType.U1)]
private byte manufacturerCode;
///
/// Manufacturer code. = = 0x00, Unlicensed, = = 0x01, Nintendo
///
public Company LicenseeCode { get => (Company)manufacturerCode; set => manufacturerCode = (byte)value; }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
byte[] gameName = Encoding.ASCII.GetBytes("---");
///
/// 3-letter ASCII code per game (e.g. ZEL for The Legend of Zelda)
///
public string GameName { get => Encoding.ASCII.GetString(gameName).Trim(new char[] { '\0', ' ' }); set => gameName = Encoding.ASCII.GetBytes(value.PadRight(3)).Take(3).ToArray(); }
[MarshalAs(UnmanagedType.U1)]
byte gameType;
///
/// = = 0x20, " " — Normal disk
/// = = 0x45, "E" — Event(e.g.Japanese national DiskFax tournaments)
/// = = 0x52, "R" — Reduction in price via advertising
///
public char GameType { get => (char)gameType; set => gameType = (byte)value; }
[MarshalAs(UnmanagedType.U1)]
byte gameVersion;
///
/// Game version/revision number. Starts at $00, increments per revision
///
public byte GameVersion { get => gameVersion; set => gameVersion = value; }
[MarshalAs(UnmanagedType.U1)]
byte diskSide;
///
/// Side number. Single-sided disks use A
///
public DiskSides DiskSide { get => (DiskSides)diskSide; set => diskSide = (byte)value; }
[MarshalAs(UnmanagedType.U1)]
byte diskNumber;
///
/// Disk number. First disk is $00, second is $01, etc.
///
public byte DiskNumber { get => diskNumber; set => diskNumber = value; }
[MarshalAs(UnmanagedType.U1)]
byte diskType;
///
/// Disk type. = = 0x00, FMC ("normal card"), = = 0x01, FSC ("card with shutter"). May correlate with FMC and FSC product codes
///
public DiskTypes DiskType { get => (DiskTypes)diskType; set => diskType = (byte)value; }
[MarshalAs(UnmanagedType.U1)]
// Speculative: (Err.10) Possibly indicates disk #; usually $00
// Speculative: = = 0x00, yellow disk, = = 0x01, blue or gold disk, = = 0xFE, white disk, = = 0xFF, blue disk
readonly byte unknown01 = 0x00;
[MarshalAs(UnmanagedType.U1)]
byte bootFile;
///
/// Boot read file code. Refers to the file code/file number to load upon boot/start-up
///
public byte BootFile { get => bootFile; set => bootFile = value; }
[MarshalAs(UnmanagedType.U1)]
readonly byte unknown02 = 0xFF;
[MarshalAs(UnmanagedType.U1)]
readonly byte unknown03 = 0xFF;
[MarshalAs(UnmanagedType.U1)]
readonly byte unknown04 = 0xFF;
[MarshalAs(UnmanagedType.U1)]
readonly byte unknown05 = 0xFF;
[MarshalAs(UnmanagedType.U1)]
readonly byte unknown06 = 0xFF;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
byte[] manufacturingDate = { 0, 0, 0 };
///
/// Manufacturing date
///
public DateTime ManufacturingDate
{
get
{
try
{
return new DateTime(
((manufacturingDate[0] & 0x0F) + ((manufacturingDate[0] >> 4) & 0x0F) * 10) + 1925,
((manufacturingDate[1] & 0x0F) + ((manufacturingDate[1] >> 4) & 0x0F) * 10),
((manufacturingDate[2] & 0x0F) + ((manufacturingDate[2] >> 4) & 0x0F) * 10)
);
}
catch
{
return new DateTime();
}
}
set
{
manufacturingDate = new byte[]
{
(byte)(((value.Year - 1925) % 10) | (((value.Year - 1925) / 10) << 4)),
(byte)(((value.Month) % 10) | (((value.Month) / 10) << 4)),
(byte)(((value.Day) % 10) | (((value.Day) / 10) << 4))
};
}
}
[MarshalAs(UnmanagedType.U1)]
// = = 0x49, Japan
byte countryCode = (byte)Country.Japan;
///
/// Country code. = = 0x49, Japan
///
public Country CountryCode { get => (Country)countryCode; set => countryCode = (byte)value; }
[MarshalAs(UnmanagedType.U1)]
// Raw byte: $61. Speculative: Region code?
readonly byte unknown07 = 0x61;
[MarshalAs(UnmanagedType.U1)]
// Raw byte: $00. Speculative: Location/site?
readonly byte unknown08 = 0x00;
[MarshalAs(UnmanagedType.U2)]
// Raw bytes: $00 $02
readonly ushort unknown09 = 0x0200;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
// Speculative: some kind of game information representation?
readonly byte[] unknown10 = { 0, 0, 0, 0, 0 };
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
byte[] rewrittenDate = { 0, 0, 0 };
///
/// "Rewritten disk" date. It's speculated this refers to the date the disk was formatted and rewritten by something like a Disk Writer kiosk.
/// In the case of an original (non-copied) disk, this should be the same as Manufacturing date
///
public DateTime RewrittenDate
{
get
{
try
{
return new DateTime(
((rewrittenDate[0] & 0x0F) + ((rewrittenDate[0] >> 4) & 0x0F) * 10) + 1925,
((rewrittenDate[1] & 0x0F) + ((rewrittenDate[1] >> 4) & 0x0F) * 10),
((rewrittenDate[2] & 0x0F) + ((rewrittenDate[2] >> 4) & 0x0F) * 10)
);
}
catch
{
return new DateTime();
}
}
set
{
rewrittenDate = new byte[]
{
(byte)(((value.Year - 1925) % 10) | (((value.Year - 1925) / 10) << 4)),
(byte)(((value.Month) % 10) | (((value.Month) / 10) << 4)),
(byte)(((value.Day) % 10) | (((value.Day) / 10) << 4))
};
}
}
[MarshalAs(UnmanagedType.U1)]
readonly byte unknown11 = 0x00;
[MarshalAs(UnmanagedType.U1)]
// Raw byte: $80
readonly byte unknown12 = 0x80;
[MarshalAs(UnmanagedType.U2)]
ushort diskWriterSerialNumber;
///
/// Disk Writer serial number
///
public ushort DiskWriterSerialNumber { get => diskWriterSerialNumber; set => diskWriterSerialNumber = value; }
[MarshalAs(UnmanagedType.U1)]
// Raw byte: $07
readonly byte unknown13 = 0x07;
[MarshalAs(UnmanagedType.U1)]
byte diskRewriteCount = 0x00;
///
/// Disk rewrite count. = = 0x00, Original (no copies)
///
public byte DiskRewriteCount
{
get
{
return (diskRewriteCount == 0xFF) ? (byte)0 : (byte)((diskRewriteCount & 0x0F) + ((diskRewriteCount >> 4) & 0x0F) * 10);
}
set
{
diskRewriteCount = (byte)(((value) % 10) | (((value) / 10) << 4));
}
}
[MarshalAs(UnmanagedType.U1)]
byte actualDiskSide = 0x00;
///
/// Actual disk side
///
public DiskSides ActualDiskSide { get => (DiskSides)actualDiskSide; set => actualDiskSide = (byte)value; }
[MarshalAs(UnmanagedType.U1)]
readonly byte unknown14 = 0x00;
[MarshalAs(UnmanagedType.U1)]
byte price = 0x00;
///
/// Price code
///
public byte Price { get => price; set => price = value; }
///
/// Length of the block
///
public uint Length { get => 56; }
///
/// Create FdsBlockDiskInfo object from raw data
///
/// Data
/// Data offset
/// FdsBlockDiskInfo object
///
public static FdsBlockDiskInfo FromBytes(byte[] data, int offset = 0)
{
int rawsize = Marshal.SizeOf(typeof(FdsBlockDiskInfo));
if (rawsize > data.Length - offset)
throw new InvalidDataException("Not enough data to fill FdsDiskInfoBlock class. Array length from position: " + (data.Length - offset) + ", struct length: " + rawsize);
IntPtr buffer = Marshal.AllocHGlobal(rawsize);
Marshal.Copy(data, offset, buffer, rawsize);
FdsBlockDiskInfo retobj = (FdsBlockDiskInfo)Marshal.PtrToStructure(buffer, typeof(FdsBlockDiskInfo));
Marshal.FreeHGlobal(buffer);
return retobj;
}
///
/// Returns raw data
///
/// Data
public byte[] ToBytes()
{
int rawSize = Marshal.SizeOf(this);
IntPtr buffer = Marshal.AllocHGlobal(rawSize);
Marshal.StructureToPtr(this, buffer, false);
byte[] data = new byte[rawSize];
Marshal.Copy(buffer, data, 0, rawSize);
Marshal.FreeHGlobal(buffer);
return data;
}
///
/// String representation
///
/// Game name
public override string ToString() => GameName;
///
/// Equality comparer
///
/// Other FdsBlockDiskInfo object
/// True if objects are equal
public bool Equals(FdsBlockDiskInfo other)
{
return Enumerable.SequenceEqual(this.ToBytes(), other.ToBytes());
}
}
}