Initial commit, ported from swf2ass-go

This commit is contained in:
DataHoarder 2023-12-02 01:46:09 +01:00
commit 952bd4c012
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
73 changed files with 4247 additions and 0 deletions

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2023 WeebDataHoarder, swf-go Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

50
README.md Normal file
View file

@ -0,0 +1,50 @@
# swf-go
Modular Shockwave Flash reader, parser and decoder in Go.
Compared to [kelvyne/swf](https://github.com/kelvyne/swf), it also decodes types and Tag contents, including sprites.
### Example
```go
package example
import (
"errors"
"git.gammaspectra.live/WeebDataHoarder/swf-go"
"git.gammaspectra.live/WeebDataHoarder/swf-go/tag"
"io"
"os"
)
func main() {
f, err := os.Open("flash.swf")
if err != nil {
panic(err)
}
defer f.Close()
reader, err := swf.NewReader(f)
if err != nil {
panic(err)
}
for {
t, err := reader.Tag()
if err != nil {
if errors.Is(err, io.EOF) {
//file is completely read
panic("EOF reached without End tag")
}
panic(err)
}
//Handle tags
switch t.(type) {
case *tag.End:
//end of file
break
}
}
}
```

10
go.mod Normal file
View file

@ -0,0 +1,10 @@
module git.gammaspectra.live/WeebDataHoarder/swf-go
go 1.21
require (
github.com/icza/bitio v1.1.0
github.com/ulikunitz/xz v0.5.11
github.com/x448/float16 v0.8.4
golang.org/x/text v0.14.0
)

10
go.sum Normal file
View file

@ -0,0 +1,10 @@
github.com/icza/bitio v1.1.0 h1:ysX4vtldjdi3Ygai5m1cWy4oLkhWTAi+SyO6HC8L9T0=
github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

102
reader.go Normal file
View file

@ -0,0 +1,102 @@
package swf
import (
"compress/zlib"
"encoding/binary"
"fmt"
"git.gammaspectra.live/WeebDataHoarder/swf-go/tag"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"github.com/icza/bitio"
"github.com/ulikunitz/xz/lzma"
"io"
)
type Reader struct {
r *bitio.Reader
header types.Header
}
func NewReader(reader io.Reader) (*Reader, error) {
r := &Reader{}
var headerData [8]byte
if _, err := reader.Read(headerData[:]); err != nil {
return nil, err
}
copy(r.header.Signature[:], headerData[:])
r.header.Version = headerData[3]
r.header.FileLength = binary.LittleEndian.Uint32(headerData[4:])
switch r.header.Signature {
case types.SignatureUncompressed:
r.r = bitio.NewReader(reader)
case types.SignatureCompressedZLIB:
if r.header.Version < 6 {
return nil, fmt.Errorf("unsupported signature %s", string(r.header.Signature[:]))
}
zlibReader, err := zlib.NewReader(reader)
if err != nil {
return nil, err
}
r.r = bitio.NewReader(zlibReader)
case types.SignatureCompressedLZMA:
if r.header.Version < 13 {
return nil, fmt.Errorf("unsupported signature %s", string(r.header.Signature[:]))
}
lzmaReader, err := lzma.NewReader(reader)
if err != nil {
return nil, err
}
r.r = bitio.NewReader(lzmaReader)
default:
return nil, fmt.Errorf("unsupported signature %s", string(r.header.Signature[:]))
}
err := types.ReadType(r.r, types.ReaderContext{
Version: r.header.Version,
}, &r.header.FrameSize)
if err != nil {
return nil, err
}
err = types.ReadSI16(r.r, &r.header.FrameRate)
if err != nil {
return nil, err
}
err = types.ReadU16[uint16](r.r, &r.header.FrameCount)
if err != nil {
return nil, err
}
return r, nil
}
func (r *Reader) Header() types.Header {
return r.header
}
// Record Reads a tag.Record, which must be handled and/or parsed
func (r *Reader) Record() (record *tag.Record, err error) {
record = &tag.Record{}
err = types.ReadType(r.r, types.ReaderContext{
Version: r.header.Version,
}, record)
if err != nil {
return nil, err
}
return record, nil
}
// Tag Reads a tag.Tag, which has been pre-parsed
func (r *Reader) Tag() (readTag tag.Tag, err error) {
record, err := r.Record()
if err != nil {
return nil, err
}
readTag, err = record.Decode()
return readTag, err
}

216
subtypes/ACTIONS.go Normal file
View file

@ -0,0 +1,216 @@
package subtypes
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"github.com/icza/bitio"
)
type CLIPACTIONS struct {
Reserved uint16
AllEventFlags CLIPEVENTFLAGS
Records CLIPACTIONRECORDS
}
type CLIPACTIONRECORDS []CLIPACTIONRECORD
func (records *CLIPACTIONRECORDS) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
for {
var flags CLIPEVENTFLAGS
err = types.ReadType(r, ctx, &flags)
if err != nil {
return err
}
if flags.IsEnd() {
break
}
record := CLIPACTIONRECORD{
EventFlags: flags,
}
err = types.ReadType(r, ctx, &record)
if err != nil {
return err
}
*records = append(*records, record)
}
return nil
}
type CLIPACTIONRECORD struct {
EventFlags CLIPEVENTFLAGS
ActionRecordSize uint32
KeyCode uint8
Actions []ACTIONRECORD
}
func (clipRecord *CLIPACTIONRECORD) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
err = types.ReadU32(r, &clipRecord.ActionRecordSize)
if err != nil {
return err
}
countReader := bitio.NewCountReader(r)
if clipRecord.EventFlags.KeyPress {
err = types.ReadU8(r, &clipRecord.KeyCode)
if err != nil {
return err
}
}
//TODO: check
for uint32(countReader.BitsCount/8) < clipRecord.ActionRecordSize {
var record ACTIONRECORD
err = types.ReadType(countReader, ctx, &record)
if err != nil {
return err
}
clipRecord.Actions = append(clipRecord.Actions, record)
}
return nil
}
type CLIPEVENTFLAGS struct {
//align?
_ struct{} `swfFlags:"root"`
KeyUp bool
KeyDown bool
MouseUp bool
MouseDown bool
MouseMove bool
Unload bool
EnterFrame bool
Load bool
DragOver bool
RollOut bool
RollOver bool
ReleaseOutside bool
Release bool
Press bool
Initialize bool
EventData bool
//SWF 6 or later
Reserved1 uint8 `swfBits:",5" swfCondition:"IsSWF6OrGreater()"`
Construct bool `swfCondition:"IsSWF6OrGreater()"`
KeyPress bool `swfCondition:"IsSWF6OrGreater()"`
DragOut bool `swfCondition:"IsSWF6OrGreater()"`
Reserved2 uint8 `swfBits:",8" swfCondition:"IsSWF6OrGreater()"`
}
func (f *CLIPEVENTFLAGS) IsEnd() bool {
return *f == CLIPEVENTFLAGS{}
}
func (f *CLIPEVENTFLAGS) IsSWF6OrGreater(ctx types.ReaderContext) bool {
return ctx.Version >= 6
}
type ActionCode uint8
// TODO: complete lists
const (
_ = ActionCode(iota)
_
_
_
ActionNextFrame
ActionPreviousFrame
ActionPlay
ActionStop
ActionToggleQuality
ActionStopSounds
ActionAdd
ActionSubtract
ActionMultiply
ActionDivide
ActionEquals
ActionLess
ActionAnd
ActionOr
ActionNot
ActionStringEquals
ActionStringLength
ActionStringAdd
ActionStringExtract
ActionPop
ActionToInteger
_
_
_
ActionGetVariable
ActionSetVariable
_
_
ActionSetTarget2
_
ActionGetProperty
ActionSetProperty
ActionCloneSprite
ActionRemoveSprite
ActionTrace
ActionStartDrag
ActionEndDrag
ActionStringLess
_
_
_
_
_
_
ActionRandomNumber
ActionMBStringLength
ActionCharToAscii
ActionAsciiToChar
ActionGetTime
ActionMBStringExtract
ActionMBCharToAscii
ActionMBAsciiToChar
)
const (
_ = ActionCode(iota) + 0x80
ActionGotoFrame
ActionGetURL
_ //83
_
_
_
_
_
_
ActionWaitForFrame // 0x8A
ActionSetTarget
ActionGoToLabel
ActionWaitForFrame2
_
_
_ //0x90
_
_
_
_
_
ActionPush
_
_
ActionJump
ActionGetURL2
_
_
ActionIf
ActionCall
ActionGotoFrame2
)
type ACTIONRECORD struct {
_ struct{} `swfFlags:"root"`
ActionCode ActionCode
Length uint16 `swfCondition:"HasActionLength()"`
Data []uint8 `swfCount:"Length"`
}
func (a *ACTIONRECORD) HasActionLength(ctx types.ReaderContext) bool {
return a.ActionCode > 0x80
}

311
subtypes/BITMAP.go Normal file
View file

@ -0,0 +1,311 @@
package subtypes
import (
"bytes"
"compress/zlib"
"encoding/binary"
"errors"
"fmt"
"image"
"image/color"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"math"
"runtime"
"slices"
)
type ImageBitsFormat uint8
const (
ImageBitsFormatPaletted ImageBitsFormat = 3
ImageBitsFormatRGB15 ImageBitsFormat = 4
ImageBitsFormatRGB32 ImageBitsFormat = 5
)
func DecodeImageBits(data []byte, width, height int, format ImageBitsFormat, paletteSize int, hasAlpha bool) (image.Image, error) {
r, err := zlib.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
defer r.Close()
var buf []byte
switch format {
// 8-bit colormapped image
case ImageBitsFormatPaletted:
advanceWidth := make([]byte, ((uint16(width)+0b11)&(^uint16(0b11)))-uint16(width))
if hasAlpha {
buf = make([]byte, 4)
} else {
buf = make([]byte, 3)
}
var palette color.Palette
for i := 0; i < paletteSize; i++ {
_, err = io.ReadFull(r, buf[:])
if err != nil {
return nil, err
}
var a uint8 = math.MaxUint8
if hasAlpha {
a = buf[3]
}
palette = append(palette, color.RGBA{R: buf[0], G: buf[1], B: buf[2], A: a})
}
im := image.NewPaletted(image.Rectangle{
Min: image.Point{},
Max: image.Point{X: width, Y: height},
}, palette)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
_, err = io.ReadFull(r, buf[:1])
if err != nil {
return nil, err
}
im.SetColorIndex(x, y, buf[0])
}
if len(advanceWidth) > 0 {
_, err = io.ReadFull(r, advanceWidth[:])
if err != nil {
return nil, err
}
}
}
return im, nil
case ImageBitsFormatRGB15:
if hasAlpha {
return nil, errors.New("rgb15 not supported in alpha mode")
}
im := image.NewRGBA(image.Rectangle{
Min: image.Point{},
Max: image.Point{X: width, Y: height},
})
advanceWidth := make([]byte, ((uint16(width)+0b1)&(^uint16(0b1)))-uint16(width))
buf = make([]byte, 2)
//TODO: check if correct
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
_, err = io.ReadFull(r, buf[:])
if err != nil {
return nil, err
}
compressed := binary.BigEndian.Uint16(buf)
im.SetRGBA(x, y, color.RGBA{
R: uint8((((compressed>>10)&0x1f)*255 + 15) / 31),
G: uint8((((compressed>>5)&0x1f)*255 + 15) / 31),
B: uint8(((compressed&0x1f)*255 + 15) / 31),
A: math.MaxUint8,
})
}
if len(advanceWidth) > 0 {
_, err = io.ReadFull(r, advanceWidth[:])
if err != nil {
return nil, err
}
}
}
return im, nil
case ImageBitsFormatRGB32:
//always read 4 bytes regardless
buf = make([]byte, 4)
im := image.NewRGBA(image.Rectangle{
Min: image.Point{},
Max: image.Point{X: width, Y: height},
})
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
_, err = io.ReadFull(r, buf[:])
if err != nil {
return nil, err
}
if !hasAlpha {
buf[0] = math.MaxUint8
}
im.SetRGBA(x, y, color.RGBA{R: buf[1], G: buf[2], B: buf[3], A: buf[0]})
}
}
return im, nil
default:
return nil, fmt.Errorf("unsupported lossless format %d", format)
}
}
var bitmapHeaderJPEG = []byte{0xff, 0xd8}
var bitmapHeaderJPEGInvalid = []byte{0xff, 0xd9, 0xff, 0xd8}
var bitmapHeaderPNG = []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}
var bitmapHeaderGIF = []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61}
var bitmapHeaderFormats = [][]byte{
bitmapHeaderJPEG,
bitmapHeaderJPEGInvalid,
bitmapHeaderPNG,
bitmapHeaderGIF,
}
func DecodeImageBitsJPEG(imageData []byte, alphaData []byte) (image.Image, error) {
var im image.Image
var err error
for i, s := range bitmapHeaderFormats {
if bytes.Compare(s, imageData[:len(s)]) == 0 {
if i == 0 || i == 1 {
//jpeg
//remove invalid data
jpegData := removeInvalidJPEGData(imageData)
im, _, err = image.Decode(bytes.NewReader(jpegData))
if im != nil {
size := im.Bounds().Size()
if len(alphaData) == size.X*size.Y {
newIm := image.NewRGBA(im.Bounds())
for x := 0; x < size.X; x++ {
for y := 0; y < size.Y; y++ {
rI, gI, bI, _ := im.At(x, y).RGBA()
// The JPEG data should be premultiplied alpha, but it isn't in some incorrect SWFs.
// This means 0% alpha pixels may have color and incorrectly show as visible.
// Flash Player clamps color to the alpha value to fix this case.
// Only applies to DefineBitsJPEG3; DefineBitsLossless does not seem to clamp.
a := alphaData[y*size.X+x]
if a != 0 {
runtime.KeepAlive(a)
}
r := min(uint8(rI>>8), a)
g := min(uint8(gI>>8), a)
b := min(uint8(bI>>8), a)
newIm.SetRGBA(x, y, color.RGBA{
R: r,
G: g,
B: b,
A: a,
})
}
}
im = newIm
}
}
} else if i == 2 {
//png
im, _, err = image.Decode(bytes.NewReader(imageData))
} else if i == 3 {
//gif
im, _, err = image.Decode(bytes.NewReader(imageData))
}
break
}
}
if err != nil {
return nil, err
}
return im, nil
}
// removeInvalidJPEGData
// SWF19 errata p.138:
// "Before version 8 of the SWF file format, SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8
// before the JPEG SOI marker."
// 0xFFD9FFD8 is a JPEG EOI+SOI marker pair. Contrary to the spec, this invalid marker sequence can actually appear
// at any time before the 0xFFC0 SOF marker, not only at the beginning of the data. I believe this is a relic from
// the SWF JPEGTables tag, which stores encoding tables separately from the DefineBits image data, encased in its
// own SOI+EOI pair. When these data are glued together, an interior EOI+SOI sequence is produced. The Flash JPEG
// decoder expects this pair and ignores it, despite standard JPEG decoders stopping at the EOI.
// When DefineBitsJPEG2 etc. were introduced, the Flash encoders/decoders weren't properly adjusted, resulting in
// this sequence persisting. Also, despite what the spec says, this doesn't appear to be version checked (e.g., a
// v9 SWF can contain one of these malformed JPEGs and display correctly).
// See https://github.com/ruffle-rs/ruffle/issues/8775 for various examples.
func removeInvalidJPEGData(data []byte) (buf []byte) {
const SOF0 uint8 = 0xC0 // Start of frame
const RST0 uint8 = 0xD0 // Restart (we shouldn't see this before SOS, but just in case)
const RST1 uint8 = 0xD0
const RST2 uint8 = 0xD0
const RST3 uint8 = 0xD0
const RST4 uint8 = 0xD0
const RST5 uint8 = 0xD0
const RST6 uint8 = 0xD0
const RST7 uint8 = 0xD7
const SOI uint8 = 0xD8 // Start of image
const EOI uint8 = 0xD9 // End of image
if bytes.HasPrefix(data, bitmapHeaderJPEGInvalid) {
data = bytes.TrimPrefix(data, bitmapHeaderJPEGInvalid)
} else {
// Parse the JPEG markers searching for the 0xFFD9FFD8 marker sequence to splice out.
// We only have to search up to the SOF0 marker.
// This might be another case where eventually we want to write our own full JPEG decoder to match Flash's decoder.
jpegData := data
var pos int
for {
if len(jpegData) < 4 {
break
}
var payloadLength int
if bytes.Compare([]byte{0xFF, EOI, 0xFF, SOI}, jpegData[:4]) == 0 {
// Invalid EOI+SOI sequence found, splice it out.
data = slices.Delete(slices.Clone(data), pos, pos+4)
break
} else if bytes.Compare([]byte{0xFF, EOI}, jpegData[:2]) == 0 { // EOI, SOI, RST markers do not include a size.
} else if bytes.Compare([]byte{0xFF, SOI}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST0}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST1}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST2}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST3}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST4}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST5}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST6}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, RST7}, jpegData[:2]) == 0 {
} else if bytes.Compare([]byte{0xFF, SOF0}, jpegData[:2]) == 0 {
// No invalid sequence found before SOF marker, return data as-is.
break
} else if jpegData[0] == 0xFF {
// Other tags include a length.
payloadLength = int(binary.BigEndian.Uint16(jpegData[2:]))
} else {
// All JPEG markers should start with 0xFF.
// So this is either not a JPEG, or we screwed up parsing the markers. Bail out.
break
}
if len(jpegData) < payloadLength+2 {
break
}
jpegData = jpegData[payloadLength+2:]
pos += payloadLength + 2
}
}
// Some JPEGs are missing the final EOI marker (JPEG optimizers truncate it?)
// Flash and most image decoders will still display these images, but jpeg-decoder errors.
// Glue on an EOI marker if its not already there and hope for the best.
if bytes.HasSuffix(data, []byte{0xff, EOI}) {
return data
} else {
//JPEG is missing EOI marker and may not decode properly
return append(slices.Clone(data), []byte{0xff, EOI}...)
}
}

