diff --git a/SabreTools.Core/Enums.cs b/SabreTools.Core/Enums.cs
index 3359082c..8182d33a 100644
--- a/SabreTools.Core/Enums.cs
+++ b/SabreTools.Core/Enums.cs
@@ -1008,1280 +1008,6 @@ namespace SabreTools.Core
#region FileTypes
- ///
- /// List of known blocks types
- ///
- ///
- public enum AaruBlockType : uint
- {
- /// Block containing data
- DataBlock = 0x4B4C4244,
- /// Block containing a deduplication table
- DeDuplicationTable = 0x2A544444,
- /// Block containing the index
- Index = 0x58444E49,
- /// Block containing the index
- Index2 = 0x32584449,
- /// Block containing logical geometry
- GeometryBlock = 0x4D4F4547,
- /// Block containing metadata
- MetadataBlock = 0x4154454D,
- /// Block containing optical disc tracks
- TracksBlock = 0x534B5254,
- /// Block containing CICM XML metadata
- CicmBlock = 0x4D434943,
- /// Block containing contents checksums
- ChecksumBlock = 0x4D534B43,
- /// TODO: Block containing data position measurements
- DataPositionMeasurementBlock = 0x2A4D5044,
- /// TODO: Block containing a snapshot index
- SnapshotBlock = 0x50414E53,
- /// TODO: Block containing how to locate the parent image
- ParentBlock = 0x50524E54,
- /// Block containing an array of hardware used to create the image
- DumpHardwareBlock = 0x2A504D44,
- /// Block containing list of files for a tape image
- TapeFileBlock = 0x454C4654,
- /// Block containing list of partitions for a tape image
- TapePartitionBlock = 0x54425054,
- /// Block containing list of indexes for Compact Disc tracks
- CompactDiscIndexesBlock = 0x58494443
- }
-
- ///
- public enum AaruChecksumAlgorithm : byte
- {
- Invalid = 0, Md5 = 1, Sha1 = 2,
- Sha256 = 3, SpamSum = 4
- }
-
- ///
- /// List of known data types
- ///
- ///
- public enum AaruDataType : ushort
- {
- /// No data
- NoData = 0,
- /// User data
- UserData = 1,
- /// CompactDisc partial Table of Contents
- CompactDiscPartialToc = 2,
- /// CompactDisc session information
- CompactDiscSessionInfo = 3,
- /// CompactDisc Table of Contents
- CompactDiscToc = 4,
- /// CompactDisc Power Management Area
- CompactDiscPma = 5,
- /// CompactDisc Absolute Time In Pregroove
- CompactDiscAtip = 6,
- /// CompactDisc Lead-in's CD-Text
- CompactDiscLeadInCdText = 7,
- /// DVD Physical Format Information
- DvdPfi = 8,
- /// DVD Lead-in's Copyright Management Information
- DvdLeadInCmi = 9,
- /// DVD Disc Key
- DvdDiscKey = 10,
- /// DVD Burst Cutting Area
- DvdBca = 11,
- /// DVD DMI
- DvdDmi = 12,
- /// DVD Media Identifier
- DvdMediaIdentifier = 13,
- /// DVD Media Key Block
- DvdMediaKeyBlock = 14,
- /// DVD-RAM Disc Definition Structure
- DvdRamDds = 15,
- /// DVD-RAM Medium Status
- DvdRamMediumStatus = 16,
- /// DVD-RAM Spare Area Information
- DvdRamSpareArea = 17,
- /// DVD-R RMD
- DvdRRmd = 18,
- /// DVD-R Pre-recorded Information
- DvdRPrerecordedInfo = 19,
- /// DVD-R Media Identifier
- DvdRMediaIdentifier = 20,
- /// DVD-R Physical Format Information
- DvdRPfi = 21,
- /// DVD ADress In Pregroove
- DvdAdip = 22,
- /// HD DVD Copy Protection Information
- HdDvdCpi = 23,
- /// HD DVD Medium Status
- HdDvdMediumStatus = 24,
- /// DVD DL Layer Capacity
- DvdDlLayerCapacity = 25,
- /// DVD DL Middle Zone Address
- DvdDlMiddleZoneAddress = 26,
- /// DVD DL Jump Interval Size
- DvdDlJumpIntervalSize = 27,
- /// DVD DL Manual Layer Jump LBA
- DvdDlManualLayerJumpLba = 28,
- /// Bluray Disc Information
- BlurayDi = 29,
- /// Bluray Burst Cutting Area
- BlurayBca = 30,
- /// Bluray Disc Definition Structure
- BlurayDds = 31,
- /// Bluray Cartridge Status
- BlurayCartridgeStatus = 32,
- /// Bluray Spare Area Information
- BluraySpareArea = 33,
- /// AACS Volume Identifier
- AacsVolumeIdentifier = 34,
- /// AACS Serial Number
- AacsSerialNumber = 35,
- /// AACS Media Identifier
- AacsMediaIdentifier = 36,
- /// AACS Media Key Block
- AacsMediaKeyBlock = 37,
- /// AACS Data Keys
- AacsDataKeys = 38,
- /// AACS LBA Extents
- AacsLbaExtents = 39,
- /// CPRM Media Key Block
- CprmMediaKeyBlock = 40,
- /// Recognized Layers
- HybridRecognizedLayers = 41,
- /// MMC Write Protection
- ScsiMmcWriteProtection = 42,
- /// MMC Disc Information
- ScsiMmcDiscInformation = 43,
- /// MMC Track Resources Information
- ScsiMmcTrackResourcesInformation = 44,
- /// MMC POW Resources Information
- ScsiMmcPowResourcesInformation = 45,
- /// SCSI INQUIRY RESPONSE
- ScsiInquiry = 46,
- /// SCSI MODE PAGE 2Ah
- ScsiModePage2A = 47,
- /// ATA IDENTIFY response
- AtaIdentify = 48,
- /// ATAPI IDENTIFY response
- AtapiIdentify = 49,
- /// PCMCIA CIS
- PcmciaCis = 50,
- /// SecureDigital CID
- SecureDigitalCid = 51,
- /// SecureDigital CSD
- SecureDigitalCsd = 52,
- /// SecureDigital SCR
- SecureDigitalScr = 53,
- /// SecureDigital OCR
- SecureDigitalOcr = 54,
- /// MultiMediaCard CID
- MultiMediaCardCid = 55,
- /// MultiMediaCard CSD
- MultiMediaCardCsd = 56,
- /// MultiMediaCard OCR
- MultiMediaCardOcr = 57,
- /// MultiMediaCard Extended CSD
- MultiMediaCardExtendedCsd = 58,
- /// Xbox Security Sector
- XboxSecuritySector = 59,
- /// Floppy Lead-out
- FloppyLeadOut = 60,
- /// Dvd Disc Control Block
- DvdDiscControlBlock = 61,
- /// CompactDisc First track pregap
- CompactDiscFirstTrackPregap = 62,
- /// CompactDisc Lead-out
- CompactDiscLeadOut = 63,
- /// SCSI MODE SENSE (6) response
- ScsiModeSense6 = 64,
- /// SCSI MODE SENSE (10) response
- ScsiModeSense10 = 65,
- /// USB descriptors
- UsbDescriptors = 66,
- /// Xbox DMI
- XboxDmi = 67,
- /// Xbox Physical Format Information
- XboxPfi = 68,
- /// CompactDisc sector prefix (sync, header
- CdSectorPrefix = 69,
- /// CompactDisc sector suffix (edc, ecc p, ecc q)
- CdSectorSuffix = 70,
- /// CompactDisc subchannel
- CdSectorSubchannel = 71,
- /// Apple Profile (20 byte) tag
- AppleProfileTag = 72,
- /// Apple Sony (12 byte) tag
- AppleSonyTag = 73,
- /// Priam Data Tower (24 byte) tag
- PriamDataTowerTag = 74,
- /// CompactDisc Media Catalogue Number (as in Lead-in), 13 bytes, ASCII
- CompactDiscMediaCatalogueNumber = 75,
- /// CompactDisc sector prefix (sync, header), only incorrect stored
- CdSectorPrefixCorrected = 76,
- /// CompactDisc sector suffix (edc, ecc p, ecc q), only incorrect stored
- CdSectorSuffixCorrected = 77,
- /// CompactDisc MODE 2 subheader
- CompactDiscMode2Subheader = 78,
- /// CompactDisc Lead-in
- CompactDiscLeadIn = 79
- }
-
- ///
- /// Internal media format for AaruFormat
- ///
- ///
- public enum AaruMediaType : uint
- {
- #region Generics, types 0 to 9
- /// Unknown disk type
- Unknown = 0,
- /// Unknown magneto-optical
- UnknownMO = 1,
- /// Generic hard disk
- GENERIC_HDD = 2,
- /// Microdrive type hard disk
- Microdrive = 3,
- /// Zoned hard disk
- Zone_HDD = 4,
- /// USB flash drives
- FlashDrive = 5,
- /// USB flash drives
- UnknownTape = 4,
- #endregion Generics, types 0 to 9
-
- #region Somewhat standard Compact Disc formats, types 10 to 39
- /// Any unknown or standard violating CD
- CD = 10,
- /// CD Digital Audio (Red Book)
- CDDA = 11,
- /// CD+G (Red Book)
- CDG = 12,
- /// CD+EG (Red Book)
- CDEG = 13,
- /// CD-i (Green Book)
- CDI = 14,
- /// CD-ROM (Yellow Book)
- CDROM = 15,
- /// CD-ROM XA (Yellow Book)
- CDROMXA = 16,
- /// CD+ (Blue Book)
- CDPLUS = 17,
- /// CD-MO (Orange Book)
- CDMO = 18,
- /// CD-Recordable (Orange Book)
- CDR = 19,
- /// CD-ReWritable (Orange Book)
- CDRW = 20,
- /// Mount-Rainier CD-RW
- CDMRW = 21,
- /// Video CD (White Book)
- VCD = 22,
- /// Super Video CD (White Book)
- SVCD = 23,
- /// Photo CD (Beige Book)
- PCD = 24,
- /// Super Audio CD (Scarlet Book)
- SACD = 25,
- /// Double-Density CD-ROM (Purple Book)
- DDCD = 26,
- /// DD CD-R (Purple Book)
- DDCDR = 27,
- /// DD CD-RW (Purple Book)
- DDCDRW = 28,
- /// DTS audio CD (non-standard)
- DTSCD = 29,
- /// CD-MIDI (Red Book)
- CDMIDI = 30,
- /// CD-Video (ISO/IEC 61104)
- CDV = 31,
- /// 120mm, Phase-Change, 1298496 sectors, 512 bytes/sector, PD650, ECMA-240, ISO 15485
- PD650 = 32,
- /// 120mm, Write-Once, 1281856 sectors, 512 bytes/sector, PD650, ECMA-240, ISO 15485
- PD650_WORM = 33,
- ///
- /// CD-i Ready, contains a track before the first TOC track, in mode 2, and all TOC tracks are Audio. Subchannel
- /// marks track as audio pause.
- ///
- CDIREADY = 34, FMTOWNS = 35,
- #endregion Somewhat standard Compact Disc formats, types 10 to 39
-
- #region Standard DVD formats, types 40 to 50
- /// DVD-ROM (applies to DVD Video and DVD Audio)
- DVDROM = 40,
- /// DVD-R
- DVDR = 41,
- /// DVD-RW
- DVDRW = 42,
- /// DVD+R
- DVDPR = 43,
- /// DVD+RW
- DVDPRW = 44,
- /// DVD+RW DL
- DVDPRWDL = 45,
- /// DVD-R DL
- DVDRDL = 46,
- /// DVD+R DL
- DVDPRDL = 47,
- /// DVD-RAM
- DVDRAM = 48,
- /// DVD-RW DL
- DVDRWDL = 49,
- /// DVD-Download
- DVDDownload = 50,
- #endregion Standard DVD formats, types 40 to 50
-
- #region Standard HD-DVD formats, types 51 to 59
- /// HD DVD-ROM (applies to HD DVD Video)
- HDDVDROM = 51,
- /// HD DVD-RAM
- HDDVDRAM = 52,
- /// HD DVD-R
- HDDVDR = 53,
- /// HD DVD-RW
- HDDVDRW = 54,
- /// HD DVD-R DL
- HDDVDRDL = 55,
- /// HD DVD-RW DL
- HDDVDRWDL = 56,
- #endregion Standard HD-DVD formats, types 51 to 59
-
- #region Standard Blu-ray formats, types 60 to 69
- /// BD-ROM (and BD Video)
- BDROM = 60,
- /// BD-R
- BDR = 61,
- /// BD-RE
- BDRE = 62,
- /// BD-R XL
- BDRXL = 63,
- /// BD-RE XL
- BDREXL = 64,
- #endregion Standard Blu-ray formats, types 60 to 69
-
- #region Rare or uncommon optical standards, types 70 to 79
- /// Enhanced Versatile Disc
- EVD = 70,
- /// Forward Versatile Disc
- FVD = 71,
- /// Holographic Versatile Disc
- HVD = 72,
- /// China Blue High Definition
- CBHD = 73,
- /// High Definition Versatile Multilayer Disc
- HDVMD = 74,
- /// Versatile Compact Disc High Density
- VCDHD = 75,
- /// Stacked Volumetric Optical Disc
- SVOD = 76,
- /// Five Dimensional disc
- FDDVD = 77,
- /// China Video Disc
- CVD = 78,
- #endregion Rare or uncommon optical standards, types 70 to 79
-
- #region LaserDisc based, types 80 to 89
- /// Pioneer LaserDisc
- LD = 80,
- /// Pioneer LaserDisc data
- LDROM = 81, LDROM2 = 82, LVROM = 83, MegaLD = 84,
- #endregion LaserDisc based, types 80 to 89
-
- #region MiniDisc based, types 90 to 99
- /// Sony Hi-MD
- HiMD = 90,
- /// Sony MiniDisc
- MD = 91,
- /// Sony MD-Data
- MDData = 92,
- /// Sony MD-Data2
- MDData2 = 93,
- /// Sony MiniDisc, 60 minutes, formatted with Hi-MD format
- MD60 = 94,
- /// Sony MiniDisc, 74 minutes, formatted with Hi-MD format
- MD74 = 95,
- /// Sony MiniDisc, 80 minutes, formatted with Hi-MD format
- MD80 = 96,
- #endregion MiniDisc based, types 90 to 99
-
- #region Plasmon UDO, types 100 to 109
- /// 5.25", Phase-Change, 1834348 sectors, 8192 bytes/sector, Ultra Density Optical, ECMA-350, ISO 17345
- UDO = 100,
- /// 5.25", Phase-Change, 3669724 sectors, 8192 bytes/sector, Ultra Density Optical 2, ECMA-380, ISO 11976
- UDO2 = 101,
- /// 5.25", Write-Once, 3668759 sectors, 8192 bytes/sector, Ultra Density Optical 2, ECMA-380, ISO 11976
- UDO2_WORM = 102,
- #endregion Plasmon UDO, types 100 to 109
-
- #region Sony game media, types 110 to 129
- PlayStationMemoryCard = 110, PlayStationMemoryCard2 = 111,
- /// Sony PlayStation game CD
- PS1CD = 112,
- /// Sony PlayStation 2 game CD
- PS2CD = 113,
- /// Sony PlayStation 2 game DVD
- PS2DVD = 114,
- /// Sony PlayStation 3 game DVD
- PS3DVD = 115,
- /// Sony PlayStation 3 game Blu-ray
- PS3BD = 116,
- /// Sony PlayStation 4 game Blu-ray
- PS4BD = 117,
- /// Sony PlayStation Portable Universal Media Disc (ECMA-365)
- UMD = 118, PlayStationVitaGameCard = 119,
- #endregion Sony game media, types 110 to 129
-
- #region Microsoft game media, types 130 to 149
- /// Microsoft X-box Game Disc
- XGD = 130,
- /// Microsoft X-box 360 Game Disc
- XGD2 = 131,
- /// Microsoft X-box 360 Game Disc
- XGD3 = 132,
- /// Microsoft X-box One Game Disc
- XGD4 = 133,
- #endregion Microsoft game media, types 130 to 149
-
- #region Sega game media, types 150 to 169
- /// Sega MegaCD
- MEGACD = 150,
- /// Sega Saturn disc
- SATURNCD = 151,
- /// Sega/Yamaha Gigabyte Disc
- GDROM = 152,
- /// Sega/Yamaha recordable Gigabyte Disc
- GDR = 153, SegaCard = 154, MilCD = 155,
- #endregion Sega game media, types 150 to 169
-
- #region Other game media, types 170 to 179
- /// PC-Engine / TurboGrafx cartridge
- HuCard = 170,
- /// PC-Engine / TurboGrafx CD
- SuperCDROM2 = 171,
- /// Atari Jaguar CD
- JaguarCD = 172,
- /// 3DO CD
- ThreeDO = 173,
- /// NEC PC-FX
- PCFX = 174,
- /// NEO-GEO CD
- NeoGeoCD = 175,
- /// Commodore CDTV
- CDTV = 176,
- /// Amiga CD32
- CD32 = 177,
- /// Nuon (DVD based videogame console)
- Nuon = 178,
- /// Bandai Playdia
- Playdia = 179,
- #endregion Other game media, types 170 to 179
-
- #region Apple standard floppy format, types 180 to 189
- /// 5.25", SS, DD, 35 tracks, 13 spt, 256 bytes/sector, GCR
- Apple32SS = 180,
- /// 5.25", DS, DD, 35 tracks, 13 spt, 256 bytes/sector, GCR
- Apple32DS = 181,
- /// 5.25", SS, DD, 35 tracks, 16 spt, 256 bytes/sector, GCR
- Apple33SS = 182,
- /// 5.25", DS, DD, 35 tracks, 16 spt, 256 bytes/sector, GCR
- Apple33DS = 183,
- /// 3.5", SS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
- AppleSonySS = 184,
- /// 3.5", DS, DD, 80 tracks, 8 to 12 spt, 512 bytes/sector, GCR
- AppleSonyDS = 185,
- /// 5.25", DS, ?D, ?? tracks, ?? spt, 512 bytes/sector, GCR, opposite side heads, aka Twiggy
- AppleFileWare = 186,
- #endregion Apple standard floppy format
-
- #region IBM/Microsoft PC floppy formats, types 190 to 209
- /// 5.25", SS, DD, 40 tracks, 8 spt, 512 bytes/sector, MFM
- DOS_525_SS_DD_8 = 190,
- /// 5.25", SS, DD, 40 tracks, 9 spt, 512 bytes/sector, MFM
- DOS_525_SS_DD_9 = 191,
- /// 5.25", DS, DD, 40 tracks, 8 spt, 512 bytes/sector, MFM
- DOS_525_DS_DD_8 = 192,
- /// 5.25", DS, DD, 40 tracks, 9 spt, 512 bytes/sector, MFM
- DOS_525_DS_DD_9 = 193,
- /// 5.25", DS, HD, 80 tracks, 15 spt, 512 bytes/sector, MFM
- DOS_525_HD = 194,
- /// 3.5", SS, DD, 80 tracks, 8 spt, 512 bytes/sector, MFM
- DOS_35_SS_DD_8 = 195,
- /// 3.5", SS, DD, 80 tracks, 9 spt, 512 bytes/sector, MFM
- DOS_35_SS_DD_9 = 196,
- /// 3.5", DS, DD, 80 tracks, 8 spt, 512 bytes/sector, MFM
- DOS_35_DS_DD_8 = 197,
- /// 3.5", DS, DD, 80 tracks, 9 spt, 512 bytes/sector, MFM
- DOS_35_DS_DD_9 = 198,
- /// 3.5", DS, HD, 80 tracks, 18 spt, 512 bytes/sector, MFM
- DOS_35_HD = 199,
- /// 3.5", DS, ED, 80 tracks, 36 spt, 512 bytes/sector, MFM
- DOS_35_ED = 200,
- /// 3.5", DS, HD, 80 tracks, 21 spt, 512 bytes/sector, MFM
- DMF = 201,
- /// 3.5", DS, HD, 82 tracks, 21 spt, 512 bytes/sector, MFM
- DMF_82 = 202,
- ///
- /// 5.25", DS, HD, 80 tracks, ? spt, ??? + ??? + ??? bytes/sector, MFM track 0 = ??15 sectors, 512 bytes/sector,
- /// falsified to DOS as 19 spt, 512 bps
- ///
- XDF_525 = 203,
- ///
- /// 3.5", DS, HD, 80 tracks, 4 spt, 8192 + 2048 + 1024 + 512 bytes/sector, MFM track 0 = 19 sectors, 512
- /// bytes/sector, falsified to DOS as 23 spt, 512 bps
- ///
- XDF_35 = 204,
- #endregion IBM/Microsoft PC standard floppy formats, types 190 to 209
-
- #region IBM standard floppy formats, types 210 to 219
- /// 8", SS, SD, 32 tracks, 8 spt, 319 bytes/sector, FM
- IBM23FD = 210,
- /// 8", SS, SD, 73 tracks, 26 spt, 128 bytes/sector, FM
- IBM33FD_128 = 211,
- /// 8", SS, SD, 74 tracks, 15 spt, 256 bytes/sector, FM, track 0 = 26 sectors, 128 bytes/sector
- IBM33FD_256 = 212,
- /// 8", SS, SD, 74 tracks, 8 spt, 512 bytes/sector, FM, track 0 = 26 sectors, 128 bytes/sector
- IBM33FD_512 = 213,
- /// 8", DS, SD, 74 tracks, 26 spt, 128 bytes/sector, FM, track 0 = 26 sectors, 128 bytes/sector
- IBM43FD_128 = 214,
- /// 8", DS, SD, 74 tracks, 26 spt, 256 bytes/sector, FM, track 0 = 26 sectors, 128 bytes/sector
- IBM43FD_256 = 215,
- ///
- /// 8", DS, DD, 74 tracks, 26 spt, 256 bytes/sector, MFM, track 0 side 0 = 26 sectors, 128 bytes/sector, track 0
- /// side 1 = 26 sectors, 256 bytes/sector
- ///
- IBM53FD_256 = 216,
- ///
- /// 8", DS, DD, 74 tracks, 15 spt, 512 bytes/sector, MFM, track 0 side 0 = 26 sectors, 128 bytes/sector, track 0
- /// side 1 = 26 sectors, 256 bytes/sector
- ///
- IBM53FD_512 = 217,
- ///
- /// 8", DS, DD, 74 tracks, 8 spt, 1024 bytes/sector, MFM, track 0 side 0 = 26 sectors, 128 bytes/sector, track 0
- /// side 1 = 26 sectors, 256 bytes/sector
- ///
- IBM53FD_1024 = 218,
- #endregion IBM standard floppy formats, types 210 to 219
-
- #region DEC standard floppy formats, types 220 to 229
- /// 8", SS, DD, 77 tracks, 26 spt, 128 bytes/sector, FM
- RX01 = 220,
- /// 8", SS, DD, 77 tracks, 26 spt, 256 bytes/sector, FM/MFM
- RX02 = 221,
- /// 8", DS, DD, 77 tracks, 26 spt, 256 bytes/sector, FM/MFM
- RX03 = 222,
- /// 5.25", SS, DD, 80 tracks, 10 spt, 512 bytes/sector, MFM
- RX50 = 223,
- #endregion DEC standard floppy formats, types 220 to 229
-
- #region Acorn standard floppy formats, types 230 to 239
- /// 5,25", SS, SD, 40 tracks, 10 spt, 256 bytes/sector, FM
- ACORN_525_SS_SD_40 = 230,
- /// 5,25", SS, SD, 80 tracks, 10 spt, 256 bytes/sector, FM
- ACORN_525_SS_SD_80 = 231,
- /// 5,25", SS, DD, 40 tracks, 16 spt, 256 bytes/sector, MFM
- ACORN_525_SS_DD_40 = 232,
- /// 5,25", SS, DD, 80 tracks, 16 spt, 256 bytes/sector, MFM
- ACORN_525_SS_DD_80 = 233,
- /// 5,25", DS, DD, 80 tracks, 16 spt, 256 bytes/sector, MFM
- ACORN_525_DS_DD = 234,
- /// 3,5", DS, DD, 80 tracks, 5 spt, 1024 bytes/sector, MFM
- ACORN_35_DS_DD = 235,
- /// 3,5", DS, HD, 80 tracks, 10 spt, 1024 bytes/sector, MFM
- ACORN_35_DS_HD = 236,
- #endregion Acorn standard floppy formats, types 230 to 239
-
- #region Atari standard floppy formats, types 240 to 249
- /// 5,25", SS, SD, 40 tracks, 18 spt, 128 bytes/sector, FM
- ATARI_525_SD = 240,
- /// 5,25", SS, ED, 40 tracks, 26 spt, 128 bytes/sector, MFM
- ATARI_525_ED = 241,
- /// 5,25", SS, DD, 40 tracks, 18 spt, 256 bytes/sector, MFM
- ATARI_525_DD = 242,
- /// 3,5", SS, DD, 80 tracks, 10 spt, 512 bytes/sector, MFM
- ATARI_35_SS_DD = 243,
- /// 3,5", DS, DD, 80 tracks, 10 spt, 512 bytes/sector, MFM
- ATARI_35_DS_DD = 244,
- /// 3,5", SS, DD, 80 tracks, 11 spt, 512 bytes/sector, MFM
- ATARI_35_SS_DD_11 = 245,
- /// 3,5", DS, DD, 80 tracks, 11 spt, 512 bytes/sector, MFM
- ATARI_35_DS_DD_11 = 246,
- #endregion Atari standard floppy formats, types 240 to 249
-
- #region Commodore standard floppy formats, types 250 to 259
- /// 3,5", DS, DD, 80 tracks, 10 spt, 512 bytes/sector, MFM (1581)
- CBM_35_DD = 250,
- /// 3,5", DS, DD, 80 tracks, 11 spt, 512 bytes/sector, MFM (Amiga)
- CBM_AMIGA_35_DD = 251,
- /// 3,5", DS, HD, 80 tracks, 22 spt, 512 bytes/sector, MFM (Amiga)
- CBM_AMIGA_35_HD = 252,
- /// 5,25", SS, DD, 35 tracks, GCR
- CBM_1540 = 253,
- /// 5,25", SS, DD, 40 tracks, GCR
- CBM_1540_Ext = 254,
- /// 5,25", DS, DD, 35 tracks, GCR
- CBM_1571 = 255,
- #endregion Commodore standard floppy formats, types 250 to 259
-
- #region NEC/SHARP standard floppy formats, types 260 to 269
- /// 8", DS, SD, 77 tracks, 26 spt, 128 bytes/sector, FM
- NEC_8_SD = 260,
- /// 8", DS, DD, 77 tracks, 26 spt, 256 bytes/sector, MFM
- NEC_8_DD = 261,
- /// 5.25", SS, SD, 80 tracks, 16 spt, 256 bytes/sector, FM
- NEC_525_SS = 262,
- /// 5.25", DS, SD, 80 tracks, 16 spt, 256 bytes/sector, MFM
- NEC_525_DS = 263,
- /// 5,25", DS, HD, 77 tracks, 8 spt, 1024 bytes/sector, MFM
- NEC_525_HD = 264,
- /// 3,5", DS, HD, 77 tracks, 8 spt, 1024 bytes/sector, MFM, aka mode 3
- NEC_35_HD_8 = 265,
- /// 3,5", DS, HD, 80 tracks, 15 spt, 512 bytes/sector, MFM
- NEC_35_HD_15 = 266,
- /// 3,5", DS, TD, 240 tracks, 38 spt, 512 bytes/sector, MFM
- NEC_35_TD = 267,
- /// 5,25", DS, HD, 77 tracks, 8 spt, 1024 bytes/sector, MFM
- SHARP_525 = NEC_525_HD,
- /// 3,5", DS, HD, 80 tracks, 9 spt, 1024 bytes/sector, MFM
- SHARP_525_9 = 268,
- /// 3,5", DS, HD, 77 tracks, 8 spt, 1024 bytes/sector, MFM
- SHARP_35 = NEC_35_HD_8,
- /// 3,5", DS, HD, 80 tracks, 9 spt, 1024 bytes/sector, MFM
- SHARP_35_9 = 269,
- #endregion NEC/SHARP standard floppy formats, types 260 to 269
-
- #region ECMA floppy standards, types 270 to 289
- ///
- /// 5,25", DS, DD, 80 tracks, 8 spt, 1024 bytes/sector, MFM, track 0 side 0 = 26 sectors, 128 bytes/sector, track
- /// 0 side 1 = 26 sectors, 256 bytes/sector
- ///
- ECMA_99_8 = 270,
- ///
- /// 5,25", DS, DD, 77 tracks, 15 spt, 512 bytes/sector, MFM, track 0 side 0 = 26 sectors, 128 bytes/sector, track
- /// 0 side 1 = 26 sectors, 256 bytes/sector
- ///
- ECMA_99_15 = 271,
- ///
- /// 5,25", DS, DD, 77 tracks, 26 spt, 256 bytes/sector, MFM, track 0 side 0 = 26 sectors, 128 bytes/sector, track
- /// 0 side 1 = 26 sectors, 256 bytes/sector
- ///
- ECMA_99_26 = 272,
- /// 3,5", DS, DD, 80 tracks, 9 spt, 512 bytes/sector, MFM
- ECMA_100 = DOS_35_DS_DD_9,
- /// 3,5", DS, HD, 80 tracks, 18 spt, 512 bytes/sector, MFM
- ECMA_125 = DOS_35_HD,
- /// 3,5", DS, ED, 80 tracks, 36 spt, 512 bytes/sector, MFM
- ECMA_147 = DOS_35_ED,
- /// 8", SS, SD, 77 tracks, 26 spt, 128 bytes/sector, FM
- ECMA_54 = 273,
- /// 8", DS, SD, 77 tracks, 26 spt, 128 bytes/sector, FM
- ECMA_59 = 274,
- /// 5,25", SS, DD, 35 tracks, 9 spt, 256 bytes/sector, FM, track 0 side 0 = 16 sectors, 128 bytes/sector
- ECMA_66 = 275,
- ///
- /// 8", DS, DD, 77 tracks, 8 spt, 1024 bytes/sector, FM, track 0 side 0 = 26 sectors, 128 bytes/sector, track 0
- /// side 1 = 26 sectors, 256 bytes/sector
- ///
- ECMA_69_8 = 276,
- ///
- /// 8", DS, DD, 77 tracks, 15 spt, 512 bytes/sector, FM, track 0 side 0 = 26 sectors, 128 bytes/sector, track 0
- /// side 1 = 26 sectors, 256 bytes/sector
- ///
- ECMA_69_15 = 277,
- ///
- /// 8", DS, DD, 77 tracks, 26 spt, 256 bytes/sector, FM, track 0 side 0 = 26 sectors, 128 bytes/sector, track 0
- /// side 1 = 26 sectors, 256 bytes/sector
- ///
- ECMA_69_26 = 278,
- ///
- /// 5,25", DS, DD, 40 tracks, 16 spt, 256 bytes/sector, FM, track 0 side 0 = 16 sectors, 128 bytes/sector, track 0
- /// side 1 = 16 sectors, 256 bytes/sector
- ///
- ECMA_70 = 279,
- ///
- /// 5,25", DS, DD, 80 tracks, 16 spt, 256 bytes/sector, FM, track 0 side 0 = 16 sectors, 128 bytes/sector, track 0
- /// side 1 = 16 sectors, 256 bytes/sector
- ///
- ECMA_78 = 280,
- /// 5,25", DS, DD, 80 tracks, 9 spt, 512 bytes/sector, FM
- ECMA_78_2 = 281,
- #endregion ECMA floppy standards, types 270 to 289
-
- #region Non-standard PC formats (FDFORMAT, 2M, etc), types 290 to 308
- /// 5,25", DS, DD, 82 tracks, 10 spt, 512 bytes/sector, MFM
- FDFORMAT_525_DD = 290,
- /// 5,25", DS, HD, 82 tracks, 17 spt, 512 bytes/sector, MFM
- FDFORMAT_525_HD = 291,
- /// 3,5", DS, DD, 82 tracks, 10 spt, 512 bytes/sector, MFM
- FDFORMAT_35_DD = 292,
- /// 3,5", DS, HD, 82 tracks, 21 spt, 512 bytes/sector, MFM
- FDFORMAT_35_HD = 293,
- #endregion Non-standard PC formats (FDFORMAT, 2M, etc), types 290 to 308
-
- #region Apricot ACT standard floppy formats, type 309
- /// 3.5", DS, DD, 70 tracks, 9 spt, 512 bytes/sector, MFM
- Apricot_35 = 309,
- #endregion Apricot ACT standard floppy formats, type 309
-
- #region OnStream ADR, types 310 to 319
- ADR2120 = 310, ADR260 = 311, ADR30 = 312,
- ADR50 = 313,
- #endregion OnStream ADR, types 310 to 319
-
- #region Advanced Intelligent Tape, types 320 to 339
- AIT1 = 320, AIT1Turbo = 321, AIT2 = 322,
- AIT2Turbo = 323, AIT3 = 324, AIT3Ex = 325,
- AIT3Turbo = 326, AIT4 = 327, AIT5 = 328,
- AITETurbo = 329, SAIT1 = 330, SAIT2 = 331,
- #endregion Advanced Intelligent Tape, types 320 to 339
-
- #region Iomega, types 340 to 359
- /// Obsolete type for 8"x11" Bernoulli Box disk
- [Obsolete]
- Bernoulli = 340,
- /// Obsolete type for 5⅓" Bernoulli Box II disks
- [Obsolete]
- Bernoulli2 = 341, Ditto = 342, DittoMax = 343, Jaz = 344,
- Jaz2 = 345, PocketZip = 346, REV120 = 347,
- REV35 = 348, REV70 = 349, ZIP100 = 350,
- ZIP250 = 351, ZIP750 = 352,
- /// 5⅓" Bernoulli Box II disk with 35Mb capacity
- Bernoulli35 = 353,
- /// 5⅓" Bernoulli Box II disk with 44Mb capacity
- Bernoulli44 = 354,
- /// 5⅓" Bernoulli Box II disk with 65Mb capacity
- Bernoulli65 = 355,
- /// 5⅓" Bernoulli Box II disk with 90Mb capacity
- Bernoulli90 = 356,
- /// 5⅓" Bernoulli Box II disk with 105Mb capacity
- Bernoulli105 = 357,
- /// 5⅓" Bernoulli Box II disk with 150Mb capacity
- Bernoulli150 = 358,
- /// 5⅓" Bernoulli Box II disk with 230Mb capacity
- Bernoulli230 = 359,
- #endregion Iomega, types 340 to 359
-
- #region Audio or video media, types 360 to 369
- CompactCassette = 360, Data8 = 361, MiniDV = 362,
- /// D/CAS-25: Digital data on Compact Cassette form factor, special magnetic media, 9-track
- Dcas25 = 363,
- /// D/CAS-85: Digital data on Compact Cassette form factor, special magnetic media, 17-track
- Dcas85 = 364,
- /// D/CAS-103: Digital data on Compact Cassette form factor, special magnetic media, 21-track
- Dcas103 = 365,
- #endregion Audio media, types 360 to 369
-
- #region CompactFlash Association, types 370 to 379
- CFast = 370, CompactFlash = 371, CompactFlashType2 = 372,
- #endregion CompactFlash Association, types 370 to 379
-
- #region Digital Audio Tape / Digital Data Storage, types 380 to 389
- DigitalAudioTape = 380, DAT160 = 381, DAT320 = 382,
- DAT72 = 383, DDS1 = 384, DDS2 = 385,
- DDS3 = 386, DDS4 = 387,
- #endregion Digital Audio Tape / Digital Data Storage, types 380 to 389
-
- #region DEC, types 390 to 399
- CompactTapeI = 390, CompactTapeII = 391, DECtapeII = 392,
- DLTtapeIII = 393, DLTtapeIIIxt = 394, DLTtapeIV = 395,
- DLTtapeS4 = 396, SDLT1 = 397, SDLT2 = 398,
- VStapeI = 399,
- #endregion DEC, types 390 to 399
-
- #region Exatape, types 400 to 419
- Exatape15m = 400, Exatape22m = 401, Exatape22mAME = 402,
- Exatape28m = 403, Exatape40m = 404, Exatape45m = 405,
- Exatape54m = 406, Exatape75m = 407, Exatape76m = 408,
- Exatape80m = 409, Exatape106m = 410, Exatape160mXL = 411,
- Exatape112m = 412, Exatape125m = 413, Exatape150m = 414,
- Exatape170m = 415, Exatape225m = 416,
- #endregion Exatape, types 400 to 419
-
- #region PCMCIA / ExpressCard, types 420 to 429
- ExpressCard34 = 420, ExpressCard54 = 421, PCCardTypeI = 422,
- PCCardTypeII = 423, PCCardTypeIII = 424, PCCardTypeIV = 425,
- #endregion PCMCIA / ExpressCard, types 420 to 429
-
- #region SyQuest, types 430 to 449
- /// SyQuest 135Mb cartridge for use in EZ135 and EZFlyer drives
- EZ135 = 430,
- /// SyQuest EZFlyer 230Mb cartridge for use in EZFlyer drive
- EZ230 = 431,
- /// SyQuest 4.7Gb for use in Quest drive
- Quest = 432,
- /// SyQuest SparQ 1Gb cartridge
- SparQ = 433,
- /// SyQuest 5Mb cartridge for SQ306RD drive
- SQ100 = 434,
- /// SyQuest 10Mb cartridge for SQ312RD drive
- SQ200 = 435,
- /// SyQuest 15Mb cartridge for SQ319RD drive
- SQ300 = 436,
- /// SyQuest 105Mb cartridge for SQ3105 and SQ3270 drives
- SQ310 = 437,
- /// SyQuest 270Mb cartridge for SQ3270 drive
- SQ327 = 438,
- /// SyQuest 44Mb cartridge for SQ555, SQ5110 and SQ5200C/SQ200 drives
- SQ400 = 439,
- /// SyQuest 88Mb cartridge for SQ5110 and SQ5200C/SQ200 drives
- SQ800 = 440,
- /// SyQuest 1.5Gb cartridge for SyJet drive
- [Obsolete]
- SQ1500 = 441,
- /// SyQuest 200Mb cartridge for use in SQ5200C drive
- SQ2000 = 442,
- /// SyQuest 1.5Gb cartridge for SyJet drive
- SyJet = 443,
- #endregion SyQuest, types 430 to 449
-
- #region Nintendo, types 450 to 469
- FamicomGamePak = 450, GameBoyAdvanceGamePak = 451, GameBoyGamePak = 452,
- /// Nintendo GameCube Optical Disc
- GOD = 453, N64DD = 454, N64GamePak = 455, NESGamePak = 456,
- Nintendo3DSGameCard = 457, NintendoDiskCard = 458, NintendoDSGameCard = 459,
- NintendoDSiGameCard = 460, SNESGamePak = 461, SNESGamePakUS = 462,
- /// Nintendo Wii Optical Disc
- WOD = 463,
- /// Nintendo Wii U Optical Disc
- WUOD = 464, SwitchGameCard = 465,
- #endregion Nintendo, types 450 to 469
-
- #region IBM Tapes, types 470 to 479
- IBM3470 = 470, IBM3480 = 471, IBM3490 = 472,
- IBM3490E = 473, IBM3592 = 474,
- #endregion IBM Tapes, types 470 to 479
-
- #region LTO Ultrium, types 480 to 509
- LTO = 480, LTO2 = 481, LTO3 = 482,
- LTO3WORM = 483, LTO4 = 484, LTO4WORM = 485,
- LTO5 = 486, LTO5WORM = 487, LTO6 = 488,
- LTO6WORM = 489, LTO7 = 490, LTO7WORM = 491,
- #endregion LTO Ultrium, types 480 to 509
-
- #region MemoryStick, types 510 to 519
- MemoryStick = 510, MemoryStickDuo = 511, MemoryStickMicro = 512,
- MemoryStickPro = 513, MemoryStickProDuo = 514,
- #endregion MemoryStick, types 510 to 519
-
- #region SecureDigital, types 520 to 529
- microSD = 520, miniSD = 521, SecureDigital = 522,
- #endregion SecureDigital, types 520 to 529
-
- #region MultiMediaCard, types 530 to 539
- MMC = 530, MMCmicro = 531, RSMMC = 532,
- MMCplus = 533, MMCmobile = 534,
- #endregion MultiMediaCard, types 530 to 539
-
- #region SLR, types 540 to 569
- MLR1 = 540, MLR1SL = 541, MLR3 = 542,
- SLR1 = 543, SLR2 = 544, SLR3 = 545,
- SLR32 = 546, SLR32SL = 547, SLR4 = 548,
- SLR5 = 549, SLR5SL = 550, SLR6 = 551,
- SLRtape7 = 552, SLRtape7SL = 553, SLRtape24 = 554,
- SLRtape24SL = 555, SLRtape40 = 556, SLRtape50 = 557,
- SLRtape60 = 558, SLRtape75 = 559, SLRtape100 = 560,
- SLRtape140 = 561,
- #endregion SLR, types 540 to 569
-
- #region QIC, types 570 to 589
- QIC11 = 570, QIC120 = 571, QIC1350 = 572,
- QIC150 = 573, QIC24 = 574, QIC3010 = 575,
- QIC3020 = 576, QIC3080 = 577, QIC3095 = 578,
- QIC320 = 579, QIC40 = 580, QIC525 = 581,
- QIC80 = 582,
- #endregion QIC, types 570 to 589
-
- #region StorageTek tapes, types 590 to 609
- STK4480 = 590, STK4490 = 591, STK9490 = 592,
- T9840A = 593, T9840B = 594, T9840C = 595,
- T9840D = 596, T9940A = 597, T9940B = 598,
- T10000A = 599, T10000B = 600, T10000C = 601,
- T10000D = 602,
- #endregion StorageTek tapes, types 590 to 609
-
- #region Travan, types 610 to 619
- Travan = 610, Travan1Ex = 611, Travan3 = 612,
- Travan3Ex = 613, Travan4 = 614, Travan5 = 615,
- Travan7 = 616,
- #endregion Travan, types 610 to 619
-
- #region VXA, types 620 to 629
- VXA1 = 620, VXA2 = 621, VXA3 = 622,
- #endregion VXA, types 620 to 629
-
- #region Magneto-optical, types 630 to 659
- /// 5,25", M.O., WORM, 650Mb, 318750 sectors, 1024 bytes/sector, ECMA-153, ISO 11560
- ECMA_153 = 630,
- /// 5,25", M.O., WORM, 600Mb, 581250 sectors, 512 bytes/sector, ECMA-153, ISO 11560
- ECMA_153_512 = 631,
- /// 3,5", M.O., RW, 128Mb, 248826 sectors, 512 bytes/sector, ECMA-154, ISO 10090
- ECMA_154 = 632,
- /// 5,25", M.O., RW/WORM, 1Gb, 904995 sectors, 512 bytes/sector, ECMA-183, ISO 13481
- ECMA_183_512 = 633,
- /// 5,25", M.O., RW/WORM, 1Gb, 498526 sectors, 1024 bytes/sector, ECMA-183, ISO 13481
- ECMA_183 = 634,
- /// 5,25", M.O., RW/WORM, 1.2Gb, 1165600 sectors, 512 bytes/sector, ECMA-184, ISO 13549
- ECMA_184_512 = 635,
- /// 5,25", M.O., RW/WORM, 1.3Gb, 639200 sectors, 1024 bytes/sector, ECMA-184, ISO 13549
- ECMA_184 = 636,
- /// 300mm, M.O., WORM, ??? sectors, 1024 bytes/sector, ECMA-189, ISO 13614
- ECMA_189 = 637,
- /// 300mm, M.O., WORM, ??? sectors, 1024 bytes/sector, ECMA-190, ISO 13403
- ECMA_190 = 638,
- /// 5,25", M.O., RW/WORM, 936921 or 948770 sectors, 1024 bytes/sector, ECMA-195, ISO 13842
- ECMA_195 = 639,
- /// 5,25", M.O., RW/WORM, 1644581 or 1647371 sectors, 512 bytes/sector, ECMA-195, ISO 13842
- ECMA_195_512 = 640,
- /// 3,5", M.O., 446325 sectors, 512 bytes/sector, ECMA-201, ISO 13963
- ECMA_201 = 641,
- /// 3,5", M.O., 429975 sectors, 512 bytes/sector, embossed, ISO 13963
- ECMA_201_ROM = 642,
- /// 3,5", M.O., 371371 sectors, 1024 bytes/sector, ECMA-223
- ECMA_223 = 643,
- /// 3,5", M.O., 694929 sectors, 512 bytes/sector, ECMA-223
- ECMA_223_512 = 644,
- /// 5,25", M.O., 1244621 sectors, 1024 bytes/sector, ECMA-238, ISO 15486
- ECMA_238 = 645,
- /// 3,5", M.O., 310352, 320332 or 321100 sectors, 2048 bytes/sector, ECMA-239, ISO 15498
- ECMA_239 = 646,
- /// 356mm, M.O., 14476734 sectors, 1024 bytes/sector, ECMA-260, ISO 15898
- ECMA_260 = 647,
- /// 356mm, M.O., 24445990 sectors, 1024 bytes/sector, ECMA-260, ISO 15898
- ECMA_260_Double = 648,
- /// 5,25", M.O., 1128134 sectors, 2048 bytes/sector, ECMA-280, ISO 18093
- ECMA_280 = 649,
- /// 300mm, M.O., 7355716 sectors, 2048 bytes/sector, ECMA-317, ISO 20162
- ECMA_317 = 650,
- /// 5,25", M.O., 1095840 sectors, 4096 bytes/sector, ECMA-322, ISO 22092
- ECMA_322 = 651,
- /// 5,25", M.O., 2043664 sectors, 2048 bytes/sector, ECMA-322, ISO 22092
- ECMA_322_2k = 652,
- /// 3,5", M.O., 605846 sectors, 2048 bytes/sector, Cherry Book, GigaMo, ECMA-351, ISO 17346
- GigaMo = 653,
- /// 3,5", M.O., 1063146 sectors, 2048 bytes/sector, Cherry Book 2, GigaMo 2, ECMA-353, ISO 22533
- GigaMo2 = 654,
- #endregion Magneto-optical, types 630 to 659
-
- #region Other floppy standards, types 660 to 689
- CompactFloppy = 660, DemiDiskette = 661,
- /// 3.5", 652 tracks, 2 sides, 512 bytes/sector, Floptical, ECMA-207, ISO 14169
- Floptical = 662, HiFD = 663, QuickDisk = 664, UHD144 = 665,
- VideoFloppy = 666, Wafer = 667, ZXMicrodrive = 668,
- #endregion Other floppy standards, types 660 to 669
-
- #region Miscellaneous, types 670 to 689
- BeeCard = 670, Borsu = 671, DataStore = 672,
- DIR = 673, DST = 674, DTF = 675,
- DTF2 = 676, Flextra3020 = 677, Flextra3225 = 678,
- HiTC1 = 679, HiTC2 = 680, LT1 = 681,
- MiniCard = 872, Orb = 683, Orb5 = 684,
- SmartMedia = 685, xD = 686, XQD = 687,
- DataPlay = 688,
- #endregion Miscellaneous, types 670 to 689
-
- #region Apple specific media, types 690 to 699
- AppleProfile = 690, AppleWidget = 691, AppleHD20 = 692,
- PriamDataTower = 693, Pippin = 694,
- #endregion Apple specific media, types 690 to 699
-
- #region DEC hard disks, types 700 to 729
- ///
- /// 2382 cylinders, 4 tracks/cylinder, 42 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 204890112 bytes
- ///
- RA60 = 700,
- ///
- /// 546 cylinders, 14 tracks/cylinder, 31 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 121325568 bytes
- ///
- RA80 = 701,
- ///
- /// 1248 cylinders, 14 tracks/cylinder, 51 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 456228864 bytes
- ///
- RA81 = 702,
- ///
- /// 302 cylinders, 4 tracks/cylinder, 42 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector, 25976832
- /// bytes
- ///
- RC25 = 703,
- ///
- /// 615 cylinders, 4 tracks/cylinder, 17 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector, 21411840
- /// bytes
- ///
- RD31 = 704,
- ///
- /// 820 cylinders, 6 tracks/cylinder, 17 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector, 42823680
- /// bytes
- ///
- RD32 = 705,
- ///
- /// 306 cylinders, 4 tracks/cylinder, 17 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector, 10653696
- /// bytes
- ///
- RD51 = 706,
- ///
- /// 480 cylinders, 7 tracks/cylinder, 18 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector, 30965760
- /// bytes
- ///
- RD52 = 707,
- ///
- /// 1024 cylinders, 7 tracks/cylinder, 18 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 75497472 bytes
- ///
- RD53 = 708,
- ///
- /// 1225 cylinders, 8 tracks/cylinder, 18 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 159936000 bytes
- ///
- RD54 = 709,
- ///
- /// 411 cylinders, 3 tracks/cylinder, 22 sectors/track, 256 words/sector, 16 bits/word, 512 bytes/sector, 13888512
- /// bytes
- ///
- RK06 = 710,
- ///
- /// 411 cylinders, 3 tracks/cylinder, 20 sectors/track, 256 words/sector, 18 bits/word, 576 bytes/sector, 14204160
- /// bytes
- ///
- RK06_18 = 711,
- ///
- /// 815 cylinders, 3 tracks/cylinder, 22 sectors/track, 256 words/sector, 16 bits/word, 512 bytes/sector, 27540480
- /// bytes
- ///
- RK07 = 712,
- ///
- /// 815 cylinders, 3 tracks/cylinder, 20 sectors/track, 256 words/sector, 18 bits/word, 576 bytes/sector, 28166400
- /// bytes
- ///
- RK07_18 = 713,
- ///
- /// 823 cylinders, 5 tracks/cylinder, 32 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector, 67420160
- /// bytes
- ///
- RM02 = 714,
- ///
- /// 823 cylinders, 5 tracks/cylinder, 32 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector, 67420160
- /// bytes
- ///
- RM03 = 715,
- ///
- /// 823 cylinders, 19 tracks/cylinder, 32 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 256196608 bytes
- ///
- RM05 = 716,
- ///
- /// 203 cylinders, 10 tracks/cylinder, 22 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 22865920 bytes
- ///
- RP02 = 717,
- ///
- /// 203 cylinders, 10 tracks/cylinder, 20 sectors/track, 128 words/sector, 36 bits/word, 576 bytes/sector,
- /// 23385600 bytes
- ///
- RP02_18 = 718,
- ///
- /// 400 cylinders, 10 tracks/cylinder, 22 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 45056000 bytes
- ///
- RP03 = 719,
- ///
- /// 400 cylinders, 10 tracks/cylinder, 20 sectors/track, 128 words/sector, 36 bits/word, 576 bytes/sector,
- /// 46080000 bytes
- ///
- RP03_18 = 720,
- ///
- /// 411 cylinders, 19 tracks/cylinder, 22 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 87960576 bytes
- ///
- RP04 = 721,
- ///
- /// 411 cylinders, 19 tracks/cylinder, 20 sectors/track, 128 words/sector, 36 bits/word, 576 bytes/sector,
- /// 89959680 bytes
- ///
- RP04_18 = 722,
- ///
- /// 411 cylinders, 19 tracks/cylinder, 22 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 87960576 bytes
- ///
- RP05 = 723,
- ///
- /// 411 cylinders, 19 tracks/cylinder, 20 sectors/track, 128 words/sector, 36 bits/word, 576 bytes/sector,
- /// 89959680 bytes
- ///
- RP05_18 = 724,
- ///
- /// 815 cylinders, 19 tracks/cylinder, 22 sectors/track, 128 words/sector, 32 bits/word, 512 bytes/sector,
- /// 174423040 bytes
- ///
- RP06 = 725,
- ///
- /// 815 cylinders, 19 tracks/cylinder, 20 sectors/track, 128 words/sector, 36 bits/word, 576 bytes/sector,
- /// 178387200 bytes
- ///
- RP06_18 = 726,
- #endregion DEC hard disks, types 700 to 729
-
- #region Imation, types 730 to 739
- LS120 = 730, LS240 = 731, FD32MB = 732,
- RDX = 733,
- /// Imation 320Gb RDX
- RDX320 = 734,
- #endregion Imation, types 730 to 739
-
- #region VideoNow, types 740 to 749
- VideoNow = 740, VideoNowColor = 741, VideoNowXp = 742,
- #endregion
-
- #region Iomega, types 750 to 759
- /// 8"x11" Bernoulli Box disk with 10Mb capacity
- Bernoulli10 = 750,
- /// 8"x11" Bernoulli Box disk with 20Mb capacity
- Bernoulli20 = 751,
- /// 5⅓" Bernoulli Box II disk with 20Mb capacity
- BernoulliBox2_20 = 752,
- #endregion Iomega, types 750 to 759
-
- #region Kodak, types 760 to 769
- KodakVerbatim3 = 760, KodakVerbatim6 = 761, KodakVerbatim12 = 762,
- #endregion Kodak, types 760 to 769
-
- #region Sony and Panasonic Blu-ray derived, types 770 to 799
- /// Professional Disc for video, single layer, rewritable, 23Gb
- ProfessionalDisc = 770,
- /// Professional Disc for video, dual layer, rewritable, 50Gb
- ProfessionalDiscDual = 771,
- /// Professional Disc for video, triple layer, rewritable, 100Gb
- ProfessionalDiscTriple = 772,
- /// Professional Disc for video, quad layer, write once, 128Gb
- ProfessionalDiscQuad = 773,
- /// Professional Disc for DATA, single layer, rewritable, 23Gb
- PDD = 774,
- /// Professional Disc for DATA, single layer, write once, 23Gb
- PDD_WORM = 775,
- /// Archival Disc, 1st gen., 300Gb
- ArchivalDisc = 776,
- /// Archival Disc, 2nd gen., 500Gb
- ArchivalDisc2 = 777,
- /// Archival Disc, 3rd gen., 1Tb
- ArchivalDisc3 = 778,
- /// Optical Disc archive, 1st gen., write once, 300Gb
- ODC300R = 779,
- /// Optical Disc archive, 1st gen., rewritable, 300Gb
- ODC300RE = 780,
- /// Optical Disc archive, 2nd gen., write once, 600Gb
- ODC600R = 781,
- /// Optical Disc archive, 2nd gen., rewritable, 600Gb
- ODC600RE = 782,
- /// Optical Disc archive, 3rd gen., rewritable, 1200Gb
- ODC1200RE = 783,
- /// Optical Disc archive, 3rd gen., write once, 1500Gb
- ODC1500R = 784,
- /// Optical Disc archive, 4th gen., write once, 3300Gb
- ODC3300R = 785,
- /// Optical Disc archive, 5th gen., write once, 5500Gb
- ODC5500R = 786
- #endregion Sony and Panasonic Blu-ray derived, types 770 to 799
- }
-
- ///
- /// Compression being used in CHD
- ///
- public enum CHDCompression : uint
- {
- CHDCOMPRESSION_NONE = 0,
- CHDCOMPRESSION_ZLIB = 1,
- CHDCOMPRESSION_ZLIB_PLUS = 2,
- CHDCOMPRESSION_AV = 3,
- }
-
- ///
- /// Availible CHD codec formats
- ///
- public enum CHD_CODEC : uint
- {
- NONE = 0,
-
- #region General Codecs
-
- ZLIB = 0x7a6c6962, // zlib
- LZMA = 0x6c7a6d61, // lzma
- HUFFMAN = 0x68756666, // huff
- FLAC = 0x666c6163, // flac
-
- #endregion
-
- #region General Codecs with CD Frontend
-
- CD_ZLIB = 0x63647a6c, // cdzl
- CD_LZMA = 0x63646c7a, // cdlz
- CD_FLAC = 0x6364666c, // cdfl
-
- #endregion
-
- #region A/V Codecs
-
- AVHUFF = 0x61766875, // avhu
-
- #endregion
-
- #region Pseudo-Codecs Returned by hunk_info
-
- SELF = 1, // copy of another hunk
- PARENT = 2, // copy of a parent's hunk
- MINI = 3, // legacy "mini" 8-byte repeat
-
- #endregion
- }
-
- ///
- /// Compression method based on flag
- ///
- public enum CompressionMethod : ushort
- {
- Stored = 0,
- Shrunk = 1,
- ReducedCompressionFactor1 = 2,
- ReducedCompressionFactor2 = 3,
- ReducedCompressionFactor3 = 4,
- ReducedCompressionFactor4 = 5,
- Imploded = 6,
- Tokenizing = 7,
- Deflated = 8,
- Delfate64 = 9,
- PKWAREDataCompressionLibrary = 10,
- Type11 = 11, // Reserved and unused (SHOULD NOT BE USED)
- BZIP2 = 12,
- Type13 = 13, // Reserved and unused (SHOULD NOT BE USED)
- LZMA = 14,
- Type15 = 15, // Reserved and unused (SHOULD NOT BE USED)
- Type16 = 16, // Reserved and unused (SHOULD NOT BE USED)
- Type17 = 17, // Reserved and unused (SHOULD NOT BE USED)
- IBMTERSE = 18,
- IBMLZ77 = 19,
- WavPak = 97,
- PPMdVersionIRev1 = 98,
- }
-
- ///
- /// Type of file that is being looked at
- ///
- public enum FileType
- {
- // Singleton
- None = 0,
- AaruFormat,
- CHD,
-
- // Can contain children
- Folder,
- SevenZipArchive,
- GZipArchive,
- LRZipArchive,
- LZ4Archive,
- RarArchive,
- TapeArchive,
- XZArchive,
- ZipArchive,
- ZPAQArchive,
- ZstdArchive,
- }
-
///
/// Output format for rebuilt files
///
diff --git a/SabreTools.Core/Globals.cs b/SabreTools.Core/Globals.cs
index 59c212cb..9206765a 100644
--- a/SabreTools.Core/Globals.cs
+++ b/SabreTools.Core/Globals.cs
@@ -12,11 +12,6 @@ namespace SabreTools.Core
{
#region Public accessors
- ///
- /// Command line arguments passed in to the parent program
- ///
- public static string CommandLineArgs => string.Join(" ", Environment.GetCommandLineArgs());
-
///
/// Directory path for the current executable
///
@@ -43,6 +38,7 @@ namespace SabreTools.Core
///
/// Temporary directory location
///
+ /// TODO: Find a way to get rid of this as a global variable and put it in DatFile
public static string TempDir { get; set; } = Path.GetTempPath();
#endregion
diff --git a/SabreTools.DatFiles/DatFile.DFD.cs b/SabreTools.DatFiles/DatFile.DFD.cs
new file mode 100644
index 00000000..734945a0
--- /dev/null
+++ b/SabreTools.DatFiles/DatFile.DFD.cs
@@ -0,0 +1,421 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.FileTypes;
+using SabreTools.IO;
+
+// This file represents all methods related to creating a DatFile
+// from a set of files and directories
+namespace SabreTools.DatFiles
+{
+ // TODO: See if any of the methods can be broken up a bit more neatly
+ public abstract partial class DatFile
+ {
+ ///
+ /// Create a new Dat from a directory
+ ///
+ /// Base folder to be used in creating the DAT
+ /// TreatAsFiles representing CHD and Archive scanning
+ /// Type of files that should be skipped
+ /// True if blank items should be created for empty folders, false otherwise
+ /// Hashes to include in the information
+ public bool PopulateFromDir(
+ string basePath,
+ TreatAsFile asFiles = 0x00,
+ SkipFileType skipFileType = SkipFileType.None,
+ bool addBlanks = false,
+ Hash hashes = Hash.Standard)
+ {
+ // Clean the temp directory path
+ Globals.TempDir = DirectoryExtensions.Ensure(Globals.TempDir, temp: true);
+
+ // Set the progress variables
+ long totalSize = 0;
+ long currentSize = 0;
+
+ // Process the input
+ if (Directory.Exists(basePath))
+ {
+ logger.Verbose($"Folder found: {basePath}");
+
+ // Get a list of all files to process
+ List files = Directory.EnumerateFiles(basePath, "*", SearchOption.AllDirectories).ToList();
+
+ // Loop through and add the file sizes
+ Parallel.ForEach(files, Globals.ParallelOptions, item =>
+ {
+ Interlocked.Add(ref totalSize, new FileInfo(item).Length);
+ });
+
+ // Process the files in the main folder or any subfolder
+ logger.User(totalSize, currentSize);
+ foreach (string item in files)
+ {
+ CheckFileForHashes(item, basePath, asFiles, skipFileType, addBlanks, hashes);
+ currentSize += new FileInfo(item).Length;
+ logger.User(totalSize, currentSize, item);
+ }
+
+ // Now find all folders that are empty, if we are supposed to
+ if (addBlanks)
+ ProcessDirectoryBlanks(basePath);
+ }
+ else if (File.Exists(basePath))
+ {
+ logger.Verbose($"File found: {basePath}");
+
+ totalSize = new FileInfo(basePath).Length;
+ logger.User(totalSize, currentSize);
+
+ string parentPath = Path.GetDirectoryName(Path.GetDirectoryName(basePath));
+ CheckFileForHashes(basePath, parentPath, asFiles, skipFileType, addBlanks, hashes);
+ logger.User(totalSize, totalSize, basePath);
+ }
+
+ // Now that we're done, delete the temp folder (if it's not the default)
+ logger.User("Cleaning temp folder");
+ if (Globals.TempDir != Path.GetTempPath())
+ {
+ if (Directory.Exists(Globals.TempDir))
+ Directory.Delete(Globals.TempDir, true);
+ }
+
+ return true;
+ }
+
+ ///
+ /// Check a given file for hashes, based on current settings
+ ///
+ /// Filename of the item to be checked
+ /// Base folder to be used in creating the DAT
+ /// TreatAsFiles representing CHD and Archive scanning
+ /// Type of files that should be skipped
+ /// True if blank items should be created for empty folders, false otherwise
+ /// Hashes to include in the information
+ private void CheckFileForHashes(string item, string basePath, TreatAsFile asFiles, SkipFileType skipFileType, bool addBlanks, Hash hashes)
+ {
+ // If we're in depot mode, process it separately
+ if (CheckDepotFile(item))
+ return;
+
+ // Initialize possible archive variables
+ BaseArchive archive = BaseArchive.Create(item);
+
+ // Process archives according to flags
+ if (archive != null)
+ {
+ // Set the archive flags
+ archive.AvailableHashes = hashes;
+
+ // Skip if we're treating archives as files and skipping files
+ if (asFiles.HasFlag(TreatAsFile.Archive) && skipFileType == SkipFileType.File)
+ {
+ return;
+ }
+
+ // Skip if we're skipping archives
+ else if (skipFileType == SkipFileType.Archive)
+ {
+ return;
+ }
+
+ // Process as archive if we're not treating archives as files
+ else if (!asFiles.HasFlag(TreatAsFile.Archive))
+ {
+ var extracted = archive.GetChildren();
+
+ // If we have internal items to process, do so
+ if (extracted != null)
+ ProcessArchive(item, basePath, extracted);
+
+ // Now find all folders that are empty, if we are supposed to
+ if (addBlanks)
+ ProcessArchiveBlanks(item, basePath, archive);
+ }
+
+ // Process as file if we're treating archives as files
+ else
+ {
+ ProcessFile(item, basePath, hashes, asFiles);
+ }
+ }
+
+ // Process non-archives according to flags
+ else
+ {
+ // Skip if we're skipping files
+ if (skipFileType == SkipFileType.File)
+ return;
+
+ // Process as file
+ else
+ ProcessFile(item, basePath, hashes, asFiles);
+ }
+ }
+
+ ///
+ /// Check an item as if it's supposed to be in a depot
+ ///
+ /// Filename of the item to be checked
+ /// True if we checked a depot file, false otherwise
+ private bool CheckDepotFile(string item)
+ {
+ // If we're not in Depot mode, return false
+ if (Header.OutputDepot?.IsActive != true)
+ return false;
+
+ // Check the file as if it were in a depot
+ GZipArchive gzarc = new GZipArchive(item);
+ BaseFile baseFile = gzarc.GetTorrentGZFileInfo();
+
+ // If the rom is valid, add it
+ if (baseFile != null && baseFile.Filename != null)
+ {
+ // Add the list if it doesn't exist already
+ Rom rom = new Rom(baseFile);
+ Items.Add(rom.GetKey(Field.DatItem_CRC), rom);
+ logger.Verbose($"File added: {Path.GetFileNameWithoutExtension(item)}");
+ }
+ else
+ {
+ logger.Verbose($"File not added: {Path.GetFileNameWithoutExtension(item)}");
+ return true;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Process a single file as an archive
+ ///
+ /// File to be added
+ /// Path the represents the parent directory
+ /// List of BaseFiles representing the internal files
+ private void ProcessArchive(string item, string basePath, List extracted)
+ {
+ // Get the parent path for all items
+ string parent = (Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar).Remove(0, basePath.Length) + Path.GetFileNameWithoutExtension(item);
+
+ // First take care of the found items
+ Parallel.ForEach(extracted, Globals.ParallelOptions, baseFile =>
+ {
+ DatItem datItem = DatItem.Create(baseFile);
+ ProcessFileHelper(item, datItem, basePath, parent);
+ });
+ }
+
+ ///
+ /// Process blank folders in an archive
+ ///
+ /// File containing the blanks
+ /// Path the represents the parent directory
+ /// BaseArchive to get blanks from
+ private void ProcessArchiveBlanks(string item, string basePath, BaseArchive archive)
+ {
+ List empties = new List();
+
+ // Get the parent path for all items
+ string parent = (Path.GetDirectoryName(Path.GetFullPath(item)) + Path.DirectorySeparatorChar).Remove(0, basePath.Length) + Path.GetFileNameWithoutExtension(item);
+
+ // Now get all blank folders from the archive
+ if (archive != null)
+ empties = archive.GetEmptyFolders();
+
+ // Add add all of the found empties to the DAT
+ Parallel.ForEach(empties, Globals.ParallelOptions, empty =>
+ {
+ Rom emptyRom = new Rom(Path.Combine(empty, "_"), item);
+ ProcessFileHelper(item, emptyRom, basePath, parent);
+ });
+ }
+
+ ///
+ /// Process blank folders in a directory
+ ///
+ /// Path the represents the parent directory
+ private void ProcessDirectoryBlanks(string basePath)
+ {
+ // If we're in depot mode, we don't process blanks
+ if (Header.OutputDepot?.IsActive == true)
+ return;
+
+ List empties = DirectoryExtensions.ListEmpty(basePath);
+ Parallel.ForEach(empties, Globals.ParallelOptions, dir =>
+ {
+ // Get the full path for the directory
+ string fulldir = Path.GetFullPath(dir);
+
+ // Set the temporary variables
+ string gamename = string.Empty;
+ string romname = string.Empty;
+
+ // If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
+ if (Header.Type == "SuperDAT")
+ {
+ gamename = fulldir.Remove(0, basePath.Length + 1);
+ romname = "_";
+ }
+
+ // Otherwise, we want just the top level folder as the game, and the file as everything else
+ else
+ {
+ gamename = fulldir.Remove(0, basePath.Length + 1).Split(Path.DirectorySeparatorChar)[0];
+ romname = Path.Combine(fulldir.Remove(0, basePath.Length + 1 + gamename.Length), "_");
+ }
+
+ // Sanitize the names
+ gamename = gamename.Trim(Path.DirectorySeparatorChar);
+ romname = romname.Trim(Path.DirectorySeparatorChar);
+
+ logger.Verbose($"Adding blank empty folder: {gamename}");
+ Items["null"].Add(new Rom(romname, gamename));
+ });
+ }
+
+ ///
+ /// Process a single file as a file
+ ///
+ /// File to be added
+ /// Path the represents the parent directory
+ /// Hashes to include in the information
+ /// TreatAsFiles representing CHD and Archive scanning
+ private void ProcessFile(string item, string basePath, Hash hashes, TreatAsFile asFiles)
+ {
+ logger.Verbose($"'{Path.GetFileName(item)}' treated like a file");
+ BaseFile baseFile = BaseFile.GetInfo(item, header: Header.HeaderSkipper, hashes: hashes, asFiles: asFiles);
+ DatItem datItem = DatItem.Create(baseFile);
+ ProcessFileHelper(item, datItem, basePath, string.Empty);
+ }
+
+ ///
+ /// Process a single file as a file (with found Rom data)
+ ///
+ /// File to be added
+ /// Rom data to be used to write to file
+ /// Path the represents the parent directory
+ /// Parent game to be used
+ private void ProcessFileHelper(string item, DatItem datItem, string basepath, string parent)
+ {
+ // If we didn't get an accepted parsed type somehow, cancel out
+ List parsed = new List { ItemType.Disk, ItemType.Media, ItemType.Rom };
+ if (!parsed.Contains(datItem.ItemType))
+ return;
+
+ try
+ {
+ // If the basepath doesn't end with a directory separator, add it
+ if (!basepath.EndsWith(Path.DirectorySeparatorChar.ToString()))
+ basepath += Path.DirectorySeparatorChar.ToString();
+
+ // Make sure we have the full item path
+ item = Path.GetFullPath(item);
+
+ // Process the item to sanitize names based on input
+ SetDatItemInfo(datItem, item, parent, basepath);
+
+ // Add the file information to the DAT
+ string key = datItem.GetKey(Field.DatItem_CRC);
+ Items.Add(key, datItem);
+
+ logger.Verbose($"File added: {datItem.GetName() ?? string.Empty}");
+ }
+ catch (IOException ex)
+ {
+ logger.Error(ex);
+ return;
+ }
+ }
+
+ ///
+ /// Set proper Game and Rom names from user inputs
+ ///
+ /// DatItem representing the input file
+ /// Item name to use
+ /// Parent name to use
+ /// Base path to use
+ private void SetDatItemInfo(DatItem datItem, string item, string parent, string basepath)
+ {
+ // Get the data to be added as game and item names
+ string machineName, itemName;
+
+ // If the parent is blank, then we have a non-archive file
+ if (string.IsNullOrWhiteSpace(parent))
+ {
+ // If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
+ if (Header.Type == "SuperDAT")
+ {
+ machineName = Path.GetDirectoryName(item.Remove(0, basepath.Length));
+ itemName = Path.GetFileName(item);
+ }
+
+ // Otherwise, we want just the top level folder as the game, and the file as everything else
+ else
+ {
+ machineName = item.Remove(0, basepath.Length).Split(Path.DirectorySeparatorChar)[0];
+ itemName = item.Remove(0, (Path.Combine(basepath, machineName).Length));
+ }
+ }
+
+ // Otherwise, we assume that we have an archive
+ else
+ {
+ // If we have a SuperDAT, we want the archive name as the game, and the file as everything else (?)
+ if (Header.Type == "SuperDAT")
+ {
+ machineName = parent;
+ itemName = datItem.GetName();
+ }
+
+ // Otherwise, we want the archive name as the game, and the file as everything else
+ else
+ {
+ machineName = parent;
+ itemName = datItem.GetName();
+ }
+ }
+
+ // Sanitize the names
+ machineName = machineName.Trim(Path.DirectorySeparatorChar);
+ itemName = itemName?.Trim(Path.DirectorySeparatorChar) ?? string.Empty;
+
+ if (!string.IsNullOrWhiteSpace(machineName) && string.IsNullOrWhiteSpace(itemName))
+ {
+ itemName = machineName;
+ machineName = "Default";
+ }
+
+ // Update machine information
+ datItem.Machine.Name = machineName;
+ datItem.Machine.Description = machineName;
+
+ // If we have a Disk, then the ".chd" extension needs to be removed
+ if (datItem.ItemType == ItemType.Disk && itemName.EndsWith(".chd"))
+ {
+ itemName = itemName.Substring(0, itemName.Length - 4);
+ }
+
+ // If we have a Media, then the extension needs to be removed
+ else if (datItem.ItemType == ItemType.Media)
+ {
+ if (itemName.EndsWith(".dicf"))
+ itemName = itemName.Substring(0, itemName.Length - 5);
+ else if (itemName.EndsWith(".aaru"))
+ itemName = itemName.Substring(0, itemName.Length - 5);
+ else if (itemName.EndsWith(".aaruformat"))
+ itemName = itemName.Substring(0, itemName.Length - 11);
+ else if (itemName.EndsWith(".aaruf"))
+ itemName = itemName.Substring(0, itemName.Length - 6);
+ else if (itemName.EndsWith(".aif"))
+ itemName = itemName.Substring(0, itemName.Length - 4);
+ }
+
+ // Set the item name back
+ datItem.SetFields(new Dictionary { [Field.DatItem_Name] = itemName });
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.DatFiles/DatFile.Filtering.cs b/SabreTools.DatFiles/DatFile.Filtering.cs
new file mode 100644
index 00000000..26832339
--- /dev/null
+++ b/SabreTools.DatFiles/DatFile.Filtering.cs
@@ -0,0 +1,1108 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.Filtering;
+using SabreTools.IO;
+
+// This file represents all methods related to the Filtering namespace
+namespace SabreTools.DatFiles
+{
+ public abstract partial class DatFile
+ {
+ ///
+ /// Apply cleaning methods to the DatFile
+ ///
+ /// Cleaner to use
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ /// True if cleaning was successful, false on error
+ public bool ApplyCleaning(Cleaner cleaner, bool throwOnError = false)
+ {
+ try
+ {
+ // Perform item-level cleaning
+ CleanDatItems(cleaner);
+
+ // Bucket and dedupe according to the flag
+ if (cleaner?.DedupeRoms == DedupeType.Full)
+ Items.BucketBy(Field.DatItem_CRC, cleaner.DedupeRoms);
+ else if (cleaner?.DedupeRoms == DedupeType.Game)
+ Items.BucketBy(Field.Machine_Name, cleaner.DedupeRoms);
+
+ // Process description to machine name
+ if (cleaner?.DescriptionAsName == true)
+ MachineDescriptionToName();
+
+ // If we are removing scene dates, do that now
+ if (cleaner?.SceneDateStrip == true)
+ StripSceneDatesFromItems();
+
+ // Run the one rom per game logic, if required
+ if (cleaner?.OneGamePerRegion == true)
+ OneGamePerRegion(cleaner.RegionList);
+
+ // Run the one rom per game logic, if required
+ if (cleaner?.OneRomPerGame == true)
+ OneRomPerGame();
+
+ // If we are removing fields, do that now
+ if (cleaner.ExcludeFields != null && cleaner.ExcludeFields.Any())
+ RemoveFieldsFromItems(cleaner.ExcludeFields);
+
+ // Remove all marked items
+ Items.ClearMarked();
+
+ // We remove any blanks, if we aren't supposed to have any
+ if (cleaner?.KeepEmptyGames == false)
+ Items.ClearEmpty();
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex);
+ if (throwOnError) throw ex;
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Apply a set of Extra INIs on the DatFile
+ ///
+ /// ExtrasIni to use
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ /// True if the extras were applied, false on error
+ public bool ApplyExtras(ExtraIni extras, bool throwOnError = false)
+ {
+ try
+ {
+ // Bucket by game first
+ Items.BucketBy(Field.Machine_Name, DedupeType.None);
+
+ // Create a new set of mappings based on the items
+ var map = new Dictionary>();
+
+ // Loop through each of the extras
+ foreach (ExtraIniItem item in extras.Items)
+ {
+ foreach (var mapping in item.Mappings)
+ {
+ string key = mapping.Key;
+ List machineNames = mapping.Value;
+
+ // Loop through the machines and add the new mappings
+ foreach (string machine in machineNames)
+ {
+ if (!map.ContainsKey(machine))
+ map[machine] = new Dictionary();
+
+ map[machine][item.Field] = key;
+ }
+ }
+ }
+
+ // Now apply the new set of mappings
+ foreach (string key in map.Keys)
+ {
+ // If the key doesn't exist, continue
+ if (!Items.ContainsKey(key))
+ continue;
+
+ List datItems = Items[key];
+ var mappings = map[key];
+
+ foreach (var datItem in datItems)
+ {
+ datItem.SetFields(mappings);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex);
+ if (throwOnError) throw ex;
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Apply a Filter on the DatFile
+ ///
+ /// Filter to use
+ /// True if entire machines are considered, false otherwise (default)
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ /// True if the DatFile was filtered, false on error
+ public bool ApplyFilter(Filter filter, bool perMachine = false, bool throwOnError = false)
+ {
+ // If we have a null filter, return false
+ if (filter == null)
+ return false;
+
+ // If we're filtering per machine, bucket by machine first
+ if (perMachine)
+ Items.BucketBy(Field.Machine_Name, DedupeType.None);
+
+ try
+ {
+ // Loop over every key in the dictionary
+ List keys = Items.Keys.ToList();
+ foreach (string key in keys)
+ {
+ // For every item in the current key
+ bool machinePass = true;
+ List items = Items[key];
+ foreach (DatItem item in items)
+ {
+ // If we have a null item, we can't pass it
+ if (item == null)
+ continue;
+
+ // If the item is already filtered out, we skip
+ if (item.Remove)
+ continue;
+
+ // If the rom doesn't pass the filter, mark for removal
+ if (!item.PassesFilter(filter))
+ {
+ item.Remove = true;
+
+ // If we're in machine mode, set and break
+ if (perMachine)
+ {
+ machinePass = false;
+ break;
+ }
+ }
+ }
+
+ // If we didn't pass and we're in machine mode, set all items as remove
+ if (perMachine && !machinePass)
+ {
+ foreach (DatItem item in items)
+ {
+ item.Remove = true;
+ }
+ }
+
+ // Assign back for caution
+ Items[key] = items;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex);
+ if (throwOnError) throw ex;
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Apply splitting on the DatFile
+ ///
+ /// Split type to try
+ /// True if DatFile tags override splitting, false otherwise
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ /// True if the DatFile was split, false on error
+ public bool ApplySplitting(MergingFlag splitType, bool useTags, bool throwOnError = false)
+ {
+ try
+ {
+ // If we are using tags from the DAT, set the proper input for split type unless overridden
+ if (useTags && splitType == MergingFlag.None)
+ splitType = Header.ForceMerging;
+
+ // Run internal splitting
+ switch (splitType)
+ {
+ case MergingFlag.None:
+ // No-op
+ break;
+ case MergingFlag.Device:
+ CreateDeviceNonMergedSets(DedupeType.None);
+ break;
+ case MergingFlag.Full:
+ CreateFullyNonMergedSets(DedupeType.None);
+ break;
+ case MergingFlag.NonMerged:
+ CreateNonMergedSets(DedupeType.None);
+ break;
+ case MergingFlag.Merged:
+ CreateMergedSets(DedupeType.None);
+ break;
+ case MergingFlag.Split:
+ CreateSplitSets(DedupeType.None);
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex);
+ if (throwOnError) throw ex;
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Apply SuperDAT naming logic to a merged DatFile
+ ///
+ /// List of inputs to use for renaming
+ public void ApplySuperDAT(List inputs)
+ {
+ List keys = Items.Keys.ToList();
+ Parallel.ForEach(keys, Globals.ParallelOptions, key =>
+ {
+ List items = Items[key].ToList();
+ List newItems = new List();
+ foreach (DatItem item in items)
+ {
+ DatItem newItem = item;
+ string filename = inputs[newItem.Source.Index].CurrentPath;
+ string rootpath = inputs[newItem.Source.Index].ParentPath;
+
+ if (!string.IsNullOrWhiteSpace(rootpath))
+ rootpath += Path.DirectorySeparatorChar.ToString();
+
+ filename = filename.Remove(0, rootpath.Length);
+ newItem.Machine.Name = Path.GetDirectoryName(filename) + Path.DirectorySeparatorChar
+ + Path.GetFileNameWithoutExtension(filename) + Path.DirectorySeparatorChar
+ + newItem.Machine.Name;
+
+ newItems.Add(newItem);
+ }
+
+ Items.Remove(key);
+ Items.AddRange(key, newItems);
+ });
+ }
+
+ ///
+ /// Clean individual items based on the current filter
+ ///
+ /// Cleaner to use
+ public void CleanDatItems(Cleaner cleaner)
+ {
+ List keys = Items.Keys.ToList();
+ foreach (string key in keys)
+ {
+ // For every item in the current key
+ List items = Items[key];
+ foreach (DatItem item in items)
+ {
+ // If we have a null item, we can't clean it it
+ if (item == null)
+ continue;
+
+ // Run cleaning per item
+ item.Clean(cleaner);
+ }
+
+ // Assign back for caution
+ Items[key] = items;
+ }
+ }
+
+ ///
+ /// Use game descriptions as names in the DAT, updating cloneof/romof/sampleof
+ ///
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ public void MachineDescriptionToName(bool throwOnError = false)
+ {
+ try
+ {
+ // First we want to get a mapping for all games to description
+ ConcurrentDictionary mapping = new ConcurrentDictionary();
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+ {
+ List items = Items[key];
+ foreach (DatItem item in items)
+ {
+ // If the key mapping doesn't exist, add it
+ mapping.TryAdd(item.Machine.Name, item.Machine.Description.Replace('/', '_').Replace("\"", "''").Replace(":", " -"));
+ }
+ });
+
+ // Now we loop through every item and update accordingly
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+ {
+ List items = Items[key];
+ List newItems = new List();
+ foreach (DatItem item in items)
+ {
+ // Update machine name
+ if (!string.IsNullOrWhiteSpace(item.Machine.Name) && mapping.ContainsKey(item.Machine.Name))
+ item.Machine.Name = mapping[item.Machine.Name];
+
+ // Update cloneof
+ if (!string.IsNullOrWhiteSpace(item.Machine.CloneOf) && mapping.ContainsKey(item.Machine.CloneOf))
+ item.Machine.CloneOf = mapping[item.Machine.CloneOf];
+
+ // Update romof
+ if (!string.IsNullOrWhiteSpace(item.Machine.RomOf) && mapping.ContainsKey(item.Machine.RomOf))
+ item.Machine.RomOf = mapping[item.Machine.RomOf];
+
+ // Update sampleof
+ if (!string.IsNullOrWhiteSpace(item.Machine.SampleOf) && mapping.ContainsKey(item.Machine.SampleOf))
+ item.Machine.SampleOf = mapping[item.Machine.SampleOf];
+
+ // Add the new item to the output list
+ newItems.Add(item);
+ }
+
+ // Replace the old list of roms with the new one
+ Items.Remove(key);
+ Items.AddRange(key, newItems);
+ });
+ }
+ catch (Exception ex)
+ {
+ logger.Warning(ex.ToString());
+ if (throwOnError) throw ex;
+ }
+ }
+
+ ///
+ /// Filter a DAT using 1G1R logic given an ordered set of regions
+ ///
+ /// Ordered list of regions to use
+ ///
+ /// In the most technical sense, the way that the region list is being used does not
+ /// confine its values to be just regions. Since it's essentially acting like a
+ /// specialized version of the machine name filter, anything that is usually encapsulated
+ /// in parenthesis would be matched on, including disc numbers, languages, editions,
+ /// and anything else commonly used. Please note that, unlike other existing 1G1R
+ /// solutions, this does not have the ability to contain custom mappings of parent
+ /// to clone sets based on name, nor does it have the ability to match on the
+ /// Release DatItem type.
+ ///
+ public void OneGamePerRegion(List regions)
+ {
+ // If we have null region list, make it empty
+ if (regions == null)
+ regions = new List();
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ Items.BucketBy(Field.Machine_Name, DedupeType.None, norename: true);
+
+ // Then we want to get a mapping of all machines to parents
+ Dictionary> parents = new Dictionary>();
+ foreach (string key in Items.Keys)
+ {
+ DatItem item = Items[key][0];
+
+ // Match on CloneOf first
+ if (!string.IsNullOrEmpty(item.Machine.CloneOf))
+ {
+ if (!parents.ContainsKey(item.Machine.CloneOf.ToLowerInvariant()))
+ parents.Add(item.Machine.CloneOf.ToLowerInvariant(), new List());
+
+ parents[item.Machine.CloneOf.ToLowerInvariant()].Add(item.Machine.Name.ToLowerInvariant());
+ }
+
+ // Then by RomOf
+ else if (!string.IsNullOrEmpty(item.Machine.RomOf))
+ {
+ if (!parents.ContainsKey(item.Machine.RomOf.ToLowerInvariant()))
+ parents.Add(item.Machine.RomOf.ToLowerInvariant(), new List());
+
+ parents[item.Machine.RomOf.ToLowerInvariant()].Add(item.Machine.Name.ToLowerInvariant());
+ }
+
+ // Otherwise, treat it as a parent
+ else
+ {
+ if (!parents.ContainsKey(item.Machine.Name.ToLowerInvariant()))
+ parents.Add(item.Machine.Name.ToLowerInvariant(), new List());
+
+ parents[item.Machine.Name.ToLowerInvariant()].Add(item.Machine.Name.ToLowerInvariant());
+ }
+ }
+
+ // Once we have the full list of mappings, filter out games to keep
+ foreach (string key in parents.Keys)
+ {
+ // Find the first machine that matches the regions in order, if possible
+ string machine = default;
+ foreach (string region in regions)
+ {
+ machine = parents[key].FirstOrDefault(m => Regex.IsMatch(m, @"\(.*" + region + @".*\)", RegexOptions.IgnoreCase));
+ if (machine != default)
+ break;
+ }
+
+ // If we didn't get a match, use the parent
+ if (machine == default)
+ machine = key;
+
+ // Remove the key from the list
+ parents[key].Remove(machine);
+
+ // Remove the rest of the items from this key
+ parents[key].ForEach(k => Items.Remove(k));
+ }
+
+ // Finally, strip out the parent tags
+ RemoveTagsFromChild();
+ }
+
+ ///
+ /// Ensure that all roms are in their own game (or at least try to ensure)
+ ///
+ public void OneRomPerGame()
+ {
+ // Because this introduces subfolders, we need to set the SuperDAT type
+ Header.Type = "SuperDAT";
+
+ // For each rom, we want to update the game to be "/"
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+ {
+ List items = Items[key];
+ for (int i = 0; i < items.Count; i++)
+ {
+ items[i].SetOneRomPerGame();
+ }
+ });
+ }
+
+ ///
+ /// Remove fields as per the header
+ ///
+ /// List of fields to use
+ public void RemoveFieldsFromItems(List fields)
+ {
+ // If we have null field list, make it empty
+ if (fields == null)
+ fields = new List();
+
+ // Output the logging statement
+ logger.User("Removing filtered fields");
+
+ // Now process all of the roms
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+ {
+ List items = Items[key];
+ for (int j = 0; j < items.Count; j++)
+ {
+ items[j].RemoveFields(fields);
+ }
+
+ Items.Remove(key);
+ Items.AddRange(key, items);
+ });
+ }
+
+ ///
+ /// Strip the dates from the beginning of scene-style set names
+ ///
+ public void StripSceneDatesFromItems()
+ {
+ // Output the logging statement
+ logger.User("Stripping scene-style dates");
+
+ // Set the regex pattern to use
+ string pattern = @"([0-9]{2}\.[0-9]{2}\.[0-9]{2}-)(.*?-.*?)";
+
+ // Now process all of the roms
+ Parallel.ForEach(Items.Keys, Globals.ParallelOptions, key =>
+ {
+ List items = Items[key];
+ for (int j = 0; j < items.Count; j++)
+ {
+ DatItem item = items[j];
+ if (Regex.IsMatch(item.Machine.Name, pattern))
+ item.Machine.Name = Regex.Replace(item.Machine.Name, pattern, "$2");
+
+ if (Regex.IsMatch(item.Machine.Description, pattern))
+ item.Machine.Description = Regex.Replace(item.Machine.Description, pattern, "$2");
+
+ items[j] = item;
+ }
+
+ Items.Remove(key);
+ Items.AddRange(key, items);
+ });
+ }
+
+ // TODO: Should any of these create a new DatFile in the process?
+ // The reason this comes up is that doing any of the splits or merges
+ // is an inherently destructive process. Making it output a new DatFile
+ // might make it easier to deal with multiple internal steps. On the other
+ // hand, this will increase memory usage significantly and would force the
+ // existing paths to behave entirely differently
+ #region Internal Splitting/Merging
+
+ ///
+ /// Use cdevice_ref tags to get full non-merged sets and remove parenting tags
+ ///
+ /// Dedupe type to be used
+ private void CreateDeviceNonMergedSets(DedupeType mergeroms)
+ {
+ logger.User("Creating device non-merged sets from the DAT");
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ Items.BucketBy(Field.Machine_Name, mergeroms, norename: true);
+
+ // Now we want to loop through all of the games and set the correct information
+ while (AddRomsFromDevices(false, false)) ;
+ while (AddRomsFromDevices(true, false)) ;
+
+ // Then, remove the romof and cloneof tags so it's not picked up by the manager
+ RemoveTagsFromChild();
+ }
+
+ ///
+ /// Use cloneof tags to create non-merged sets and remove the tags plus using the device_ref tags to get full sets
+ ///
+ /// Dedupe type to be used
+ private void CreateFullyNonMergedSets(DedupeType mergeroms)
+ {
+ logger.User("Creating fully non-merged sets from the DAT");
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ Items.BucketBy(Field.Machine_Name, mergeroms, norename: true);
+
+ // Now we want to loop through all of the games and set the correct information
+ while (AddRomsFromDevices(true, true)) ;
+ AddRomsFromDevices(false, true);
+ AddRomsFromParent();
+
+ // Now that we have looped through the cloneof tags, we loop through the romof tags
+ AddRomsFromBios();
+
+ // Then, remove the romof and cloneof tags so it's not picked up by the manager
+ RemoveTagsFromChild();
+ }
+
+ ///
+ /// Use cloneof tags to create merged sets and remove the tags
+ ///
+ /// Dedupe type to be used
+ private void CreateMergedSets(DedupeType mergeroms)
+ {
+ logger.User("Creating merged sets from the DAT");
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ Items.BucketBy(Field.Machine_Name, mergeroms, norename: true);
+
+ // Now we want to loop through all of the games and set the correct information
+ AddRomsFromChildren();
+
+ // Now that we have looped through the cloneof tags, we loop through the romof tags
+ RemoveBiosRomsFromChild(false);
+ RemoveBiosRomsFromChild(true);
+
+ // Finally, remove the romof and cloneof tags so it's not picked up by the manager
+ RemoveTagsFromChild();
+ }
+
+ ///
+ /// Use cloneof tags to create non-merged sets and remove the tags
+ ///
+ /// Dedupe type to be used
+ private void CreateNonMergedSets(DedupeType mergeroms)
+ {
+ logger.User("Creating non-merged sets from the DAT");
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ Items.BucketBy(Field.Machine_Name, mergeroms, norename: true);
+
+ // Now we want to loop through all of the games and set the correct information
+ AddRomsFromParent();
+
+ // Now that we have looped through the cloneof tags, we loop through the romof tags
+ RemoveBiosRomsFromChild(false);
+ RemoveBiosRomsFromChild(true);
+
+ // Finally, remove the romof and cloneof tags so it's not picked up by the manager
+ RemoveTagsFromChild();
+ }
+
+ ///
+ /// Use cloneof and romof tags to create split sets and remove the tags
+ ///
+ /// Dedupe type to be used
+ private void CreateSplitSets(DedupeType mergeroms)
+ {
+ logger.User("Creating split sets from the DAT");
+
+ // For sake of ease, the first thing we want to do is bucket by game
+ Items.BucketBy(Field.Machine_Name, mergeroms, norename: true);
+
+ // Now we want to loop through all of the games and set the correct information
+ RemoveRomsFromChild();
+
+ // Now that we have looped through the cloneof tags, we loop through the romof tags
+ RemoveBiosRomsFromChild(false);
+ RemoveBiosRomsFromChild(true);
+
+ // Finally, remove the romof and cloneof tags so it's not picked up by the manager
+ RemoveTagsFromChild();
+ }
+
+ ///
+ /// Use romof tags to add roms to the children
+ ///
+ private void AddRomsFromBios()
+ {
+ List games = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string game in games)
+ {
+ // If the game has no items in it, we want to continue
+ if (Items[game].Count == 0)
+ continue;
+
+ // Determine if the game has a parent or not
+ string parent = null;
+ if (!string.IsNullOrWhiteSpace(Items[game][0].Machine.RomOf))
+ parent = Items[game][0].Machine.RomOf;
+
+ // If the parent doesnt exist, we want to continue
+ if (string.IsNullOrWhiteSpace(parent))
+ continue;
+
+ // If the parent doesn't have any items, we want to continue
+ if (Items[parent].Count == 0)
+ continue;
+
+ // If the parent exists and has items, we copy the items from the parent to the current game
+ DatItem copyFrom = Items[game][0];
+ List parentItems = Items[parent];
+ foreach (DatItem item in parentItems)
+ {
+ DatItem datItem = (DatItem)item.Clone();
+ datItem.CopyMachineInformation(copyFrom);
+ if (Items[game].Where(i => i.GetName() == datItem.GetName()).Count() == 0 && !Items[game].Contains(datItem))
+ Items.Add(game, datItem);
+ }
+ }
+ }
+
+ ///
+ /// Use device_ref and optionally slotoption tags to add roms to the children
+ ///
+ /// True if only child device sets are touched, false for non-device sets (default)
+ /// True if slotoptions tags are used as well, false otherwise
+ private bool AddRomsFromDevices(bool dev = false, bool useSlotOptions = false)
+ {
+ bool foundnew = false;
+ List machines = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string machine in machines)
+ {
+ // If the machine doesn't have items, we continue
+ if (Items[machine] == null || Items[machine].Count == 0)
+ continue;
+
+ // If the machine (is/is not) a device, we want to continue
+ if (dev ^ (Items[machine][0].Machine.MachineType.HasFlag(MachineType.Device)))
+ continue;
+
+ // Get all device reference names from the current machine
+ List deviceReferences = Items[machine]
+ .Where(i => i.ItemType == ItemType.DeviceReference)
+ .Select(i => i as DeviceReference)
+ .Select(dr => dr.Name)
+ .Distinct()
+ .ToList();
+
+ // Get all slot option names from the current machine
+ List slotOptions = Items[machine]
+ .Where(i => i.ItemType == ItemType.Slot)
+ .Select(i => i as Slot)
+ .Where(s => s.SlotOptionsSpecified)
+ .SelectMany(s => s.SlotOptions)
+ .Select(so => so.DeviceName)
+ .Distinct()
+ .ToList();
+
+ // If we're checking device references
+ if (deviceReferences.Any())
+ {
+ // Loop through all names and check the corresponding machines
+ List newDeviceReferences = new List();
+ foreach (string deviceReference in deviceReferences)
+ {
+ // If the machine doesn't exist then we continue
+ if (Items[deviceReference] == null || Items[deviceReference].Count == 0)
+ continue;
+
+ // Add to the list of new device reference names
+ List devItems = Items[deviceReference];
+ newDeviceReferences.AddRange(devItems
+ .Where(i => i.ItemType == ItemType.DeviceReference)
+ .Select(i => (i as DeviceReference).Name));
+
+ // Set new machine information and add to the current machine
+ DatItem copyFrom = Items[machine][0];
+ foreach (DatItem item in devItems)
+ {
+ // If the parent machine doesn't already contain this item, add it
+ if (!Items[machine].Any(i => i.ItemType == item.ItemType && i.GetName() == item.GetName()))
+ {
+ // Set that we found new items
+ foundnew = true;
+
+ // Clone the item and then add it
+ DatItem datItem = (DatItem)item.Clone();
+ datItem.CopyMachineInformation(copyFrom);
+ Items.Add(machine, datItem);
+ }
+ }
+ }
+
+ // Now that every device reference is accounted for, add the new list of device references, if they don't already exist
+ foreach (string deviceReference in newDeviceReferences.Distinct())
+ {
+ if (!deviceReferences.Contains(deviceReference))
+ Items[machine].Add(new DeviceReference() { Name = deviceReference });
+ }
+ }
+
+ // If we're checking slotoptions
+ if (useSlotOptions && slotOptions.Any())
+ {
+ // Loop through all names and check the corresponding machines
+ List newSlotOptions = new List();
+ foreach (string slotOption in slotOptions)
+ {
+ // If the machine doesn't exist then we continue
+ if (Items[slotOption] == null || Items[slotOption].Count == 0)
+ continue;
+
+ // Add to the list of new slot option names
+ List slotItems = Items[slotOption];
+ newSlotOptions.AddRange(slotItems
+ .Where(i => i.ItemType == ItemType.Slot)
+ .Where(s => (s as Slot).SlotOptionsSpecified)
+ .SelectMany(s => (s as Slot).SlotOptions)
+ .Select(o => o.DeviceName));
+
+ // Set new machine information and add to the current machine
+ DatItem copyFrom = Items[machine][0];
+ foreach (DatItem item in slotItems)
+ {
+ // If the parent machine doesn't already contain this item, add it
+ if (!Items[machine].Any(i => i.ItemType == item.ItemType && i.GetName() == item.GetName()))
+ {
+ // Set that we found new items
+ foundnew = true;
+
+ // Clone the item and then add it
+ DatItem datItem = (DatItem)item.Clone();
+ datItem.CopyMachineInformation(copyFrom);
+ Items.Add(machine, datItem);
+ }
+ }
+ }
+
+ // Now that every device is accounted for, add the new list of slot options, if they don't already exist
+ foreach (string slotOption in newSlotOptions.Distinct())
+ {
+ if (!slotOptions.Contains(slotOption))
+ Items[machine].Add(new Slot() { SlotOptions = new List { new SlotOption { DeviceName = slotOption } } });
+ }
+ }
+ }
+
+ return foundnew;
+ }
+
+ ///
+ /// Use cloneof tags to add roms to the children, setting the new romof tag in the process
+ ///
+ private void AddRomsFromParent()
+ {
+ List games = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string game in games)
+ {
+ // If the game has no items in it, we want to continue
+ if (Items[game].Count == 0)
+ continue;
+
+ // Determine if the game has a parent or not
+ string parent = null;
+ if (!string.IsNullOrWhiteSpace(Items[game][0].Machine.CloneOf))
+ parent = Items[game][0].Machine.CloneOf;
+
+ // If the parent doesnt exist, we want to continue
+ if (string.IsNullOrWhiteSpace(parent))
+ continue;
+
+ // If the parent doesn't have any items, we want to continue
+ if (Items[parent].Count == 0)
+ continue;
+
+ // If the parent exists and has items, we copy the items from the parent to the current game
+ DatItem copyFrom = Items[game][0];
+ List parentItems = Items[parent];
+ foreach (DatItem item in parentItems)
+ {
+ DatItem datItem = (DatItem)item.Clone();
+ datItem.CopyMachineInformation(copyFrom);
+ if (Items[game].Where(i => i.GetName()?.ToLowerInvariant() == datItem.GetName()?.ToLowerInvariant()).Count() == 0
+ && !Items[game].Contains(datItem))
+ {
+ Items.Add(game, datItem);
+ }
+ }
+
+ // Now we want to get the parent romof tag and put it in each of the items
+ List items = Items[game];
+ string romof = Items[parent][0].Machine.RomOf;
+ foreach (DatItem item in items)
+ {
+ item.Machine.RomOf = romof;
+ }
+ }
+ }
+
+ ///
+ /// Use cloneof tags to add roms to the parents, removing the child sets in the process
+ ///
+ /// True to add DatItems to subfolder of parent (not including Disk), false otherwise
+ private void AddRomsFromChildren(bool subfolder = true)
+ {
+ List games = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string game in games)
+ {
+ // If the game has no items in it, we want to continue
+ if (Items[game].Count == 0)
+ continue;
+
+ // Determine if the game has a parent or not
+ string parent = null;
+ if (!string.IsNullOrWhiteSpace(Items[game][0].Machine.CloneOf))
+ parent = Items[game][0].Machine.CloneOf;
+
+ // If there is no parent, then we continue
+ if (string.IsNullOrWhiteSpace(parent))
+ continue;
+
+ // Otherwise, move the items from the current game to a subfolder of the parent game
+ DatItem copyFrom;
+ if (Items[parent].Count == 0)
+ {
+ copyFrom = new Rom();
+ copyFrom.Machine.Name = parent;
+ copyFrom.Machine.Description = parent;
+ }
+ else
+ {
+ copyFrom = Items[parent][0];
+ }
+
+ List items = Items[game];
+ foreach (DatItem item in items)
+ {
+ // Special disk handling
+ if (item.ItemType == ItemType.Disk)
+ {
+ Disk disk = item as Disk;
+
+ // If the merge tag exists and the parent already contains it, skip
+ if (disk.MergeTag != null && Items[parent].Where(i => i.ItemType == ItemType.Disk).Select(i => (i as Disk).Name).Contains(disk.MergeTag))
+ {
+ continue;
+ }
+
+ // If the merge tag exists but the parent doesn't contain it, add to parent
+ else if (disk.MergeTag != null && !Items[parent].Where(i => i.ItemType == ItemType.Disk).Select(i => (i as Disk).Name).Contains(disk.MergeTag))
+ {
+ disk.CopyMachineInformation(copyFrom);
+ Items.Add(parent, disk);
+ }
+
+ // If there is no merge tag, add to parent
+ else if (disk.MergeTag == null)
+ {
+ disk.CopyMachineInformation(copyFrom);
+ Items.Add(parent, disk);
+ }
+ }
+
+ // Special rom handling
+ else if (item.ItemType == ItemType.Rom)
+ {
+ Rom rom = item as Rom;
+
+ // If the merge tag exists and the parent already contains it, skip
+ if (rom.MergeTag != null && Items[parent].Where(i => i.ItemType == ItemType.Rom).Select(i => (i as Rom).Name).Contains(rom.MergeTag))
+ {
+ continue;
+ }
+
+ // If the merge tag exists but the parent doesn't contain it, add to subfolder of parent
+ else if (rom.MergeTag != null && !Items[parent].Where(i => i.ItemType == ItemType.Rom).Select(i => (i as Rom).Name).Contains(rom.MergeTag))
+ {
+ if (subfolder)
+ rom.Name = $"{rom.Machine.Name}\\{rom.Name}";
+
+ rom.CopyMachineInformation(copyFrom);
+ Items.Add(parent, rom);
+ }
+
+ // If the parent doesn't already contain this item, add to subfolder of parent
+ else if (!Items[parent].Contains(item))
+ {
+ if (subfolder)
+ rom.Name = $"{item.Machine.Name}\\{rom.Name}";
+
+ rom.CopyMachineInformation(copyFrom);
+ Items.Add(parent, rom);
+ }
+ }
+
+ // All other that would be missing to subfolder of parent
+ else if (!Items[parent].Contains(item))
+ {
+ if (subfolder)
+ item.SetFields(new Dictionary { [Field.DatItem_Name] = $"{item.Machine.Name}\\{item.GetName()}" });
+
+ item.CopyMachineInformation(copyFrom);
+ Items.Add(parent, item);
+ }
+ }
+
+ // Then, remove the old game so it's not picked up by the writer
+ Items.Remove(game);
+ }
+ }
+
+ ///
+ /// Remove all BIOS and device sets
+ ///
+ private void RemoveBiosAndDeviceSets()
+ {
+ List games = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string game in games)
+ {
+ if (Items[game].Count > 0
+ && (Items[game][0].Machine.MachineType.HasFlag(MachineType.Bios)
+ || Items[game][0].Machine.MachineType.HasFlag(MachineType.Device)))
+ {
+ Items.Remove(game);
+ }
+ }
+ }
+
+ ///
+ /// Use romof tags to remove bios roms from children
+ ///
+ /// True if only child Bios sets are touched, false for non-bios sets (default)
+ private void RemoveBiosRomsFromChild(bool bios = false)
+ {
+ // Loop through the romof tags
+ List games = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string game in games)
+ {
+ // If the game has no items in it, we want to continue
+ if (Items[game].Count == 0)
+ continue;
+
+ // If the game (is/is not) a bios, we want to continue
+ if (bios ^ Items[game][0].Machine.MachineType.HasFlag(MachineType.Bios))
+ continue;
+
+ // Determine if the game has a parent or not
+ string parent = null;
+ if (!string.IsNullOrWhiteSpace(Items[game][0].Machine.RomOf))
+ parent = Items[game][0].Machine.RomOf;
+
+ // If the parent doesnt exist, we want to continue
+ if (string.IsNullOrWhiteSpace(parent))
+ continue;
+
+ // If the parent doesn't have any items, we want to continue
+ if (Items[parent].Count == 0)
+ continue;
+
+ // If the parent exists and has items, we remove the items that are in the parent from the current game
+ List parentItems = Items[parent];
+ foreach (DatItem item in parentItems)
+ {
+ DatItem datItem = (DatItem)item.Clone();
+ while (Items[game].Contains(datItem))
+ {
+ Items.Remove(game, datItem);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Use cloneof tags to remove roms from the children
+ ///
+ private void RemoveRomsFromChild()
+ {
+ List games = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string game in games)
+ {
+ // If the game has no items in it, we want to continue
+ if (Items[game].Count == 0)
+ continue;
+
+ // Determine if the game has a parent or not
+ string parent = null;
+ if (!string.IsNullOrWhiteSpace(Items[game][0].Machine.CloneOf))
+ parent = Items[game][0].Machine.CloneOf;
+
+ // If the parent doesnt exist, we want to continue
+ if (string.IsNullOrWhiteSpace(parent))
+ continue;
+
+ // If the parent doesn't have any items, we want to continue
+ if (Items[parent].Count == 0)
+ continue;
+
+ // If the parent exists and has items, we remove the parent items from the current game
+ List parentItems = Items[parent];
+ foreach (DatItem item in parentItems)
+ {
+ DatItem datItem = (DatItem)item.Clone();
+ while (Items[game].Contains(datItem))
+ {
+ Items.Remove(game, datItem);
+ }
+ }
+
+ // Now we want to get the parent romof tag and put it in each of the remaining items
+ List items = Items[game];
+ string romof = Items[parent][0].Machine.RomOf;
+ foreach (DatItem item in items)
+ {
+ item.Machine.RomOf = romof;
+ }
+ }
+ }
+
+ ///
+ /// Remove all romof and cloneof tags from all games
+ ///
+ private void RemoveTagsFromChild()
+ {
+ List games = Items.Keys.OrderBy(g => g).ToList();
+ foreach (string game in games)
+ {
+ List items = Items[game];
+ foreach (DatItem item in items)
+ {
+ item.Machine.CloneOf = null;
+ item.Machine.RomOf = null;
+ item.Machine.SampleOf = null;
+ }
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.DatFiles/DatFile.Parsing.cs b/SabreTools.DatFiles/DatFile.Parsing.cs
new file mode 100644
index 00000000..50582586
--- /dev/null
+++ b/SabreTools.DatFiles/DatFile.Parsing.cs
@@ -0,0 +1,316 @@
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.IO;
+
+// This file represents all methods related to parsing from a file
+namespace SabreTools.DatFiles
+{
+ public abstract partial class DatFile
+ {
+ ///
+ /// Create a DatFile and parse a file into it
+ ///
+ /// Name of the file to be parsed
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ public static DatFile CreateAndParse(string filename, bool throwOnError = false)
+ {
+ DatFile datFile = Create();
+ datFile.Parse(new ParentablePath(filename), throwOnError: throwOnError);
+ return datFile;
+ }
+
+ ///
+ /// Parse a DAT and return all found games and roms within
+ ///
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ /// True if full pathnames are to be kept, false otherwise (default)
+ /// True if original extension should be kept, false otherwise (default)
+ /// True if quotes are assumed in supported types (default), false otherwise
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ public void Parse(
+ string filename,
+ int indexId = 0,
+ bool keep = false,
+ bool keepext = false,
+ bool quotes = true,
+ bool throwOnError = false)
+ {
+ ParentablePath path = new ParentablePath(filename.Trim('"'));
+ Parse(path, indexId, keep, keepext, quotes, throwOnError);
+ }
+
+ ///
+ /// Parse a DAT and return all found games and roms within
+ ///
+ /// Name of the file to be parsed
+ /// Index ID for the DAT
+ /// True if full pathnames are to be kept, false otherwise (default)
+ /// True if original extension should be kept, false otherwise (default)
+ /// True if quotes are assumed in supported types (default), false otherwise
+ /// True if the error that is thrown should be thrown back to the caller, false otherwise
+ public void Parse(
+ ParentablePath input,
+ int indexId = 0,
+ bool keep = false,
+ bool keepext = false,
+ bool quotes = true,
+ bool throwOnError = true)
+ {
+ // Get the current path from the filename
+ string currentPath = input.CurrentPath;
+
+ // Check the file extension first as a safeguard
+ if (!PathExtensions.HasValidDatExtension(currentPath))
+ return;
+
+ // If the output filename isn't set already, get the internal filename
+ Header.FileName = (string.IsNullOrWhiteSpace(Header.FileName) ? (keepext ? Path.GetFileName(currentPath) : Path.GetFileNameWithoutExtension(currentPath)) : Header.FileName);
+
+ // If the output type isn't set already, get the internal output type
+ DatFormat currentPathFormat = GetDatFormat(currentPath);
+ Header.DatFormat = (Header.DatFormat == 0 ? currentPathFormat : Header.DatFormat);
+ Items.SetBucketedBy(Field.DatItem_CRC); // Setting this because it can reduce issues later
+
+ // Now parse the correct type of DAT
+ try
+ {
+ Create(currentPathFormat, this, quotes)?.ParseFile(currentPath, indexId, keep, throwOnError);
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, $"Error with file '{currentPath}'");
+ if (throwOnError) throw ex;
+ }
+ }
+
+ ///
+ /// Get what type of DAT the input file is
+ ///
+ /// Name of the file to be parsed
+ /// The DatFormat corresponding to the DAT
+ protected DatFormat GetDatFormat(string filename)
+ {
+ // Limit the output formats based on extension
+ if (!PathExtensions.HasValidDatExtension(filename))
+ return 0;
+
+ // Get the extension from the filename
+ string ext = PathExtensions.GetNormalizedExtension(filename);
+
+ // Check if file exists
+ if (!File.Exists(filename))
+ return 0;
+
+ // Some formats should only require the extension to know
+ switch (ext)
+ {
+ case "csv":
+ return DatFormat.CSV;
+ case "json":
+ return DatFormat.SabreJSON;
+ case "md5":
+ return DatFormat.RedumpMD5;
+#if NET_FRAMEWORK
+ case "ripemd160":
+ return DatFormat.RedumpRIPEMD160;
+#endif
+ case "sfv":
+ return DatFormat.RedumpSFV;
+ case "sha1":
+ return DatFormat.RedumpSHA1;
+ case "sha256":
+ return DatFormat.RedumpSHA256;
+ case "sha384":
+ return DatFormat.RedumpSHA384;
+ case "sha512":
+ return DatFormat.RedumpSHA512;
+ case "spamsum":
+ return DatFormat.RedumpSpamSum;
+ case "ssv":
+ return DatFormat.SSV;
+ case "tsv":
+ return DatFormat.TSV;
+ }
+
+ // For everything else, we need to read it
+ // Get the first two non-whitespace, non-comment lines to check, if possible
+ string first = string.Empty, second = string.Empty;
+
+ try
+ {
+ using (StreamReader sr = File.OpenText(filename))
+ {
+ first = sr.ReadLine().ToLowerInvariant();
+ while ((string.IsNullOrWhiteSpace(first) || first.StartsWith("