2023-12-10 04:25:04 +00:00
|
|
|
package main
|
|
|
|
|
2023-12-10 05:19:52 +00:00
|
|
|
// Binary apgbpal creates a 56-byte file named "test.pal" in the current working
|
|
|
|
// directory. I hope it might be an Analogue Pocket GB Palette file, but I don't
|
|
|
|
// know that for sure yet.
|
|
|
|
|
|
|
|
// apgbpal is licensed under 0BSD.
|
|
|
|
//
|
|
|
|
// Zero-Clause BSD
|
|
|
|
// =============
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and/or distribute this software for
|
|
|
|
// any purpose with or without fee is hereby granted.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
|
|
|
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
|
|
// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
|
|
|
// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
|
|
|
// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
|
|
|
// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
|
|
|
// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
|
2023-12-10 04:50:15 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2023-12-10 05:00:02 +00:00
|
|
|
"os"
|
2023-12-10 05:13:16 +00:00
|
|
|
"strconv"
|
2023-12-10 05:08:52 +00:00
|
|
|
"strings"
|
2023-12-10 04:50:15 +00:00
|
|
|
)
|
|
|
|
|
2023-12-10 04:25:04 +00:00
|
|
|
type RGBA struct {
|
|
|
|
R byte
|
|
|
|
G byte
|
|
|
|
B byte
|
|
|
|
A byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c RGBA) Bytes() [4]byte {
|
2023-12-10 05:13:16 +00:00
|
|
|
return [4]byte{c.R, c.G, c.B, c.A}
|
2023-12-10 04:25:04 +00:00
|
|
|
}
|
|
|
|
|
2023-12-10 04:50:15 +00:00
|
|
|
func HexByteAt(str string, index int) (byte, error) {
|
2023-12-10 05:13:16 +00:00
|
|
|
if len(str) < index+2 {
|
2023-12-10 04:50:15 +00:00
|
|
|
return 0, fmt.Errorf("no hex byte at %d in too short string %q", index, str)
|
|
|
|
}
|
|
|
|
big, err := strconv.ParseUint(str[index:index+1], 16, 8)
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("can't parse hex byte at %d in string %q: %w", index, str, err)
|
|
|
|
}
|
|
|
|
return byte(big), nil
|
|
|
|
}
|
|
|
|
|
2023-12-10 05:16:12 +00:00
|
|
|
func ParseRGBA(str string) (RGBA, error) {
|
2023-12-10 04:50:15 +00:00
|
|
|
s := strings.TrimSpace(str)
|
|
|
|
s = strings.ToLower(s)
|
|
|
|
s, _ = strings.CutPrefix(s, "#")
|
|
|
|
var ret RGBA
|
|
|
|
switch len(s) {
|
|
|
|
case 6:
|
|
|
|
ret.A = 0xFF
|
|
|
|
case 8:
|
|
|
|
var err error
|
|
|
|
ret.A, err = HexByteAt(s, 6)
|
|
|
|
if err != nil {
|
2023-12-10 05:13:16 +00:00
|
|
|
return RGBA{}, fmt.Errorf("can't parse alpha channel: %v", err)
|
2023-12-10 04:50:15 +00:00
|
|
|
}
|
|
|
|
default:
|
2023-12-10 05:13:16 +00:00
|
|
|
return RGBA{}, fmt.Errorf("%q is not an RGBA value: wrong length after trimming", str)
|
2023-12-10 04:50:15 +00:00
|
|
|
}
|
2023-12-10 05:13:16 +00:00
|
|
|
var err error
|
|
|
|
if ret.R, err = HexByteAt(s, 0); err != nil {
|
|
|
|
return RGBA{}, fmt.Errorf("can't parse red channel: %v", err)
|
2023-12-10 04:50:15 +00:00
|
|
|
}
|
2023-12-10 05:13:16 +00:00
|
|
|
if ret.G, err = HexByteAt(s, 2); err != nil {
|
|
|
|
return RGBA{}, fmt.Errorf("can't parse green channel: %v", err)
|
2023-12-10 04:50:15 +00:00
|
|
|
}
|
2023-12-10 05:13:16 +00:00
|
|
|
if ret.B, err = HexByteAt(s, 4); err != nil {
|
|
|
|
return RGBA{}, fmt.Errorf("can't parse blue channel: %v", err)
|
2023-12-10 04:50:15 +00:00
|
|
|
}
|
2023-12-10 05:13:16 +00:00
|
|
|
return ret, nil
|
2023-12-10 04:50:15 +00:00
|
|
|
}
|
|
|
|
|
2023-12-10 04:25:04 +00:00
|
|
|
type BGPal [4]RGBA
|
|
|
|
|
|
|
|
func (b BGPal) Bytes() [20]byte {
|
|
|
|
var ret [20]byte
|
|
|
|
ret[0] = 'b'
|
|
|
|
ret[1] = 'g'
|
|
|
|
ret[2] = ':'
|
|
|
|
for c := 0; c < 4; c++ {
|
|
|
|
bb := b[c].Bytes()
|
|
|
|
for i := 0; i < 4; i++ {
|
2023-12-10 05:08:52 +00:00
|
|
|
ret[3+i+4*c] = bb[i]
|
2023-12-10 04:25:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ret[19] = '\n'
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2023-12-10 04:50:15 +00:00
|
|
|
func MustParseBGPal(strs []string) BGPal {
|
|
|
|
if len(strs) != 4 {
|
|
|
|
log.Fatalf("MustParseBGPal requires length 4, got %d", len(strs))
|
|
|
|
}
|
|
|
|
var ret BGPal
|
|
|
|
for i := 0; i < 4; i++ {
|
2023-12-10 05:16:12 +00:00
|
|
|
var err error
|
|
|
|
ret[i], err = ParseRGBA(strs[i])
|
2023-12-10 04:50:15 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("MustParseBGPal can't parse at %d: %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2023-12-10 04:25:04 +00:00
|
|
|
type OBJPal [3]RGBA
|
|
|
|
|
|
|
|
func (o OBJPal) Bytes(indexchar byte) [18]byte {
|
|
|
|
var ret [18]byte
|
|
|
|
ret[0] = 'o'
|
|
|
|
ret[1] = 'b'
|
|
|
|
ret[2] = 'j'
|
|
|
|
ret[3] = indexchar
|
|
|
|
ret[4] = ':'
|
|
|
|
for c := 0; c < 3; c++ {
|
|
|
|
oo := o[c].Bytes()
|
|
|
|
for i := 0; i < 4; i++ {
|
2023-12-10 05:08:52 +00:00
|
|
|
ret[5+i+4*c] = oo[i]
|
2023-12-10 04:25:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ret[17] = '\n'
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2023-12-10 05:00:02 +00:00
|
|
|
func MustParseOBJPal(strs []string) OBJPal {
|
|
|
|
if len(strs) == 4 {
|
2023-12-10 05:13:16 +00:00
|
|
|
log.Println("Discarding index 0 of OBJ pal")
|
2023-12-10 05:00:02 +00:00
|
|
|
strs = strs[1:]
|
|
|
|
}
|
2023-12-10 04:50:15 +00:00
|
|
|
if len(strs) != 3 {
|
2023-12-10 05:08:52 +00:00
|
|
|
log.Fatalf("MustParseObjPal requires length 3, got %d", len(strs))
|
2023-12-10 04:50:15 +00:00
|
|
|
}
|
|
|
|
var ret OBJPal
|
|
|
|
for i := 0; i < 3; i++ {
|
2023-12-10 05:16:12 +00:00
|
|
|
var err error
|
|
|
|
ret[i], err = ParseRGBA(strs[i])
|
2023-12-10 04:50:15 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("MustParseOBJPal can't parse at %d: %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2023-12-10 04:25:04 +00:00
|
|
|
type APGBPal struct {
|
2023-12-10 05:08:52 +00:00
|
|
|
BG BGPal
|
2023-12-10 04:50:15 +00:00
|
|
|
OBJ0 OBJPal
|
2023-12-10 04:25:04 +00:00
|
|
|
OBJ1 OBJPal
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a APGBPal) Bytes() [56]byte {
|
2023-12-10 04:50:15 +00:00
|
|
|
var ret [56]byte
|
|
|
|
for i, b := range a.BG.Bytes() {
|
|
|
|
ret[i] = b
|
|
|
|
}
|
|
|
|
for i, b := range a.OBJ0.Bytes('0') {
|
|
|
|
ret[i+20] = b
|
|
|
|
}
|
|
|
|
for i, b := range a.OBJ1.Bytes('1') {
|
|
|
|
ret[i+38] = b
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2023-12-10 05:00:02 +00:00
|
|
|
func main() {
|
2023-12-10 05:16:12 +00:00
|
|
|
f, err := os.OpenFile(
|
|
|
|
"test.pal",
|
|
|
|
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
|
|
|
0644)
|
2023-12-10 05:00:02 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("can't create test.pal: %v", err)
|
|
|
|
}
|
2023-12-10 05:08:52 +00:00
|
|
|
aUpPal := APGBPal{
|
|
|
|
BG: MustParseBGPal([]string{"ffffff", "ff8484", "943a3a", "000000"}),
|
2023-12-10 05:16:12 +00:00
|
|
|
OBJ0: MustParseOBJPal([]string{"7bff31", "008400", "000000"}),
|
|
|
|
OBJ1: MustParseOBJPal([]string{"63a5ff", "0000ff", "000000"}),
|
2023-12-10 05:00:02 +00:00
|
|
|
}
|
2023-12-10 05:16:12 +00:00
|
|
|
palb := aUpPal.Bytes()
|
|
|
|
n, err := f.Write(palb[:])
|
2023-12-10 05:00:02 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("can't save test.pal: %v", err)
|
|
|
|
}
|
|
|
|
if n != 56 {
|
|
|
|
log.Fatalf("unexpected write length: %d", n)
|
|
|
|
}
|
|
|
|
f.Close()
|
|
|
|
os.Exit(0)
|
2023-12-10 05:08:52 +00:00
|
|
|
}
|