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 }