Initial commit, ported from swf2ass-go
This commit is contained in:
commit
952bd4c012
19
LICENSE
Normal file
19
LICENSE
Normal 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
50
README.md
Normal 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
10
go.mod
Normal 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
10
go.sum
Normal 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
102
reader.go
Normal 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
216
subtypes/ACTIONS.go
Normal 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
311
subtypes/BITMAP.go
Normal 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
189
subtypes/FILTERLIST.go
Normal 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
90
subtypes/FONT.go
Normal 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
96
subtypes/GRADIENT.go
Normal 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
44
subtypes/MORPHGRADIENT.go
Normal 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
118
subtypes/MORPHSTYLE.go
Normal 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
335
subtypes/SHAPE.go
Normal 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
175
subtypes/STYLE.go
Normal 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
15
tag/DefineBits.go
Normal 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
13
tag/DefineBitsJPEG2.go
Normal 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
36
tag/DefineBitsJPEG3.go
Normal 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
37
tag/DefineBitsJPEG4.go
Normal 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
28
tag/DefineBitsLossless.go
Normal 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
|
||||
}
|
28
tag/DefineBitsLossless2.go
Normal file
28
tag/DefineBitsLossless2.go
Normal 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
26
tag/DefineFont.go
Normal 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
52
tag/DefineFont2.go
Normal 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
15
tag/DefineFont3.go
Normal 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
26
tag/DefineFont4.go
Normal 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
|
||||
}
|
18
tag/DefineFontAlignZones.go
Normal file
18
tag/DefineFontAlignZones.go
Normal 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
27
tag/DefineFontInfo.go
Normal 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
28
tag/DefineFontInfo2.go
Normal 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
12
tag/DefineFontName.go
Normal 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
20
tag/DefineMorphShape.go
Normal 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
24
tag/DefineMorphShape2.go
Normal 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
12
tag/DefineScalingGrid.go
Normal 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
|
||||
}
|
21
tag/DefineSceneAndFrameLabelData.go
Normal file
21
tag/DefineSceneAndFrameLabelData.go
Normal 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
17
tag/DefineShape.go
Normal 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
17
tag/DefineShape2.go
Normal 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
17
tag/DefineShape3.go
Normal 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
22
tag/DefineShape4.go
Normal 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
52
tag/DefineSprite.go
Normal 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
20
tag/DefineText.go
Normal 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
20
tag/DefineText2.go
Normal 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
30
tag/DoAction.go
Normal 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
35
tag/DoInitAction.go
Normal 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
8
tag/End.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package tag
|
||||
|
||||
type End struct {
|
||||
}
|
||||
|
||||
func (t *End) Code() Code {
|
||||
return RecordEnd
|
||||
}
|
16
tag/FileAttributes.go
Normal file
16
tag/FileAttributes.go
Normal 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
33
tag/FrameLabel.go
Normal 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
14
tag/JPEGTables.go
Normal 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
9
tag/Metadata.go
Normal 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
52
tag/PlaceObject.go
Normal 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
32
tag/PlaceObject2.go
Normal 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
70
tag/PlaceObject3.go
Normal 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
51
tag/PlaceObject4.go
Normal 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
8
tag/Protect.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package tag
|
||||
|
||||
type Protect struct {
|
||||
}
|
||||
|
||||
func (t *Protect) Code() Code {
|
||||
return RecordProtect
|
||||
}
|
10
tag/RemoveObject.go
Normal file
10
tag/RemoveObject.go
Normal 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
9
tag/RemoveObject2.go
Normal 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
11
tag/SetBackgroundColor.go
Normal 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
8
tag/ShowFrame.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package tag
|
||||
|
||||
type ShowFrame struct {
|
||||
}
|
||||
|
||||
func (t *ShowFrame) Code() Code {
|
||||
return RecordShowFrame
|
||||
}
|
152
tag/Sound.go
Normal file
152
tag/Sound.go
Normal 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
162
tag/Tag.go
Normal 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
101
tag/code.go
Normal 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
24
types/ARGB.go
Normal 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
27
types/CXFORM.go
Normal 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
30
types/CXFORMWITHALPHA.go
Normal 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
8
types/Color.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package types
|
||||
|
||||
type Color interface {
|
||||
R() uint8
|
||||
G() uint8
|
||||
B() uint8
|
||||
A() uint8
|
||||
}
|
19
types/MATRIX.go
Normal file
19
types/MATRIX.go
Normal 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
10
types/RECT.go
Normal 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
23
types/RGB.go
Normal 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
24
types/RGBA.go
Normal 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
26
types/UntilEnd.go
Normal 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
23
types/fixed.go
Normal 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
16
types/float.go
Normal 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
16
types/header.go
Normal 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
727
types/reader.go
Normal 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
63
types/reader_test.go
Normal 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
32
types/twip.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue