📖 本文由 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 语句或其他判断逻辑来识别 startstop 参数。否则,你的代码在开机和关机时都会被执行一次。


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 -ae2fsck -p 指令。这相当于给系统穿了一层“防弹衣”,即使突然停电或意外断开连接,也能最大限度防止数据损坏。

postshare:数据挂载后的“急先锋”,卸载前的“守门员”

在 Batocera v35 及更高版本中,系统提供了一个全新的脚本触发点:就在 /userdata 分区挂载成功之后、开机视频(Splash video)播放之前(此时 EmulationStation 尚未开始加载)。

  • 存放位置:必须放置在 /boot/postshare.sh
  • 运行方式:该脚本直接由 Bash 解释器(Bash interpreter)执行,因此你不需要手动为它设置“可执行”权限。

【注意】:这是所有初始化脚本中唯一一个不在后台运行的脚本。

生活化类比:这就像是一个“排队办事”的窗口。如果你的脚本里写了一个“休息 10 秒(sleep 10)”的指令,整个系统的启动流程就会乖乖地在那儿原地踏步 10 秒钟,直到你的脚本执行完毕。

  • 第一步:警惕死循环
    绝对不要在脚本里使用没有出口的 do-while 循环,否则系统将永远停留在开机界面。
  • 第二步:活用后台运行符
    如果你希望脚本在后台静默运行而不阻塞开机进度,请务必学习如何使用 & 符号

custom:启动时的“收尾人”,关机时的“先行官”

虽然 custom.shv40 版本中依然可以工作,但请注意,它从 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

【注意】:由于执行权限依赖于文件系统,如果你的用户数据分区使用的是 NTFSexFAT 或其他不支持“执行位(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)使用了 NTFSexFAT 或其他老旧的文件系统,它们不支持“执行位(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

© 版权声明
评论 抢沙发

请登录后发表评论

    暂无评论内容