diff --git a/Taskfile.dist.yml b/Taskfile.dist.yml index 8e8cdd94..393e4eb8 100644 --- a/Taskfile.dist.yml +++ b/Taskfile.dist.yml @@ -11,7 +11,7 @@ dotenv: [".env"] tasks: build: cmds: - - go build --ldflags "-linkmode external -extldflags -static -s -w" -o _build/${PLATFORM}_{{ARCH}} ./cmd/$PLATFORM + - go build --ldflags "-linkmode external -extldflags -static -s -w" -o _build/${PLATFORM}_{{ARCH}}/${TAPTO_BIN} ./cmd/$PLATFORM build-image-mister: vars: @@ -20,6 +20,13 @@ tasks: cmds: - docker build --platform linux/arm/v7 -t {{.IMAGE_NAME}} {{.DOCKERFILE}} + build-image-mistex: + vars: + IMAGE_NAME: tapto/mistex-build + DOCKERFILE: "{{.PWD}}/scripts/linux_arm64/build" + cmds: + - docker build --platform linux/arm/v8 -t {{.IMAGE_NAME}} {{.DOCKERFILE}} + build-mister: vars: BUILD_DIR: "{{.PWD}}/_build/mister_arm" @@ -34,6 +41,20 @@ tasks: - rm -f {{.BUILD_DIR}}/tapto-mister.zip - zip {{.BUILD_DIR}}/tapto-mister.zip {{.BUILD_DIR}}/tapto.sh {{.BUILD_DIR}}/taptui.sh + build-mistex: + vars: + BUILD_DIR: "{{.PWD}}/_build/mistex_arm64" + BUILDCACHE: "{{.BUILD_DIR}}/.go-buildcache" + MODCACHE: "{{.BUILD_DIR}}/.go-modcache" + IMAGE_NAME: tapto/mistex-build + IMG_BUILDCACHE: /home/build/.cache/go-build + IMG_MODCACHE: /home/build/go/pkg/mod + cmds: + - docker run --rm --platform linux/arm/v8 -v {{.BUILDCACHE}}:{{.IMG_BUILDCACHE}} -v {{.MODCACHE}}:{{.IMG_MODCACHE}} -v {{.PWD}}:/build --user 1000:1000 {{.IMAGE_NAME}} bash -c "PLATFORM=mistex TAPTO_BIN=tapto.sh task build" + - cp {{.PWD}}/scripts/taptui/taptui.sh {{.BUILD_DIR}} + - rm -f {{.BUILD_DIR}}/tapto-mistex.zip + - zip {{.BUILD_DIR}}/tapto-mistex.zip {{.BUILD_DIR}}/tapto.sh {{.BUILD_DIR}}/taptui.sh + deploy-mister: cmds: - task: build-mister @@ -41,6 +62,13 @@ tasks: - scp _build/mister_arm/tapto.sh root@${MISTER_IP}:/media/fat/Scripts/tapto.sh - ssh root@${MISTER_IP} /media/fat/Scripts/tapto.sh -service restart + deploy-mistex: + cmds: + - task: build-mistex + - scp _build/mistex_arm64/taptui.sh root@${MISTEX_IP}:/media/fat/Scripts/taptui.sh + - scp _build/mistex_arm64/tapto.sh root@${MISTEX_IP}:/media/fat/Scripts/tapto.sh + - ssh root@${MISTEX_IP} /media/fat/Scripts/tapto.sh -service restart + clean: rm -rf _build test: go test ./... diff --git a/cmd/mister/main.go b/cmd/mister/main.go index a9919a38..98979d73 100755 --- a/cmd/mister/main.go +++ b/cmd/mister/main.go @@ -137,7 +137,7 @@ func main() { flag.Parse() if *versionOpt { - fmt.Println(appVersion) + fmt.Println("TapTo v" + appVersion + " (mister)") os.Exit(0) } @@ -158,17 +158,6 @@ func main() { os.Exit(1) } - log.Info().Msgf("TapTo v%s", appVersion) - log.Info().Msgf("config path = %s", cfg.IniPath) - log.Info().Msgf("app path = %s", cfg.AppPath) - log.Info().Msgf("connection_string = %s", cfg.GetConnectionString()) - log.Info().Msgf("allow_commands = %t", cfg.GetAllowCommands()) - log.Info().Msgf("disable_sounds = %t", cfg.GetDisableSounds()) - log.Info().Msgf("probe_device = %t", cfg.GetProbeDevice()) - log.Info().Msgf("exit_game = %t", cfg.GetExitGame()) - log.Info().Msgf("exit_game_blocklist = %s", cfg.GetExitGameBlocklist()) - log.Info().Msgf("debug = %t", cfg.GetDebug()) - if cfg.GetDebug() { zerolog.SetGlobalLevel(zerolog.DebugLevel) } else { diff --git a/cmd/mistex/main.go b/cmd/mistex/main.go new file mode 100755 index 00000000..28c63f4f --- /dev/null +++ b/cmd/mistex/main.go @@ -0,0 +1,245 @@ +/* +TapTo +Copyright (C) 2023 Gareth Jones +Copyright (C) 2023, 2024 Callan Barrett + +This file is part of TapTo. + +TapTo is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +TapTo is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with TapTo. If not, see . +*/ + +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "net/url" + "os" + "os/exec" + "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + + "github.com/wizzomafizzo/tapto/pkg/daemon/api" + "github.com/wizzomafizzo/tapto/pkg/platforms/mister" + "github.com/wizzomafizzo/tapto/pkg/platforms/mistex" + "github.com/wizzomafizzo/tapto/pkg/utils" + + "github.com/wizzomafizzo/tapto/pkg/config" + "github.com/wizzomafizzo/tapto/pkg/daemon" +) + +const appName = "tapto" + +func tryAddToStartup() (bool, error) { + unitPath := "/etc/systemd/system/tapto.service" + unitFile := `[Unit] +Description=TapTo service + +[Service] +Type=forking +Restart=no +ExecStart=/media/fat/Scripts/tapto.sh -service start + +[Install] +WantedBy=multi-user.target +` + + _, err := os.Stat(unitPath) + if err == nil { + return false, nil + } + + err = os.WriteFile(unitPath, []byte(unitFile), 0644) + if err != nil { + return false, err + } + + cmd := exec.Command("systemctl", "daemon-reload") + err = cmd.Run() + if err != nil { + return false, err + } + + cmd = exec.Command("systemctl", "enable", "tapto.service") + err = cmd.Run() + if err != nil { + return false, err + } + + return true, nil +} + +func handleWriteCommand(textToWrite string, svc *mister.Service, cfg *config.UserConfig) { + log.Info().Msgf("writing text to tag: %s", textToWrite) + + if !svc.Running() { + _, _ = fmt.Fprintln(os.Stderr, "TapTo service is not running, please start it before writing.") + log.Error().Msg("TapTo service is not running, exiting") + os.Exit(1) + } + + body, err := json.Marshal(api.ReaderWriteRequest{Text: textToWrite}) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Error encoding request:", err) + log.Error().Msgf("error encoding request: %s", err) + os.Exit(1) + } + + resp, err := http.Post( + // TODO: don't hardcode port + "http://127.0.0.1:7497/api/v1/readers/0/write", + "application/json", + bytes.NewBuffer(body), + ) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Error sending request:", err) + log.Error().Msgf("error sending request: %s", err) + os.Exit(1) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + errBody, err := io.ReadAll(resp.Body) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Error reading response:", err) + log.Error().Msgf("error reading response: %s", err) + os.Exit(1) + } + + _, _ = fmt.Fprintf(os.Stderr, "Error writing to tag: %s\n", strings.TrimSpace(string(errBody))) + log.Error().Msgf("error writing to tag: %s", strings.TrimSpace(string(errBody))) + os.Exit(1) + } + + _, _ = fmt.Fprintln(os.Stderr, "Successfully wrote to tag.") + log.Info().Msg("successfully wrote to tag") + os.Exit(0) +} + +func handleLaunchCommand(tokenToLaunch string, svc *mister.Service, cfg *config.UserConfig) { + log.Info().Msgf("launching token: %s", tokenToLaunch) + + if !svc.Running() { + _, _ = fmt.Fprintln(os.Stderr, "TapTo service is not running, please start it before launching.") + log.Error().Msg("TapTo service is not running, exiting") + os.Exit(1) + } + + resp, err := http.Get("http://127.0.0.1:7497/api/v1/launch/" + url.QueryEscape(tokenToLaunch)) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Error sending request:", err) + log.Error().Msgf("error sending request: %s", err) + os.Exit(1) + } + defer resp.Body.Close() + + _, _ = fmt.Fprintln(os.Stderr, "Successfully launched token.") + log.Info().Msg("successfully launched token") + os.Exit(0) +} + +func main() { + svcOpt := flag.String("service", "", "manage TapTo service (start, stop, restart, status)") + writeOpt := flag.String("write", "", "write text to tag") + launchOpt := flag.String("launch", "", "execute text as if it were a token") + versionOpt := flag.Bool("version", false, "print version and exit") + flag.Parse() + + if *versionOpt { + fmt.Println("TapTo v" + config.Version + " (mistex)") + os.Exit(0) + } + + err := utils.InitLogging() + if err != nil { + fmt.Println("Error initializing logging:", err) + os.Exit(1) + } + + cfg, err := config.NewUserConfig(appName, &config.UserConfig{ + TapTo: config.TapToConfig{ + ProbeDevice: true, + }, + }) + if err != nil { + log.Error().Msgf("error loading user config: %s", err) + fmt.Println("Error loading config:", err) + os.Exit(1) + } + + if cfg.GetDebug() { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } else { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } + + svc, err := mister.NewService(mister.ServiceArgs{ + Name: appName, + Entry: func() (func() error, error) { + return daemon.StartDaemon(&mistex.Platform{}, cfg) + }, + }) + if err != nil { + log.Error().Msgf("error creating service: %s", err) + fmt.Println("Error creating service:", err) + os.Exit(1) + } + + if *writeOpt != "" { + handleWriteCommand(*writeOpt, svc, cfg) + } else if *launchOpt != "" { + handleLaunchCommand(*launchOpt, svc, cfg) + } + + svc.ServiceHandler(svcOpt) + + fmt.Println("TapTo v" + config.Version) + + added, err := tryAddToStartup() + if err != nil { + log.Error().Msgf("error adding to startup: %s", err) + fmt.Println("Error adding to startup:", err) + os.Exit(1) + } else if added { + log.Info().Msg("added to startup") + fmt.Println("Added TapTo to MiSTeX startup.") + } + + if !svc.Running() { + err := svc.Start() + fmt.Println("TapTo service not running, starting...") + if err != nil { + log.Error().Msgf("error starting service: %s", err) + fmt.Println("Error starting TapTo service:", err) + } else { + log.Info().Msg("service started manually") + fmt.Println("TapTo service started.") + } + } else { + fmt.Println("TapTo service is running.") + } + + ip, err := utils.GetLocalIp() + if err != nil { + fmt.Println("Device address: Unknown") + } else { + fmt.Println("Device address:", ip.String()) + } +} diff --git a/go.mod b/go.mod index 2b51d951..3ca11ac0 100755 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/txn2/txeh v1.4.0 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) @@ -27,7 +27,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/stretchr/testify v1.9.0 // indirect - golang.org/x/term v0.21.0 // indirect + golang.org/x/term v0.22.0 // indirect ) require ( @@ -39,7 +39,7 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/hsanjuan/go-ndef v0.0.1 github.com/rs/zerolog v1.31.0 - github.com/wizzomafizzo/mrext v0.0.0-20240702040552-4b6236c5c8f0 + github.com/wizzomafizzo/mrext v0.0.0-20240705120538-5efdc40f9c08 go.etcd.io/bbolt v1.3.9 golang.org/x/sync v0.7.0 ) diff --git a/go.sum b/go.sum index d1b8472b..47f7e8eb 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/txn2/txeh v1.4.0 h1:0tdvpA4HGJrj8X3kmrU6o/JFStI009nKxwDpMK5CnRU= github.com/txn2/txeh v1.4.0/go.mod h1:Mgq0hY184zCrDBLgvkIp+9NYGHoYbJcu4xKqUcx1shc= -github.com/wizzomafizzo/mrext v0.0.0-20240702040552-4b6236c5c8f0 h1:6/sixD2xPLQ4mWufWV3WMdpvcGvVQ+Uwv3RwN1k63Pg= -github.com/wizzomafizzo/mrext v0.0.0-20240702040552-4b6236c5c8f0/go.mod h1:pWjoPIzJIXlDfEmdf++eUqsZKrEsYVkOHy39s/H7WLA= +github.com/wizzomafizzo/mrext v0.0.0-20240705120538-5efdc40f9c08 h1:IfCVEDGCzNY4Lx+1tUUkTOLEv4yxXh9Jpsj6T+8YWr4= +github.com/wizzomafizzo/mrext v0.0.0-20240705120538-5efdc40f9c08/go.mod h1:pWjoPIzJIXlDfEmdf++eUqsZKrEsYVkOHy39s/H7WLA= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= @@ -67,11 +67,11 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 941569f3..1ac67309 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -271,6 +271,17 @@ func StartDaemon( tq := tokens.NewTokenQueue() lsq := make(chan *tokens.Token) + log.Info().Msgf("TapTo v%s", config.Version) + log.Info().Msgf("config path = %s", cfg.IniPath) + log.Info().Msgf("app path = %s", cfg.AppPath) + log.Info().Msgf("connection_string = %s", cfg.GetConnectionString()) + log.Info().Msgf("allow_commands = %t", cfg.GetAllowCommands()) + log.Info().Msgf("disable_sounds = %t", cfg.GetDisableSounds()) + log.Info().Msgf("probe_device = %t", cfg.GetProbeDevice()) + log.Info().Msgf("exit_game = %t", cfg.GetExitGame()) + log.Info().Msgf("exit_game_blocklist = %s", cfg.GetExitGameBlocklist()) + log.Info().Msgf("debug = %t", cfg.GetDebug()) + db, err := database.Open() if err != nil { log.Error().Err(err).Msgf("error opening database") diff --git a/pkg/platforms/mister/commands.go b/pkg/platforms/mister/commands.go index cfed7c7a..5e7a29d6 100644 --- a/pkg/platforms/mister/commands.go +++ b/pkg/platforms/mister/commands.go @@ -12,16 +12,16 @@ import ( "github.com/wizzomafizzo/tapto/pkg/platforms" ) -var commandsMappings = map[string]func(Platform, platforms.CmdEnv) error{ - "mister.ini": cmdIni, - "mister.core": cmdLaunchCore, +var commandsMappings = map[string]func(platforms.Platform, platforms.CmdEnv) error{ + "mister.ini": CmdIni, + "mister.core": CmdLaunchCore, // "mister.script": cmdMisterScript, - "mister.mgl": cmdMisterMgl, + "mister.mgl": CmdMisterMgl, - "ini": cmdIni, // DEPRECATED + "ini": CmdIni, // DEPRECATED } -func cmdIni(pl Platform, env platforms.CmdEnv) error { +func CmdIni(pl platforms.Platform, env platforms.CmdEnv) error { inis, err := mister.GetAllMisterIni() if err != nil { return err @@ -54,11 +54,11 @@ func cmdIni(pl Platform, env platforms.CmdEnv) error { return nil } -func cmdLaunchCore(pl Platform, env platforms.CmdEnv) error { +func CmdLaunchCore(pl platforms.Platform, env platforms.CmdEnv) error { return mister.LaunchShortCore(env.Args) } -func cmdMisterScript(pl Platform, env platforms.CmdEnv) error { +func cmdMisterScript(pl platforms.Platform, env platforms.CmdEnv) error { // TODO: escaping arguments // TODO: does this work if game is running? @@ -91,10 +91,12 @@ func cmdMisterScript(pl Platform, env platforms.CmdEnv) error { script = fmt.Sprintf("%s %s", script, scriptArgs) } - return mister.RunScript(pl.kbd, script) + // TODO: implement without specific reference to uinput.Keyboard + // return mister.RunScript(pl.kbd, script) + return nil } -func cmdMisterMgl(pl Platform, env platforms.CmdEnv) error { +func CmdMisterMgl(pl platforms.Platform, env platforms.CmdEnv) error { if env.Args == "" { return fmt.Errorf("no mgl specified") } diff --git a/pkg/platforms/mister/platform.go b/pkg/platforms/mister/platform.go index 4617879a..e4206d07 100644 --- a/pkg/platforms/mister/platform.go +++ b/pkg/platforms/mister/platform.go @@ -182,7 +182,7 @@ func (p *Platform) KeyboardInput(input string) error { func (p *Platform) ForwardCmd(env platforms.CmdEnv) error { if f, ok := commandsMappings[env.Cmd]; ok { - return f(*p, env) + return f(p, env) } else { return fmt.Errorf("command not supported on mister: %s", env.Cmd) } diff --git a/pkg/platforms/mister/tracker.go b/pkg/platforms/mister/tracker.go index 31700803..abc0a44e 100644 --- a/pkg/platforms/mister/tracker.go +++ b/pkg/platforms/mister/tracker.go @@ -384,27 +384,56 @@ func StartFileWatch(tr *Tracker) (*fsnotify.Watcher, error) { } }() + if _, err := os.Stat(config.CoreNameFile); os.IsNotExist(err) { + err := os.WriteFile(config.CoreNameFile, []byte(""), 0644) + if err != nil { + return nil, err + } + log.Info().Msgf("created core name file: %s", config.CoreNameFile) + } + err = watcher.Add(config.CoreNameFile) if err != nil { return nil, err } + if _, err := os.Stat(config.CoreConfigFolder); os.IsNotExist(err) { + err := os.MkdirAll(config.CoreConfigFolder, 0755) + if err != nil { + return nil, err + } + log.Info().Msgf("created core config folder: %s", config.CoreConfigFolder) + } + err = watcher.Add(config.CoreConfigFolder) if err != nil { return nil, err } + if _, err := os.Stat(config.ActiveGameFile); os.IsNotExist(err) { + err := os.WriteFile(config.ActiveGameFile, []byte(""), 0644) + if err != nil { + return nil, err + } + log.Info().Msgf("created active game file: %s", config.ActiveGameFile) + } + err = watcher.Add(config.ActiveGameFile) if err != nil { return nil, err } - _, fileExistsError := os.Stat(config.CurrentPathFile) - if fileExistsError == nil { - err = watcher.Add(config.CurrentPathFile) + if _, err := os.Stat(config.CurrentPathFile); os.IsNotExist(err) { + err := os.WriteFile(config.CurrentPathFile, []byte(""), 0644) if err != nil { return nil, err } + log.Info().Msgf("created current path file: %s", config.CurrentPathFile) + } + + err = watcher.Add(config.CurrentPathFile) + if err != nil { + return nil, err } return watcher, nil diff --git a/pkg/platforms/mistex/commands.go b/pkg/platforms/mistex/commands.go new file mode 100644 index 00000000..8d05167e --- /dev/null +++ b/pkg/platforms/mistex/commands.go @@ -0,0 +1,15 @@ +package mistex + +import ( + "github.com/wizzomafizzo/tapto/pkg/platforms" + "github.com/wizzomafizzo/tapto/pkg/platforms/mister" +) + +var commandsMappings = map[string]func(platforms.Platform, platforms.CmdEnv) error{ + "mister.ini": mister.CmdIni, + "mister.core": mister.CmdLaunchCore, + // "mister.script": cmdMisterScript, + "mister.mgl": mister.CmdMisterMgl, + + "ini": mister.CmdIni, // DEPRECATED +} diff --git a/pkg/platforms/mistex/platform.go b/pkg/platforms/mistex/platform.go new file mode 100644 index 00000000..a8d0c62d --- /dev/null +++ b/pkg/platforms/mistex/platform.go @@ -0,0 +1,207 @@ +package mistex + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + + mrextConfig "github.com/wizzomafizzo/mrext/pkg/config" + "github.com/wizzomafizzo/mrext/pkg/games" + "github.com/wizzomafizzo/mrext/pkg/input" + mm "github.com/wizzomafizzo/mrext/pkg/mister" + "github.com/wizzomafizzo/tapto/pkg/config" + "github.com/wizzomafizzo/tapto/pkg/platforms" + "github.com/wizzomafizzo/tapto/pkg/platforms/mister" + "github.com/wizzomafizzo/tapto/pkg/tokens" +) + +type Platform struct { + kbd input.Keyboard + tr *mister.Tracker + stopTr func() error +} + +func (p *Platform) Id() string { + return "mistex" +} + +func (p *Platform) Setup(cfg *config.UserConfig) error { + kbd, err := input.NewKeyboard() + if err != nil { + return err + } + + p.kbd = kbd + + tr, stopTr, err := mister.StartTracker(*mister.UserConfigToMrext(cfg)) + if err != nil { + return err + } + + p.tr = tr + p.stopTr = stopTr + + err = mister.Setup(p.tr) + if err != nil { + return err + } + + return nil +} + +func (p *Platform) Stop() error { + if p.stopTr != nil { + return p.stopTr() + } + + return nil +} + +func (p *Platform) AfterScanHook(token tokens.Token) error { + f, err := os.Create(mister.TokenReadFile) + if err != nil { + return fmt.Errorf("unable to create scan result file %s: %s", mister.TokenReadFile, err) + } + defer func(f *os.File) { + _ = f.Close() + }(f) + + _, err = f.WriteString(fmt.Sprintf("%s,%s", token.UID, token.Text)) + if err != nil { + return fmt.Errorf("unable to write scan result file %s: %s", mister.TokenReadFile, err) + } + + return nil +} + +func (p *Platform) RootFolders(cfg *config.UserConfig) []string { + return games.GetGamesFolders(mister.UserConfigToMrext(cfg)) +} + +func (p *Platform) ZipsAsFolders() bool { + return true +} + +func (p *Platform) ConfigFolder() string { + return mister.ConfigFolder +} + +func (p *Platform) NormalizePath(cfg *config.UserConfig, path string) string { + return mister.NormalizePath(cfg, path) +} + +func LaunchMenu() error { + if _, err := os.Stat(mrextConfig.CmdInterface); err != nil { + return fmt.Errorf("command interface not accessible: %s", err) + } + + cmd, err := os.OpenFile(mrextConfig.CmdInterface, os.O_RDWR, 0) + if err != nil { + return err + } + defer cmd.Close() + + // TODO: hardcoded for xilinx variant, should read pref from mister.ini + cmd.WriteString(fmt.Sprintf("load_core %s\n", filepath.Join(mrextConfig.SdFolder, "menu.bit"))) + + return nil +} + +func (p *Platform) KillLauncher() error { + return LaunchMenu() +} + +func (p *Platform) LaunchingEnabled() bool { + _, err := os.Stat(mister.DisableLaunchFile) + return err != nil +} + +func (p *Platform) SetLaunching(disabled bool) error { + if disabled { + return os.Remove(mister.DisableLaunchFile) + } else { + _, err := os.Create(mister.DisableLaunchFile) + return err + } +} + +func (p *Platform) GetActiveLauncher() string { + core := mister.GetActiveCoreName() + + if core == mrextConfig.MenuCore { + return "" + } + + return core +} + +func (p *Platform) PlayFailSound(cfg *config.UserConfig) { + mister.PlayFail(cfg) +} + +func (p *Platform) PlaySuccessSound(cfg *config.UserConfig) { + mister.PlaySuccess(cfg) +} + +func (p *Platform) ActiveSystem() string { + return p.tr.ActiveSystem +} + +func (p *Platform) ActiveGame() string { + return p.tr.ActiveGameId +} + +func (p *Platform) ActiveGameName() string { + return p.tr.ActiveGameName +} + +func (p *Platform) ActiveGamePath() string { + return p.tr.ActiveGamePath +} + +func (p *Platform) SetEventHook(f *func()) { + p.tr.SetEventHook(f) +} + +func (p *Platform) LaunchSystem(cfg *config.UserConfig, id string) error { + system, err := games.LookupSystem(id) + if err != nil { + return err + } + + return mm.LaunchCore(mister.UserConfigToMrext(cfg), *system) +} + +func (p *Platform) LaunchFile(cfg *config.UserConfig, path string) error { + return mm.LaunchGenericFile(mister.UserConfigToMrext(cfg), path) +} + +func (p *Platform) Shell(cmd string) error { + command := exec.Command("bash", "-c", cmd) + err := command.Start() + if err != nil { + return err + } + return nil +} + +func (p *Platform) KeyboardInput(input string) error { + code, err := strconv.Atoi(input) + if err != nil { + return err + } + + p.kbd.Press(code) + + return nil +} + +func (p *Platform) ForwardCmd(env platforms.CmdEnv) error { + if f, ok := commandsMappings[env.Cmd]; ok { + return f(p, env) + } else { + return fmt.Errorf("command not supported on mister: %s", env.Cmd) + } +} diff --git a/scripts/linux_arm64/build/Dockerfile b/scripts/linux_arm64/build/Dockerfile new file mode 100755 index 00000000..15a77f15 --- /dev/null +++ b/scripts/linux_arm64/build/Dockerfile @@ -0,0 +1,35 @@ +FROM arm64v8/debian:bookworm + +RUN apt-get update -y && apt-get upgrade -y + +# make go mod download work +RUN apt-get install -y ca-certificates openssl && \ + openssl s_client -showcerts -connect github.com:443 /dev/null|openssl x509 -outform PEM > /usr/local/share/ca-certificates/github.crt && \ + openssl s_client -showcerts -connect proxy.golang.org:443 /dev/null|openssl x509 -outform PEM > /usr/local/share/ca-certificates/proxy.golang.crt && \ + update-ca-certificates + +# install go and app dependencies +RUN apt-get install build-essential git curl wget ncurses-dev -y && \ + apt-get install golang-doc golang-go golang-src golang -y + +# install libnfc dependencies +RUN apt-get install -y libusb-dev libtool autoconf automake +# install custom version of libnfc +RUN mkdir /internal && cd /internal && \ + git clone --depth 1 https://github.com/sam1902/libnfc && \ + cd libnfc && \ + autoreconf -vis && \ + ./configure && \ + make -j "$(nproc)" && \ + make install + +# install task +RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin + +# drop permissions on output files +RUN useradd -m -u 1000 build +USER build + +WORKDIR /build + +RUN git config --global --add safe.directory /build