You've already forked Microtransactions64-server
mirror of
https://github.com/Print-and-Panic/Microtransactions64-server.git
synced 2026-01-21 10:17:31 -08:00
init: intial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vscode/tasks.json
|
||||
125
devices/N64.go
Normal file
125
devices/N64.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
const (
|
||||
MAX_DATA_SIZE = 512
|
||||
)
|
||||
|
||||
type N64 interface {
|
||||
Start() error
|
||||
Close() error
|
||||
SendData(data []byte) error
|
||||
ReceiveData() ([]byte, error)
|
||||
}
|
||||
|
||||
type EverDrive struct {
|
||||
PortName string
|
||||
port serial.Port
|
||||
running bool
|
||||
}
|
||||
|
||||
func (e *EverDrive) Start() error {
|
||||
mode := &serial.Mode{
|
||||
BaudRate: 9600,
|
||||
DataBits: 8,
|
||||
Parity: serial.NoParity, // Changed from EvenParity
|
||||
StopBits: serial.OneStopBit, // Changed from TwoStopBits
|
||||
}
|
||||
port, err := serial.Open(e.PortName, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.port = port
|
||||
e.running = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EverDrive) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EverDrive) SendData(data []byte) error {
|
||||
if !e.running {
|
||||
return fmt.Errorf("N64 is not running")
|
||||
}
|
||||
|
||||
// --- 1. CONSTRUCT THE UNFLOADER PACKET ---
|
||||
|
||||
// Header (4 Bytes)
|
||||
packet := []byte{'D', 'M', 'A', '@'}
|
||||
|
||||
// Data Type (1 Byte)
|
||||
// using 0x01 (DataTypeText) is fine, or define your own.
|
||||
packet = append(packet, 0x01)
|
||||
|
||||
// Size (3 Bytes, Big Endian)
|
||||
// This tells usb.c how many REAL bytes of payload to expect.
|
||||
lengthBuf := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(lengthBuf, uint32(len(data)))
|
||||
packet = append(packet, lengthBuf[1:]...) // Skip first byte, append last 3
|
||||
|
||||
// Payload (The Coin)
|
||||
packet = append(packet, data...)
|
||||
|
||||
// PAYLOAD ALIGNMENT (Crucial per usb.c line 640)
|
||||
// len = ALIGN(usb_datasize, 2);
|
||||
// If we send 1 byte (odd), usb.c will read 2 bytes.
|
||||
// We must provide that padding byte or it will eat the 'C' of the footer.
|
||||
if len(data)%2 != 0 {
|
||||
packet = append(packet, 0x00)
|
||||
}
|
||||
|
||||
// Footer (4 Bytes)
|
||||
packet = append(packet, 'C', 'M', 'P', 'H')
|
||||
|
||||
// --- 2. HARDWARE PADDING (The Fix) ---
|
||||
// The EverDrive needs a 512-byte transfer to wake up.
|
||||
// We pad the rest of the buffer with zeros.
|
||||
// The N64 will read the packet above, then fail 'DMA@' checks
|
||||
// on the zeros until the buffer is empty. This is expected behavior.
|
||||
|
||||
blockSize := 512
|
||||
totalLen := len(packet)
|
||||
|
||||
if totalLen < blockSize {
|
||||
padding := make([]byte, blockSize-totalLen)
|
||||
packet = append(packet, padding...)
|
||||
}
|
||||
|
||||
// Write exactly 512 bytes
|
||||
_, err := e.port.Write(packet)
|
||||
if err != nil {
|
||||
log.Printf("❌ N64 Write Error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("📤 Sent Packet (Payload: %v | Total Wire: %d)", data, len(packet))
|
||||
return nil
|
||||
}
|
||||
func (e *EverDrive) ReceiveData() ([]byte, error) {
|
||||
if !e.running {
|
||||
return nil, fmt.Errorf("N64 is not running")
|
||||
}
|
||||
|
||||
data := make([]byte, MAX_DATA_SIZE)
|
||||
_, err := e.port.Read(data)
|
||||
if err != nil {
|
||||
log.Printf("❌ N64 Read Error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("📥 Received %s from N64", data)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func NewEverDrive(portName string) *EverDrive {
|
||||
return &EverDrive{PortName: portName}
|
||||
}
|
||||
96
devices/coin_acceptor.go
Normal file
96
devices/coin_acceptor.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package devices
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
type Coin int
|
||||
|
||||
func (c Coin) Value() int {
|
||||
return int(c)
|
||||
}
|
||||
|
||||
func (c Coin) String() string {
|
||||
switch c {
|
||||
case Quarter:
|
||||
return "Quarter (25c)"
|
||||
case Dime:
|
||||
return "Dime (10c)"
|
||||
case Nickel:
|
||||
return "Nickel (5c)"
|
||||
case Penny:
|
||||
return "Penny (1c)"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown Coin (%d)", c)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
Quarter Coin = 25
|
||||
Dime Coin = 10
|
||||
Nickel Coin = 5
|
||||
Penny Coin = 1
|
||||
)
|
||||
|
||||
type CoinAcceptor interface {
|
||||
Start() error
|
||||
Close() error
|
||||
Coins() <-chan Coin
|
||||
}
|
||||
|
||||
type DG600F struct {
|
||||
PortName string
|
||||
BaudRate int
|
||||
coins chan Coin
|
||||
port serial.Port
|
||||
}
|
||||
|
||||
func (c *DG600F) Start() error {
|
||||
mode := &serial.Mode{
|
||||
BaudRate: c.BaudRate,
|
||||
DataBits: 8,
|
||||
Parity: serial.EvenParity,
|
||||
StopBits: serial.TwoStopBits,
|
||||
}
|
||||
|
||||
port, err := serial.Open(c.PortName, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.port = port
|
||||
c.coins = make(chan Coin, 10)
|
||||
log.Println("Coin acceptor started")
|
||||
|
||||
go c.listen()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DG600F) listen() {
|
||||
for {
|
||||
data := make([]byte, 1)
|
||||
_, err := c.port.Read(data)
|
||||
if err != nil {
|
||||
log.Println("Error reading from port:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if data[0] == 0x00 {
|
||||
continue
|
||||
}
|
||||
|
||||
c.coins <- Coin(data[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DG600F) Close() error {
|
||||
return c.port.Close()
|
||||
}
|
||||
|
||||
func (c *DG600F) Coins() <-chan Coin {
|
||||
return c.coins
|
||||
}
|
||||
27
go.mod
Normal file
27
go.mod
Normal file
@@ -0,0 +1,27 @@
|
||||
module github.com/PrintAndPanic/microtransactions64-server
|
||||
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/creack/goselect v0.1.2 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.bug.st/serial v1.6.4 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
)
|
||||
45
go.sum
Normal file
45
go.sum
Normal file
@@ -0,0 +1,45 @@
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
38
main.go
Normal file
38
main.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/PrintAndPanic/microtransactions64-server/devices"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// "n64" defaults to /dev/ttyUSB0 (The Everdrive)
|
||||
n64Port := flag.String("n64", "/dev/ttyUSB0", "Serial port for the N64 Everdrive")
|
||||
|
||||
// 2. Parse Flags (Critical: Must be called before accessing the variables)
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("🚀 STARTING: N64 on %s", *n64Port)
|
||||
// --- CONFIGURATION ---
|
||||
coinPortName := "/dev/serial0"
|
||||
coinPortBaud := 9600
|
||||
// ---------------------
|
||||
|
||||
// 1. Configure the Serial Port
|
||||
coinAcceptor := devices.DG600F{PortName: coinPortName, BaudRate: coinPortBaud}
|
||||
coinAcceptor.Start()
|
||||
defer coinAcceptor.Close()
|
||||
|
||||
n64 := devices.NewEverDrive(*n64Port)
|
||||
n64.Start()
|
||||
defer n64.Close()
|
||||
|
||||
// // 2. Listen for coins
|
||||
for coin := range coinAcceptor.Coins() {
|
||||
log.Println("Coin accepted:", coin)
|
||||
n64.SendData([]byte{byte(coin)})
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user