📖 本文由 Batocera 官方 Wiki 翻译整理,内容可能随版本更新而变化。
Batocera 脚本使用指南
如果你正在寻找 v37 及更早版本的脚本运行方式,请参考旧版脚本说明页面。
在 Batocera 中,你可以选择在不同的时间点自动运行脚本(Scripts)。根据你想要实现的功能,脚本存放的位置以及启动方式也会有所不同。
开机与关机期间的脚本
在系统启动和关闭的过程中,Batocera 会在三个不同的阶段检查并运行脚本。这就像是赛车比赛中的不同站点,每个站点负责不同的整备工作:
/boot/boot-custom.sh:极早期阶段。如果你需要在系统刚露面时就运行脚本,请用这个。/boot/postshare.sh:挂载阶段。当用户的数据分区(Userdata)刚刚加载完成后,这个脚本就会启动。/userdata/system/custom.sh:界面加载阶段。在 EmulationStation(主图形界面)加载完成后启动。这是最常用的方法,适合绝大多数场景。
运行逻辑说明
当这些脚本运行时,Batocera 会给脚本发送一个“暗号”(参数):
* 开机时:发送 start。
* 关机时:发送 stop。
生活化类比:这就像是你雇了一个管家(脚本),你告诉他:“我进门(start)时请帮我开灯,我出门(stop)时请帮我锁门。”如果你不告诉他区分进门还是出门,他就会在这两件事发生时把开灯和锁门全都做一遍。
【提示】:为了确保代码只在特定时刻运行,建议在脚本中使用 case 语句或其他判断逻辑来识别 start 或 stop 参数。否则,你的代码在开机和关机时都会被执行一次。
boot-custom:启动时的“先锋”,关机时的“殿后”
这是系统最早执行(以及最后结束)的脚本位置。如果你有非常底层的硬件初始化需求,这里是最佳选择。
在 Batocera v32 及更高版本中,你可以在系统刚刚“睁眼”的时候,也就是大部分程序还没开始加载时,就先行启动脚本。
这种脚本必须存放在引导分区(Boot partition)中,路径为 /boot/boot-custom.sh。
【注意】:由于此时系统大部分功能还在“沉睡”,你只能使用最基础的命令和模块。另外,文件名必须严格命名为 boot-custom.sh,否则系统会完全忽略它。
运行时间点:系统的“第一声哨响”
更精确地说,一旦初始化守护进程(init.d)开始工作,这个脚本就会第一个被执行。
生活化类比:如果把 Batocera 启动比作一场大型舞台剧,那么
boot-custom.sh就是在幕布还没拉开、连灯光和音响(网络、共享文件夹、红外遥控驱动等)都还没通电的时候,就已经冲上台的小剧务。
如果你想查看具体的加载顺序,可以去 /etc/init.d 目录下瞧瞧。你会发现 S00bootcustom 排在最前面,它就是触发 boot-custom.sh 的那个机关。
这个脚本能用来做什么?
正因为它运行得足够早,你可以用它来修补或覆盖 Batocera 随后要加载的系统模块。以下是一些只有通过这种方式才能实现的骚操作:
- 第一步:深度自定义控制台
你可以修改或禁用S33disablealtfn模块,从而在特定的电传打字机终端(TTY session)上开启文本模式的控制台。 - 第二步:调校硬件时钟
你可以摆弄硬件时钟(hwclock),让系统把本地时间(Local Time)而不是格林威治标准时间(UTC)保存到实时时钟(RTC)芯片里。 - 第三步:加固文件系统
运行fsck -a或e2fsck -p指令。这相当于给系统穿了一层“防弹衣”,即使突然停电或意外断开连接,也能最大限度防止数据损坏。
postshare:数据挂载后的“急先锋”,卸载前的“守门员”
在 Batocera v35 及更高版本中,系统提供了一个全新的脚本触发点:就在 /userdata 分区挂载成功之后、开机视频(Splash video)播放之前(此时 EmulationStation 尚未开始加载)。
- 存放位置:必须放置在
/boot/postshare.sh。 - 运行方式:该脚本直接由 Bash 解释器(Bash interpreter)执行,因此你不需要手动为它设置“可执行”权限。
【注意】:这是所有初始化脚本中唯一一个不在后台运行的脚本。
生活化类比:这就像是一个“排队办事”的窗口。如果你的脚本里写了一个“休息 10 秒(sleep 10)”的指令,整个系统的启动流程就会乖乖地在那儿原地踏步 10 秒钟,直到你的脚本执行完毕。
- 第一步:警惕死循环
绝对不要在脚本里使用没有出口的do-while循环,否则系统将永远停留在开机界面。 - 第二步:活用后台运行符
如果你希望脚本在后台静默运行而不阻塞开机进度,请务必学习如何使用 & 符号。
custom:启动时的“收尾人”,关机时的“先行官”
虽然 custom.sh 在 v40 版本中依然可以工作,但请注意,它从 v38 版本起就已经被标记为已弃用(Deprecated),并可能在未来的版本中被彻底删除。
【提示】:目前 Batocera 官方推荐的自定义脚本方式是下面将要详细介绍的“服务模式(Services method)”。
custom.sh(已弃用)
这是过去最常用的自定义方式,通常位于 /userdata/system/custom.sh。虽然它现在还能用,但为了长远考虑,建议大家开始转向新的标准。
如果你只想在系统成功启动后跑一个脚本,比如自动开启 虚拟专用网络(VPN) 或者执行一些开机后的琐碎任务,那么 custom.sh 就是为你准备的。
你只需要在 /userdata/system/custom.sh 创建一个脚本文件即可。
【提示】:这个脚本非常特殊,无论你是否给它设置了“可执行”权限(x 属性),系统都会照样运行它。
运行时间点:最后的“大轴”
这个脚本是 Batocera 启动流程中的“大轴戏”,它会在 EmulationStation(主界面)完全启动后最后一个被触发。
生活化类比:如果把开机过程比作一场婚礼,那么前面的步骤都在布置会场、迎接宾客,而
custom.sh就像是婚礼最后登场的敬酒环节,等所有大事都办完了它才出现。
如果你想看它排在谁后面,可以检查 /etc/init.d/ 目录。你会发现一个名为 S99userservices 的文件(在 v38 及更早版本中叫 S99custom),它就是负责拉起你这个脚本的“引信”。
【注意】:文件名必须精准地叫作 custom.sh,少一个字母系统都不会理它。
重要:避开“换行符”陷阱
编写脚本时,文件的行尾结束符(Line terminators)至关重要:
* 第一步:选择正确的工具
不要使用 Windows 自带的记事本,请使用专业的文本编辑器(如 VS Code 或 Notepad++)。
* 第二步:设置编码格式
确保脚本保存为 Unix 风格的换行符(LF),而不是 Windows 风格的(CR/LF)。
* 第三步:保存并检查
如果换行符错了,脚本在 Linux 环境下会直接罢工,完全无法启动。
示例:一个简单的自定义脚本
下面是一个脚本模板,你可以参考它的逻辑来编写自己的代码。
#!/bin/bash
# 这里的代码在每次开机和关机时都会被执行。
# 第一步:检查系统安全设置是否开启
# 我们将获取到的设置值存储在一个变量中,以便后续使用。
# 获取系统安全设置并存入变量
securityenabled="$(/usr/bin/Batocera-settings-get system.security.enabled)"
# 根据系统传入的参数($1)执行不同的动作
case "$1" in
start)
# 【开机逻辑】:这里的代码仅在系统启动时运行
enabled="$(/usr/bin/Batocera-settings-get system.samba.enabled)"
if [ "$enabled" = "0" ]; then
echo "SMB 文件共享服务:已禁用"
exit 0
fi
;;
stop)
# 【关机逻辑】:这里的代码仅在系统关闭时运行
echo -n "正在关闭 SMB 文件共享服务: "
kill -9 `pidof smbd`
RETVAL=$?
rm -f /var/run/samba/smbd.pid
echo "完成"
;;
restart|reload)
# 【重载逻辑】:当服务需要重启或重新加载配置时运行
echo "SMB 文件共享服务:已重载"
;;
*)
# 【兜底逻辑】:输入了错误的指令时运行
echo "使用方法: $0 {start|stop|restart}"
;;
esac
exit $?
服务化管理(Services)
随着系统升级,官方更推荐你将传统的 /userdata/system/custom.sh 脚本迁移为服务(Services)模式,存放在 /userdata/system/services/ 目录下(例如命名为 myservice)。
生活化类比:如果
custom.sh是一个装着所有工具的大杂烩“工具箱”,那么“服务模式”就像是把工具整齐地挂在墙上,每个工具都有自己的挂钩,你可以随时单独取下某一个,而不会影响其他的。
【重要提示】:
* 第一步:注意命名
确保你的服务文件名不要带 .sh 后缀。
* 第二步:调试技巧
如果服务运行不符合预期,可以使用命令 bash -x Batocera-services list 来进行调试(Debug)。
相比旧版 custom.sh 的优势:
- 多任务并行:你现在可以同时运行多个独立的服务脚本,互不干扰。
- 独立管控:你可以单独开启或关闭某个服务。
- 操作便捷:支持多种控制方式,既可以在命令行(Command line)使用
Batocera-services指令,也可以直接在 EmulationStation 界面(系统设置 -> 服务)中用手柄开关。
服务状态管理详解
在管理这些服务时,有两个概念容易混淆:“禁用/启用”与“停止/启动”。
- 禁用服务(通过
Batocera-services disable):这并不会立刻停止正在运行的服务,它的作用是让服务在下次系统开机时不再自动启动。 - 启用服务(通过
Batocera-services enable):同理,这只是给服务贴上了“下次开机请自启”的标签。如果你想现在就看到效果,需要重启系统或者手动执行启动命令。
生活化类比:这就像设置闹钟。你“禁用”闹钟只是取消了明早的响铃计划,但如果现在闹钟正在响,它还是会继续响完,除非你手动按掉它。
【注意】:如果你直接在 EmulationStation 界面(图形化操作界面)中启用或禁用服务,系统会自动帮你完成“立刻启动/停止”的操作,非常省心。
服务脚本示例
下面是一个标准的服务(Service)脚本模板,你可以按照这个结构编写自己的代码:
#!/bin/bash
case "$1" in
start)
# 当服务启动时执行的命令
echo "服务已启动。"
;;
stop)
# 当服务停止时执行的命令
echo "服务已停止。"
;;
status)
# 查看服务当前状态时执行的命令
echo "这是我目前的状态。"
;;
esac
监听游戏启动与退出事件
从 Batocera 5.23 版本开始,系统支持在游戏启动前或退出后立刻运行脚本。这个功能非常强大,可以帮你实现很多自动化操作:
- 第一步:自动化手柄配置(例如针对特定游戏调整按键映射)。
- 第二步:自动抓取信息(为新添加的游戏自动获取封面和介绍)。
- 第三步:动态调整屏幕分辨率(尽管官方更推荐使用
switchres工具来处理)。 - 第四步:云存档同步(在游戏结束时,自动将存档上传至外部设备或网络驱动器)。
提示:这里的功能充满了无限可能,如果你有更好的创意,欢迎尝试!
自动化脚本:让系统感知游戏的“起止”
如果你希望在进入或退出游戏时自动触发某些动作,可以通过在指定目录放置脚本来实现。
- 第一步:准备脚本文件
你需要将编写好的脚本或二进制执行文件(Executable binary)放入/userdata/system/scripts/目录下。文件名可以随你心意,但脚本的第一行必须包含正确的 Shebang(解释器路径声明)(例如#!/bin/bash)。 - 第二步:赋予执行权限
所有的游戏事件脚本都必须被标记为“可执行”。你可以使用chmod +x命令来完成:
# 首先,打开文本编辑器创建你的脚本:
nano /userdata/system/scripts/first_script.sh
# 编写脚本内容,保存并退出。
# 然后,为该脚本添加执行权限:
chmod +x /userdata/system/scripts/first_script.sh
【注意】:由于执行权限依赖于文件系统,如果你的用户数据分区使用的是 NTFS、exFAT 或其他不支持“执行位(Executable bit)”的老式文件系统,脚本将无法正常运行。
运行逻辑:如何区分“开始”与“结束”
你可以根据需要在这个目录下建立任意数量的子文件夹,存放在里面的每一个脚本都会被系统调用。
生活化类比:这就像是给系统雇佣了一群“观察员”。每当游戏状态发生变化,系统就会大喊一声动作指令,所有的“观察员”听到后都会立刻行动。
为了让脚本知道现在是该“上工”还是“收工”,Batocera 会向脚本传递第一个参数(Argument):
- gameStart:当你从 EmulationStation 界面选择并进入游戏时,系统会发送这个暗号。
- gameStop:当你退出游戏返回主界面时,系统会发送这个暗号。
【提示】:如果你没有在脚本中设置针对这两个参数的逻辑判断(Case),那么该脚本会在每次游戏开始和结束时各运行一次。如果你只想在特定时刻触发,请务必在脚本中加入判断逻辑。
脚本接收的参数详解
当脚本运行的时候,系统会按照固定的顺序塞给它一串信息。你可以通过这些“参数”来精准识别当前正在运行的是哪款游戏、哪个平台。
以下是系统按顺序传递的参数对照表:
| 参数编号 | 传递的参数内容 | 具体用途 |
|---|---|---|
| 1 | gameStart 或 gameStop | 用来判断当前动作是“开始游戏”还是“结束游戏”。 |
| 2 | 系统简称(systemName) | 对应配置文件里的系统代号。例如:atari2600。 |
| 3 | 模拟器设置(emulator) | 你所使用的模拟器引擎。例如:libretro。 |
| 4 | 核心设置(core) | 你为该模拟器选定的具体核心。例如:stella。 |
| 5 | ROM 完整路径(args.rom) | 游戏的具体存储位置。例如:/userdata/roms/atari2600/游戏名.zip。 |
实战演示:创建一个简单的游戏触发脚本
我们来亲手制作一个脚本。在这个例子中,我们将学习如何创建目录并编写一个基础模板。
- 第一步:创建存放目录
首先,通过 SSH 远程连接(Secure Shell) 登录系统,输入命令mkdir /userdata/system/scripts来建立专门存放脚本的文件夹。 - 第二步:新建脚本文件
使用内置编辑器创建一个新文件:输入nano /userdata/system/scripts/first_script.sh。
【提示】:文件名你可以随便起,只要是 Bash 能识别的字符就行。 - 第三步:编写脚本内容
你可以参考下面的模板。在编写时,养成先定义变量的好习惯。
生活化类比:这就像是在写一封自动回复邮件。你先规定好“收件箱”在哪,然后设定:如果是收到“你好”,就执行动作 A;如果是收到“再见”,就执行动作 B。
#!/bin/bash
# 这是一个演示如何利用 gameStart 和 gameStop 事件的示例文件。
# 良好的编程习惯是先定义所有的常量。
# 这里我们定义一个日志文件的存放路径。
logfile=/tmp/scriptlog.txt
# 使用 case 语句来判断第一个参数(即系统发来的事件指令)。
case $1 in
gameStart)
# 【游戏开始】:进入任何游戏时都会执行这里的指令
echo "START" > $logfile
# 将所有接收到的参数信息追加到日志中
echo "$@" >> $logfile
;;
gameStop)
# 【游戏结束】:退出任何游戏时都会执行这里的指令
echo "END" >> $logfile
;;
esac
完成上述操作后,你会在 /tmp/scriptlog.txt 看到一个自动生成的日志文件,里面记录了系统传递给脚本的所有参数。
生活化类比:这就像是给你的游戏机装了一个“行车记录仪”。每当你发动(开始游戏)或熄火(退出游戏),它都会自动在小本本(日志文件)上记下当时的时间和状态。
当然,这只是一个入门小测试。如果你想探索更多高阶玩法,可以查阅 SSH 远程连接 页面或 Batocera-settings 指令集,学习更多通用及系统专用的命令。
EmulationStation 深度脚本定制
如果 Batocera 官方提供的脚本功能还不能满足你的“极客心”,EmulationStation(简称 ES,即系统图形界面) 本身也拥有一套独立的脚本触发机制。
不同于普通的 Batocera 脚本,ES 的逻辑是按文件夹分类的:你只需在 /userdata/system/configs/EmulationStation/scripts/<事件名称>/ 目录下放入脚本,系统就会自动执行该文件夹下的所有脚本。
- 案例:如果你想在每个游戏启动时运行脚本,请将其放入
/userdata/system/configs/EmulationStation/scripts/game-start/目录。
第一步:设置执行权限
所有的 EmulationStation 脚本都必须通过 chmod +x 命令开启“可执行”权限,否则系统会视而不见。
第二步:编写脚本
使用文本编辑器创建并编写你的脚本文件:
# 打开编辑器开始创作:
nano /userdata/system/configs/EmulationStation/scripts/game-start/my_custom_script.sh
- 第一步:创建并编辑脚本
使用命令nano /userdata/system/configs/EmulationStation/scripts/game-start/first_script.sh打开编辑器,输入你的代码后保存并退出。 - 第二步:激活脚本
输入chmod +x /userdata/system/configs/EmulationStation/scripts/game-start/first_script.sh赋予脚本“可执行”权限。
【注意】:如果你的数据分区(Userdata Partition)使用了 NTFS、exFAT 或其他老旧的文件系统,它们不支持“执行位(Executable bit)”功能,脚本将无法正常运行。
EmulationStation 事件清单
EmulationStation(简称 ES)能感知的事件比系统底层更多。你可以针对以下不同的“触发时刻”编写脚本:
| 事件名称 (Event name) | 传入参数 (Arguments) | 说明 (Notes) |
|---|---|---|
| game-start | ROM名称、ROM路径等 | 游戏启动时触发。与系统级的 gameStart 类似,但信息稍简略,且仅限从 ES 界面启动时生效。 |
| game-end | 无 | 游戏结束时触发。 |
| game-selected | 系统名、路径、游戏名 | v33 新增。当你在菜单里选中(光标停在)某个游戏时触发,屏保滚动时也有效。 |
| system-selected | 所选系统名 | v33 新增。当你在主列表里切换并选中某个游戏系统(如 FC 切换到 GBA)时触发。 |
| theme-changed | 当前主题名、旧主题名 | 切换主题时触发。通常用于重新加载界面元素。 |
| settings-changed | 无 | 当系统设置被保存时触发。 |
| controls-changed | 无 | 在“手柄映射”菜单中保存按键配置时触发。 |
| config-changed | 无 | 无论是改了系统设置还是手柄配置,只要配置变动就会触发。 |
| quit | 无 | 执行常规关机指令时触发。 |
| reboot | 无 | 执行重启指令时触发。 |
| shutdown | 无 | 执行快速关机指令时触发。 |
| sleep | 无 | 系统进入待机睡眠状态时触发。 |
| wake | 无 | 从睡眠中唤醒时触发。 |
| achievements | 具体的成就信息 | v38 新增。当解锁 RetroAchievements(全成就平台) 的奖杯时触发。 |
| screensaver-start | 无 | 屏幕保护程序启动时触发。 |
| screensaver-stop | 无 | 屏保被唤醒停止时触发。 |
| suspend | 无 | 同样是睡眠触发,但由底层 pm-utils 管理,而非 ES 直接触发。 |
| resume | 无 | 由键盘操作唤醒系统时触发。 |
| controller-connected | 被接入的设备信息 | v43 新增。当新的手柄设备接入时触发。 |
| controller-disconnected | 被断开的设备信息 | v43 新增。当手柄连接断开时触发。 |
参数传递的奥秘
当特定事件发生时,EmulationStation 会给脚本派发不同的“信息包”。对于与游戏相关的事件,参数的排列顺序和含义如下:
| 参数编号 | 解析参数 | 具体用途 |
|---|---|---|
| 1 | 系统(system) | 当前游戏所属的模拟平台系统。 |
| 2 | ROM文件(rom) | 当前游戏文件的具体文件名。 |
| 3 | 游戏名称(romname) | 在元数据(Metadata)中记录的游戏显示名称。 |
所有的事件都需要根据这个逻辑来编写对应的脚本。
EmulationStation 脚本实战案例:控制 Pixelcade 显示屏
下面是一个专门为 Pixelcade(一种动态 LED 副屏) 设计的“选中游戏(game-selected)”脚本示例。通过这个脚本,每当你移动光标选中一个游戏,副屏就会自动切换显示对应的图标。
生活化类比:这就像是给你的游戏机装了一个“智能展示窗”。每当你指着架子上的一盘卡带(选中游戏),展示窗(Pixelcade)就会立刻心领神会,换上那个游戏的精美海报。
- 第一步:捕获系统传来的信息
脚本开始后,先把系统发给我们的三个参数存入变量(Variables),方便后面调用。 - 第二步:转换特定参数
有时候系统给的名字不符合副屏的要求,我们需要做个转换。比如把FBNeo识别为MAME。 - 第三步:清理文件名
如果是某些特定系统(如 ScummVM),我们需要去掉文件的后缀名,让显示更简洁。 - 第四步:发送指令
最后通过 Curl(命令行传输工具) 将这些信息打包发送给副屏的控制端口。
#!/bin/bash
# 第一步:将参数保存到变量中
system="${1}"
rom="${2}"
romname="${3}"
# 第二步:将某个参数转换为另一个值
if "${system}" == "FBNeo" ; then
system="MAME"
fi
# 第三步:针对特定系统进行条件判断(Switch case)
case ${system} in
FBNeo)
system="MAME"
;;
scummvm)
# 去掉文件后缀名
rom="${rom%.*}"
;;
esac
# 第四步:每次触发事件时执行发送动作
curl -G \
--data-urlencode "t=${romname}" \
http://127.0.0.1:8080/arcade/stream/${system}/`basename ${rom}`
实际应用场景说明
温馨提示:旧版本的脚本案例现已移至“遗留脚本说明页面”。
Batocera 开机后常用脚本案例
这里有一些实用的脚本,可以帮你实现系统的自动化管理。
定时自动关机
如果你想让 Batocera 在每天的固定时间自动关机(例如防止熬夜),可以使用这个脚本。
#!/bin/bash
while true; do
# 获取当前的小时和分钟
currenthour=$(date +%k%M)
# 如果时间到了 23:55,则立即关机
if [ $currenthour -eq 2355 ]; then
shutdown -h now
fi
# 每隔 30 秒检查一次
sleep 30
done
禁用特定网卡
如果你需要关闭某个特定的网络接口(Network Interface),比如禁用第二个网卡(eth1):
#!/bin/bash
ifconfig eth1 down
进阶:闲置 1 小时后自动关机
在这个案例中,我们希望系统在进入屏幕保护程序 1 小时后自动关机,从而实现“无人操作即自动省电”的功能。
生活化类比:这就像是给你的游戏机装了一个“睡眠监测器”。当你停下操作(触发屏保)时,它开始倒计时;如果你中途回来碰了下摇杆(退出屏保),它就会立刻取消关机计划。
为了实现这个功能,你需要准备两个脚本:
-
第一步:设置关机触发器
在/userdata/system/configs/EmulationStation/scripts/screensaver-start/目录下创建shutdown-trigger.sh。
功能:当屏保启动时,该脚本会开启一个 1 小时的定时器。你可以根据需要修改脚本中的date命令来调整时间长短。 -
第二步:设置关机取消器
在/userdata/system/configs/EmulationStation/scripts/screensaver-stop/目录下创建shutdown-cancel.sh。
功能:当你触碰控制器让系统“醒来”时,该脚本会立刻取消之前的自动关机指令。
实现闲置自动关机:脚本配置步骤
为了让系统在进入屏保一小时后自动关机,我们需要在对应的目录下分别创建“触发器”和“拦截器”。
- 第一步:配置关机触发脚本
将以下脚本放入/userdata/system/configs/EmulationStation/scripts/screensaver-start/目录下,并命名为shutdown-trigger.sh。
#!/bin/sh
# 定义倒计时标记文件的存放位置
TRG=/tmp/shutdown.screensaver
# 设置关机时间为 1 小时后,并以时间戳格式存入标记文件
date -d "+1 hour" +"%s" > "$TRG"
# 进入循环检测
while true; do
now=$(date +"%s")
# 如果标记文件被删除了(说明用户回来了),则停止脚本
[ -f "$TRG" ] || break
trg=$(cat "$TRG")
# 如果当前时间超过了预设的关机时间
if [ "$now" -ge "$trg" ]; then
shutdown -h now # 立即关机
rm "$TRG" # 关机前清理标记文件
fi
sleep 5 # 每 5 秒检查一次,不占用过多系统资源
done
- 第二步:配置关机取消脚本
将以下脚本放入/userdata/system/configs/EmulationStation/scripts/screensaver-stop/目录下,并命名为shutdown-cancel.sh。
#!/bin/sh
# 找到之前设置的倒计时标记文件
TRG=/tmp/shutdown.screensaver
# 删掉它!这样触发脚本就会知道用户已经回来,从而停止倒计时
rm "$TRG"
Batocera 事件监控脚本案例
这些脚本利用了系统在游戏切换时传递的参数,可以实现各种有趣的自动化功能。
案例一:将游戏运行详情输出到文件
这个脚本可以帮你记录每次玩游戏的详细信息。
#!/bin/bash
# 由 cyperghost 为 Batocera 编写
# 指定日志文件的保存路径
testfile="/userdata/system/scripts/output.txt"
echo "脚本运行结束!" >> $testfile
echo "所有参数: $@" >> $testfile
echo "游戏系统名称: $1" >> $testfile
echo "模拟器核心: $2" >> $testfile
echo "ROM 路径: $(basename "$3")" >> $testfile
# 生活化类比:判断你是否在使用万能核心,给你不同的“鼓励”
[ "$4" == "libretro" ] && echo "你是自由的!" >> $testfile || echo "没有快捷键的日子不好过吧?" >> $testfile
案例二:运行游戏时改变 LED 灯颜色,并支持按键关机
这是一个更具操作感的脚本,它能直接通过控制硬件的 GPIO 引脚(General-purpose input/output) 来控制灯光和读取按钮状态。
#!/bin/bash
# 定义 LED 灯和关机按钮连接的引脚编号
LED1=22
LED2=27
SHUTDOWN=3
【提示】:在编写涉及硬件控制的脚本时,请务必确认你的引脚连接正确,以免对电路造成损坏。
case "$1" in
start)
# 第一步:初始化 LED 灯
# 我们通过系统的 GPIO(通用型之输入输出)接口来点亮它们
echo "$LED1" > /sys/class/gpio/export
echo "$LED2" > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio$LED1/direction
echo 0 > /sys/class/gpio/gpio$LED1/value # 设置 LED1 的初始状态
echo out > /sys/class/gpio/gpio$LED2/direction
echo 1 > /sys/class/gpio/gpio$LED2/value # 设置 LED2 的初始状态
# 第二步:初始化关机按钮
echo "$SHUTDOWN" > /sys/class/gpio/export
echo "in" > /sys/class/gpio/gpio$SHUTDOWN/direction
# 第三步:循环监测按钮状态
# 就像一个忠诚的哨兵,这个循环会不断检查关机按钮是否被按下。
# 只要没人按按钮,脚本就会一直处于“待命”状态。
buttonstate1=$(cat /sys/class/gpio/gpio$SHUTDOWN/value)
shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value)
while [ $shutdownSignal = $buttonstate1 ]; do
shutdownSignal=$(cat /sys/class/gpio/gpio$SHUTDOWN/value)
sleep 0.5 # 每隔 0.5 秒观察一次,防止消耗过多系统资源
done
# 第四步:执行关机
shutdown -h now
;;
stop)
# 释放所有 GPIO 资源
# 当系统关闭时,我们需要把之前占用的“插座”归还给系统
echo "$LED1" > /sys/class/gpio/unexport
echo "$LED2" > /sys/class/gpio/unexport
;;
esac
exit $?
自动加载最新的即时存档
通常我们希望进入游戏时能直接回到上次退出的地方。这个脚本可以帮助你自动寻找并加载那个最新的即时存档(Savestate)。
生活化类比:这就像是你去图书馆看书,每次离开前都会夹一个书签。这个脚本的作用就是在你下次翻开同一本书时,自动帮你把书翻到有书签的那一页。
配置说明
在正式使用此脚本前,请按照以下步骤操作:
- 第一步:禁用 EmulationStation 自带的自动加载/保存功能
为了避免冲突,我们需要先关闭系统默认的相关设置。 - 第二步:修改配置文件
打开你的Batocera.conf配置文件,手动添加以下代码行:
global.RetroArch.savestate_auto_load=true
【提示】:该脚本由 cyperghost(又名 lala)为 Batocera 特别编写,旨在让游戏体验更加无缝。
#!/bin/bash
# 脚本内容开始...
- 第一步:安装脚本
将此脚本下载或保存到/userdata/system/scripts目录下。 - 第二步:开启执行权限
别忘了给它设置执行位(Executable bit),否则脚本无法生效。
# 获取不带后缀的游戏文件名
rom_no_ext="$(basename "${5%.*}")"
# 定义该系统的存档路径
sav_path="/userdata/saves/$2"
# 检查设置:如果已经开启了全局自动存档,或者没开启自动加载功能,则退出脚本
[ "$(Batocera-settings get global.autosave)" -eq 1 ] && exit
[ "$(Batocera-settings get global.RetroArch.savestate_auto_load)" == "true" ] || exit
if [ "$1" == "gameStart" ]; then
# 在存档目录中寻找最新修改的文件
file="$(/bin/ls "$sav_path/$rom_no_ext."* -turR1A | tail -1)"
[ -n "$file" ] || exit
# 如果找到的是截图文件(png),则转换回存档文件名
[ "${file##*.}" == "png" ] && file="${file%.*}"
[ -f "$file" ] || exit
# 将最新存档复制为“自动加载存档”文件
cp -f "$file" "$sav_path/$rom_no_ext.state.auto"
fi
if [ "$1" == "gameStop" ]; then
# 游戏结束时,清理掉生成的自动加载临时文件
rm -f "$sav_path/$rom_no_ext.state.auto"
fi
玩转音频设置(Audio Shenanigans)
有时候你可能希望在特定时刻调整音量或切换音响设备,这个脚本展示了如何通过命令行精准控制 Batocera 的音频系统。
生活化类比:这就像是一个“智能调音师”。当你进屋(启动脚本)时,他会自动帮你把线插到电视上(切换 HDMI),把静音取消,然后把音量调到最舒服的刻度,甚至还会弹一段小曲儿(播放测试音)确认声音是否正常。
case "$1" in
start)
# 第一步:备份当前状态
# 获取当前的音频配置文件(Audio profile)和输出设备(Audio device)
defaultprofile="$(Batocera-audio get-profile)"
defaultaudio="$(Batocera-audio get)"
# 第二步:调整设备与配置
# 将输出设备切换到 HDMI-2
Batocera-audio set HDMI-2
# 将音频配置设置为“自动(auto)”
Batocera-audio set-profile auto
# 第三步:精调音量
# 取消系统的静音状态
Batocera-audio setSystemVolume unmute
# 将系统音量调整为 69
Batocera-audio setSystemVolume 69
# 第四步:设备测试
# 使用系统自带的 Mallet.wav 文件进行音频测试
Batocera-audio test
;;
stop)
# 切换静音状态
Batocera-audio setSystemVolume mute
;;
esac
【提示】:在调整音频脚本时,建议先通过 SSH 手动执行命令测试一下,确保你的 HDMI 编号(如 HDMI-2)与硬件实际连接一致。
# 切换静音状态(开/关切换)
Batocera-audio setSystemVolume mute-toggle
手柄闲置监测脚本
如果你想在手柄长时间没人动时触发某些操作(比如自动关机或进入演示模式),可以使用这个脚本。它会像一个“守卫”一样,时刻盯着手柄的动静。
生活化类比:这就像是一个带有感应器的楼道灯。如果一段时间没人走动(没有操作手柄),灯就会自动熄灭(执行闲置操作);一旦有人走过(按了下按钮),灯就会再次亮起。
脚本配置与逻辑说明
- 第一步:建立锁定机制(Locking mechanism)
脚本启动时会创建一个锁定文件/var/run/controller-inactivity-checker.lock。这能确保同一时间只有一个监测脚本在运行,防止多个“守卫”互相打架。 - 第二步:设置闲置计时器
脚本会读取系统中system.controllerinactivitytimer的数值。如果没设值或数值太小(小于 60 秒),它会默认设为 900 秒(15 分钟)。 - 第三步:更新设备列表
通过js_update函数,脚本会自动扫描并记录系统中所有的手柄设备(Joystick devices)路径(如/dev/input/js*)。 - 第四步:定义动作
你可以根据需要在脚本的特定位置填入你自己的代码:- 闲置时执行:在
do_inactivity()部分填入你想在手柄“罢工”后做的事。 - 活跃时执行:在
do_activity()部分填入当用户再次拿起手柄时要做的事。
- 闲置时执行:在
【提示】:脚本使用了 trap 命令,这保证了当你关闭脚本时,它会自动清理掉锁定文件,不留垃圾。
核心监测代码参考
#!/bin/bash
# ... [前面的锁定与初始化逻辑] ...
# 监测函数:这是脚本的心脏
monitor_controllers() {
local JS_REFRESH_INTERVAL=10 # 每隔 10 次循环重新刷新一次手柄列表
LOOP_COUNT=0
while true; do
# 定期检查手柄是否插拔
if (( LOOP_COUNT % JS_REFRESH_INTERVAL == 0 )); then
js_update
fi
# 遍历每一个找到的手柄
for i in "${!JS_DEVICES[@]}"; do
js="${JS_DEVICES[$i]}"
if [ -e "$js" ]; then
# 在此处继续编写检测手柄输入信号的逻辑...
# 使用 jstest 工具检测手柄是否有事件发生(即按键或摇杆动作)
if timeout 1 jstest --event "$js" | tail -n +25 | grep -q "Event"; then
LOOP_COUNT=0 # 一旦有动作,计时器立刻清零
# 将当前活跃的手柄移到检测列表最前端,提高下次检测效率
JS_DEVICES=("$js" "${JS_DEVICES[@]:0:$i}" "${JS_DEVICES[@]:$((i + 1))}")
if [ "$STATE" = "inactive" ]; then
do_activity # 从闲置状态唤醒,执行活跃动作
fi
break
fi
fi
done
((LOOP_COUNT++))
# 当计时器的数值超过了预设的闲置时间(TIMER)
if (( LOOP_COUNT >= TIMER )); then
if [ "$STATE" = "active" ]; then
do_inactivity # 确认闲置,执行闲置动作
fi
fi
done
}
# 脚本启动:先更新手柄列表,然后开始监测
js_update
monitor_controllers
exit 0
Batocera 开机启动脚本案例
延迟启动 Syncthing 文件同步服务
有时某些服务启动太早会抢占资源或导致冲突。这个脚本展示了如何通过修改启动优先级,让 Syncthing(文件同步服务) 在系统所有任务都完成后最后启动。
生活化类比:这就像是排队进场。原本 Syncthing 站在队伍中间(S27),我们通过脚本把它领到了队伍的最末尾(S99),确保它在别人都坐好(系统加载完)之后再进场。
#!/bin/bash
# 延迟启动 Syncthing,直到其他启动任务全部完成。
# 第一步:检查指令,仅在系统启动(start)时运行
if [ "$1" != "start" ]; then
exit 0
fi
# 第二步:调整启动顺序
# 将启动文件名从 S27 改为 S99
if mv /etc/init.d/S27syncthing /etc/init.d/S99syncthing; then
echo "成功延迟了 Syncthing 的启动。"
elif ls /etc/init.d/S99syncthing; then
echo "操作已经执行过了,无需重复!"
exit 0
else
# 第三步:异常处理
echo "延迟 Syncthing 失败。"
touch /userdata/check-boot-custom-sh # 留下一个标记文件方便排查
shutdown -h now
exit 1
fi
exit $?
EmulationStation 脚本案例
在第二块屏幕上播放视频
如果你有一块副屏,可以让它在选中游戏时播放对应的视频。在开始之前,你需要先准备好素材,并放入对应的 Marquee(跑马灯/副屏素材) 文件夹。
- 操作步骤:
将名为game.sh的脚本放置到/userdata/system/configs/EmulationStation/scripts/game-selected/目录下。
【提示】:具体原理和更深层的解释可以参考论坛的原贴说明。当你移动光标选中游戏时,这个脚本就会被触发,让你的副屏动起来。
通过以下三个脚本的配合,你可以让 Batocera 的第二块屏幕(如副屏或跑马灯屏)根据你的操作实时显示动态内容。
第一步:配置游戏选中脚本
将以下脚本保存为 game.sh,并放入 /userdata/system/configs/EmulationStation/scripts/game-selected/ 目录。
功能:当你移动光标选中某个具体游戏时,它会告诉后台脚本去更新副屏画面。
#!/bin/bash
System=$1 # 获取系统名称
Romname=${2%.*} # 获取不带后缀的 ROM 文件名
rom=${Romname##*/}
# 调用核心处理脚本
/userdata/marquee.sh Gameselected $System "$rom"
第二步:配置系统选中脚本
将以下脚本保存为 system.sh,并放入 /userdata/system/configs/EmulationStation/scripts/system-selected/ 目录。
功能:当你在主菜单切换不同的游戏机平台(如从 GBA 切换到 PS1)时,触发副屏更新。
#!/bin/bash
System=$1 # 获取系统名称
# 在后台运行核心处理脚本
/userdata/marquee.sh Systemselected $System &
第三步:配置核心处理脚本
这是最关键的一步,负责具体的画面渲染。请将 marquee.sh 放在 /userdata/ 目录下。
生活化类比:这个脚本就像是一个“导播”。前面的脚本负责下达指令(“选了某个游戏”或“换了系统”),而这位“导播”则负责翻箱倒柜寻找对应的视频或图片素材,并把它们投射到副屏这个“监视器”上。
#!/bin/bash
case $1 in
Start)
# 当游戏启动时:优先寻找对应的 mp4 视频素材
Romname=$3
Gamepath=$2
marqueeimage=$Gamepath/images/$romname-marquee.png
if [ -f "/userdata/roms/Marquee/videos/$Romname.mp4" ]
then
# 使用 ffmpeg 渲染视频到帧缓冲(Framebuffer)设备 /dev/fb0
ffmpeg -i /userdata/roms/Marquee/videos/$Romname.mp4 -vf scale=1280:720 -sws_flags bilinear -pix_fmt rgb565le -f fbdev /dev/fb0
fi
# 如果没有视频,则按顺序寻找图片素材
if [ -f "/userdata/roms/Marquee/hires/$Romname.jpg" ]
then
fbv /userdata/roms/Marquee/hires/$Romname.jpg -fer
elif [ -f "$marqueeimage" ]
then
fbv $marqueeimage -fer
else
# 都没有则显示默认的 MAME 封面图
fbv /userdata/roms/MAME/images/MAME.png -fer
fi
;;
Gameselected)
# 当选中某个游戏时:
System=$2
Romname=$3
# 优先显示 Marquee 专用文件夹下的图片,若无则显示系统默认图片目录下的跑马灯图
if [ -f "/userdata/roms/Marquee/$Romname.png" ]
then
fbv /userdata/roms/Marquee/$Romname.png -fer
elif [ -f "/userdata/roms/$System/images/$Romname-marquee.png" ]
then
# [继续后续图片检测逻辑]
【提示】:由于涉及视频渲染,确保你的副屏设备路径正确(通常是 /dev/fb0 或 /dev/fb1)。如果画面显示不出来,请检查素材文件的路径和格式是否完全匹配。
# 显示系统默认图片路径下的跑马灯图
fbv "/userdata/roms/$System/images/$Romname-marquee.png" -fer
else
# 如果还是找不到,则显示通用的 MAME 标志图
fbv /userdata/roms/Marquee/MAME.png -fer
fi
;;
Systemselected)
# 当你在主菜单切换系统(如从街机切换到任天堂)时:
imagepath="/userdata/roms/sysimages/$2"
if [ -f "$imagepath.png" ]
then
# 显示该系统对应的图标
fbv "$imagepath.png" -fer
else
# 兜底方案,显示 MAME 默认图
fbv /userdata/roms/MAME/images/MAME.png -fer
fi
;;
esac
第四步:配置系统级事件脚本
为了让前面的“导播”脚本(marquee.sh)知道游戏什么时候正式开始,什么时候正式结束,我们需要在系统级脚本目录中增加一个“信号员”。
请将以下脚本保存为 script.sh,并放入 /userdata/system/scripts/ 目录。
生活化类比:这就像是给导播配了一个对讲机。每当玩家真正按下按钮开始玩游戏时,信号员就会通过
gameStart喊话:“演出开始了,快播视频!”;当玩家退出游戏时,信号员就喊gameStop:“收工,关掉播放器!”
#!/bin/bash
case $1 in
gameStart)
# 第一步:解析游戏路径和名称
# 从系统参数中提取 ROM 所在的文件夹和具体文件名
gamepath=${5%/*}
romname=${5##*/}
# 第二步:启动副屏渲染
# 传递“Start”信号给 marquee.sh 脚本,并以后台模式运行
/userdata/marquee.sh Start $gamepath ${romname%.*} &
;;
gameStop)
# 第三步:清理现场
# 游戏结束时,强行关闭负责视频渲染的 ffmpeg 进程,防止它占用副屏
killall ffmpeg
;;
esac
【注意】:请务必确保该脚本拥有执行权限。你可以使用 SSH 运行 chmod +x /userdata/system/scripts/script.sh 来激活它。如果副屏在游戏结束后依然卡在最后一帧,通常是因为 killall 命令没有成功清理渲染进程,请检查后台权限设置。
原文: Batocera脚本使用教程 • 翻译: DIY8 Bot



暂无评论内容