Skip to content

Instantly share code, notes, and snippets.

@magical
Forked from smealum/bch2obj.py
Last active December 13, 2015 23:42
Show Gist options
  • Save magical/5a78c91a4c1ac6056943 to your computer and use it in GitHub Desktop.
Save magical/5a78c91a4c1ac6056943 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"io"
"io/ioutil"
"math"
//"os"
"path/filepath"
)
var le = binary.LittleEndian
var (
totalVcount uint32 = 1
totalNcount uint32 = 1
totalTCcount uint32 = 1
hasNormals = false
)
var symbols []string
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 (buf readBuf) float32(off uint32) float32 {
return math.Float32frombits(buf.uint32(off))
}
func main() {
flag.Parse()
srcfn := flag.Arg(0)
dstfn := flag.Arg(1)
dstdir := flag.Arg(2)
if dstdir == "" {
dstdir = "."
}
data, err := ioutil.ReadFile(srcfn)
if err != nil {
fmt.Println(err)
return
}
if data[0] != 'P' || data[1] != 'C' {
fmt.Println("not a pokémon model")
return
}
bch := readBuf(data)
bch = bch[bch.uint32(0x4):]
if bch[0] != 'B' || bch[1] != 'C' || bch[2] != 'H' || bch[3] != 0 {
fmt.Println("not a BCH")
return
}
// 0 [4]byte "BCH\x00"
// 4 [4]byte 07 07 0D 97
// 8 uint32 38
// C symbol offset
// 10 desc offset
// 14 data offset
// 18 unknown offset
// 1C size of 08
// 20 size of symbols
// 24 size of desc table
// 28 size of data table
// 2C size of 18
// 30 e.g. 0x26B8
// 34 e.g. 0x73D0
info := bch[bch.uint32(0x08) : bch.uint32(0x08)+bch.uint32(0x1C)]
symb := bch[bch.uint32(0x0C) : bch.uint32(0x0C)+bch.uint32(0x20)]
desc := bch[bch.uint32(0x10) : bch.uint32(0x10)+bch.uint32(0x24)]
vert := bch[bch.uint32(0x14) : bch.uint32(0x14)+bch.uint32(0x28)]
off := info.uint32(0xCC)
texTableOffset := info.uint32(off + 0x34)
texTableEntries := info.uint32(off + 0x38)
tableOffset := info.uint32(off + 0x40)
tableEntries := info.uint32(off + 0x44)
var out bytes.Buffer
p := info[texTableOffset:]
for i := uint32(0); i != texTableEntries; i++ {
s := parseTexTableEntry(&out, p, symb)
symbols = append(symbols, s)
p = p[0x58:]
}
err = ioutil.WriteFile(filepath.Join(dstdir, dstfn+".mtl"), out.Bytes(), 0666)
if err != nil {
fmt.Println(err)
return
}
out.Reset()
//f, err := os.Create(filepath.Join(dstdir, dstfn+".obj"))
//if err != nil {
// fmt.Println(err)
// return
//}
//defer f.Close()
fmt.Fprintf(&out, "mtllib %s.mtl\n", dstfn)
for i := uint32(0); i != tableEntries; i++ {
offset := tableOffset + i*0x38
parseTableEntry(&out, offset, info[offset:], info, desc, vert)
}
err = ioutil.WriteFile(filepath.Join(dstdir, dstfn+".obj"), out.Bytes(), 0666)
if err != nil {
fmt.Println(err)
return
}
}
func parseTexTableEntry(out io.Writer, data, symb readBuf) string {
// 0 uint32 pointer, relative to info chunk
// 18 . 00 03 02 00 02 00 00 00 00 00 00 00 00 00 00 FF
// 28 . 01 03 02 00 02 00 00 00 00 00 00 00 00 00 00 FF
// 38 . 01 02 02 01 05 00 00 00 00 00 00 00 00 00 00 FF
// 48 uint32 diffuse map symbol
// 4C uint32 ?? map symbol
// 50 uint32 normal map symbol
// 54 uint32 texture name symbol
s := string(parseSymbol(symb, data.uint32(0x54)))
fmt.Fprintf(out, "newmtl %s\n", s)
// Uhh
io.WriteString(out, "illum 2\n")
io.WriteString(out, "Kd 0.8 0.8 0.8\n")
io.WriteString(out, "Ka 0.8 0.8 0.8\n")
io.WriteString(out, "Ks 0.0 0.0 0.0\n")
io.WriteString(out, "Ke 0.0 0.0 0.0\n")
io.WriteString(out, "Ns 0.0\n")
fmt.Fprintf(out, "map_Kd %s.png\n", parseSymbol(symb, data.uint32(0x48)))
fmt.Fprintf(out, "# %s.png\n", parseSymbol(symb, data.uint32(0x4C)))
fmt.Fprintf(out, "# %s.png\n", parseSymbol(symb, data.uint32(0x50)))
io.WriteString(out, "\n")
return s
}
func parseSymbol(s readBuf, i uint32) readBuf {
s = s[i:]
s = s[:bytes.IndexByte(s, 0)]
return s
}
func parseTableEntry(out io.Writer, o uint32, entry, info, desc, tbl readBuf) {
// Table entry
// 0 uint16 texture ID
// 4 uint32 vf2
// 8 uint32 vertex desc offset, relative to desc
// C uint32 count?
// 10 uint32 face offset, relative to info chunk
// 14 uint32 face count
// 1C uint32
// 34 uint32 face offset 2, relative to info chunk
//
// Face entry
// length: 0x34
// 0 uint16 1
// 2 uint16 count
// 4 [0x14]uint16 unknown, first $count are set
// 2C uint32 face desc offset
// 30 uint32 0x18
//
// Face entry 2
// length: 0x48
// C uint32 FFFFFFFF
//
texID := entry.uint16(0x0)
vf2 := entry.uint32(0x4)
vtxOffset := entry.uint32(0x8)
vtx := desc[vtxOffset:]
vf := entry.uint32(0xC)
faceOffset := entry.uint32(0x10)
faceCount := int(entry.uint32(0x14))
face := info[faceOffset:]
fmt.Fprintf(out, "# desc0 %#x\n", faceOffset)
for i := 0; i < faceCount; i++ {
//face := face[uint32(i)*34:]
//fmt.Fprintf(out, "# flag %#x\n", face.uint16(2))
//fmt.Fprintf(out, "# offset %#x\n", face.uint32(0x2C))
}
fmt.Fprintf(out, "# %#x %#x %#x %#x %#x %#x\n",
o,
entry.uint32(0),
vf2,
vf,
entry.uint32(0x14),
entry.uint32(0x1c),
)
// Compute number of vertices.
// TODO: Figure out the proper way to do this.
vo := vtx.uint32(0x30)
fo := desc.uint32(face.uint32(0x2C) + 0x10)
vn := int((fo - vo) / uint32(vtx[0x3A]))
parseVTX(out, vtx, tbl, fo-vo)
//fmt.Fprintf(out, "g OBJ_%#x\n", o)
fmt.Fprintf(out, "usemtl %s\n", symbols[texID])
for i := 0; i < faceCount; i++ {
off := face.uint32(0x2C)
fmt.Fprintf(out, "g face_%x\n", off)
//fmt.Fprintf(out, "# flag %#x\n", face.uint16(2))
fmt.Fprintf(out, "# face offset %#x\n", off)
parseFC(out, desc[off:], tbl, vn)
face = face[0x34:]
io.WriteString(out, "\n")
}
totalVcount += uint32(vn)
totalNcount += uint32(vn)
totalTCcount += uint32(vn)
}
func parseVTX(out io.Writer, vtx, tbl readBuf, size uint32) {
// 30 offset, relative to data table
// 3A "format" (record size)
offset := vtx.uint32(0x30)
format := uint32(vtx[0x3A])
fmt.Fprintf(out, "# vertex format %#x\n", format)
parseVertices(out, tbl[offset:offset+size], format)
}
func parseFC(out io.Writer, desc, tbl readBuf, vn int) {
// 10 uint32 offset, relative to data table
// 18 uint32 count
// A8 uint8 header data (huh? actually byte C in the third face after this one)
format := uint32(1)
if vn > 256 {
format = uint32(2)
}
fmt.Fprintf(out, "# face format %#x\n", format)
offset := desc.uint32(0x10)
size := desc.uint32(0x18) * format
fmt.Fprintf(out, "# vn %#x\n", vn)
fmt.Fprintf(out, "# size %#x\n", size)
fmt.Fprintf(out, "# %#x %#x\n", desc[0x0C], desc[0xA8])
parseFaces(out, tbl[offset:offset+size], format)
}
func parseVertices(out io.Writer, data readBuf, f uint32) {
for len(data) > 0 {
fmt.Fprintf(out, "v % 11f % 11f % 11f\n", data.float32(0), data.float32(4), data.float32(8))
fmt.Fprintf(out, "vn % 11f % 11f % 11f\n", data.float32(12), data.float32(16), data.float32(20))
fmt.Fprintf(out, "vt % 11f % 11f\n", adaptU(data.float32(24)), data.float32(28))
hasNormals = true
data = data[f:]
}
}
func parseFaces(out io.Writer, data readBuf, f uint32) {
switch f {
case 1:
// for u8 face descriptors
for ; len(data) > 0; data = data[3*f:] {
a, b, c := uint32(data[0]), uint32(data[1]), uint32(data[2])
fmt.Fprintf(out, "f %d/%d/%d %d/%d/%d %d/%d/%d\n",
a+totalVcount, a+totalTCcount, a+totalNcount,
b+totalVcount, b+totalTCcount, b+totalNcount,
c+totalVcount, c+totalTCcount, c+totalNcount,
)
}
case 2:
// for u16 face descriptors
for ; len(data) > 0; data = data[3*f:] {
a, b, c := uint32(data.uint16(0)), uint32(data.uint16(2)), uint32(data.uint16(4))
fmt.Fprintf(out, "f %d/%d/%d %d/%d/%d %d/%d/%d\n",
a+totalVcount, a+totalTCcount, a+totalNcount,
b+totalVcount, b+totalTCcount, b+totalNcount,
c+totalVcount, c+totalTCcount, c+totalNcount,
)
}
}
}
// Double and mirror the X coordinate of the texture vertex.
// Note: the Egg texture should not be mirrored.
func adaptU(u float32) float32 {
u *= 2
//if u > 1 {
// u = 2 - u
//}
//if u < 0 {
// u = -u
//}
return u
}
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
}
@Lilothestitch16
Copy link

How do I use this?

@fm360
Copy link

fm360 commented Dec 13, 2015

How can I use this???

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment