lychee-slicer/asar/pickle/pickle.go
2024-10-20 23:04:38 +02:00

225 lines
5.1 KiB
Go

package pickle
import (
"encoding/binary"
"errors"
"fmt"
"math"
)
const (
SIZE_UINT32 = 4
PAYLOAD_UNIT = 64
CAPACITY_READ_ONLY = 1 << 53
)
func alignInt(i, alignment int) int {
return i + ((alignment - (i % alignment)) % alignment)
}
type PickleIterator struct {
payload []byte
payloadOffset int
readIndex int
endIndex int
}
func NewPickleIterator(pickle *Pickle) *PickleIterator {
return &PickleIterator{
payload: pickle.header,
payloadOffset: pickle.headerSize,
readIndex: 0,
endIndex: pickle.getPayloadSize(),
}
}
func (pi *PickleIterator) ReadBool() bool {
return pi.ReadInt() != 0
}
func (pi *PickleIterator) ReadInt() int32 {
buf := pi.readBytes(binary.Size(int32(0)))
return int32(binary.LittleEndian.Uint32(buf))
}
func (pi *PickleIterator) ReadUInt32() uint32 {
buf := pi.readBytes(binary.Size(uint32(0)))
return binary.LittleEndian.Uint32(buf)
}
func (pi *PickleIterator) ReadInt64() int64 {
buf := pi.readBytes(binary.Size(int64(0)))
return int64(binary.LittleEndian.Uint64(buf))
}
func (pi *PickleIterator) ReadUInt64() uint64 {
buf := pi.readBytes(binary.Size(uint64(0)))
return binary.LittleEndian.Uint64(buf)
}
func (pi *PickleIterator) ReadFloat() float32 {
return math.Float32frombits(pi.ReadUInt32())
}
func (pi *PickleIterator) readDouble() float64 {
return math.Float64frombits(pi.ReadUInt64())
}
func (pi *PickleIterator) ReadString() string {
length := pi.ReadInt()
return string(pi.readBytes(int(length)))
}
func (pi *PickleIterator) readBytes(length int) []byte {
readPayloadOffset := pi.getReadPayloadOffsetAndAdvance(length)
return pi.payload[readPayloadOffset : readPayloadOffset+length]
}
func (pi *PickleIterator) getReadPayloadOffsetAndAdvance(length int) int {
if length > pi.endIndex-pi.readIndex {
pi.readIndex = pi.endIndex
panic(errors.New("failed to read data with length of " + fmt.Sprint(length)))
}
readPayloadOffset := pi.payloadOffset + pi.readIndex
pi.advance(length)
return readPayloadOffset
}
func (pi *PickleIterator) advance(size int) {
alignedSize := alignInt(size, SIZE_UINT32)
if pi.endIndex-pi.readIndex < alignedSize {
pi.readIndex = pi.endIndex
} else {
pi.readIndex += alignedSize
}
}
type Pickle struct {
header []byte
headerSize int
capacityAfterHeader int
writeOffset int
}
func newPickle(buffer []byte) *Pickle {
p := &Pickle{}
if buffer != nil {
p.header = buffer
p.headerSize = len(buffer) - p.getPayloadSize()
p.capacityAfterHeader = CAPACITY_READ_ONLY
p.writeOffset = 0
if p.headerSize > len(buffer) {
p.headerSize = 0
}
if p.headerSize != alignInt(p.headerSize, SIZE_UINT32) {
p.headerSize = 0
}
if p.headerSize == 0 {
p.header = make([]byte, 0)
}
} else {
p.header = make([]byte, 0)
p.headerSize = SIZE_UINT32
p.capacityAfterHeader = 0
p.writeOffset = 0
p.resize(PAYLOAD_UNIT)
p.setPayloadSize(0)
}
return p
}
func CreateEmpty() *Pickle {
return newPickle(nil)
}
func CreateFromBuffer(buffer []byte) *Pickle {
return newPickle(buffer)
}
func (p *Pickle) GetHeader() []byte {
return p.header
}
func (p *Pickle) GetHeaderSize() int {
return p.headerSize
}
func (p *Pickle) CreateIterator() *PickleIterator {
return NewPickleIterator(p)
}
func (p *Pickle) ToBuffer() []byte {
return p.header[:p.headerSize+p.getPayloadSize()]
}
func (p *Pickle) WriteBool(value bool) {
if value {
p.WriteInt(1)
}
p.WriteInt(0)
}
func (p *Pickle) WriteInt(value int32) {
p.writeBytes(value, binary.LittleEndian)
}
func (p *Pickle) WriteUInt32(value uint32) {
p.writeBytes(value, binary.LittleEndian)
}
func (p *Pickle) WriteInt64(value int64) {
p.writeBytes(value, binary.LittleEndian)
}
func (p *Pickle) WriteUInt64(value uint64) {
p.writeBytes(value, binary.LittleEndian)
}
func (p *Pickle) WriteFloat(value float32) {
p.writeBytes(math.Float32bits(value), binary.LittleEndian)
}
func (p *Pickle) WriteDouble(value float64) {
p.writeBytes(math.Float64bits(value), binary.LittleEndian)
}
func (p *Pickle) WriteString(value string) {
length := len(value)
p.WriteInt(int32(length))
p.writeBytes([]byte(value), binary.LittleEndian)
}
func (p *Pickle) setPayloadSize(payloadSize int) {
binary.LittleEndian.PutUint32(p.header[:4], uint32(payloadSize))
}
func (p *Pickle) getPayloadSize() int {
return int(binary.LittleEndian.Uint32(p.header[:4]))
}
func (p *Pickle) writeBytes(data any, byteOrder binary.ByteOrder) {
length := binary.Size(data)
if length == -1 {
panic(errors.New("unsupported data type"))
}
dataLength := alignInt(length, SIZE_UINT32)
newSize := p.writeOffset + dataLength
if newSize > p.capacityAfterHeader {
p.resize(max(p.capacityAfterHeader*2, newSize))
}
binary.Encode(p.header[p.headerSize+p.writeOffset:], byteOrder, data)
endOffset := p.headerSize + p.writeOffset + length
for i := endOffset; i < endOffset+dataLength-length; i++ {
p.header[i] = 0
}
p.setPayloadSize(newSize)
p.writeOffset = newSize
}
func (p *Pickle) resize(newCapacity int) {
newCapacity = alignInt(newCapacity, PAYLOAD_UNIT)
p.header = append(p.header, make([]byte, newCapacity)...)
p.capacityAfterHeader = newCapacity
}