189
subtypes/FILTERLIST.go Normal file
View file

@ -0,0 +1,189 @@
package subtypes
import "git.gammaspectra.live/WeebDataHoarder/swf-go/types"
type FILTERLIST struct {
_ struct{} `swfFlags:"root"`
NumberOfFilters uint8
Filters []FILTER `swfCount:"NumberOfFilters"`
}
type FILTER struct {
ID FilterId
Data any
}
type FilterId uint8
const (
FilterDropShadow = FilterId(iota)
FilterBlur
FilterGlow
FilterBevel
FilterGradientGlow
FilterConvolution
FilterColorMatrix
FilterGradientBevel
)
func (f *FILTER) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
err = types.ReadU8(r, &f.ID)
if err != nil {
return err
}
switch f.ID {
case FilterDropShadow:
var value DROPSHADOWFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
case FilterBlur:
var value BLURFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
case FilterGlow:
var value GLOWFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
case FilterBevel:
var value BEVELFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
case FilterGradientGlow:
var value GRADIENTGLOWFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
case FilterConvolution:
var value CONVOLUTIONFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
case FilterColorMatrix:
var value COLORMATRIXFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
case FilterGradientBevel:
var value GRADIENTBEVELFILTER
err = types.ReadType(r, ctx, &value)
if err != nil {
return err
}
f.Data = value
}
return nil
}
type DROPSHADOWFILTER struct {
DropShadowColor types.RGBA
BlurX, BlurY types.Fixed16
Angle types.Fixed16
Distance types.Fixed16
Strength types.Fixed8
InnerShadow bool
Knockout bool
CompositeSource bool
Passes uint8 `swfBits:",5"`
}
type BLURFILTER struct {
BlurX, BlurY types.Fixed16
Passes uint8 `swfBits:",5"`
Reserved uint8 `swfBits:",3"`
}
type GLOWFILTER struct {
GlowColor types.RGBA
BlurX, BlurY types.Fixed16
Strength types.Fixed8
InnerGlob bool
Knockout bool
CompositeSource bool
Passes uint8 `swfBits:",5"`
}
type BEVELFILTER struct {
ShadowColor types.RGBA
HighLightColor types.RGBA
BlurX, BlurY types.Fixed16
Angle types.Fixed16
Distance types.Fixed16
Strength types.Fixed8
InnerShadow bool
Knockout bool
CompositeSource bool
OnTop bool
Passes uint8 `swfBits:",4"`
}
type GRADIENTGLOWFILTER struct {
_ struct{} `swfFlags:"root"`
NumColors uint8
GradientColors []types.RGBA `swfCount:"NumColors"`
GradientRatio []uint8 `swfCount:"NumColors"`
BlurX, BlurY types.Fixed16
Angle types.Fixed16
Distance types.Fixed16
Strength types.Fixed8
InnerShadow bool
Knockout bool
CompositeSource bool
OnTop bool
Passes uint8 `swfBits:",4"`
}
type CONVOLUTIONFILTER struct {
_ struct{} `swfFlags:"root"`
MatrixX, MatrixY uint8
Divisor float32
Bias float32
Matrix []float32 `swfCount:"MatrixSize()"`
DefaultColor types.RGBA
Reserved uint8 `swfBits:",6"`
Clamp bool
PreserveAlpha bool
}
func (f *CONVOLUTIONFILTER) MatrixSize(ctx types.ReaderContext) uint64 {
return uint64(f.MatrixX) * uint64(f.MatrixY)
}
type COLORMATRIXFILTER struct {
Matrix [20]float32
}
type GRADIENTBEVELFILTER struct {
_ struct{} `swfFlags:"root"`
NumColors uint8
GradientColors []types.RGBA `swfCount:"NumColors"`
GradientRatio []uint8 `swfCount:"NumColors"`
BlurX, BlurY types.Fixed16
Angle types.Fixed16
Distance types.Fixed16
Strength types.Fixed8
InnerShadow bool
Knockout bool
CompositeSource bool
OnTop bool
Passes uint8 `swfBits:",4"`
}

90
subtypes/FONT.go Normal file
View file

@ -0,0 +1,90 @@
package subtypes
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"slices"
)
type KERNINGRECORD struct {
KerningCodeLeft8 uint8 `swfCondition:"!Flag.WideCodes"`
KerningCodeLeft16 uint16 `swfCondition:"Flag.WideCodes"`
KerningCodeRight8 uint8 `swfCondition:"!Flag.WideCodes"`
KerningCodeRight16 uint16 `swfCondition:"Flag.WideCodes"`
KerningAdjustment int16
}
type ZONERECORD struct {
_ struct{} `swfFlags:"root"`
NumZoneData uint8
ZoneData []ZONEDATA `swfCount:"NumZoneData"`
Reserved uint8 `swfBits:",6"`
MaskY bool
MaskX bool
}
type ZONEDATA struct {
AlignmentCoordinate types.Float16
Range types.Float16
}
type TEXTRECORDS []TEXTRECORD
func (records *TEXTRECORDS) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
glyphBits := uint8(ctx.GetNestedType("GlyphBits").Uint())
advanceBits := uint8(ctx.GetNestedType("AdvanceBits").Uint())
for {
isContinue, err := types.ReadBool(r)
if err != nil {
return err
}
if !isContinue {
break
}
var record TEXTRECORD
record.Flag.Type = isContinue
record.GlyphBits = glyphBits
record.AdvanceBits = advanceBits
err = types.ReadType(r, ctx, &record)
if err != nil {
return err
}
*records = append(*records, record)
r.Align()
}
return nil
}
type TEXTRECORD struct {
_ struct{} `swfFlags:"root,alignend"`
Flag struct {
Type bool `swfFlags:"skip"`
Reserved uint8 `swfBits:",3"`
HasFont bool
HasColor bool
HasYOffset bool
HasXOffset bool
}
FontId uint16 `swfCondition:"Flag.HasFont"`
Color types.Color `swfCondition:"Flag.HasColor"`
XOffset int16 `swfCondition:"Flag.HasXOffset"`
YOffset int16 `swfCondition:"Flag.HasYOffset"`
TextHeight uint16 `swfCondition:"Flag.HasFont"`
GlyphCount uint8
GlyphEntries []GLYPHENTRY `swfCount:"GlyphCount"`
GlyphBits uint8 `swfFlags:"skip"`
AdvanceBits uint8 `swfFlags:"skip"`
}
func (t *TEXTRECORD) SWFDefault(ctx types.ReaderContext) {
if slices.Contains(ctx.Flags, "Text2") {
t.Color = &types.RGBA{}
} else {
t.Color = &types.RGB{}
}
}
type GLYPHENTRY struct {
Index uint32 `swfBits:"GlyphBits"`
Advance int32 `swfBits:"AdvanceBits,signed"`
}

96
subtypes/GRADIENT.go Normal file
View file

@ -0,0 +1,96 @@
package subtypes
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"slices"
)
type GradientSpreadMode uint8
const (
GradientSpreadPad = GradientSpreadMode(iota)
GradientSpreadReflect
GradientSpreadRepeat
GradientSpreadReserved
)
type GradientInterpolationMode uint8
const (
GradientInterpolationRGB = GradientInterpolationMode(iota)
GradientInterpolationLinearRGB
GradientInterpolationReserved2
GradientInterpolationReserved3
)
type GRADIENT struct {
_ struct{} `swfFlags:"root"`
SpreadMode GradientSpreadMode `swfBits:",2"`
InterpolationMode GradientInterpolationMode `swfBits:",2"`
NumGradients uint8 `swfBits:",4"`
Records []GRADRECORD `swfCount:"NumGradients"`
GradientCheck struct{} `swfCondition:"GradientCheckField()"`
}
func (g *GRADIENT) GradientCheckField(ctx types.ReaderContext) bool {
if g.NumGradients < 1 {
panic("wrong length")
}
if g.SpreadMode == GradientSpreadReserved {
// Per SWF19 p. 136, SpreadMode 3 is reserved.
// Flash treats it as pad mode.
g.SpreadMode = GradientSpreadPad
}
if g.InterpolationMode == GradientInterpolationReserved2 || g.InterpolationMode == GradientInterpolationReserved3 {
// Per SWF19 p. 136, InterpolationMode 2 and 3 are reserved.
// Flash treats them as normal RGB mode interpolation.
g.InterpolationMode = GradientInterpolationRGB
}
return false
}
type FOCALGRADIENT struct {
_ struct{} `swfFlags:"root"`
SpreadMode GradientSpreadMode `swfBits:",2"`
InterpolationMode GradientInterpolationMode `swfBits:",2"`
NumGradients uint8 `swfBits:",4"`
Records []GRADRECORD `swfCount:"NumGradients"`
FocalPoint types.Fixed8
GradientCheck struct{} `swfCondition:"GradientCheckField()"`
}
func (g *FOCALGRADIENT) GradientCheckField(ctx types.ReaderContext) bool {
if g.NumGradients < 1 {
panic("wrong length")
}
if g.SpreadMode == GradientSpreadReserved {
// Per SWF19 p. 136, SpreadMode 3 is reserved.
// Flash treats it as pad mode.
g.SpreadMode = GradientSpreadPad
}
if g.InterpolationMode == GradientInterpolationReserved2 || g.InterpolationMode == GradientInterpolationReserved3 {
// Per SWF19 p. 136, InterpolationMode 2 and 3 are reserved.
// Flash treats them as normal RGB mode interpolation.
g.InterpolationMode = GradientInterpolationRGB
}
return false
}
type GRADRECORD struct {
Ratio uint8
Color types.Color
}
func (g *GRADRECORD) SWFDefault(ctx types.ReaderContext) {
if slices.Contains(ctx.Flags, "Shape3") || slices.Contains(ctx.Flags, "Shape4") {
g.Color = &types.RGBA{}
} else {
g.Color = &types.RGB{}
}
}

44
subtypes/MORPHGRADIENT.go Normal file
View file

@ -0,0 +1,44 @@
package subtypes
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type MORPHGRADIENT struct {
_ struct{} `swfFlags:"root"`
NumGradients uint8
Records []MORPHGRADRECORD `swfCount:"NumGradients"`
}
func (g MORPHGRADIENT) StartGradient() (g2 GRADIENT) {
g2.SpreadMode = 0 //TODO
g2.InterpolationMode = 0 //TODO
g2.NumGradients = g.NumGradients
for _, r := range g.Records {
g2.Records = append(g2.Records, GRADRECORD{
Ratio: r.StartRatio,
Color: r.StartColor,
})
}
return g2
}
func (g MORPHGRADIENT) EndGradient() (g2 GRADIENT) {
g2.SpreadMode = 0 //TODO
g2.InterpolationMode = 0 //TODO
g2.NumGradients = g.NumGradients
for _, r := range g.Records {
g2.Records = append(g2.Records, GRADRECORD{
Ratio: r.EndRatio,
Color: r.EndColor,
})
}
return g2
}
type MORPHGRADRECORD struct {
StartRatio uint8
StartColor types.RGBA
EndRatio uint8
EndColor types.RGBA
}

118
subtypes/MORPHSTYLE.go Normal file
View file

@ -0,0 +1,118 @@
package subtypes
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"slices"
)
type MORPHFILLSTYLEARRAY struct {
_ struct{} `swfFlags:"root"`
FillStyleCount uint8
FillStyleCountExtended uint16 `swfCondition:"HasFillStyleCountExtended()"`
FillStyles []MORPHFILLSTYLE `swfCount:"FillStylesLength()"`
}
func (t *MORPHFILLSTYLEARRAY) HasFillStyleCountExtended(ctx types.ReaderContext) bool {
return t.FillStyleCount == 0xff
}
func (t *MORPHFILLSTYLEARRAY) FillStylesLength(ctx types.ReaderContext) uint64 {
if t.FillStyleCount == 0xff {
return uint64(t.FillStyleCountExtended)
}
return uint64(t.FillStyleCount)
}
type MORPHFILLSTYLE struct {
_ struct{} `swfFlags:"root"`
FillStyleType FillStyleType
StartColor, EndColor types.RGBA `swfCondition:"HasRGB()"`
StartGradientMatrix, EndGradientMatrix types.MATRIX `swfCondition:"HasGradientMatrix()"`
Gradient MORPHGRADIENT `swfCondition:"HasGradient()"`
BitmapId uint16 `swfCondition:"HasBitmap()"`
StartBitmapMatrix, EndBitmapMatrix types.MATRIX `swfCondition:"HasBitmap()"`
}
func (s *MORPHFILLSTYLE) HasRGB(ctx types.ReaderContext) bool {
//check first
switch s.FillStyleType {
case FillStyleSolid:
case FillStyleLinearGradient:
case FillStyleRadialGradient:
case FillStyleRepeatingBitmap:
case FillStyleClippedBitmap:
case FillStyleNonSmoothedRepeatingBitmap:
case FillStyleNonSmoothedClippedBitmap:
default:
panic("unknown fill style")
}
return s.FillStyleType == FillStyleSolid
}
func (s *MORPHFILLSTYLE) HasGradientMatrix(ctx types.ReaderContext) bool {
return s.HasGradient(ctx)
}
func (s *MORPHFILLSTYLE) HasGradient(ctx types.ReaderContext) bool {
return s.FillStyleType == FillStyleLinearGradient || s.FillStyleType == FillStyleRadialGradient
}
func (s *MORPHFILLSTYLE) HasBitmap(ctx types.ReaderContext) bool {
return s.FillStyleType == FillStyleRepeatingBitmap || s.FillStyleType == FillStyleClippedBitmap || s.FillStyleType == FillStyleNonSmoothedRepeatingBitmap || s.FillStyleType == FillStyleNonSmoothedClippedBitmap
}
type MORPHLINESTYLEARRAY struct {
_ struct{} `swfFlags:"root"`
LineStyleCount uint8
LineStyleCountExtended uint16 `swfCondition:"HasLineStyleCountExtended()"`
LineStyles []MORPHLINESTYLE `swfCondition:"!HasLineStyles2()" swfCount:"LineStylesLength()"`
LineStyles2 []MORPHLINESTYLE2 `swfCondition:"HasLineStyles2()" swfCount:"LineStylesLength()"`
}
func (t *MORPHLINESTYLEARRAY) HasLineStyleCountExtended(ctx types.ReaderContext) bool {
return t.LineStyleCount == 0xff
}
func (t *MORPHLINESTYLEARRAY) HasLineStyles2(ctx types.ReaderContext) bool {
return slices.Contains(ctx.Flags, "MorphShape2")
}
func (t *MORPHLINESTYLEARRAY) LineStylesLength(ctx types.ReaderContext) uint64 {
if t.LineStyleCount == 0xff {
return uint64(t.LineStyleCountExtended)
}
return uint64(t.LineStyleCount)
}
type MORPHLINESTYLE struct {
StartWidth uint16
StartColor types.RGBA
EndWidth uint16
EndColor types.RGBA
}
type MORPHLINESTYLE2 struct {
_ struct{} `swfFlags:"root"`
StartWidth, EndWidth uint16
Flag struct {
StartCapStyle uint8 `swfBits:",2"`
JoinStyle uint8 `swfBits:",2"`
HasFill bool
NoHScale, NoVScale bool
PixelHinting bool
Reserved uint8 `swfBits:",5"`
NoClose bool
EndCapStyle uint8 `swfBits:",2"`
}
MitterLimitFactor uint16 `swfCondition:"HasMitterLimitFactor()"`
StartColor, EndColor types.RGBA `swfCondition:"!Flag.HasFill"`
FillType MORPHFILLSTYLE `swfCondition:"Flag.HasFill"`
}
func (t *MORPHLINESTYLE2) HasMitterLimitFactor(ctx types.ReaderContext) bool {
return t.Flag.JoinStyle == 2
}

335
subtypes/SHAPE.go Normal file
View file

@ -0,0 +1,335 @@
package subtypes
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"reflect"
)
type SHAPE struct {
_ struct{} `swfFlags:"root"`
FillBits uint8 `swfBits:",4"`
LineBits uint8 `swfBits:",4"`
Records SHAPERECORDS
}
type SHAPEWITHSTYLE struct {
_ struct{} `swfFlags:"root"`
FillStyles FILLSTYLEARRAY
LineStyles LINESTYLEARRAY
FillBits uint8 `swfBits:",4"`
LineBits uint8 `swfBits:",4"`
Records SHAPERECORDS
}
type SHAPERECORDS []SHAPERECORD
func (records *SHAPERECORDS) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
fillBits := uint8(ctx.GetNestedType("FillBits").Uint())
lineBits := uint8(ctx.GetNestedType("LineBits").Uint())
*records = make(SHAPERECORDS, 0, 512)
for {
isEdge, err := types.ReadBool(r)
if err != nil {
return err
}
if !isEdge {
rec := StyleChangeRecord{}
rec.FillBits = fillBits
rec.LineBits = lineBits
err = rec.SWFRead(r, types.ReaderContext{
Version: ctx.Version,
Root: reflect.ValueOf(rec),
Flags: ctx.Flags,
})
if err != nil {
return err
}
if rec.IsEnd() {
//end record
*records = append(*records, &EndShapeRecord{})
break
}
//store new value
fillBits = rec.FillBits
lineBits = rec.LineBits
*records = append(*records, &rec)
} else {
isStraight, err := types.ReadBool(r)
if err != nil {
return err
}
if isStraight {
rec := StraightEdgeRecord{}
err = rec.SWFRead(r, types.ReaderContext{
Version: ctx.Version,
Root: reflect.ValueOf(rec),
Flags: ctx.Flags,
})
if err != nil {
return err
}
*records = append(*records, &rec)
} else {
rec := CurvedEdgeRecord{}
err = rec.SWFRead(r, types.ReaderContext{
Version: ctx.Version,
Root: reflect.ValueOf(rec),
Flags: ctx.Flags,
})
if err != nil {
return err
}
*records = append(*records, &rec)
}
}
}
r.Align()
return nil
}
type EndShapeRecord struct {
}
func (s *EndShapeRecord) RecordType() RecordType {
return RecordTypeEndShape
}
type StyleChangeRecord struct {
_ struct{} `swfFlags:"root"`
Flag struct {
NewStyles bool
LineStyle bool
FillStyle1 bool
FillStyle0 bool
MoveTo bool
}
MoveBits uint8 `swfBits:",5" swfCondition:"Flag.MoveTo"`
MoveDeltaX, MoveDeltaY types.Twip `swfBits:"MoveBits,signed" swfCondition:"Flag.MoveTo"`
FillStyle0 uint16 `swfBits:"FillBits" swfCondition:"Flag.FillStyle0"`
FillStyle1 uint16 `swfBits:"FillBits" swfCondition:"Flag.FillStyle1"`
LineStyle uint16 `swfBits:"LineBits" swfCondition:"Flag.LineStyle"`
FillStyles FILLSTYLEARRAY `swfFlags:"align" swfCondition:"Flag.NewStyles"`
LineStyles LINESTYLEARRAY `swfCondition:"Flag.NewStyles"`
FillBits uint8 `swfBits:",4" swfCondition:"Flag.NewStyles"`
LineBits uint8 `swfBits:",4" swfCondition:"Flag.NewStyles"`
}
func (rec *StyleChangeRecord) IsEnd() bool {
return !rec.Flag.NewStyles && !rec.Flag.LineStyle && !rec.Flag.FillStyle1 && !rec.Flag.FillStyle0 && !rec.Flag.MoveTo
}
func (rec *StyleChangeRecord) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
//this is not necessary, but speedups read massively
rec.Flag.NewStyles, err = types.ReadBool(r)
if err != nil {
return err
}
rec.Flag.LineStyle, err = types.ReadBool(r)
if err != nil {
return err
}
rec.Flag.FillStyle1, err = types.ReadBool(r)
if err != nil {
return err
}
rec.Flag.FillStyle0, err = types.ReadBool(r)
if err != nil {
return err
}
rec.Flag.MoveTo, err = types.ReadBool(r)
if err != nil {
return err
}
if rec.Flag.MoveTo {
rec.MoveBits, err = types.ReadUB[uint8](r, 5)
if err != nil {
return err
}
rec.MoveDeltaX, err = types.ReadSB[types.Twip](r, uint64(rec.MoveBits))
if err != nil {
return err
}
rec.MoveDeltaY, err = types.ReadSB[types.Twip](r, uint64(rec.MoveBits))
if err != nil {
return err
}
}
if rec.Flag.FillStyle0 {
rec.FillStyle0, err = types.ReadUB[uint16](r, uint64(rec.FillBits))
if err != nil {
return err
}
}
if rec.Flag.FillStyle1 {
rec.FillStyle1, err = types.ReadUB[uint16](r, uint64(rec.FillBits))
if err != nil {
return err
}
}
if rec.Flag.LineStyle {
rec.LineStyle, err = types.ReadUB[uint16](r, uint64(rec.LineBits))
if err != nil {
return err
}
}
if rec.Flag.NewStyles {
r.Align()
err = types.ReadType(r, types.ReaderContext{
Version: ctx.Version,
Root: reflect.ValueOf(rec.FillStyles),
Flags: ctx.Flags,
}, &rec.FillStyles)
if err != nil {
return err
}
err = types.ReadType(r, types.ReaderContext{
Version: ctx.Version,
Root: reflect.ValueOf(rec.LineStyles),
Flags: ctx.Flags,
}, &rec.LineStyles)
if err != nil {
return err
}
rec.FillBits, err = types.ReadUB[uint8](r, 4)
if err != nil {
return err
}
rec.LineBits, err = types.ReadUB[uint8](r, 4)
if err != nil {
return err
}
}
return nil
}
func (rec *StyleChangeRecord) RecordType() RecordType {
return RecordTypeStyleChange
}
type StraightEdgeRecord struct {
_ struct{} `swfFlags:"root"`
NumBits uint8 `swfBits:",4"`
GeneralLine bool
VertLine bool `swfCondition:"HasVertLine()"`
DeltaX types.Twip `swfBits:"NumBits+2,signed" swfCondition:"HasDeltaX()"`
DeltaY types.Twip `swfBits:"NumBits+2,signed" swfCondition:"HasDeltaY()"`
}
func (s *StraightEdgeRecord) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
//this is not necessary, but speedups read massively
s.NumBits, err = types.ReadUB[uint8](r, 4)
if err != nil {
return err
}
s.GeneralLine, err = types.ReadBool(r)
if err != nil {
return err
}
if s.HasVertLine(ctx) {
s.VertLine, err = types.ReadBool(r)
if err != nil {
return err
}
}
if s.HasDeltaX(ctx) {
s.DeltaX, err = types.ReadSB[types.Twip](r, uint64(s.NumBits+2))
if err != nil {
return err
}
}
if s.HasDeltaY(ctx) {
s.DeltaY, err = types.ReadSB[types.Twip](r, uint64(s.NumBits+2))
if err != nil {
return err
}
}
return nil
}
func (s *StraightEdgeRecord) HasVertLine(ctx types.ReaderContext) bool {
return !s.GeneralLine
}
func (s *StraightEdgeRecord) HasDeltaX(ctx types.ReaderContext) bool {
return s.GeneralLine || !s.VertLine
}
func (s *StraightEdgeRecord) HasDeltaY(ctx types.ReaderContext) bool {
return s.GeneralLine || s.VertLine
}
func (s *StraightEdgeRecord) RecordType() RecordType {
return RecordTypeStraightEdge
}
type CurvedEdgeRecord struct {
_ struct{} `swfFlags:"root"`
NumBits uint8 `swfBits:",4"`
ControlDeltaX, ControlDeltaY types.Twip `swfBits:"NumBits+2,signed"`
AnchorDeltaX, AnchorDeltaY types.Twip `swfBits:"NumBits+2,signed"`
}
func (s *CurvedEdgeRecord) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
//this is not necessary, but speedups read massively
s.NumBits, err = types.ReadUB[uint8](r, 4)
if err != nil {
return err
}
s.ControlDeltaX, err = types.ReadSB[types.Twip](r, uint64(s.NumBits+2))
if err != nil {
return err
}
s.ControlDeltaY, err = types.ReadSB[types.Twip](r, uint64(s.NumBits+2))
if err != nil {
return err
}
s.AnchorDeltaX, err = types.ReadSB[types.Twip](r, uint64(s.NumBits+2))
if err != nil {
return err
}
s.AnchorDeltaY, err = types.ReadSB[types.Twip](r, uint64(s.NumBits+2))
if err != nil {
return err
}
return nil
}
func (s *CurvedEdgeRecord) RecordType() RecordType {
return RecordTypeCurvedEdge
}
type RecordType uint8
const (
RecordTypeEndShape = RecordType(iota)
RecordTypeStyleChange
RecordTypeStraightEdge
RecordTypeCurvedEdge
)
type SHAPERECORD interface {
RecordType() RecordType
}

175
subtypes/STYLE.go Normal file
View file

@ -0,0 +1,175 @@
package subtypes
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"slices"
)
type FILLSTYLEARRAY struct {
_ struct{} `swfFlags:"root"`
FillStyleCount uint8
FillStyleCountExtended uint16 `swfCondition:"HasFillStyleCountExtended()"`
FillStyles []FILLSTYLE `swfCount:"FillStylesLength()"`
}
func (t *FILLSTYLEARRAY) HasFillStyleCountExtended(ctx types.ReaderContext) bool {
return t.FillStyleCount == 0xff
}
func (t *FILLSTYLEARRAY) FillStylesLength(ctx types.ReaderContext) uint64 {
if t.FillStyleCount == 0xff {
return uint64(t.FillStyleCountExtended)
}
return uint64(t.FillStyleCount)
}
type FillStyleType uint8
const (
FillStyleSolid = FillStyleType(0x00)
FillStyleLinearGradient = FillStyleType(0x10)
FillStyleRadialGradient = FillStyleType(0x12)
FillStyleFocalRadialGradient = FillStyleType(0x13)
FillStyleRepeatingBitmap = FillStyleType(0x40)
FillStyleClippedBitmap = FillStyleType(0x41)
FillStyleNonSmoothedRepeatingBitmap = FillStyleType(0x42)
FillStyleNonSmoothedClippedBitmap = FillStyleType(0x43)
)
func (t *FillStyleType) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
err = types.ReadU8(r, t)
if err != nil {
return err
}
// Bitmap smoothing only occurs in SWF version 8+.
if ctx.Version < 8 {
switch *t {
case FillStyleClippedBitmap:
*t = FillStyleNonSmoothedClippedBitmap
case FillStyleRepeatingBitmap:
*t = FillStyleNonSmoothedRepeatingBitmap
}
}
return nil
}
type FILLSTYLE struct {
_ struct{} `swfFlags:"root,alignend"`
FillStyleType FillStyleType
Color types.Color `swfCondition:"HasRGB()"`
GradientMatrix types.MATRIX `swfCondition:"HasGradientMatrix()"`
Gradient GRADIENT `swfCondition:"HasGradient()"`
FocalGradient FOCALGRADIENT `swfCondition:"HasFocalGradient()"`
BitmapId uint16 `swfCondition:"HasBitmap()"`
BitmapMatrix types.MATRIX `swfCondition:"HasBitmap()"`
}
func (s *FILLSTYLE) SWFDefault(ctx types.ReaderContext) {
if slices.Contains(ctx.Flags, "Shape3") || slices.Contains(ctx.Flags, "Shape4") {
s.Color = &types.RGBA{}
} else {
s.Color = &types.RGB{}
}
}
func (s *FILLSTYLE) HasRGB(ctx types.ReaderContext) bool {
//check first
switch s.FillStyleType {
case FillStyleSolid:
case FillStyleLinearGradient:
case FillStyleRadialGradient:
case FillStyleFocalRadialGradient:
case FillStyleRepeatingBitmap:
case FillStyleClippedBitmap:
case FillStyleNonSmoothedRepeatingBitmap:
case FillStyleNonSmoothedClippedBitmap:
default:
panic("unknown fill style")
}
return s.FillStyleType == FillStyleSolid
}
func (s *FILLSTYLE) HasGradientMatrix(ctx types.ReaderContext) bool {
return s.HasGradient(ctx) || s.HasFocalGradient(ctx)
}
func (s *FILLSTYLE) HasGradient(ctx types.ReaderContext) bool {
return s.FillStyleType == FillStyleLinearGradient || s.FillStyleType == FillStyleRadialGradient
}
func (s *FILLSTYLE) HasFocalGradient(ctx types.ReaderContext) bool {
return s.FillStyleType == FillStyleFocalRadialGradient
}
func (s *FILLSTYLE) HasBitmap(ctx types.ReaderContext) bool {
return s.FillStyleType == FillStyleRepeatingBitmap || s.FillStyleType == FillStyleClippedBitmap || s.FillStyleType == FillStyleNonSmoothedRepeatingBitmap || s.FillStyleType == FillStyleNonSmoothedClippedBitmap
}
type LINESTYLEARRAY struct {
_ struct{} `swfFlags:"root"`
LineStyleCount uint8
LineStyleCountExtended uint16 `swfCondition:"HasLineStyleCountExtended()"`
LineStyles []LINESTYLE `swfCondition:"!HasLineStyles2()" swfCount:"LineStylesLength()"`
LineStyles2 []LINESTYLE2 `swfCondition:"HasLineStyles2()" swfCount:"LineStylesLength()"`
}
func (t *LINESTYLEARRAY) HasLineStyleCountExtended(ctx types.ReaderContext) bool {
return t.LineStyleCount == 0xff
}
func (t *LINESTYLEARRAY) HasLineStyles2(ctx types.ReaderContext) bool {
return slices.Contains(ctx.Flags, "Shape4")
}
func (t *LINESTYLEARRAY) LineStylesLength(ctx types.ReaderContext) uint64 {
if t.LineStyleCount == 0xff {
return uint64(t.LineStyleCountExtended)
}
return uint64(t.LineStyleCount)
}
type LINESTYLE struct {
Width uint16
Color types.Color
}
func (s *LINESTYLE) SWFDefault(ctx types.ReaderContext) {
if slices.Contains(ctx.Flags, "Shape3") {
s.Color = &types.RGBA{}
} else {
s.Color = &types.RGB{}
}
}
type LINESTYLE2 struct {
_ struct{} `swfFlags:"root"`
Width uint16
Flag struct {
StartCapStyle uint8 `swfBits:",2"`
JoinStyle uint8 `swfBits:",2"`
HasFill bool
NoHScale, NoVScale bool
PixelHinting bool
Reserved uint8 `swfBits:",5"`
NoClose bool
EndCapStyle uint8 `swfBits:",2"`
}
MitterLimitFactor uint16 `swfCondition:"HasMitterLimitFactor()"`
Color types.RGBA `swfCondition:"HasColor()"`
FillType FILLSTYLE `swfCondition:"Flag.HasFill"`
}
func (t *LINESTYLE2) HasMitterLimitFactor(ctx types.ReaderContext) bool {
return t.Flag.JoinStyle == 2
}
func (t *LINESTYLE2) HasColor(ctx types.ReaderContext) bool {
return !t.Flag.HasFill
}

15
tag/DefineBits.go Normal file
View file

@ -0,0 +1,15 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineBits struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
Data types.UntilEndBytes
}
func (t *DefineBits) Code() Code {
return RecordDefineBits
}

13
tag/DefineBitsJPEG2.go Normal file
View file

@ -0,0 +1,13 @@
package tag
import "git.gammaspectra.live/WeebDataHoarder/swf-go/types"
type DefineBitsJPEG2 struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
Data types.UntilEndBytes
}
func (t *DefineBitsJPEG2) Code() Code {
return RecordDefineBitsJPEG2
}

36
tag/DefineBitsJPEG3.go Normal file
View file

@ -0,0 +1,36 @@
package tag
import (
"bytes"
"compress/zlib"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"io"
)
type DefineBitsJPEG3 struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
AlphaDataOffset uint32
ImageData []byte `swfCount:"AlphaDataOffset"`
BitmapAlphaData types.UntilEndBytes
}
func (t *DefineBitsJPEG3) GetAlphaData() []byte {
if len(t.BitmapAlphaData) == 0 {
return nil
}
r, err := zlib.NewReader(bytes.NewReader(t.BitmapAlphaData))
if err != nil {
return nil
}
defer r.Close()
buf, err := io.ReadAll(r)
if err != nil {
return nil
}
return buf
}
func (t *DefineBitsJPEG3) Code() Code {
return RecordDefineBitsJPEG3
}

37
tag/DefineBitsJPEG4.go Normal file
View file

@ -0,0 +1,37 @@
package tag
import (
"bytes"
"compress/zlib"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"io"
)
type DefineBitsJPEG4 struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
AlphaDataOffset uint32
DeblockParam types.Fixed8
ImageData []byte `swfCount:"AlphaDataOffset"`
BitmapAlphaData types.UntilEndBytes
}
func (t *DefineBitsJPEG4) GetAlphaData() []byte {
if len(t.BitmapAlphaData) == 0 {
return nil
}
r, err := zlib.NewReader(bytes.NewReader(t.BitmapAlphaData))
if err != nil {
return nil
}
defer r.Close()
buf, err := io.ReadAll(r)
if err != nil {
return nil
}
return buf
}
func (t *DefineBitsJPEG4) Code() Code {
return RecordDefineBitsJPEG4
}

28
tag/DefineBitsLossless.go Normal file
View file

@ -0,0 +1,28 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"image"
)
type DefineBitsLossless struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
Format subtypes.ImageBitsFormat
Width, Height uint16
ColorTableSize uint8 `swfCondition:"HasColorTableSize()"`
ZlibData types.UntilEndBytes
}
func (t *DefineBitsLossless) HasColorTableSize(ctx types.ReaderContext) bool {
return t.Format == 3
}
func (t *DefineBitsLossless) GetImage() (image.Image, error) {
return subtypes.DecodeImageBits(t.ZlibData, int(t.Width), int(t.Height), t.Format, int(t.ColorTableSize)+1, false)
}
func (t *DefineBitsLossless) Code() Code {
return RecordDefineBitsLossless
}

View file

@ -0,0 +1,28 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"image"
)
type DefineBitsLossless2 struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
Format subtypes.ImageBitsFormat
Width, Height uint16
ColorTableSize uint8 `swfCondition:"HasColorTableSize()"`
ZlibData types.UntilEndBytes
}
func (t *DefineBitsLossless2) HasColorTableSize(ctx types.ReaderContext) bool {
return t.Format == 3
}
func (t *DefineBitsLossless2) GetImage() (image.Image, error) {
return subtypes.DecodeImageBits(t.ZlibData, int(t.Width), int(t.Height), t.Format, int(t.ColorTableSize)+1, true)
}
func (t *DefineBitsLossless2) Code() Code {
return RecordDefineBitsLossless2
}

26
tag/DefineFont.go Normal file
View file

@ -0,0 +1,26 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineFont struct {
_ struct{} `swfFlags:"root"`
FontId uint16
NumGlyphsEntries uint16
OffsetTable []uint16 `swfCount:"TableLength()"`
ShapeTable []subtypes.SHAPE `swfCount:"TableLength()"`
}
func (t *DefineFont) Scale() float64 {
return 1024
}
func (t *DefineFont) TableLength(ctx types.ReaderContext) uint64 {
return uint64(t.NumGlyphsEntries / 2)
}
func (t *DefineFont) Code() Code {
return RecordDefineFont
}

52
tag/DefineFont2.go Normal file
View file

@ -0,0 +1,52 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineFont2 struct {
_ struct{} `swfFlags:"root"`
FontId uint16
Flag struct {
HasLayout bool
ShiftJIS bool
SmallText bool
ANSI bool
WideOffsets bool
WideCodes bool
Italic bool
Bold bool
}
LanguageCode uint8
FontNameLen uint8
FontName []byte `swfCount:"FontNameLen"`
NumGlyphs uint16
OffsetTable16 []uint16 `swfCount:"NumGlyphs" swfCondition:"!Flag.WideOffsets"`
CodeTableOffset16 uint16 `swfCondition:"!Flag.WideOffsets"`
OffsetTable32 []uint32 `swfCount:"NumGlyphs" swfCondition:"Flag.WideOffsets"`
CodeTableOffset32 uint32 `swfCondition:"Flag.WideOffsets"`
ShapeTable []subtypes.SHAPE `swfCount:"NumGlyphs"`
CodeTable8 []uint8 `swfCount:"NumGlyphs" swfCondition:"!Flag.WideCodes"`
CodeTable16 []uint16 `swfCount:"NumGlyphs" swfCondition:"Flag.WideCodes"`
FontAscent uint16 `swfCondition:"Flag.HasLayout"`
FontDescent uint16 `swfCondition:"Flag.HasLayout"`
FontLeading int16 `swfCondition:"Flag.HasLayout"`
FontAdvanceTable []int16 `swfCount:"NumGlyphs" swfCondition:"Flag.HasLayout"`
FontBoundsTable []types.RECT `swfCount:"NumGlyphs" swfCondition:"Flag.HasLayout"`
KerningCount uint16 `swfCondition:"Flag.HasLayout"`
KerningTable []subtypes.KERNINGRECORD `swfCount:"KerningCount" swfCondition:"Flag.HasLayout"`
}
func (t *DefineFont2) Scale() float64 {
return 1024
}
func (t *DefineFont2) Code() Code {
return RecordDefineFont2
}

15
tag/DefineFont3.go Normal file
View file

@ -0,0 +1,15 @@
package tag
type DefineFont3 struct {
_ struct{} `swfFlags:"root"`
// DefineFont2 Equal except that DefineFont2.Flag.WideCodes must be true
DefineFont2
}
func (t *DefineFont3) Scale() float64 {
return 1024 * 20
}
func (t *DefineFont3) Code() Code {
return RecordDefineFont3
}

26
tag/DefineFont4.go Normal file
View file

@ -0,0 +1,26 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineFont4 struct {
_ struct{} `swfFlags:"root"`
FontId uint16
Flag struct {
Reserved uint8 `swfBits:",5"`
HasFontData bool
Italic bool
Bold bool
}
Name string
FontData types.UntilEndBytes `swfCondition:"Flag.HasFontData"`
}
func (t *DefineFont4) Scale() float64 {
return 1024 * 20
}
func (t *DefineFont4) Code() Code {
return RecordDefineFont4
}

View file

@ -0,0 +1,18 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineFontAlignZones struct {
_ struct{} `swfFlags:"root"`
FontId uint16
CSMTableHint uint8 `swfBits:",2"`
Reserved uint8 `swfBits:",6"`
ZoneTable types.UntilEnd[subtypes.ZONERECORD]
}
func (t *DefineFontAlignZones) Code() Code {
return RecordDefineFontAlignZones
}

27
tag/DefineFontInfo.go Normal file
View file

@ -0,0 +1,27 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineFontInfo struct {
_ struct{} `swfFlags:"root"`
FontId uint16
FontNameLen uint8
FontName []byte `swfCount:"FontNameLen"`
Flag struct {
Reserved uint8 `swfBits:",2"`
SmallText bool
ShiftJIS bool
ANSI bool
Italic bool
Bold bool
WideCodes bool
}
CodeTable8 types.UntilEnd[uint8] `swfCondition:"!Flag.WideCodes"`
CodeTable16 types.UntilEnd[uint16] `swfCondition:"Flag.WideCodes"`
}
func (t *DefineFontInfo) Code() Code {
return RecordDefineFontInfo
}

28
tag/DefineFontInfo2.go Normal file
View file

@ -0,0 +1,28 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineFontInfo2 struct {
_ struct{} `swfFlags:"root"`
FontId uint16
FontNameLen uint8
FontName []byte `swfCount:"FontNameLen"`
Flag struct {
Reserved uint8 `swfBits:",2"`
SmallText bool
ShiftJIS bool
ANSI bool
Italic bool
Bold bool
WideCodes bool
}
LanguageCode uint8
CodeTable8 types.UntilEnd[uint8] `swfCondition:"!Flag.WideCodes"`
CodeTable16 types.UntilEnd[uint16] `swfCondition:"Flag.WideCodes"`
}
func (t *DefineFontInfo2) Code() Code {
return RecordDefineFontInfo2
}

12
tag/DefineFontName.go Normal file
View file

@ -0,0 +1,12 @@
package tag
type DefineFontName struct {
_ struct{} `swfFlags:"root"`
FontId uint16
Name string
Copyright string
}
func (t *DefineFontName) Code() Code {
return RecordDefineFontName
}

20
tag/DefineMorphShape.go Normal file
View file

@ -0,0 +1,20 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineMorphShape struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
StartBounds, EndBounds types.RECT
Offset uint32
MorphFillStyles subtypes.MORPHFILLSTYLEARRAY
MorphLineStyles subtypes.MORPHLINESTYLEARRAY
StartEdges, EndEdges subtypes.SHAPE
}
func (t *DefineMorphShape) Code() Code {
return RecordDefineMorphShape
}

24
tag/DefineMorphShape2.go Normal file
View file

@ -0,0 +1,24 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineMorphShape2 struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
StartBounds, EndBounds types.RECT
StartEdgeBounds, EndEdgeBounds types.RECT
Reserved uint8 `swfBits:",6"`
UsesNonScalingStrokes bool
UsesScalingStrokes bool
Offset uint32
MorphFillStyles subtypes.MORPHFILLSTYLEARRAY
MorphLineStyles subtypes.MORPHLINESTYLEARRAY `swfFlags:"MorphShape2"`
StartEdges, EndEdges subtypes.SHAPE
}
func (t *DefineMorphShape2) Code() Code {
return RecordDefineMorphShape2
}

12
tag/DefineScalingGrid.go Normal file
View file

@ -0,0 +1,12 @@
package tag
import "git.gammaspectra.live/WeebDataHoarder/swf-go/types"
type DefineScalingGrid struct {
CharacterId uint16
Splitter types.RECT
}
func (t *DefineScalingGrid) Code() Code {
return RecordDefineScalingGrid
}

View file

@ -0,0 +1,21 @@
package tag
type DefineSceneAndFrameLabelData struct {
_ struct{} `swfFlags:"root"`
SceneCount uint32 `swfFlags:"encoded"`
Scenes []struct {
Offset uint32 `swfFlags:"encoded"`
Name string
} `swfCount:"SceneCount"`
FrameLabelCount uint32 `swfFlags:"encoded"`
FrameLabels []struct {
FrameNumber uint32 `swfFlags:"encoded"`
Label string
} `swfCount:"FrameLabelCount"`
}
func (t *DefineSceneAndFrameLabelData) Code() Code {
return RecordDefineSceneAndFrameLabelData
}

17
tag/DefineShape.go Normal file
View file

@ -0,0 +1,17 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineShape struct {
_ struct{} `swfFlags:"root"`
ShapeId uint16
ShapeBounds types.RECT
Shapes subtypes.SHAPEWITHSTYLE
}
func (t *DefineShape) Code() Code {
return RecordDefineShape
}

17
tag/DefineShape2.go Normal file
View file

@ -0,0 +1,17 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineShape2 struct {
_ struct{} `swfFlags:"root,align"`
ShapeId uint16
ShapeBounds types.RECT
Shapes subtypes.SHAPEWITHSTYLE
}
func (t *DefineShape2) Code() Code {
return RecordDefineShape2
}

17
tag/DefineShape3.go Normal file
View file

@ -0,0 +1,17 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineShape3 struct {
_ struct{} `swfFlags:"root,align"`
ShapeId uint16
ShapeBounds types.RECT
Shapes subtypes.SHAPEWITHSTYLE `swfFlags:"Shape3"`
}
func (t *DefineShape3) Code() Code {
return RecordDefineShape3
}

22
tag/DefineShape4.go Normal file
View file

@ -0,0 +1,22 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineShape4 struct {
_ struct{} `swfFlags:"root,align"`
ShapeId uint16
ShapeBounds types.RECT
EdgeBounds types.RECT
Reserved uint8 `swfBits:",5"`
UsesFillWindingRule bool
UsesNonScalingStrokes bool
UsesScalingStrokes bool
Shapes subtypes.SHAPEWITHSTYLE `swfFlags:"Shape4"`
}
func (t *DefineShape4) Code() Code {
return RecordDefineShape4
}

52
tag/DefineSprite.go Normal file
View file

@ -0,0 +1,52 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineSprite struct {
_ struct{} `swfFlags:"root"`
SpriteId uint16
FrameCount uint16
ControlTags []Tag
}
func (t *DefineSprite) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
err = types.ReadU16(r, &t.SpriteId)
if err != nil {
return err
}
err = types.ReadU16(r, &t.FrameCount)
for {
record := &Record{}
err = types.ReadType(r, types.ReaderContext{
Version: ctx.Version,
}, record)
if err != nil {
return err
}
readTag, err := record.Decode()
if err != nil {
return err
}
if readTag == nil {
//not decoded
continue
}
t.ControlTags = append(t.ControlTags, readTag)
if readTag.Code() == RecordEnd {
break
}
}
return nil
}
func (t *DefineSprite) Code() Code {
return RecordDefineSprite
}

20
tag/DefineText.go Normal file
View file

@ -0,0 +1,20 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineText struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
Bounds types.RECT
Matrix types.MATRIX
GlyphBits uint8
AdvanceBits uint8
TextRecords subtypes.TEXTRECORDS
}
func (t *DefineText) Code() Code {
return RecordDefineText
}

20
tag/DefineText2.go Normal file
View file

@ -0,0 +1,20 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DefineText2 struct {
_ struct{} `swfFlags:"root"`
CharacterId uint16
Bounds types.RECT
Matrix types.MATRIX
GlyphBits uint8
AdvanceBits uint8
TextRecords subtypes.TEXTRECORDS `swfFlags:"Text2"`
}
func (t *DefineText2) Code() Code {
return RecordDefineText2
}

30
tag/DoAction.go Normal file
View file

@ -0,0 +1,30 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DoAction struct {
Actions []subtypes.ACTIONRECORD
}
func (t *DoAction) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
for {
var record subtypes.ACTIONRECORD
err = types.ReadType(r, ctx, &record)
if err != nil {
return err
}
if record.ActionCode == 0 {
break
}
t.Actions = append(t.Actions, record)
}
return nil
}
func (t *DoAction) Code() Code {
return RecordDoAction
}

35
tag/DoInitAction.go Normal file
View file

@ -0,0 +1,35 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type DoInitAction struct {
SpriteID uint16
Actions []subtypes.ACTIONRECORD
}
func (t *DoInitAction) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
err = types.ReadU16(r, &t.SpriteID)
if err != nil {
return err
}
for {
var record subtypes.ACTIONRECORD
err = types.ReadType(r, ctx, &record)
if err != nil {
return err
}
if record.ActionCode == 0 {
break
}
t.Actions = append(t.Actions, record)
}
return nil
}
func (t *DoInitAction) Code() Code {
return RecordDoInitAction
}

8
tag/End.go Normal file
View file

@ -0,0 +1,8 @@
package tag
type End struct {
}
func (t *End) Code() Code {
return RecordEnd
}

16
tag/FileAttributes.go Normal file
View file

@ -0,0 +1,16 @@
package tag
type FileAttributes struct {
Reserved1 uint8 `swfBits:",1"`
UseDirectBlit bool
UseGPU bool
HasMetadata bool
ActionScript3 bool
Reserved2 uint8 `swfBits:",2"`
UseNetwork bool
Reserved uint32 `swfBits:",24"`
}
func (s *FileAttributes) Code() Code {
return RecordFileAttributes
}

33
tag/FrameLabel.go Normal file
View file

@ -0,0 +1,33 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"io"
)
type FrameLabel struct {
Name string
HasAnchor bool
Anchor uint8
}
func (t *FrameLabel) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
t.Name, err = types.ReadNullTerminatedString(r, ctx.Version)
if err != nil {
return err
}
r.Align()
data, err := io.ReadAll(r)
if err != nil {
return err
}
if len(data) > 0 {
t.Anchor = data[0]
t.HasAnchor = true
}
return nil
}
func (t *FrameLabel) Code() Code {
return RecordFrameLabel
}

14
tag/JPEGTables.go Normal file
View file

@ -0,0 +1,14 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type JPEGTables struct {
_ struct{} `swfFlags:"root"`
Data types.UntilEndBytes
}
func (t *JPEGTables) Code() Code {
return RecordJPEGTables
}

9
tag/Metadata.go Normal file
View file

@ -0,0 +1,9 @@
package tag
type Metadata struct {
Metadata string
}
func (t *Metadata) Code() Code {
return RecordMetadata
}

52
tag/PlaceObject.go Normal file
View file

@ -0,0 +1,52 @@
package tag
import (
"bytes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"github.com/icza/bitio"
"io"
)
type PlaceObject struct {
Flag struct {
HasColorTransform bool
}
CharacterId uint16
Depth uint16
Matrix types.MATRIX
ColorTransform *types.CXFORM
}
func (t *PlaceObject) SWFRead(r types.DataReader, ctx types.ReaderContext) (err error) {
err = types.ReadU16(r, &t.CharacterId)
if err != nil {
return err
}
err = types.ReadU16(r, &t.Depth)
if err != nil {
return err
}
err = types.ReadType(r, ctx, &t.Matrix)
if err != nil {
return err
}
r.Align()
data, err := io.ReadAll(r)
if err != nil {
return err
}
if len(data) > 0 {
ct := &types.CXFORM{}
err = types.ReadType(bitio.NewReader(bytes.NewReader(data)), ctx, ct)
if err != nil {
return err
}
t.ColorTransform = ct
t.Flag.HasColorTransform = true
}
return nil
}
func (t *PlaceObject) Code() Code {
return RecordPlaceObject
}

32
tag/PlaceObject2.go Normal file
View file

@ -0,0 +1,32 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type PlaceObject2 struct {
_ struct{} `swfFlags:"root,align"`
Flag struct {
HasClipActions bool
HasClipDepth bool
HasName bool
HasRatio bool
HasColorTransform bool
HasMatrix bool
HasCharacter bool
Move bool
}
Depth uint16
CharacterId uint16 `swfCondition:"Flag.HasCharacter"`
Matrix types.MATRIX `swfCondition:"Flag.HasMatrix"`
ColorTransform types.CXFORMWITHALPHA `swfCondition:"Flag.HasColorTransform"`
Ratio uint16 `swfCondition:"Flag.HasRatio"`
Name string `swfCondition:"Flag.HasName"`
ClipDepth uint16 `swfCondition:"Flag.HasClipDepth"`
ClipActions subtypes.CLIPACTIONS `swfCondition:"Flag.HasClipActions"`
}
func (t *PlaceObject2) Code() Code {
return RecordPlaceObject2
}

70
tag/PlaceObject3.go Normal file
View file

@ -0,0 +1,70 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type BlendMode uint8
const (
BlendNormal0 = BlendMode(iota)
BlendNormal1
BlendLayer
BlendMultiply
BlendScreen
BlendLighten
BlendDarken
BlendDifference
BlendAdd
BlendSubtract
BlendInvert
BlendAlpha
BlendErase
BlendOverlay
BlenHardlight
)
type PlaceObject3 struct {
_ struct{} `swfFlags:"root,align"`
Flag struct {
HasClipActions bool
HasClipDepth bool
HasName bool
HasRatio bool
HasColorTransform bool
HasMatrix bool
HasCharacter bool
Move bool
Reserved bool
OpaqueBackground bool
HasVisible bool
HasImage bool
HasClassName bool
HasCacheAsBitmap bool
HasBlendMode bool
HasFilterList bool
}
Depth uint16
ClassName string `swfCondition:"HasClassName()"`
CharacterId uint16 `swfCondition:"Flag.HasCharacter"`
Matrix types.MATRIX `swfCondition:"Flag.HasMatrix"`
ColorTransform types.CXFORMWITHALPHA `swfCondition:"Flag.HasColorTransform"`
Ratio uint16 `swfCondition:"Flag.HasRatio"`
Name string `swfCondition:"Flag.HasName"`
ClipDepth uint16 `swfCondition:"Flag.HasClipDepth"`
SurfaceFilterList subtypes.FILTERLIST `swfCondition:"Flag.HasFilterList"`
BlendMode BlendMode `swfCondition:"Flag.HasBlendMode"`
BitmapCache uint8 `swfCondition:"Flag.HasCacheAsBitmap"`
Visible uint8 `swfCondition:"Flag.HasVisible"`
BackgroundColor types.RGBA `swfCondition:"Flag.OpaqueBackground"`
ClipActions subtypes.CLIPACTIONS `swfCondition:"Flag.HasClipActions"`
}
func (t *PlaceObject3) HasClassName(ctx types.ReaderContext) bool {
return t.Flag.HasClassName || (t.Flag.HasName && t.Flag.HasImage)
}
func (t *PlaceObject3) Code() Code {
return RecordPlaceObject3
}

51
tag/PlaceObject4.go Normal file
View file

@ -0,0 +1,51 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type PlaceObject4 struct {
_ struct{} `swfFlags:"root,align"`
Flag struct {
HasClipActions bool
HasClipDepth bool
HasName bool
HasRatio bool
HasColorTransform bool
HasMatrix bool
HasCharacter bool
Move bool
Reserved bool
OpaqueBackground bool
HasVisible bool
HasImage bool
HasClassName bool
HasCacheAsBitmap bool
HasBlendMode bool
HasFilterList bool
}
Depth uint16
ClassName string `swfCondition:"HasClassName()"`
CharacterId uint16 `swfCondition:"Flag.HasCharacter"`
Matrix types.MATRIX `swfCondition:"Flag.HasMatrix"`
ColorTransform types.CXFORMWITHALPHA `swfCondition:"Flag.HasColorTransform"`
Ratio uint16 `swfCondition:"Flag.HasRatio"`
Name string `swfCondition:"Flag.HasName"`
ClipDepth uint16 `swfCondition:"Flag.HasClipDepth"`
SurfaceFilterList subtypes.FILTERLIST `swfCondition:"Flag.HasFilterList"`
BlendMode BlendMode `swfCondition:"Flag.HasBlendMode"`
BitmapCache uint8 `swfCondition:"Flag.HasCacheAsBitmap"`
Visible uint8 `swfCondition:"Flag.HasVisible"`
BackgroundColor types.RGBA `swfCondition:"Flag.OpaqueBackground"`
ClipActions subtypes.CLIPACTIONS `swfCondition:"Flag.HasClipActions"`
AMFData types.UntilEndBytes
}
func (t *PlaceObject4) HasClassName(ctx types.ReaderContext) bool {
return t.Flag.HasClassName || (t.Flag.HasName && t.Flag.HasImage)
}
func (t *PlaceObject4) Code() Code {
return RecordPlaceObject4
}

8
tag/Protect.go Normal file
View file

@ -0,0 +1,8 @@
package tag
type Protect struct {
}
func (t *Protect) Code() Code {
return RecordProtect
}

10
tag/RemoveObject.go Normal file
View file

@ -0,0 +1,10 @@
package tag
type RemoveObject struct {
CharacterId uint16
Depth uint16
}
func (t *RemoveObject) Code() Code {
return RecordRemoveObject
}

9
tag/RemoveObject2.go Normal file
View file

@ -0,0 +1,9 @@
package tag
type RemoveObject2 struct {
Depth uint16
}
func (t *RemoveObject2) Code() Code {
return RecordRemoveObject2
}

11
tag/SetBackgroundColor.go Normal file
View file

@ -0,0 +1,11 @@
package tag
import "git.gammaspectra.live/WeebDataHoarder/swf-go/types"
type SetBackgroundColor struct {
BackgroundColor types.RGB
}
func (s *SetBackgroundColor) Code() Code {
return RecordSetBackgroundColor
}

8
tag/ShowFrame.go Normal file
View file

@ -0,0 +1,8 @@
package tag
type ShowFrame struct {
}
func (t *ShowFrame) Code() Code {
return RecordShowFrame
}

152
tag/Sound.go Normal file
View file

@ -0,0 +1,152 @@
package tag
import (
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
)
type SoundFormat uint8
const (
SoundFormatPCMNative = SoundFormat(iota)
SoundFormatADPCM
SoundFormatMP3
SoundFormatPCMLittle
SoundFormatNellymoser16KHz
SoundFormatNellymoser8KHz
SoundFormatNellymoser
_
_
_
_
SoundFormatSpeex
)
type SoundRate uint8
const (
SoundRate5512Hz = SoundRate(iota)
SoundRate11025Hz
SoundRate22050Hz
SoundRate44100Hz
)
type SOUNDINFO struct {
_ struct{} `swfFlags:"root"`
Flag struct {
Reserved uint8 `swfBits:",2"`
SyncStop bool
SyncNoMultiple bool
HasEnvelope bool
HasLoops bool
HasOutPoint bool
HasInPoint bool
}
InPoint uint32 `swfCondition:"Flag.HasInPoint"`
OutPoint uint32 `swfCondition:"Flag.HasOutPoint"`
LoopCount uint16 `swfCondition:"Flag.HasLoops"`
EnvPoints uint8 `swfCondition:"Flag.HasEnvelope"`
EnvelopeRecords []SOUNDENVELOPE `swfCondition:"Flag.HasEnvelope" swfCount:"EnvPoints"`
}
type SOUNDENVELOPE struct {
Pos44 uint32
LeftLevel uint16
RightLevel uint16
}
type StartSound struct {
_ struct{} `swfFlags:"root"`
SoundId uint16
SoundInfo SOUNDINFO
}
func (t *StartSound) Code() Code {
return RecordStartSound
}
type StartSound2 struct {
_ struct{} `swfFlags:"root"`
SoundId uint16
SoundClassName string
SoundInfo SOUNDINFO
}
func (t *StartSound2) Code() Code {
return RecordStartSound2
}
type DefineSound struct {
_ struct{} `swfFlags:"root"`
SoundId uint16
SoundFormat SoundFormat `swfBits:",4"`
SoundRate SoundRate `swfBits:",2"`
SoundSize uint8 `swfBits:",1"`
IsStereo bool
SoundSampleCount uint32
SoundData types.UntilEndBytes
}
func (t *DefineSound) Code() Code {
return RecordDefineSound
}
type SoundCompression uint8
const (
_ = SoundCompression(iota)
SoundCompressionADPCM
SoundCompressionMP3
)
type SoundStreamHead struct {
_ struct{} `swfFlags:"root"`
Reserved uint8 `swfBits:",4"`
PlaybackSoundRate SoundRate `swfBits:",2"`
PlaybackSoundSize uint8 `swfBits:",1"`
PlaybackIsStereo bool
StreamSoundCompression SoundCompression `swfBits:",4"`
StreamSoundRate SoundRate `swfBits:",2"`
StreamSoundSize uint8 `swfBits:",1"`
StreamIsStereo bool
StreamSampleCount uint16
LatencySeek int16 `swfCondition:"HasLatencySeek()"`
}
func (t *SoundStreamHead) HasLatencySeek(ctx types.ReaderContext) bool {
return t.StreamSoundCompression == SoundCompressionMP3
}
func (t *SoundStreamHead) Code() Code {
return RecordSoundStreamHead
}
type SoundStreamHead2 struct {
_ struct{} `swfFlags:"root"`
Reserved uint8 `swfBits:",4"`
PlaybackSoundRate SoundRate `swfBits:",2"`
PlaybackSoundSize uint8 `swfBits:",1"`
PlaybackIsStereo bool
StreamSoundFormat SoundFormat `swfBits:",4"`
StreamSoundRate SoundRate `swfBits:",2"`
StreamSoundSize uint8 `swfBits:",1"`
StreamIsStereo bool
StreamSampleCount uint16
LatencySeek int16 `swfCondition:"HasLatencySeek()"`
}
func (t *SoundStreamHead2) HasLatencySeek(ctx types.ReaderContext) bool {
return t.StreamSoundFormat == SoundFormatMP3
}
func (t *SoundStreamHead2) Code() Code {
return RecordSoundStreamHead2
}
type SoundStreamBlock struct {
_ struct{} `swfFlags:"root"`
Data types.UntilEndBytes
}
func (t *SoundStreamBlock) Code() Code {
return RecordSoundStreamBlock
}

162
tag/Tag.go Normal file
View file

@ -0,0 +1,162 @@
package tag
import (
"bytes"
"errors"
"git.gammaspectra.live/WeebDataHoarder/swf-go/types"
"github.com/icza/bitio"
)
type Tag interface {
Code() Code
}
type Record struct {
_ struct{} `swfFlags:"root,align"`
ctx types.ReaderContext `swfFlags:"skip"`
TagCodeAndLength uint16
ExtraLength uint32 `swfCondition:"HasExtraLength()"`
Data []byte `swfCount:"DataLength()"`
}
func (r *Record) SWFDefault(ctx types.ReaderContext) {
r.ctx = ctx
}
func (r *Record) HasExtraLength(ctx types.ReaderContext) bool {
return (r.TagCodeAndLength & 0x3f) == 0x3f
}
func (r *Record) DataLength(ctx types.ReaderContext) uint64 {
if (r.TagCodeAndLength & 0x3f) == 0x3f {
return uint64(r.ExtraLength)
}
return uint64(r.TagCodeAndLength & 0x3f)
}
func (r *Record) Decode() (readTag Tag, err error) {
bitReader := bitio.NewReader(bytes.NewReader(r.Data))
switch r.Code() {
case RecordShowFrame:
readTag = &ShowFrame{}
case RecordPlaceObject:
readTag = &PlaceObject{}
case RecordRemoveObject:
readTag = &RemoveObject{}
case RecordPlaceObject2:
readTag = &PlaceObject2{}
case RecordRemoveObject2:
readTag = &RemoveObject2{}
case RecordPlaceObject3:
readTag = &PlaceObject3{}
case RecordEnd:
readTag = &End{}
case RecordSetBackgroundColor:
readTag = &SetBackgroundColor{}
case RecordProtect:
readTag = &Protect{}
case RecordFrameLabel:
readTag = &FrameLabel{}
case RecordDefineShape:
readTag = &DefineShape{}
case RecordDoAction:
readTag = &DoAction{}
case RecordDefineShape2:
readTag = &DefineShape2{}
case RecordDefineShape3:
readTag = &DefineShape3{}
case RecordDoInitAction:
readTag = &DoInitAction{}
case RecordFileAttributes:
readTag = &FileAttributes{}
case RecordMetadata:
readTag = &Metadata{}
case RecordDefineScalingGrid:
readTag = &DefineScalingGrid{}
case RecordDefineShape4:
readTag = &DefineShape4{}
case RecordDefineSceneAndFrameLabelData:
readTag = &DefineSceneAndFrameLabelData{}
case RecordDefineBits:
readTag = &DefineBits{}
case RecordJPEGTables:
readTag = &JPEGTables{}
case RecordDefineBitsJPEG2:
readTag = &DefineBitsJPEG2{}
case RecordDefineBitsJPEG3:
readTag = &DefineBitsJPEG3{}
case RecordDefineMorphShape:
readTag = &DefineMorphShape{}
case RecordDefineMorphShape2:
readTag = &DefineMorphShape2{}
case RecordDefineBitsJPEG4:
readTag = &DefineBitsJPEG4{}
case RecordDefineBitsLossless:
readTag = &DefineBitsLossless{}
case RecordDefineBitsLossless2:
readTag = &DefineBitsLossless2{}
case RecordDefineSprite:
readTag = &DefineSprite{}
case RecordDefineSound:
readTag = &DefineSound{}
case RecordSoundStreamHead:
readTag = &SoundStreamHead{}
case RecordSoundStreamHead2:
readTag = &SoundStreamHead2{}
case RecordSoundStreamBlock:
readTag = &SoundStreamBlock{}
case RecordStartSound:
readTag = &StartSound{}
case RecordStartSound2:
readTag = &StartSound2{}
case RecordDefineFont:
readTag = &DefineFont{}
case RecordDefineFontInfo:
readTag = &DefineFontInfo{}
case RecordDefineFont2:
readTag = &DefineFont2{}
case RecordDefineFontInfo2:
readTag = &DefineFontInfo2{}
case RecordDefineText:
readTag = &DefineText{}
case RecordDefineText2:
readTag = &DefineText2{}
case RecordDefineFontAlignZones:
readTag = &DefineFontAlignZones{}
case RecordDefineFont3:
readTag = &DefineFont3{}
case RecordDefineFontName:
readTag = &DefineFontName{}
case RecordDefineFont4:
readTag = &DefineFont4{}
case RecordPlaceObject4:
readTag = &PlaceObject4{}
case RecordExportAssets:
case RecordImportAssets:
case RecordImportAssets2:
case RecordSymbolClass:
}
if readTag == nil {
return nil, errors.New("could not decode tag")
}
err = types.ReadType(bitReader, types.ReaderContext{
Version: r.ctx.Version,
}, readTag)
if err != nil {
return nil, err
}
if readTag.Code() != r.Code() {
return nil, errors.New("mismatched decoded tag code")
}
return readTag, nil
}
func (r *Record) Code() Code {
return Code(r.TagCodeAndLength >> 6)
}

101
tag/code.go Normal file
View file

