package main import ( "bytes" "encoding/binary" "flag" "fmt" "image" "image/png" "io/ioutil" "os" "path/filepath" ) var le = binary.LittleEndian func die(v ...interface{}) { fmt.Println(v...) os.Exit(1) } type readBuf []byte func (buf readBuf) uint32(off uint32) uint32 { return le.Uint32(buf[off:]) } func (buf readBuf) uint16(off uint32) uint16 { return le.Uint16(buf[off:]) } func main() { dryrun := flag.Bool("dry", false, "dry run - don't save files") flag.Parse() srcfn := flag.Arg(0) dstdir := flag.Arg(1) srcdata, err := ioutil.ReadFile(srcfn) if err != nil { die(err) } if srcdata[0] != 'P' || srcdata[1] != 'T' { die("not a texture") } bch := readBuf(srcdata) if bch.uint16(2) == 3 { if len(bch) <= 0x180 { return } bch = bch[0x180:] } else { if len(srcdata) <= 0x80 { return } bch = bch[0x80:] } info := bch[bch.uint32(0x8) : bch.uint32(0x8)+bch.uint32(0x1C)] symb := bch[bch.uint32(0xC) : bch.uint32(0xC)+bch.uint32(0x20)] desc := bch[bch.uint32(0x10) : bch.uint32(0x10)+bch.uint32(0x24)] data := bch[bch.uint32(0x14) : bch.uint32(0x14)+bch.uint32(0x28)] tblOffset := info.uint32(0x24) numTex := int(info.uint32(0x28)) p := info[tblOffset:] for i := 0; i < numTex; i++ { tbl := info[p.uint32(0):] texOffset := tbl.uint32(0x00) symOffset := tbl.uint32(0x1C) texFormat := tbl[0x18] p = p[4:] sym := parseSymbol(symb[symOffset:]) fmt.Printf("%s %x\n", sym, texFormat) img := parseEntry(desc[texOffset:], data) if img == nil { //fmt.Println("nil image") continue } if *dryrun { continue } err := writePNG(img, filepath.Join(dstdir, sym+".png")) if err != nil { fmt.Println(err) } } } func writePNG(img image.Image, filename string) error { f, err := os.Create(filename) if err != nil { return err } defer f.Close() return png.Encode(f, img) } func parseSymbols(data []byte, n int) (sym []string) { for len(sym) < n { i := bytes.IndexByte(data, 0) sym = append(sym, string(data[:i])) data = data[i+1:] } return sym } func parseSymbol(sym []byte) string { return string(sym[:bytes.IndexByte(sym, 0)]) } func parseEntry(entry, data readBuf) image.Image { // 0 uint16 height // 2 uint16 width // 8 uint32 texture offset // 10 uint32 texture format // w := int(entry.uint16(2)) h := int(entry.uint16(0)) if w == 0 && h == 0 { //fmt.Println("zero width or height") return nil } offset := entry.uint32(8) fmt := entry.uint32(16) img := parseTexture(data[offset:], w, h, fmt) return img } // Texture formats const ( tfRGBA8 = iota tfRGB8 tfRGBA5551 tfRGB565 tfRGBA4 tfGA8 _ tfG8 tfA8 tfGA4 tfG4 ) func parseTexture(data []byte, w, h int, tf uint32) (img image.Image) { bpp := 0 var ( nrgba *image.NRGBA gray *image.Gray ) switch tf { case tfRGBA8: bpp = 32 nrgba = image.NewNRGBA(image.Rect(0, 0, w, h)) img = nrgba case tfRGB8: bpp = 24 nrgba = image.NewNRGBA(image.Rect(0, 0, w, h)) img = nrgba case tfRGBA5551, tfRGB565, tfGA8: bpp = 16 nrgba = image.NewNRGBA(image.Rect(0, 0, w, h)) img = nrgba case tfG8: bpp = 8 gray = image.NewGray(image.Rect(0, 0, w, h)) img = gray default: fmt.Println("unknown texture format", tf) return } stride := 8 * 8 * bpp / 8 //fmt.Println(tf, w, h, stride) for y := 0; y+8 <= h; y += 8 { for x := 0; x+8 <= w; x += 8 { switch tf { case tfRGBA8: for ty := 0; ty < 8; ty++ { for tx := 0; tx < 8; tx++ { di := nrgba.PixOffset(x+tx, y+ty) si := mingle(tx, ty) * bpp / 8 nrgba.Pix[di+0] = data[si+3] nrgba.Pix[di+1] = data[si+2] nrgba.Pix[di+2] = data[si+1] nrgba.Pix[di+3] = data[si+0] } } case tfRGB8: for ty := 0; ty < 8; ty++ { for tx := 0; tx < 8; tx++ { di := nrgba.PixOffset(x+tx, y+ty) si := mingle(tx, ty) * bpp / 8 nrgba.Pix[di+0] = data[si+2] nrgba.Pix[di+1] = data[si+1] nrgba.Pix[di+2] = data[si+0] nrgba.Pix[di+3] = 0xFF } } case tfRGBA5551: for ty := 0; ty < 8; ty++ { for tx := 0; tx < 8; tx++ { di := nrgba.PixOffset(x+tx, y+ty) si := mingle(tx, ty) * bpp / 8 pix := uint16(data[si]) | uint16(data[si])<<8 nrgba.Pix[di+0] = uint8((pix>>1&31*0xff + 15) / 31) nrgba.Pix[di+1] = uint8((pix>>6&31*0xff + 15) / 31) nrgba.Pix[di+2] = uint8((pix>>11&31*0xff + 15) / 31) nrgba.Pix[di+3] = uint8((pix & 1) * 0xff) } } case tfRGB565: for ty := 0; ty < 8; ty++ { for tx := 0; tx < 8; tx++ { di := nrgba.PixOffset(x+tx, y+ty) si := mingle(tx, ty) * bpp / 8 pix := uint16(data[si]) | uint16(data[si])<<8 nrgba.Pix[di+0] = uint8((pix>>0&31*0xff + 15) / 31) nrgba.Pix[di+1] = uint8((pix>>6&63*0xff + 31) / 63) nrgba.Pix[di+2] = uint8((pix>>11&31*0xff + 15) / 31) nrgba.Pix[di+3] = 0xFF } } case tfGA8: for ty := 0; ty < 8; ty++ { for tx := 0; tx < 8; tx++ { di := nrgba.PixOffset(x+tx, y+ty) si := mingle(tx, ty) * bpp / 8 nrgba.Pix[di+0] = data[si+1] nrgba.Pix[di+1] = data[si+1] nrgba.Pix[di+2] = data[si+1] nrgba.Pix[di+3] = data[si+0] } } case tfG8: for ty := 0; ty < 8; ty++ { for tx := 0; tx < 8; tx++ { di := gray.PixOffset(x+tx, y+ty) si := mingle(tx, ty) * bpp / 8 gray.Pix[di] = data[si] } } } data = data[stride:] } } return img } func mingle(x, y int) int { x = (x | x<<2) & 0x33 x = (x | x<<1) & 0x55 y = (y | y<<2) & 0x33 y = (y | y<<1) & 0x55 return x | y<<1 }