├── .github └── workflows │ ├── build.yml │ ├── main.yml │ └── mister_repo.yml ├── .gitignore ├── LICENSE ├── README.md ├── Taskfile.dist.yml ├── cmd ├── batocera │ ├── main.go │ └── scripts │ │ ├── configs │ │ ├── emulationstation │ │ │ └── scripts │ │ │ │ └── game-selected │ │ │ │ └── zaparoo_game_select.sh │ │ └── multimedia_keys.conf │ │ ├── services │ │ └── zaparoo_service │ │ └── zaparoo_write_game.sh ├── bazzite │ └── main.go ├── chimeraos │ └── main.go ├── libreelec │ └── main.go ├── linux │ ├── icon.png │ └── main.go ├── mac │ ├── app │ │ ├── Zaparoo Core.app │ │ │ └── Contents │ │ │ │ ├── Info.plist │ │ │ │ ├── MacOS │ │ │ │ └── app.sh │ │ │ │ └── Resources │ │ │ │ └── icon.icns │ │ └── systrayicon.png │ └── main.go ├── mister │ ├── gui.go │ └── main.go ├── mistex │ └── main.go ├── recalbox │ └── main.go ├── retropie │ └── main.go ├── steamos │ ├── conf │ │ ├── 60-zaparoo.rules │ │ ├── blacklist-zaparoo.conf │ │ └── zaparoo.service │ ├── install.go │ └── main.go ├── testscanner │ ├── launchers.go │ ├── main.go │ └── platform.go └── windows │ ├── main.go │ ├── setup.iss.tmpl │ └── winres │ ├── icon.ico │ ├── icon.png │ ├── icon16.png │ └── winres.json.tmpl ├── docs ├── index.md └── scan-behavior.md ├── go.mod ├── go.sum ├── pkg ├── api │ ├── client │ │ └── client.go │ ├── methods │ │ ├── history.go │ │ ├── mappings.go │ │ ├── media.go │ │ ├── readers.go │ │ ├── run.go │ │ ├── settings.go │ │ ├── systems.go │ │ └── utils.go │ ├── models │ │ ├── models.go │ │ ├── params.go │ │ ├── requests │ │ │ └── requests.go │ │ └── responses.go │ ├── notifications │ │ └── notifications.go │ └── server.go ├── assets │ ├── _app │ │ └── README │ ├── assets.go │ ├── sounds │ │ ├── fail.wav │ │ └── success.wav │ └── systems │ │ ├── 3DO.json │ │ ├── 3DS.json │ │ ├── AcornAtom.json │ │ ├── AcornElectron.json │ │ ├── AdventureVision.json │ │ ├── AliceMC10.json │ │ ├── Amiga.json │ │ ├── Amiga1200.json │ │ ├── Amiga500.json │ │ ├── AmigaCD32.json │ │ ├── Amstrad.json │ │ ├── AmstradPCW.json │ │ ├── Android.json │ │ ├── Apogee.json │ │ ├── AppleI.json │ │ ├── AppleII.json │ │ ├── Aquarius.json │ │ ├── Arcade.json │ │ ├── Arcadia.json │ │ ├── Arduboy.json │ │ ├── Astrocade.json │ │ ├── Atari2600.json │ │ ├── Atari5200.json │ │ ├── Atari7800.json │ │ ├── Atari800.json │ │ ├── AtariLynx.json │ │ ├── AtariXEGS.json │ │ ├── Atomiswave.json │ │ ├── BBCMicro.json │ │ ├── BK0011M.json │ │ ├── C16.json │ │ ├── C64.json │ │ ├── CDI.json │ │ ├── CasioPV1000.json │ │ ├── CasioPV2000.json │ │ ├── ChannelF.json │ │ ├── Chip8.json │ │ ├── CoCo2.json │ │ ├── ColecoVision.json │ │ ├── CreatiVision.json │ │ ├── DAPHNE.json │ │ ├── DOS.json │ │ ├── Dreamcast.json │ │ ├── EDSAC.json │ │ ├── FDS.json │ │ ├── GBA.json │ │ ├── GBA2P.json │ │ ├── Galaksija.json │ │ ├── Gamate.json │ │ ├── GameCom.json │ │ ├── GameCube.json │ │ ├── GameGear.json │ │ ├── GameNWatch.json │ │ ├── Gameboy.json │ │ ├── Gameboy2P.json │ │ ├── GameboyColor.json │ │ ├── Genesis.json │ │ ├── Intellivision.json │ │ ├── Interact.json │ │ ├── Jaguar.json │ │ ├── JaguarCD.json │ │ ├── Jupiter.json │ │ ├── Laser.json │ │ ├── Lynx48.json │ │ ├── MSX.json │ │ ├── MacOS.json │ │ ├── MacPlus.json │ │ ├── MasterSystem.json │ │ ├── MegaCD.json │ │ ├── MegaDuck.json │ │ ├── Model3.json │ │ ├── MultiComp.json │ │ ├── NAOMI.json │ │ ├── NAOMI2.json │ │ ├── NDS.json │ │ ├── NES.json │ │ ├── NESMusic.json │ │ ├── NeoGeo.json │ │ ├── NeoGeoCD.json │ │ ├── NeoGeoPocket.json │ │ ├── NeoGeoPocketColor.json │ │ ├── Nintendo64.json │ │ ├── Odyssey2.json │ │ ├── Orao.json │ │ ├── Oric.json │ │ ├── Ouya.json │ │ ├── PC.json │ │ ├── PCFX.json │ │ ├── PCXT.json │ │ ├── PDP1.json │ │ ├── PET2001.json │ │ ├── PMD85.json │ │ ├── PS2.json │ │ ├── PS3.json │ │ ├── PS4.json │ │ ├── PS5.json │ │ ├── PSP.json │ │ ├── PSX.json │ │ ├── PocketChallengeV2.json │ │ ├── PokemonMini.json │ │ ├── QL.json │ │ ├── RX78.json │ │ ├── SAMCoupe.json │ │ ├── SG1000.json │ │ ├── SNES.json │ │ ├── SNESMSU1.json │ │ ├── SNESMusic.json │ │ ├── SVI328.json │ │ ├── Saturn.json │ │ ├── ScummVM.json │ │ ├── Sega32X.json │ │ ├── SeriesXS.json │ │ ├── SordM5.json │ │ ├── Specialist.json │ │ ├── SuperGameboy.json │ │ ├── SuperGrafx.json │ │ ├── SuperVision.json │ │ ├── Switch.json │ │ ├── TI994A.json │ │ ├── TRS80.json │ │ ├── TSConf.json │ │ ├── TatungEinstein.json │ │ ├── TomyTutor.json │ │ ├── TurboGrafx16.json │ │ ├── TurboGrafx16CD.json │ │ ├── UK101.json │ │ ├── VC4000.json │ │ ├── VIC20.json │ │ ├── Vector06C.json │ │ ├── Vectrex.json │ │ ├── Video.json │ │ ├── VirtualBoy.json │ │ ├── Vita.json │ │ ├── Wii.json │ │ ├── WiiU.json │ │ ├── Windows.json │ │ ├── WonderSwan.json │ │ ├── WonderSwanColor.json │ │ ├── X68000.json │ │ ├── Xbox.json │ │ ├── Xbox360.json │ │ ├── XboxOne.json │ │ ├── ZX81.json │ │ ├── ZXNext.json │ │ ├── ZXSpectrum.json │ │ └── iOS.json ├── cli │ └── cli.go ├── config │ ├── app.go │ ├── config.go │ └── migrate │ │ ├── iniconfig │ │ └── user.go │ │ └── migrate.go ├── database │ ├── database.go │ ├── mediadb │ │ ├── mediadb.go │ │ ├── migrations │ │ │ └── 20250605011734_init.sql │ │ └── sql.go │ ├── mediascanner │ │ ├── dbutils.go │ │ └── mediascanner.go │ ├── systemdefs │ │ └── systemdefs.go │ └── userdb │ │ ├── boltmigration │ │ └── boltmigration.go │ │ ├── mappings.go │ │ ├── migrations │ │ └── 20250605021915_init.sql │ │ ├── sql.go │ │ └── userdb.go ├── groovyproxy │ └── server.go ├── platforms │ ├── batocera │ │ ├── esapi.go │ │ ├── platform.go │ │ └── systemmap.go │ ├── bazzite │ │ └── platform.go │ ├── chimeraos │ │ └── platform.go │ ├── libreelec │ │ └── platform.go │ ├── linux │ │ ├── installer │ │ │ ├── conf │ │ │ │ ├── 60-zaparoo.rules │ │ │ │ ├── blacklist-zaparoo.conf │ │ │ │ └── zaparoo.service │ │ │ └── install.go │ │ └── platform.go │ ├── mac │ │ └── platform.go │ ├── mister │ │ ├── arcadedb.go │ │ ├── commands.go │ │ ├── config.go │ │ ├── csvmappings.go │ │ ├── launchers.go │ │ ├── platform.go │ │ ├── scripts.go │ │ ├── tracker.go │ │ ├── ui.go │ │ └── utils.go │ ├── mistex │ │ ├── commands.go │ │ └── platform.go │ ├── platforms.go │ ├── recalbox │ │ └── platform.go │ ├── retropie │ │ └── platform.go │ ├── steamos │ │ └── platform.go │ └── windows │ │ └── platform.go ├── readers │ ├── acr122_pcsc │ │ ├── acr122_pcsc.go │ │ └── ndef.go │ ├── file │ │ └── file.go │ ├── libnfc │ │ ├── libnfc.go │ │ └── tags │ │ │ ├── mifare.go │ │ │ ├── ndef.go │ │ │ ├── ndef_test.go │ │ │ ├── ntag.go │ │ │ └── tags.go │ ├── optical_drive │ │ └── optical_drive.go │ ├── pn532_uart │ │ ├── ndef.go │ │ ├── pn532.go │ │ └── pn532_uart.go │ ├── readers.go │ └── simple_serial │ │ └── simple_serial.go ├── service │ ├── mappings.go │ ├── playlists │ │ └── playlists.go │ ├── queues.go │ ├── readers.go │ ├── service.go │ ├── state │ │ └── state.go │ └── tokens │ │ └── tokens.go ├── ui │ ├── systray │ │ └── systray.go │ ├── tui │ │ ├── buildapp_linux.go │ │ ├── buildapp_others.go │ │ ├── exportlog.go │ │ ├── generatedb.go │ │ ├── main.go │ │ ├── searchmedia.go │ │ ├── settings.go │ │ ├── utils.go │ │ └── writetag.go │ └── widgets │ │ ├── models │ │ └── models.go │ │ └── widgets.go ├── utils │ ├── launchers.go │ ├── linuxinput │ │ ├── keyboardmap │ │ │ └── keyboardmap.go │ │ ├── keymap.go │ │ └── linuxinput.go │ ├── logging.go │ ├── paths.go │ ├── serial.go │ ├── service.go │ └── utils.go └── zapscript │ ├── commands.go │ ├── http.go │ ├── input.go │ ├── launch.go │ ├── models │ └── zapscript.go │ ├── online.go │ ├── playlist.go │ ├── playlist_test.go │ └── utils.go └── scripts ├── cross └── Dockerfile ├── linux_amd64 └── Dockerfile ├── linux_arm └── Dockerfile ├── linux_arm64 └── Dockerfile ├── mister └── repo │ ├── generate.py │ └── tapto.json ├── taptui └── taptui.sh └── tasks ├── batocera.yml ├── bazzite.yml ├── chimeraos.yml ├── docker.yml ├── libreelec.yml ├── linux.yml ├── mac.yml ├── mister.yml ├── mistex.yml ├── recalbox.yml ├── retropie.yml ├── steamos.yml ├── test.yml ├── utils ├── makezip.go ├── windowsiss.go └── windowsmeta.go └── windows.yml /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Update README contributors 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | contrib-readme-job: 9 | runs-on: ubuntu-latest 10 | name: A job to automate contrib in readme 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | steps: 15 | - name: Contribute List 16 | uses: akhilmhdh/contributors-readme-action@v2.3.10 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/mister_repo.yml: -------------------------------------------------------------------------------- 1 | name: Generate MiSTer Downloader repo 2 | on: 3 | workflow_dispatch: 4 | 5 | permissions: write-all 6 | jobs: 7 | mister-repo: 8 | runs-on: ubuntu-latest 9 | env: 10 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Get latest Zaparoo release 14 | id: zaparooreleaseinfo 15 | uses: cardinalby/git-get-release-action@v1 16 | with: 17 | latest: true 18 | repo: ZaparooProject/zaparoo-core 19 | - name: Create repo database 20 | run: | 21 | python3 scripts/mister/repo/generate.py ${{ steps.zaparooreleaseinfo.outputs.tag_name }} 22 | - name: Commit repo database 23 | uses: EndBug/add-and-commit@v9 24 | with: 25 | add: scripts/mister/repo/tapto.json -f -A 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.exe~ 3 | *.dll 4 | *.so 5 | *.dylib 6 | *.test 7 | *.out 8 | 9 | go.work 10 | 11 | .vscode/ 12 | .idea/ 13 | qodana.yaml 14 | 15 | *.db 16 | *.tar 17 | 18 | .env 19 | Taskfile.yml 20 | .task/ 21 | 22 | /pkg/assets/_app/dist 23 | 24 | /bin 25 | /_bin 26 | /_scratch 27 | /_build 28 | /out 29 | /scripts/mister/build/_build 30 | 31 | .DS_Store 32 | tmp/ 33 | /cmd/scratch 34 | /cmd/windows/*.syso 35 | /cmd/windows/winres/winres.json 36 | -------------------------------------------------------------------------------- /Taskfile.dist.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | env: 4 | GOPROXY: https://goproxy.io,direct 5 | APP_VERSION: 6 | sh: echo -n "$(git rev-parse --short HEAD)-dev" 7 | UID: 8 | sh: '{{if ne OS "windows"}}id -u{{end}}' 9 | GID: 10 | sh: '{{if ne OS "windows"}}id -g{{end}}' 11 | 12 | dotenv: [".env"] 13 | 14 | includes: 15 | docker: ./scripts/tasks/docker.yml 16 | batocera: ./scripts/tasks/batocera.yml 17 | bazzite: ./scripts/tasks/bazzite.yml 18 | chimeraos: ./scripts/tasks/chimeraos.yml 19 | libreelec: ./scripts/tasks/libreelec.yml 20 | linux: ./scripts/tasks/linux.yml 21 | mac: ./scripts/tasks/mac.yml 22 | mister: ./scripts/tasks/mister.yml 23 | mistex: ./scripts/tasks/mistex.yml 24 | recalbox: ./scripts/tasks/recalbox.yml 25 | retropie: ./scripts/tasks/retropie.yml 26 | steamos: ./scripts/tasks/steamos.yml 27 | windows: ./scripts/tasks/windows.yml 28 | test: ./scripts/tasks/test.yml 29 | 30 | tasks: 31 | build: 32 | vars: 33 | APP_BIN: '{{default "zaparoo" .APP_BIN}}' 34 | BUILD_OS: "{{default OS .BUILD_OS}}" 35 | BUILD_ARCH: "{{default ARCH .BUILD_ARCH}}" 36 | PLATFORM: '{{default (OS | replace "darwin" "mac") .PLATFORM}}' 37 | BUILD_DIR: "_build/{{.PLATFORM}}_{{.BUILD_ARCH}}" 38 | EXTRA_LDFLAGS: "{{.EXTRA_LDFLAGS}}" 39 | env: 40 | GO111MODULE: on 41 | CGO_ENABLED: 1 42 | GOARCH: "{{.BUILD_ARCH}}" 43 | GOOS: "{{.BUILD_OS}}" 44 | CGO_LDFLAGS: '{{if not .NO_LIBNFC}}-lnfc -lusb{{end}}' 45 | PLATFORM: '{{.PLATFORM}}' 46 | CC: "{{.CC}}" 47 | CXX: "{{.CXX}}" 48 | APP_VERSION: '{{default "dev" .APP_VERSION}}' 49 | cmds: 50 | - >- 51 | go build 52 | -ldflags "-X 'github.com/ZaparooProject/zaparoo-core/pkg/config.AppVersion=${APP_VERSION}' 53 | -linkmode external -extldflags 54 | '{{if not .NO_LIBNFC}}${CGO_LDFLAGS}{{end}} 55 | {{if not .NO_STATIC}}-static{{end}}' 56 | -s -w {{.EXTRA_LDFLAGS}}" 57 | -tags "netgo,sqlite_omit_load_extension" 58 | -o "{{.BUILD_DIR}}/{{.APP_BIN}}" 59 | "./cmd/${PLATFORM}" 60 | 61 | clean: '{{if eq OS "windows"}}powershell rm -Recurse -Force{{else}}rm -rf{{end}} _build' 62 | 63 | test: go test ./... 64 | 65 | test-coverage: 66 | cmds: 67 | - go test -coverprofile=coverage.out ./... 68 | - go tool cover -html=coverage.out 69 | - '{{if eq OS "windows"}}powershell rm {{else}}rm {{end}} coverage.out' 70 | -------------------------------------------------------------------------------- /cmd/batocera/scripts/configs/emulationstation/scripts/game-selected/zaparoo_game_select.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Save the arguments into variables. 4 | system="${1}" 5 | rom="${2}" 6 | romname="${3}" 7 | 8 | # Convert an argument into another value. 9 | if [[ "${system}" == "fbneo" ]]; then 10 | system="mame" 11 | fi 12 | 13 | # Switch case for certain systems. 14 | case ${system} in 15 | fbneo) 16 | system="mame" 17 | ;; 18 | scummvm) 19 | rom="${rom%.*}" 20 | ;; 21 | esac 22 | 23 | # Execute this part every time this event triggers. 24 | echo ${rom} > /var/run/zaparoo.tmp -------------------------------------------------------------------------------- /cmd/batocera/scripts/configs/multimedia_keys.conf: -------------------------------------------------------------------------------- 1 | KEY_LEFTMETA+KEY_W 1 /userdata/system/zaparoo_write_game.sh 2 | KEY_W+KEY_LEFTMETA 1 /userdata/system/zaparoo_write_game.sh 3 | BTN_MODE+BTN_THUMBR 1 /userdata/system/zaparoo_write_game.sh 4 | BTN_THUMBR+BTN_MODE 1 /userdata/system/zaparoo_write_game.sh -------------------------------------------------------------------------------- /cmd/batocera/scripts/services/zaparoo_service: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | chmod +x /userdata/system/zaparoo 3 | /userdata/system/zaparoo -service "$1" 4 | -------------------------------------------------------------------------------- /cmd/batocera/scripts/zaparoo_write_game.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Retrieve latest selected item 4 | item="$(cat /var/run/zaparoo.tmp)" 5 | 6 | # POST to batocera to put a card on the reader 7 | curl -X POST --data "Place a tag on the NFC writer to write '${item}'. Write will timeout in 30 seconds" http://127.0.0.1:1234/messagebox 8 | 9 | /userdata/system/zaparoo -write "${item}" 10 | 11 | if [ $? -eq 0 ]; then 12 | curl -X POST --data "The tag '${item}' has been written sucesfully!" http://127.0.0.1:1234/messagebox 13 | else 14 | curl -X POST --data "Something went wrong writing the token :(" http://127.0.0.1:1234/messagebox 15 | fi -------------------------------------------------------------------------------- /cmd/bazzite/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023-2025 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "fmt" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/bazzite" 30 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/linux/installer" 31 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 32 | "github.com/rs/zerolog" 33 | "github.com/rs/zerolog/log" 34 | "io" 35 | "os" 36 | "os/signal" 37 | "syscall" 38 | ) 39 | 40 | // default user bazzite/bazzite, sudo is enabled 41 | // SSH is disabled by default (sudo systemctl enable --now sshd) 42 | // home: /home/bazzite 43 | // pn532 works without any changes, tag scans 44 | // acr122u works after modeprobe blacklist 45 | // add steam as a default launcher 46 | 47 | func main() { 48 | sigs := make(chan os.Signal, 1) 49 | defer close(sigs) 50 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 51 | 52 | pl := &bazzite.Platform{} 53 | flags := cli.SetupFlags() 54 | 55 | doInstall := flag.Bool("install", false, "configure system for zaparoo") 56 | doUninstall := flag.Bool("uninstall", false, "revert zaparoo system configuration") 57 | asDaemon := flag.Bool("daemon", false, "run zaparoo in daemon mode") 58 | 59 | flags.Pre(pl) 60 | 61 | // TODO: bazzite runs on fedora silverblue and has a read-only root fs 62 | // which will conflict with this install 63 | if *doInstall { 64 | err := installer.CLIInstall() 65 | if err != nil { 66 | os.Exit(1) 67 | } else { 68 | os.Exit(0) 69 | } 70 | } else if *doUninstall { 71 | err := installer.CLIUninstall() 72 | if err != nil { 73 | os.Exit(1) 74 | } else { 75 | os.Exit(0) 76 | } 77 | } 78 | 79 | if os.Geteuid() == 0 { 80 | _, _ = fmt.Fprintf(os.Stderr, "Zaparoo must not be run as root\n") 81 | os.Exit(1) 82 | } 83 | 84 | // only difference with daemon mode right now is no log pretty printing 85 | // TODO: launch simple gui 86 | // TODO: fork service if it's not running 87 | logWriters := []io.Writer{zerolog.ConsoleWriter{Out: os.Stderr}} 88 | if *asDaemon { 89 | logWriters = []io.Writer{os.Stderr} 90 | } 91 | 92 | cfg := cli.Setup( 93 | pl, 94 | config.BaseDefaults, 95 | logWriters, 96 | ) 97 | 98 | flags.Post(cfg, pl) 99 | 100 | stop, err := service.Start(pl, cfg) 101 | if err != nil { 102 | log.Error().Err(err).Msg("error starting service") 103 | os.Exit(1) 104 | } 105 | 106 | <-sigs 107 | err = stop() 108 | if err != nil { 109 | log.Error().Err(err).Msg("error stopping service") 110 | os.Exit(1) 111 | } 112 | 113 | os.Exit(0) 114 | } 115 | -------------------------------------------------------------------------------- /cmd/chimeraos/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023-2025 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "fmt" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/chimeraos" 30 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/linux/installer" 31 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 32 | "github.com/rs/zerolog" 33 | "github.com/rs/zerolog/log" 34 | "io" 35 | "os" 36 | "os/signal" 37 | "syscall" 38 | ) 39 | 40 | // launch api: https://github.com/ChimeraOS/chimera/tree/master/bin 41 | // default user gamer/gamer 42 | // default launcher steam 43 | // ssh is disabled by default, requires public key 44 | // acr122u works by default 45 | // pn532 requires udev rules 46 | 47 | func main() { 48 | sigs := make(chan os.Signal, 1) 49 | defer close(sigs) 50 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 51 | 52 | pl := &chimeraos.Platform{} 53 | flags := cli.SetupFlags() 54 | 55 | doInstall := flag.Bool("install", false, "configure system for zaparoo") 56 | doUninstall := flag.Bool("uninstall", false, "revert zaparoo system configuration") 57 | asDaemon := flag.Bool("daemon", false, "run zaparoo in daemon mode") 58 | 59 | flags.Pre(pl) 60 | 61 | if *doInstall { 62 | err := installer.CLIInstall() 63 | if err != nil { 64 | os.Exit(1) 65 | } else { 66 | os.Exit(0) 67 | } 68 | } else if *doUninstall { 69 | err := installer.CLIUninstall() 70 | if err != nil { 71 | os.Exit(1) 72 | } else { 73 | os.Exit(0) 74 | } 75 | } 76 | 77 | if os.Geteuid() == 0 { 78 | _, _ = fmt.Fprintf(os.Stderr, "Zaparoo must not be run as root\n") 79 | os.Exit(1) 80 | } 81 | 82 | // only difference with daemon mode right now is no log pretty printing 83 | // TODO: launch simple gui 84 | // TODO: fork service if it's not running 85 | logWriters := []io.Writer{zerolog.ConsoleWriter{Out: os.Stderr}} 86 | if *asDaemon { 87 | logWriters = []io.Writer{os.Stderr} 88 | } 89 | 90 | cfg := cli.Setup( 91 | pl, 92 | config.BaseDefaults, 93 | logWriters, 94 | ) 95 | 96 | flags.Post(cfg, pl) 97 | 98 | stop, err := service.Start(pl, cfg) 99 | if err != nil { 100 | log.Error().Err(err).Msg("error starting service") 101 | os.Exit(1) 102 | } 103 | 104 | <-sigs 105 | err = stop() 106 | if err != nil { 107 | log.Error().Err(err).Msg("error stopping service") 108 | os.Exit(1) 109 | } 110 | 111 | os.Exit(0) 112 | } 113 | -------------------------------------------------------------------------------- /cmd/libreelec/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023-2025 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/libreelec" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/linux/installer" 30 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 31 | "github.com/rs/zerolog" 32 | "github.com/rs/zerolog/log" 33 | "io" 34 | "os" 35 | "os/signal" 36 | "syscall" 37 | ) 38 | 39 | // ssh disabled by default 40 | // probably need arm32 build 41 | // default user root/libreelec 42 | // home folder is /storage 43 | // api is available for indexing and launching. may need to be turned on 44 | // pn532 and acr122u work out of box 45 | // https://wiki.libreelec.tv/configuration/startup-shutdown 46 | 47 | func main() { 48 | sigs := make(chan os.Signal, 1) 49 | defer close(sigs) 50 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 51 | 52 | pl := &libreelec.Platform{} 53 | flags := cli.SetupFlags() 54 | 55 | doInstall := flag.Bool("install", false, "configure system for zaparoo") 56 | doUninstall := flag.Bool("uninstall", false, "revert zaparoo system configuration") 57 | asDaemon := flag.Bool("daemon", false, "run zaparoo in daemon mode") 58 | 59 | flags.Pre(pl) 60 | 61 | if *doInstall { 62 | err := installer.CLIInstall() 63 | if err != nil { 64 | os.Exit(1) 65 | } else { 66 | os.Exit(0) 67 | } 68 | } else if *doUninstall { 69 | err := installer.CLIUninstall() 70 | if err != nil { 71 | os.Exit(1) 72 | } else { 73 | os.Exit(0) 74 | } 75 | } 76 | 77 | // only difference with daemon mode right now is no log pretty printing 78 | // TODO: launch simple gui 79 | // TODO: fork service if it's not running 80 | logWriters := []io.Writer{zerolog.ConsoleWriter{Out: os.Stderr}} 81 | if *asDaemon { 82 | logWriters = []io.Writer{os.Stderr} 83 | } 84 | 85 | cfg := cli.Setup( 86 | pl, 87 | config.BaseDefaults, 88 | logWriters, 89 | ) 90 | 91 | flags.Post(cfg, pl) 92 | 93 | stop, err := service.Start(pl, cfg) 94 | if err != nil { 95 | log.Error().Err(err).Msg("error starting service") 96 | os.Exit(1) 97 | } 98 | 99 | <-sigs 100 | err = stop() 101 | if err != nil { 102 | log.Error().Err(err).Msg("error stopping service") 103 | os.Exit(1) 104 | } 105 | 106 | os.Exit(0) 107 | } 108 | -------------------------------------------------------------------------------- /cmd/linux/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/cmd/linux/icon.png -------------------------------------------------------------------------------- /cmd/mac/app/Zaparoo Core.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleInfoDictionaryVersion 6 | 6.0 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundlePackageType 10 | APPL 11 | CFBundleIdentifier 12 | org.zaparoo.core 13 | CFBundleExecutable 14 | app.sh 15 | CFBundleIconFile 16 | icon.icns 17 | CFBundleDisplayName 18 | Zaparoo Core 19 | CFBundleName 20 | Zaparoo Core 21 | CFBundleVersion 22 | 0.0.1 23 | CFBundleShortVersionString 24 | 0.0.1 25 | NSHumanReadableCopyright 26 | © 2025 Contributors to the Zaparoo project 27 | CFBundleSignature 28 | ???? 29 | NSHighResolutionCapable 30 | 31 | LSUIElement 32 | 1 33 | LSMultipleInstancesProhibited 34 | 35 | 36 | -------------------------------------------------------------------------------- /cmd/mac/app/Zaparoo Core.app/Contents/MacOS/app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "${0%/*}" || exit 3 | exec ./Zaparoo\ Core -gui 4 | -------------------------------------------------------------------------------- /cmd/mac/app/Zaparoo Core.app/Contents/Resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/cmd/mac/app/Zaparoo Core.app/Contents/Resources/icon.icns -------------------------------------------------------------------------------- /cmd/mac/app/systrayicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/cmd/mac/app/systrayicon.png -------------------------------------------------------------------------------- /cmd/mac/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023, 2024 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "fmt" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/mac" 30 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 31 | "github.com/ZaparooProject/zaparoo-core/pkg/ui/systray" 32 | "github.com/ZaparooProject/zaparoo-core/pkg/ui/tui" 33 | "github.com/ZaparooProject/zaparoo-core/pkg/utils" 34 | "github.com/rs/zerolog/log" 35 | "io" 36 | "os" 37 | "os/signal" 38 | "path/filepath" 39 | "syscall" 40 | 41 | _ "embed" 42 | ) 43 | 44 | //go:embed app/systrayicon.png 45 | var systrayIcon []byte 46 | 47 | func main() { 48 | if os.Geteuid() == 0 { 49 | _, _ = fmt.Fprintf(os.Stderr, "Zaparoo cannot be run as root\n") 50 | os.Exit(1) 51 | } 52 | 53 | pl := &mac.Platform{} 54 | flags := cli.SetupFlags() 55 | 56 | daemonMode := flag.Bool( 57 | "daemon", 58 | false, 59 | "run service in foreground with no UI", 60 | ) 61 | guiMode := flag.Bool( 62 | "gui", 63 | false, 64 | "run service as daemon with GUI", 65 | ) 66 | 67 | flags.Pre(pl) 68 | 69 | var logWriters []io.Writer 70 | if *daemonMode || *guiMode { 71 | logWriters = []io.Writer{os.Stderr} 72 | } 73 | 74 | cfg := cli.Setup( 75 | pl, 76 | config.BaseDefaults, 77 | logWriters, 78 | ) 79 | 80 | defer func() { 81 | if err := recover(); err != nil { 82 | _, _ = fmt.Fprintf(os.Stderr, "Panic: %s\n", err) 83 | log.Fatal().Msgf("panic: %v", err) 84 | } 85 | }() 86 | 87 | flags.Post(cfg, pl) 88 | 89 | if !utils.IsServiceRunning(cfg) { 90 | stopSvc, err := service.Start(pl, cfg) 91 | if err != nil { 92 | log.Error().Msgf("error starting service: %s", err) 93 | _, _ = fmt.Fprintf(os.Stderr, "Error starting service: %s\n", err) 94 | os.Exit(1) 95 | } 96 | 97 | defer func() { 98 | err := stopSvc() 99 | if err != nil { 100 | log.Error().Msgf("error stopping service: %s", err) 101 | } 102 | }() 103 | } 104 | 105 | sigs := make(chan os.Signal, 1) 106 | defer close(sigs) 107 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 108 | 109 | exit := make(chan bool, 1) 110 | defer close(exit) 111 | 112 | if *daemonMode { 113 | log.Info().Msg("started in daemon mode") 114 | } else if *guiMode { 115 | systray.Run(cfg, pl, systrayIcon, func(string) {}, func() { 116 | exit <- true 117 | }) 118 | } else { 119 | // default to showing the TUI 120 | app, err := tui.BuildMain( 121 | cfg, pl, 122 | func() bool { return utils.IsServiceRunning(cfg) }, 123 | filepath.Join(os.Getenv("HOME"), "Desktop", "core.log"), 124 | "desktop", 125 | ) 126 | if err != nil { 127 | log.Error().Err(err).Msgf("error building UI") 128 | _, _ = fmt.Fprintf(os.Stderr, "Error building UI: %s\n", err) 129 | os.Exit(1) 130 | } 131 | 132 | err = app.Run() 133 | if err != nil { 134 | log.Error().Err(err).Msg("error running UI") 135 | _, _ = fmt.Fprintf(os.Stderr, "Error running UI: %s\n", err) 136 | os.Exit(1) 137 | } 138 | 139 | exit <- true 140 | } 141 | 142 | select { 143 | case <-sigs: 144 | case <-exit: 145 | } 146 | 147 | os.Exit(0) 148 | } 149 | -------------------------------------------------------------------------------- /cmd/mister/gui.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023, 2024 Callan Barrett 4 | 5 | This file is part of Zaparoo Core. 6 | 7 | Zaparoo Core is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Zaparoo Core is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Zaparoo Core. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "github.com/ZaparooProject/zaparoo-core/pkg/ui/tui" 26 | "os" 27 | "path" 28 | 29 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 30 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 31 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/mister" 32 | "github.com/ZaparooProject/zaparoo-core/pkg/utils" 33 | "github.com/rivo/tview" 34 | "github.com/rs/zerolog/log" 35 | mrextMister "github.com/wizzomafizzo/mrext/pkg/mister" 36 | ) 37 | 38 | func buildTheInstallRequestApp() (*tview.Application, error) { 39 | var startup mrextMister.Startup 40 | app := tview.NewApplication() 41 | // create the main modal 42 | modal := tview.NewModal() 43 | modal.SetTitle("Install service"). 44 | SetBorder(true). 45 | SetTitleAlign(tview.AlignCenter) 46 | modal.SetText("Add Zaparoo service to MiSTer startup?\nThis won't impact MiSTer's performance."). 47 | AddButtons([]string{"Yes", "No"}). 48 | SetDoneFunc(func(buttonIndex int, buttonLabel string) { 49 | if buttonLabel == "Yes" { 50 | err := startup.AddService("mrext/" + config.AppName) 51 | if err != nil { 52 | _, _ = fmt.Fprintf(os.Stderr, "Error adding to startup: %v\n", err) 53 | os.Exit(1) 54 | } 55 | if len(startup.Entries) > 0 { 56 | err = startup.Save() 57 | if err != nil { 58 | _, _ = fmt.Fprintf(os.Stderr, "Error saving startup: %v\n", err) 59 | os.Exit(1) 60 | } 61 | } 62 | app.Stop() 63 | } else if buttonLabel == "No" { 64 | app.Stop() 65 | } 66 | }) 67 | 68 | return app.SetRoot(modal, true).EnableMouse(true), nil 69 | } 70 | 71 | func tryAddStartup() error { 72 | var startup mrextMister.Startup 73 | 74 | err := startup.Load() 75 | if err != nil { 76 | log.Error().Msgf("failed to load startup file: %s", err) 77 | } 78 | 79 | // migration from tapto name 80 | if startup.Exists("mrext/tapto") { 81 | err = startup.Remove("mrext/tapto") 82 | if err != nil { 83 | return err 84 | } 85 | } 86 | 87 | if !startup.Exists("mrext/" + config.AppName) { 88 | err := tui.BuildAndRetry(func() (*tview.Application, error) { 89 | return buildTheInstallRequestApp() 90 | }) 91 | if err != nil { 92 | log.Error().Msgf("failed to build app: %s", err) 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func displayServiceInfo(pl platforms.Platform, cfg *config.Instance, service *utils.Service) error { 100 | // Asturur > Wizzo 101 | return tui.BuildAndRetry(func() (*tview.Application, error) { 102 | logDestinationPath := path.Join(mister.DataDir, config.LogFile) 103 | return tui.BuildMain(cfg, pl, service.Running, logDestinationPath, "SD card") 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /cmd/recalbox/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023-2025 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/recalbox" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 30 | "github.com/rs/zerolog" 31 | "github.com/rs/zerolog/log" 32 | "io" 33 | "os" 34 | "os/signal" 35 | "syscall" 36 | ) 37 | 38 | // home: /recalbox/share/system 39 | // user: root/recalboxroot (ssh on by default) 40 | // https://wiki.recalbox.com/en/advanced-usage/scripts-on-emulationstation-events 41 | // most of fs is read-only? no +x allowed 42 | // /recalbox/share/userscripts allows writes 43 | // mount -o remount,rw / 44 | // put in /recalbox/scripts 45 | 46 | func main() { 47 | sigs := make(chan os.Signal, 1) 48 | defer close(sigs) 49 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 50 | 51 | pl := &recalbox.Platform{} 52 | flags := cli.SetupFlags() 53 | 54 | asDaemon := flag.Bool("daemon", false, "run zaparoo in daemon mode") 55 | 56 | flags.Pre(pl) 57 | 58 | logWriters := []io.Writer{zerolog.ConsoleWriter{Out: os.Stderr}} 59 | if *asDaemon { 60 | logWriters = []io.Writer{os.Stderr} 61 | } 62 | 63 | cfg := cli.Setup( 64 | pl, 65 | config.BaseDefaults, 66 | logWriters, 67 | ) 68 | 69 | flags.Post(cfg, pl) 70 | 71 | stop, err := service.Start(pl, cfg) 72 | if err != nil { 73 | log.Error().Err(err).Msg("error starting service") 74 | os.Exit(1) 75 | } 76 | 77 | <-sigs 78 | err = stop() 79 | if err != nil { 80 | log.Error().Err(err).Msg("error stopping service") 81 | os.Exit(1) 82 | } 83 | 84 | os.Exit(0) 85 | } 86 | -------------------------------------------------------------------------------- /cmd/retropie/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023-2025 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "fmt" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/linux/installer" 30 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/retropie" 31 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 32 | "github.com/rs/zerolog" 33 | "github.com/rs/zerolog/log" 34 | "io" 35 | "os" 36 | "os/signal" 37 | "syscall" 38 | ) 39 | 40 | func main() { 41 | sigs := make(chan os.Signal, 1) 42 | defer close(sigs) 43 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 44 | 45 | pl := &retropie.Platform{} 46 | flags := cli.SetupFlags() 47 | 48 | doInstall := flag.Bool("install", false, "configure system for zaparoo") 49 | doUninstall := flag.Bool("uninstall", false, "revert zaparoo system configuration") 50 | asDaemon := flag.Bool("daemon", false, "run zaparoo in daemon mode") 51 | 52 | flags.Pre(pl) 53 | 54 | if *doInstall { 55 | err := installer.CLIInstall() 56 | if err != nil { 57 | os.Exit(1) 58 | } else { 59 | os.Exit(0) 60 | } 61 | } else if *doUninstall { 62 | err := installer.CLIUninstall() 63 | if err != nil { 64 | os.Exit(1) 65 | } else { 66 | os.Exit(0) 67 | } 68 | } 69 | 70 | if os.Geteuid() == 0 { 71 | _, _ = fmt.Fprintf(os.Stderr, "Zaparoo must not be run as root\n") 72 | os.Exit(1) 73 | } 74 | 75 | // only difference with daemon mode right now is no log pretty printing 76 | // TODO: launch simple gui 77 | // TODO: fork service if it's not running 78 | logWriters := []io.Writer{zerolog.ConsoleWriter{Out: os.Stderr}} 79 | if *asDaemon { 80 | logWriters = []io.Writer{os.Stderr} 81 | } 82 | 83 | cfg := cli.Setup( 84 | pl, 85 | config.BaseDefaults, 86 | logWriters, 87 | ) 88 | 89 | flags.Post(cfg, pl) 90 | 91 | stop, err := service.Start(pl, cfg) 92 | if err != nil { 93 | log.Error().Err(err).Msg("error starting service") 94 | os.Exit(1) 95 | } 96 | 97 | <-sigs 98 | err = stop() 99 | if err != nil { 100 | log.Error().Err(err).Msg("error stopping service") 101 | os.Exit(1) 102 | } 103 | 104 | os.Exit(0) 105 | } 106 | -------------------------------------------------------------------------------- /cmd/steamos/conf/60-zaparoo.rules: -------------------------------------------------------------------------------- 1 | # Allow user (deck) access to NFC readers 2 | 3 | # CH340 serial devices (PN532 V2, ESP32, DIY Reader, etc.) 4 | SUBSYSTEMS=="usb", ATTRS{idProduct}=="7523", ATTRS{idVendor}=="1a86", MODE="0660", GROUP="deck" 5 | -------------------------------------------------------------------------------- /cmd/steamos/conf/blacklist-zaparoo.conf: -------------------------------------------------------------------------------- 1 | blacklist pn533 2 | blacklist pn533_usb 3 | -------------------------------------------------------------------------------- /cmd/steamos/conf/zaparoo.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Zaparoo Core service 3 | 4 | [Service] 5 | Type=exec 6 | Restart=on-failure 7 | RestartSec=5 8 | StandardError=syslog 9 | User=deck 10 | Group=deck 11 | WorkingDirectory=%%WORKING%% 12 | ExecStart=%%EXEC%% 13 | 14 | [Install] 15 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /cmd/steamos/install.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | // TODO: allow updating if files have changed 13 | 14 | //go:embed conf/zaparoo.service 15 | var serviceFile string 16 | 17 | //go:embed conf/blacklist-zaparoo.conf 18 | var modprobeFile string 19 | 20 | //go:embed conf/60-zaparoo.rules 21 | var udevFile string 22 | 23 | const ( 24 | servicePath = "/etc/systemd/system/zaparoo.service" 25 | modprobePath = "/etc/modprobe.d/blacklist-zaparoo.conf" 26 | udevPath = "/etc/udev/rules.d/60-zaparoo.rules" 27 | ) 28 | 29 | func install() error { 30 | // install and prep systemd service 31 | if _, err := os.Stat(servicePath); os.IsNotExist(err) { 32 | exe, err := os.Executable() 33 | if err != nil { 34 | exe = "/home/deck/zaparoo/" + config.AppName 35 | } 36 | serviceFile = strings.ReplaceAll(serviceFile, "%%EXEC%%", exe) 37 | serviceFile = strings.ReplaceAll(serviceFile, "%%WORKING%%", filepath.Dir(exe)) 38 | 39 | err = os.WriteFile(servicePath, []byte(serviceFile), 0644) 40 | if err != nil { 41 | return err 42 | } 43 | err = exec.Command("systemctl", "daemon-reload").Run() 44 | if err != nil { 45 | return err 46 | } 47 | err = exec.Command("systemctl", "enable", "zaparoo").Run() 48 | if err != nil { 49 | return err 50 | } 51 | } 52 | 53 | // install udev rules and refresh 54 | if _, err := os.Stat(udevPath); os.IsNotExist(err) { 55 | err = os.WriteFile(udevPath, []byte(udevFile), 0644) 56 | if err != nil { 57 | return err 58 | } 59 | err = exec.Command("udevadm", "control", "--reload-rules").Run() 60 | if err != nil { 61 | return err 62 | } 63 | err = exec.Command("udevadm", "trigger").Run() 64 | if err != nil { 65 | return err 66 | } 67 | } 68 | 69 | // install modprobe blacklist 70 | if _, err := os.Stat(modprobePath); os.IsNotExist(err) { 71 | err = os.WriteFile(modprobePath, []byte(modprobeFile), 0644) 72 | if err != nil { 73 | return err 74 | } 75 | } 76 | 77 | return nil 78 | } 79 | 80 | func uninstall() error { 81 | if _, err := os.Stat(servicePath); !os.IsNotExist(err) { 82 | err = exec.Command("systemctl", "disable", "zaparoo").Run() 83 | if err != nil { 84 | return err 85 | } 86 | err = exec.Command("systemctl", "stop", "zaparoo").Run() 87 | if err != nil { 88 | return err 89 | } 90 | err = exec.Command("systemctl", "daemon-reload").Run() 91 | if err != nil { 92 | return err 93 | } 94 | err = os.Remove(servicePath) 95 | if err != nil { 96 | return err 97 | } 98 | } 99 | 100 | if _, err := os.Stat(modprobePath); !os.IsNotExist(err) { 101 | err = os.Remove(modprobePath) 102 | if err != nil { 103 | return err 104 | } 105 | } 106 | 107 | if _, err := os.Stat(udevPath); !os.IsNotExist(err) { 108 | err = os.Remove(udevPath) 109 | if err != nil { 110 | return err 111 | } 112 | } 113 | 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /cmd/steamos/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023, 2024 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "flag" 26 | "fmt" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/config/migrate" 30 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/steamos" 31 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 32 | "github.com/ZaparooProject/zaparoo-core/pkg/utils" 33 | "github.com/adrg/xdg" 34 | "github.com/rs/zerolog/log" 35 | "io" 36 | "os" 37 | "os/signal" 38 | "path/filepath" 39 | "syscall" 40 | 41 | _ "embed" 42 | ) 43 | 44 | // TODO: fix permissions on files in ~/zaparoo so root doesn't lock them 45 | 46 | func main() { 47 | sigs := make(chan os.Signal, 1) 48 | defer close(sigs) 49 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 50 | 51 | pl := &steamos.Platform{} 52 | flags := cli.SetupFlags() 53 | 54 | doInstall := flag.Bool("install", false, "install zaparoo service") 55 | doUninstall := flag.Bool("uninstall", false, "uninstall zaparoo service") 56 | 57 | flags.Pre(pl) 58 | 59 | uid := os.Getuid() 60 | if *doInstall { 61 | if uid != 0 { 62 | _, _ = fmt.Fprintf(os.Stderr, "Install must be run as root\n") 63 | os.Exit(1) 64 | } 65 | err := install() 66 | if err != nil { 67 | _, _ = fmt.Fprintf(os.Stderr, "Error installing service: %v\n", err) 68 | os.Exit(1) 69 | } 70 | os.Exit(0) 71 | } else if *doUninstall { 72 | if uid != 0 { 73 | _, _ = fmt.Fprintf(os.Stderr, "Uninstall must be run as root\n") 74 | os.Exit(1) 75 | } 76 | err := uninstall() 77 | if err != nil { 78 | _, _ = fmt.Fprintf(os.Stderr, "Error uninstalling service: %v\n", err) 79 | os.Exit(1) 80 | } 81 | os.Exit(0) 82 | } 83 | 84 | if uid == 0 { 85 | _, _ = fmt.Fprintf(os.Stderr, "Service must not be run as root\n") 86 | os.Exit(1) 87 | } 88 | 89 | err := os.MkdirAll(filepath.Join(xdg.DataHome, config.AppName), 0755) 90 | if err != nil { 91 | _, _ = fmt.Fprintf(os.Stderr, "Error creating data directory: %v\n", err) 92 | os.Exit(1) 93 | } 94 | 95 | defaults := config.BaseDefaults 96 | iniPath := filepath.Join(utils.ExeDir(), "tapto.ini") 97 | if migrate.Required(iniPath, filepath.Join(utils.ConfigDir(pl), config.CfgFile)) { 98 | migrated, err := migrate.IniToToml(iniPath) 99 | if err != nil { 100 | _, _ = fmt.Fprintf(os.Stderr, "Error migrating config: %v\n", err) 101 | os.Exit(1) 102 | } else { 103 | defaults = migrated 104 | } 105 | } 106 | 107 | cfg := cli.Setup( 108 | pl, 109 | defaults, 110 | []io.Writer{os.Stderr}, 111 | ) 112 | 113 | flags.Post(cfg, pl) 114 | 115 | stop, err := service.Start(pl, cfg) 116 | if err != nil { 117 | log.Error().Err(err).Msg("error starting service") 118 | os.Exit(1) 119 | } 120 | 121 | <-sigs 122 | err = stop() 123 | if err != nil { 124 | log.Error().Err(err).Msg("error stopping service") 125 | os.Exit(1) 126 | } 127 | 128 | os.Exit(0) 129 | } 130 | -------------------------------------------------------------------------------- /cmd/testscanner/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023-2025 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "github.com/ZaparooProject/zaparoo-core/pkg/cli" 27 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 28 | "github.com/ZaparooProject/zaparoo-core/pkg/service" 29 | "github.com/rs/zerolog" 30 | "github.com/rs/zerolog/log" 31 | "io" 32 | "os" 33 | "os/signal" 34 | "syscall" 35 | ) 36 | 37 | // platform-agnostic build with a cut down platform def and modified copy of 38 | // launchers taken from mister. just using it to test out media scanner 39 | // performance with a decent number of test files 40 | 41 | func main() { 42 | pl := &Platform{} 43 | flags := cli.SetupFlags() 44 | 45 | flags.Pre(pl) 46 | 47 | defaultCfg := config.BaseDefaults 48 | defaultCfg.DebugLogging = true 49 | 50 | cfg := cli.Setup( 51 | pl, defaultCfg, 52 | []io.Writer{zerolog.ConsoleWriter{Out: os.Stdout}}, 53 | ) 54 | 55 | defer func() { 56 | if err := recover(); err != nil { 57 | _, _ = fmt.Fprintf(os.Stderr, "Panic: %s\n", err) 58 | log.Fatal().Msgf("panic: %v", err) 59 | } 60 | }() 61 | 62 | flags.Post(cfg, pl) 63 | 64 | stopSvc, err := service.Start(pl, cfg) 65 | if err != nil { 66 | log.Error().Msgf("error starting service: %s", err) 67 | _, _ = fmt.Fprintf(os.Stderr, "Error starting service: %s\n", err) 68 | os.Exit(1) 69 | } 70 | 71 | sigs := make(chan os.Signal, 1) 72 | defer close(sigs) 73 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 74 | 75 | exit := make(chan bool, 1) 76 | defer close(exit) 77 | 78 | select { 79 | case <-sigs: 80 | case <-exit: 81 | } 82 | 83 | err = stopSvc() 84 | if err != nil { 85 | log.Error().Msgf("error stopping service: %s", err) 86 | os.Exit(1) 87 | } 88 | 89 | os.Exit(0) 90 | } 91 | -------------------------------------------------------------------------------- /cmd/windows/setup.iss.tmpl: -------------------------------------------------------------------------------- 1 | #define MyAppName "Zaparoo Core" 2 | #define MyAppPublisher "Zaparoo" 3 | #define MyAppURL "https://zaparoo.org" 4 | #define MyAppExeName "Zaparoo.exe" 5 | 6 | [Setup] 7 | AppId={{"{{"}}0BB9CB87-754A-4FEF-8238-6D7F47CB0B14} 8 | AppName={#MyAppName} 9 | AppVerName=Zaparoo Core 10 | VersionInfoVersion={{.Version}} 11 | AppPublisher={#MyAppPublisher} 12 | AppPublisherURL={#MyAppURL} 13 | AppSupportURL={#MyAppURL}/support 14 | AppUpdatesURL={#MyAppURL} 15 | PrivilegesRequired=lowest 16 | PrivilegesRequiredOverridesAllowed=dialog 17 | DefaultDirName={autopf}\{#MyAppName} 18 | DefaultGroupName={#MyAppName} 19 | AllowNoIcons=yes 20 | OutputBaseFilename=zaparoo-{{.Arch}}-{{.Version}}-setup 21 | Compression=lzma 22 | SolidCompression=yes 23 | WizardStyle=modern 24 | {{if .ArchitecturesAllowed}}ArchitecturesAllowed={{.ArchitecturesAllowed}}{{end}} 25 | {{if .ArchitecturesInstall64}}ArchitecturesInstallIn64BitMode={{.ArchitecturesInstall64}}{{end}} 26 | SetupIconFile=icon.ico 27 | UninstallDisplayIcon={app}\{#MyAppExeName} 28 | VersionInfoDescription=Zaparoo Core 29 | VersionInfoCopyright=Copyright © {{.Year}} Contributors to the Zaparoo project 30 | VersionInfoProductName=Zaparoo Core 31 | 32 | [Languages] 33 | Name: "english"; MessagesFile: "compiler:Default.isl" 34 | 35 | [Tasks] 36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 37 | Name: "runonstartup"; Description: "Run Zaparoo Core on startup"; GroupDescription: "Startup options:" 38 | Name: "addtopath"; Description: "Add Zaparoo Core to PATH"; GroupDescription: "System:" 39 | 40 | [Registry] 41 | Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; \ 42 | ValueType: string; ValueName: "ZaparooCore"; ValueData: """{app}\Zaparoo.exe"""; \ 43 | Tasks: runonstartup; Flags: uninsdeletevalue 44 | Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; \ 45 | ValueData: "{olddata};{app}"; Tasks: addtopath; \ 46 | Check: NeedsAddPath(ExpandConstant('{app}')) 47 | 48 | [Files] 49 | Source: "{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion 50 | Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion 51 | Source: "README.txt"; DestDir: "{app}"; Flags: ignoreversion 52 | 53 | [Icons] 54 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 55 | Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" 56 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 57 | 58 | [Run] 59 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 60 | Filename: "{app}\README.txt"; Description: "View README"; Flags: shellexec postinstall skipifsilent 61 | 62 | [UninstallRun] 63 | Filename: "taskkill.exe"; Parameters: "/F /IM ""{#MyAppExeName}"""; Flags: runhidden; RunOnceId: "KillService" 64 | 65 | [CustomMessages] 66 | LaunchAfterInstall=&Launch Zaparoo Core after installation 67 | 68 | [Code] 69 | function NeedsAddPath(Param: string): boolean; 70 | var 71 | OrigPath: string; 72 | begin 73 | if not RegQueryStringValue(HKEY_CURRENT_USER, 74 | 'Environment', 75 | 'Path', OrigPath) 76 | then begin 77 | Result := True; 78 | exit; 79 | end; 80 | Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0; 81 | end; 82 | -------------------------------------------------------------------------------- /cmd/windows/winres/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/cmd/windows/winres/icon.ico -------------------------------------------------------------------------------- /cmd/windows/winres/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/cmd/windows/winres/icon.png -------------------------------------------------------------------------------- /cmd/windows/winres/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/cmd/windows/winres/icon16.png -------------------------------------------------------------------------------- /cmd/windows/winres/winres.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "RT_GROUP_ICON": { 3 | "APP": { 4 | "0000": "icon.ico" 5 | } 6 | }, 7 | "RT_MANIFEST": { 8 | "#1": { 9 | "0409": { 10 | "description": "Zaparoo Core", 11 | "minimum-os": "win7", 12 | "execution-level": "as invoker", 13 | "ui-access": false, 14 | "auto-elevate": false, 15 | "dpi-awareness": "per monitor v2", 16 | "disable-theming": false, 17 | "disable-window-filtering": false, 18 | "high-resolution-scrolling-aware": false, 19 | "ultra-high-resolution-scrolling-aware": false, 20 | "long-path-aware": true, 21 | "printer-driver-isolation": false, 22 | "gdi-scaling": false, 23 | "segment-heap": false, 24 | "use-common-controls-v6": true 25 | } 26 | } 27 | }, 28 | "RT_VERSION": { 29 | "#1": { 30 | "0000": { 31 | "fixed": { 32 | "file_version": "{{.Version}}.0", 33 | "product_version": "{{.Version}}.0" 34 | }, 35 | "info": { 36 | "0409": { 37 | "CompanyName": "Zaparoo", 38 | "FileDescription": "Zaparoo Core", 39 | "FileVersion": "{{.Version}}", 40 | "InternalName": "zaparoo-core", 41 | "LegalCopyright": "Copyright © {{.Year}} Contributors to the Zaparoo project", 42 | "LegalTrademarks": "Zaparoo name and logos are trademarks of Wizzo Pty Ltd", 43 | "OriginalFilename": "Zaparoo.exe", 44 | "ProductName": "Zaparoo Core", 45 | "ProductVersion": "{{.Version}}" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Developer Guide 2 | 3 | ## Environment 4 | 5 | Zaparoo Core is written in Go, uses Task for build scripts, and Docker for building all platforms. 6 | 7 | Build scripts work on Linux, Mac and Windows (natively or WSL). Just make sure all dependencies are installed with Go, Task and Docker binaries available in your path. 8 | 9 | ### Dependencies 10 | 11 | - [Go](https://go.dev/) 12 | 13 | Version 1.23 or newer. The build script assumes your Go path is in the default location, for caching between Docker build environments: `$HOME/go` 14 | 15 | - [Task](https://taskfile.dev/) 16 | - [Docker](https://www.docker.com/) 17 | 18 | On Linux, enable cross-platform builds with something like: `apt install qemu binfmt-support qemu-user-static` 19 | 20 | On Mac and Windows, Docker Desktop comes with everything you need already. If you're using WSL, make sure it's using Docker from the host machine. 21 | 22 | ## Building 23 | 24 | To start, you can run `go mod download` from the root of the project folder. This will download all dependencies used by the project. Builds automatically do this, but running it now will stop your editor from complaining about missing modules. 25 | 26 | All build steps are done with the `task` command run from the root of the project folder. Run `task --list-all` by itself to see a list of available commands. 27 | 28 | Built binaries will be created in the `_build` directory under its appropriate platform and architecture subdirectory. 29 | 30 | These are the important commands: 31 | 32 | - `task :build-` 33 | 34 | Complete a full build for the given platform and architecture. This will also automatically create any necessary Docker images. 35 | 36 | - `task :deploy-` 37 | 38 | Some builds also have a helper command to automatically make a new build, transfer it to a remote device and remotely restart the service running. For example, to enable this for the MiSTer ARM build, add `MISTER_IP=1.2.3.4` to a `.env` file in the root of the project and then run `task mister:deploy-arm`. 39 | 40 | ### Direct Builds 41 | 42 | Core can be built directly on the host using the `task build` command, but will require some extra dependencies in the environment depending on the platform, to satisfy the Cgo dependency. Docker is not required. 43 | 44 | #### Linux 45 | 46 | Linux is the most complex because it uses a custom build of libnfc. Check a Dockerfile like `scripts/linux_amd64/Dockerfile` for full details of setting up the environment. 47 | 48 | You will need: 49 | 50 | - Standard build tools (GCC) 51 | - libnfc build dependencies (check Dockerfile) 52 | - Latest commit of [libnfc repo](https://github.com/nfc-tools/libnfc) 53 | 54 | Once these are installed and set up, you can just run `task build` for a generic Linux desktop build or `PLATFORM= task build` for one of the variant platforms. 55 | 56 | #### Windows 57 | 58 | Windows doesn't use libnfc so doesn't have as many dependencies, but does need a compiler available for sqlite. 59 | 60 | You will need: 61 | 62 | - [TDM-GCC](https://jmeubank.github.io/tdm-gcc/) 63 | 64 | Then you can build with `NO_LIBNFC=true task build`. The `CC` and `CXX` environment variables can also be set if you need to specify the path to TDM-GCC. 65 | 66 | #### Mac 67 | 68 | Mac also doesn't use libnfc but does need Xcode for sqlite and some native UI elements. It also cannot produce static builds which is the default. 69 | 70 | You will need: 71 | 72 | - Install Xcode from the App Store 73 | 74 | Then build with `NO_LIBNFC=true NO_STATIC=true task build`. This will produce a binary for your host's architecture, not a universal binary or .app folder. 75 | 76 | ## Testing 77 | 78 | When changing the application behavior, in particular the reader loop, some testing is required. The [Scan Behavior checklist](./scan-behavior) contains a list of expected behavior for the application under certain conditions. It is useful to test them and ensure we didn't break any flows. 79 | -------------------------------------------------------------------------------- /docs/scan-behavior.md: -------------------------------------------------------------------------------- 1 | # Scan Behavior 2 | 3 | This is the expected behavior of scanning tokens. 4 | 5 | --- 6 | 7 | ## Default Behavior: `readers.scan.mode='tap'` 8 | 9 | When `readers.scan.mode='tap'` (default setting) and a game is launched using a card: 10 | 11 | - [ ] Removing the card from the reader won't close the game. 12 | - [ ] Leaving the card on the reader will have no effect. 13 | - [ ] Tapping another card to launch a game will start the new game without returning to the core menu. 14 | - [ ] Tapping the same card will reload the game from the beginning. 15 | - [ ] Tapping a command like `input.coin` will execute the command without interrupting the game. 16 | - [ ] Exiting the game manually through the internal menu will reset the state, allowing you to tap any card to launch a different game. 17 | - [ ] Exiting the game manually while the card remains on the reader will not cause the game to relaunch once in the menu. 18 | 19 | --- 20 | 21 | ## Behavior: `readers.scan.mode='hold'` and `readers.scan.exit_delay=0.0` 22 | 23 | When `readers.scan.mode='hold'` with `readers.scan.exit_delay=0.0` and a game is launched using a card: 24 | 25 | - [ ] Removing the card from the reader will immediately close the game. 26 | - [ ] Exiting the game manually while the card is still on the reader will not cause the game to relaunch when returning to the menu. 27 | - [ ] Exiting the game manually via the internal menu and then removing the card won't trigger a core menu reload. 28 | 29 | --- 30 | 31 | ## Behavior: `readers.scan.mode='hold'` and `readers.scan.exit_delay=N` 32 | 33 | When `readers.scan.mode='hold'` with `readers.scan.exit_delay=N` and a game is launched using a card: 34 | 35 | - [ ] Removing the card from the reader will close the game after **N seconds**. 36 | - [ ] Removing the card and reinserting it before the N-second countdown ends will not interrupt the ongoing game. 37 | - [ ] Removing the card and tapping a different game card will immediately launch the other game. 38 | - [ ] Removing the card and tapping a command card will execute the command and reset the countdown timer. You can repeatedly tap various command cards without changing the game, resetting the timer each time, and then reinsert the original game card to continue the session. 39 | - [ ] Exiting the game manually while the card is still on the reader will not cause the game to relaunch when returning to the menu. 40 | - [ ] Exiting the game manually via the internal menu and then removing the card won't trigger a core menu reload. 41 | - [ ] Exiting the game manually during the N-second countdown cancels the countdown and returns to the menu. 42 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ZaparooProject/zaparoo-core 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.6 6 | 7 | require ( 8 | fyne.io/systray v1.11.0 9 | github.com/adrg/xdg v0.5.3 10 | github.com/andygrunwald/vdf v1.1.0 11 | github.com/bendahl/uinput v1.7.0 12 | github.com/clausecker/nfc/v2 v2.1.4 13 | github.com/ebfe/scard v0.0.0-20230420082256-7db3f9b7c8a7 14 | github.com/fsnotify/fsnotify v1.6.0 15 | github.com/gdamore/tcell/v2 v2.8.1 16 | github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 17 | github.com/go-chi/chi/v5 v5.0.12 18 | github.com/go-chi/cors v1.2.1 19 | github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d 20 | github.com/google/uuid v1.6.0 21 | github.com/gorilla/websocket v1.5.3 22 | github.com/hsanjuan/go-ndef v0.0.1 23 | github.com/mattn/go-sqlite3 v1.14.28 24 | github.com/nixinwang/dialog v0.0.0-20240524023314-b4bad92eff4d 25 | github.com/olahol/melody v1.2.1 26 | github.com/pelletier/go-toml/v2 v2.2.3 27 | github.com/pressly/goose/v3 v3.24.3 28 | github.com/rivo/tview v0.0.0-20250501113434-0c592cd31026 29 | github.com/rs/zerolog v1.34.0 30 | github.com/stretchr/testify v1.10.0 31 | github.com/wizzomafizzo/mrext v0.1.3 32 | go.bug.st/serial v1.6.2 33 | go.etcd.io/bbolt v1.4.0 34 | golang.design/x/clipboard v0.7.0 35 | golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 36 | golang.org/x/sys v0.33.0 37 | golang.org/x/text v0.25.0 38 | gopkg.in/ini.v1 v1.67.0 39 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 40 | ) 41 | 42 | require ( 43 | github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d // indirect 44 | github.com/creack/goselect v0.1.2 // indirect 45 | github.com/davecgh/go-spew v1.1.1 // indirect 46 | github.com/gdamore/encoding v1.0.1 // indirect 47 | github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect 48 | github.com/godbus/dbus/v5 v5.1.0 // indirect 49 | github.com/libp2p/zeroconf/v2 v2.2.0 // indirect 50 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 51 | github.com/mattn/go-colorable v0.1.13 // indirect 52 | github.com/mattn/go-isatty v0.0.20 // indirect 53 | github.com/mattn/go-runewidth v0.0.16 // indirect 54 | github.com/mfridman/interpolate v0.0.2 // indirect 55 | github.com/miekg/dns v1.1.55 // indirect 56 | github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect 57 | github.com/pkg/errors v0.9.1 // indirect 58 | github.com/pmezard/go-difflib v1.0.0 // indirect 59 | github.com/rivo/uniseg v0.4.7 // indirect 60 | github.com/sethvargo/go-retry v0.3.0 // indirect 61 | github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect 62 | github.com/txn2/txeh v1.4.0 // indirect 63 | go.uber.org/multierr v1.11.0 // indirect 64 | golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect 65 | golang.org/x/image v0.20.0 // indirect 66 | golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect 67 | golang.org/x/mod v0.24.0 // indirect 68 | golang.org/x/net v0.40.0 // indirect 69 | golang.org/x/sync v0.14.0 // indirect 70 | golang.org/x/term v0.32.0 // indirect 71 | golang.org/x/tools v0.33.0 // indirect 72 | gopkg.in/yaml.v3 v3.0.1 // indirect 73 | ) 74 | -------------------------------------------------------------------------------- /pkg/api/methods/history.go: -------------------------------------------------------------------------------- 1 | package methods 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models" 7 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models/requests" 8 | "github.com/rs/zerolog/log" 9 | ) 10 | 11 | func HandleTokens(env requests.RequestEnv) (any, error) { 12 | log.Info().Msg("received tokens request") 13 | 14 | resp := models.TokensResponse{ 15 | Active: make([]models.TokenResponse, 0), 16 | } 17 | 18 | active := env.State.GetActiveCard() 19 | if !active.ScanTime.IsZero() { 20 | resp.Active = append(resp.Active, models.TokenResponse{ 21 | Type: active.Type, 22 | UID: active.UID, 23 | Text: active.Text, 24 | Data: active.Data, 25 | ScanTime: active.ScanTime, 26 | }) 27 | } 28 | 29 | last := env.State.GetLastScanned() 30 | if !last.ScanTime.IsZero() { 31 | resp.Last = &models.TokenResponse{ 32 | Type: last.Type, 33 | UID: last.UID, 34 | Text: last.Text, 35 | Data: last.Data, 36 | ScanTime: last.ScanTime, 37 | } 38 | } 39 | 40 | return resp, nil 41 | } 42 | 43 | func HandleHistory(env requests.RequestEnv) (any, error) { 44 | log.Info().Msg("received history request") 45 | 46 | entries, err := env.Database.UserDB.GetHistory(0) 47 | if err != nil { 48 | log.Error().Err(err).Msgf("error getting history") 49 | return nil, errors.New("error getting history") 50 | } 51 | 52 | resp := models.HistoryResponse{ 53 | Entries: make([]models.HistoryResponseEntry, len(entries)), 54 | } 55 | 56 | for i, e := range entries { 57 | resp.Entries[i] = models.HistoryResponseEntry{ 58 | Time: e.Time, 59 | Type: e.Type, 60 | UID: e.TokenID, 61 | Text: e.TokenValue, 62 | Data: e.TokenData, 63 | Success: e.Success, 64 | } 65 | } 66 | 67 | return resp, nil 68 | } 69 | -------------------------------------------------------------------------------- /pkg/api/methods/readers.go: -------------------------------------------------------------------------------- 1 | package methods 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | 7 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models" 8 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models/requests" 9 | "github.com/rs/zerolog/log" 10 | ) 11 | 12 | func HandleReaderWrite(env requests.RequestEnv) (any, error) { 13 | log.Info().Msg("received reader write request") 14 | 15 | if len(env.Params) == 0 { 16 | return nil, ErrMissingParams 17 | } 18 | 19 | var params models.ReaderWriteParams 20 | err := json.Unmarshal(env.Params, ¶ms) 21 | if err != nil { 22 | return nil, ErrInvalidParams 23 | } 24 | 25 | rs := env.State.ListReaders() 26 | if len(rs) == 0 { 27 | return nil, errors.New("no readers connected") 28 | } 29 | 30 | rid := rs[0] 31 | lt := env.State.GetLastScanned() 32 | 33 | if !lt.ScanTime.IsZero() && !lt.FromAPI { 34 | rid = lt.Source 35 | } 36 | 37 | reader, ok := env.State.GetReader(rid) 38 | if !ok || reader == nil { 39 | return nil, errors.New("reader not connected: " + rs[0]) 40 | } 41 | 42 | t, err := reader.Write(params.Text) 43 | if err != nil { 44 | log.Error().Err(err).Msg("error writing to reader") 45 | return nil, errors.New("error writing to reader") 46 | } 47 | 48 | if t != nil { 49 | env.State.SetWroteToken(t) 50 | } 51 | 52 | return nil, nil 53 | } 54 | 55 | func HandleReaderWriteCancel(env requests.RequestEnv) (any, error) { 56 | log.Info().Msg("received reader write cancel request") 57 | 58 | rs := env.State.ListReaders() 59 | if len(rs) == 0 { 60 | return nil, errors.New("no readers connected") 61 | } 62 | 63 | rid := rs[0] 64 | reader, ok := env.State.GetReader(rid) 65 | if !ok || reader == nil { 66 | return nil, errors.New("reader not connected: " + rs[0]) 67 | } 68 | 69 | reader.CancelWrite() 70 | 71 | return nil, nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/api/methods/systems.go: -------------------------------------------------------------------------------- 1 | package methods 2 | 3 | import ( 4 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models/requests" 6 | "github.com/ZaparooProject/zaparoo-core/pkg/assets" 7 | "github.com/ZaparooProject/zaparoo-core/pkg/database/systemdefs" 8 | "github.com/rs/zerolog/log" 9 | ) 10 | 11 | func HandleSystems(env requests.RequestEnv) (any, error) { 12 | log.Info().Msg("received systems request") 13 | 14 | indexed, err := env.Database.MediaDB.IndexedSystems() 15 | if err != nil { 16 | log.Error().Err(err).Msgf("error getting indexed systems") 17 | indexed = []string{} 18 | } 19 | 20 | if len(indexed) == 0 { 21 | log.Warn().Msg("no indexed systems found") 22 | } 23 | 24 | respSystems := make([]models.System, 0) 25 | 26 | for _, id := range indexed { 27 | system, err := systemdefs.GetSystem(id) 28 | if err != nil { 29 | log.Error().Err(err).Msgf("error getting system: %s", id) 30 | continue 31 | } 32 | 33 | sr := models.System{ 34 | Id: system.ID, 35 | } 36 | 37 | sm, err := assets.GetSystemMetadata(id) 38 | if err != nil { 39 | log.Error().Err(err).Msgf("error getting system metadata: %s", id) 40 | } 41 | 42 | sr.Name = sm.Name 43 | sr.Category = sm.Category 44 | 45 | respSystems = append(respSystems, sr) 46 | } 47 | 48 | return models.SystemsResponse{ 49 | Systems: respSystems, 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/api/methods/utils.go: -------------------------------------------------------------------------------- 1 | package methods 2 | 3 | import ( 4 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models/requests" 6 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 7 | "github.com/rs/zerolog/log" 8 | ) 9 | 10 | func HandleVersion(env requests.RequestEnv) (any, error) { 11 | log.Info().Msg("received version request") 12 | return models.VersionResponse{ 13 | Version: config.AppVersion, 14 | Platform: env.Platform.ID(), 15 | }, nil 16 | } 17 | -------------------------------------------------------------------------------- /pkg/api/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/google/uuid" 6 | ) 7 | 8 | const ( 9 | NotificationReadersConnected = "readers.added" 10 | NotificationReadersDisconnected = "readers.removed" 11 | NotificationRunning = "running" 12 | NotificationTokensAdded = "tokens.added" 13 | NotificationTokensRemoved = "tokens.removed" 14 | NotificationStopped = "media.stopped" 15 | NotificationStarted = "media.started" 16 | NotificationMediaIndexing = "media.indexing" // TODO: rename to generating 17 | ) 18 | 19 | const ( 20 | MethodLaunch = "launch" // DEPRECATED 21 | MethodRun = "run" 22 | MethodRunScript = "run.script" 23 | MethodStop = "stop" 24 | MethodTokens = "tokens" 25 | MethodMedia = "media" 26 | MethodMediaGenerate = "media.generate" 27 | MethodMediaIndex = "media.index" // DEPRECATED 28 | MethodMediaSearch = "media.search" 29 | MethodMediaActive = "media.active" 30 | MethodMediaActiveUpdate = "media.active.update" 31 | MethodSettings = "settings" 32 | MethodSettingsUpdate = "settings.update" 33 | MethodSettingsReload = "settings.reload" 34 | MethodClients = "clients" 35 | MethodClientsNew = "clients.new" 36 | MethodClientsDelete = "clients.delete" 37 | MethodSystems = "systems" 38 | MethodHistory = "tokens.history" 39 | MethodMappings = "mappings" 40 | MethodMappingsNew = "mappings.new" 41 | MethodMappingsDelete = "mappings.delete" 42 | MethodMappingsUpdate = "mappings.update" 43 | MethodMappingsReload = "mappings.reload" 44 | MethodReadersWrite = "readers.write" 45 | MethodReadersWriteCancel = "readers.write.cancel" 46 | MethodVersion = "version" 47 | ) 48 | 49 | type Notification struct { 50 | Method string 51 | Params json.RawMessage 52 | } 53 | 54 | type RequestObject struct { 55 | JSONRPC string `json:"jsonrpc"` 56 | ID *uuid.UUID `json:"id,omitempty"` 57 | Method string `json:"method"` 58 | Params json.RawMessage `json:"params,omitempty"` 59 | } 60 | 61 | type ErrorObject struct { 62 | Code int `json:"code"` 63 | Message string `json:"message"` 64 | } 65 | 66 | type ResponseObject struct { 67 | JSONRPC string `json:"jsonrpc"` 68 | ID uuid.UUID `json:"id"` 69 | Result any `json:"result"` 70 | Error *ErrorObject `json:"error,omitempty"` 71 | } 72 | 73 | // ResponseErrorObject exists for sending errors, so we can omit result from 74 | // the response, but so nil responses are still returned when using the main 75 | // ResponseObject. 76 | type ResponseErrorObject struct { 77 | JSONRPC string `json:"jsonrpc"` 78 | ID uuid.UUID `json:"id"` 79 | Error *ErrorObject `json:"error"` 80 | } 81 | -------------------------------------------------------------------------------- /pkg/api/models/params.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/ZaparooProject/zaparoo-core/pkg/zapscript/models" 4 | 5 | type SearchParams struct { 6 | Query string `json:"query"` 7 | Systems *[]string `json:"systems"` 8 | MaxResults *int `json:"maxResults"` 9 | } 10 | 11 | type MediaIndexParams struct { 12 | Systems *[]string `json:"systems"` 13 | } 14 | 15 | type RunParams struct { 16 | Type *string `json:"type"` 17 | UID *string `json:"uid"` 18 | Text *string `json:"text"` 19 | Data *string `json:"data"` 20 | Unsafe bool `json:"unsafe"` 21 | } 22 | 23 | type RunScriptParams struct { 24 | ZapScript int `json:"zapscript"` 25 | Name *string `json:"name"` 26 | Cmds []models.ZapScriptCmd `json:"cmds"` 27 | Unsafe bool `json:"unsafe"` 28 | } 29 | 30 | type AddMappingParams struct { 31 | Label string `json:"label"` 32 | Enabled bool `json:"enabled"` 33 | Type string `json:"type"` 34 | Match string `json:"match"` 35 | Pattern string `json:"pattern"` 36 | Override string `json:"override"` 37 | } 38 | 39 | type DeleteMappingParams struct { 40 | Id int `json:"id"` 41 | } 42 | 43 | type UpdateMappingParams struct { 44 | Id int `json:"id"` 45 | Label *string `json:"label"` 46 | Enabled *bool `json:"enabled"` 47 | Type *string `json:"type"` 48 | Match *string `json:"match"` 49 | Pattern *string `json:"pattern"` 50 | Override *string `json:"override"` 51 | } 52 | 53 | type ReaderWriteParams struct { 54 | Text string `json:"text"` 55 | } 56 | 57 | type UpdateSettingsParams struct { 58 | RunZapScript *bool `json:"runZapScript"` 59 | DebugLogging *bool `json:"debugLogging"` 60 | AudioScanFeedback *bool `json:"audioScanFeedback"` 61 | ReadersAutoDetect *bool `json:"readersAutoDetect"` 62 | ReadersScanMode *string `json:"readersScanMode"` 63 | ReadersScanExitDelay *float32 `json:"readersScanExitDelay"` 64 | ReadersScanIgnoreSystem *[]string `json:"readersScanIgnoreSystems"` 65 | } 66 | 67 | type NewClientParams struct { 68 | Name string `json:"name"` 69 | } 70 | 71 | type DeleteClientParams struct { 72 | Id string `json:"id"` 73 | } 74 | 75 | type MediaStartedParams struct { 76 | SystemID string `json:"systemId"` 77 | SystemName string `json:"systemName"` 78 | MediaPath string `json:"mediaPath"` 79 | MediaName string `json:"mediaName"` 80 | } 81 | 82 | type UpdateActiveMediaParams struct { 83 | SystemID string `json:"systemId"` 84 | MediaPath string `json:"mediaPath"` 85 | MediaName string `json:"mediaName"` 86 | } 87 | -------------------------------------------------------------------------------- /pkg/api/models/requests/requests.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 6 | "github.com/ZaparooProject/zaparoo-core/pkg/database" 7 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 8 | "github.com/ZaparooProject/zaparoo-core/pkg/service/state" 9 | "github.com/ZaparooProject/zaparoo-core/pkg/service/tokens" 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type RequestEnv struct { 14 | Platform platforms.Platform 15 | Config *config.Instance 16 | State *state.State 17 | Database *database.Database 18 | TokenQueue chan<- tokens.Token 19 | IsLocal bool 20 | ID uuid.UUID 21 | Params json.RawMessage 22 | } 23 | -------------------------------------------------------------------------------- /pkg/api/notifications/notifications.go: -------------------------------------------------------------------------------- 1 | package notifications 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models" 6 | "github.com/rs/zerolog/log" 7 | ) 8 | 9 | func sendNotification(ns chan<- models.Notification, method string, payload any) { 10 | if payload != nil { 11 | params, err := json.Marshal(payload) 12 | if err != nil { 13 | log.Error().Err(err).Msgf("error marshalling notification params: %s", method) 14 | return 15 | } 16 | ns <- models.Notification{ 17 | Method: method, 18 | Params: params, 19 | } 20 | } else { 21 | ns <- models.Notification{ 22 | Method: method, 23 | } 24 | } 25 | } 26 | 27 | func MediaIndexing(ns chan<- models.Notification, payload models.IndexingStatusResponse) { 28 | sendNotification(ns, models.NotificationMediaIndexing, payload) 29 | } 30 | 31 | func MediaStopped(ns chan<- models.Notification) { 32 | sendNotification(ns, models.NotificationStopped, nil) 33 | } 34 | 35 | func MediaStarted(ns chan<- models.Notification, payload models.MediaStartedParams) { 36 | sendNotification(ns, models.NotificationStarted, payload) 37 | } 38 | 39 | func TokensAdded(ns chan<- models.Notification, payload models.TokenResponse) { 40 | sendNotification(ns, models.NotificationTokensAdded, payload) 41 | } 42 | 43 | func TokensRemoved(ns chan<- models.Notification) { 44 | sendNotification(ns, models.NotificationTokensRemoved, nil) 45 | } 46 | 47 | func ReadersAdded(ns chan<- models.Notification, payload models.ReaderResponse) { 48 | sendNotification(ns, models.NotificationReadersConnected, payload) 49 | } 50 | 51 | func ReadersRemoved(ns chan<- models.Notification, payload models.ReaderResponse) { 52 | sendNotification(ns, models.NotificationReadersDisconnected, payload) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/assets/_app/README: -------------------------------------------------------------------------------- 1 | Copy the zaparoo-app web dist directory here. -------------------------------------------------------------------------------- /pkg/assets/assets.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "embed" 5 | "encoding/json" 6 | ) 7 | 8 | //go:embed _app 9 | var App embed.FS 10 | 11 | // SuccessSound Breviceps (https://freesound.org/people/Breviceps/sounds/445978/) 12 | // Licence: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication 13 | // 14 | //go:embed sounds/success.wav 15 | var SuccessSound []byte 16 | 17 | // FailSound PaulMorek (https://freesound.org/people/PaulMorek/sounds/330046/) 18 | // Licence: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication 19 | // 20 | //go:embed sounds/fail.wav 21 | var FailSound []byte 22 | 23 | //go:embed systems/* 24 | var Systems embed.FS 25 | 26 | type SystemMetadata struct { 27 | Id string `json:"id"` 28 | Name string `json:"name"` 29 | Category string `json:"category"` 30 | ReleaseDate string `json:"releaseDate"` 31 | Manufacturer string `json:"manufacturer"` 32 | } 33 | 34 | func GetSystemMetadata(system string) (SystemMetadata, error) { 35 | var metadata SystemMetadata 36 | 37 | data, err := Systems.ReadFile("systems/" + system + ".json") 38 | if err != nil { 39 | return metadata, err 40 | } 41 | 42 | err = json.Unmarshal(data, &metadata) 43 | return metadata, err 44 | } 45 | -------------------------------------------------------------------------------- /pkg/assets/sounds/fail.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/pkg/assets/sounds/fail.wav -------------------------------------------------------------------------------- /pkg/assets/sounds/success.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZaparooProject/zaparoo-core/a3a28b251d2ded8ddac8e33d9f41b856ffb5eb4c/pkg/assets/sounds/success.wav -------------------------------------------------------------------------------- /pkg/assets/systems/3DO.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3DO", 3 | "name": "3DO", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/3DS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3DS", 3 | "name": "3DS", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Nintendo" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/AcornAtom.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AcornAtom", 3 | "name": "Atom", 4 | "category": "Computer", 5 | "releaseDate": "1979-01-01", 6 | "manufacturer": "Acorn" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AcornElectron.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AcornElectron", 3 | "name": "Electron", 4 | "category": "Computer", 5 | "releaseDate": "1983-08-01", 6 | "manufacturer": "Acorn" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AdventureVision.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AdventureVision", 3 | "name": "Adventure Vision", 4 | "category": "Console", 5 | "releaseDate": "1982-01-01", 6 | "manufacturer": "Entex" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AliceMC10.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AliceMC10", 3 | "name": "Tandy MC-10", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "Tandy" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Amiga.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Amiga", 3 | "name": "Amiga", 4 | "category": "Computer", 5 | "releaseDate": "1985-07-23", 6 | "manufacturer": "Commodore" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Amiga1200.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Amiga1200", 3 | "name": "Amiga 1200", 4 | "category": "Computer", 5 | "releaseDate": "1992-10-21", 6 | "manufacturer": "Commodore" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Amiga500.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Amiga500", 3 | "name": "Amiga 500", 4 | "category": "Computer", 5 | "releaseDate": "1987-01-01", 6 | "manufacturer": "Commodore" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AmigaCD32.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AmigaCD32", 3 | "name": "Amiga CD32", 4 | "category": "Console", 5 | "releaseDate": "1993-09-17", 6 | "manufacturer": "Commodore" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Amstrad.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Amstrad", 3 | "name": "Amstrad CPC", 4 | "category": "Computer", 5 | "releaseDate": "1984-06-21", 6 | "manufacturer": "Amstrad" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AmstradPCW.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AmstradPCW", 3 | "name": "Amstrad PCW", 4 | "category": "Computer", 5 | "releaseDate": "1985-09-01", 6 | "manufacturer": "Amstrad" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Android.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Android", 3 | "name": "Android", 4 | "category": "Other", 5 | "releaseDate": "", 6 | "manufacturer": "Google" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Apogee.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Apogee", 3 | "name": "Apogee BK-01", 4 | "category": "Computer", 5 | "releaseDate": "1992-01-01", 6 | "manufacturer": "Apogee" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AppleI.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AppleI", 3 | "name": "Apple I", 4 | "category": "Computer", 5 | "releaseDate": "1976-04-01", 6 | "manufacturer": "Apple" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AppleII.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AppleII", 3 | "name": "Apple IIe", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "Apple" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Aquarius.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Aquarius", 3 | "name": "Mattel Aquarius", 4 | "category": "Computer", 5 | "releaseDate": "1983-06-01", 6 | "manufacturer": "Mattel" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Arcade.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Arcade", 3 | "name": "Arcade", 4 | "category": "Arcade", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Arcadia.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Arcadia", 3 | "name": "Arcadia 2001", 4 | "category": "Console", 5 | "releaseDate": "1982-01-01", 6 | "manufacturer": "Emerson" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Arduboy.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Arduboy", 3 | "name": "Arduboy", 4 | "category": "Other", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Astrocade.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Astrocade", 3 | "name": "Bally Astrocade", 4 | "category": "Console", 5 | "releaseDate": "1978-04-01", 6 | "manufacturer": "Bally" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Atari2600.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Atari2600", 3 | "name": "Atari 2600", 4 | "category": "Console", 5 | "releaseDate": "1977-09-11", 6 | "manufacturer": "Atari" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Atari5200.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Atari5200", 3 | "name": "Atari 5200", 4 | "category": "Console", 5 | "releaseDate": "1982-11-01", 6 | "manufacturer": "Atari" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Atari7800.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Atari7800", 3 | "name": "Atari 7800", 4 | "category": "Console", 5 | "releaseDate": "1986-05-01", 6 | "manufacturer": "Atari" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Atari800.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Atari800", 3 | "name": "Atari 800XL", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "Atari" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AtariLynx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AtariLynx", 3 | "name": "Atari Lynx", 4 | "category": "Handheld", 5 | "releaseDate": "1989-09-01", 6 | "manufacturer": "Atari" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/AtariXEGS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "AtariXEGS", 3 | "name": "Atari XEGS", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Atari" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/Atomiswave.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Atomiswave", 3 | "name": "Atomiswave", 4 | "category": "Arcade", 5 | "releaseDate": "2003-04-01", 6 | "manufacturer": "Sammy" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/BBCMicro.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "BBCMicro", 3 | "name": "BBC Micro/Master", 4 | "category": "Computer", 5 | "releaseDate": "1981-12-01", 6 | "manufacturer": "Acorn" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/BK0011M.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "BK0011M", 3 | "name": "BK0011M", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "Elektronika" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/C16.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "C16", 3 | "name": "Commodore 16", 4 | "category": "Computer", 5 | "releaseDate": "1984-07-01", 6 | "manufacturer": "Commodore" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/C64.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "C64", 3 | "name": "Commodore 64", 4 | "category": "Computer", 5 | "releaseDate": "1982-08-01", 6 | "manufacturer": "Commodore" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/CDI.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "CDI", 3 | "name": "CD-i", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/CasioPV1000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "CasioPV1000", 3 | "name": "Casio PV-1000", 4 | "category": "Console", 5 | "releaseDate": "1983-10-01", 6 | "manufacturer": "Casio" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/CasioPV2000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "CasioPV2000", 3 | "name": "Casio PV-2000", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "Casio" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/ChannelF.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ChannelF", 3 | "name": "Channel F", 4 | "category": "Console", 5 | "releaseDate": "1976-11-01", 6 | "manufacturer": "Fairchild" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Chip8.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Chip8", 3 | "name": "CHIP-8", 4 | "category": "Other", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/CoCo2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "CoCo2", 3 | "name": "TRS-80 CoCo 2", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "Tandy" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/ColecoVision.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ColecoVision", 3 | "name": "ColecoVision", 4 | "category": "Console", 5 | "releaseDate": "1982-08-01", 6 | "manufacturer": "Coleco" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/CreatiVision.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "CreatiVision", 3 | "name": "VTech CreatiVision", 4 | "category": "Console", 5 | "releaseDate": "1981-01-01", 6 | "manufacturer": "VTech" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/DAPHNE.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "DAPHNE", 3 | "name": "DAPHNE", 4 | "category": "Arcade", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/DOS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "DOS", 3 | "name": "PC (DOS)", 4 | "category": "Computer", 5 | "releaseDate": "1989-04-10", 6 | "manufacturer": "IBM" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Dreamcast.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Dreamcast", 3 | "name": "Dreamcast", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Sega" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/EDSAC.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "EDSAC", 3 | "name": "EDSAC", 4 | "category": "Computer", 5 | "releaseDate": "1949-01-01", 6 | "manufacturer": "Cambridge" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/FDS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "FDS", 3 | "name": "Famicom Disk System", 4 | "category": "Console", 5 | "releaseDate": "1986-02-21", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/GBA.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GBA", 3 | "name": "Gameboy Advance", 4 | "category": "Handheld", 5 | "releaseDate": "2001-03-21", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/GBA2P.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GBA2P", 3 | "name": "Gameboy Advance (2 Player)", 4 | "category": "Handheld", 5 | "releaseDate": "2001-03-21", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Galaksija.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Galaksija", 3 | "name": "Galaksija", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Gamate.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Gamate", 3 | "name": "Gamate", 4 | "category": "Handheld", 5 | "releaseDate": "1990-01-01", 6 | "manufacturer": "Bit Corporation" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/GameCom.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GameCom", 3 | "name": "Game.com", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Tiger" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/GameCube.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GameCube", 3 | "name": "GameCube", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Nintendo" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/GameGear.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GameGear", 3 | "name": "Game Gear", 4 | "category": "Handheld", 5 | "releaseDate": "1990-10-06", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/GameNWatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GameNWatch", 3 | "name": "Game \u0026 Watch", 4 | "category": "Handheld", 5 | "releaseDate": "1980-04-28", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Gameboy.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Gameboy", 3 | "name": "Gameboy", 4 | "category": "Handheld", 5 | "releaseDate": "1989-04-21", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Gameboy2P.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Gameboy2P", 3 | "name": "Gameboy (2 Player)", 4 | "category": "Handheld", 5 | "releaseDate": "1989-04-21", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/GameboyColor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "GameboyColor", 3 | "name": "Gameboy Color", 4 | "category": "Handheld", 5 | "releaseDate": "1998-10-21", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Genesis", 3 | "name": "Genesis", 4 | "category": "Console", 5 | "releaseDate": "1988-10-29", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Intellivision.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Intellivision", 3 | "name": "Intellivision", 4 | "category": "Console", 5 | "releaseDate": "1979-12-03", 6 | "manufacturer": "Mattel" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Interact.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Interact", 3 | "name": "Interact", 4 | "category": "Computer", 5 | "releaseDate": "1981-01-01", 6 | "manufacturer": "Interact" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Jaguar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Jaguar", 3 | "name": "Jaguar", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Atari" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/JaguarCD.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "JaguarCD", 3 | "name": "Jaguar CD", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Atari" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/Jupiter.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Jupiter", 3 | "name": "Jupiter Ace", 4 | "category": "Computer", 5 | "releaseDate": "1982-01-01", 6 | "manufacturer": "Jupiter" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Laser.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Laser", 3 | "name": "Laser 350/500/700", 4 | "category": "Computer", 5 | "releaseDate": "1984-01-01", 6 | "manufacturer": "Video Technology" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Lynx48.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Lynx48", 3 | "name": "Lynx 48/96K", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "Cambridge" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/MSX.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MSX", 3 | "name": "MSX", 4 | "category": "Computer", 5 | "releaseDate": "1983-06-01", 6 | "manufacturer": "Microsoft" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/MacOS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MacOS", 3 | "name": "Mac OS", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "Apple" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/MacPlus.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MacPlus", 3 | "name": "Macintosh Plus", 4 | "category": "Computer", 5 | "releaseDate": "1986-01-01", 6 | "manufacturer": "Apple" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/MasterSystem.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MasterSystem", 3 | "name": "Master System", 4 | "category": "Console", 5 | "releaseDate": "1985-10-20", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/MegaCD.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MegaCD", 3 | "name": "Sega CD", 4 | "category": "Console", 5 | "releaseDate": "1991-12-12", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/MegaDuck.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MegaDuck", 3 | "name": "Mega Duck", 4 | "category": "Handheld", 5 | "releaseDate": "1993-01-01", 6 | "manufacturer": "Watara" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Model3.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Model3", 3 | "name": "Model 3", 4 | "category": "Arcade", 5 | "releaseDate": "1996-01-01", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/MultiComp.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "MultiComp", 3 | "name": "MultiComp", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/NAOMI.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NAOMI", 3 | "name": "NAOMI", 4 | "category": "Arcade", 5 | "releaseDate": "1998-01-01", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/NAOMI2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NAOMI2", 3 | "name": "NAOMI 2", 4 | "category": "Arcade", 5 | "releaseDate": "2000-01-01", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/NDS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NDS", 3 | "name": "Nintendo DS", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Nintendo" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/NES.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NES", 3 | "name": "NES", 4 | "category": "Console", 5 | "releaseDate": "1985-10-18", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/NESMusic.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NESMusic", 3 | "name": "NES Music", 4 | "category": "Other", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/NeoGeo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NeoGeo", 3 | "name": "Neo Geo", 4 | "category": "Console", 5 | "releaseDate": "1990-01-01", 6 | "manufacturer": "SNK" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/NeoGeoCD.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NeoGeoCD", 3 | "name": "Neo Geo CD", 4 | "category": "Console", 5 | "releaseDate": "1994-09-09", 6 | "manufacturer": "SNK" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/NeoGeoPocket.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NeoGeoPocket", 3 | "name": "Neo Geo Pocket", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/NeoGeoPocketColor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "NeoGeoPocketColor", 3 | "name": "Neo Geo Pocket Color", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/Nintendo64.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Nintendo64", 3 | "name": "Nintendo 64", 4 | "category": "Console", 5 | "releaseDate": "1996-06-23", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Odyssey2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Odyssey2", 3 | "name": "Magnavox Odyssey2", 4 | "category": "Console", 5 | "releaseDate": "1978-09-01", 6 | "manufacturer": "Magnavox" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Orao.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Orao", 3 | "name": "Orao", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Oric.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Oric", 3 | "name": "Oric", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Ouya.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Ouya", 3 | "name": "Ouya", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } -------------------------------------------------------------------------------- /pkg/assets/systems/PC.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PC", 3 | "name": "PC", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PCFX.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PCFX", 3 | "name": "PC-FX", 4 | "category": "Console", 5 | "releaseDate": "1994-12-01", 6 | "manufacturer": "NEC" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PCXT.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PCXT", 3 | "name": "PC/XT", 4 | "category": "Computer", 5 | "releaseDate": "1983-01-01", 6 | "manufacturer": "IBM" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PDP1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PDP1", 3 | "name": "PDP-1", 4 | "category": "Computer", 5 | "releaseDate": "1960-01-01", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PET2001.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PET2001", 3 | "name": "Commodore PET 2001", 4 | "category": "Computer", 5 | "releaseDate": "1977-01-01", 6 | "manufacturer": "Commodore" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PMD85.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PMD85", 3 | "name": "PMD 85-2A", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PS2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PS2", 3 | "name": "Playstation 2", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Sony" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PS3.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PS3", 3 | "name": "Playstation 3", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Sony" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PS4.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PS4", 3 | "name": "Playstation 4", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Sony" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PS5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PS5", 3 | "name": "Playstation 5", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Sony" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PSP.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PSP", 3 | "name": "Playstation Portable", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Sony" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PSX.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PSX", 3 | "name": "Playstation", 4 | "category": "Console", 5 | "releaseDate": "1994-12-03", 6 | "manufacturer": "Sony" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PocketChallengeV2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PocketChallengeV2", 3 | "name": "Pocket Challenge V2", 4 | "category": "Handheld", 5 | "releaseDate": "2000-01-01", 6 | "manufacturer": "Benesse" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/PokemonMini.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "PokemonMini", 3 | "name": "Pokemon Mini", 4 | "category": "Handheld", 5 | "releaseDate": "2001-11-16", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/QL.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "QL", 3 | "name": "Sinclair QL", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/RX78.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "RX78", 3 | "name": "RX-78 Gundam", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SAMCoupe.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SAMCoupe", 3 | "name": "SAM Coupe", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SG1000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SG1000", 3 | "name": "SG-1000", 4 | "category": "Console", 5 | "releaseDate": "1983-07-15", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SNES.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SNES", 3 | "name": "SNES", 4 | "category": "Console", 5 | "releaseDate": "1990-11-21", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SNESMSU1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SNESMSU1", 3 | "name": "SNES MSU-1", 4 | "category": "Console", 5 | "releaseDate": "2012-05-25", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SNESMusic.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SNESMusic", 3 | "name": "SNES Music", 4 | "category": "Other", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SVI328.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SVI328", 3 | "name": "SV-328", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Saturn.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Saturn", 3 | "name": "Saturn", 4 | "category": "Console", 5 | "releaseDate": "1994-11-22", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/ScummVM.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ScummVM", 3 | "name": "ScummVM", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Sega32X.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Sega32X", 3 | "name": "Genesis 32X", 4 | "category": "Console", 5 | "releaseDate": "1994-11-21", 6 | "manufacturer": "Sega" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SeriesXS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SeriesXS", 3 | "name": "Xbox Series X/S", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Microsoft" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SordM5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SordM5", 3 | "name": "M5", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Specialist.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Specialist", 3 | "name": "Specialist/MX", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SuperGameboy.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SuperGameboy", 3 | "name": "Super Gameboy", 4 | "category": "Console", 5 | "releaseDate": "1994-06-14", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SuperGrafx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SuperGrafx", 3 | "name": "SuperGrafx", 4 | "category": "Console", 5 | "releaseDate": "1989-12-08", 6 | "manufacturer": "NEC" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/SuperVision.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SuperVision", 3 | "name": "SuperVision", 4 | "category": "Handheld", 5 | "releaseDate": "1992-01-01", 6 | "manufacturer": "Watara" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Switch.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Switch", 3 | "name": "Nintendo Switch", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/TI994A.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "TI994A", 3 | "name": "TI-99/4A", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/TRS80.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "TRS80", 3 | "name": "TRS-80", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/TSConf.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "TSConf", 3 | "name": "TS-Config", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/TatungEinstein.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "TatungEinstein", 3 | "name": "Tatung Einstein", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/TomyTutor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "TomyTutor", 3 | "name": "Tutor", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/TurboGrafx16.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "TurboGrafx16", 3 | "name": "TurboGrafx-16", 4 | "category": "Console", 5 | "releaseDate": "1987-10-30", 6 | "manufacturer": "NEC" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/TurboGrafx16CD.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "TurboGrafx16CD", 3 | "name": "TurboGrafx-16 CD", 4 | "category": "Console", 5 | "releaseDate": "1989-11-01", 6 | "manufacturer": "NEC" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/UK101.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "UK101", 3 | "name": "UK101", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/VC4000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VC4000", 3 | "name": "VC4000", 4 | "category": "Console", 5 | "releaseDate": "1978-01-01", 6 | "manufacturer": "Interton" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/VIC20.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VIC20", 3 | "name": "Commodore VIC-20", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Vector06C.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Vector06C", 3 | "name": "Vector-06C", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Vectrex.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Vectrex", 3 | "name": "Vectrex", 4 | "category": "Console", 5 | "releaseDate": "1982-11-01", 6 | "manufacturer": "GCE" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Video.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Video", 3 | "name": "Video", 4 | "category": "Other", 5 | "releaseDate": "1888-01-01", 6 | "manufacturer": "N/A" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/VirtualBoy.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "VirtualBoy", 3 | "name": "Virtual Boy", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Vita.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Vita", 3 | "name": "Playstation Vita", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Sony" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Wii.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Wii", 3 | "name": "Nintendo Wii", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/WiiU.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Wii U", 3 | "name": "Nintendo Wii U", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Nintendo" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Windows", 3 | "name": "Windows", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "Microsoft" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/WonderSwan.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "WonderSwan", 3 | "name": "WonderSwan", 4 | "category": "Handheld", 5 | "releaseDate": "1999-03-04", 6 | "manufacturer": "Bandai" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/WonderSwanColor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "WonderSwanColor", 3 | "name": "WonderSwan Color", 4 | "category": "Handheld", 5 | "releaseDate": "1999-12-30", 6 | "manufacturer": "Bandai" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/X68000.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "X68000", 3 | "name": "X68000", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Xbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Xbox", 3 | "name": "Xbox", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Microsoft" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/Xbox360.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Xbox360", 3 | "name": "Xbox 360", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Microsoft" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/XboxOne.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "Xbox One", 3 | "name": "Xbox One", 4 | "category": "Console", 5 | "releaseDate": "", 6 | "manufacturer": "Microsoft" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/ZX81.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ZX81", 3 | "name": "TS-1500", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/ZXNext.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ZXNext", 3 | "name": "ZX Spectrum Next", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/ZXSpectrum.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ZXSpectrum", 3 | "name": "ZX Spectrum", 4 | "category": "Computer", 5 | "releaseDate": "", 6 | "manufacturer": "" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/assets/systems/iOS.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "iOS", 3 | "name": "iOS", 4 | "category": "Other", 5 | "releaseDate": "", 6 | "manufacturer": "Apple" 7 | } 8 | -------------------------------------------------------------------------------- /pkg/config/app.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "time" 4 | 5 | var AppVersion = "DEVELOPMENT" 6 | 7 | const ( 8 | AppName = "zaparoo" 9 | MediaDbFile = "media.db" 10 | UserDbFile = "user.db" 11 | LogFile = "core.log" 12 | PidFile = "core.pid" 13 | CfgFile = "config.toml" 14 | UserDir = "user" 15 | ApiRequestTimeout = 30 * time.Second 16 | SuccessSoundFilename = "success.wav" 17 | FailSoundFilename = "fail.wav" 18 | ) 19 | -------------------------------------------------------------------------------- /pkg/config/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/config/migrate/iniconfig" 6 | "gopkg.in/ini.v1" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | func IniToToml(iniPath string) (config.Values, error) { 13 | // allow_commands is being purposely ignored and must be explicitly enabled 14 | // by the user after migration 15 | 16 | vals := config.BaseDefaults 17 | var iniVals iniconfig.UserConfig 18 | 19 | iniCfg, err := ini.ShadowLoad(iniPath) 20 | if err != nil { 21 | return vals, err 22 | } 23 | 24 | err = iniCfg.StrictMapTo(&iniVals) 25 | if err != nil { 26 | return vals, err 27 | } 28 | 29 | // readers 30 | for _, r := range iniVals.TapTo.Reader { 31 | ps := strings.SplitN(r, ":", 2) 32 | if len(ps) != 2 { 33 | continue 34 | } 35 | 36 | vals.Readers.Connect = append( 37 | vals.Readers.Connect, 38 | config.ReadersConnect{ 39 | Driver: ps[0], 40 | Path: ps[1], 41 | }, 42 | ) 43 | } 44 | 45 | // connection string 46 | conStr := iniVals.TapTo.ConnectionString 47 | if conStr != "" { 48 | ps := strings.SplitN(conStr, ":", 2) 49 | if len(ps) != 2 { 50 | vals.Readers.Connect = append( 51 | vals.Readers.Connect, 52 | config.ReadersConnect{ 53 | Driver: ps[0], 54 | Path: ps[1], 55 | }, 56 | ) 57 | } 58 | } 59 | 60 | // disable sounds 61 | vals.Audio.ScanFeedback = !iniVals.TapTo.DisableSounds 62 | 63 | // probe device 64 | vals.Readers.AutoDetect = iniVals.TapTo.ProbeDevice 65 | 66 | // exit game mode 67 | if iniVals.TapTo.ExitGame { 68 | vals.Readers.Scan.Mode = config.ScanModeHold 69 | } else { 70 | vals.Readers.Scan.Mode = config.ScanModeTap 71 | } 72 | 73 | // exit game blocklist 74 | vals.Readers.Scan.IgnoreSystem = iniVals.TapTo.ExitGameBlocklist 75 | 76 | // exit game delay 77 | vals.Readers.Scan.ExitDelay = float32(iniVals.TapTo.ExitGameDelay) 78 | 79 | // debug 80 | vals.DebugLogging = iniVals.TapTo.Debug 81 | 82 | // systems - games folder 83 | vals.Launchers.IndexRoot = iniVals.Systems.GamesFolder 84 | 85 | // systems - set core 86 | for _, v := range iniVals.Systems.SetCore { 87 | ps := strings.SplitN(v, ":", 2) 88 | if len(ps) != 2 { 89 | continue 90 | } 91 | 92 | vals.Systems.Default = append( 93 | vals.Systems.Default, 94 | config.SystemsDefault{ 95 | System: ps[0], 96 | Launcher: ps[1], 97 | }, 98 | ) 99 | } 100 | 101 | // launchers - allow file 102 | for _, v := range iniVals.Launchers.AllowFile { 103 | s := strings.ReplaceAll(v, "\\", "\\\\") 104 | s = "^" + s + "$" 105 | vals.Launchers.AllowFile = append(vals.Launchers.AllowFile, s) 106 | } 107 | 108 | // api - port 109 | port, err := strconv.Atoi(iniVals.Api.Port) 110 | if err == nil { 111 | if port != vals.Service.ApiPort { 112 | vals.Service.ApiPort = port 113 | } 114 | } 115 | 116 | // api - allow launch 117 | vals.Service.AllowRun = iniVals.Api.AllowLaunch 118 | for _, v := range iniVals.Api.AllowLaunch { 119 | s := "^" + v + "$" 120 | vals.Service.AllowRun = append(vals.Service.AllowRun, s) 121 | } 122 | 123 | return vals, nil 124 | } 125 | 126 | func Required(oldIni string, newToml string) bool { 127 | iniExists := false 128 | if _, err := os.Stat(oldIni); err == nil { 129 | iniExists = true 130 | } 131 | 132 | tomlExists := false 133 | if _, err := os.Stat(newToml); err == nil { 134 | tomlExists = true 135 | } 136 | 137 | return iniExists && !tomlExists 138 | } 139 | -------------------------------------------------------------------------------- /pkg/database/mediadb/migrations/20250605011734_init.sql: -------------------------------------------------------------------------------- 1 | -- +goose NO TRANSACTION 2 | -- +goose Up 3 | PRAGMA 4 | journal_mode = OFF; 5 | PRAGMA 6 | synchronous = OFF; 7 | 8 | create table DBConfig 9 | ( 10 | Name text PRIMARY KEY, 11 | Value text 12 | ); 13 | 14 | -- ROWID is an internal subject to change on vacuum 15 | -- DBID INTEGER PRIMARY KEY aliases ROWID and makes it 16 | -- persistent between vacuums 17 | 18 | create table Systems 19 | ( 20 | DBID INTEGER PRIMARY KEY, 21 | SystemID text unique not null, 22 | Name text not null 23 | ); 24 | 25 | create table MediaTitles 26 | ( 27 | DBID INTEGER PRIMARY KEY, 28 | SystemDBID integer not null, 29 | Slug text not null, 30 | Name text not null 31 | ); 32 | 33 | create table Media 34 | ( 35 | DBID INTEGER PRIMARY KEY, 36 | MediaTitleDBID integer not null, 37 | Path text not null 38 | ); 39 | 40 | create table TagTypes 41 | ( 42 | DBID INTEGER PRIMARY KEY, 43 | Type text unique not null 44 | ); 45 | 46 | create table Tags 47 | ( 48 | DBID INTEGER PRIMARY KEY, 49 | TypeDBID integer not null, 50 | Tag text not null 51 | ); 52 | 53 | create table MediaTags 54 | ( 55 | DBID INTEGER PRIMARY KEY, 56 | MediaDBID integer not null, 57 | TagDBID integer not null 58 | ); 59 | 60 | create table MediaTitleTags 61 | ( 62 | DBID INTEGER PRIMARY KEY, 63 | TagDBID integer not null, 64 | MediaTitleDBID integer not null 65 | ); 66 | 67 | create table SupportingMedia 68 | ( 69 | DBID INTEGER PRIMARY KEY, 70 | MediaTitleDBID integer not null, 71 | TypeTagDBID integer not null, 72 | Path string not null, 73 | ContentType text not null, 74 | Binary blob 75 | ); 76 | 77 | -- +goose Down 78 | drop table SupportingMedia; 79 | drop table MediaTitleTags; 80 | drop table MediaTags; 81 | drop table Tags; 82 | drop table TagTypes; 83 | drop table Media; 84 | drop table MediaTitles; 85 | drop table Systems; 86 | drop table DBConfig; 87 | -------------------------------------------------------------------------------- /pkg/database/userdb/mappings.go: -------------------------------------------------------------------------------- 1 | package userdb 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | "time" 8 | 9 | "github.com/ZaparooProject/zaparoo-core/pkg/database" 10 | "github.com/ZaparooProject/zaparoo-core/pkg/utils" 11 | ) 12 | 13 | const ( 14 | MappingTypeID = "id" 15 | MappingTypeValue = "value" 16 | MappingTypeData = "data" 17 | MatchTypeExact = "exact" 18 | MatchTypePartial = "partial" 19 | MatchTypeRegex = "regex" 20 | LegacyMappingTypeUID = "uid" 21 | LegacyMappingTypeText = "text" 22 | ) 23 | 24 | var AllowedMappingTypes = []string{ 25 | MappingTypeID, 26 | MappingTypeValue, 27 | MappingTypeData, 28 | } 29 | 30 | var AllowedMatchTypes = []string{ 31 | MatchTypeExact, 32 | MatchTypePartial, 33 | MatchTypeRegex, 34 | } 35 | 36 | func NormalizeID(uid string) string { 37 | uid = strings.TrimSpace(uid) 38 | uid = strings.ToLower(uid) 39 | uid = strings.ReplaceAll(uid, ":", "") 40 | return uid 41 | } 42 | 43 | func (db *UserDB) AddMapping(m database.Mapping) error { 44 | if !utils.Contains(AllowedMappingTypes, m.Type) { 45 | return fmt.Errorf("invalid mapping type: %s", m.Type) 46 | } 47 | 48 | if !utils.Contains(AllowedMatchTypes, m.Match) { 49 | return fmt.Errorf("invalid match type: %s", m.Match) 50 | } 51 | 52 | if m.Type == MappingTypeID { 53 | m.Pattern = NormalizeID(m.Pattern) 54 | } 55 | 56 | if m.Pattern == "" { 57 | return fmt.Errorf("missing pattern") 58 | } 59 | 60 | if m.Match == MatchTypeRegex { 61 | _, err := regexp.Compile(m.Pattern) 62 | if err != nil { 63 | return fmt.Errorf("invalid regex pattern: %s", m.Pattern) 64 | } 65 | } 66 | 67 | m.Added = time.Now().Unix() 68 | 69 | return sqlAddMapping(db.sql, m) 70 | } 71 | 72 | func (db *UserDB) GetMapping(id int64) (database.Mapping, error) { 73 | return sqlGetMapping(db.sql, id) 74 | } 75 | 76 | func (db *UserDB) DeleteMapping(id int64) error { 77 | return sqlDeleteMapping(db.sql, id) 78 | } 79 | 80 | func (db *UserDB) UpdateMapping(id int64, m database.Mapping) error { 81 | if !utils.Contains(AllowedMappingTypes, m.Type) { 82 | return fmt.Errorf("invalid mapping type: %s", m.Type) 83 | } 84 | 85 | if !utils.Contains(AllowedMatchTypes, m.Match) { 86 | return fmt.Errorf("invalid match type: %s", m.Match) 87 | } 88 | 89 | if m.Type == MappingTypeID { 90 | m.Pattern = NormalizeID(m.Pattern) 91 | } 92 | 93 | if m.Pattern == "" { 94 | return fmt.Errorf("missing pattern") 95 | } 96 | 97 | if m.Match == MatchTypeRegex { 98 | _, err := regexp.Compile(m.Pattern) 99 | if err != nil { 100 | return fmt.Errorf("invalid regex pattern: %s", m.Pattern) 101 | } 102 | } 103 | 104 | return sqlUpdateMapping(db.sql, id, m) 105 | } 106 | 107 | func (db *UserDB) GetAllMappings() ([]database.Mapping, error) { 108 | return sqlGetAllMappings(db.sql) 109 | } 110 | 111 | func (db *UserDB) GetEnabledMappings() ([]database.Mapping, error) { 112 | return sqlGetEnabledMappings(db.sql) 113 | } 114 | -------------------------------------------------------------------------------- /pkg/database/userdb/migrations/20250605021915_init.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | -- +goose StatementBegin 3 | 4 | -- ROWID is an internal subject to change on vacuum 5 | -- DBID INTEGER PRIMARY KEY aliases ROWID and makes it 6 | -- persistent between vacuums 7 | 8 | create table History 9 | ( 10 | DBID INTEGER PRIMARY KEY, 11 | Time integer not null, 12 | Type text not null, 13 | TokenID text not null, 14 | TokenValue text not null, 15 | TokenData text not null, 16 | Success integer not null 17 | ); 18 | 19 | create table Mappings 20 | ( 21 | DBID INTEGER PRIMARY KEY, 22 | Added integer not null, 23 | Label text not null, 24 | Enabled integer not null, 25 | Type text not null, 26 | Match text not null, 27 | Pattern text not null, 28 | Override text not null 29 | ); 30 | -- +goose StatementEnd 31 | 32 | -- +goose Down 33 | -- +goose StatementBegin 34 | drop table Mappings; 35 | drop table History; 36 | -- +goose StatementEnd 37 | -------------------------------------------------------------------------------- /pkg/database/userdb/userdb.go: -------------------------------------------------------------------------------- 1 | package userdb 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 10 | "github.com/ZaparooProject/zaparoo-core/pkg/database" 11 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 12 | _ "github.com/mattn/go-sqlite3" 13 | ) 14 | 15 | var ErrorNullSql = errors.New("UserDB is not connected") 16 | 17 | type UserDB struct { 18 | sql *sql.DB 19 | pl platforms.Platform 20 | } 21 | 22 | func OpenUserDB(pl platforms.Platform) (*UserDB, error) { 23 | db := &UserDB{sql: nil, pl: pl} 24 | err := db.Open() 25 | return db, err 26 | } 27 | 28 | func (db *UserDB) Open() error { 29 | exists := true 30 | dbPath := db.GetDBPath() 31 | _, err := os.Stat(dbPath) 32 | if err != nil { 33 | exists = false 34 | err := os.MkdirAll(filepath.Dir(dbPath), 0755) 35 | if err != nil { 36 | return err 37 | } 38 | } 39 | sqlInstance, err := sql.Open("sqlite3", dbPath) 40 | if err != nil { 41 | return err 42 | } 43 | db.sql = sqlInstance 44 | if !exists { 45 | return db.Allocate() 46 | } 47 | return nil 48 | } 49 | 50 | func (db *UserDB) GetDBPath() string { 51 | return filepath.Join(db.pl.Settings().DataDir, config.UserDbFile) 52 | } 53 | 54 | func (db *UserDB) UnsafeGetSqlDb() *sql.DB { 55 | return db.sql 56 | } 57 | 58 | func (db *UserDB) Truncate() error { 59 | if db.sql == nil { 60 | return ErrorNullSql 61 | } 62 | return sqlTruncate(db.sql) 63 | } 64 | 65 | func (db *UserDB) Allocate() error { 66 | if db.sql == nil { 67 | return ErrorNullSql 68 | } 69 | return sqlAllocate(db.sql) 70 | } 71 | 72 | func (db *UserDB) MigrateUp() error { 73 | if db.sql == nil { 74 | return ErrorNullSql 75 | } 76 | return sqlMigrateUp(db.sql) 77 | } 78 | 79 | func (db *UserDB) Vacuum() error { 80 | if db.sql == nil { 81 | return ErrorNullSql 82 | } 83 | return sqlVacuum(db.sql) 84 | } 85 | 86 | func (db *UserDB) Close() error { 87 | if db.sql == nil { 88 | return nil 89 | } 90 | return db.sql.Close() 91 | } 92 | 93 | // TODO: reader source (physical reader vs web) 94 | // TODO: metadata 95 | 96 | func (db *UserDB) AddHistory(entry database.HistoryEntry) error { 97 | return sqlAddHistory(db.sql, entry) 98 | } 99 | 100 | func (db *UserDB) GetHistory(lastId int) ([]database.HistoryEntry, error) { 101 | return sqlGetHistoryWithOffset(db.sql, lastId) 102 | } 103 | -------------------------------------------------------------------------------- /pkg/platforms/batocera/esapi.go: -------------------------------------------------------------------------------- 1 | package batocera 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/rs/zerolog/log" 8 | "io" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | const apiURL = "http://localhost:1234" 14 | 15 | func apiRequest(path string, body string, timeout time.Duration) ([]byte, error) { 16 | if timeout == 0 { 17 | timeout = 30 * time.Second 18 | } 19 | client := &http.Client{ 20 | Timeout: timeout, 21 | } 22 | 23 | var kodiReq *http.Request 24 | var err error 25 | if body != "" { 26 | kodiReq, err = http.NewRequest("POST", apiURL+path, bytes.NewBuffer([]byte(body))) 27 | if err != nil { 28 | return nil, fmt.Errorf("failed to create request: %w", err) 29 | } 30 | } else { 31 | kodiReq, err = http.NewRequest("GET", apiURL+path, nil) 32 | if err != nil { 33 | return nil, fmt.Errorf("failed to create request: %w", err) 34 | } 35 | } 36 | 37 | resp, err := client.Do(kodiReq) 38 | if err != nil { 39 | return nil, fmt.Errorf("failed to send request: %w", err) 40 | } 41 | defer func(Body io.ReadCloser) { 42 | err := Body.Close() 43 | if err != nil { 44 | log.Warn().Err(err).Msg("failed to close response body") 45 | } 46 | }(resp.Body) 47 | 48 | respBody, err := io.ReadAll(resp.Body) 49 | if err != nil { 50 | return nil, fmt.Errorf("failed to read response body: %w", err) 51 | } 52 | 53 | log.Debug().Msgf("response body %s: %s", path, string(respBody)) 54 | 55 | return respBody, nil 56 | } 57 | 58 | func apiEmuKill() error { 59 | _, err := apiRequest("/emukill", "", 1*time.Second) 60 | return err 61 | } 62 | 63 | func apiLaunch(path string) error { 64 | _, err := apiRequest("/launch", path, 0) 65 | return err 66 | } 67 | 68 | func apiNotify(msg string) error { 69 | _, err := apiRequest("/notify", msg, 0) 70 | return err 71 | } 72 | 73 | const noGameRunning = "{\"msg\":\"NO GAME RUNNING\"}" 74 | 75 | type APIRunningGameResponse struct { 76 | ID string `json:"id"` 77 | Path string `json:"path"` 78 | Name string `json:"name"` 79 | SystemName string `json:"systemName"` 80 | Desc string `json:"desc"` 81 | Image string `json:"image"` 82 | Video string `json:"video"` 83 | Marquee string `json:"marquee"` 84 | Thumbnail string `json:"thumbnail"` 85 | Rating string `json:"rating"` 86 | ReleaseDate string `json:"releaseDate"` 87 | Developer string `json:"developer"` 88 | Genre string `json:"genre"` 89 | Genres string `json:"genres"` 90 | Players string `json:"players"` 91 | Favorite string `json:"favorite"` 92 | KidGame string `json:"kidgame"` 93 | LastPlayed string `json:"lastplayed"` 94 | CRC32 string `json:"crc32"` 95 | MD5 string `json:"md5"` 96 | GameTime string `json:"gametime"` 97 | Lang string `json:"lang"` 98 | CheevosHash string `json:"cheevosHash"` 99 | } 100 | 101 | func apiRunningGame() (APIRunningGameResponse, bool, error) { 102 | // for some reason this is more accurate if we do a fake request first 103 | _, _ = apiRequest("/runningGame", "", 500*time.Millisecond) 104 | 105 | resp, err := apiRequest("/runningGame", "", 1*time.Second) 106 | if err != nil { 107 | return APIRunningGameResponse{}, false, err 108 | } 109 | 110 | if string(resp) == noGameRunning { 111 | return APIRunningGameResponse{}, false, nil 112 | } 113 | 114 | var game APIRunningGameResponse 115 | err = json.Unmarshal(resp, &game) 116 | if err != nil { 117 | return APIRunningGameResponse{}, false, err 118 | } 119 | 120 | return game, true, nil 121 | } 122 | -------------------------------------------------------------------------------- /pkg/platforms/linux/installer/conf/60-zaparoo.rules: -------------------------------------------------------------------------------- 1 | # Allow user access access to NFC readers which use the CH340 USB serial chip 2 | SUBSYSTEMS=="usb", ATTRS{idProduct}=="7523", ATTRS{idVendor}=="1a86", MODE="0660", TAG+="uaccess" 3 | 4 | # Example of allowing user access to all USB serial devices 5 | # SUBSYSTEMS=="usb-serial", TAG+="uaccess" 6 | 7 | # Allow user access to create uinput devices (keyboard, gamepad, etc.) 8 | KERNEL=="uinput", TAG+="uaccess" 9 | -------------------------------------------------------------------------------- /pkg/platforms/linux/installer/conf/blacklist-zaparoo.conf: -------------------------------------------------------------------------------- 1 | # Prevent these modules from loading to avoid a conflict which stops libnfc 2 | # from being able to access ACR122U NFC readers 3 | blacklist pn533 4 | blacklist pn533_usb 5 | -------------------------------------------------------------------------------- /pkg/platforms/linux/installer/conf/zaparoo.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Zaparoo Core service 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/local/bin/zaparoo -daemon 8 | 9 | [Install] 10 | WantedBy=default.target -------------------------------------------------------------------------------- /pkg/platforms/mister/config.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package mister 4 | 5 | import ( 6 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 7 | mrextConfig "github.com/wizzomafizzo/mrext/pkg/config" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | TempDir = "/tmp/zaparoo" 14 | LegacyMappingsPath = "/media/fat/nfc.csv" 15 | TokenReadFile = "/tmp/TOKENREAD" 16 | DataDir = "/media/fat/zaparoo" 17 | ArcadeDbUrl = "https://api.github.com/repositories/521644036/contents/ArcadeDatabase_CSV" 18 | ArcadeDbFile = "ArcadeDatabase.csv" 19 | ScriptsDir = "/media/fat/Scripts" 20 | CmdInterface = "/dev/MiSTer_cmd" 21 | LinuxDir = "/media/fat/linux" 22 | MainPickerDir = "/tmp/PICKERITEMS" 23 | MainPickerSelected = "/tmp/PICKERSELECTED" 24 | MainFeaturesFile = "/tmp/MAINFEATURES" 25 | MainFeaturePicker = "PICKER" 26 | MainFeatureNotice = "NOTICE" 27 | ) 28 | 29 | func MainHasFeature(feature string) bool { 30 | if _, err := os.Stat(MainFeaturesFile); os.IsNotExist(err) { 31 | return false 32 | } 33 | 34 | contents, err := os.ReadFile(MainFeaturesFile) 35 | if err != nil { 36 | return false 37 | } 38 | 39 | features := strings.Split(string(contents), ",") 40 | 41 | for _, f := range features { 42 | if strings.EqualFold(f, feature) { 43 | return true 44 | } 45 | } 46 | 47 | return false 48 | } 49 | 50 | func UserConfigToMrext(cfg *config.Instance) *mrextConfig.UserConfig { 51 | var setCore []string 52 | for _, v := range cfg.SystemDefaults() { 53 | if v.Launcher == "" { 54 | continue 55 | } 56 | setCore = append(setCore, v.System+":"+v.Launcher) 57 | } 58 | return &mrextConfig.UserConfig{ 59 | Systems: mrextConfig.SystemsConfig{ 60 | GamesFolder: cfg.IndexRoots(), 61 | SetCore: setCore, 62 | }, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/platforms/mister/utils.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package mister 4 | 5 | import ( 6 | "fmt" 7 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 8 | "os" 9 | "strings" 10 | 11 | "github.com/rs/zerolog/log" 12 | mrextConfig "github.com/wizzomafizzo/mrext/pkg/config" 13 | "github.com/wizzomafizzo/mrext/pkg/games" 14 | mrextMister "github.com/wizzomafizzo/mrext/pkg/mister" 15 | ) 16 | 17 | func ExitGame() { 18 | _ = mrextMister.LaunchMenu() 19 | } 20 | 21 | func GetActiveCoreName() string { 22 | coreName, err := mrextMister.GetActiveCoreName() 23 | if err != nil { 24 | log.Error().Msgf("error trying to get the core name: %s", err) 25 | } 26 | return coreName 27 | } 28 | 29 | func NormalizePath(cfg *config.Instance, path string) string { 30 | sys, err := games.BestSystemMatch(UserConfigToMrext(cfg), path) 31 | if err != nil { 32 | return path 33 | } 34 | 35 | var match string 36 | for _, parent := range mrextConfig.GamesFolders { 37 | if strings.HasPrefix(path, parent) { 38 | match = path[len(parent):] 39 | break 40 | } 41 | } 42 | 43 | if match == "" { 44 | return path 45 | } 46 | 47 | match = strings.Trim(match, "/") 48 | 49 | parts := strings.Split(match, "/") 50 | if len(parts) < 2 { 51 | return path 52 | } 53 | 54 | return sys.Id + "/" + strings.Join(parts[1:], "/") 55 | } 56 | 57 | func RunDevCmd(cmd string, args string) error { 58 | _, err := os.Stat(mrextConfig.CmdInterface) 59 | if err != nil { 60 | return fmt.Errorf("command interface not accessible: %s", err) 61 | } 62 | 63 | dev, err := os.OpenFile(mrextConfig.CmdInterface, os.O_RDWR, 0) 64 | if err != nil { 65 | return err 66 | } 67 | defer func(dev *os.File) { 68 | err := dev.Close() 69 | if err != nil { 70 | log.Error().Msgf("error closing cmd interface: %s", err) 71 | } 72 | }(dev) 73 | 74 | _, err = dev.WriteString(fmt.Sprintf("%s %s\n", cmd, args)) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/platforms/mistex/commands.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package mistex 4 | 5 | import ( 6 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 7 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms/mister" 8 | ) 9 | 10 | var commandsMappings = map[string]func(platforms.Platform, platforms.CmdEnv) (platforms.CmdResult, error){ 11 | "mister.ini": mister.CmdIni, 12 | "mister.core": mister.CmdLaunchCore, 13 | // "mister.script": cmdMisterScript, 14 | "mister.mgl": mister.CmdMisterMgl, 15 | 16 | "ini": mister.CmdIni, // DEPRECATED 17 | } 18 | -------------------------------------------------------------------------------- /pkg/readers/acr122_pcsc/ndef.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023, 2024 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package acr122_pcsc 23 | 24 | import ( 25 | "bytes" 26 | "encoding/binary" 27 | "fmt" 28 | 29 | "github.com/hsanjuan/go-ndef" 30 | ) 31 | 32 | var NdefEnd = []byte{0xFE} 33 | var NdefStart = []byte{0x54, 0x02, 0x65, 0x6E} 34 | 35 | func ParseRecordText(blocks []byte) (string, error) { 36 | startIndex := bytes.Index(blocks, NdefStart) 37 | if startIndex == -1 { 38 | return "", fmt.Errorf("NDEF start not found: %x", blocks) 39 | } 40 | 41 | endIndex := bytes.Index(blocks, NdefEnd) 42 | if endIndex == -1 { 43 | return "", fmt.Errorf("NDEF end not found: %x", blocks) 44 | } 45 | 46 | if startIndex >= endIndex || startIndex+4 >= len(blocks) { 47 | return "", fmt.Errorf("start index out of bounds: %d, %x", startIndex, blocks) 48 | } 49 | 50 | if endIndex <= startIndex || endIndex >= len(blocks) { 51 | return "", fmt.Errorf("end index out of bounds: %d, %x", endIndex, blocks) 52 | } 53 | 54 | tagText := string(blocks[startIndex+4 : endIndex]) 55 | 56 | // TODO: why does this happen here but not in libnfc? 57 | cleaned := "" 58 | for _, r := range tagText { 59 | if r != '\x00' { 60 | cleaned += string(r) 61 | } 62 | } 63 | 64 | return cleaned, nil 65 | } 66 | 67 | func BuildMessage(text string) ([]byte, error) { 68 | msg := ndef.NewTextMessage(text, "en") 69 | var payload, err = msg.Marshal() 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | header, err := CalculateNdefHeader(payload) 75 | if err != nil { 76 | return nil, err 77 | } 78 | payload = append(header, payload...) 79 | payload = append(payload, []byte{0xFE}...) 80 | return payload, nil 81 | } 82 | 83 | func CalculateNdefHeader(ndefRecord []byte) ([]byte, error) { 84 | var recordLength = len(ndefRecord) 85 | if recordLength < 255 { 86 | return []byte{0x03, byte(len(ndefRecord))}, nil 87 | } 88 | 89 | // NFCForum-TS-Type-2-Tag_1.1.pdf Page 9 90 | // > 255 Use three consecutive bytes format 91 | buf := new(bytes.Buffer) 92 | err := binary.Write(buf, binary.BigEndian, uint16(recordLength)) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | var header = []byte{0x03, 0xFF} 98 | return append(header, buf.Bytes()...), nil 99 | } 100 | -------------------------------------------------------------------------------- /pkg/readers/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 7 | "github.com/ZaparooProject/zaparoo-core/pkg/service/tokens" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "time" 12 | 13 | "github.com/ZaparooProject/zaparoo-core/pkg/readers" 14 | "github.com/ZaparooProject/zaparoo-core/pkg/utils" 15 | "github.com/rs/zerolog/log" 16 | ) 17 | 18 | const TokenType = "file" 19 | 20 | type Reader struct { 21 | cfg *config.Instance 22 | device config.ReadersConnect 23 | path string 24 | polling bool 25 | } 26 | 27 | func NewReader(cfg *config.Instance) *Reader { 28 | return &Reader{ 29 | cfg: cfg, 30 | } 31 | } 32 | 33 | func (r *Reader) Ids() []string { 34 | return []string{"file"} 35 | } 36 | 37 | func (r *Reader) Open(device config.ReadersConnect, iq chan<- readers.Scan) error { 38 | if !utils.Contains(r.Ids(), device.Driver) { 39 | return errors.New("invalid reader id: " + device.Driver) 40 | } 41 | 42 | path := device.Path 43 | 44 | if !filepath.IsAbs(path) { 45 | return errors.New("invalid device path, must be absolute") 46 | } 47 | 48 | parent := filepath.Dir(path) 49 | if parent == "" { 50 | return errors.New("invalid device path") 51 | } 52 | 53 | if _, err := os.Stat(parent); err != nil { 54 | return err 55 | } 56 | 57 | if _, err := os.Stat(path); err != nil { 58 | // attempt to create empty file 59 | f, err := os.Create(path) 60 | if err != nil { 61 | return err 62 | } 63 | _ = f.Close() 64 | } 65 | 66 | r.device = device 67 | r.path = path 68 | r.polling = true 69 | 70 | go func() { 71 | var token *tokens.Token 72 | 73 | for r.polling { 74 | time.Sleep(100 * time.Millisecond) 75 | 76 | contents, err := os.ReadFile(r.path) 77 | if err != nil { 78 | // TODO: have a max retries? 79 | iq <- readers.Scan{ 80 | Source: r.device.ConnectionString(), 81 | Error: err, 82 | } 83 | continue 84 | } 85 | 86 | text := strings.TrimSpace(string(contents)) 87 | 88 | // "remove" the token if the file is now empty 89 | if text == "" && token != nil { 90 | log.Debug().Msg("file is empty, removing token") 91 | token = nil 92 | iq <- readers.Scan{ 93 | Source: r.device.ConnectionString(), 94 | Token: nil, 95 | } 96 | continue 97 | } 98 | 99 | if token != nil && token.Text == text { 100 | continue 101 | } 102 | 103 | if text == "" { 104 | continue 105 | } 106 | 107 | token = &tokens.Token{ 108 | Type: TokenType, 109 | Text: text, 110 | Data: hex.EncodeToString(contents), 111 | ScanTime: time.Now(), 112 | Source: r.device.ConnectionString(), 113 | } 114 | 115 | log.Debug().Msgf("new token: %s", token.Text) 116 | iq <- readers.Scan{ 117 | Source: r.device.ConnectionString(), 118 | Token: token, 119 | } 120 | } 121 | }() 122 | 123 | return nil 124 | } 125 | 126 | func (r *Reader) Close() error { 127 | r.polling = false 128 | return nil 129 | } 130 | 131 | func (r *Reader) Detect(connected []string) string { 132 | return "" 133 | } 134 | 135 | func (r *Reader) Device() string { 136 | return r.device.ConnectionString() 137 | } 138 | 139 | func (r *Reader) Connected() bool { 140 | return r.polling 141 | } 142 | 143 | func (r *Reader) Info() string { 144 | return r.path 145 | } 146 | 147 | func (r *Reader) Write(text string) (*tokens.Token, error) { 148 | return nil, errors.New("writing not supported on this reader") 149 | } 150 | 151 | func (r *Reader) CancelWrite() { 152 | return 153 | } 154 | -------------------------------------------------------------------------------- /pkg/readers/libnfc/tags/ndef.go: -------------------------------------------------------------------------------- 1 | //go:build (linux || darwin) && cgo 2 | 3 | /* 4 | Zaparoo Core 5 | Copyright (C) 2023 Gareth Jones 6 | Copyright (C) 2023, 2024 Callan Barrett 7 | 8 | This file is part of Zaparoo Core. 9 | 10 | Zaparoo Core is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | Zaparoo Core is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with Zaparoo Core. If not, see . 22 | */ 23 | 24 | package tags 25 | 26 | import ( 27 | "bytes" 28 | "encoding/binary" 29 | "fmt" 30 | 31 | "github.com/hsanjuan/go-ndef" 32 | ) 33 | 34 | var NdefEnd = []byte{0xFE} 35 | var NdefStart = []byte{0x54, 0x02, 0x65, 0x6E} 36 | 37 | func ParseRecordText(blocks []byte) (string, error) { 38 | startIndex := bytes.Index(blocks, NdefStart) 39 | if startIndex == -1 { 40 | return "", fmt.Errorf("NDEF start not found: %x", blocks) 41 | } 42 | 43 | endIndex := bytes.Index(blocks, NdefEnd) 44 | if endIndex == -1 { 45 | return "", fmt.Errorf("NDEF end not found: %x", blocks) 46 | } 47 | 48 | if startIndex >= endIndex || startIndex+4 >= len(blocks) { 49 | return "", fmt.Errorf("start index out of bounds: %d, %x", startIndex, blocks) 50 | } 51 | 52 | if endIndex <= startIndex || endIndex >= len(blocks) { 53 | return "", fmt.Errorf("end index out of bounds: %d, %x", endIndex, blocks) 54 | } 55 | 56 | tagText := string(blocks[startIndex+4 : endIndex]) 57 | 58 | return tagText, nil 59 | } 60 | 61 | func BuildMessage(text string) ([]byte, error) { 62 | msg := ndef.NewTextMessage(text, "en") 63 | payload, err := msg.Marshal() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | header, err := CalculateNdefHeader(payload) 69 | if err != nil { 70 | return nil, err 71 | } 72 | payload = append(header, payload...) 73 | payload = append(payload, []byte{0xFE}...) 74 | return payload, nil 75 | } 76 | 77 | func CalculateNdefHeader(ndefRecord []byte) ([]byte, error) { 78 | var recordLength = len(ndefRecord) 79 | if recordLength < 255 { 80 | return []byte{0x03, byte(len(ndefRecord))}, nil 81 | } 82 | 83 | // NFCForum-TS-Type-2-Tag_1.1.pdf Page 9 84 | // > 255 Use three consecutive bytes format 85 | buf := new(bytes.Buffer) 86 | err := binary.Write(buf, binary.BigEndian, uint16(recordLength)) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | var header = []byte{0x03, 0xFF} 92 | return append(header, buf.Bytes()...), nil 93 | } 94 | -------------------------------------------------------------------------------- /pkg/readers/libnfc/tags/ndef_test.go: -------------------------------------------------------------------------------- 1 | //go:build (linux || darwin) && cgo 2 | 3 | /* 4 | Zaparoo Core 5 | Copyright (C) 2023 Gareth Jones 6 | 7 | This file is part of Zaparoo Core. 8 | 9 | Zaparoo Core is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | Zaparoo Core is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with Zaparoo Core. If not, see . 21 | */ 22 | 23 | package tags 24 | 25 | import ( 26 | "bytes" 27 | "encoding/hex" 28 | "strings" 29 | "testing" 30 | ) 31 | 32 | func TestCalculateNdefHeader(t *testing.T) { 33 | test2 := map[string]struct { 34 | input []byte 35 | want []byte 36 | }{ 37 | "minimum": {input: bytes.Repeat([]byte{0x69}, 1), want: []byte{0x03, 0x01}}, 38 | "255": {input: bytes.Repeat([]byte{0x69}, 255), want: []byte{0x03, 0xFF, 0x00, 0xFF}}, 39 | "256": {input: bytes.Repeat([]byte{0x69}, 256), want: []byte{0x03, 0xFF, 0x01, 0x00}}, 40 | "257": {input: bytes.Repeat([]byte{0x69}, 257), want: []byte{0x03, 0xFF, 0x01, 0x01}}, 41 | "258": {input: bytes.Repeat([]byte{0x69}, 258), want: []byte{0x03, 0xFF, 0x01, 0x02}}, 42 | "512": {input: bytes.Repeat([]byte{0x69}, 512), want: []byte{0x03, 0xFF, 0x02, 0x00}}, 43 | "maximum": {input: bytes.Repeat([]byte{0x69}, 865), want: []byte{0x03, 0xFF, 0x03, 0x61}}, 44 | } 45 | 46 | for name, tc := range test2 { 47 | t.Run(name, func(t *testing.T) { 48 | got, err := CalculateNdefHeader(tc.input) 49 | if err != nil { 50 | t.Fatalf("Got error: %v", err) 51 | } 52 | if !bytes.Equal(got, tc.want) { 53 | t.Fatalf("test %v, expected: %v, got: %v", name, hex.EncodeToString(tc.want), hex.EncodeToString(got)) 54 | } 55 | }) 56 | 57 | } 58 | } 59 | 60 | func TestBuildMessage(t *testing.T) { 61 | test2 := []struct { 62 | input string 63 | want string 64 | }{ 65 | {input: "**random:snes", want: "0314d101105402656e2a2a72616e646f6d3a736e6573fe"}, 66 | {input: "A", want: "0308d101045402656e41fe"}, 67 | {input: "AAAA", want: "030bd101075402656e41414141fe"}, 68 | {input: strings.Repeat("A", 512), want: "03ff020ac101000002035402656e4141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141fe"}, 69 | } 70 | 71 | for name, tc := range test2 { 72 | t.Run(tc.input, func(t *testing.T) { 73 | got, err := BuildMessage(tc.input) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | want, err := hex.DecodeString(tc.want) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | if !bytes.Equal(got, want) { 82 | t.Fatalf("test %v, expected: %v, got: %v", name, hex.EncodeToString(want), hex.EncodeToString(got)) 83 | } 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pkg/readers/libnfc/tags/tags.go: -------------------------------------------------------------------------------- 1 | //go:build (linux || darwin) && cgo 2 | 3 | /* 4 | Zaparoo Core 5 | Copyright (C) 2023, 2024 Callan Barrett 6 | Copyright (C) 2023 Gareth Jones 7 | 8 | This file is part of Zaparoo Core. 9 | 10 | Zaparoo Core is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | Zaparoo Core is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with Zaparoo Core. If not, see . 22 | */ 23 | 24 | package tags 25 | 26 | import ( 27 | "encoding/hex" 28 | "fmt" 29 | "github.com/ZaparooProject/zaparoo-core/pkg/service/tokens" 30 | 31 | "github.com/clausecker/nfc/v2" 32 | ) 33 | 34 | const ( 35 | WriteCommand = byte(0xA2) 36 | ReadCommand = byte(0x30) 37 | ) 38 | 39 | var SupportedCardTypes = []nfc.Modulation{ 40 | {Type: nfc.ISO14443a, BaudRate: nfc.Nbr106}, 41 | } 42 | 43 | type TagData struct { 44 | Type string 45 | Bytes []byte 46 | } 47 | 48 | func GetTagUID(target nfc.Target) string { 49 | var uid string 50 | switch target.Modulation() { 51 | case nfc.Modulation{Type: nfc.ISO14443a, BaudRate: nfc.Nbr106}: 52 | var card = target.(*nfc.ISO14443aTarget) 53 | var ID = card.UID 54 | uid = hex.EncodeToString(ID[:card.UIDLen]) 55 | break 56 | default: 57 | uid = "" 58 | } 59 | return uid 60 | } 61 | 62 | func comm(pnd nfc.Device, tx []byte, replySize int) ([]byte, error) { 63 | rx := make([]byte, replySize) 64 | 65 | timeout := 0 66 | _, err := pnd.InitiatorTransceiveBytes(tx, rx, timeout) 67 | if err != nil { 68 | return nil, fmt.Errorf("comm error: %s", err) 69 | } 70 | 71 | return rx, nil 72 | } 73 | 74 | func GetTagType(target nfc.Target) string { 75 | switch target.Modulation() { 76 | case nfc.Modulation{Type: nfc.ISO14443a, BaudRate: nfc.Nbr106}: 77 | var card = target.(*nfc.ISO14443aTarget) 78 | if card.Atqa == [2]byte{0x00, 0x04} && card.Sak == 0x08 { 79 | // https://www.nxp.com/docs/en/application-note/AN10833.pdf page 9 80 | return tokens.TypeMifare 81 | } 82 | if card.Atqa == [2]byte{0x00, 0x44} && card.Sak == 0x00 { 83 | // https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf page 33 84 | return tokens.TypeNTAG 85 | } 86 | } 87 | return "" 88 | } 89 | -------------------------------------------------------------------------------- /pkg/readers/pn532_uart/ndef.go: -------------------------------------------------------------------------------- 1 | /* 2 | Zaparoo Core 3 | Copyright (C) 2023 Gareth Jones 4 | Copyright (C) 2023, 2024 Callan Barrett 5 | 6 | This file is part of Zaparoo Core. 7 | 8 | Zaparoo Core is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | Zaparoo Core is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with Zaparoo Core. If not, see . 20 | */ 21 | 22 | package pn532_uart 23 | 24 | import ( 25 | "bytes" 26 | "encoding/binary" 27 | "fmt" 28 | "github.com/rs/zerolog/log" 29 | 30 | "github.com/hsanjuan/go-ndef" 31 | ) 32 | 33 | var NdefEnd = []byte{0xFE} 34 | var NdefStart = []byte{0x54, 0x02, 0x65, 0x6E} 35 | 36 | var ErrNoNdef = fmt.Errorf("no NDEF record found") 37 | 38 | func ParseRecordText(bs []byte) (string, error) { 39 | // sometimes there can be some read corruption and multiple copies of the 40 | // NDEF header get pulled in. we just flick through until the last one 41 | // TODO: is this going to mess up if there are multiple NDEF records? 42 | startIndex := bytes.LastIndex(bs, NdefStart) 43 | if startIndex == -1 { 44 | return "", ErrNoNdef 45 | } 46 | 47 | // check if there is another ndef start left, as it can mean we got come 48 | // corrupt data at the beginning 49 | if len(bs) > startIndex+8 { 50 | nextStart := bytes.Index(bs[startIndex+4:], NdefStart) 51 | if nextStart != -1 { 52 | startIndex += nextStart 53 | } 54 | } 55 | 56 | endIndex := bytes.Index(bs, NdefEnd) 57 | if endIndex == -1 { 58 | return "", fmt.Errorf("NDEF end not found: %x", bs) 59 | } 60 | 61 | if startIndex >= endIndex || startIndex+4 >= len(bs) { 62 | return "", fmt.Errorf("start index out of bounds: %d, %x", startIndex, bs) 63 | } 64 | 65 | if endIndex <= startIndex || endIndex >= len(bs) { 66 | return "", fmt.Errorf("end index out of bounds: %d, %x", endIndex, bs) 67 | } 68 | 69 | log.Debug().Msgf("NDEF start: %d, end: %d", startIndex, endIndex) 70 | log.Debug().Msgf("NDEF: %x", bs[startIndex:endIndex]) 71 | 72 | tagText := string(bs[startIndex+4 : endIndex]) 73 | 74 | return tagText, nil 75 | } 76 | 77 | func BuildMessage(text string) ([]byte, error) { 78 | msg := ndef.NewTextMessage(text, "en") 79 | var payload, err = msg.Marshal() 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | header, err := CalculateNdefHeader(payload) 85 | if err != nil { 86 | return nil, err 87 | } 88 | payload = append(header, payload...) 89 | payload = append(payload, []byte{0xFE}...) 90 | return payload, nil 91 | } 92 | 93 | func CalculateNdefHeader(ndefRecord []byte) ([]byte, error) { 94 | var recordLength = len(ndefRecord) 95 | if recordLength < 255 { 96 | return []byte{0x03, byte(len(ndefRecord))}, nil 97 | } 98 | 99 | // NFCForum-TS-Type-2-Tag_1.1.pdf Page 9 100 | // > 255 Use three consecutive bytes format 101 | buf := new(bytes.Buffer) 102 | err := binary.Write(buf, binary.BigEndian, uint16(recordLength)) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | var header = []byte{0x03, 0xFF} 108 | return append(header, buf.Bytes()...), nil 109 | } 110 | -------------------------------------------------------------------------------- /pkg/readers/readers.go: -------------------------------------------------------------------------------- 1 | package readers 2 | 3 | import ( 4 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/service/tokens" 6 | ) 7 | 8 | type Scan struct { 9 | Source string 10 | Token *tokens.Token 11 | Error error 12 | } 13 | 14 | type Reader interface { 15 | // TODO: type? file, libnfc, etc. 16 | // Ids returns the device string prefixes supported by this reader. 17 | Ids() []string 18 | // Open any necessary connections to the device and start polling. 19 | // Takes a device connection string and a channel to send scanned tokens. 20 | Open(config.ReadersConnect, chan<- Scan) error 21 | // Close any open connections to the device and stop polling. 22 | Close() error 23 | // Detect attempts to search for a connected device and returns the device 24 | // connection string. If no device is found, an empty string is returned. 25 | // Takes a list of currently connected device strings. 26 | Detect([]string) string 27 | // Device returns the device connection string. 28 | Device() string 29 | // Connected returns true if the device is connected and active. 30 | Connected() bool 31 | // Info returns a string with information about the connected device. 32 | Info() string 33 | // Write sends a string to the device to be written to a token, if 34 | // that device supports writing. Blocks until completion or timeout. 35 | Write(string) (*tokens.Token, error) 36 | // CancelWrite sends a request to cancel an active write request. 37 | CancelWrite() 38 | } 39 | -------------------------------------------------------------------------------- /pkg/service/playlists/playlists.go: -------------------------------------------------------------------------------- 1 | package playlists 2 | 3 | type PlaylistMedia struct { 4 | Path string 5 | Name string 6 | } 7 | 8 | type Playlist struct { 9 | ID string 10 | Media []PlaylistMedia 11 | Index int 12 | Playing bool 13 | } 14 | 15 | func NewPlaylist(id string, media []PlaylistMedia) *Playlist { 16 | return &Playlist{ 17 | ID: id, 18 | Media: media, 19 | Index: 0, 20 | Playing: false, 21 | } 22 | } 23 | 24 | func Next(p Playlist) *Playlist { 25 | idx := p.Index + 1 26 | if idx >= len(p.Media) { 27 | idx = 0 28 | } 29 | return &Playlist{ 30 | ID: p.ID, 31 | Media: p.Media, 32 | Index: idx, 33 | Playing: p.Playing, 34 | } 35 | } 36 | 37 | func Previous(p Playlist) *Playlist { 38 | idx := p.Index - 1 39 | if idx < 0 { 40 | idx = len(p.Media) - 1 41 | } 42 | return &Playlist{ 43 | ID: p.ID, 44 | Media: p.Media, 45 | Index: idx, 46 | Playing: p.Playing, 47 | } 48 | } 49 | 50 | func Goto(p Playlist, idx int) *Playlist { 51 | if idx >= len(p.Media) { 52 | idx = len(p.Media) - 1 53 | } else if idx < 0 { 54 | idx = 0 55 | } 56 | p.Index = idx 57 | return &Playlist{ 58 | ID: p.ID, 59 | Media: p.Media, 60 | Index: idx, 61 | Playing: p.Playing, 62 | } 63 | } 64 | 65 | func Play(p Playlist) *Playlist { 66 | return &Playlist{ 67 | ID: p.ID, 68 | Media: p.Media, 69 | Index: p.Index, 70 | Playing: true, 71 | } 72 | } 73 | 74 | func Pause(p Playlist) *Playlist { 75 | return &Playlist{ 76 | ID: p.ID, 77 | Media: p.Media, 78 | Index: p.Index, 79 | Playing: false, 80 | } 81 | } 82 | 83 | func (p *Playlist) Current() PlaylistMedia { 84 | return p.Media[p.Index] 85 | } 86 | 87 | type PlaylistController struct { 88 | Active *Playlist 89 | Queue chan<- *Playlist 90 | } 91 | -------------------------------------------------------------------------------- /pkg/service/tokens/tokens.go: -------------------------------------------------------------------------------- 1 | package tokens 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | TypeNTAG = "NTAG" 9 | TypeMifare = "MIFARE" 10 | TypeAmiibo = "Amiibo" 11 | TypeLegoDimensions = "LegoDimensions" 12 | SourcePlaylist = "Playlist" 13 | ) 14 | 15 | type Token struct { 16 | Type string 17 | UID string 18 | Text string 19 | Data string 20 | ScanTime time.Time 21 | FromAPI bool 22 | Source string 23 | Unsafe bool 24 | } 25 | -------------------------------------------------------------------------------- /pkg/ui/tui/buildapp_linux.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | "github.com/rivo/tview" 6 | "github.com/rs/zerolog/log" 7 | "os" 8 | ) 9 | 10 | func tryRunApp( 11 | app *tview.Application, 12 | builder func() (*tview.Application, error), 13 | ) error { 14 | if err := app.Run(); err != nil { 15 | appTty2, err := builder() 16 | if err != nil { 17 | return err 18 | } 19 | 20 | ttyPath := "/dev/tty2" 21 | if os.Getenv("ZAPAROO_RUN_SCRIPT") == "2" { 22 | log.Debug().Msg("alternate tty for widgets from zapscript") 23 | ttyPath = "/dev/tty4" 24 | } 25 | 26 | tty, err := tcell.NewDevTtyFromDev(ttyPath) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | screen, err := tcell.NewTerminfoScreenFromTty(tty) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | appTty2.SetScreen(screen) 37 | 38 | if err := appTty2.Run(); err != nil { 39 | return err 40 | } 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/ui/tui/buildapp_others.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tui 4 | 5 | import ( 6 | "github.com/rivo/tview" 7 | ) 8 | 9 | func tryRunApp( 10 | app *tview.Application, 11 | _ func() (*tview.Application, error), 12 | ) error { 13 | return app.Run() 14 | } 15 | -------------------------------------------------------------------------------- /pkg/ui/tui/exportlog.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "path" 7 | "strings" 8 | 9 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 10 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 11 | "github.com/ZaparooProject/zaparoo-core/pkg/utils" 12 | "github.com/gdamore/tcell/v2" 13 | "github.com/rivo/tview" 14 | "github.com/rs/zerolog/log" 15 | ) 16 | 17 | func BuildExportLogModal( 18 | pl platforms.Platform, 19 | app *tview.Application, 20 | pages *tview.Pages, 21 | logDestPath string, 22 | logDestName string, 23 | ) tview.Primitive { 24 | exportPages := tview.NewPages() 25 | 26 | exportMenu := tview.NewList() 27 | exportPages.AddAndSwitchToPage("export", exportMenu, true) 28 | 29 | exportMenu.AddItem( 30 | "Upload to termbin.com", 31 | "Upload log file to termbin.com and display URL", 32 | '1', func() { 33 | outcome := uploadLog(pl, exportPages, app) 34 | resultModal := genericModal(outcome, "Upload Log File", 35 | func(buttonIndex int, buttonLabel string) { 36 | exportPages.RemovePage("upload") 37 | }, true) 38 | exportPages.AddPage("upload", resultModal, true, true) 39 | app.SetFocus(resultModal) 40 | }) 41 | if logDestPath != "" { 42 | exportMenu.AddItem( 43 | "Copy to "+logDestName, 44 | "Copy log file to a permanent location on disk", 45 | '2', 46 | func() { 47 | outcome := copyLogToSd(pl, logDestPath, logDestName) 48 | resultModal := genericModal(outcome, "Copy Log File", 49 | func(buttonIndex int, buttonLabel string) { 50 | exportPages.RemovePage("copy") 51 | }, true) 52 | exportPages.AddPage("copy", resultModal, true, true) 53 | app.SetFocus(resultModal) 54 | }) 55 | } 56 | exportMenu.AddItem("Go back", "Back to main menu", 'b', func() { 57 | pages.SwitchToPage(PageMain) 58 | }) 59 | exportMenu.SetTitle("Export Log File") 60 | exportMenu.SetSecondaryTextColor(tcell.ColorYellow) 61 | exportMenu.SetBorder(true) 62 | 63 | exportPages.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 64 | if event.Key() == tcell.KeyEscape { 65 | pages.SwitchToPage(PageMain) 66 | return nil 67 | } 68 | return event 69 | }) 70 | 71 | pages.AddPage(PageExportLog, exportPages, true, false) 72 | return exportMenu 73 | } 74 | 75 | func copyLogToSd(pl platforms.Platform, logDestPath string, logDestName string) string { 76 | logPath := path.Join(pl.Settings().TempDir, config.LogFile) 77 | newPath := logDestPath 78 | err := utils.CopyFile(logPath, newPath) 79 | outcome := "" 80 | if err != nil { 81 | outcome = fmt.Sprintf("Unable to copy log file to %s.", logDestName) 82 | log.Error().Err(err).Msgf("error copying log file") 83 | } else { 84 | outcome = fmt.Sprintf("Copied %s to %s.", config.LogFile, logDestName) 85 | } 86 | return outcome 87 | } 88 | 89 | func uploadLog(pl platforms.Platform, pages *tview.Pages, app *tview.Application) string { 90 | logPath := path.Join(pl.Settings().TempDir, config.LogFile) 91 | modal := genericModal("Uploading log file...", "Log upload", func(buttonIndex int, buttonLabel string) {}, false) 92 | pages.AddPage("temp_upload", modal, true, true) 93 | app.SetFocus(modal) 94 | app.ForceDraw() 95 | 96 | uploadCmd := "cat '" + logPath + "' | nc termbin.com 9999" 97 | out, err := exec.Command("bash", "-c", uploadCmd).Output() 98 | pages.RemovePage("temp_upload") 99 | if err != nil { 100 | log.Error().Err(err).Msgf("error uploading log file to termbin") 101 | return "Error uploading log file." 102 | } else { 103 | return "Log file URL:\n\n" + strings.TrimSpace(string(out)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pkg/ui/tui/utils.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | "github.com/rivo/tview" 6 | ) 7 | 8 | func centerWidget(width, height int, p tview.Primitive) tview.Primitive { 9 | return tview.NewFlex(). 10 | AddItem(nil, 0, 1, false). 11 | AddItem(tview.NewFlex(). 12 | SetDirection(tview.FlexRow). 13 | AddItem(nil, 0, 1, false). 14 | AddItem(p, height, 1, true). 15 | AddItem(nil, 0, 1, false), width, 1, true). 16 | AddItem(nil, 0, 1, false) 17 | } 18 | 19 | func pageDefaults[S PrimitiveWithSetBorder](name string, pages *tview.Pages, widget S) tview.Primitive { 20 | widget.SetBorder(true) 21 | pages.AddPage(name, widget, true, false) 22 | return widget 23 | } 24 | 25 | func SetTheme(theme *tview.Theme) { 26 | theme.BorderColor = tcell.ColorLightYellow 27 | theme.PrimaryTextColor = tcell.ColorWhite 28 | theme.ContrastSecondaryTextColor = tcell.ColorFuchsia 29 | theme.PrimitiveBackgroundColor = tcell.ColorDarkBlue 30 | theme.ContrastBackgroundColor = tcell.ColorBlue 31 | theme.InverseTextColor = tcell.ColorDarkBlue 32 | } 33 | 34 | func genericModal( 35 | message string, 36 | title string, 37 | action func(buttonIndex int, buttonLabel string), 38 | withButton bool, 39 | ) *tview.Modal { 40 | modal := tview.NewModal() 41 | modal.SetTitle(title). 42 | SetBorder(true). 43 | SetTitleAlign(tview.AlignCenter) 44 | modal.SetText(message) 45 | if withButton { 46 | modal.AddButtons([]string{"OK"}). 47 | SetDoneFunc(action) 48 | } 49 | return modal 50 | } 51 | 52 | type PrimitiveWithSetBorder interface { 53 | tview.Primitive 54 | SetBorder(arg bool) *tview.Box 55 | } 56 | 57 | // BuildAndRetry attempts to build and display a TUI dialog, retrying with 58 | // alternate settings on error. 59 | // It's used to work around issues on MiSTer, which has an unusual setup for 60 | // showing TUI applications. 61 | func BuildAndRetry( 62 | builder func() (*tview.Application, error), 63 | ) error { 64 | app, err := builder() 65 | if err != nil { 66 | return err 67 | } 68 | return tryRunApp(app, builder) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/ui/tui/writetag.go: -------------------------------------------------------------------------------- 1 | package tui 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "strings" 7 | 8 | "github.com/ZaparooProject/zaparoo-core/pkg/api/client" 9 | "github.com/ZaparooProject/zaparoo-core/pkg/api/models" 10 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 11 | "github.com/gdamore/tcell/v2" 12 | "github.com/rivo/tview" 13 | ) 14 | 15 | func BuildTagsWriteMenu(cfg *config.Instance, pages *tview.Pages, _ *tview.Application) *tview.Form { 16 | topTextView := tview.NewTextView(). 17 | SetLabel(""). 18 | SetText("Place card on reader, input your ZapScript and press ENTER to write. ESC to exit.") 19 | zapScriptTextArea := tview.NewTextArea(). 20 | SetLabel("ZapScript") 21 | 22 | tagsWriteMenu := tview.NewForm(). 23 | AddFormItem(topTextView). 24 | AddFormItem(zapScriptTextArea) 25 | 26 | tagsWriteMenu.SetTitle("Settings - NFC Tags - Write") 27 | tagsWriteMenu.SetFocus(1) 28 | 29 | tagsWriteMenu.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 30 | k := event.Key() 31 | if k == tcell.KeyEnter { 32 | text := zapScriptTextArea.GetText() 33 | text = strings.Trim(text, "\r\n ") 34 | data, _ := json.Marshal(&models.ReaderWriteParams{ 35 | Text: text, 36 | }) 37 | _, _ = client.LocalClient(context.Background(), cfg, models.MethodReadersWrite, string(data)) 38 | zapScriptTextArea.SetText("", true) 39 | } else if k == tcell.KeyEscape { 40 | pages.SwitchToPage(PageMain) 41 | } 42 | return event 43 | }) 44 | 45 | pageDefaults(PageSettingsTagsWrite, pages, tagsWriteMenu) 46 | return tagsWriteMenu 47 | } 48 | -------------------------------------------------------------------------------- /pkg/ui/widgets/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/ZaparooProject/zaparoo-core/pkg/zapscript/models" 4 | 5 | type NoticeArgs struct { 6 | Text string `json:"text"` 7 | Timeout int `json:"timeout"` 8 | Complete string `json:"complete"` 9 | } 10 | 11 | type PickerArgs struct { 12 | Items []models.ZapScript `json:"items"` 13 | Title string `json:"title"` 14 | Selected int `json:"selected"` 15 | Timeout int `json:"timeout"` 16 | Unsafe bool `json:"unsafe"` 17 | } 18 | -------------------------------------------------------------------------------- /pkg/utils/launchers.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 6 | "github.com/ZaparooProject/zaparoo-core/pkg/database/systemdefs" 7 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 8 | "github.com/rs/zerolog/log" 9 | "os/exec" 10 | "runtime" 11 | "strings" 12 | "text/template" 13 | ) 14 | 15 | func formatExtensions(exts []string) []string { 16 | newExts := make([]string, 0) 17 | for _, v := range exts { 18 | if v == "" { 19 | continue 20 | } 21 | newExt := strings.TrimSpace(v) 22 | if newExt[0] != '.' { 23 | newExt = "." + newExt 24 | } 25 | newExt = strings.ToLower(newExt) 26 | newExts = append(newExts, newExt) 27 | } 28 | return newExts 29 | } 30 | 31 | func ParseCustomLaunchers(customLaunchers []config.LaunchersCustom) []platforms.Launcher { 32 | launchers := make([]platforms.Launcher, 0) 33 | for _, v := range customLaunchers { 34 | systemID := "" 35 | if v.System != "" { 36 | system, err := systemdefs.LookupSystem(v.System) 37 | if err != nil { 38 | log.Err(err).Msgf("custom launcher %s: system not found: %s", v.ID, v.System) 39 | continue 40 | } 41 | systemID = system.ID 42 | } 43 | 44 | launchers = append(launchers, platforms.Launcher{ 45 | ID: v.ID, 46 | SystemID: systemID, 47 | Folders: v.MediaDirs, 48 | Extensions: formatExtensions(v.FileExts), 49 | Launch: func(_ *config.Instance, path string) error { 50 | data := struct { 51 | MediaPath string 52 | }{ 53 | MediaPath: path, 54 | } 55 | 56 | tmpl, err := template.New("command").Parse(v.Execute) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | var buf bytes.Buffer 62 | if err := tmpl.Execute(&buf, data); err != nil { 63 | return err 64 | } 65 | 66 | if runtime.GOOS == "windows" { 67 | cmd := exec.Command("cmd", "/c", buf.String()) 68 | err = cmd.Run() 69 | } else { 70 | cmd := exec.Command("sh", "-c", buf.String()) 71 | err = cmd.Run() 72 | } 73 | 74 | if err != nil { 75 | log.Error().Err(err).Msgf("error running custom launcher: %s", buf.String()) 76 | return err 77 | } 78 | 79 | return nil 80 | }, 81 | }) 82 | } 83 | return launchers 84 | } 85 | -------------------------------------------------------------------------------- /pkg/utils/linuxinput/keymap.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package linuxinput 4 | 5 | import ( 6 | "github.com/bendahl/uinput" 7 | "strings" 8 | ) 9 | 10 | // ToKeyboardCode converts a single ZapScript key symbol to a uinput code. 11 | func ToKeyboardCode(name string) (int, bool) { 12 | if len(name) > 2 && name[0] == '{' && name[len(name)-1] == '}' { 13 | name = strings.ToLower(name) 14 | } 15 | if v, ok := GamepadMap[name]; ok { 16 | return v, ok 17 | } else { 18 | return 0, false 19 | } 20 | } 21 | 22 | var GamepadMap = map[string]int{ 23 | "^": uinput.ButtonDpadUp, 24 | "{up}": uinput.ButtonDpadUp, 25 | "v": uinput.ButtonDpadUp, 26 | "V": uinput.ButtonDpadDown, 27 | "{down}": uinput.ButtonDpadDown, 28 | "<": uinput.ButtonDpadLeft, 29 | "{left}": uinput.ButtonDpadLeft, 30 | ">": uinput.ButtonDpadRight, 31 | "{right}": uinput.ButtonDpadRight, 32 | "A": uinput.ButtonEast, 33 | "a": uinput.ButtonEast, 34 | "{east}": uinput.ButtonEast, 35 | "B": uinput.ButtonSouth, 36 | "b": uinput.ButtonSouth, 37 | "{south}": uinput.ButtonSouth, 38 | "X": uinput.ButtonNorth, 39 | "x": uinput.ButtonNorth, 40 | "{north}": uinput.ButtonNorth, 41 | "Y": uinput.ButtonWest, 42 | "y": uinput.ButtonWest, 43 | "{west}": uinput.ButtonWest, 44 | "{start}": uinput.ButtonStart, 45 | "{select}": uinput.ButtonSelect, 46 | "{menu}": uinput.ButtonMode, 47 | "L": uinput.ButtonBumperLeft, 48 | "l": uinput.ButtonBumperLeft, 49 | "{l1}": uinput.ButtonBumperLeft, 50 | "R": uinput.ButtonBumperRight, 51 | "r": uinput.ButtonBumperRight, 52 | "{r1}": uinput.ButtonBumperRight, 53 | "{l2}": uinput.ButtonTriggerLeft, 54 | "{r2}": uinput.ButtonTriggerRight, 55 | } 56 | 57 | // ToGamepadCode converts a single ZapScript button symbol to a uinput code. 58 | func ToGamepadCode(name string) (int, bool) { 59 | if len(name) > 2 && name[0] == '{' && name[len(name)-1] == '}' { 60 | name = strings.ToLower(name) 61 | } 62 | if v, ok := GamepadMap[name]; ok { 63 | return v, ok 64 | } else { 65 | return 0, false 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/utils/linuxinput/linuxinput.go: -------------------------------------------------------------------------------- 1 | package linuxinput 2 | 3 | import ( 4 | "github.com/ZaparooProject/zaparoo-core/pkg/utils/linuxinput/keyboardmap" 5 | "github.com/bendahl/uinput" 6 | "time" 7 | ) 8 | 9 | const ( 10 | DeviceName = "Zaparoo" 11 | DefaultTimeout = 40 * time.Millisecond 12 | uinputDev = "/dev/uinput" 13 | ) 14 | 15 | type Keyboard struct { 16 | Device uinput.Keyboard 17 | Delay time.Duration 18 | } 19 | 20 | // NewKeyboard returns a uinput virtual keyboard device. It takes a delay 21 | // duration which is used between presses to avoid overloading the OS or user 22 | // applications. This device must be closed when the service stops. 23 | func NewKeyboard(delay time.Duration) (Keyboard, error) { 24 | keyboardmap.SetupLegacyKeyboardMap() 25 | kbd, err := uinput.CreateKeyboard(uinputDev, []byte(DeviceName)) 26 | if err != nil { 27 | return Keyboard{}, err 28 | } 29 | return Keyboard{ 30 | Device: kbd, 31 | Delay: delay, 32 | }, nil 33 | } 34 | 35 | func (k *Keyboard) Close() error { 36 | return k.Device.Close() 37 | } 38 | 39 | func (k *Keyboard) Press(key int) error { 40 | if key < 0 { 41 | return k.Combo(42, -key) 42 | } 43 | 44 | err := k.Device.KeyDown(key) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | time.Sleep(k.Delay) 50 | 51 | return k.Device.KeyUp(key) 52 | } 53 | 54 | func (k *Keyboard) Combo(keys ...int) error { 55 | for _, key := range keys { 56 | err := k.Device.KeyDown(key) 57 | if err != nil { 58 | return err 59 | } 60 | } 61 | time.Sleep(k.Delay) 62 | for _, key := range keys { 63 | err := k.Device.KeyUp(key) 64 | if err != nil { 65 | return err 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | type Gamepad struct { 72 | Device uinput.Gamepad 73 | Delay time.Duration 74 | } 75 | 76 | // NewGamepad returns a uinput virtual gamepad device. It takes a delay 77 | // duration which is used between presses to avoid overloading the OS or user 78 | // applications. This device must be closed when the service stops. 79 | func NewGamepad(delay time.Duration) (Gamepad, error) { 80 | gpd, err := uinput.CreateGamepad( 81 | uinputDev, 82 | []byte(DeviceName), 83 | 0x1234, 84 | 0x5678, 85 | ) 86 | if err != nil { 87 | return Gamepad{}, err 88 | } 89 | return Gamepad{ 90 | Device: gpd, 91 | Delay: delay, 92 | }, nil 93 | } 94 | 95 | func (k *Gamepad) Close() error { 96 | return k.Device.Close() 97 | } 98 | 99 | func (k *Gamepad) Press(key int) error { 100 | err := k.Device.ButtonDown(key) 101 | if err != nil { 102 | return err 103 | } 104 | time.Sleep(k.Delay) 105 | return k.Device.ButtonUp(key) 106 | } 107 | 108 | func (k *Gamepad) Combo(keys ...int) error { 109 | for _, key := range keys { 110 | err := k.Device.ButtonDown(key) 111 | if err != nil { 112 | return err 113 | } 114 | } 115 | time.Sleep(k.Delay) 116 | for _, key := range keys { 117 | err := k.Device.ButtonUp(key) 118 | if err != nil { 119 | return err 120 | } 121 | } 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /pkg/utils/logging.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 9 | "github.com/rs/zerolog" 10 | "github.com/rs/zerolog/pkgerrors" 11 | 12 | "github.com/ZaparooProject/zaparoo-core/pkg/config" 13 | "github.com/rs/zerolog/log" 14 | "gopkg.in/natefinch/lumberjack.v2" 15 | ) 16 | 17 | func InitLogging(pl platforms.Platform, writers []io.Writer) error { 18 | err := os.MkdirAll(pl.Settings().TempDir, 0755) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | var logWriters = []io.Writer{&lumberjack.Logger{ 24 | Filename: filepath.Join(pl.Settings().TempDir, config.LogFile), 25 | MaxSize: 1, 26 | MaxBackups: 2, 27 | }} 28 | 29 | if len(writers) > 0 { 30 | logWriters = append(logWriters, writers...) 31 | } 32 | 33 | zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack 34 | 35 | log.Logger = log.Output(io.MultiWriter(logWriters...)) 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/zapscript/http.go: -------------------------------------------------------------------------------- 1 | package zapscript 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/rs/zerolog/log" 9 | 10 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 11 | ) 12 | 13 | func cmdHttpGet(_ platforms.Platform, env platforms.CmdEnv) (platforms.CmdResult, error) { 14 | go func() { 15 | resp, err := http.Get(env.Args) 16 | if err != nil { 17 | log.Error().Err(err).Msgf("getting url: %s", env.Args) 18 | return 19 | } 20 | err = resp.Body.Close() 21 | if err != nil { 22 | log.Error().Err(err).Msgf("closing body") 23 | return 24 | } 25 | }() 26 | 27 | return platforms.CmdResult{}, nil 28 | } 29 | 30 | func cmdHttpPost(pl platforms.Platform, env platforms.CmdEnv) (platforms.CmdResult, error) { 31 | parts := strings.SplitN(env.Args, ",", 3) 32 | if len(parts) < 3 { 33 | return platforms.CmdResult{}, fmt.Errorf("invalid post format: %s", env.Args) 34 | } 35 | 36 | url, format, data := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), strings.TrimSpace(parts[2]) 37 | 38 | go func() { 39 | resp, err := http.Post(url, format, strings.NewReader(data)) 40 | if err != nil { 41 | log.Error().Err(err).Msgf("error posting to url: %s", env.Args) 42 | return 43 | } 44 | err = resp.Body.Close() 45 | if err != nil { 46 | log.Error().Err(err).Msgf("closing body") 47 | return 48 | } 49 | }() 50 | 51 | return platforms.CmdResult{}, nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/zapscript/models/zapscript.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "encoding/json" 4 | 5 | const ( 6 | ZapScriptCmdLaunch = "launch" 7 | ZapScriptCmdLaunchSystem = "launch.system" 8 | ZapScriptCmdLaunchRandom = "launch.random" 9 | ZapScriptCmdLaunchSearch = "launch.search" 10 | 11 | ZapScriptCmdPlaylistPlay = "playlist.play" 12 | ZapScriptCmdPlaylistStop = "playlist.stop" 13 | ZapScriptCmdPlaylistNext = "playlist.next" 14 | ZapScriptCmdPlaylistPrevious = "playlist.previous" 15 | ZapScriptCmdPlaylistGoto = "playlist.goto" 16 | ZapScriptCmdPlaylistPause = "playlist.pause" 17 | ZapScriptCmdPlaylistLoad = "playlist.load" 18 | ZapScriptCmdPlaylistOpen = "playlist.open" 19 | 20 | ZapScriptCmdExecute = "execute" 21 | ZapScriptCmdDelay = "delay" 22 | ZapScriptCmdEvaluate = "evaluate" 23 | ZapScriptCmdStop = "stop" 24 | ZapScriptCmdEcho = "echo" 25 | 26 | ZapScriptCmdMisterINI = "mister.ini" 27 | ZapScriptCmdMisterCore = "mister.core" 28 | ZapScriptCmdMisterScript = "mister.script" 29 | ZapScriptCmdMisterMGL = "mister.mgl" 30 | 31 | ZapScriptCmdHTTPGet = "http.get" 32 | ZapScriptCmdHTTPPost = "http.post" 33 | 34 | ZapScriptCmdInputKeyboard = "input.keyboard" 35 | ZapScriptCmdInputGamepad = "input.gamepad" 36 | ZapScriptCmdInputCoinP1 = "input.coinp1" 37 | ZapScriptCmdInputCoinP2 = "input.coinp2" 38 | 39 | ZapScriptCmdUINotice = "ui.notice" 40 | ZapScriptCmdUIPicker = "ui.picker" 41 | 42 | ZapScriptCmdInputKey = "input.key" // DEPRECATED 43 | ZapScriptCmdKey = "key" // DEPRECATED 44 | ZapScriptCmdCoinP1 = "coinp1" // DEPRECATED 45 | ZapScriptCmdCoinP2 = "coinp2" // DEPRECATED 46 | ZapScriptCmdRandom = "random" // DEPRECATED 47 | ZapScriptCmdShell = "shell" // DEPRECATED 48 | ZapScriptCmdCommand = "command" // DEPRECATED 49 | ZapScriptCmdINI = "ini" // DEPRECATED 50 | ZapScriptCmdSystem = "system" // DEPRECATED 51 | ZapScriptCmdGet = "get" // DEPRECATED 52 | ) 53 | 54 | type ZapScript struct { 55 | ZapScript int `json:"zapscript"` // schema version 56 | Name *string `json:"name"` // optional display name 57 | Cmds []ZapScriptCmd `json:"cmds"` 58 | } 59 | 60 | type ZapScriptCmd struct { 61 | ID string `json:"id"` // internal id of command instance 62 | Name *string `json:"name"` // optional display name 63 | Cmd string `json:"cmd"` 64 | Args json.RawMessage `json:"args"` 65 | } 66 | 67 | type CmdEvaluateArgs struct { 68 | ZapScript string `json:"zapscript" arg:"position=1"` 69 | } 70 | 71 | type CmdLaunchArgs struct { 72 | Path string `json:"path" arg:"position=1"` 73 | Launcher *string `json:"launcher"` 74 | Name *string `json:"name"` 75 | System *string `json:"system"` 76 | URL *string `json:"url"` 77 | PreNotice *string `json:"preNotice"` 78 | } 79 | 80 | type CmdNotice struct { 81 | Text string `json:"text" arg:"position=1"` 82 | Loader *bool `json:"loader"` 83 | } 84 | 85 | type CmdPicker struct { 86 | Items []ZapScript `json:"items"` 87 | } 88 | -------------------------------------------------------------------------------- /pkg/zapscript/playlist_test.go: -------------------------------------------------------------------------------- 1 | package zapscript 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/ZaparooProject/zaparoo-core/pkg/service/playlists" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestReadPlsFile(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | plsContent string 16 | expectedMedia []playlists.PlaylistMedia 17 | expectedErrMsg string 18 | }{ 19 | { 20 | name: "valid_pls_with_multiple_entries", 21 | plsContent: `[playlist] 22 | File1=/path/to/song1.mp3 23 | Title1=Song 1 24 | File2=/path/to/song2.mp3 25 | Title2=Song 2`, 26 | expectedMedia: []playlists.PlaylistMedia{ 27 | {Name: "Song 1", Path: "/path/to/song1.mp3"}, 28 | {Name: "Song 2", Path: "/path/to/song2.mp3"}, 29 | }, 30 | expectedErrMsg: "", 31 | }, 32 | { 33 | name: "valid_pls_with_missing_titles", 34 | plsContent: `[playlist] 35 | File1=/path/to/song1.mp3 36 | File2=/path/to/song2.mp3`, 37 | expectedMedia: []playlists.PlaylistMedia{ 38 | {Name: "", Path: "/path/to/song1.mp3"}, 39 | {Name: "", Path: "/path/to/song2.mp3"}, 40 | }, 41 | expectedErrMsg: "", 42 | }, 43 | { 44 | name: "valid_pls_with_missing_files", 45 | plsContent: `[playlist] 46 | Title1=Song 1 47 | File2=/path/to/song2.mp3`, 48 | expectedMedia: []playlists.PlaylistMedia{ 49 | {Name: "", Path: "/path/to/song2.mp3"}, 50 | }, 51 | expectedErrMsg: "", 52 | }, 53 | { 54 | name: "missing_header", 55 | plsContent: `File1=/path/to/song1.mp3 56 | Title1=Song 1 57 | File2=/path/to/song2.mp3 58 | Title2=Song 2`, 59 | expectedMedia: nil, 60 | expectedErrMsg: "no entries found in pls file", 61 | }, 62 | { 63 | name: "empty_pls_file", 64 | plsContent: ` 65 | `, 66 | expectedMedia: nil, 67 | expectedErrMsg: "no entries found in pls file", 68 | }, 69 | { 70 | name: "invalid_entry_ids", 71 | plsContent: `[playlist] 72 | FileA=/path/to/song1.mp3 73 | TitleB=Song 1`, 74 | expectedMedia: nil, 75 | expectedErrMsg: "no entries found in pls file", 76 | }, 77 | } 78 | 79 | for _, tt := range tests { 80 | t.Run(tt.name, func(t *testing.T) { 81 | plsFile := filepath.Join(t.TempDir(), "test.pls") 82 | err := os.WriteFile(plsFile, []byte(tt.plsContent), 0644) 83 | assert.NoError(t, err) 84 | 85 | media, err := readPlsFile(plsFile) 86 | if tt.expectedErrMsg != "" { 87 | assert.Error(t, err) 88 | assert.Contains(t, err.Error(), tt.expectedErrMsg) 89 | } else { 90 | assert.NoError(t, err) 91 | assert.Equal(t, tt.expectedMedia, media) 92 | } 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /pkg/zapscript/utils.go: -------------------------------------------------------------------------------- 1 | package zapscript 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/rs/zerolog/log" 11 | 12 | "github.com/ZaparooProject/zaparoo-core/pkg/platforms" 13 | ) 14 | 15 | func cmdEcho(_ platforms.Platform, env platforms.CmdEnv) (platforms.CmdResult, error) { 16 | log.Info().Msg(env.Args) 17 | return platforms.CmdResult{}, nil 18 | } 19 | 20 | func cmdStop(pl platforms.Platform, _ platforms.CmdEnv) (platforms.CmdResult, error) { 21 | log.Info().Msg("stopping media") 22 | return platforms.CmdResult{ 23 | MediaChanged: true, 24 | }, pl.StopActiveLauncher() 25 | } 26 | 27 | func cmdDelay(_ platforms.Platform, env platforms.CmdEnv) (platforms.CmdResult, error) { 28 | log.Info().Msgf("delaying for: %s", env.Args) 29 | 30 | amount, err := strconv.Atoi(env.Args) 31 | if err != nil { 32 | return platforms.CmdResult{}, err 33 | } 34 | 35 | time.Sleep(time.Duration(amount) * time.Millisecond) 36 | 37 | return platforms.CmdResult{}, nil 38 | } 39 | 40 | func cmdExecute(_ platforms.Platform, env platforms.CmdEnv) (platforms.CmdResult, error) { 41 | if env.Unsafe { 42 | return platforms.CmdResult{}, fmt.Errorf("command cannot be run from a remote source") 43 | } else if !env.Cfg.IsExecuteAllowed(env.Args) { 44 | return platforms.CmdResult{}, fmt.Errorf("execute not allowed: %s", env.Args) 45 | } 46 | 47 | // very basic support for treating quoted strings as a single field 48 | // probably needs to be expanded to include single quotes and 49 | // escaped characters 50 | // TODO: this probably doesn't work on windows? 51 | sb := &strings.Builder{} 52 | quoted := false 53 | var tokenArgs []string 54 | for _, r := range env.Args { 55 | if r == '"' { 56 | quoted = !quoted 57 | sb.WriteRune(r) 58 | } else if !quoted && r == ' ' { 59 | tokenArgs = append(tokenArgs, sb.String()) 60 | sb.Reset() 61 | } else { 62 | sb.WriteRune(r) 63 | } 64 | } 65 | if sb.Len() > 0 { 66 | tokenArgs = append(tokenArgs, sb.String()) 67 | } 68 | 69 | if len(tokenArgs) == 0 { 70 | return platforms.CmdResult{}, fmt.Errorf("execute command is empty") 71 | } 72 | 73 | cmd := tokenArgs[0] 74 | var cmdArgs []string 75 | 76 | if len(tokenArgs) > 1 { 77 | cmdArgs = tokenArgs[1:] 78 | } 79 | 80 | return platforms.CmdResult{}, exec.Command(cmd, cmdArgs...).Run() 81 | } 82 | -------------------------------------------------------------------------------- /scripts/cross/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:plucky 2 | 3 | RUN apt-get update -y && apt-get upgrade -y 4 | RUN apt-get install ca-certificates openssl -y && update-ca-certificates 5 | RUN apt-get install build-essential git curl golang-go gcc-mingw-w64 -y 6 | 7 | # install zig 8 | RUN mkdir -p /opt/zig && \ 9 | curl -sSL -o /tmp/zig.tar.xz \ 10 | "https://ziglang.org/builds/zig-linux-$(uname -m)-0.15.0-dev.552+bc2f7c754.tar.xz" && \ 11 | tar --strip-components=1 -C /opt/zig -xf /tmp/zig.tar.xz && \ 12 | rm /tmp/zig.tar.xz 13 | 14 | # install macos sdk 15 | RUN mkdir -p /opt/macosx-sdk && \ 16 | curl -sSL -o /tmp/macosx-sdk.tar.xz \ 17 | "https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz" && \ 18 | tar --strip-components=1 -C /opt/macosx-sdk -xf /tmp/macosx-sdk.tar.xz && \ 19 | rm /tmp/macosx-sdk.tar.xz 20 | 21 | # install build tools 22 | RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin 23 | RUN env GOBIN=/usr/local/bin go install github.com/konoui/lipo@v0.9.3 24 | RUN env GOBIN=/usr/local/bin go install github.com/tc-hib/go-winres@latest 25 | 26 | # match output file permissions with host 27 | ARG UID=1000 28 | ARG GID=1000 29 | RUN groupadd -f -g $GID build && \ 30 | useradd -m -u $UID -g $GID build 31 | USER build 32 | 33 | ENV PATH="/opt/zig:${PATH}" 34 | # avoids an error where zig tries to write to /usr 35 | ENV ZIG_GLOBAL_CACHE_DIR="/home/build/zig-cache" 36 | 37 | RUN git config --global --add safe.directory /build 38 | WORKDIR /build 39 | -------------------------------------------------------------------------------- /scripts/linux_amd64/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amd64/debian:bookworm 2 | 3 | RUN apt-get update -y && apt-get upgrade -y 4 | RUN apt-get install ca-certificates openssl -y && update-ca-certificates 5 | RUN apt-get install build-essential git curl -y && \ 6 | apt-get install libusb-dev libtool autoconf automake pkgconf -y 7 | 8 | COPY --from=golang:1.23 /usr/local/go/ /usr/local/go/ 9 | ENV PATH="/usr/local/go/bin:${PATH}" 10 | 11 | RUN mkdir /internal 12 | 13 | # install custom libnfc 14 | RUN cd /internal && \ 15 | git clone --depth 1 https://github.com/nfc-tools/libnfc.git 16 | RUN cd /internal/libnfc && \ 17 | autoreconf -vis && \ 18 | ./configure --with-drivers=acr122_usb,pn532_i2c,pn532_uart && \ 19 | make -j "$(nproc)" && \ 20 | make install 21 | 22 | # install task 23 | RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin 24 | 25 | # drop permissions on output files 26 | ARG UID=1000 27 | ARG GID=1000 28 | RUN groupadd -f -g $GID build && useradd -m -u $UID -g $GID build 29 | USER build 30 | 31 | WORKDIR /build 32 | RUN git config --global --add safe.directory /build 33 | -------------------------------------------------------------------------------- /scripts/linux_arm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM arm32v7/debian:bookworm 2 | 3 | RUN apt-get update -y && apt-get upgrade -y 4 | RUN apt-get install ca-certificates openssl -y && update-ca-certificates 5 | RUN apt-get install build-essential git -y && \ 6 | apt-get install libusb-dev libtool autoconf automake pkgconf -y 7 | 8 | COPY --from=golang:1.23 /usr/local/go/ /usr/local/go/ 9 | ENV PATH="/usr/local/go/bin:${PATH}" 10 | 11 | RUN mkdir /internal 12 | 13 | # install custom libnfc 14 | RUN cd /internal && \ 15 | git clone --depth 1 https://github.com/nfc-tools/libnfc.git 16 | RUN cd /internal/libnfc && \ 17 | autoreconf -vis && \ 18 | ./configure --with-drivers=acr122_usb,pn532_i2c,pn532_uart && \ 19 | make -j "$(nproc)" && \ 20 | make install 21 | 22 | # manually build arm32 version of task 23 | RUN env GOBIN=/usr/local/go/bin go install github.com/go-task/task/v3/cmd/task@latest 24 | 25 | # drop permissions on output files 26 | ARG UID=1000 27 | ARG GID=1000 28 | RUN groupadd -f -g $GID build && useradd -m -u $UID -g $GID build 29 | USER build 30 | 31 | WORKDIR /build 32 | RUN git config --global --add safe.directory /build 33 | -------------------------------------------------------------------------------- /scripts/linux_arm64/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM arm64v8/debian:bookworm 2 | 3 | RUN apt-get update -y && apt-get upgrade -y 4 | RUN apt-get install ca-certificates openssl -y && update-ca-certificates 5 | RUN apt-get install build-essential git curl -y && \ 6 | apt-get install libusb-dev libtool autoconf automake pkgconf -y 7 | 8 | COPY --from=golang:1.23 /usr/local/go/ /usr/local/go/ 9 | ENV PATH="/usr/local/go/bin:${PATH}" 10 | 11 | RUN mkdir /internal 12 | 13 | # install custom libnfc 14 | RUN cd /internal && \ 15 | git clone --depth 1 https://github.com/nfc-tools/libnfc.git 16 | RUN cd /internal/libnfc && \ 17 | autoreconf -vis && \ 18 | ./configure --with-drivers=acr122_usb,pn532_i2c,pn532_uart && \ 19 | make -j "$(nproc)" && \ 20 | make install 21 | 22 | # install task 23 | RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin 24 | 25 | # drop permissions on output files 26 | ARG UID=1000 27 | ARG GID=1000 28 | RUN groupadd -f -g $GID build && useradd -m -u $UID -g $GID build 29 | USER build 30 | 31 | WORKDIR /build 32 | RUN git config --global --add safe.directory /build 33 | -------------------------------------------------------------------------------- /scripts/mister/repo/tapto.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_id": "mrext/tapto", 3 | "timestamp": 1746185095, 4 | "files": {}, 5 | "folders": {}, 6 | "zips": { 7 | "tapto": { 8 | "contents_file": { 9 | "hash": "da072799723d617b0ab9017cf3b1b1ac", 10 | "size": 9169802, 11 | "url": "https://github.com/ZaparooProject/zaparoo-core/releases/download/v2.3.1/zaparoo-mister_arm-2.3.1.zip" 12 | }, 13 | "description": "Extracting Zaparoo release", 14 | "internal_summary": { 15 | "files": { 16 | "Scripts/zaparoo.sh": { 17 | "hash": "c328986acbf805880ad2c89ba745859a", 18 | "size": 18916416, 19 | "reboot": true, 20 | "tags": [ 21 | "tapto" 22 | ], 23 | "zip_id": "tapto", 24 | "zip_path": "zaparoo.sh" 25 | } 26 | }, 27 | "folders": { 28 | "Scripts/": { 29 | "zip_id": "tapto" 30 | } 31 | } 32 | }, 33 | "kind": "extract_single_files" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /scripts/tasks/batocera.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | setup-arm: 14 | internal: true 15 | cmds: 16 | - task: :docker:build-image 17 | vars: 18 | IMAGE_NAME: zaparoo/linux-arm-build 19 | DOCKERFILE: "./scripts/linux_arm" 20 | DOCKER_PLATFORM: linux/arm/v7 21 | 22 | setup-amd64: 23 | internal: true 24 | cmds: 25 | - task: :docker:build-image 26 | vars: 27 | IMAGE_NAME: zaparoo/linux-amd64-build 28 | DOCKERFILE: "./scripts/linux_amd64" 29 | DOCKER_PLATFORM: linux/amd64 30 | 31 | build-arm64: 32 | cmds: 33 | - task: setup-arm64 34 | - task: :docker:build-app 35 | vars: 36 | PLATFORM: batocera 37 | BUILD_ARCH: arm64 38 | DOCKER_PLATFORM: linux/arm64/v8 39 | IMAGE_NAME: zaparoo/linux-arm64-build 40 | APP_BIN: zaparoo 41 | 42 | build-amd64: 43 | cmds: 44 | - task: setup-amd64 45 | - task: :docker:build-app 46 | vars: 47 | PLATFORM: batocera 48 | BUILD_ARCH: amd64 49 | DOCKER_PLATFORM: linux/amd64 50 | IMAGE_NAME: zaparoo/linux-amd64-build 51 | APP_BIN: zaparoo 52 | 53 | build-arm: 54 | cmds: 55 | - task: setup-arm 56 | - task: :docker:build-app 57 | vars: 58 | PLATFORM: batocera 59 | BUILD_ARCH: arm 60 | DOCKER_PLATFORM: linux/arm/v7 61 | IMAGE_NAME: zaparoo/linux-arm-build 62 | APP_BIN: zaparoo 63 | 64 | deploy-arm64: 65 | cmds: 66 | - task: build-arm64 67 | - scp _build/batocera_arm64/zaparoo root@${BATOCERA_IP}:/userdata/system/zaparoo 68 | - ssh root@${BATOCERA_IP} /userdata/system/zaparoo -service restart -------------------------------------------------------------------------------- /scripts/tasks/bazzite.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | setup-amd64: 14 | internal: true 15 | cmds: 16 | - task: :docker:build-image 17 | vars: 18 | IMAGE_NAME: zaparoo/linux-amd64-build 19 | DOCKERFILE: "./scripts/linux_amd64" 20 | DOCKER_PLATFORM: linux/amd64 21 | 22 | build-arm64: 23 | cmds: 24 | - task: setup-arm64 25 | - task: :docker:build-app 26 | vars: 27 | PLATFORM: bazzite 28 | BUILD_ARCH: arm64 29 | DOCKER_PLATFORM: linux/arm64/v8 30 | IMAGE_NAME: zaparoo/linux-arm64-build 31 | APP_BIN: zaparoo 32 | 33 | build-amd64: 34 | cmds: 35 | - task: setup-amd64 36 | - task: :docker:build-app 37 | vars: 38 | PLATFORM: bazzite 39 | BUILD_ARCH: amd64 40 | DOCKER_PLATFORM: linux/amd64 41 | IMAGE_NAME: zaparoo/linux-amd64-build 42 | APP_BIN: zaparoo -------------------------------------------------------------------------------- /scripts/tasks/chimeraos.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | setup-amd64: 14 | internal: true 15 | cmds: 16 | - task: :docker:build-image 17 | vars: 18 | IMAGE_NAME: zaparoo/linux-amd64-build 19 | DOCKERFILE: "./scripts/linux_amd64" 20 | DOCKER_PLATFORM: linux/amd64 21 | 22 | build-arm64: 23 | cmds: 24 | - task: setup-arm64 25 | - task: :docker:build-app 26 | vars: 27 | PLATFORM: chimeraos 28 | BUILD_ARCH: arm64 29 | DOCKER_PLATFORM: linux/arm64/v8 30 | IMAGE_NAME: zaparoo/linux-arm64-build 31 | APP_BIN: zaparoo 32 | 33 | build-amd64: 34 | cmds: 35 | - task: setup-amd64 36 | - task: :docker:build-app 37 | vars: 38 | PLATFORM: chimeraos 39 | BUILD_ARCH: amd64 40 | DOCKER_PLATFORM: linux/amd64 41 | IMAGE_NAME: zaparoo/linux-amd64-build 42 | APP_BIN: zaparoo -------------------------------------------------------------------------------- /scripts/tasks/docker.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | build-image: 5 | internal: true 6 | sources: 7 | - scripts/**/Dockerfile 8 | status: 9 | - docker image inspect {{.IMAGE_NAME}} 10 | cmds: 11 | - >- 12 | docker build 13 | {{if .DOCKER_PLATFORM}}--platform {{.DOCKER_PLATFORM}}{{end}} 14 | --build-arg UID=$UID --build-arg GID=$GID 15 | {{default "" .CACHE_ARGS}} 16 | -t {{.IMAGE_NAME}} 17 | {{.DOCKERFILE}} 18 | 19 | build-app: 20 | internal: true 21 | vars: 22 | BUILD_DIR: "_build/{{.PLATFORM}}_{{default ARCH .BUILD_ARCH}}" 23 | APP_BIN: '{{default "zaparoo" .APP_BIN}}' 24 | cmds: 25 | - task: run 26 | vars: 27 | IMAGE_NAME: "{{.IMAGE_NAME}}" 28 | DOCKER_PLATFORM: "{{.DOCKER_PLATFORM}}" 29 | PLATFORM: "{{.PLATFORM}}" 30 | EXEC: '{{default "task build" .EXEC}}' 31 | EXTRA_DOCKER_ARGS: '{{default "" .EXTRA_DOCKER_ARGS}}' 32 | BUILD_ARCH: "{{default ARCH .BUILD_ARCH}}" 33 | BUILD_OS: "{{.BUILD_OS}}" 34 | NO_LIBNFC: "{{.NO_LIBNFC}}" 35 | NO_STATIC: "{{.NO_STATIC}}" 36 | CC: "{{.CC}}" 37 | CXX: "{{.CXX}}" 38 | EXTRA_LDFLAGS: "{{.EXTRA_LDFLAGS}}" 39 | APP_BIN: "{{.APP_BIN}}" 40 | - >- 41 | go run scripts/tasks/utils/makezip.go 42 | {{.PLATFORM}} {{.BUILD_DIR}} {{.APP_BIN}} 43 | "zaparoo-{{.PLATFORM}}_{{default ARCH .BUILD_ARCH}}-${APP_VERSION}.zip" 44 | 45 | run: 46 | internal: true 47 | vars: 48 | BUILDCACHE: '{{if eq OS "windows"}}{{.LOCALAPPDATA}}\\go-build{{else}}{{.HOME}}/.cache/go-build{{end}}' 49 | GOCACHE: '{{if eq OS "windows"}}{{.USERPROFILE}}\\go{{else}}{{.HOME}}/go{{end}}' 50 | IMG_BUILDCACHE: /home/build/.cache/go-build 51 | IMG_GOCACHE: /home/build/go 52 | cmds: 53 | - '{{if eq OS "windows"}}cmd /c if not exist "{{.BUILDCACHE}}" mkdir "{{.BUILDCACHE}}"{{else}}mkdir -p "{{.BUILDCACHE}}"{{end}}' 54 | - '{{if eq OS "windows"}}cmd /c if not exist "{{.GOCACHE}}" mkdir "{{.GOCACHE}}"{{else}}mkdir -p "{{.GOCACHE}}"{{end}}' 55 | - >- 56 | docker run --rm 57 | {{if .DOCKER_PLATFORM}}--platform {{.DOCKER_PLATFORM}}{{end}} 58 | -v "{{.BUILDCACHE}}:{{.IMG_BUILDCACHE}}" 59 | -v "{{.GOCACHE}}:{{.IMG_GOCACHE}}" 60 | -v "${PWD}:/build" 61 | -e PLATFORM={{.PLATFORM}} 62 | -e APP_BIN={{.APP_BIN}} 63 | {{if .BUILD_ARCH}}-e BUILD_ARCH={{.BUILD_ARCH}}{{end}} 64 | {{if .BUILD_OS}}-e BUILD_OS={{.BUILD_OS}}{{end}} 65 | {{if .NO_LIBNFC}}-e NO_LIBNFC={{.NO_LIBNFC}}{{end}} 66 | {{if .NO_STATIC}}-e NO_STATIC={{.NO_STATIC}}{{end}} 67 | {{if .CC}}-e CC="{{.CC}}"{{end}} 68 | {{if .CXX}}-e CXX="{{.CXX}}"{{end}} 69 | {{if .EXTRA_LDFLAGS}}-e EXTRA_LDFLAGS="{{.EXTRA_LDFLAGS}}"{{end}} 70 | {{.EXTRA_DOCKER_ARGS}} 71 | {{.IMAGE_NAME}} {{.EXEC}} -------------------------------------------------------------------------------- /scripts/tasks/libreelec.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | setup-amd64: 14 | internal: true 15 | cmds: 16 | - task: :docker:build-image 17 | vars: 18 | IMAGE_NAME: zaparoo/linux-amd64-build 19 | DOCKERFILE: "./scripts/linux_amd64" 20 | DOCKER_PLATFORM: linux/amd64 21 | 22 | build-arm64: 23 | cmds: 24 | - task: setup-arm64 25 | - task: :docker:build-app 26 | vars: 27 | PLATFORM: libreelec 28 | BUILD_ARCH: arm64 29 | DOCKER_PLATFORM: linux/arm64/v8 30 | IMAGE_NAME: zaparoo/linux-arm64-build 31 | APP_BIN: zaparoo 32 | 33 | build-amd64: 34 | cmds: 35 | - task: setup-amd64 36 | - task: :docker:build-app 37 | vars: 38 | PLATFORM: libreelec 39 | BUILD_ARCH: amd64 40 | DOCKER_PLATFORM: linux/amd64 41 | IMAGE_NAME: zaparoo/linux-amd64-build 42 | APP_BIN: zaparoo -------------------------------------------------------------------------------- /scripts/tasks/linux.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | setup-amd64: 14 | internal: true 15 | cmds: 16 | - task: :docker:build-image 17 | vars: 18 | IMAGE_NAME: zaparoo/linux-amd64-build 19 | DOCKERFILE: "./scripts/linux_amd64" 20 | DOCKER_PLATFORM: linux/amd64 21 | 22 | build-arm64: 23 | cmds: 24 | - task: setup-arm64 25 | - task: :docker:build-app 26 | vars: 27 | PLATFORM: linux 28 | BUILD_ARCH: arm64 29 | DOCKER_PLATFORM: linux/arm64/v8 30 | IMAGE_NAME: zaparoo/linux-arm64-build 31 | APP_BIN: zaparoo 32 | 33 | build-amd64: 34 | cmds: 35 | - task: setup-amd64 36 | - task: :docker:build-app 37 | vars: 38 | PLATFORM: linux 39 | BUILD_ARCH: amd64 40 | DOCKER_PLATFORM: linux/amd64 41 | IMAGE_NAME: zaparoo/linux-amd64-build 42 | APP_BIN: zaparoo -------------------------------------------------------------------------------- /scripts/tasks/mac.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | build-app: 5 | cmds: 6 | - task: build-arm64 7 | - task: build-amd64 8 | - rm -rf "./_build/mac_universal" && mkdir -p "./_build/mac_universal" 9 | - cp -r "./cmd/mac/app/Zaparoo Core.app" "./_build/mac_universal/Zaparoo Core.app" 10 | - task: :docker:run 11 | vars: 12 | BUILD_OS: darwin 13 | BUILD_ARCH: arm64 14 | PLATFORM: mac 15 | IMAGE_NAME: zaparoo/cross-build 16 | EXEC: lipo -output ./_build/mac_universal/zaparoo -create ./_build/mac_arm64/zaparoo ./_build/mac_amd64/zaparoo 17 | - cp ./_build/mac_universal/zaparoo "./_build/mac_universal/Zaparoo Core.app/Contents/MacOS/Zaparoo Core" 18 | 19 | setup: 20 | internal: true 21 | cmds: 22 | - task: :docker:build-image 23 | vars: 24 | IMAGE_NAME: zaparoo/cross-build 25 | DOCKERFILE: "./scripts/cross" 26 | 27 | build-arm64: 28 | cmds: 29 | - task: setup 30 | - task: :docker:build-app 31 | vars: 32 | BUILD_OS: darwin 33 | BUILD_ARCH: arm64 34 | PLATFORM: mac 35 | IMAGE_NAME: zaparoo/cross-build 36 | NO_LIBNFC: true 37 | NO_STATIC: true 38 | CC: >- 39 | zig cc -w --target=aarch64-macos 40 | -I/opt/macosx-sdk/usr/include 41 | -L/opt/macosx-sdk/usr/lib 42 | -F/opt/macosx-sdk/System/Library/Frameworks 43 | CXX: >- 44 | zig c++ -w --target=aarch64-macos 45 | -I/opt/macosx-sdk/usr/include 46 | -L/opt/macosx-sdk/usr/lib 47 | -F/opt/macosx-sdk/System/Library/Frameworks 48 | 49 | build-amd64: 50 | cmds: 51 | - task: setup 52 | - task: :docker:build-app 53 | vars: 54 | BUILD_OS: darwin 55 | BUILD_ARCH: amd64 56 | PLATFORM: mac 57 | IMAGE_NAME: zaparoo/cross-build 58 | NO_LIBNFC: true 59 | NO_STATIC: true 60 | CC: >- 61 | zig cc -w --target=x86_64-macos 62 | -I/opt/macosx-sdk/usr/include 63 | -L/opt/macosx-sdk/usr/lib 64 | -F/opt/macosx-sdk/System/Library/Frameworks 65 | CXX: >- 66 | zig c++ -w --target=x86_64-macos 67 | -I/opt/macosx-sdk/usr/include 68 | -L/opt/macosx-sdk/usr/lib 69 | -F/opt/macosx-sdk/System/Library/Frameworks 70 | 71 | dev: 72 | cmds: 73 | - air --build.cmd "task mac:build-{{.CLI_ARGS}}" --build.bin "./_build/mac_{{.CLI_ARGS}}/zaparoo -daemon" 74 | -------------------------------------------------------------------------------- /scripts/tasks/mister.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm-build 10 | DOCKERFILE: "./scripts/linux_arm" 11 | DOCKER_PLATFORM: linux/arm/v7 12 | 13 | build-arm: 14 | cmds: 15 | - task: setup-arm 16 | - task: :docker:build-app 17 | vars: 18 | PLATFORM: mister 19 | BUILD_ARCH: arm 20 | DOCKER_PLATFORM: linux/arm/v7 21 | IMAGE_NAME: zaparoo/linux-arm-build 22 | APP_BIN: zaparoo.sh 23 | 24 | deploy-arm: 25 | cmds: 26 | - task: build-arm 27 | - scp _build/mister_arm/zaparoo.sh root@${MISTER_IP}:/media/fat/Scripts/zaparoo.sh 28 | - ssh root@${MISTER_IP} /media/fat/Scripts/zaparoo.sh -service restart -------------------------------------------------------------------------------- /scripts/tasks/mistex.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | build-arm64: 14 | cmds: 15 | - task: setup-arm64 16 | - task: :docker:build-app 17 | vars: 18 | PLATFORM: mistex 19 | BUILD_ARCH: arm64 20 | DOCKER_PLATFORM: linux/arm64/v8 21 | IMAGE_NAME: zaparoo/linux-arm64-build 22 | APP_BIN: zaparoo.sh 23 | 24 | deploy-arm64: 25 | cmds: 26 | - task: build-arm64 27 | - scp _build/mistex_arm64/zaparoo.sh root@${MISTEX_IP}:/media/fat/Scripts/zaparoo.sh 28 | - ssh root@${MISTEX_IP} /media/fat/Scripts/zaparoo.sh -service restart -------------------------------------------------------------------------------- /scripts/tasks/recalbox.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | setup-amd64: 14 | internal: true 15 | cmds: 16 | - task: :docker:build-image 17 | vars: 18 | IMAGE_NAME: zaparoo/linux-amd64-build 19 | DOCKERFILE: "./scripts/linux_amd64" 20 | DOCKER_PLATFORM: linux/amd64 21 | 22 | build-arm64: 23 | cmds: 24 | - task: setup-arm64 25 | - task: :docker:build-app 26 | vars: 27 | PLATFORM: recalbox 28 | BUILD_ARCH: arm64 29 | DOCKER_PLATFORM: linux/arm64/v8 30 | IMAGE_NAME: zaparoo/linux-arm64-build 31 | APP_BIN: zaparoo 32 | 33 | build-amd64: 34 | cmds: 35 | - task: setup-amd64 36 | - task: :docker:build-app 37 | vars: 38 | PLATFORM: recalbox 39 | BUILD_ARCH: amd64 40 | DOCKER_PLATFORM: linux/amd64 41 | IMAGE_NAME: zaparoo/linux-amd64-build 42 | APP_BIN: zaparoo -------------------------------------------------------------------------------- /scripts/tasks/retropie.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-arm64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-arm64-build 10 | DOCKERFILE: "./scripts/linux_arm64" 11 | DOCKER_PLATFORM: linux/arm64/v8 12 | 13 | setup-amd64: 14 | internal: true 15 | cmds: 16 | - task: :docker:build-image 17 | vars: 18 | IMAGE_NAME: zaparoo/linux-amd64-build 19 | DOCKERFILE: "./scripts/linux_amd64" 20 | DOCKER_PLATFORM: linux/amd64 21 | 22 | build-arm64: 23 | cmds: 24 | - task: setup-arm64 25 | - task: :docker:build-app 26 | vars: 27 | PLATFORM: retropie 28 | BUILD_ARCH: arm64 29 | DOCKER_PLATFORM: linux/arm64/v8 30 | IMAGE_NAME: zaparoo/linux-arm64-build 31 | APP_BIN: zaparoo 32 | 33 | build-amd64: 34 | cmds: 35 | - task: setup-amd64 36 | - task: :docker:build-app 37 | vars: 38 | PLATFORM: retropie 39 | BUILD_ARCH: amd64 40 | DOCKER_PLATFORM: linux/amd64 41 | IMAGE_NAME: zaparoo/linux-amd64-build 42 | APP_BIN: zaparoo -------------------------------------------------------------------------------- /scripts/tasks/steamos.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | setup-amd64: 5 | internal: true 6 | cmds: 7 | - task: :docker:build-image 8 | vars: 9 | IMAGE_NAME: zaparoo/linux-amd64-build 10 | DOCKERFILE: "./scripts/linux_amd64" 11 | DOCKER_PLATFORM: linux/amd64 12 | 13 | build-amd64: 14 | cmds: 15 | - task: setup-amd64 16 | - task: :docker:build-app 17 | vars: 18 | PLATFORM: steamos 19 | BUILD_ARCH: amd64 20 | DOCKER_PLATFORM: linux/amd64 21 | IMAGE_NAME: zaparoo/linux-amd64-build 22 | APP_BIN: zaparoo 23 | 24 | deploy-amd64: 25 | cmds: 26 | - task: build-amd64 27 | - scp _build/steamos_amd64/zaparoo deck@${STEAMOS_IP}:/home/deck/zaparoo/zaparoo -------------------------------------------------------------------------------- /scripts/tasks/test.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | build: 5 | cmds: 6 | - task: :build 7 | vars: 8 | PLATFORM: testscanner 9 | IMAGE_NAME: zaparoo/cross-build 10 | NO_LIBNFC: true 11 | NO_STATIC: true 12 | 13 | dev: 14 | cmds: 15 | - air --build.cmd "task test:build" --build.bin "./_build/testscanner_{{ARCH}}/zaparoo" 16 | -------------------------------------------------------------------------------- /scripts/tasks/utils/windowsiss.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "text/template" 10 | "time" 11 | ) 12 | 13 | type InnoSetupData struct { 14 | Version string 15 | Arch string 16 | ArchitecturesAllowed string 17 | ArchitecturesInstall64 string 18 | Year string 19 | } 20 | 21 | func main() { 22 | version := flag.String("version", "", "Version number") 23 | arch := flag.String("arch", "", "Architecture (386, amd64, or arm64)") 24 | flag.Parse() 25 | 26 | if *version == "" || *arch == "" { 27 | _, _ = fmt.Fprintf(os.Stderr, "Error: version and arch are required\n") 28 | os.Exit(1) 29 | } 30 | 31 | if strings.Contains(*version, "-dev") { 32 | *version = "0.0.0" 33 | } 34 | 35 | var archAllowed, archInstall string 36 | switch *arch { 37 | case "amd64": 38 | archAllowed = "x64compatible" 39 | archInstall = "x64compatible" 40 | case "arm64": 41 | archAllowed = "arm64" 42 | archInstall = "arm64" 43 | case "386": 44 | archAllowed = "x86compatible" 45 | archInstall = "" // 32-bit doesn't need special install mode 46 | default: 47 | _, _ = fmt.Fprintf(os.Stderr, "Error: unsupported architecture: %s\n", *arch) 48 | os.Exit(1) 49 | } 50 | 51 | data := InnoSetupData{ 52 | Version: *version, 53 | Arch: *arch, 54 | ArchitecturesAllowed: archAllowed, 55 | ArchitecturesInstall64: archInstall, 56 | Year: strconv.Itoa(time.Now().Year()), 57 | } 58 | 59 | tmpl, err := template.ParseFiles("cmd/windows/setup.iss.tmpl") 60 | if err != nil { 61 | _, _ = fmt.Fprintf(os.Stderr, "Error parsing template: %v\n", err) 62 | os.Exit(1) 63 | } 64 | 65 | outFile, err := os.Create("_build/windows_" + *arch + "/setup.iss") 66 | if err != nil { 67 | _, _ = fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err) 68 | os.Exit(1) 69 | } 70 | defer func(outFile *os.File) { 71 | _ = outFile.Close() 72 | }(outFile) 73 | 74 | if err := tmpl.Execute(outFile, data); err != nil { 75 | _, _ = fmt.Fprintf(os.Stderr, "Error executing template: %v\n", err) 76 | os.Exit(1) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /scripts/tasks/utils/windowsmeta.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "text/template" 9 | "time" 10 | ) 11 | 12 | type TemplateData struct { 13 | Version string 14 | Year int 15 | } 16 | 17 | func main() { 18 | version := flag.String("version", "", "Version number") 19 | flag.Parse() 20 | 21 | if *version == "" { 22 | _, _ = fmt.Fprintf(os.Stderr, "Error: version is required\n") 23 | os.Exit(1) 24 | } 25 | 26 | // If version contains -dev, use 0.0.0 27 | if strings.Contains(*version, "-dev") { 28 | *version = "0.0.0" 29 | } 30 | 31 | data := TemplateData{ 32 | Version: *version, 33 | Year: time.Now().Year(), 34 | } 35 | 36 | tmpl, err := template.ParseFiles("cmd/windows/winres/winres.json.tmpl") 37 | if err != nil { 38 | _, _ = fmt.Fprintf(os.Stderr, "Error parsing template: %v\n", err) 39 | os.Exit(1) 40 | } 41 | 42 | outFile, err := os.Create("cmd/windows/winres/winres.json") 43 | if err != nil { 44 | _, _ = fmt.Fprintf(os.Stderr, "Error creating output file: %v\n", err) 45 | os.Exit(1) 46 | } 47 | defer func(outFile *os.File) { 48 | _ = outFile.Close() 49 | }(outFile) 50 | 51 | if err := tmpl.Execute(outFile, data); err != nil { 52 | _, _ = fmt.Fprintf(os.Stderr, "Error executing template: %v\n", err) 53 | os.Exit(1) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /scripts/tasks/windows.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | build-metadata: 5 | internal: true 6 | cmds: 7 | - go run scripts/tasks/utils/windowsmeta.go -version "${APP_VERSION}" 8 | - task: :docker:run 9 | vars: 10 | IMAGE_NAME: zaparoo/cross-build 11 | EXEC: sh -c 'cd cmd/windows && go-winres make --arch="amd64,arm64,386"' 12 | 13 | build-installer: 14 | internal: true 15 | cmds: 16 | - go run scripts/tasks/utils/windowsiss.go -version "${APP_VERSION}" -arch "{{ARCH}}" 17 | - cp "cmd/windows/winres/icon.ico" "_build/windows_{{ARCH}}" 18 | - >- 19 | docker run --rm -i -v "${PWD}:/work" 20 | amake/innosetup 21 | "_build/windows_{{.BUILD_ARCH}}/setup.iss" 22 | 23 | setup: 24 | internal: true 25 | cmds: 26 | - task: :docker:build-image 27 | vars: 28 | IMAGE_NAME: zaparoo/cross-build 29 | DOCKERFILE: scripts/cross 30 | 31 | build-arm64: 32 | cmds: 33 | - task: setup 34 | - task: build-metadata 35 | - task: :docker:build-app 36 | vars: 37 | BUILD_OS: windows 38 | BUILD_ARCH: arm64 39 | PLATFORM: windows 40 | IMAGE_NAME: zaparoo/cross-build 41 | NO_LIBNFC: true 42 | APP_BIN: Zaparoo.exe 43 | EXTRA_LDFLAGS: -H=windowsgui 44 | CC: zig cc -w --target=aarch64-windows-gnu 45 | CXX: zig c++ -w --target=aarch64-windows-gnu 46 | 47 | build-amd64: 48 | cmds: 49 | - task: setup 50 | - task: build-metadata 51 | - task: :docker:build-app 52 | vars: 53 | BUILD_OS: windows 54 | BUILD_ARCH: amd64 55 | PLATFORM: windows 56 | IMAGE_NAME: zaparoo/cross-build 57 | NO_LIBNFC: true 58 | APP_BIN: Zaparoo.exe 59 | EXTRA_LDFLAGS: -H=windowsgui 60 | CC: x86_64-w64-mingw32-gcc 61 | CXX: x86_64-w64-mingw32-g++ 62 | - task: build-installer 63 | vars: 64 | BUILD_ARCH: amd64 65 | 66 | build-386: 67 | cmds: 68 | - task: setup 69 | - task: build-metadata 70 | - task: :docker:build-app 71 | vars: 72 | BUILD_OS: windows 73 | BUILD_ARCH: 386 74 | PLATFORM: windows 75 | IMAGE_NAME: zaparoo/cross-build 76 | NO_LIBNFC: true 77 | APP_BIN: Zaparoo.exe 78 | EXTRA_LDFLAGS: -H=windowsgui 79 | CC: i686-w64-mingw32-gcc-win32 80 | CXX: i686-w64-mingw32-g++-win32 81 | --------------------------------------------------------------------------------