diff --git a/Makefile b/Makefile index 92ee2ea..964f4d9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PACKAGES= raylib raygui raymath easings physics +PACKAGES= raylib raygui raymath easings physics rres GO?= go diff --git a/examples/others/resources/data.h b/examples/others/resources/data.h new file mode 100644 index 0000000..da00b6e --- /dev/null +++ b/examples/others/resources/data.h @@ -0,0 +1,8 @@ +#define NUM_RESOURCES 6 + +#define RES_coin.wav 0x00000000 // Embedded as WAVE +#define RES_raylib_logo.gif 0x00000001 // Embedded as IMAGE +#define RES_raylib_logo.jpg 0x00000002 // Embedded as IMAGE +#define RES_raylib_logo.png 0x00000003 // Embedded as IMAGE +#define RES_raylib_logo.tga 0x00000004 // Embedded as IMAGE +#define RES_tanatana.ogg 0x00000005 // Embedded as VORBIS diff --git a/examples/others/resources/data.rres b/examples/others/resources/data.rres new file mode 100644 index 0000000..b0d8519 Binary files /dev/null and b/examples/others/resources/data.rres differ diff --git a/examples/others/resources/main.go b/examples/others/resources/main.go new file mode 100644 index 0000000..733b82b --- /dev/null +++ b/examples/others/resources/main.go @@ -0,0 +1,95 @@ +package main + +import ( + //"bytes" + + "github.com/gen2brain/raylib-go/raylib" +) + +const numTextures = 4 + +func main() { + screenWidth := int32(800) + screenHeight := int32(450) + + raylib.InitWindow(screenWidth, screenHeight, "raylib [core] example - resources loading") + + raylib.InitAudioDevice() + + // OpenAsset() will also work on Android (reads files from assets/) + reader, err := raylib.OpenAsset("data.rres") + if err != nil { + raylib.TraceLog(raylib.LogWarning, "[%s] rRES raylib resource file could not be opened: %v", "data.rres", err) + } + + defer reader.Close() + + // bindata + //b := MustAsset("data.rres") + //reader := bytes.NewReader(b) + + res := raylib.LoadResource(reader, 0, nil) + wav := raylib.LoadWaveEx(res.Data, int32(res.Param1), int32(res.Param2), int32(res.Param3), int32(res.Param4)) + snd := raylib.LoadSoundFromWave(wav) + raylib.UnloadWave(wav) + + textures := make([]raylib.Texture2D, numTextures) + for i := 0; i < numTextures; i++ { + r := raylib.LoadResource(reader, i+1, nil) + image := raylib.LoadImagePro(r.Data, int32(r.Param1), int32(r.Param2), raylib.TextureFormat(r.Param3)) + textures[i] = raylib.LoadTextureFromImage(image) + raylib.UnloadImage(image) + } + + currentTexture := 0 + + raylib.SetTargetFPS(60) + + for !raylib.WindowShouldClose() { + if raylib.IsKeyPressed(raylib.KeySpace) { + raylib.PlaySound(snd) + } + + if raylib.IsMouseButtonPressed(raylib.MouseLeftButton) { + currentTexture = (currentTexture + 1) % numTextures // Cycle between the textures + } + + raylib.BeginDrawing() + + raylib.ClearBackground(raylib.RayWhite) + + raylib.DrawTexture(textures[currentTexture], screenWidth/2-textures[currentTexture].Width/2, screenHeight/2-textures[currentTexture].Height/2, raylib.RayWhite) + + raylib.DrawText("MOUSE LEFT BUTTON to CYCLE TEXTURES", 40, 410, 10, raylib.Gray) + raylib.DrawText("SPACE to PLAY SOUND", 40, 430, 10, raylib.Gray) + + switch currentTexture { + case 0: + raylib.DrawText("GIF", 272, 70, 20, raylib.Gray) + break + case 1: + raylib.DrawText("JPEG", 272, 70, 20, raylib.Gray) + break + case 2: + raylib.DrawText("PNG", 272, 70, 20, raylib.Gray) + break + case 3: + raylib.DrawText("TGA", 272, 70, 20, raylib.Gray) + break + default: + break + } + + raylib.EndDrawing() + } + + raylib.UnloadSound(snd) + + for _, t := range textures { + raylib.UnloadTexture(t) + } + + raylib.CloseAudioDevice() + + raylib.CloseWindow() +} diff --git a/raylib/audio.go b/raylib/audio.go index 4dc8627..486f725 100644 --- a/raylib/audio.go +++ b/raylib/audio.go @@ -132,12 +132,13 @@ func LoadWave(fileName string) Wave { } // LoadWaveEx - Load wave data from float array data (32bit) -func LoadWaveEx(data unsafe.Pointer, sampleCount int32, sampleRate int32, sampleSize int32, channels int32) Wave { +func LoadWaveEx(data []byte, sampleCount int32, sampleRate int32, sampleSize int32, channels int32) Wave { + cdata := unsafe.Pointer(&data[0]) csampleCount := (C.int)(sampleCount) csampleRate := (C.int)(sampleRate) csampleSize := (C.int)(sampleSize) cchannels := (C.int)(channels) - ret := C.LoadWaveEx(data, csampleCount, csampleRate, csampleSize, cchannels) + ret := C.LoadWaveEx(cdata, csampleCount, csampleRate, csampleSize, cchannels) v := NewWaveFromPointer(unsafe.Pointer(&ret)) return v } diff --git a/raylib/rres.go b/raylib/rres.go index 39a182b..73992ff 100644 --- a/raylib/rres.go +++ b/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 +} diff --git a/rres/README.md b/rres/README.md new file mode 100644 index 0000000..9844231 --- /dev/null +++ b/rres/README.md @@ -0,0 +1,3 @@ +## rres [![GoDoc](https://godoc.org/github.com/gen2brain/raylib-go/rres?status.svg)](https://godoc.org/github.com/gen2brain/raylib-go/rres) + +raylib resources. diff --git a/rres/cmd/rrem/README.md b/rres/cmd/rrem/README.md new file mode 100644 index 0000000..73018e4 --- /dev/null +++ b/rres/cmd/rrem/README.md @@ -0,0 +1,25 @@ +## rrem + +rREM - raylib Resource EMbedder. + +### Usage + +``` +Usage of ./rrem: + -base string + Resources file basename (default "data") + -bin + Generate Go bindata (.go file) + -comp int + Compression type, 0=None, 1=Deflate, 2=LZ4, 5=LZMA2 (XZ), 6=BZIP2 (default 5) + -enc int + Encryption type, 0=None, 1=XOR, 2=AES, 3=3DES + -header + Generate C header (.h file) + -key string + Encryption key +``` + +### Example + +[Example](https://github.com/gen2brain/raylib-go/tree/master/examples/others/resources). diff --git a/rres/cmd/rrem/main.go b/rres/cmd/rrem/main.go new file mode 100644 index 0000000..99997e1 --- /dev/null +++ b/rres/cmd/rrem/main.go @@ -0,0 +1,453 @@ +// rREM - raylib Resource EMbedder +package main + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/rand" + "encoding/binary" + "flag" + "fmt" + "image" + "image/color" + "image/draw" + "io" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strings" + "unsafe" + + _ "image/gif" + _ "image/jpeg" + _ "image/png" + + "github.com/blezek/tga" + _ "github.com/jbuchbinder/gopnm" + _ "golang.org/x/image/bmp" + + "github.com/dsnet/compress/bzip2" + "github.com/jfreymuth/oggvorbis" + "github.com/jteeuwen/go-bindata" + "github.com/klauspost/compress/flate" + "github.com/moutend/go-wav" + "github.com/pierrec/lz4" + "github.com/rootlch/encrypt" + "github.com/ulikunitz/xz" + + "github.com/gen2brain/raylib-go/rres" +) + +func init() { + tga.RegisterFormat() +} + +func main() { + base := flag.String("base", "data", "Resources file basename") + comp := flag.Int("comp", rres.CompLZMA2, "Compression type, 0=None, 1=Deflate, 2=LZ4, 5=LZMA2 (XZ), 6=BZIP2") + enc := flag.Int("enc", rres.CryptoNone, "Encryption type, 0=None, 1=XOR, 2=AES, 3=3DES") + key := flag.String("key", "", "Encryption key") + header := flag.Bool("header", false, "Generate C header (.h file)") + bin := flag.Bool("bin", false, "Generate Go bindata (.go file)") + flag.Parse() + + if len(flag.Args()) == 0 { + flag.Usage() + os.Exit(1) + } + + switch *comp { + case rres.CompNone: + case rres.CompDeflate: + case rres.CompLZ4: + case rres.CompLZMA2: + case rres.CompBZIP2: + default: + fmt.Printf("compression type %d not implemented\n", *comp) + os.Exit(1) + } + + switch *enc { + case rres.CryptoNone: + case rres.CryptoXOR: + case rres.CryptoAES: + case rres.Crypto3DES: + default: + fmt.Printf("encryption type %d not implemented\n", *enc) + os.Exit(1) + } + + if *enc != 0 { + if *key == "" { + fmt.Printf("encryption requires key (-k)\n") + os.Exit(1) + } + if len(*key) != 16 && len(*key) != 24 { + fmt.Printf("wrong key length, it should be 16 or 24\n") + os.Exit(1) + } + } + + rresFile, err := os.Create(fmt.Sprintf("%s.rres", *base)) + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + + defer rresFile.Close() + + var headerFile *os.File + if *header { + headerFile, err = os.Create(fmt.Sprintf("%s.h", *base)) + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + + defer headerFile.Close() + } + + var fileHeader rres.FileHeader + + // "rRES" identifier + copy(fileHeader.ID[:], "rRES") + fileHeader.Count = uint16(len(flag.Args())) + fileHeader.Version = 1 + + // Write file header + err = binary.Write(rresFile, binary.LittleEndian, &fileHeader) + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + + rresFile.Seek(int64(unsafe.Sizeof(fileHeader)), os.SEEK_CUR) + + if *header { + // Write C header file + _, err = headerFile.Write([]byte(fmt.Sprintf("#define NUM_RESOURCES %d\n\n", flag.NArg()))) + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } + } + + for id, filename := range flag.Args() { + var data []byte + var infoHeader rres.InfoHeader + + file, err := os.Open(filename) + if err != nil { + fmt.Printf("%s: %v\n", filename, err) + continue + } + + data, err = ioutil.ReadAll(file) + if err != nil { + fmt.Printf("%s: %v\n", filename, err) + } + + file.Close() + + infoHeader.ID = uint32(id) + infoHeader.CompType = uint8(*comp) + infoHeader.CryptoType = uint8(*enc) + infoHeader.DataType = uint8(fileType(filename)) + infoHeader.PartsCount = uint8(1) + + // Params + switch infoHeader.DataType { + case rres.TypeImage: + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + fmt.Printf("%s: %v\n", filename, err) + continue + } + + rect := img.Bounds() + width, height := rect.Dx(), rect.Dy() + + infoHeader.Param1 = uint32(width) + infoHeader.Param2 = uint32(height) + + switch img.ColorModel() { + case color.GrayModel: + infoHeader.Param3 = rres.ImUncompGrayscale + + i := image.NewGray(rect) + draw.Draw(i, rect, img, rect.Min, draw.Src) + data = i.Pix + case color.Gray16Model: + infoHeader.Param3 = rres.ImUncompGrayAlpha + + i := image.NewGray16(rect) + draw.Draw(i, rect, img, rect.Min, draw.Src) + data = i.Pix + default: + infoHeader.Param3 = rres.ImUncompR8g8b8a8 + + i := image.NewNRGBA(rect) + draw.Draw(i, rect, img, rect.Min, draw.Src) + data = i.Pix + } + + case rres.TypeWave: + a := &wav.File{} + err := wav.Unmarshal(data, a) + if err != nil { + fmt.Printf("%s: %v\n", filename, err) + } + + data, err = ioutil.ReadAll(a) + if err != nil { + fmt.Printf("%s: %v\n", filename, err) + } + + infoHeader.Param1 = uint32(a.Samples()) + infoHeader.Param2 = uint32(a.SamplesPerSec()) + infoHeader.Param3 = uint32(a.BitsPerSample()) + infoHeader.Param4 = uint32(a.Channels()) + case rres.TypeVorbis: + r, err := oggvorbis.NewReader(bytes.NewReader(data)) + if err != nil { + fmt.Printf("%s: %v\n", filename, err) + } + + d, _, err := oggvorbis.ReadAll(bytes.NewReader(data)) + if err != nil { + fmt.Printf("%s: %v\n", filename, err) + } + + // Convert []float32 to []byte + header := *(*reflect.SliceHeader)(unsafe.Pointer(&d)) + header.Len *= 4 + header.Cap *= 4 + data = *(*[]byte)(unsafe.Pointer(&header)) + + infoHeader.Param1 = uint32(r.SampleRate()) + infoHeader.Param2 = uint32(r.Bitrate().Nominal) + infoHeader.Param3 = uint32(r.Channels()) + case rres.TypeVertex: + // TODO https://github.com/sheenobu/go-obj + case rres.TypeText, rres.TypeRaw: + } + + // Encryption + switch infoHeader.CryptoType { + case rres.CryptoXOR: + c, err := encrypt.NewXor(*key) + if err != nil { + fmt.Printf("%v\n", err) + } + + b := c.Encode(data) + data = b + case rres.CryptoAES: + b, err := encryptAES([]byte(*key), data) + if err != nil { + fmt.Printf("%v\n", err) + } + + data = b + case rres.Crypto3DES: + b, err := encrypt3DES([]byte(*key), data) + if err != nil { + fmt.Printf("%v\n", err) + } + + data = b + } + + infoHeader.UncompSize = uint32(len(data)) + + // Compression + switch infoHeader.CompType { + case rres.CompNone: + infoHeader.DataSize = uint32(len(data)) + case rres.CompDeflate: + buf := new(bytes.Buffer) + + w, err := flate.NewWriter(buf, flate.BestCompression) + if err != nil { + fmt.Printf("%v\n", err) + } + + _, err = w.Write(data) + if err != nil { + fmt.Printf("%v\n", err) + } + + w.Close() + + infoHeader.DataSize = uint32(len(buf.Bytes())) + data = buf.Bytes() + case rres.CompLZ4: + buf := new(bytes.Buffer) + + w := lz4.NewWriter(buf) + if err != nil { + fmt.Printf("%v\n", err) + } + + _, err = w.Write(data) + if err != nil { + fmt.Printf("%v\n", err) + } + + w.Close() + + infoHeader.DataSize = uint32(len(buf.Bytes())) + data = buf.Bytes() + case rres.CompLZMA2: + buf := new(bytes.Buffer) + + w, err := xz.NewWriter(buf) + if err != nil { + fmt.Printf("%v\n", err) + } + + _, err = w.Write(data) + if err != nil { + fmt.Printf("%v\n", err) + } + + w.Close() + + infoHeader.DataSize = uint32(len(buf.Bytes())) + data = buf.Bytes() + case rres.CompBZIP2: + buf := new(bytes.Buffer) + + w, err := bzip2.NewWriter(buf, &bzip2.WriterConfig{Level: bzip2.BestCompression}) + if err != nil { + fmt.Printf("%v\n", err) + } + + _, err = w.Write(data) + if err != nil { + fmt.Printf("%v\n", err) + } + + w.Close() + + infoHeader.DataSize = uint32(len(buf.Bytes())) + data = buf.Bytes() + } + + // Write resource info and parameters + err = binary.Write(rresFile, binary.LittleEndian, &infoHeader) + if err != nil { + fmt.Printf("%v\n", err) + } + + rresFile.Seek(int64(unsafe.Sizeof(infoHeader)), os.SEEK_CUR) + + // Write resource data + _, err = rresFile.Write(data) + if err != nil { + fmt.Printf("%v\n", err) + } + + var typeName string + switch infoHeader.DataType { + case rres.TypeImage: + typeName = "IMAGE" + case rres.TypeWave: + typeName = "WAVE" + case rres.TypeVorbis: + typeName = "VORBIS" + case rres.TypeText: + typeName = "TEXT" + default: + typeName = "RAW" + } + + fmt.Printf("%s %d // Embedded as %s\n", filepath.Base(filename), id, typeName) + + if *header { + headerFile.Write([]byte(fmt.Sprintf("#define RES_%s 0x%08x\t\t// Embedded as %s\n", filepath.Base(filename), id, typeName))) + } + } + + // Generate bindata + if *bin { + cfg := bindata.NewConfig() + cfg.NoCompress = true + cfg.Output = fmt.Sprintf("%s.go", *base) + cfg.Input = make([]bindata.InputConfig, 1) + cfg.Input[0] = bindata.InputConfig{Path: fmt.Sprintf("%s.rres", *base), Recursive: false} + + err := bindata.Translate(cfg) + if err != nil { + fmt.Printf("%v\n", err) + } + } +} + +// fileType returns resource file type +func fileType(f string) int { + switch strings.ToLower(filepath.Ext(f)) { + case ".jpg", ".jpeg", ".png", ".bmp", ".tga", ".gif": + return rres.TypeImage + case ".txt", ".csv", ".info", ".md": + return rres.TypeText + case ".wav": + return rres.TypeWave + case ".ogg": + return rres.TypeVorbis + case ".obj": + return rres.TypeVertex + default: + return rres.TypeRaw + } +} + +// 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...) +} + +// 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 +} + +// encrypt3DES +func encrypt3DES(key, text []byte) ([]byte, error) { + block, err := des.NewTripleDESCipher([]byte(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 +} diff --git a/rres/rres.go b/rres/rres.go new file mode 100644 index 0000000..1a13d67 --- /dev/null +++ b/rres/rres.go @@ -0,0 +1,163 @@ +package rres + +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 +) + +// 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 +)