rREM - raylib Resource EMbedder
This commit is contained in:
parent
d25f6e5b1f
commit
5b59c0b7e9
10 changed files with 911 additions and 166 deletions
323
raylib/rres.go
323
raylib/rres.go
|
@ -2,212 +2,209 @@ package raylib
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/dsnet/compress/bzip2"
|
||||
"github.com/klauspost/compress/flate"
|
||||
"github.com/pierrec/lz4"
|
||||
"github.com/rootlch/encrypt"
|
||||
"github.com/ulikunitz/xz"
|
||||
|
||||
"github.com/gen2brain/raylib-go/rres"
|
||||
)
|
||||
|
||||
// RRESFileHeader - rRES file header (8 byte)
|
||||
type RRESFileHeader struct {
|
||||
// File identifier: rRES (4 byte)
|
||||
ID [4]int8
|
||||
// File version and subversion (2 byte)
|
||||
Version uint16
|
||||
// Number of resources in this file (2 byte)
|
||||
Count uint16
|
||||
}
|
||||
|
||||
// RRESInfoHeader - rRES info header, every resource includes this header (16 byte + 16 byte)
|
||||
type RRESInfoHeader struct {
|
||||
// Resource unique identifier (4 byte)
|
||||
ID uint32
|
||||
// Resource data type (1 byte)
|
||||
DataType uint8
|
||||
// Resource data compression type (1 byte)
|
||||
CompType uint8
|
||||
// Resource data encryption type (1 byte)
|
||||
CryptoType uint8
|
||||
// Resource data parts count, used for splitted data (1 byte)
|
||||
PartsCount uint8
|
||||
// Resource data size (compressed or not, only DATA) (4 byte)
|
||||
DataSize uint32
|
||||
// Resource data size (uncompressed, only DATA) (4 byte)
|
||||
UncompSize uint32
|
||||
// Resouce parameter 1 (4 byte)
|
||||
Param1 uint32
|
||||
// Resouce parameter 2 (4 byte)
|
||||
Param2 uint32
|
||||
// Resouce parameter 3 (4 byte)
|
||||
Param3 uint32
|
||||
// Resouce parameter 4 (4 byte)
|
||||
Param4 uint32
|
||||
}
|
||||
|
||||
// rRES data types
|
||||
const (
|
||||
RRESTypeRaw = iota
|
||||
RRESTypeImage
|
||||
RRESTypeWave
|
||||
RRESTypeVertex
|
||||
RRESTypeText
|
||||
RRESTypeFontImage
|
||||
RRESTypeFontData
|
||||
RRESTypeDirectory
|
||||
)
|
||||
|
||||
// Compression types
|
||||
const (
|
||||
// No data compression
|
||||
RRESCompNone = iota
|
||||
// DEFLATE compression
|
||||
RRESCompDeflate
|
||||
// LZ4 compression
|
||||
RRESCompLz4
|
||||
// LZMA compression
|
||||
RRESCompLzma
|
||||
// BROTLI compression
|
||||
RRESCompBrotli
|
||||
)
|
||||
|
||||
// Image formats
|
||||
const (
|
||||
// 8 bit per pixel (no alpha)
|
||||
RRESImUncompGrayscale = iota + 1
|
||||
// 16 bpp (2 channels)
|
||||
RRESImUncompGrayAlpha
|
||||
// 16 bpp
|
||||
RRESImUncompR5g6b5
|
||||
// 24 bpp
|
||||
RRESImUncompR8g8b8
|
||||
// 16 bpp (1 bit alpha)
|
||||
RRESImUncompR5g5b5a1
|
||||
// 16 bpp (4 bit alpha)
|
||||
RRESImUncompR4g4b4a4
|
||||
// 32 bpp
|
||||
RRESImUncompR8g8b8a8
|
||||
// 4 bpp (no alpha)
|
||||
RRESImCompDxt1Rgb
|
||||
// 4 bpp (1 bit alpha)
|
||||
RRESImCompDxt1Rgba
|
||||
// 8 bpp
|
||||
RRESImCompDxt3Rgba
|
||||
// 8 bpp
|
||||
RRESImCompDxt5Rgba
|
||||
// 4 bpp
|
||||
RRESImCompEtc1Rgb
|
||||
// 4 bpp
|
||||
RRESImCompEtc2Rgb
|
||||
// 8 bpp
|
||||
RRESImCompEtc2EacRgba
|
||||
// 4 bpp
|
||||
RRESImCompPvrtRgb
|
||||
// 4 bpp
|
||||
RRESImCompPvrtRgba
|
||||
// 8 bpp
|
||||
RRESImCompAstc4x4Rgba
|
||||
// 2 bpp
|
||||
RRESImCompAstc8x8Rgba
|
||||
)
|
||||
|
||||
// RRESVert
|
||||
const (
|
||||
RRESVertPosition = iota
|
||||
RRESVertTexcoord1
|
||||
RRESVertTexcoord2
|
||||
RRESVertTexcoord3
|
||||
RRESVertTexcoord4
|
||||
RRESVertNormal
|
||||
RRESVertTangent
|
||||
RRESVertColor
|
||||
RRESVertIndex
|
||||
)
|
||||
|
||||
// RRESVert
|
||||
const (
|
||||
RRESVertByte = iota
|
||||
RRESVertShort
|
||||
RRESVertInt
|
||||
RRESVertHfloat
|
||||
RRESVertFloat
|
||||
)
|
||||
|
||||
// LoadResource - Load resource from file (only one)
|
||||
// NOTE: Returns uncompressed data with parameters, only first resource found
|
||||
func LoadResource(fileName string) []byte {
|
||||
return LoadResourceByID(fileName, 0)
|
||||
}
|
||||
|
||||
// LoadResourceByID - Load resource from file by id
|
||||
// LoadResource - Load resource from file by id
|
||||
// NOTE: Returns uncompressed data with parameters, search resource by id
|
||||
func LoadResourceByID(fileName string, rresID int) (data []byte) {
|
||||
file, err := OpenAsset(fileName)
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, "[%s] rRES raylib resource file could not be opened", fileName)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
func LoadResource(reader io.ReadSeeker, rresID int, key []byte) (data rres.Data) {
|
||||
var fileHeader rres.FileHeader
|
||||
var infoHeader rres.InfoHeader
|
||||
|
||||
fileHeader := RRESFileHeader{}
|
||||
infoHeader := RRESInfoHeader{}
|
||||
reader.Seek(0, 0)
|
||||
|
||||
// Read rres file header
|
||||
err = binary.Read(file, binary.LittleEndian, &fileHeader)
|
||||
err := binary.Read(reader, binary.LittleEndian, &fileHeader)
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Printf("%+v\n", fileHeader)
|
||||
|
||||
// Verify "rRES" identifier
|
||||
id := fmt.Sprintf("%c", fileHeader.ID)
|
||||
if id != "[r R E S]" {
|
||||
TraceLog(LogWarning, "[%s] is not a valid raylib resource file", fileName)
|
||||
TraceLog(LogWarning, "not a valid raylib resource file")
|
||||
return
|
||||
}
|
||||
|
||||
file.Seek(int64(unsafe.Sizeof(fileHeader)), os.SEEK_CUR)
|
||||
reader.Seek(int64(unsafe.Sizeof(fileHeader)), os.SEEK_CUR)
|
||||
|
||||
for i := 0; i < int(fileHeader.Count); i++ {
|
||||
// Read resource info and parameters
|
||||
err = binary.Read(file, binary.LittleEndian, &infoHeader)
|
||||
err = binary.Read(reader, binary.LittleEndian, &infoHeader)
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Printf("%+v\n", infoHeader)
|
||||
|
||||
file.Seek(int64(unsafe.Sizeof(infoHeader)), os.SEEK_CUR)
|
||||
reader.Seek(int64(unsafe.Sizeof(infoHeader)), os.SEEK_CUR)
|
||||
|
||||
if int(infoHeader.ID) == rresID {
|
||||
data.Type = uint32(infoHeader.DataType)
|
||||
data.Param1 = infoHeader.Param1
|
||||
data.Param2 = infoHeader.Param2
|
||||
data.Param3 = infoHeader.Param3
|
||||
data.Param4 = infoHeader.Param4
|
||||
|
||||
// Read resource data block
|
||||
data = make([]byte, infoHeader.DataSize)
|
||||
file.Read(data)
|
||||
b := make([]byte, infoHeader.DataSize)
|
||||
reader.Read(b)
|
||||
|
||||
if infoHeader.CompType == RRESCompDeflate {
|
||||
// Uncompress data
|
||||
b := bytes.NewReader(data)
|
||||
r := flate.NewReader(b)
|
||||
// Uncompress data
|
||||
switch infoHeader.CompType {
|
||||
case rres.CompNone:
|
||||
data.Data = b
|
||||
case rres.CompDeflate:
|
||||
r := flate.NewReader(bytes.NewReader(b))
|
||||
|
||||
data = make([]byte, infoHeader.UncompSize)
|
||||
r.Read(data)
|
||||
u := make([]byte, infoHeader.UncompSize)
|
||||
r.Read(u)
|
||||
|
||||
data.Data = u
|
||||
|
||||
r.Close()
|
||||
case rres.CompLZ4:
|
||||
r := lz4.NewReader(bytes.NewReader(b))
|
||||
|
||||
u := make([]byte, infoHeader.UncompSize)
|
||||
r.Read(u)
|
||||
|
||||
data.Data = u
|
||||
case rres.CompLZMA2:
|
||||
r, err := xz.NewReader(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, "[ID %d] %v", infoHeader.ID, err)
|
||||
}
|
||||
|
||||
u := make([]byte, infoHeader.UncompSize)
|
||||
r.Read(u)
|
||||
|
||||
data.Data = u
|
||||
case rres.CompBZIP2:
|
||||
r, err := bzip2.NewReader(bytes.NewReader(b), &bzip2.ReaderConfig{})
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, "[ID %d] %v", infoHeader.ID, err)
|
||||
}
|
||||
|
||||
u := make([]byte, infoHeader.UncompSize)
|
||||
r.Read(u)
|
||||
|
||||
data.Data = u
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
TraceLog(LogInfo, "[%s][ID %d] Resource data loaded successfully", fileName, infoHeader.ID)
|
||||
// Decrypt data
|
||||
switch infoHeader.CryptoType {
|
||||
case rres.CryptoXOR:
|
||||
c, err := encrypt.NewXor(string(key))
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, "[ID %d] %v", infoHeader.ID, err)
|
||||
}
|
||||
|
||||
b := c.Encode(data.Data)
|
||||
data.Data = b
|
||||
case rres.CryptoAES:
|
||||
b, err := decryptAES(key, data.Data)
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, "[ID %d] %v", infoHeader.ID, err)
|
||||
}
|
||||
data.Data = b
|
||||
case rres.Crypto3DES:
|
||||
b, err := decrypt3DES(key, data.Data)
|
||||
if err != nil {
|
||||
TraceLog(LogWarning, "[ID %d] %v", infoHeader.ID, err)
|
||||
}
|
||||
data.Data = b
|
||||
}
|
||||
|
||||
if data.Data != nil {
|
||||
TraceLog(LogInfo, "[ID %d] Resource data loaded successfully", infoHeader.ID)
|
||||
}
|
||||
} else {
|
||||
// Skip required data to read next resource infoHeader
|
||||
file.Seek(int64(infoHeader.DataSize), os.SEEK_CUR)
|
||||
reader.Seek(int64(infoHeader.DataSize), os.SEEK_CUR)
|
||||
}
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
TraceLog(LogInfo, "[%s][ID %d] Requested resource could not be found", fileName, rresID)
|
||||
if data.Data == nil {
|
||||
TraceLog(LogInfo, "[ID %d] Requested resource could not be found", rresID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// unpad
|
||||
func unpad(src []byte) ([]byte, error) {
|
||||
length := len(src)
|
||||
unpadding := int(src[length-1])
|
||||
|
||||
if unpadding > length {
|
||||
return nil, fmt.Errorf("unpad error. This can happen when incorrect encryption key is used.")
|
||||
}
|
||||
|
||||
return src[:(length - unpadding)], nil
|
||||
}
|
||||
|
||||
// decryptAES
|
||||
func decryptAES(key, text []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (len(text) % aes.BlockSize) != 0 {
|
||||
return nil, fmt.Errorf("blocksize must be multiple of decoded message length")
|
||||
}
|
||||
|
||||
iv := text[:aes.BlockSize]
|
||||
msg := text[aes.BlockSize:]
|
||||
|
||||
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||
cfb.XORKeyStream(msg, msg)
|
||||
|
||||
unpadMsg, err := unpad(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unpadMsg, nil
|
||||
}
|
||||
|
||||
// decrypt3DES
|
||||
func decrypt3DES(key, text []byte) ([]byte, error) {
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (len(text) % des.BlockSize) != 0 {
|
||||
return nil, fmt.Errorf("blocksize must be multiple of decoded message length")
|
||||
}
|
||||
|
||||
iv := text[:des.BlockSize]
|
||||
msg := text[des.BlockSize:]
|
||||
|
||||
cbc := cipher.NewCBCDecrypter(block, iv)
|
||||
cbc.CryptBlocks(msg, msg)
|
||||
|
||||
unpadMsg, err := unpad(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unpadMsg, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue