Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Last active April 17, 2020 11:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterhellberg/c2c30bef0cd9c9e03ba295f505b74b4d to your computer and use it in GitHub Desktop.
Save peterhellberg/c2c30bef0cd9c9e03ba295f505b74b4d to your computer and use it in GitHub Desktop.
Initial version of a parser for ACNL pattern files
package acnl
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"io"
"io/ioutil"
)
// Pattern data layout (identical to binary QR code contents):
//
// 0x 00 - 0x 29 ( 42) = Pattern Title
// 0x 2A - 0x 2B ( 2) = User ID
// 0x 2C - 0x 3F ( 20) = User Name
// 0x 40 - 0x 41 ( 2) = Town ID
// 0x 42 - 0x 55 ( 20) = Town Name
// 0x 56 - 0x 57 ( 2) = Unknown (values are usually random - changing seems to have no effect)
// 0x 58 - 0x 66 ( 15) = Color code indexes
// 0x 67 ( 1) = Unknown (value is usually random - changing seems to have no effect)
// 0x 68 ( 1) = Ten? (seems to always be 0x0A or 0x00)
// 0x 69 ( 1) = Pattern type (see below)
// 0x 6A - 0x 6B ( 2) = Zero? (seems to always be 0x0000)
// 0x 6C - 0x26B (512) = Pattern Data 1 (mandatory)
// 0x26C - 0x46B (512) = Pattern Data 2 (optional)
// 0x46C - 0x66B (512) = Pattern Data 3 (optional)
// 0x66C - 0x86B (512) = Pattern Data 4 (optional)
// 0x86C - 0x86F ( 4) = Zero padding (optional)
//
// Pattern types:
// 0x00 = fullsleeve dress (pro)
// 0x01 = halfsleeve dress (pro)
// 0x02 = sleeveless dress (pro)
// 0x03 = Longsleeve shirt (pro)
// 0x04 = Midsleeve shirt (pro)
// 0x07 = Plain pattern (hat)
// 0x08 = Standee (pro)
// 0x09 = Plain pattern (easel)
//
type Pattern struct {
Title [42]byte
UserID uint16
UserName [20]byte
TownID uint16
TownName [20]byte
Hash uint16
ColorIndexes [15]uint8
Unknown uint8
Ten uint8
Type PatternType
Padding uint16
Data1 [512]uint8
Data2 [512]uint8
Data3 [512]uint8
Data4 [512]uint8
ZeroPadding [4]byte
}
// PatternType of the pattern
type PatternType uint8
// The various types of patterns
var (
DressLongSleeves PatternType = 0
DressShortSleeves PatternType = 1
DressSleveless PatternType = 2
ShirtLongSleeves PatternType = 3
ShirtShortSleeves PatternType = 4
ShirtSleeveless PatternType = 5
HatWithHorns PatternType = 6
HatWithoutHorns PatternType = 7
Standee PatternType = 8
NormalPattern PatternType = 9
)
// ReadBytes containing the bytes from an .acnl file
func ReadBytes(b []byte) (Pattern, error) {
return Read(bytes.NewReader(b))
}
// Read and .acnl file
func Read(r io.Reader) (Pattern, error) {
var p Pattern
b, err := ioutil.ReadAll(io.LimitReader(r, 2160))
if err != nil {
return Pattern{}, err
}
if len(b) == 620 {
b = append(b, make([]byte, 1540)...)
}
return p, binary.Read(bytes.NewReader(b), binary.LittleEndian, &p)
}
// Read the bytes for the pattern
func (p Pattern) Read(b []byte) (int, error) {
var buf bytes.Buffer
if err := binary.Write(&buf, binary.LittleEndian, &p); err != nil {
return 0, err
}
if p.Size() == 32 {
return copy(b, buf.Next(620)), io.EOF
}
return copy(b, buf.Bytes()), io.EOF
}
// TitleString of the pattern
func (p Pattern) TitleString() string {
return string(bytes.ReplaceAll(p.Title[:], []byte{0x0}, nil))
}
// UserNameString of the pattern
func (p Pattern) UserNameString() string {
return string(bytes.ReplaceAll(p.UserName[:], []byte{0x0}, nil))
}
// Size of the pattern
func (p Pattern) Size() int {
if p.Type < HatWithHorns {
return 64
}
return 32
}
// Bounds of the pattern
func (p Pattern) Bounds() image.Rectangle {
s := p.Size()
return image.Rect(0, 0, s, s)
}
// ColorModel of the pattern
func (p Pattern) ColorModel() color.Model {
return color.RGBAModel
}
// At returns the color at x and y
func (p Pattern) At(x, y int) color.Color {
var i uint8
switch {
case x >= 0 && x < 32 && y >= 0 && y < 32:
i = idx(x+32*y, p.Data1)
case x >= 0 && x < 32 && y >= 32 && y < 64:
i = idx(x+32*(y-32), p.Data2)
case x >= 32 && x < 64 && y >= 0 && y < 32:
i = idx(x-32+32*y, p.Data3)
case x >= 32 && x < 64 && y >= 32 && y < 64:
i = idx(x-32+32*(y-32), p.Data4)
}
if i == 15 {
return color.Transparent
}
return colorFromIndex(p.ColorIndexes[i])
}
func idx(n int, d [512]byte) uint8 {
o := n / 2
if n == o+o {
return d[o] & 0b00001111
}
return d[o] & 0b11110000 >> 4
}
func colorFromIndex(i uint8) color.RGBA {
switch i {
case 0x00:
return color.RGBA{0xFF, 0xEF, 0xFF, 0xFF}
case 0x01:
return color.RGBA{0xFF, 0x9A, 0xAD, 0xFF}
case 0x02:
return color.RGBA{0xEF, 0x55, 0x9C, 0xFF}
case 0x03:
return color.RGBA{0xFF, 0x65, 0xAD, 0xFF}
case 0x04:
return color.RGBA{0xFF, 0x00, 0x63, 0xFF}
case 0x05:
return color.RGBA{0xBD, 0x45, 0x73, 0xFF}
case 0x06:
return color.RGBA{0xCE, 0x00, 0x52, 0xFF}
case 0x07:
return color.RGBA{0x9C, 0x00, 0x31, 0xFF}
case 0x08:
return color.RGBA{0x52, 0x20, 0x31, 0xFF}
case 0x10:
return color.RGBA{0xFF, 0xBA, 0xCE, 0xFF}
case 0x11:
return color.RGBA{0xFF, 0x75, 0x73, 0xFF}
case 0x12:
return color.RGBA{0xDE, 0x30, 0x10, 0xFF}
case 0x13:
return color.RGBA{0xFF, 0x55, 0x42, 0xFF}
case 0x14:
return color.RGBA{0xFF, 0x00, 0x00, 0xFF}
case 0x15:
return color.RGBA{0xCE, 0x65, 0x63, 0xFF}
case 0x16:
return color.RGBA{0xBD, 0x45, 0x42, 0xFF}
case 0x17:
return color.RGBA{0xBD, 0x00, 0x00, 0xFF}
case 0x18:
return color.RGBA{0x8C, 0x20, 0x21, 0xFF}
case 0x20:
return color.RGBA{0xDE, 0xCF, 0xBD, 0xFF}
case 0x21:
return color.RGBA{0xFF, 0xCF, 0x63, 0xFF}
case 0x22:
return color.RGBA{0xDE, 0x65, 0x21, 0xFF}
case 0x23:
return color.RGBA{0xFF, 0xAA, 0x21, 0xFF}
case 0x24:
return color.RGBA{0xFF, 0x65, 0x00, 0xFF}
case 0x25:
return color.RGBA{0xBD, 0x8A, 0x52, 0xFF}
case 0x26:
return color.RGBA{0xDE, 0x45, 0x00, 0xFF}
case 0x27:
return color.RGBA{0xBD, 0x45, 0x00, 0xFF}
case 0x28:
return color.RGBA{0x63, 0x30, 0x10, 0xFF}
case 0x30:
return color.RGBA{0xFF, 0xEF, 0xDE, 0xFF}
case 0x31:
return color.RGBA{0xFF, 0xDF, 0xCE, 0xFF}
case 0x32:
return color.RGBA{0xFF, 0xCF, 0xAD, 0xFF}
case 0x33:
return color.RGBA{0xFF, 0xBA, 0x8C, 0xFF}
case 0x34:
return color.RGBA{0xFF, 0xAA, 0x8C, 0xFF}
case 0x35:
return color.RGBA{0xDE, 0x8A, 0x63, 0xFF}
case 0x36:
return color.RGBA{0xBD, 0x65, 0x42, 0xFF}
case 0x37:
return color.RGBA{0x9C, 0x55, 0x31, 0xFF}
case 0x38:
return color.RGBA{0x8C, 0x45, 0x21, 0xFF}
case 0x40:
return color.RGBA{0xFF, 0xCF, 0xFF, 0xFF}
case 0x41:
return color.RGBA{0xEF, 0x8A, 0xFF, 0xFF}
case 0x42:
return color.RGBA{0xCE, 0x65, 0xDE, 0xFF}
case 0x43:
return color.RGBA{0xBD, 0x8A, 0xCE, 0xFF}
case 0x44:
return color.RGBA{0xCE, 0x00, 0xFF, 0xFF}
case 0x45:
return color.RGBA{0x9C, 0x65, 0x9C, 0xFF}
case 0x46:
return color.RGBA{0x8C, 0x00, 0xAD, 0xFF}
case 0x47:
return color.RGBA{0x52, 0x00, 0x73, 0xFF}
case 0x48:
return color.RGBA{0x31, 0x00, 0x42, 0xFF}
case 0x50:
return color.RGBA{0xFF, 0xBA, 0xFF, 0xFF}
case 0x51:
return color.RGBA{0xFF, 0x9A, 0xFF, 0xFF}
case 0x52:
return color.RGBA{0xDE, 0x20, 0xBD, 0xFF}
case 0x53:
return color.RGBA{0xFF, 0x55, 0xEF, 0xFF}
case 0x54:
return color.RGBA{0xFF, 0x00, 0xCE, 0xFF}
case 0x55:
return color.RGBA{0x8C, 0x55, 0x73, 0xFF}
case 0x56:
return color.RGBA{0xBD, 0x00, 0x9C, 0xFF}
case 0x57:
return color.RGBA{0x8C, 0x00, 0x63, 0xFF}
case 0x58:
return color.RGBA{0x52, 0x00, 0x42, 0xFF}
case 0x60:
return color.RGBA{0xDE, 0xBA, 0x9C, 0xFF}
case 0x61:
return color.RGBA{0xCE, 0xAA, 0x73, 0xFF}
case 0x62:
return color.RGBA{0x73, 0x45, 0x31, 0xFF}
case 0x63:
return color.RGBA{0xAD, 0x75, 0x42, 0xFF}
case 0x64:
return color.RGBA{0x9C, 0x30, 0x00, 0xFF}
case 0x65:
return color.RGBA{0x73, 0x30, 0x21, 0xFF}
case 0x66:
return color.RGBA{0x52, 0x20, 0x00, 0xFF}
case 0x67:
return color.RGBA{0x31, 0x10, 0x00, 0xFF}
case 0x68:
return color.RGBA{0x21, 0x10, 0x00, 0xFF}
case 0x70:
return color.RGBA{0xFF, 0xFF, 0xCE, 0xFF}
case 0x71:
return color.RGBA{0xFF, 0xFF, 0x73, 0xFF}
case 0x72:
return color.RGBA{0xDE, 0xDF, 0x21, 0xFF}
case 0x73:
return color.RGBA{0xFF, 0xFF, 0x00, 0xFF}
case 0x74:
return color.RGBA{0xFF, 0xDF, 0x00, 0xFF}
case 0x75:
return color.RGBA{0xCE, 0xAA, 0x00, 0xFF}
case 0x76:
return color.RGBA{0x9C, 0x9A, 0x00, 0xFF}
case 0x77:
return color.RGBA{0x8C, 0x75, 0x00, 0xFF}
case 0x78:
return color.RGBA{0x52, 0x55, 0x00, 0xFF}
case 0x80:
return color.RGBA{0xDE, 0xBA, 0xFF, 0xFF}
case 0x81:
return color.RGBA{0xBD, 0x9A, 0xEF, 0xFF}
case 0x82:
return color.RGBA{0x63, 0x30, 0xCE, 0xFF}
case 0x83:
return color.RGBA{0x9C, 0x55, 0xFF, 0xFF}
case 0x84:
return color.RGBA{0x63, 0x00, 0xFF, 0xFF}
case 0x85:
return color.RGBA{0x52, 0x45, 0x8C, 0xFF}
case 0x86:
return color.RGBA{0x42, 0x00, 0x9C, 0xFF}
case 0x87:
return color.RGBA{0x21, 0x00, 0x63, 0xFF}
case 0x88:
return color.RGBA{0x21, 0x10, 0x31, 0xFF}
case 0x90:
return color.RGBA{0xBD, 0xBA, 0xFF, 0xFF}
case 0x91:
return color.RGBA{0x8C, 0x9A, 0xFF, 0xFF}
case 0x92:
return color.RGBA{0x31, 0x30, 0xAD, 0xFF}
case 0x93:
return color.RGBA{0x31, 0x55, 0xEF, 0xFF}
case 0x94:
return color.RGBA{0x00, 0x00, 0xFF, 0xFF}
case 0x95:
return color.RGBA{0x31, 0x30, 0x8C, 0xFF}
case 0x96:
return color.RGBA{0x00, 0x00, 0xAD, 0xFF}
case 0x97:
return color.RGBA{0x10, 0x10, 0x63, 0xFF}
case 0x98:
return color.RGBA{0x00, 0x00, 0x21, 0xFF}
case 0xA0:
return color.RGBA{0x9C, 0xEF, 0xBD, 0xFF}
case 0xA1:
return color.RGBA{0x63, 0xCF, 0x73, 0xFF}
case 0xA2:
return color.RGBA{0x21, 0x65, 0x10, 0xFF}
case 0xA3:
return color.RGBA{0x42, 0xAA, 0x31, 0xFF}
case 0xA4:
return color.RGBA{0x00, 0x8A, 0x31, 0xFF}
case 0xA5:
return color.RGBA{0x52, 0x75, 0x52, 0xFF}
case 0xA6:
return color.RGBA{0x21, 0x55, 0x00, 0xFF}
case 0xA7:
return color.RGBA{0x10, 0x30, 0x21, 0xFF}
case 0xA8:
return color.RGBA{0x00, 0x20, 0x10, 0xFF}
case 0xB0:
return color.RGBA{0xDE, 0xFF, 0xBD, 0xFF}
case 0xB1:
return color.RGBA{0xCE, 0xFF, 0x8C, 0xFF}
case 0xB2:
return color.RGBA{0x8C, 0xAA, 0x52, 0xFF}
case 0xB3:
return color.RGBA{0xAD, 0xDF, 0x8C, 0xFF}
case 0xB4:
return color.RGBA{0x8C, 0xFF, 0x00, 0xFF}
case 0xB5:
return color.RGBA{0xAD, 0xBA, 0x9C, 0xFF}
case 0xB6:
return color.RGBA{0x63, 0xBA, 0x00, 0xFF}
case 0xB7:
return color.RGBA{0x52, 0x9A, 0x00, 0xFF}
case 0xB8:
return color.RGBA{0x31, 0x65, 0x00, 0xFF}
case 0xC0:
return color.RGBA{0xBD, 0xDF, 0xFF, 0xFF}
case 0xC1:
return color.RGBA{0x73, 0xCF, 0xFF, 0xFF}
case 0xC2:
return color.RGBA{0x31, 0x55, 0x9C, 0xFF}
case 0xC3:
return color.RGBA{0x63, 0x9A, 0xFF, 0xFF}
case 0xC4:
return color.RGBA{0x10, 0x75, 0xFF, 0xFF}
case 0xC5:
return color.RGBA{0x42, 0x75, 0xAD, 0xFF}
case 0xC6:
return color.RGBA{0x21, 0x45, 0x73, 0xFF}
case 0xC7:
return color.RGBA{0x00, 0x20, 0x73, 0xFF}
case 0xC8:
return color.RGBA{0x00, 0x10, 0x42, 0xFF}
case 0xD0:
return color.RGBA{0xAD, 0xFF, 0xFF, 0xFF}
case 0xD1:
return color.RGBA{0x52, 0xFF, 0xFF, 0xFF}
case 0xD2:
return color.RGBA{0x00, 0x8A, 0xBD, 0xFF}
case 0xD3:
return color.RGBA{0x52, 0xBA, 0xCE, 0xFF}
case 0xD4:
return color.RGBA{0x00, 0xCF, 0xFF, 0xFF}
case 0xD5:
return color.RGBA{0x42, 0x9A, 0xAD, 0xFF}
case 0xD6:
return color.RGBA{0x00, 0x65, 0x8C, 0xFF}
case 0xD7:
return color.RGBA{0x00, 0x45, 0x52, 0xFF}
case 0xD8:
return color.RGBA{0x00, 0x20, 0x31, 0xFF}
case 0xE0:
return color.RGBA{0xCE, 0xFF, 0xEF, 0xFF}
case 0xE1:
return color.RGBA{0xAD, 0xEF, 0xDE, 0xFF}
case 0xE2:
return color.RGBA{0x31, 0xCF, 0xAD, 0xFF}
case 0xE3:
return color.RGBA{0x52, 0xEF, 0xBD, 0xFF}
case 0xE4:
return color.RGBA{0x00, 0xFF, 0xCE, 0xFF}
case 0xE5:
return color.RGBA{0x73, 0xAA, 0xAD, 0xFF}
case 0xE6:
return color.RGBA{0x00, 0xAA, 0x9C, 0xFF}
case 0xE7:
return color.RGBA{0x00, 0x8A, 0x73, 0xFF}
case 0xE8:
return color.RGBA{0x00, 0x45, 0x31, 0xFF}
case 0xF0:
return color.RGBA{0xAD, 0xFF, 0xAD, 0xFF}
case 0xF1:
return color.RGBA{0x73, 0xFF, 0x73, 0xFF}
case 0xF2:
return color.RGBA{0x63, 0xDF, 0x42, 0xFF}
case 0xF3:
return color.RGBA{0x00, 0xFF, 0x00, 0xFF}
case 0xF4:
return color.RGBA{0x21, 0xDF, 0x21, 0xFF}
case 0xF5:
return color.RGBA{0x52, 0xBA, 0x52, 0xFF}
case 0xF6:
return color.RGBA{0x00, 0xBA, 0x00, 0xFF}
case 0xF7:
return color.RGBA{0x00, 0x8A, 0x00, 0xFF}
case 0xF8:
return color.RGBA{0x21, 0x45, 0x21, 0xFF}
case 0x0F:
return color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
case 0x1F:
return color.RGBA{0xEC, 0xEC, 0xEC, 0xFF}
case 0x2F:
return color.RGBA{0xDA, 0xDA, 0xDA, 0xFF}
case 0x3F:
return color.RGBA{0xC8, 0xC8, 0xC8, 0xFF}
case 0x4F:
return color.RGBA{0xB6, 0xB6, 0xB6, 0xFF}
case 0x5F:
return color.RGBA{0xA3, 0xA3, 0xA3, 0xFF}
case 0x6F:
return color.RGBA{0x91, 0x91, 0x91, 0xFF}
case 0x7F:
return color.RGBA{0x7F, 0x7F, 0x7F, 0xFF}
case 0x8F:
return color.RGBA{0x6D, 0x6D, 0x6D, 0xFF}
case 0x9F:
return color.RGBA{0x5B, 0x5B, 0x5B, 0xFF}
case 0xAF:
return color.RGBA{0x48, 0x48, 0x48, 0xFF}
case 0xBF:
return color.RGBA{0x36, 0x36, 0x36, 0xFF}
case 0xCF:
return color.RGBA{0x24, 0x24, 0x24, 0xFF}
case 0xDF:
return color.RGBA{0x12, 0x12, 0x12, 0xFF}
case 0xEF:
return color.RGBA{0x00, 0x00, 0x00, 0xFF}
default:
return color.RGBA{0, 0, 0, 0}
}
}
@peterhellberg
Copy link
Author

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