Adobe Flash (AKA Shockwave Flash, Macromedia Flash): Nim parsing library

SWF files are used by Adobe Flash (AKA Shockwave Flash, Macromedia Flash) to encode rich interactive multimedia content and are, essentially, a container for special bytecode instructions to play back that content. In early 2000s, it was dominant rich multimedia web format (.swf files were integrated into web pages and played back with a browser plugin), but its usage largely declined in 2010s, as HTML5 and performant browser-native solutions (i.e. JavaScript engines and graphical approaches, such as WebGL) emerged.

There are a lot of versions of SWF (~36), format is somewhat documented by Adobe.

File extension

swf

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Adobe Flash (AKA Shockwave Flash, Macromedia Flash) 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 Adobe Flash (AKA Shockwave Flash, Macromedia Flash)

swf.nim

import kaitai_struct_nim_runtime
import options

type
  Swf* = ref object of KaitaiStruct
    `compression`*: Swf_Compressions
    `signature`*: seq[byte]
    `version`*: uint8
    `lenFile`*: uint32
    `plainBody`*: Swf_SwfBody
    `zlibBody`*: Swf_SwfBody
    `parent`*: KaitaiStruct
    `rawPlainBody`*: seq[byte]
    `rawZlibBody`*: seq[byte]
    `rawRawZlibBody`*: seq[byte]
  Swf_Compressions* = enum
    zlib = 67
    none = 70
    lzma = 90
  Swf_TagType* = enum
    end_of_file = 0
    place_object = 4
    remove_object = 5
    set_background_color = 9
    define_sound = 14
    place_object2 = 26
    remove_object2 = 28
    frame_label = 43
    export_assets = 56
    script_limits = 65
    file_attributes = 69
    place_object3 = 70
    symbol_class = 76
    metadata = 77
    define_scaling_grid = 78
    do_abc = 82
    define_scene_and_frame_label_data = 86
  Swf_Rgb* = ref object of KaitaiStruct
    `r`*: uint8
    `g`*: uint8
    `b`*: uint8
    `parent`*: Swf_Tag
  Swf_DoAbcBody* = ref object of KaitaiStruct
    `flags`*: uint32
    `name`*: string
    `abcdata`*: seq[byte]
    `parent`*: Swf_Tag
  Swf_SwfBody* = ref object of KaitaiStruct
    `rect`*: Swf_Rect
    `frameRate`*: uint16
    `frameCount`*: uint16
    `fileAttributesTag`*: Swf_Tag
    `tags`*: seq[Swf_Tag]
    `parent`*: Swf
  Swf_Rect* = ref object of KaitaiStruct
    `b1`*: uint8
    `skip`*: seq[byte]
    `parent`*: Swf_SwfBody
    `numBitsInst`: int
    `numBitsInstFlag`: bool
    `numBytesInst`: int
    `numBytesInstFlag`: bool
  Swf_Tag* = ref object of KaitaiStruct
    `recordHeader`*: Swf_RecordHeader
    `tagBody`*: KaitaiStruct
    `parent`*: Swf_SwfBody
    `rawTagBody`*: seq[byte]
  Swf_SymbolClassBody* = ref object of KaitaiStruct
    `numSymbols`*: uint16
    `symbols`*: seq[Swf_SymbolClassBody_Symbol]
    `parent`*: Swf_Tag
  Swf_SymbolClassBody_Symbol* = ref object of KaitaiStruct
    `tag`*: uint16
    `name`*: string
    `parent`*: Swf_SymbolClassBody
  Swf_DefineSoundBody* = ref object of KaitaiStruct
    `id`*: uint16
    `format`*: uint64
    `samplingRate`*: Swf_DefineSoundBody_SamplingRates
    `bitsPerSample`*: Swf_DefineSoundBody_Bps
    `numChannels`*: Swf_DefineSoundBody_Channels
    `numSamples`*: uint32
    `parent`*: Swf_Tag
  Swf_DefineSoundBody_SamplingRates* = enum
    rate_5_5_khz = 0
    rate_11_khz = 1
    rate_22_khz = 2
    rate_44_khz = 3
  Swf_DefineSoundBody_Bps* = enum
    sound_8_bit = 0
    sound_16_bit = 1
  Swf_DefineSoundBody_Channels* = enum
    mono = 0
    stereo = 1
  Swf_RecordHeader* = ref object of KaitaiStruct
    `tagCodeAndLength`*: uint16
    `bigLen`*: int32
    `parent`*: Swf_Tag
    `tagTypeInst`: Swf_TagType
    `tagTypeInstFlag`: bool
    `smallLenInst`: int
    `smallLenInstFlag`: bool
    `lenInst`: int
    `lenInstFlag`: bool
  Swf_ScriptLimitsBody* = ref object of KaitaiStruct
    `maxRecursionDepth`*: uint16
    `scriptTimeoutSeconds`*: uint16
    `parent`*: Swf_Tag

proc read*(_: typedesc[Swf], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Swf
proc read*(_: typedesc[Swf_Rgb], io: KaitaiStream, root: KaitaiStruct, parent: Swf_Tag): Swf_Rgb
proc read*(_: typedesc[Swf_DoAbcBody], io: KaitaiStream, root: KaitaiStruct, parent: Swf_Tag): Swf_DoAbcBody
proc read*(_: typedesc[Swf_SwfBody], io: KaitaiStream, root: KaitaiStruct, parent: Swf): Swf_SwfBody
proc read*(_: typedesc[Swf_Rect], io: KaitaiStream, root: KaitaiStruct, parent: Swf_SwfBody): Swf_Rect
proc read*(_: typedesc[Swf_Tag], io: KaitaiStream, root: KaitaiStruct, parent: Swf_SwfBody): Swf_Tag
proc read*(_: typedesc[Swf_SymbolClassBody], io: KaitaiStream, root: KaitaiStruct, parent: Swf_Tag): Swf_SymbolClassBody
proc read*(_: typedesc[Swf_SymbolClassBody_Symbol], io: KaitaiStream, root: KaitaiStruct, parent: Swf_SymbolClassBody): Swf_SymbolClassBody_Symbol
proc read*(_: typedesc[Swf_DefineSoundBody], io: KaitaiStream, root: KaitaiStruct, parent: Swf_Tag): Swf_DefineSoundBody
proc read*(_: typedesc[Swf_RecordHeader], io: KaitaiStream, root: KaitaiStruct, parent: Swf_Tag): Swf_RecordHeader
proc read*(_: typedesc[Swf_ScriptLimitsBody], io: KaitaiStream, root: KaitaiStruct, parent: Swf_Tag): Swf_ScriptLimitsBody

proc numBits*(this: Swf_Rect): int
proc numBytes*(this: Swf_Rect): int
proc tagType*(this: Swf_RecordHeader): Swf_TagType
proc smallLen*(this: Swf_RecordHeader): int
proc len*(this: Swf_RecordHeader): int


##[
SWF files are used by Adobe Flash (AKA Shockwave Flash, Macromedia
Flash) to encode rich interactive multimedia content and are,
essentially, a container for special bytecode instructions to play
back that content. In early 2000s, it was dominant rich multimedia
web format (.swf files were integrated into web pages and played
back with a browser plugin), but its usage largely declined in
2010s, as HTML5 and performant browser-native solutions
(i.e. JavaScript engines and graphical approaches, such as WebGL)
emerged.

There are a lot of versions of SWF (~36), format is somewhat
documented by Adobe.

@see <a href="https://open-flash.github.io/mirrors/swf-spec-19.pdf">Source</a>
]##
proc read*(_: typedesc[Swf], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Swf =
  template this: untyped = result
  this = new(Swf)
  let root = if root == nil: cast[Swf](this) else: cast[Swf](root)
  this.io = io
  this.root = root
  this.parent = parent

  let compressionExpr = Swf_Compressions(this.io.readU1())
  this.compression = compressionExpr
  let signatureExpr = this.io.readBytes(int(2))
  this.signature = signatureExpr
  let versionExpr = this.io.readU1()
  this.version = versionExpr
  let lenFileExpr = this.io.readU4le()
  this.lenFile = lenFileExpr
  if this.compression == swf.none:
    let rawPlainBodyExpr = this.io.readBytesFull()
    this.rawPlainBody = rawPlainBodyExpr
    let rawPlainBodyIo = newKaitaiStream(rawPlainBodyExpr)
    let plainBodyExpr = Swf_SwfBody.read(rawPlainBodyIo, this.root, this)
    this.plainBody = plainBodyExpr
  if this.compression == swf.zlib:
    let rawRawZlibBodyExpr = this.io.readBytesFull()
    this.rawRawZlibBody = rawRawZlibBodyExpr
    let rawZlibBodyExpr = this.rawRawZlibBody.processZlib()
    this.rawZlibBody = rawZlibBodyExpr
    let rawZlibBodyIo = newKaitaiStream(rawZlibBodyExpr)
    let zlibBodyExpr = Swf_SwfBody.read(rawZlibBodyIo, this.root, this)
    this.zlibBody = zlibBodyExpr

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

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

  let rExpr = this.io.readU1()
  this.r = rExpr
  let gExpr = this.io.readU1()
  this.g = gExpr
  let bExpr = this.io.readU1()
  this.b = bExpr

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

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

  let flagsExpr = this.io.readU4le()
  this.flags = flagsExpr
  let nameExpr = encode(this.io.readBytesTerm(0, false, true, true), "ASCII")
  this.name = nameExpr
  let abcdataExpr = this.io.readBytesFull()
  this.abcdata = abcdataExpr

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

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

  let rectExpr = Swf_Rect.read(this.io, this.root, this)
  this.rect = rectExpr
  let frameRateExpr = this.io.readU2le()
  this.frameRate = frameRateExpr
  let frameCountExpr = this.io.readU2le()
  this.frameCount = frameCountExpr
  if Swf(this.root).version >= 8:
    let fileAttributesTagExpr = Swf_Tag.read(this.io, this.root, this)
    this.fileAttributesTag = fileAttributesTagExpr
  block:
    var i: int
    while not this.io.isEof:
      let it = Swf_Tag.read(this.io, this.root, this)
      this.tags.add(it)
      inc i

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

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

  let b1Expr = this.io.readU1()
  this.b1 = b1Expr
  let skipExpr = this.io.readBytes(int(this.numBytes))
  this.skip = skipExpr

proc numBits(this: Swf_Rect): int = 
  if this.numBitsInstFlag:
    return this.numBitsInst
  let numBitsInstExpr = int((this.b1 shr 3))
  this.numBitsInst = numBitsInstExpr
  this.numBitsInstFlag = true
  return this.numBitsInst

proc numBytes(this: Swf_Rect): int = 
  if this.numBytesInstFlag:
    return this.numBytesInst
  let numBytesInstExpr = int(((((this.numBits * 4) - 3) + 7) div 8))
  this.numBytesInst = numBytesInstExpr
  this.numBytesInstFlag = true
  return this.numBytesInst

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

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

  let recordHeaderExpr = Swf_RecordHeader.read(this.io, this.root, this)
  this.recordHeader = recordHeaderExpr
  block:
    let on = this.recordHeader.tagType
    if on == swf.define_sound:
      let rawTagBodyExpr = this.io.readBytes(int(this.recordHeader.len))
      this.rawTagBody = rawTagBodyExpr
      let rawTagBodyIo = newKaitaiStream(rawTagBodyExpr)
      let tagBodyExpr = Swf_DefineSoundBody.read(rawTagBodyIo, this.root, this)
      this.tagBody = tagBodyExpr
    elif on == swf.set_background_color:
      let rawTagBodyExpr = this.io.readBytes(int(this.recordHeader.len))
      this.rawTagBody = rawTagBodyExpr
      let rawTagBodyIo = newKaitaiStream(rawTagBodyExpr)
      let tagBodyExpr = Swf_Rgb.read(rawTagBodyIo, this.root, this)
      this.tagBody = tagBodyExpr
    elif on == swf.script_limits:
      let rawTagBodyExpr = this.io.readBytes(int(this.recordHeader.len))
      this.rawTagBody = rawTagBodyExpr
      let rawTagBodyIo = newKaitaiStream(rawTagBodyExpr)
      let tagBodyExpr = Swf_ScriptLimitsBody.read(rawTagBodyIo, this.root, this)
      this.tagBody = tagBodyExpr
    elif on == swf.do_abc:
      let rawTagBodyExpr = this.io.readBytes(int(this.recordHeader.len))
      this.rawTagBody = rawTagBodyExpr
      let rawTagBodyIo = newKaitaiStream(rawTagBodyExpr)
      let tagBodyExpr = Swf_DoAbcBody.read(rawTagBodyIo, this.root, this)
      this.tagBody = tagBodyExpr
    elif on == swf.export_assets:
      let rawTagBodyExpr = this.io.readBytes(int(this.recordHeader.len))
      this.rawTagBody = rawTagBodyExpr
      let rawTagBodyIo = newKaitaiStream(rawTagBodyExpr)
      let tagBodyExpr = Swf_SymbolClassBody.read(rawTagBodyIo, this.root, this)
      this.tagBody = tagBodyExpr
    elif on == swf.symbol_class:
      let rawTagBodyExpr = this.io.readBytes(int(this.recordHeader.len))
      this.rawTagBody = rawTagBodyExpr
      let rawTagBodyIo = newKaitaiStream(rawTagBodyExpr)
      let tagBodyExpr = Swf_SymbolClassBody.read(rawTagBodyIo, this.root, this)
      this.tagBody = tagBodyExpr
    else:
      let tagBodyExpr = this.io.readBytes(int(this.recordHeader.len))
      this.tagBody = tagBodyExpr

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

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

  let numSymbolsExpr = this.io.readU2le()
  this.numSymbols = numSymbolsExpr
  for i in 0 ..< int(this.numSymbols):
    let it = Swf_SymbolClassBody_Symbol.read(this.io, this.root, this)
    this.symbols.add(it)

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

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

  let tagExpr = this.io.readU2le()
  this.tag = tagExpr
  let nameExpr = encode(this.io.readBytesTerm(0, false, true, true), "ASCII")
  this.name = nameExpr

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

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

  let idExpr = this.io.readU2le()
  this.id = idExpr
  let formatExpr = this.io.readBitsIntBe(4)
  this.format = formatExpr

  ##[
  Sound sampling rate, as per enum. Ignored for Nellymoser and Speex codecs.
  ]##
  let samplingRateExpr = Swf_DefineSoundBody_SamplingRates(this.io.readBitsIntBe(2))
  this.samplingRate = samplingRateExpr
  let bitsPerSampleExpr = Swf_DefineSoundBody_Bps(this.io.readBitsIntBe(1))
  this.bitsPerSample = bitsPerSampleExpr
  let numChannelsExpr = Swf_DefineSoundBody_Channels(this.io.readBitsIntBe(1))
  this.numChannels = numChannelsExpr
  alignToByte(this.io)
  let numSamplesExpr = this.io.readU4le()
  this.numSamples = numSamplesExpr

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

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

  let tagCodeAndLengthExpr = this.io.readU2le()
  this.tagCodeAndLength = tagCodeAndLengthExpr
  if this.smallLen == 63:
    let bigLenExpr = this.io.readS4le()
    this.bigLen = bigLenExpr

proc tagType(this: Swf_RecordHeader): Swf_TagType = 
  if this.tagTypeInstFlag:
    return this.tagTypeInst
  let tagTypeInstExpr = Swf_TagType(Swf_TagType((this.tagCodeAndLength shr 6)))
  this.tagTypeInst = tagTypeInstExpr
  this.tagTypeInstFlag = true
  return this.tagTypeInst

proc smallLen(this: Swf_RecordHeader): int = 
  if this.smallLenInstFlag:
    return this.smallLenInst
  let smallLenInstExpr = int((this.tagCodeAndLength and 63))
  this.smallLenInst = smallLenInstExpr
  this.smallLenInstFlag = true
  return this.smallLenInst

proc len(this: Swf_RecordHeader): int = 
  if this.lenInstFlag:
    return this.lenInst
  let lenInstExpr = int((if this.smallLen == 63: this.bigLen else: this.smallLen))
  this.lenInst = lenInstExpr
  this.lenInstFlag = true
  return this.lenInst

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

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

  let maxRecursionDepthExpr = this.io.readU2le()
  this.maxRecursionDepth = maxRecursionDepthExpr
  let scriptTimeoutSecondsExpr = this.io.readU2le()
  this.scriptTimeoutSeconds = scriptTimeoutSecondsExpr

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