691 lines
13 KiB
Go
691 lines
13 KiB
Go
package rres
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/des"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"github.com/dsnet/compress/bzip2"
|
|
"github.com/golang/snappy"
|
|
"github.com/klauspost/compress/flate"
|
|
"github.com/pierrec/lz4"
|
|
xor "github.com/rootlch/encrypt"
|
|
"github.com/ulikunitz/xz"
|
|
"golang.org/x/crypto/blowfish"
|
|
"golang.org/x/crypto/xtea"
|
|
|
|
"github.com/Konstantin8105/raylib-go/raylib"
|
|
)
|
|
|
|
type Data struct {
|
|
// Resource type (4 byte)
|
|
Type uint32
|
|
|
|
// Resource parameter 1 (4 byte)
|
|
Param1 uint32
|
|
// Resource parameter 2 (4 byte)
|
|
Param2 uint32
|
|
// Resource parameter 3 (4 byte)
|
|
Param3 uint32
|
|
// Resource parameter 4 (4 byte)
|
|
Param4 uint32
|
|
|
|
// Resource data
|
|
Data []byte
|
|
}
|
|
|
|
// FileHeader - rRES file header (8 byte)
|
|
type FileHeader struct {
|
|
// File identifier: rRES (4 byte)
|
|
ID [4]byte
|
|
// File version and subversion (2 byte)
|
|
Version uint16
|
|
// Number of resources in this file (2 byte)
|
|
Count uint16
|
|
}
|
|
|
|
// InfoHeader - rRES info header, every resource includes this header (16 byte + 16 byte)
|
|
type InfoHeader 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
|
|
// Resource parameter 1 (4 byte)
|
|
Param1 uint32
|
|
// Resource parameter 2 (4 byte)
|
|
Param2 uint32
|
|
// Resource parameter 3 (4 byte)
|
|
Param3 uint32
|
|
// Resource parameter 4 (4 byte)
|
|
Param4 uint32
|
|
}
|
|
|
|
// rRES data types
|
|
const (
|
|
TypeRaw = iota
|
|
TypeImage
|
|
TypeWave
|
|
TypeVertex
|
|
TypeText
|
|
TypeFontImage
|
|
TypeFontCharData
|
|
TypeDirectory
|
|
TypeVorbis
|
|
)
|
|
|
|
// Compression types
|
|
const (
|
|
// No data compression
|
|
CompNone = iota
|
|
// DEFLATE compression
|
|
CompDeflate
|
|
// LZ4 compression
|
|
CompLZ4
|
|
// LZMA compression
|
|
CompLZMA
|
|
// BROTLI compression
|
|
CompBrotli
|
|
// LZMA2 (XZ) compression
|
|
CompLZMA2
|
|
// BZIP2 compression
|
|
CompBZIP2
|
|
// Snappy compression
|
|
CompSnappy
|
|
)
|
|
|
|
// Encryption types
|
|
const (
|
|
// No data encryption
|
|
CryptoNone = iota
|
|
// XOR (128 bit) encryption
|
|
CryptoXOR
|
|
// RIJNDAEL (128 bit) encryption (AES)
|
|
CryptoAES
|
|
// Triple DES encryption
|
|
Crypto3DES
|
|
// Blowfish encryption
|
|
CryptoBlowfish
|
|
// Extended TEA encryption
|
|
CryptoXTEA
|
|
)
|
|
|
|
// Image formats
|
|
const (
|
|
// 8 bit per pixel (no alpha)
|
|
ImUncompGrayscale = iota + 1
|
|
// 16 bpp (2 channels)
|
|
ImUncompGrayAlpha
|
|
// 16 bpp
|
|
ImUncompR5g6b5
|
|
// 24 bpp
|
|
ImUncompR8g8b8
|
|
// 16 bpp (1 bit alpha)
|
|
ImUncompR5g5b5a1
|
|
// 16 bpp (4 bit alpha)
|
|
ImUncompR4g4b4a4
|
|
// 32 bpp
|
|
ImUncompR8g8b8a8
|
|
// 4 bpp (no alpha)
|
|
ImCompDxt1Rgb
|
|
// 4 bpp (1 bit alpha)
|
|
ImCompDxt1Rgba
|
|
// 8 bpp
|
|
ImCompDxt3Rgba
|
|
// 8 bpp
|
|
ImCompDxt5Rgba
|
|
// 4 bpp
|
|
ImCompEtc1Rgb
|
|
// 4 bpp
|
|
ImCompEtc2Rgb
|
|
// 8 bpp
|
|
ImCompEtc2EacRgba
|
|
// 4 bpp
|
|
ImCompPvrtRgb
|
|
// 4 bpp
|
|
ImCompPvrtRgba
|
|
// 8 bpp
|
|
ImCompAstc4x4Rgba
|
|
// 2 bpp
|
|
ImCompAstc8x8Rgba
|
|
)
|
|
|
|
// Vert
|
|
const (
|
|
VertPosition = iota
|
|
VertTexcoord1
|
|
VertTexcoord2
|
|
VertTexcoord3
|
|
VertTexcoord4
|
|
VertNormal
|
|
VertTangent
|
|
VertColor
|
|
VertIndex
|
|
)
|
|
|
|
// Vert
|
|
const (
|
|
VertByte = iota
|
|
VertShort
|
|
VertInt
|
|
VertHfloat
|
|
VertFloat
|
|
)
|
|
|
|
// LoadResource - Load resource from file by id
|
|
// NOTE: Returns uncompressed data with parameters, search resource by id
|
|
func LoadResource(reader io.ReadSeeker, rresID int, key []byte) (data Data) {
|
|
var fileHeader FileHeader
|
|
var infoHeader InfoHeader
|
|
|
|
reader.Seek(0, 0)
|
|
|
|
// Read rres file header
|
|
err := binary.Read(reader, binary.LittleEndian, &fileHeader)
|
|
if err != nil {
|
|
rl.TraceLog(rl.LogWarning, err.Error())
|
|
return
|
|
}
|
|
|
|
// Verify "rRES" identifier
|
|
if string(fileHeader.ID[:]) != "rRES" {
|
|
rl.TraceLog(rl.LogWarning, "not a valid raylib resource file")
|
|
return
|
|
}
|
|
|
|
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(reader, binary.LittleEndian, &infoHeader)
|
|
if err != nil {
|
|
rl.TraceLog(rl.LogWarning, err.Error())
|
|
return
|
|
}
|
|
|
|
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
|
|
b := make([]byte, infoHeader.DataSize)
|
|
reader.Read(b)
|
|
|
|
// Decompress data
|
|
data.Data, err = Decompress(b, int(infoHeader.CompType))
|
|
if err != nil {
|
|
rl.TraceLog(rl.LogWarning, "[ID %d] %v", infoHeader.ID, err)
|
|
}
|
|
|
|
// Decrypt data
|
|
data.Data, err = Decrypt(key, data.Data, int(infoHeader.CryptoType))
|
|
if err != nil {
|
|
rl.TraceLog(rl.LogWarning, "[ID %d] %v", infoHeader.ID, err)
|
|
}
|
|
|
|
if data.Data != nil && len(data.Data) == int(infoHeader.UncompSize) {
|
|
rl.TraceLog(rl.LogInfo, "[ID %d] Resource data loaded successfully", infoHeader.ID)
|
|
}
|
|
} else {
|
|
// Skip required data to read next resource infoHeader
|
|
reader.Seek(int64(infoHeader.DataSize), os.SEEK_CUR)
|
|
}
|
|
}
|
|
|
|
if data.Data == nil {
|
|
rl.TraceLog(rl.LogWarning, "[ID %d] Requested resource could not be found", rresID)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Encrypt data
|
|
func Encrypt(key, data []byte, cryptoType int) ([]byte, error) {
|
|
switch cryptoType {
|
|
case CryptoXOR:
|
|
c, err := xor.NewXor(string(key))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return c.Encode(data), nil
|
|
case CryptoAES:
|
|
b, err := encryptAES(key, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b, nil
|
|
case Crypto3DES:
|
|
b, err := encrypt3DES(key, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b, nil
|
|
case CryptoBlowfish:
|
|
b, err := encryptBlowfish(key, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b, nil
|
|
case CryptoXTEA:
|
|
b, err := encryptXTEA(key, data)
|
|
if err != nil {
|
|
fmt.Printf("%v\n", err)
|
|
}
|
|
|
|
return b, nil
|
|
default:
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// Decrypt data
|
|
func Decrypt(key, data []byte, cryptoType int) ([]byte, error) {
|
|
switch cryptoType {
|
|
case CryptoXOR:
|
|
c, err := xor.NewXor(string(key))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b := c.Encode(data)
|
|
return b, nil
|
|
case CryptoAES:
|
|
b, err := decryptAES(key, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
case Crypto3DES:
|
|
b, err := decrypt3DES(key, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
case CryptoBlowfish:
|
|
b, err := decryptBlowfish(key, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
case CryptoXTEA:
|
|
b, err := decryptXTEA(key, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
default:
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// Compress data
|
|
func Compress(data []byte, compType int) ([]byte, error) {
|
|
switch compType {
|
|
case CompNone:
|
|
return data, nil
|
|
case CompDeflate:
|
|
buf := new(bytes.Buffer)
|
|
|
|
w, err := flate.NewWriter(buf, flate.DefaultCompression)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = w.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Close()
|
|
|
|
return buf.Bytes(), nil
|
|
case CompLZ4:
|
|
buf := new(bytes.Buffer)
|
|
|
|
w := lz4.NewWriter(buf)
|
|
|
|
_, err := w.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Close()
|
|
|
|
return buf.Bytes(), nil
|
|
case CompLZMA2:
|
|
buf := new(bytes.Buffer)
|
|
|
|
w, err := xz.NewWriter(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = w.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Close()
|
|
|
|
return buf.Bytes(), nil
|
|
case CompBZIP2:
|
|
buf := new(bytes.Buffer)
|
|
|
|
w, err := bzip2.NewWriter(buf, &bzip2.WriterConfig{Level: bzip2.BestCompression})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = w.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Close()
|
|
|
|
return buf.Bytes(), nil
|
|
case CompSnappy:
|
|
buf := new(bytes.Buffer)
|
|
|
|
w := snappy.NewWriter(buf)
|
|
|
|
_, err := w.Write(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Close()
|
|
|
|
return buf.Bytes(), nil
|
|
default:
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// Decompress data
|
|
func Decompress(data []byte, compType int) ([]byte, error) {
|
|
switch compType {
|
|
case CompNone:
|
|
return data, nil
|
|
case CompDeflate:
|
|
r := flate.NewReader(bytes.NewReader(data))
|
|
|
|
u, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r.Close()
|
|
|
|
return u, nil
|
|
case CompLZ4:
|
|
r := lz4.NewReader(bytes.NewReader(data))
|
|
|
|
u, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return u, nil
|
|
case CompLZMA2:
|
|
r, err := xz.NewReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
u, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return u, nil
|
|
case CompBZIP2:
|
|
r, err := bzip2.NewReader(bytes.NewReader(data), &bzip2.ReaderConfig{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
u, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return u, nil
|
|
case CompSnappy:
|
|
r := snappy.NewReader(bytes.NewReader(data))
|
|
|
|
u, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return u, nil
|
|
default:
|
|
return data, nil
|
|
}
|
|
}
|
|
|
|
// pad to block size
|
|
func pad(src []byte, blockSize int) []byte {
|
|
padding := blockSize - len(src)%blockSize
|
|
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
|
return append(src, padtext...)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// encryptAES
|
|
func encryptAES(key, text []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
msg := pad(text, aes.BlockSize)
|
|
ciphertext := make([]byte, aes.BlockSize+len(msg))
|
|
iv := ciphertext[:aes.BlockSize]
|
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfb := cipher.NewCFBEncrypter(block, iv)
|
|
cfb.XORKeyStream(ciphertext[aes.BlockSize:], msg)
|
|
|
|
return ciphertext, 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
|
|
}
|
|
|
|
// encrypt3DES
|
|
func encrypt3DES(key, text []byte) ([]byte, error) {
|
|
block, err := des.NewTripleDESCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
msg := pad(text, des.BlockSize)
|
|
ciphertext := make([]byte, des.BlockSize+len(msg))
|
|
iv := ciphertext[:des.BlockSize]
|
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cbc := cipher.NewCBCEncrypter(block, iv)
|
|
cbc.CryptBlocks(ciphertext[des.BlockSize:], msg)
|
|
|
|
return ciphertext, 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
|
|
}
|
|
|
|
// encryptBlowfish
|
|
func encryptBlowfish(key, text []byte) ([]byte, error) {
|
|
block, err := blowfish.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
msg := pad(text, blowfish.BlockSize)
|
|
ciphertext := make([]byte, blowfish.BlockSize+len(msg))
|
|
iv := ciphertext[:blowfish.BlockSize]
|
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cbc := cipher.NewCBCEncrypter(block, iv)
|
|
cbc.CryptBlocks(ciphertext[blowfish.BlockSize:], msg)
|
|
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// decryptBlowfish
|
|
func decryptBlowfish(key, text []byte) ([]byte, error) {
|
|
block, err := blowfish.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if (len(text) % blowfish.BlockSize) != 0 {
|
|
return nil, fmt.Errorf("blocksize must be multiple of decoded message length")
|
|
}
|
|
|
|
iv := text[:blowfish.BlockSize]
|
|
msg := text[blowfish.BlockSize:]
|
|
|
|
cbc := cipher.NewCBCDecrypter(block, iv)
|
|
cbc.CryptBlocks(msg, msg)
|
|
|
|
unpadMsg, err := unpad(msg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return unpadMsg, nil
|
|
}
|
|
|
|
// encryptXTEA
|
|
func encryptXTEA(key, text []byte) ([]byte, error) {
|
|
block, err := xtea.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
msg := pad(text, xtea.BlockSize)
|
|
ciphertext := make([]byte, xtea.BlockSize+len(msg))
|
|
iv := ciphertext[:xtea.BlockSize]
|
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cbc := cipher.NewCBCEncrypter(block, iv)
|
|
cbc.CryptBlocks(ciphertext[xtea.BlockSize:], msg)
|
|
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// decryptXTEA
|
|
func decryptXTEA(key, text []byte) ([]byte, error) {
|
|
block, err := xtea.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if (len(text) % xtea.BlockSize) != 0 {
|
|
return nil, fmt.Errorf("blocksize must be multiple of decoded message length")
|
|
}
|
|
|
|
iv := text[:xtea.BlockSize]
|
|
msg := text[xtea.BlockSize:]
|
|
|
|
cbc := cipher.NewCBCDecrypter(block, iv)
|
|
cbc.CryptBlocks(msg, msg)
|
|
|
|
unpadMsg, err := unpad(msg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return unpadMsg, nil
|
|
}
|