ISO9660 CD filesystem: Nim parsing library

ISO9660 is standard filesystem used on read-only optical discs (mostly CD-ROM). The standard was based on earlier High Sierra Format (HSF), proposed for CD-ROMs in 1985, and, after several revisions, it was accepted as ISO9960:1998.

The format emphasizes portability (thus having pretty minimal features and very conservative file names standards) and sequential access (which favors disc devices with relatively slow rotation speed).

File extension

iso

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of ISO9660 CD filesystem using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Nim source code to parse ISO9660 CD filesystem

iso9660.nim

import kaitai_struct_nim_runtime
import options

type
  Iso9660* = ref object of KaitaiStruct
    `parent`*: KaitaiStruct
    `sectorSizeInst`: int
    `sectorSizeInstFlag`: bool
    `primaryVolDescInst`: Iso9660_VolDesc
    `primaryVolDescInstFlag`: bool
  Iso9660_VolDescPrimary* = ref object of KaitaiStruct
    `unused1`*: seq[byte]
    `systemId`*: string
    `volumeId`*: string
    `unused2`*: seq[byte]
    `volSpaceSize`*: Iso9660_U4bi
    `unused3`*: seq[byte]
    `volSetSize`*: Iso9660_U2bi
    `volSeqNum`*: Iso9660_U2bi
    `logicalBlockSize`*: Iso9660_U2bi
    `pathTableSize`*: Iso9660_U4bi
    `lbaPathTableLe`*: uint32
    `lbaOptPathTableLe`*: uint32
    `lbaPathTableBe`*: uint32
    `lbaOptPathTableBe`*: uint32
    `rootDir`*: Iso9660_DirEntry
    `volSetId`*: string
    `publisherId`*: string
    `dataPreparerId`*: string
    `applicationId`*: string
    `copyrightFileId`*: string
    `abstractFileId`*: string
    `bibliographicFileId`*: string
    `volCreateDatetime`*: Iso9660_DecDatetime
    `volModDatetime`*: Iso9660_DecDatetime
    `volExpireDatetime`*: Iso9660_DecDatetime
    `volEffectiveDatetime`*: Iso9660_DecDatetime
    `fileStructureVersion`*: uint8
    `unused4`*: uint8
    `applicationArea`*: seq[byte]
    `parent`*: Iso9660_VolDesc
    `rawRootDir`*: seq[byte]
    `rawPathTableInst`*: seq[byte]
    `pathTableInst`: Iso9660_PathTableLe
    `pathTableInstFlag`: bool
  Iso9660_VolDescBootRecord* = ref object of KaitaiStruct
    `bootSystemId`*: string
    `bootId`*: string
    `parent`*: Iso9660_VolDesc
  Iso9660_Datetime* = ref object of KaitaiStruct
    `year`*: uint8
    `month`*: uint8
    `day`*: uint8
    `hour`*: uint8
    `minute`*: uint8
    `sec`*: uint8
    `timezone`*: uint8
    `parent`*: Iso9660_DirEntryBody
  Iso9660_DirEntry* = ref object of KaitaiStruct
    `len`*: uint8
    `body`*: Iso9660_DirEntryBody
    `parent`*: KaitaiStruct
    `rawBody`*: seq[byte]
  Iso9660_VolDesc* = ref object of KaitaiStruct
    `type`*: uint8
    `magic`*: seq[byte]
    `version`*: uint8
    `volDescBootRecord`*: Iso9660_VolDescBootRecord
    `volDescPrimary`*: Iso9660_VolDescPrimary
    `parent`*: Iso9660
  Iso9660_PathTableEntryLe* = ref object of KaitaiStruct
    `lenDirName`*: uint8
    `lenExtAttrRec`*: uint8
    `lbaExtent`*: uint32
    `parentDirIdx`*: uint16
    `dirName`*: string
    `padding`*: uint8
    `parent`*: Iso9660_PathTableLe
  Iso9660_DirEntries* = ref object of KaitaiStruct
    `entries`*: seq[Iso9660_DirEntry]
    `parent`*: Iso9660_DirEntryBody
  Iso9660_U4bi* = ref object of KaitaiStruct
    `le`*: uint32
    `be`*: uint32
    `parent`*: KaitaiStruct
  Iso9660_U2bi* = ref object of KaitaiStruct
    `le`*: uint16
    `be`*: uint16
    `parent`*: KaitaiStruct
  Iso9660_PathTableLe* = ref object of KaitaiStruct
    `entries`*: seq[Iso9660_PathTableEntryLe]
    `parent`*: Iso9660_VolDescPrimary
  Iso9660_DecDatetime* = ref object of KaitaiStruct
    `year`*: string
    `month`*: string
    `day`*: string
    `hour`*: string
    `minute`*: string
    `sec`*: string
    `secHundreds`*: string
    `timezone`*: uint8
    `parent`*: Iso9660_VolDescPrimary
  Iso9660_DirEntryBody* = ref object of KaitaiStruct
    `lenExtAttrRec`*: uint8
    `lbaExtent`*: Iso9660_U4bi
    `sizeExtent`*: Iso9660_U4bi
    `datetime`*: Iso9660_Datetime
    `fileFlags`*: uint8
    `fileUnitSize`*: uint8
    `interleaveGapSize`*: uint8
    `volSeqNum`*: Iso9660_U2bi
    `lenFileName`*: uint8
    `fileName`*: string
    `padding`*: uint8
    `rest`*: seq[byte]
    `parent`*: Iso9660_DirEntry
    `rawExtentAsDirInst`*: seq[byte]
    `extentAsDirInst`: Iso9660_DirEntries
    `extentAsDirInstFlag`: bool
    `extentAsFileInst`: seq[byte]
    `extentAsFileInstFlag`: bool

proc read*(_: typedesc[Iso9660], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660
proc read*(_: typedesc[Iso9660_VolDescPrimary], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDesc): Iso9660_VolDescPrimary
proc read*(_: typedesc[Iso9660_VolDescBootRecord], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDesc): Iso9660_VolDescBootRecord
proc read*(_: typedesc[Iso9660_Datetime], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_DirEntryBody): Iso9660_Datetime
proc read*(_: typedesc[Iso9660_DirEntry], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660_DirEntry
proc read*(_: typedesc[Iso9660_VolDesc], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660): Iso9660_VolDesc
proc read*(_: typedesc[Iso9660_PathTableEntryLe], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_PathTableLe): Iso9660_PathTableEntryLe
proc read*(_: typedesc[Iso9660_DirEntries], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_DirEntryBody): Iso9660_DirEntries
proc read*(_: typedesc[Iso9660_U4bi], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660_U4bi
proc read*(_: typedesc[Iso9660_U2bi], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660_U2bi
proc read*(_: typedesc[Iso9660_PathTableLe], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDescPrimary): Iso9660_PathTableLe
proc read*(_: typedesc[Iso9660_DecDatetime], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDescPrimary): Iso9660_DecDatetime
proc read*(_: typedesc[Iso9660_DirEntryBody], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_DirEntry): Iso9660_DirEntryBody

proc sectorSize*(this: Iso9660): int
proc primaryVolDesc*(this: Iso9660): Iso9660_VolDesc
proc pathTable*(this: Iso9660_VolDescPrimary): Iso9660_PathTableLe
proc extentAsDir*(this: Iso9660_DirEntryBody): Iso9660_DirEntries
proc extentAsFile*(this: Iso9660_DirEntryBody): seq[byte]


##[
ISO9660 is standard filesystem used on read-only optical discs
(mostly CD-ROM). The standard was based on earlier High Sierra
Format (HSF), proposed for CD-ROMs in 1985, and, after several
revisions, it was accepted as ISO9960:1998.

The format emphasizes portability (thus having pretty minimal
features and very conservative file names standards) and sequential
access (which favors disc devices with relatively slow rotation
speed).

]##
proc read*(_: typedesc[Iso9660], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660 =
  template this: untyped = result
  this = new(Iso9660)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent


proc sectorSize(this: Iso9660): int = 
  if this.sectorSizeInstFlag:
    return this.sectorSizeInst
  let sectorSizeInstExpr = int(2048)
  this.sectorSizeInst = sectorSizeInstExpr
  this.sectorSizeInstFlag = true
  return this.sectorSizeInst

proc primaryVolDesc(this: Iso9660): Iso9660_VolDesc = 
  if this.primaryVolDescInstFlag:
    return this.primaryVolDescInst
  let pos = this.io.pos()
  this.io.seek(int((16 * this.sectorSize)))
  let primaryVolDescInstExpr = Iso9660_VolDesc.read(this.io, this.root, this)
  this.primaryVolDescInst = primaryVolDescInstExpr
  this.io.seek(pos)
  this.primaryVolDescInstFlag = true
  return this.primaryVolDescInst

proc fromFile*(_: typedesc[Iso9660], filename: string): Iso9660 =
  Iso9660.read(newKaitaiFileStream(filename), nil, nil)


##[
@see <a href="https://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor">Source</a>
]##
proc read*(_: typedesc[Iso9660_VolDescPrimary], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDesc): Iso9660_VolDescPrimary =
  template this: untyped = result
  this = new(Iso9660_VolDescPrimary)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let unused1Expr = this.io.readBytes(int(1))
  this.unused1 = unused1Expr
  let systemIdExpr = encode(this.io.readBytes(int(32)), "UTF-8")
  this.systemId = systemIdExpr
  let volumeIdExpr = encode(this.io.readBytes(int(32)), "UTF-8")
  this.volumeId = volumeIdExpr
  let unused2Expr = this.io.readBytes(int(8))
  this.unused2 = unused2Expr
  let volSpaceSizeExpr = Iso9660_U4bi.read(this.io, this.root, this)
  this.volSpaceSize = volSpaceSizeExpr
  let unused3Expr = this.io.readBytes(int(32))
  this.unused3 = unused3Expr
  let volSetSizeExpr = Iso9660_U2bi.read(this.io, this.root, this)
  this.volSetSize = volSetSizeExpr
  let volSeqNumExpr = Iso9660_U2bi.read(this.io, this.root, this)
  this.volSeqNum = volSeqNumExpr
  let logicalBlockSizeExpr = Iso9660_U2bi.read(this.io, this.root, this)
  this.logicalBlockSize = logicalBlockSizeExpr
  let pathTableSizeExpr = Iso9660_U4bi.read(this.io, this.root, this)
  this.pathTableSize = pathTableSizeExpr
  let lbaPathTableLeExpr = this.io.readU4le()
  this.lbaPathTableLe = lbaPathTableLeExpr
  let lbaOptPathTableLeExpr = this.io.readU4le()
  this.lbaOptPathTableLe = lbaOptPathTableLeExpr
  let lbaPathTableBeExpr = this.io.readU4be()
  this.lbaPathTableBe = lbaPathTableBeExpr
  let lbaOptPathTableBeExpr = this.io.readU4be()
  this.lbaOptPathTableBe = lbaOptPathTableBeExpr
  let rawRootDirExpr = this.io.readBytes(int(34))
  this.rawRootDir = rawRootDirExpr
  let rawRootDirIo = newKaitaiStream(rawRootDirExpr)
  let rootDirExpr = Iso9660_DirEntry.read(rawRootDirIo, this.root, this)
  this.rootDir = rootDirExpr
  let volSetIdExpr = encode(this.io.readBytes(int(128)), "UTF-8")
  this.volSetId = volSetIdExpr
  let publisherIdExpr = encode(this.io.readBytes(int(128)), "UTF-8")
  this.publisherId = publisherIdExpr
  let dataPreparerIdExpr = encode(this.io.readBytes(int(128)), "UTF-8")
  this.dataPreparerId = dataPreparerIdExpr
  let applicationIdExpr = encode(this.io.readBytes(int(128)), "UTF-8")
  this.applicationId = applicationIdExpr
  let copyrightFileIdExpr = encode(this.io.readBytes(int(38)), "UTF-8")
  this.copyrightFileId = copyrightFileIdExpr
  let abstractFileIdExpr = encode(this.io.readBytes(int(36)), "UTF-8")
  this.abstractFileId = abstractFileIdExpr
  let bibliographicFileIdExpr = encode(this.io.readBytes(int(37)), "UTF-8")
  this.bibliographicFileId = bibliographicFileIdExpr
  let volCreateDatetimeExpr = Iso9660_DecDatetime.read(this.io, this.root, this)
  this.volCreateDatetime = volCreateDatetimeExpr
  let volModDatetimeExpr = Iso9660_DecDatetime.read(this.io, this.root, this)
  this.volModDatetime = volModDatetimeExpr
  let volExpireDatetimeExpr = Iso9660_DecDatetime.read(this.io, this.root, this)
  this.volExpireDatetime = volExpireDatetimeExpr
  let volEffectiveDatetimeExpr = Iso9660_DecDatetime.read(this.io, this.root, this)
  this.volEffectiveDatetime = volEffectiveDatetimeExpr
  let fileStructureVersionExpr = this.io.readU1()
  this.fileStructureVersion = fileStructureVersionExpr
  let unused4Expr = this.io.readU1()
  this.unused4 = unused4Expr
  let applicationAreaExpr = this.io.readBytes(int(512))
  this.applicationArea = applicationAreaExpr

proc pathTable(this: Iso9660_VolDescPrimary): Iso9660_PathTableLe = 
  if this.pathTableInstFlag:
    return this.pathTableInst
  let pos = this.io.pos()
  this.io.seek(int((this.lbaPathTableLe * Iso9660(this.root).sectorSize)))
  let rawPathTableInstExpr = this.io.readBytes(int(this.pathTableSize.le))
  this.rawPathTableInst = rawPathTableInstExpr
  let rawPathTableInstIo = newKaitaiStream(rawPathTableInstExpr)
  let pathTableInstExpr = Iso9660_PathTableLe.read(rawPathTableInstIo, this.root, this)
  this.pathTableInst = pathTableInstExpr
  this.io.seek(pos)
  this.pathTableInstFlag = true
  return this.pathTableInst

proc fromFile*(_: typedesc[Iso9660_VolDescPrimary], filename: string): Iso9660_VolDescPrimary =
  Iso9660_VolDescPrimary.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_VolDescBootRecord], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDesc): Iso9660_VolDescBootRecord =
  template this: untyped = result
  this = new(Iso9660_VolDescBootRecord)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let bootSystemIdExpr = encode(this.io.readBytes(int(32)), "UTF-8")
  this.bootSystemId = bootSystemIdExpr
  let bootIdExpr = encode(this.io.readBytes(int(32)), "UTF-8")
  this.bootId = bootIdExpr

proc fromFile*(_: typedesc[Iso9660_VolDescBootRecord], filename: string): Iso9660_VolDescBootRecord =
  Iso9660_VolDescBootRecord.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_Datetime], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_DirEntryBody): Iso9660_Datetime =
  template this: untyped = result
  this = new(Iso9660_Datetime)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let yearExpr = this.io.readU1()
  this.year = yearExpr
  let monthExpr = this.io.readU1()
  this.month = monthExpr
  let dayExpr = this.io.readU1()
  this.day = dayExpr
  let hourExpr = this.io.readU1()
  this.hour = hourExpr
  let minuteExpr = this.io.readU1()
  this.minute = minuteExpr
  let secExpr = this.io.readU1()
  this.sec = secExpr
  let timezoneExpr = this.io.readU1()
  this.timezone = timezoneExpr

