- 桌面应用
- CLI
【免费下载链接】ns-usbloader
Awoo Installer and GoldLeaf uploader of the NSPs (and other files), RCM payload injector, application for split/merge files.
本文以仓库根目录的 BUILD.md 为核心,系统讲解 NS-USBloader(一款运行在 PC 端的 NSP 安装器 / RCM 载荷注入工具 / 文件拆分合并工具)的完整构建流程:包括一条 Maven 命令完成 Java 应用打包、构建 legacy 版本的依赖修改方法,以及为 RCM 注入功能编译 Linux(amd64/x86)、Raspberry Pi ARM、Windows(x86/amd64)四套smashlibJNI 原生库的操作步骤与底层原理。读完本文,你能够在本机从零构建出可运行的ns-usbloader-<version>.jar,并能按需重新编译打包进 JAR 的跨平台原生库。
1. 构建总览:两个层次、四套原生库
NS-USBloader 的构建分为两个相互独立的层次:
- Java 应用层(必需):一个标准 Maven 工程,
mvn package即产出 fat JAR(含依赖),Windows 场景下还会由 launch4j 生成.exe启动器。 - JNI 原生库层(可选):
JNI sources/目录下按平台存放的 C 源码与 Makefile,产物为smashlib.so/smashlib.dll,最终被安装到src/main/resources/native/<os>/<arch>/并随 JAR 一起分发。
为什么原生库是"可选"的?从源码结构看,RCM payload 的主发送路径由纯 Java 实现 Rcm.java 承担(基于 usb4java,注释中说明其 'Fusée Gelée' RCM 载荷实现参考了 fusee-launcher 项目的思路)。而smashlib通过 RcmSmash.java 暴露两个native方法,作为低层 USB 操作的补充通道:
public static native int smashLinux(final int bus_id, final int device_addr); public static native int smashWindows(); public static boolean isSupported() { return supported; }isSupported()的返回值由静态块中的 JNIRcmLoader.load() 决定——它按os.name/os.arch拼接资源路径/native/linux|windows/x86|amd64|arm/smashlib.so|dll定位库文件;从磁盘(file 协议)或 JAR 内部(jar 协议,先解压到临时目录)加载,找不到对应架构的库则返回false,应用降级为纯 Java 路径而不会崩溃。这正是 BUILD.md 中把 JNI 部分标注为OPTIONAL的原因:仓库中不携带编译好的.so/.dll(src/main/resources/下无native/目录),发布构建流程中另行编译注入。
| 目标平台 | 源码目录 | 构建命令 | 产物 | 安装位置 |
|---|---|---|---|---|
| Linux x86 | JNI sources/linux | make x86(-m32交叉编译) | x86/smashlib.so | src/main/resources/native/linux/x86/ |
| Linux amd64 | JNI sources/linux | make amd64 | amd64/smashlib.so | src/main/resources/native/linux/amd64/ |
| Raspberry Pi (ARM) | JNI sources/linux | make arm(交叉工具链) | arm/smashlib.so | src/main/resources/native/linux/arm/ |
| Windows x86 | JNI sources/windows | make x86(MinGW) | x86/smashlib.dll | src/main/resources/native/windows/x86/ |
| Windows amd64 | JNI sources/windows | make amd64(MinGW-w64) | amd64/smashlib.dll | src/main/resources/native/windows/amd64/ |
注意:Linux 的
x86目标是 amd64 主机上以-m32交叉编译出的 32 位库(需要 32 位 glibc 开发头文件),并非只能在 32 位系统上编译。
2. 前置条件
Java 应用层
- JDK 23:pom.xml 中
maven.compiler.release与maven-compiler-plugin的<release>均为23,即必须使用 Java 23 或更高版本编译(这与 README 中"运行只需 JRE 17"是两回事——运行版本低于编译版本要求即可放宽,但构建不行)。 - Maven:BUILD.md 明确要求 "Install Maven"。
- OpenJFX 25:GUI 依赖通过 Maven 依赖自动拉取(
javafx.version属性为25,graphics/controls/fxml三件套同时声明了linux、win、mac三个 classifier,因此单一 JAR 可跨平台运行)。
JNI 原生库层(BUILD.md 原文概括为 "install JDK, GCC, make, kernel headers and whatever else (or use Gentoo <3)")
- JDK 且设置好
JAVA_HOME:Linux Makefile 通过-I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/linux"引用jni.h。 - GCC + make + Linux 内核头文件(
linux/usbdevice_fs.h、linux/usb/ch9.h,见 smashlib.c 头文件引用)。 - Raspberry Pi 目标另需 ARM 交叉编译工具链:Makefile_raspberry 中
CC=arm-linux-gnueabihf-gcc。 - Windows 目标需要 MinGW(x86)与 MinGW-w64(amd64)及 JDK,
JAVA_HOME指向 JDK 根目录。
3. 构建 Java 应用:一条 mvn 命令
BUILD.md 给出的标准构建命令为:
# 安装 Maven 后,在仓库根目录执行 mvn -B -DskipTests clean package-B(batch mode):批处理模式,不输出交互式进度条,适合 CI 与脚本。-DskipTests:跳过 JUnit 5 测试(测试位于src/test/java/,包括需要真实 USB 设备的集成测试,如src/main/test下的EsIntegrationTest、FsIntegrationTest、LoaderIntegrationTest,本地无设备时跳过是合理的默认)。- 产物名由
<finalName>${project.artifactId}-${project.version}</finalName>决定,当前 pom 版本为7.3,因此得到target/ns-usbloader-7.3.jar。
3.1 打包链路:jar-with-dependencies + launch4j
pom.xml 的插件配置决定了这条命令背后实际发生了什么,这里完整列出构建链:
- maven-jar-plugin 的
default-jar被禁用(<phase>none</phase>)——pom 中注释写明 "Don't generate default JAR without dependencies",即不产出无依赖的瘦 JAR。 - maven-assembly-plugin 生成 fat JAR:
descriptorRef为jar-with-dependencies,且appendAssemblyId为false,所以最终 JAR 名就是ns-usbloader-7.3.jar(不带-jar-with-dependencies后缀);manifest 的mainClass为nsusbloader.Main——它只是一个转发类(Main.java 注释 "It's a workaround to execute JavaFX application"),把main直接委托给NSLMain.main(args),以此规避 JavaFX 应用类被LauncherHelper拦截的问题。 - launch4j-maven-plugin 在
package阶段生成 Windows 启动器:产物为target/NS-USBloader.exe,headerType为gui(无控制台窗口)、图标取自appicon.ico,并配置了 JRE 探测(<minVersion>23.0.0</minVersion>)。该插件绑定 Windows 资源(versionInfo、messages如 "JDK not found" / "JDK Version mismatch"),实际用于产物的 Windows 分发场景。 - 测试插件:surefire / failsafe 均为 2.22.2,对应 JUnit Jupiter 5.9.0;不加
-DskipTests时构建会执行src/test/java/下的用例(含 USB 传输相关的TransferModuleTest、SplitTest、MergeTest等)。
3.2 资源过滤:版本号如何进入应用
pom 中声明了两组资源:src/main/resources(不过滤)与src/main/resources-filtered(filtering=true)。后者目前只有一个文件 app.properties,内容为:
_version=v${project.version}构建时 Maven 会把${project.version}替换为7.3打入 JAR,运行时由应用读取以显示/校验版本——这就是版本号"构建期注入"而非硬编码的实现方式。
3.3 版本号与 NSIS 安装器的联动
pom 第 11 行注释明确:"linked via script to NSIS system. Should have format of 2 blocks of numbers",即版本必须是X.Y两段数字格式。联动脚本 misc/windows/update_version.sh 会:
- 从
pom.xml中grep出主/次版本号; sed改写 misc/windows/NSIS/installer.nsi 中的!define VERSIONMAJOR/VERSIONMINOR、BrandingText为 "NS-USBloader vX.Y";- 无参数运行时把
OutFile置为Installer-X.Y.exe;带任意参数运行时改写为Installer-legacy-X.Y.exe——这正是构建 legacy 版安装器的入口。
4. 构建 legacy 版本:修改 usb4java 依赖
BUILD.md 对 "legacy" 版本的唯一要求,是把 pom.xml 中的 usb4java 依赖替换为 1.2.0:
<dependency> <groupId>org.usb4java</groupId> <artifactId>usb4java</artifactId> <version>1.2.0</version> <scope>compile</scope> </dependency>当前仓库中该依赖为1.3.0,并附注释 "Must be 1.2.0 for macOS lower than Mojave"。README 对两者差异的解释是:usb4java 在不同版本中对 USB 栈的封装行为不同(涉及 libusb 与 AppleIOKit/IOUSB等后端的桥接),1.3.0 在 Mojave 之前的 macOS 上行为异常,故为老系统单独产出-legacyJAR 与-legacy安装器;在 Linux/Windows 上 legacy JAR 同样可用,但"偶尔在 Windows 上失效"。
因此完整的 legacy 构建流程为:修改 pom 中 usb4java 版本 → 照常mvn -B -DskipTests clean package→ (Windows 分发场景)执行./misc/windows/update_version.sh anyarg生成Installer-legacy-X.Y.exe脚本改写 → NSIS 打包。
5. 构建 JNI 原生库(可选但推荐深入)
5.1 JNI 头文件生成(通常可跳过)
BUILD.md 指出仓库已附带生成好的头文件,无需重新生成。其生成流程为:
$ cp NS-USBloader/src/main/java/nsusbloader/Utilities/RcmSmash.java . $ javac -h . RcmSmash.javajavac -h会扫描类中的native方法并产出 C 头文件。仓库中已生成的 nsusbloader_Utilities_RcmSmash.h 声明了两个导出符号,与 Java 侧签名一一对应:
JNIEXPORT jint JNICALL Java_nsusbloader_Utilities_RcmSmash_smashLinux (JNIEnv *, jclass, jint, jint); /* (II)I */ JNIEXPORT jint JNICALL Java_nsusbloader_Utilities_RcmSmash_smashWindows (JNIEnv *, jclass); /* ()I */C 实现侧(JNI sources/linux/smashlib.c 与 JNI sources/windows/smashlib.c)按平台各自实现真实逻辑,另一个平台则提供返回-1的桩函数——因此 Linux 版.so中smashWindows()恒返回失败,Windows 版.dll中smashLinux()恒返回-1。
5.2 Linux(amd64 主机):make install clean
$ cd 'JNI sources/linux' $ make install cleanmake无参数时命中all: x86 amd64目标。查看 JNI sources/linux/Makefile 可确认其细节:
CC=gcc CFLAGS=-O2 APP_NAME = smashlib.so all: x86 amd64 x86: $(MKDIR_P) ./x86 $(CC) ${CFLAGS} -m32 -c -fPIC -I"${JAVA_HOME}/include" -I"${JAVA_HOME}/include/linux" smashlib.c -o smashlib_x86.o $(CC) ${CFLAGS} -m32 -shared -fPIC -o ./x86/${APP_NAME} smashlib_x86.o -lc amd64: # 同上,-m64 编译 install: x86 amd64 install ./x86/${APP_NAME} ../../src/main/resources/native/linux/x86/ install ./amd64/${APP_NAME} ../../src/main/resources/native/linux/amd64/要点:
-fPIC -shared产出位置无关的共享库;-lc显式链接 C 标准库。-m32目标要求安装 32 位开发包(如lib32gcc/glibc-devel.i686一类),否则编译失败。install目标直接把两个架构的.so复制到../../src/main/resources/native/linux/{x86,amd64}/——即 JAR 内资源路径/native/linux/<arch>/smashlib.so,与JNIRcmLoader的加载路径完全对应。clean清理.o与产物目录;uninstall删除已安装的资源文件。
5.3 Raspberry Pi(ARM 32 位)
BUILD.md 的步骤是用专用 Makefile 顶替默认 Makefile 再构建:
$ cd 'JNI sources/linux' $ mv Makefile_rapsberry Makefile # BUILD.md 原文拼写为 'Makefile_rapsberry',实际文件名为 Makefile_raspberry $ make install clean注意 BUILD.md 中文件名有笔误(
rapsberry),当前仓库实际文件为 Makefile_raspberry。
两个值得注意的实现细节(相对 BUILD.md 的直接展开):
- 编译器为
arm-linux-gnueabihf-gcc(ARMv7 硬浮点交叉工具链),只产出arm/smashlib.so,安装到../../src/main/resources/native/linux/arm/。 - 该 Makefile 对
JAVA_HOME做了兜底:未设置时回落到/usr/lib/jvm/default-java/include,这对应 Debian/Raspberry Pi OS 上default-jdk的默认布局,比 Linux amd64 版 Makefile 的"硬性要求JAVA_HOME"更宽松。
ifeq ($(JAVA_HOME),) JAVA_LIB_PATH := "/usr/lib/jvm/default-java/include" JAVA_LIB_PATH_LINUX := "/usr/lib/jvm/default-java/include/linux" else JAVA_LIB_PATH = ${JAVA_HOME}/include JAVA_LIB_PATH_LINUX = ${JAVA_HOME}/include/linux endif5.4 Windows(x86 与 amd64)
BUILD.md 将 Windows 段标注为[ This part should be updated ],因此以"文档步骤 + 当前仓库实际 Makefile"两者结合来看。
环境准备(文档原文步骤)
- 安装 MinGW(x86,文档建议装入
C:\MinGW\)、msys(文档原文以 "msy (?)" 标注,表明作者自己也不确定是否必需)与 MinGW-w64(amd64),以及 JDK; - 设置
JAVA_HOME; - 将 MinGW、MSYS、JDK/bin 等目录加入
PATH。x86 场景下文档明确列出:C:\MinGW\bin、C:\MinGW\msys\1.0\bin。
x86 构建
Update sources: set (uncomment) line 'BUILD_FOR_X86'$ cd 'JNI sources/windows' $ make x86当前 JNI sources/windows/Makefile 中 x86 目标的实际命令为:
x86: $(MKDIR_P) ./x86 export PATH="C/MinGW/bin/:${PATH}" $(CC32) ${CFLAGS} -m32 -c -fPIC -I "C:/MinGW/include/ddk" -I "${JAVA_HOME}/include" -I "${JAVA_HOME}/include/win32" smashlib.c -o ./smashlib_x86.o $(CC32) ${CFLAGS} -shared -o ./x86/${APP_NAME} ./smashlib_x86.o -lsetupapi -lhid -Wl,--add-stdcall-alias其中CC32='C:/MinGW/bin/gcc'路径是写死的,与 BUILD.md "Install MinGw to C:\MinGW" 的建议一致;链接参数-lsetupapi -lhid对应setupapi.h(设备枚举)与 HID 支持,--add-stdcall-alias兼容调用方使用__stdcall别名导入。
关于 BUILD.md 提到的BUILD_FOR_X86宏:当前 JNI sources/windows/smashlib.c 第 16 行存在的是#define _BUILD_FOR_X86(带下划线前缀),而第 25 行实际判断的是#ifdef BUILD_FOR_X86(不带下划线),二者并不对应。可以推断该宏用于在编译 x86 版时条件引入ntddser.h等头文件;从源码现状看,若要按文档"set (uncomment) line 'BUILD_FOR_X86'"的说法使条件编译真正生效,可能需要在源码中把宏名对齐,这属于文档与源码尚未完全同步的遗留问题。
amd64 构建
Update sources: remove line 'BUILD_FOR_X86' $ make amd64Makefile 中CC64指向C:/Program Files/mingw-w64/x86_64-8.1.0-win32-seh-rt_v6-rev0/mingw64/bin/gcc——即文档所说 "Install MinGw-w64" 的具体发行版(8.1.0 win32-seh 构建);如果本机安装的是别的 MinGW-w64 版本/路径,需要相应修改该变量。
install目标与 Linux 对称,把smashlib.dll分别装入src/main/resources/native/windows/{x86,amd64}/。
5.5 原生库做了什么:smashlib 的工作原理
理解构建产物"装进去有什么用"有助于判断是否值得投入编译。以 Linux 版 smashlib.c 为例,smashLinux(bus_id, device_addr)的完整流程:
- 拼出
/dev/bus/usb/%03d/%03d设备路径并open(O_RDWR)(要求对应 udev 规则放权,README 中 RCM 设备规则为SUBSYSTEM=="usb", ATTRS{idVendor}=="0955", ATTRS{idProduct}=="7321", MODE="0666"); - 构造一条
USBDEVFS_URB_TYPE_CONTROLURB:bRequestType=0x82(设备→接口/设备级输入控制请求)、bRequest=USB_REQ_GET_STATUS、wLength=0x7000,即向设备请求读取一段状态块——这正是 'Fusée Gelée' 式攻击中触发 Tegra X1 状态读取的关键控制传输; ioctl(fd, USBDEVFS_SUBMITURB, &urb)提交 URB,等待 1/4 秒后USBDEVFS_REAPURBNDELAY非阻塞收割结果;遇到EAGAIN则DISCARDURB取消、再等 40ms 重试收割一次(代码注释直接指向内核实现/usr/src/linux/drivers/usb/core/devio.c的行为);- 成功返回 0,任何一步失败返回负值/错误码。
Windows 版 smashlib.c 思路相同但走 Win32 栈:用SetupDiGetClassDevs+GUID_DEVINTERFACE_USB_DEVICE枚举 USB 接口设备,按vid_0955&pid_7321(Switch RCM 模式下的设备标识)匹配设备路径,随后CreateFile打开设备句柄,向 libusb 兼容的LIBUSB_IOCTL_GET_STATUS(CTL_CODE(..., 0x807, ...))发起DeviceIoControl完成同样的控制传输。文件头注释明确了返回值约定:-2设备未连接、-1无法打开句柄、0成功。
这也解释了为什么 BUILD.md 中 JNI 一节可以整体标注 OPTIONAL:不编译时JNIRcmLoader.load()返回false,RcmSmash.isSupported()为false,应用退回 Rcm.java 的纯 Java usb4java 路径发送 payload;编译并安装到资源目录后,原生路径才会在相应平台被启用。
6. 构建排错速查
| 现象 | 原因与处理 |
|---|---|
mvn package报编译错误(如 "invalid target release") | 构建 JDK 低于 23。pom 的<release>与maven.compiler.release均锁定 23,请升级构建 JDK |
Maven 拉取ru.redrise:libKonogonka:0.1失败 | 该依赖托管在 pom<repositories>声明的私有仓库(redrise.ru),需网络可达该镜像源 |
make x86(Linux)报缺头文件 | 未装 32 位开发环境(32 位 glibc/gcc 支持)或JAVA_HOME未设置;Makefile 依赖${JAVA_HOME}/include/{,linux}下的jni.h |
Raspberry Pi Makefile 找不到arm-linux-gnueabihf-gcc | 未安装交叉工具链;或在 Pi 本机编译时应使用本机 gcc 而非交叉编译器 |
| Windows 下找不到 gcc | CC32/CC64是硬编码绝对路径,与文档建议的安装位置不符时需修改 Makefile 变量 |
JAR 中无native/资源、RCM 注入走 Java 路径 | 属预期行为:仓库不携带预编译原生库,需按第 5 节make install后再执行一次mvn package才会被打进 JAR |
7. 小结:版本号的单一来源
NS-USBloader 的构建体系以 pom.xml 的<version>为唯一事实来源:它同时驱动 fat JAR 文件名(finalName)、app.properties过滤注入的_version、launch4j 的versionInfo,并经 update_version.sh 同步到 NSIS 安装器的!define与OutFile。只要遵循 "两段数字" 的版本格式约束,一次mvn -B -DskipTests clean package(加可选的 JNImake install前置步骤)即可在 Linux/macOS/Windows 上得到一致的分发产物;需要兼容 Mojave 之前 macOS 时,仅改动 usb4java 至 1.2.0 重跑同一命令即得到 legacy 变体。
相关仓库入口,供进一步深入:BUILD.md、pom.xml、JNI sources/linux/Makefile、JNI sources/windows/Makefile、src/main/java/nsusbloader/Utilities/JNIRcmLoader.java、src/main/java/nsusbloader/Utilities/Rcm.java。
- 桌面应用
- CLI
【免费下载链接】ns-usbloader
Awoo Installer and GoldLeaf uploader of the NSPs (and other files), RCM payload injector, application for split/merge files.
相关推荐
TuxGuitar 源码构建与多平台打包实战:从 SWT 依赖配置到 Maven 原生模块编译
TuxGuitar 源码构建与多平台打包实战:从 SWT 依赖配置到 Maven 原生模块编译 本文围绕 TuxGuitar 仓库的 INSTALL.md ht
桌面应用音视频AionUi 跨平台构建脚本全解析:从 electron-vite 编译到原生模块重建
AionUi 跨平台构建脚本全解析:从 electron vite 编译到原生模块重建 AionUi 是 iOfficeAI 旗下基于 Electron 的跨平
人工智能AI 应用AI Agent交互助手桌面应用移动开发RocksDB Java 跨平台 JNI 静态库构建与 Maven 中央仓库发布指南
RocksDB Java 跨平台 JNI 静态库构建与 Maven 中央仓库发布指南 本文基于当前仓库的 java/RELEASE.md https://lin
数据库KV存储嵌入式数据库存储
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考