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()); } } }