├── .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 |
--------------------------------------------------------------------------------