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 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