Skip to content

Commit

Permalink
feat(nesutil): Add subcommand to decode Game Genie codes
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe565 committed Jan 28, 2025
1 parent cde0fcc commit cce5deb
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 1 deletion.
15 changes: 15 additions & 0 deletions cmd/nesutil/genie/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package genie

import (
"gabe565.com/gones/cmd/nesutil/genie/decode"
"github.com/spf13/cobra"
)

func New() *cobra.Command {
cmd := &cobra.Command{
Use: "genie",
Short: "Game Genie code utilities",
}
cmd.AddCommand(decode.New())
return cmd
}
111 changes: 111 additions & 0 deletions cmd/nesutil/genie/decode/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package decode

import (
"errors"
"fmt"
"strings"
"text/tabwriter"

"github.com/spf13/cobra"
)

func New() *cobra.Command {
cmd := &cobra.Command{
Use: "decode code...",
Short: "Decode a Game Genie code",
Args: cobra.MinimumNArgs(1),
RunE: run,
}
return cmd
}

func run(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true

w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 3, ' ', 0)
if _, err := fmt.Fprintln(w, "CODE\tCPU ADDRESS\tREPLACE VALUE\tCOMPARE VALUE\t"); err != nil {
return err
}

var errs []error
for _, c := range args {
result, err := decode(c)
if err != nil {
errs = append(errs, err)
continue
}

if _, err := fmt.Fprintf(w,
"%s\t0x%04X\t0x%02X\t%s\t\n",
result.Code, result.Address, result.Replace, result.compareString(),
); err != nil {
return err
}
}

if err := w.Flush(); err != nil {
return err
}

return errors.Join(errs...)
}

type decodeResult struct {
Code string
Address int
Replace int
Compare *int
}

var (
ErrInvalidCodeLen = errors.New("invalid length")
ErrInvalidCharacter = errors.New("invalid character")
)

func decode(code string) (decodeResult, error) {
code = strings.ToUpper(code)
result := decodeResult{Code: code}

switch len(code) {
case 6, 8:
default:
return result, fmt.Errorf("%w %d in code %q; expected 6 or 8 characters", ErrInvalidCodeLen, len(code), code)
}

// Convert letters into integers (0x0-0xF)
const lookup = "APZLGITYEOXUKSVN"
codeValues := make([]int, 0, len(code))
for _, r := range code {
index := strings.IndexRune(lookup, r)
if index == -1 {
return result, fmt.Errorf("%w %q in code %q", ErrInvalidCharacter, r, code)
}
codeValues = append(codeValues, index)
}

// 24/32 bits (16 for address, 8 for replacement, 0 or 8 for compare)
var bigint int
loPosOrder := []int{3, 5, 2, 4, 1, 0, 7, 6}
for _, loPos := range loPosOrder[:len(code)] {
hiPos := (loPos - 1 + len(code)) % len(code)
bigint = (bigint << 4) | (codeValues[hiPos] & 8) | (codeValues[loPos] & 7)
}

// Split integer and set MSB of address
if len(code) == 8 {
compValue := bigint & 0xFF
result.Compare = &compValue
bigint >>= 8
}

result.Address = (bigint >> 8) | 0x8000
result.Replace = bigint & 0xFF
return result, nil
}

func (d decodeResult) compareString() string {
if d.Compare == nil {
return "<none>"
}
return fmt.Sprintf("0x%02X", *d.Compare)
}
3 changes: 2 additions & 1 deletion cmd/nesutil/root/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package root

import (
"gabe565.com/gones/cmd/nesutil/chr"
"gabe565.com/gones/cmd/nesutil/genie"
"gabe565.com/gones/cmd/nesutil/ines"
"gabe565.com/gones/cmd/nesutil/ls"
"gabe565.com/gones/cmd/options"
Expand All @@ -16,7 +17,7 @@ func New(opts ...options.Option) *cobra.Command {
SilenceErrors: true,
DisableAutoGenTag: true,
}
cmd.AddCommand(ls.New(), ines.New(), chr.New())
cmd.AddCommand(ls.New(), ines.New(), chr.New(), genie.New())

for _, opt := range opts {
opt(cmd)
Expand Down
1 change: 1 addition & 0 deletions docs/nesutil.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ GoNES command-line utilities
### SEE ALSO

* [nesutil chr](nesutil_chr.md) - CHR graphics data utilities
* [nesutil genie](nesutil_genie.md) - Game Genie code utilities
* [nesutil ines](nesutil_ines.md) - INES ROM utilities
* [nesutil ls](nesutil_ls.md) - List ROM files and metadata

15 changes: 15 additions & 0 deletions docs/nesutil_genie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## nesutil genie

Game Genie code utilities

### Options

```
-h, --help help for genie
```

### SEE ALSO

* [nesutil](nesutil.md) - GoNES command-line utilities
* [nesutil genie decode](nesutil_genie_decode.md) - Decode a Game Genie code

18 changes: 18 additions & 0 deletions docs/nesutil_genie_decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## nesutil genie decode

Decode a Game Genie code

```
nesutil genie decode code... [flags]
```

### Options

```
-h, --help help for decode
```

### SEE ALSO

* [nesutil genie](nesutil_genie.md) - Game Genie code utilities

0 comments on commit cce5deb

Please sign in to comment.