proc fromFile*(_: typedesc[Iso9660_Datetime], filename: string): Iso9660_Datetime =
  Iso9660_Datetime.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_DirEntry], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660_DirEntry =
  template this: untyped = result
  this = new(Iso9660_DirEntry)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenExpr = this.io.readU1()
  this.len = lenExpr
  if this.len > 0:
    let rawBodyExpr = this.io.readBytes(int((this.len - 1)))
    this.rawBody = rawBodyExpr
    let rawBodyIo = newKaitaiStream(rawBodyExpr)
    let bodyExpr = Iso9660_DirEntryBody.read(rawBodyIo, this.root, this)
    this.body = bodyExpr

proc fromFile*(_: typedesc[Iso9660_DirEntry], filename: string): Iso9660_DirEntry =
  Iso9660_DirEntry.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_VolDesc], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660): Iso9660_VolDesc =
  template this: untyped = result
  this = new(Iso9660_VolDesc)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let typeExpr = this.io.readU1()
  this.type = typeExpr
  let magicExpr = this.io.readBytes(int(5))
  this.magic = magicExpr
  let versionExpr = this.io.readU1()
  this.version = versionExpr
  if this.type == 0:
    let volDescBootRecordExpr = Iso9660_VolDescBootRecord.read(this.io, this.root, this)
    this.volDescBootRecord = volDescBootRecordExpr
  if this.type == 1:
    let volDescPrimaryExpr = Iso9660_VolDescPrimary.read(this.io, this.root, this)
    this.volDescPrimary = volDescPrimaryExpr

proc fromFile*(_: typedesc[Iso9660_VolDesc], filename: string): Iso9660_VolDesc =
  Iso9660_VolDesc.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_PathTableEntryLe], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_PathTableLe): Iso9660_PathTableEntryLe =
  template this: untyped = result
  this = new(Iso9660_PathTableEntryLe)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenDirNameExpr = this.io.readU1()
  this.lenDirName = lenDirNameExpr
  let lenExtAttrRecExpr = this.io.readU1()
  this.lenExtAttrRec = lenExtAttrRecExpr
  let lbaExtentExpr = this.io.readU4le()
  this.lbaExtent = lbaExtentExpr
  let parentDirIdxExpr = this.io.readU2le()
  this.parentDirIdx = parentDirIdxExpr
  let dirNameExpr = encode(this.io.readBytes(int(this.lenDirName)), "UTF-8")
  this.dirName = dirNameExpr
  if (this.lenDirName %%% 2) == 1:
    let paddingExpr = this.io.readU1()
    this.padding = paddingExpr

proc fromFile*(_: typedesc[Iso9660_PathTableEntryLe], filename: string): Iso9660_PathTableEntryLe =
  Iso9660_PathTableEntryLe.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_DirEntries], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_DirEntryBody): Iso9660_DirEntries =
  template this: untyped = result
  this = new(Iso9660_DirEntries)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  block:
    var i: int
    while true:
      let it = Iso9660_DirEntry.read(this.io, this.root, this)
      this.entries.add(it)
      if it.len == 0:
        break
      inc i

proc fromFile*(_: typedesc[Iso9660_DirEntries], filename: string): Iso9660_DirEntries =
  Iso9660_DirEntries.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_U4bi], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660_U4bi =
  template this: untyped = result
  this = new(Iso9660_U4bi)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let leExpr = this.io.readU4le()
  this.le = leExpr
  let beExpr = this.io.readU4be()
  this.be = beExpr

proc fromFile*(_: typedesc[Iso9660_U4bi], filename: string): Iso9660_U4bi =
  Iso9660_U4bi.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_U2bi], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Iso9660_U2bi =
  template this: untyped = result
  this = new(Iso9660_U2bi)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let leExpr = this.io.readU2le()
  this.le = leExpr
  let beExpr = this.io.readU2be()
  this.be = beExpr

proc fromFile*(_: typedesc[Iso9660_U2bi], filename: string): Iso9660_U2bi =
  Iso9660_U2bi.read(newKaitaiFileStream(filename), nil, nil)


##[
@see <a href="https://wiki.osdev.org/ISO_9660#The_Path_Table">Source</a>
]##
proc read*(_: typedesc[Iso9660_PathTableLe], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDescPrimary): Iso9660_PathTableLe =
  template this: untyped = result
  this = new(Iso9660_PathTableLe)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  block:
    var i: int
    while not this.io.isEof:
      let it = Iso9660_PathTableEntryLe.read(this.io, this.root, this)
      this.entries.add(it)
      inc i

proc fromFile*(_: typedesc[Iso9660_PathTableLe], filename: string): Iso9660_PathTableLe =
  Iso9660_PathTableLe.read(newKaitaiFileStream(filename), nil, nil)


##[
@see <a href="https://wiki.osdev.org/ISO_9660#Date.2Ftime_format">Source</a>
]##
proc read*(_: typedesc[Iso9660_DecDatetime], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_VolDescPrimary): Iso9660_DecDatetime =
  template this: untyped = result
  this = new(Iso9660_DecDatetime)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let yearExpr = encode(this.io.readBytes(int(4)), "ASCII")
  this.year = yearExpr
  let monthExpr = encode(this.io.readBytes(int(2)), "ASCII")
  this.month = monthExpr
  let dayExpr = encode(this.io.readBytes(int(2)), "ASCII")
  this.day = dayExpr
  let hourExpr = encode(this.io.readBytes(int(2)), "ASCII")
  this.hour = hourExpr
  let minuteExpr = encode(this.io.readBytes(int(2)), "ASCII")
  this.minute = minuteExpr
  let secExpr = encode(this.io.readBytes(int(2)), "ASCII")
  this.sec = secExpr
  let secHundredsExpr = encode(this.io.readBytes(int(2)), "ASCII")
  this.secHundreds = secHundredsExpr
  let timezoneExpr = this.io.readU1()
  this.timezone = timezoneExpr