@ -0,0 +1,101 @@
package tag
type Code uint16
const (
RecordEnd = Code(iota)
RecordShowFrame
RecordDefineShape
_
RecordPlaceObject
RecordRemoveObject
RecordDefineBits
_
RecordJPEGTables
RecordSetBackgroundColor
RecordDefineFont
RecordDefineText
RecordDoAction
RecordDefineFontInfo
RecordDefineSound
RecordStartSound
_
_
RecordSoundStreamHead
RecordSoundStreamBlock
RecordDefineBitsLossless
RecordDefineBitsJPEG2
RecordDefineShape2
_
RecordProtect
_
RecordPlaceObject2
_
RecordRemoveObject2
_
_
_
RecordDefineShape3
RecordDefineText2
_
RecordDefineBitsJPEG3
RecordDefineBitsLossless2
_
_
RecordDefineSprite
_
_
_
RecordFrameLabel
_
RecordSoundStreamHead2
RecordDefineMorphShape
_
RecordDefineFont2
_
_
_
_
_
_
_
RecordExportAssets
RecordImportAssets
_
RecordDoInitAction
_
_
RecordDefineFontInfo2
_
_
_
_
_
_
RecordFileAttributes
RecordPlaceObject3
RecordImportAssets2
_
RecordDefineFontAlignZones
_
RecordDefineFont3
RecordSymbolClass
RecordMetadata
RecordDefineScalingGrid
_
_
_
_
RecordDefineShape4
RecordDefineMorphShape2
_
RecordDefineSceneAndFrameLabelData
_
RecordDefineFontName
RecordStartSound2
RecordDefineBitsJPEG4
RecordDefineFont4
_
_
RecordPlaceObject4
)

24
types/ARGB.go Normal file
View file

@ -0,0 +1,24 @@
package types
type ARGB struct {
Alpha uint8
Red uint8
Green uint8
Blue uint8
}
func (argb ARGB) R() uint8 {
return argb.Red
}
func (argb ARGB) G() uint8 {
return argb.Green
}
func (argb ARGB) B() uint8 {
return argb.Blue
}
func (argb ARGB) A() uint8 {
return argb.Alpha
}

27
types/CXFORM.go Normal file
View file

@ -0,0 +1,27 @@
package types
type CXFORM struct {
_ struct{} `swfFlags:"root,alignend"`
Flag struct {
HasAddTerms bool
HasMultTerms bool
}
NBits uint8 `swfBits:",4"`
Multiply struct {
Red Fixed8 `swfBits:"NBits,fixed"`
Green Fixed8 `swfBits:"NBits,fixed"`
Blue Fixed8 `swfBits:"NBits,fixed"`
} `swfCondition:"Flag.HasMultTerms"`
Add struct {
Red int16 `swfBits:"NBits,signed"`
Green int16 `swfBits:"NBits,signed"`
Blue int16 `swfBits:"NBits,signed"`
} `swfCondition:"Flag.HasAddTerms"`
}
func (cf *CXFORM) SWFDefault(ctx ReaderContext) {
*cf = CXFORM{}
cf.Multiply.Red = 256
cf.Multiply.Green = 256
cf.Multiply.Blue = 256
}

30
types/CXFORMWITHALPHA.go Normal file
View file

@ -0,0 +1,30 @@
package types
type CXFORMWITHALPHA struct {
_ struct{} `swfFlags:"root,alignend"`
Flag struct {
HasAddTerms bool
HasMultTerms bool
}
NBits uint8 `swfBits:",4"`
Multiply struct {
Red Fixed8 `swfBits:"NBits,fixed"`
Green Fixed8 `swfBits:"NBits,fixed"`
Blue Fixed8 `swfBits:"NBits,fixed"`
Alpha Fixed8 `swfBits:"NBits,fixed"`
} `swfCondition:"Flag.HasMultTerms"`
Add struct {
Red int16 `swfBits:"NBits,signed"`
Green int16 `swfBits:"NBits,signed"`
Blue int16 `swfBits:"NBits,signed"`
Alpha int16 `swfBits:"NBits,signed"`
} `swfCondition:"Flag.HasAddTerms"`
}
func (cf *CXFORMWITHALPHA) SWFDefault(ctx ReaderContext) {
*cf = CXFORMWITHALPHA{}
cf.Multiply.Red = 256
cf.Multiply.Green = 256
cf.Multiply.Blue = 256
cf.Multiply.Alpha = 256
}

8
types/Color.go Normal file
View file

@ -0,0 +1,8 @@
package types
type Color interface {
R() uint8
G() uint8
B() uint8
A() uint8
}

19
types/MATRIX.go Normal file
View file

@ -0,0 +1,19 @@
package types
type MATRIX struct {
_ struct{} `swfFlags:"root,alignend"`
HasScale bool
NScaleBits uint8 `swfCondition:"HasScale" swfBits:",5"`
ScaleX, ScaleY Fixed16 `swfCondition:"HasScale" swfBits:"NScaleBits,fixed"`
HasRotate bool
NRotateBits uint8 `swfCondition:"HasRotate" swfBits:",5"`
RotateSkew0, RotateSkew1 Fixed16 `swfCondition:"HasRotate" swfBits:"NRotateBits,fixed"`
NTranslateBits uint8 `swfBits:",5"`
TranslateX, TranslateY Twip `swfBits:"NTranslateBits,signed"`
}
func (matrix *MATRIX) SWFDefault(ctx ReaderContext) {
*matrix = MATRIX{}
matrix.ScaleX = 1 << 16
matrix.ScaleY = 1 << 16
}

10
types/RECT.go Normal file
View file

@ -0,0 +1,10 @@
package types
type RECT struct {
_ struct{} `swfFlags:"root,alignend"`
NBits uint8 `swfBits:",5"`
Xmin Twip `swfBits:"NBits,signed"`
Xmax Twip `swfBits:"NBits,signed"`
Ymin Twip `swfBits:"NBits,signed"`
Ymax Twip `swfBits:"NBits,signed"`
}

23
types/RGB.go Normal file
View file

@ -0,0 +1,23 @@
package types
type RGB struct {
Red uint8
Green uint8
Blue uint8
}
func (rgb RGB) R() uint8 {
return rgb.Red
}
func (rgb RGB) G() uint8 {
return rgb.Green
}
func (rgb RGB) B() uint8 {
return rgb.Blue
}
func (rgb RGB) A() uint8 {
return 255
}

24
types/RGBA.go Normal file
View file

@ -0,0 +1,24 @@
package types
type RGBA struct {
Red uint8
Green uint8
Blue uint8
Alpha uint8
}
func (rgba RGBA) R() uint8 {
return rgba.Red
}
func (rgba RGBA) G() uint8 {
return rgba.Green
}
func (rgba RGBA) B() uint8 {
return rgba.Blue
}
func (rgba RGBA) A() uint8 {
return rgba.Alpha
}

26
types/UntilEnd.go Normal file
View file

@ -0,0 +1,26 @@
package types
import (
"errors"
"io"
)
type UntilEndBytes = UntilEnd[byte]
type UntilEnd[T any] []T
func (b *UntilEnd[T]) SWFRead(r DataReader, ctx ReaderContext) (err error) {
for {
var data T
err := ReadType(r, ctx, &data)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
*b = append(*b, data)
}
return nil
}

23
types/fixed.go Normal file
View file

@ -0,0 +1,23 @@
package types
import "math"
type Fixed16 int32
func (t Fixed16) Float64() float64 {
return float64(t) / (math.MaxUint16 + 1)
}
func (t Fixed16) Float32() float32 {
return float32(t) / (math.MaxUint16 + 1)
}
type Fixed8 int16
func (t Fixed8) Float64() float64 {
return float64(t) / (math.MaxUint8 + 1)
}
func (t Fixed8) Float32() float32 {
return float32(t) / (math.MaxUint8 + 1)
}

16
types/float.go Normal file
View file

@ -0,0 +1,16 @@
package types
import (
"github.com/x448/float16"
)
// Float16 TODO: check if proper values
type Float16 float16.Float16
func (f *Float16) SWFRead(r DataReader, ctx ReaderContext) (err error) {
err = ReadU16(r, f)
if err != nil {
return err
}
return nil
}

16
types/header.go Normal file
View file

@ -0,0 +1,16 @@
package types
type HeaderSignature [3]uint8
var SignatureUncompressed = HeaderSignature{'F', 'W', 'S'}
var SignatureCompressedZLIB = HeaderSignature{'C', 'W', 'S'}
var SignatureCompressedLZMA = HeaderSignature{'Z', 'W', 'S'}
type Header struct {
Signature HeaderSignature
Version uint8
FileLength uint32
FrameSize RECT
FrameRate Fixed8
FrameCount uint16
}

727
types/reader.go Normal file
View file

@ -0,0 +1,727 @@
package types
import (
"encoding/binary"
"errors"
"fmt"
"golang.org/x/text/encoding/japanese"
"io"
"math"
"reflect"
"slices"
"strconv"
"strings"
)
const DoParserDebug = false
type DataReader interface {
io.ByteReader
io.Reader
ReadBits(n uint8) (u uint64, err error)
Align() (skipped uint8)
}
type ReaderContext struct {
Version uint8
Root reflect.Value
Flags []string
FieldType reflect.StructField
}
func (ctx ReaderContext) GetNestedType(fields ...string) reflect.Value {
if len(fields) == 2 && fields[0] == "context" {
return reflect.ValueOf(slices.Contains(ctx.Flags, fields[1]))
}
el := ctx.Root
for len(fields) > 0 && fields[0] != "" {
if strings.HasSuffix(fields[0], "()") {
n := strings.TrimSuffix(fields[0], "()")
m := el.Addr().MethodByName(n)
if !m.IsValid() {
m = el.MethodByName(n)
}
el = m
} else {
el = el.FieldByName(fields[0])
}
fields = fields[1:]
}
return el
}
type TypeReader interface {
SWFRead(reader DataReader, ctx ReaderContext) error
}
type TypeDefault interface {
SWFDefault(ctx ReaderContext)
}
type TypeFuncConditional func(ctx ReaderContext) bool
type TypeFuncNumber func(ctx ReaderContext) uint64
func ReadBool(r DataReader) (d bool, err error) {
v, err := r.ReadBits(1)
if err != nil {
return false, err
}
return v == 1, nil
}
func ReadUB[T ~uint | ~uint64 | ~uint32 | ~uint16 | ~uint8](r DataReader, n uint64) (d T, err error) {
v, err := r.ReadBits(uint8(n))
if err != nil {
return 0, err
}
return T(v), nil
}
func ReadSB[T ~int | ~int64 | ~int32 | ~int16 | ~int8](r DataReader, n uint64) (d T, err error) {
v, err := r.ReadBits(uint8(n))
if err != nil {
return 0, err
}
//TODO: check
//sign bit is set
if v&(1<<(n-1)) > 0 {
v |= math.MaxUint64 << (n - 1)
}
return T(v), nil
}
func ReadFB(r DataReader, n uint64) (d int32, err error) {
//TODO: check
v, err := ReadSB[int32](r, n)
if err != nil {
return 0, err
}
return v, nil
}
func ReadU64[T ~uint64](r DataReader, d *T) (err error) {
r.Align()
var buf [8]byte
_, err = io.ReadFull(r, buf[:])
if err != nil {
return err
}
*d = T(binary.LittleEndian.Uint16(buf[:]))
return nil
}
func ReadU32[T ~uint32](r DataReader, d *T) (err error) {
r.Align()
var buf [4]byte
_, err = io.ReadFull(r, buf[:])
if err != nil {
return err
}
*d = T(binary.LittleEndian.Uint32(buf[:]))
return nil
}
func ReadU24[T ~uint32](r DataReader, d *T) (err error) {
r.Align()
var buf [4]byte
_, err = io.ReadFull(r, buf[:3])
if err != nil {
return err
}
*d = T(binary.LittleEndian.Uint32(buf[:]))
return nil
}
func ReadU16[T ~uint16](r DataReader, d *T) (err error) {
r.Align()
var buf [2]byte
_, err = io.ReadFull(r, buf[:])
if err != nil {
return err
}
*d = T(binary.LittleEndian.Uint16(buf[:]))
return nil
}
func ReadU8[T ~uint8](r DataReader, d *T) (err error) {
r.Align()
var buf [1]byte
_, err = io.ReadFull(r, buf[:])
if err != nil {
return err
}
*d = T(buf[0])
return nil
}
func ReadSI64[T ~int64](r DataReader, d *T) (err error) {
var v uint64
err = ReadU64(r, &v)
*d = T(v)
return err
}
func ReadSI32[T ~int32](r DataReader, d *T) (err error) {
var v uint32
err = ReadU32(r, &v)
*d = T(v)
return err
}
func ReadSI16[T ~int16](r DataReader, d *T) (err error) {
var v uint16
err = ReadU16(r, &v)
*d = T(v)
return err
}
func ReadSI8[T ~int8](r DataReader, d *T) (err error) {
var v uint8
err = ReadU8(r, &v)
*d = T(v)
return err
}
func ReadArraySI8[T ~int8](r DataReader, n int) (d []T, err error) {
d = make([]T, n)
for i := range d {
err = ReadSI8(r, &d[i])
if err != nil {
return nil, err
}
}
return d, nil
}
func ReadArraySI16[T ~int16](r DataReader, n int) (d []T, err error) {
d = make([]T, n)
for i := range d {
err = ReadSI16(r, &d[i])
if err != nil {
return nil, err
}
}
return d, nil
}
func ReadArrayU8[T ~uint8](r DataReader, n int) (d []T, err error) {
d = make([]T, n)
for i := range d {
err = ReadU8(r, &d[i])
if err != nil {
return nil, err
}
}
return d, nil
}
func ReadArrayU16[T ~uint16](r DataReader, n int) (d []T, err error) {
d = make([]T, n)
for i := range d {
err = ReadU16(r, &d[i])
if err != nil {
return nil, err
}
}
return d, nil
}
func ReadArrayU24[T ~uint32](r DataReader, n int) (d []T, err error) {
d = make([]T, n)
for i := range d {
err = ReadU24(r, &d[i])
if err != nil {
return nil, err
}
}
return d, nil
}
func ReadArrayU32[T ~uint32](r DataReader, n int) (d []T, err error) {
d = make([]T, n)
for i := range d {
err = ReadU32(r, &d[i])
if err != nil {
return nil, err
}
}
return d, nil
}
func ReadArrayU64[T ~uint64](r DataReader, n int) (d []T, err error) {
d = make([]T, n)
for i := range d {
err = ReadU64(r, &d[i])
if err != nil {
return nil, err
}
}
return d, nil
}
func ReadEncodedU32[T ~uint32](r DataReader) (d T, err error) {
//TODO: verify
r.Align()
v, err := binary.ReadUvarint(r)
if err != nil {
return 0, err
}
return T(v), nil
}
func ReadNullTerminatedString(r DataReader, swfVersion uint8) (d string, err error) {
var v uint8
for {
err = ReadU8[uint8](r, &v)
if err != nil {
if errors.Is(err, io.EOF) {
return "", io.ErrUnexpectedEOF
}
return "", err
}
if v == 0 {
break
}
d += string(v)
}
if swfVersion >= 6 {
//always utf-8
return d, nil
}
//TODO: detect
decoder := japanese.ShiftJIS.NewDecoder()
newD, err := decoder.String(d)
if err != nil {
//probably ascii?
return d, nil
}
return newD, nil
}
var typeReaderReflect = reflect.TypeOf((*TypeReader)(nil)).Elem()
var typeDefaultReflect = reflect.TypeOf((*TypeDefault)(nil)).Elem()
var typeFuncConditionalReflect = reflect.TypeOf((*TypeFuncConditional)(nil)).Elem()
var typeFuncNumberReflect = reflect.TypeOf((*TypeFuncNumber)(nil)).Elem()
func ReadType(r DataReader, ctx ReaderContext, data any) (err error) {
if tr, ok := data.(TypeDefault); ok {
tr.SWFDefault(ctx)
}
if tr, ok := data.(TypeReader); ok {
return tr.SWFRead(r, ctx)
}
dataValue := reflect.ValueOf(data)
if dataValue.Kind() != reflect.Pointer {
return errors.New("not a pointer")
}
if !ctx.Root.IsValid() {
ctx.Root = dataValue.Elem()
}
return ReadTypeInner(r, ctx, data)
}
func ReadTypeInner(r DataReader, ctx ReaderContext, data any) (err error) {
dataValue := reflect.ValueOf(data)
dataType := dataValue.Type()
dataElement := dataValue.Elem()
dataElementType := dataElement.Type()
if DoParserDebug {
fmt.Printf(" reading %s %s(%s) from root %s\n", ctx.FieldType.Name, dataElementType.Name(), dataElementType.Kind().String(), ctx.Root.Type().Name())
}
if tr, ok := data.(TypeDefault); ok {
tr.SWFDefault(ctx)
}
if tr, ok := data.(TypeReader); ok {
return tr.SWFRead(r, ctx)
}
if dataType.Kind() != reflect.Pointer {
return fmt.Errorf("not a pointer: %s is %s", dataType.Name(), dataType.Kind().String())
}
switch dataElementType.Kind() {
case reflect.Struct:
//get struct flags
var flags []string
flagsField, ok := dataElementType.FieldByName("_")
if ok {
flags = strings.Split(flagsField.Tag.Get("swfFlags"), ",")
}
if slices.Contains(flags, "align") {
r.Align()
}
structCtx := ctx
if slices.Contains(flags, "root") {
structCtx.Root = dataElement
}
structCtx.Flags = append(structCtx.Flags, flags...)
var lastBitsCachedPath string
var lastBitsCachedValue uint64
var lastConditionCachedPath string
var lastConditionCachedValue bool
n := dataElementType.NumField()
for i := 0; i < n; i++ {
fieldValue := dataElement.Field(i)
fieldType := dataElementType.Field(i)
if fieldType.Name == "_" {
continue
}
fieldCtx := structCtx
fieldCtx.FieldType = fieldType
fieldFlags := strings.Split(fieldType.Tag.Get("swfFlags"), ",")
if slices.Contains(fieldFlags, "skip") {
continue
}
if slices.Contains(fieldFlags, "encoded") {
value, err := ReadEncodedU32[uint32](r)
if err != nil {
return err
}
fieldValue.SetUint(uint64(value))
continue
}
fieldCtx.Flags = append(fieldCtx.Flags, fieldFlags...)
//Check if we should read this entry or not
if swfTag := fieldType.Tag.Get("swfCondition"); swfTag != "" {
if swfTag == lastConditionCachedPath && lastConditionCachedPath != "" {
if !lastConditionCachedValue {
continue
}
} else {
splits := strings.Split(swfTag, ".")
negate := false
if len(splits) > 0 && strings.HasPrefix(splits[0], "!") {
negate = true
splits[0] = splits[0][1:]
}
el := fieldCtx.GetNestedType(splits...)
switch el.Kind() {
case reflect.Bool:
lastConditionCachedPath = swfTag
lastConditionCachedValue = el.Bool()
if negate {
lastConditionCachedValue = !lastConditionCachedValue
}
if !lastConditionCachedValue {
continue
}
case reflect.Func:
if el.Type().AssignableTo(typeFuncConditionalReflect) {
lastConditionCachedPath = swfTag
lastConditionCachedValue = el.Interface().(func(ctx ReaderContext) bool)(fieldCtx)
if negate {
lastConditionCachedValue = !lastConditionCachedValue
}
if !lastConditionCachedValue {
continue
}
} else {
return fmt.Errorf("invalid conditional method %s", swfTag)
}
default:
return fmt.Errorf("invalid conditional type %s", swfTag)
}
}
}
if slices.Contains(fieldFlags, "align") {
r.Align()
}
if swfTag := fieldType.Tag.Get("swfBits"); swfTag != "" {
var nbits uint64
entries := strings.Split(swfTag, ",")
bitFlags := entries[1:]
if entries[0] == lastBitsCachedPath && lastBitsCachedPath != "" {
nbits = lastBitsCachedValue
} else {
splits := strings.Split(entries[0], ".")
addition := strings.Split(splits[len(splits)-1], "+")
var offset int64
if len(addition) == 2 {
splits[len(splits)-1] = addition[0]
offset, err = strconv.ParseInt(addition[1], 10, 0)
if err != nil {
return err
}
}
el := fieldCtx.GetNestedType(splits...)
if len(splits) == 1 && len(splits[0]) == 0 && len(bitFlags) > 0 {
//numerical fixed
nbits, err = strconv.ParseUint(bitFlags[0], 10, 0)
if err != nil {
return err
}
} else {
switch el.Kind() {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
nbits = el.Uint()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
nbits = uint64(el.Int())
case reflect.Func:
if el.Type().AssignableTo(typeFuncNumberReflect) {
nbits = el.Interface().(func(ctx ReaderContext) uint64)(fieldCtx)
} else {
return fmt.Errorf("invalid nbits method %s", swfTag)
}
default:
return fmt.Errorf("invalid nbits type %s", swfTag)
}
}
nbits = uint64(int64(nbits) + offset)
lastBitsCachedValue = nbits
lastBitsCachedPath = entries[0]
}
if DoParserDebug {
fmt.Printf(" reading %s %s(%s) from struct %s\n", fieldType.Name, fieldType.Type.Name(), fieldType.Type.Kind().String(), dataElementType.Name())
}
if slices.Contains(bitFlags, "signed") {
value, err := ReadSB[int64](r, nbits)
if err != nil {
return err
}
fieldValue.SetInt(value)
} else if slices.Contains(bitFlags, "fixed") {
//TODO: check
value, err := ReadFB(r, nbits)
if err != nil {
return err
}
fieldValue.SetInt(int64(value))
} else {
value, err := ReadUB[uint64](r, nbits)
if err != nil {
return err
}
fieldValue.SetUint(value)
}
continue
}
err = ReadTypeInner(r, fieldCtx, fieldValue.Addr().Interface())
if err != nil {
return err
}
}
if slices.Contains(flags, "alignend") {
r.Align()
}
case reflect.Slice:
var number uint64
readMoreRecords := func() bool {
more := number > 0
number--
return more
}
sliceType := dataElementType.Elem()
if swfTag := ctx.FieldType.Tag.Get("swfCount"); swfTag != "" {
splits := strings.Split(swfTag, ".")
el := ctx.GetNestedType(splits...)
switch el.Kind() {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
number = el.Uint()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
number = uint64(el.Int())
case reflect.Func:
if el.Type().AssignableTo(typeFuncNumberReflect) {
number = el.Interface().(func(ctx ReaderContext) uint64)(ctx)
} else {
return fmt.Errorf("invalid count method %s", swfTag)
}
default:
return fmt.Errorf("invalid count type %s", swfTag)
}
} else if swfTag := ctx.FieldType.Tag.Get("swfMore"); swfTag != "" {
splits := strings.Split(swfTag, ".")
el := ctx.GetNestedType(splits...)
switch el.Kind() {
case reflect.Func:
if el.Type().AssignableTo(typeFuncConditionalReflect) {
fnPtr := el.Interface().(func(ctx ReaderContext) bool)
readMoreRecords = func() bool {
return fnPtr(ctx)
}
} else {
return fmt.Errorf("invalid more method %s", swfTag)
}
default:
return fmt.Errorf("invalid more type %s", swfTag)
}
}
if sliceType.Kind() == reflect.Pointer {
return errors.New("unsupported pointer in slice")
}
//shortcuts
if number > 0 {
switch dataElement.Interface().(type) {
case []uint8:
d := make([]byte, number)
_, err = io.ReadFull(r, d)
if err != nil {
return err
}
dataElement.SetBytes(d)
return nil
}
}
newSlice := reflect.MakeSlice(dataElementType, 0, max(256, int(number)))
for readMoreRecords() {
value := reflect.New(sliceType)
err = ReadTypeInner(r, ctx, value.Interface())
if err != nil {
return err
}
newSlice = reflect.Append(newSlice, value.Elem())
}
//TODO: check this
dataElement.Set(newSlice)
if DoParserDebug {
fmt.Printf("read %d %s(%s) elements into array\n", newSlice.Len(), sliceType.Name(), sliceType.Kind().String())
}
case reflect.Array:
if dataElementType.Elem().Kind() == reflect.Pointer {
return errors.New("unsupported pointer in slice")
}
for i := 0; i < dataElement.Len(); i++ {
err = ReadTypeInner(r, ctx, dataElement.Index(i).Addr().Interface())
if err != nil {
return err
}
}
case reflect.Bool:
value, err := ReadBool(r)
if err != nil {
return err
}
dataElement.SetBool(value)
case reflect.Uint8:
var value uint8
err = ReadU8(r, &value)
if err != nil {
return err
}
dataElement.SetUint(uint64(value))
case reflect.Uint16:
var value uint16
err = ReadU16(r, &value)
if err != nil {
return err
}
dataElement.SetUint(uint64(value))
case reflect.Uint32:
var value uint32
err = ReadU32(r, &value)
if err != nil {
return err
}
dataElement.SetUint(uint64(value))
case reflect.Uint64:
var value uint64
err = ReadU64(r, &value)
if err != nil {
return err
}
dataElement.SetUint(value)
case reflect.Int8:
var value int8
err = ReadSI8(r, &value)
if err != nil {
return err
}
dataElement.SetInt(int64(value))
case reflect.Int16:
var value int16
err = ReadSI16(r, &value)
if err != nil {
return err
}
dataElement.SetInt(int64(value))
case reflect.Int32:
var value int32
err = ReadSI32(r, &value)
if err != nil {
return err
}
dataElement.SetInt(int64(value))
case reflect.Int64:
var value int64
err = ReadSI64(r, &value)
if err != nil {
return err
}
dataElement.SetInt(value)
case reflect.String:
value, err := ReadNullTerminatedString(r, ctx.Version)
if err != nil {
return err
}
dataElement.SetString(value)
case reflect.Float32:
var value uint32
err = ReadU32(r, &value)
if err != nil {
return err
}
dataElement.SetFloat(float64(math.Float32frombits(value)))
case reflect.Float64:
var value uint64
err = ReadU64(r, &value)
if err != nil {
return err
}
dataElement.SetFloat(math.Float64frombits(value))
case reflect.Interface:
return ReadTypeInner(r, ctx, dataElement.Interface())
default:
return fmt.Errorf("unsupported type: %s %s", dataElementType.Name(), dataElementType.String())
}
return nil
}

63
types/reader_test.go Normal file
View file

@ -0,0 +1,63 @@
package types
import (
"bytes"
"github.com/icza/bitio"
"testing"
)
func TestReadSBMinus1(t *testing.T) {
val := int8(-1)
data := []byte{uint8(val)}
r := bitio.NewReader(bytes.NewReader(data))
result, err := ReadSB[int64](r, 8)
if err != nil {
t.Fatal(err)
}
if result != int64(val) {
t.Fatal("does not match")
}
}
func TestReadSBMinus7(t *testing.T) {
val := int8(-7)
data := []byte{uint8(val)}
r := bitio.NewReader(bytes.NewReader(data))
result, err := ReadSB[int64](r, 8)
if err != nil {
t.Fatal(err)
}
if result != int64(val) {
t.Fatal("does not match")
}
}
func TestReadSB127(t *testing.T) {
val := int8(127)
data := []byte{uint8(val)}
r := bitio.NewReader(bytes.NewReader(data))
result, err := ReadSB[int64](r, 8)
if err != nil {
t.Fatal(err)
}
if result != int64(val) {
t.Fatal("does not match")
}
}
func TestReadSBMinus128(t *testing.T) {
val := int8(-128)
data := []byte{uint8(val)}
r := bitio.NewReader(bytes.NewReader(data))
result, err := ReadSB[int64](r, 8)
if err != nil {
t.Fatal(err)
}
if result != int64(val) {
t.Fatal("does not match")
}
}

32
types/twip.go Normal file
View file

@ -0,0 +1,32 @@
package types
import "fmt"
const TwipFactor = 20
type Twip int64
func (t Twip) Components() (pixel int64, subPixel uint8) {
if t < 0 {
return int64(t / TwipFactor), uint8(-int64(t%TwipFactor) * (100 / TwipFactor))
} else {
return int64(t / TwipFactor), uint8(int64(t%TwipFactor) * (100 / TwipFactor))
}
}
func (t Twip) FromFloat64(v float64) Twip {
return Twip(v * TwipFactor)
}
func (t Twip) Float64() float64 {
return float64(t) / TwipFactor
}
func (t Twip) Float32() float32 {
return float32(t) / TwipFactor
}
func (t Twip) String() string {
p, subPixel := t.Components()
return fmt.Sprintf("%d.%02d", p, subPixel)
}