news 2026/10/15 3:41:49

NS-USBloader 构建指南:从 Maven 打包到跨平台 JNI 原生库编译全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NS-USBloader 构建指南:从 Maven 打包到跨平台 JNI 原生库编译全解析
  • 桌面应用
  • CLI

【免费下载链接】ns-usbloader

Awoo Installer and GoldLeaf uploader of the NSPs (and other files), RCM payload injector, application for split/merge files.

项目地址:https://gitcode.com/gh_mirrors/ns/ns-usbloader
点击查看免费下载

本文以仓库根目录的 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 的构建分为两个相互独立的层次:

  1. Java 应用层(必需):一个标准 Maven 工程,mvn package即产出 fat JAR(含依赖),Windows 场景下还会由 launch4j 生成.exe启动器。
  2. 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 x86JNI sources/linuxmake x86(-m32交叉编译)x86/smashlib.sosrc/main/resources/native/linux/x86/
Linux amd64JNI sources/linuxmake amd64amd64/smashlib.sosrc/main/resources/native/linux/amd64/
Raspberry Pi (ARM)JNI sources/linuxmake arm(交叉工具链)arm/smashlib.sosrc/main/resources/native/linux/arm/
Windows x86JNI sources/windowsmake x86(MinGW)x86/smashlib.dllsrc/main/resources/native/windows/x86/
Windows amd64JNI sources/windowsmake amd64(MinGW-w64)amd64/smashlib.dllsrc/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 的插件配置决定了这条命令背后实际发生了什么,这里完整列出构建链:

  1. maven-jar-plugin 的default-jar被禁用(<phase>none</phase>)——pom 中注释写明 "Don't generate default JAR without dependencies",即不产出无依赖的瘦 JAR。
  2. 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拦截的问题。
  3. 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 分发场景。
  4. 测试插件: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.java

javac -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 clean

make无参数时命中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 endif

5.4 Windows(x86 与 amd64)

BUILD.md 将 Windows 段标注为[ This part should be updated ],因此以"文档步骤 + 当前仓库实际 Makefile"两者结合来看。

环境准备(文档原文步骤)

  1. 安装 MinGW(x86,文档建议装入C:\MinGW\)、msys(文档原文以 "msy (?)" 标注,表明作者自己也不确定是否必需)与 MinGW-w64(amd64),以及 JDK;
  2. 设置JAVA_HOME;
  3. 将 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 amd64

Makefile 中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)的完整流程:

  1. 拼出/dev/bus/usb/%03d/%03d设备路径并open(O_RDWR)(要求对应 udev 规则放权,README 中 RCM 设备规则为SUBSYSTEM=="usb", ATTRS{idVendor}=="0955", ATTRS{idProduct}=="7321", MODE="0666");
  2. 构造一条USBDEVFS_URB_TYPE_CONTROLURB:bRequestType=0x82(设备→接口/设备级输入控制请求)、bRequest=USB_REQ_GET_STATUS、wLength=0x7000,即向设备请求读取一段状态块——这正是 'Fusée Gelée' 式攻击中触发 Tegra X1 状态读取的关键控制传输;
  3. ioctl(fd, USBDEVFS_SUBMITURB, &urb)提交 URB,等待 1/4 秒后USBDEVFS_REAPURBNDELAY非阻塞收割结果;遇到EAGAIN则DISCARDURB取消、再等 40ms 重试收割一次(代码注释直接指向内核实现/usr/src/linux/drivers/usb/core/devio.c的行为);
  4. 成功返回 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 下找不到 gccCC32/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.

项目地址:https://gitcode.com/gh_mirrors/ns/ns-usbloader
点击查看免费下载

相关推荐

上一篇:CopilotKit 与 CrewAI Conversational Flows 集成之 Agentic Chat 深度测试与验证指南
下一篇:Angular Material Toolbar 测试 Harness API 全解析:从 API 报告到 TestBed 实战

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 3:40:17

我写了个爬虫-1000条数据5分钟抓完还不会崩

我写了个爬虫&#xff0c;1000 条数据 5 分钟抓完&#xff0c;还不会崩 需要抓取 books.toscrape.com 上 1000 本书的书名、价格、评分&#xff0c;分页共 50 页。脚本一次运行 5 分钟左右抓完&#xff0c;全程零人工干预&#xff1b;最终实收 1000 条&#xff0c;缺失 0、重复…

作者头像 李华
网站建设 2026/10/15 3:39:55

大麦网自动抢票指南:如何用 Python 脚本 4 步跑通自动购票流程

大麦网自动抢票指南&#xff1a;如何用 Python 脚本 4 步跑通自动购票流程 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 19:30:00&#xff0c;大麦网开票倒计时归零的那一刻…

作者头像 李华
网站建设 2026/10/15 3:39:06

时序网络瓦解Ⅲ--时序供应链网络中的级联失效和恢复机制

一、概念背景 三种经典级联失效模型。级联失效是指局部节点 / 边的失效会通过负载重分配、邻接影响等方式连锁传导,最终引发全网大范围崩溃的过程,是网络鲁棒性、网络瓦解研究的重要基础方向。 一、容量 - 负载模型(Motter-Lai 模型,2002) 核心原理 这是最经典的负载…

作者头像 李华