proc fromFile*(_: typedesc[Iso9660_DecDatetime], filename: string): Iso9660_DecDatetime =
  Iso9660_DecDatetime.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[Iso9660_DirEntryBody], io: KaitaiStream, root: KaitaiStruct, parent: Iso9660_DirEntry): Iso9660_DirEntryBody =
  template this: untyped = result
  this = new(Iso9660_DirEntryBody)
  let root = if root == nil: cast[Iso9660](this) else: cast[Iso9660](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenExtAttrRecExpr = this.io.readU1()
  this.lenExtAttrRec = lenExtAttrRecExpr
  let lbaExtentExpr = Iso9660_U4bi.read(this.io, this.root, this)
  this.lbaExtent = lbaExtentExpr
  let sizeExtentExpr = Iso9660_U4bi.read(this.io, this.root, this)
  this.sizeExtent = sizeExtentExpr
  let datetimeExpr = Iso9660_Datetime.read(this.io, this.root, this)
  this.datetime = datetimeExpr
  let fileFlagsExpr = this.io.readU1()
  this.fileFlags = fileFlagsExpr
  let fileUnitSizeExpr = this.io.readU1()
  this.fileUnitSize = fileUnitSizeExpr
  let interleaveGapSizeExpr = this.io.readU1()
  this.interleaveGapSize = interleaveGapSizeExpr
  let volSeqNumExpr = Iso9660_U2bi.read(this.io, this.root, this)
  this.volSeqNum = volSeqNumExpr
  let lenFileNameExpr = this.io.readU1()
  this.lenFileName = lenFileNameExpr
  let fileNameExpr = encode(this.io.readBytes(int(this.lenFileName)), "UTF-8")
  this.fileName = fileNameExpr
  if (this.lenFileName %%% 2) == 0:
    let paddingExpr = this.io.readU1()
    this.padding = paddingExpr
  let restExpr = this.io.readBytesFull()
  this.rest = restExpr

proc extentAsDir(this: Iso9660_DirEntryBody): Iso9660_DirEntries = 
  if this.extentAsDirInstFlag:
    return this.extentAsDirInst
  if (this.fileFlags and 2) != 0:
    let io = Iso9660(this.root).io
    let pos = io.pos()
    io.seek(int((this.lbaExtent.le * Iso9660(this.root).sectorSize)))
    let rawExtentAsDirInstExpr = io.readBytes(int(this.sizeExtent.le))
    this.rawExtentAsDirInst = rawExtentAsDirInstExpr
    let rawExtentAsDirInstIo = newKaitaiStream(rawExtentAsDirInstExpr)
    let extentAsDirInstExpr = Iso9660_DirEntries.read(rawExtentAsDirInstIo, this.root, this)
    this.extentAsDirInst = extentAsDirInstExpr
    io.seek(pos)
  this.extentAsDirInstFlag = true
  return this.extentAsDirInst

proc extentAsFile(this: Iso9660_DirEntryBody): seq[byte] = 
  if this.extentAsFileInstFlag:
    return this.extentAsFileInst
  if (this.fileFlags and 2) == 0:
    let io = Iso9660(this.root).io
    let pos = io.pos()
    io.seek(int((this.lbaExtent.le * Iso9660(this.root).sectorSize)))
    let extentAsFileInstExpr = io.readBytes(int(this.sizeExtent.le))
    this.extentAsFileInst = extentAsFileInstExpr
    io.seek(pos)
  this.extentAsFileInstFlag = true
  return this.extentAsFileInst

proc fromFile*(_: typedesc[Iso9660_DirEntryBody], filename: string): Iso9660_DirEntryBody =
  Iso9660_DirEntryBody.read(newKaitaiFileStream(filename), nil, nil)