Created
February 20, 2020 15:20
-
-
Save huderlem/a5c4f66969ab57a0dc0f71de4e321297 to your computer and use it in GitHub Desktop.
Generates a full image of a level in Looney Tunes: Carrot Crazy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "encoding/binary" | |
| "image" | |
| "image/color" | |
| "image/draw" | |
| "image/png" | |
| "io/ioutil" | |
| "os" | |
| ) | |
| type levelMap struct { | |
| blockWidth int | |
| blockHeight int | |
| blockMap []int | |
| blockDefinitions map[int][]int | |
| } | |
| func readImageFile(filepath string) *image.Paletted { | |
| f, err := os.Open(filepath) | |
| if err != nil { | |
| panic(err) | |
| } | |
| defer f.Close() | |
| img, err := png.Decode(f) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return img.(*image.Paletted) | |
| } | |
| func readFileAsBytes(filepath string) []byte { | |
| data, err := ioutil.ReadFile(filepath) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return data | |
| } | |
| func readMetatiles(filepath string) [][]int { | |
| data := readFileAsBytes(filepath) | |
| metatiles := [][]int{} | |
| for i := 0; i < len(data); i += 4 { | |
| metatiles = append(metatiles, []int{ | |
| int(data[i]), | |
| int(data[i+1]), | |
| int(data[i+2]), | |
| int(data[i+3]), | |
| }) | |
| } | |
| for len(metatiles) < 256 { | |
| metatiles = append(metatiles, []int{0, 0, 0, 0}) | |
| } | |
| return metatiles | |
| } | |
| func readLevelMap(filepath string) levelMap { | |
| data := readFileAsBytes(filepath) | |
| level := levelMap{ | |
| blockWidth: int(binary.LittleEndian.Uint16(data[0:2]) / 64), | |
| blockHeight: int((binary.LittleEndian.Uint16(data[2:4]) - 64) / 64), | |
| blockMap: []int{}, | |
| blockDefinitions: map[int][]int{}, | |
| } | |
| data = data[6:] | |
| for i := 0; i < level.blockWidth*(level.blockHeight+1); i++ { | |
| addr := int(binary.LittleEndian.Uint16(data[i*2 : i*2+2])) | |
| level.blockMap = append(level.blockMap, addr) | |
| if _, ok := level.blockDefinitions[addr]; !ok { | |
| blockOffset := addr - 0xc600 - 6 | |
| blockMetatiles := make([]int, 16) | |
| for j := 0; j < 16; j++ { | |
| blockMetatiles[j] = int(data[blockOffset+j]) | |
| } | |
| level.blockDefinitions[addr] = blockMetatiles | |
| } | |
| } | |
| return level | |
| } | |
| func readGBCPalettes(level int) [][]color.RGBA { | |
| switch level { | |
| case 0: | |
| pal0 := []color.RGBA{color.RGBA{0x7 * 8, 0x14 * 8, 0x12 * 8, 255}, color.RGBA{0x1b * 8, 0x12 * 8, 0xe * 8, 255}, color.RGBA{0xf * 8, 0x4 * 8, 0x3 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal1 := []color.RGBA{color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0x1b * 8, 0x12 * 8, 0xe * 8, 255}, color.RGBA{0xf * 8, 0x4 * 8, 0x3 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal2 := []color.RGBA{color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0xe * 8, 0x1c * 8, 0x5 * 8, 255}, color.RGBA{0x5 * 8, 0xf * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal3 := []color.RGBA{color.RGBA{0x1f * 8, 0x19 * 8, 0xe * 8, 255}, color.RGBA{0x14 * 8, 0xd * 8, 0x3 * 8, 255}, color.RGBA{0xc * 8, 0x5 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal4 := []color.RGBA{color.RGBA{0x8 * 8, 0x15 * 8, 0x1f * 8, 255}, color.RGBA{0x1d * 8, 0x17 * 8, 0xc * 8, 255}, color.RGBA{0x10 * 8, 0x9 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal5 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x12 * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0x0 * 8, 0xc * 8, 0x1d * 8, 255}} | |
| pal6 := []color.RGBA{color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x1f * 8, 0x0 * 8, 255}, color.RGBA{0x18 * 8, 0x15 * 8, 0x0 * 8, 255}, color.RGBA{0x12 * 8, 0xe * 8, 0x0 * 8, 255}} | |
| pal7 := []color.RGBA{color.RGBA{0x1d * 8, 0x14 * 8, 0x10 * 8, 255}, color.RGBA{0x16 * 8, 0x9 * 8, 0x8 * 8, 255}, color.RGBA{0xf * 8, 0x4 * 8, 0x3 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7} | |
| case 1: | |
| pal0 := []color.RGBA{color.RGBA{0x1e * 8, 0x1b * 8, 0x11 * 8, 255}, color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0xa * 8, 0x8 * 8, 0x1 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal1 := []color.RGBA{color.RGBA{0xa * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0xf * 8, 0x17 * 8, 0x1f * 8, 255}, color.RGBA{0x19 * 8, 0x1b * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}} | |
| pal2 := []color.RGBA{color.RGBA{0x13 * 8, 0x1f * 8, 0x17 * 8, 255}, color.RGBA{0xc * 8, 0x16 * 8, 0x11 * 8, 255}, color.RGBA{0x6 * 8, 0x10 * 8, 0xb * 8, 255}, color.RGBA{0x0 * 8, 0x4 * 8, 0x3 * 8, 255}} | |
| pal3 := []color.RGBA{color.RGBA{0xa * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0x1b * 8, 0xe * 8, 0x7 * 8, 255}, color.RGBA{0x12 * 8, 0x5 * 8, 0x0 * 8, 255}, color.RGBA{0x6 * 8, 0x1 * 8, 0x0 * 8, 255}} | |
| pal4 := []color.RGBA{color.RGBA{0x15 * 8, 0x18 * 8, 0x1a * 8, 255}, color.RGBA{0xa * 8, 0xf * 8, 0x14 * 8, 255}, color.RGBA{0x5 * 8, 0x8 * 8, 0xb * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal5 := []color.RGBA{color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0x1b * 8, 0xe * 8, 0x7 * 8, 255}, color.RGBA{0x12 * 8, 0x5 * 8, 0x0 * 8, 255}, color.RGBA{0x6 * 8, 0x1 * 8, 0x0 * 8, 255}} | |
| pal6 := []color.RGBA{color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0x17 * 8, 0x15 * 8, 0x15 * 8, 255}, color.RGBA{0xa * 8, 0x9 * 8, 0x9 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal7 := []color.RGBA{color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0x1f * 8, 0xb * 8, 0x13 * 8, 255}, color.RGBA{0xe * 8, 0x3 * 8, 0x6 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7} | |
| case 2: | |
| pal0 := []color.RGBA{color.RGBA{0x1f * 8, 0x1a * 8, 0x1d * 8, 255}, color.RGBA{0x15 * 8, 0x11 * 8, 0x13 * 8, 255}, color.RGBA{0xb * 8, 0x8 * 8, 0x6 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal1 := []color.RGBA{color.RGBA{0xb * 8, 0x14 * 8, 0x1f * 8, 255}, color.RGBA{0x12 * 8, 0x1c * 8, 0xa * 8, 255}, color.RGBA{0x8 * 8, 0x17 * 8, 0x2 * 8, 255}, color.RGBA{0x1 * 8, 0xa * 8, 0x0 * 8, 255}} | |
| pal2 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x14 * 8, 0x1b * 8, 0x1f * 8, 255}, color.RGBA{0xb * 8, 0x14 * 8, 0x1f * 8, 255}, color.RGBA{0x6 * 8, 0xc * 8, 0x17 * 8, 255}} | |
| pal3 := []color.RGBA{color.RGBA{0xb * 8, 0x14 * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x19 * 8, 0x14 * 8, 255}, color.RGBA{0x17 * 8, 0xb * 8, 0x9 * 8, 255}, color.RGBA{0xd * 8, 0x3 * 8, 0x3 * 8, 255}} | |
| pal4 := []color.RGBA{color.RGBA{0x1f * 8, 0x19 * 8, 0x14 * 8, 255}, color.RGBA{0x17 * 8, 0xb * 8, 0x9 * 8, 255}, color.RGBA{0x8 * 8, 0x17 * 8, 0x2 * 8, 255}, color.RGBA{0x1 * 8, 0xa * 8, 0x0 * 8, 255}} | |
| pal5 := []color.RGBA{color.RGBA{0x1d * 8, 0x19 * 8, 0xa * 8, 255}, color.RGBA{0x14 * 8, 0xf * 8, 0x2 * 8, 255}, color.RGBA{0x9 * 8, 0x6 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal6 := []color.RGBA{color.RGBA{0x1d * 8, 0x19 * 8, 0xa * 8, 255}, color.RGBA{0x14 * 8, 0xf * 8, 0x2 * 8, 255}, color.RGBA{0x9 * 8, 0x6 * 8, 0x0 * 8, 255}, color.RGBA{0x8 * 8, 0x17 * 8, 0x2 * 8, 255}} | |
| pal7 := []color.RGBA{color.RGBA{0x1d * 8, 0xc * 8, 0xa * 8, 255}, color.RGBA{0x16 * 8, 0x8 * 8, 0x1 * 8, 255}, color.RGBA{0xd * 8, 0x0 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7} | |
| case 3: | |
| pal0 := []color.RGBA{color.RGBA{0x17 * 8, 0xa * 8, 0x17 * 8, 255}, color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}, color.RGBA{0x8 * 8, 0x0 * 8, 0x8 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal1 := []color.RGBA{color.RGBA{0x15 * 8, 0x1f * 8, 0xa * 8, 255}, color.RGBA{0xb * 8, 0x17 * 8, 0x2 * 8, 255}, color.RGBA{0x3 * 8, 0xd * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal2 := []color.RGBA{color.RGBA{0x1a * 8, 0x17 * 8, 0x14 * 8, 255}, color.RGBA{0x11 * 8, 0x10 * 8, 0xd * 8, 255}, color.RGBA{0x9 * 8, 0x8 * 8, 0x7 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal3 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x1f * 8, 0x0 * 8, 255}, color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal4 := []color.RGBA{color.RGBA{0x1f * 8, 0x19 * 8, 0x12 * 8, 255}, color.RGBA{0x1b * 8, 0x11 * 8, 0x5 * 8, 255}, color.RGBA{0x11 * 8, 0x9 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal5 := []color.RGBA{color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}, color.RGBA{0x17 * 8, 0x5 * 8, 0x4 * 8, 255}, color.RGBA{0xe * 8, 0x1 * 8, 0x1 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal6 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x0 * 8, 255}, color.RGBA{0x1c * 8, 0xf * 8, 0x4 * 8, 255}, color.RGBA{0x16 * 8, 0x4 * 8, 0xe * 8, 255}, color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}} | |
| pal7 := []color.RGBA{color.RGBA{0x11 * 8, 0x10 * 8, 0x1f * 8, 255}, color.RGBA{0xa * 8, 0x8 * 8, 0x18 * 8, 255}, color.RGBA{0x5 * 8, 0x3 * 8, 0x11 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7} | |
| default: | |
| pal0 := []color.RGBA{color.RGBA{0x1f * 8, 0x15 * 8, 0x13 * 8, 255}, color.RGBA{0x16 * 8, 0xa * 8, 0x6 * 8, 255}, color.RGBA{0xd * 8, 0x4 * 8, 0x2 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal1 := []color.RGBA{color.RGBA{0xf * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0xc * 8, 0x19 * 8, 0x3 * 8, 255}, color.RGBA{0x5 * 8, 0xd * 8, 0x1 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal2 := []color.RGBA{color.RGBA{0xf * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0x12 * 8, 0x1a * 8, 0xb * 8, 255}, color.RGBA{0xb * 8, 0xa * 8, 0x1 * 8, 255}, color.RGBA{0x1c * 8, 0x14 * 8, 0xe * 8, 255}} | |
| pal3 := []color.RGBA{color.RGBA{0x1f * 8, 0x17 * 8, 0xe * 8, 255}, color.RGBA{0x16 * 8, 0xd * 8, 0x2 * 8, 255}, color.RGBA{0xc * 8, 0x6 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal4 := []color.RGBA{color.RGBA{0xc * 8, 0x19 * 8, 0x3 * 8, 255}, color.RGBA{0x5 * 8, 0xd * 8, 0x1 * 8, 255}, color.RGBA{0x12 * 8, 0x1a * 8, 0xb * 8, 255}, color.RGBA{0x1c * 8, 0x14 * 8, 0xe * 8, 255}} | |
| pal5 := []color.RGBA{color.RGBA{0x1f * 8, 0x1c * 8, 0x4 * 8, 255}, color.RGBA{0x11 * 8, 0x10 * 8, 0x0 * 8, 255}, color.RGBA{0x9 * 8, 0x8 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal6 := []color.RGBA{color.RGBA{0xf * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0x16 * 8, 0x14 * 8, 0x11 * 8, 255}, color.RGBA{0xc * 8, 0x9 * 8, 0x8 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| pal7 := []color.RGBA{color.RGBA{0xc * 8, 0x19 * 8, 0x3 * 8, 255}, color.RGBA{0x5 * 8, 0xd * 8, 0x1 * 8, 255}, color.RGBA{0x11 * 8, 0x10 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}} | |
| return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7} | |
| } | |
| } | |
| func readGBCTileAttributes(filepath string) []int { | |
| data := readFileAsBytes(filepath) | |
| attributes := make([]int, len(data)) | |
| for i := 0; i < len(data); i++ { | |
| attributes[i] = int(data[i]) | |
| } | |
| return attributes | |
| } | |
| func translateTileID(tileID, baseAddress int) int { | |
| baseID := (baseAddress - 0x8800) / 16 | |
| if tileID > 0x80 { | |
| tileID -= 0x80 | |
| } else { | |
| tileID += 0x80 | |
| } | |
| return tileID - baseID | |
| } | |
| func getTileCoords(tileID int, tileset *image.Paletted) (int, int) { | |
| width := tileset.Bounds().Max.X / 8 | |
| return tileID % width, tileID / width | |
| } | |
| func getColoredTile(tileID int, tile *image.Paletted, gbcPalettes [][]color.RGBA, gbcTileAttributes []int) *image.Paletted { | |
| paletteID := gbcTileAttributes[tileID] | |
| for i := 0; i < 4; i++ { | |
| tile.Palette[i] = gbcPalettes[paletteID][i] | |
| } | |
| return tile | |
| } | |
| func createLevelImg(metatiles [][]int, level levelMap, tileset *image.Paletted, tilesetAddress int, gbcMode bool, gbcPalettes [][]color.RGBA, gbcTileAttributes []int) *image.RGBA { | |
| pixelWidth := level.blockWidth * 64 | |
| pixelHeight := level.blockHeight * 64 | |
| img := image.NewRGBA(image.Rectangle{image.ZP, image.Point{pixelWidth, pixelHeight}}) | |
| for i := 0; i < level.blockWidth; i++ { | |
| for j := 0; j < level.blockHeight; j++ { | |
| blockAddr := level.blockMap[j*level.blockWidth+i] | |
| blockDefinition := level.blockDefinitions[blockAddr] | |
| for k := 0; k < 4; k++ { | |
| for l := 0; l < 4; l++ { | |
| metatile := blockDefinition[l*4+k] | |
| for m := 0; m < 2; m++ { | |
| for n := 0; n < 2; n++ { | |
| x := i*64 + k*16 + m*8 | |
| y := j*64 + l*16 + n*8 | |
| dr := image.Rectangle{image.Point{x, y}, image.Point{x + 8, y + 8}} | |
| tileID := metatiles[metatile][n*2+m] | |
| translatedTileID := translateTileID(tileID, tilesetAddress) | |
| coordX, coordY := getTileCoords(translatedTileID, tileset) | |
| tx := coordX * 8 | |
| ty := coordY * 8 | |
| sr := image.Rectangle{image.Point{tx, ty}, image.Point{tx + 8, ty + 8}} | |
| tile := tileset.SubImage(sr).(*image.Paletted) | |
| if gbcMode { | |
| tile = getColoredTile(translatedTileID, tile, gbcPalettes, gbcTileAttributes) | |
| } | |
| draw.Draw(img, dr, tile, sr.Min, draw.Src) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return img | |
| } | |
| func main() { | |
| tilesetFilepath := `D:\cygwin64\home\huder\carrotcrazy\gfx\fudd_forest\level_tiles_gbc.png` | |
| tilesetAddress := 0x8a40 | |
| metatilesFilepath := `D:\cygwin64\home\huder\carrotcrazy\data\levels\fudd_forest_metatiles_gbc.bin` | |
| levelMapFilepath := `D:\cygwin64\home\huder\carrotcrazy\data\levels\fudd_forest_2_gbc.vdmap` | |
| tileAttributesFilepath := `D:\cygwin64\home\huder\carrotcrazy\gfx\fudd_forest\tile_attributes_gbc.bin` | |
| tileset := readImageFile(tilesetFilepath) | |
| metatiles := readMetatiles(metatilesFilepath) | |
| levelMap := readLevelMap(levelMapFilepath) | |
| gbcPalettes := readGBCPalettes(4) | |
| gbcTileAttributes := readGBCTileAttributes(tileAttributesFilepath) | |
| img := createLevelImg(metatiles, levelMap, tileset, tilesetAddress, true, gbcPalettes, gbcTileAttributes) | |
| f, _ := os.Create("test.png") | |
| png.Encode(f, img) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment