funinkina/Ricoh-SP200-Linux

GitHub: funinkina/Ricoh-SP200-Linux

通过对 Windows 驱动的 USB 流量进行逆向工程,为无官方 Linux 支持的 Ricoh SP 200 打印机实现了完整的 CUPS 过滤器,填补了 foo2zjs 等开源驱动方案的空白。

Stars: 0 | Forks: 0

# Ricoh SP 200 Linux CUPS 驱动 这是一个为 Ricoh SP 200 黑白激光打印机编写的原生 Linux CUPS 过滤器,通过对官方 Windows 驱动的 USB 数据抓包进行逆向工程开发而来。 该打印机没有官方的 Linux 驱动程序,并且在 foo2zjs、OpenPrinting、HPLIP 和 Gutenprint 中均未提供支持。本驱动从头实现了完整的打印协议。 ## 文件 | 文件 | 用途 | |---|---| | `rastertoricohjbig.c` | CUPS 过滤器源代码 | | `ricoh-sp200.ppd` | PPD 打印机描述文件 | ## 依赖 **Arch Linux** ``` sudo pacman -S cups ghostscript jbigkit ``` **Debian / Ubuntu** ``` sudo apt install libcups2-dev libcupsimage2-dev libjbig-dev jbigkit-bin gcc ghostscript ``` **Fedora / RHEL** ``` sudo dnf install cups-devel cups-libs jbigkit-devel jbigkit-libs gcc ghostscript ``` **macOS** ``` brew install cups jbigkit brew services start cups # run Homebrew CUPS on port 631 ``` ## 构建与安装 ### 使用 make(推荐) ``` # 构建并安装 filter + PPD sudo make install # 向 CUPS 注册打印机(打印机必须通过 USB 连接) sudo make register # 测试打印 echo "Hello from Linux" | lpr -P Ricoh_SP_200_DDST # 卸载全部内容 sudo make uninstall ``` ### 手动步骤 — Linux ``` # 编译 gcc -O2 -o rastertoricohjbig rastertoricohjbig.c \ $(cups-config --libs) -lcupsimage -ljbig # 安装 filter 和 PPD sudo install -m 755 rastertoricohjbig /usr/lib/cups/filter/rastertoricohjbig sudo install -m 644 ricoh-sp200.ppd /usr/share/ppd/cupsfilters/ # 注册打印机(打印机必须通过 USB 连接) sudo lpadmin -p Ricoh_SP_200_DDST \ -v "$(lpinfo -v | grep -i ricoh | awk '{print $2}' | head -1)" \ -P /usr/share/ppd/cupsfilters/ricoh-sp200.ppd \ -E # 测试打印 echo "Hello from Linux" | lpr -P Ricoh_SP_200_DDST ``` ### 手动步骤 — macOS ``` BREW=$(brew --prefix) # 编译(Homebrew 提供 cups 和 jbigkit 头文件/库) gcc -O2 -I$BREW/include -o rastertoricohjbig rastertoricohjbig.c \ -L$BREW/lib -lcups -lcupsimage -ljbig # 安装 filter 和 PPD sudo install -m 755 rastertoricohjbig $BREW/libexec/cups/filter/rastertoricohjbig sudo install -m 644 ricoh-sp200.ppd /Library/Printers/PPDs/Contents/Resources/ # 注册打印机(打印机必须通过 USB 连接) sudo lpadmin -p Ricoh_SP_200_DDST \ -v "$(lpinfo -v | grep -i ricoh | awk '{print $2}' | head -1)" \ -P /Library/Printers/PPDs/Contents/Resources/ricoh-sp200.ppd \ -E # 测试打印 echo "Hello from macOS" | lpr -P Ricoh_SP_200_DDST ``` ## 逆向工程过程 ### 1. 抓取 USB 流量 打印机通过 USB 透传给 Windows 虚拟机。在 Linux 主机上,使用 `usbmon` + Wireshark 在打印测试文档时抓取了所有的 USB 批量传输。 ``` # 使用的 Wireshark filter: usb.transfer_type == 0x03 && usb.endpoint_address.direction == OUT ``` 单页打印任务产生了两个 `URB_BULK out` 数据包(约 65 KB 和约 59 KB)。双页打印任务产生了三个数据包,这揭示了 Windows 驱动程序会将 JBIG 流按照需要分割成多个 USB 批量传输(每个不超过 65 508 字节)。 ### 2. 识别协议 数据包 1 的前几个字节解码为 ASCII: ``` ESC%-12345X@PJL @PJL SET COMPRESS=JBIG @PJL SET PAPERWIDTH=4961 @PJL SET IMAGELEN=65556 ``` 这立即识别出了协议:包装 **JBIG1 (ITU-T T.82)** 压缩光栅数据的 **PJL (Printer Job Language)**。最初关于打印机使用 HBPL2(像其他 foo2zjs Ricoh 打印机那样)的假设是错误的——在数据流中用 `grep` 搜索 `HBPL` 一无所获。 ### 3. 解码 JBIG BIE 头 紧随 PJL 头之后的 20 个字节最初被假定为一个 Ricoh 专有头。结果证明它们是一个标准的 **JBIG1 BIE (Bi-level Image Entity) 头**: | 偏移量 | 值 | 字段 | 含义 | |---|---|---|---| | 0–3 | `00 00 01 00` | DL, D, P, reserved | Layer 0, 0 diffs, 1 plane | | 4–7 | `00 00 13 61` | Xd | **4961 px** = A4 宽度 @ 600 dpi | | 8–11 | `00 00 1b 68` | Yd | **7016 px** = A4 高度 @ 600 dpi | | 12–15 | `00 00 00 80` | L0 | 128 行/条 | | 16–17 | `00 00` | Mx, Dmax | 0 | | 18 | `03` | order | 直接存储在 BIE 字节 18 中 | | 19 | `48` | options | 直接存储在 BIE 字节 19 中 | ### 4. 发现多页协议 第二次抓取了打印双页文档的过程。从拼接的 USB 流中提取所有 `@PJL` 令牌揭示了完整的页面生命周期: ``` [page 1 JBIG data — first chunk] @PJL SET IMAGELEN=13451 ← second chunk of page 1 (driver splits large pages) [13451 bytes — continuation of page 1 JBIG stream] @PJL SET DOTCOUNT=1477935 ← black pixel count for page 1 @PJL SET PAGESTATUS=END ← eject page 1 @PJL SET PAGESTATUS=START ← begin page 2 @PJL SET COPIES=1 @PJL SET MEDIASOURCE=TRAY1 @PJL SET MEDIATYPE=PLAINRECYCLE @PJL SET PAPER=A4 ← page 2 media settings (repeated every page) @PJL SET PAPERWIDTH=4961 @PJL SET PAPERLENGTH=7016 @PJL SET RESOLUTION=600 @PJL SET IMAGELEN=65556 ← page 2 first chunk [65556 bytes — page 2 JBIG data, first chunk] @PJL SET IMAGELEN=10550 ← page 2 second chunk [10550 bytes — page 2 JBIG continuation] @PJL SET DOTCOUNT=1266291 @PJL SET PAGESTATUS=END ← eject page 2 @PJL EOJ ESC%-12345X ``` 主要发现: - **`PAGESTATUS=END` 是页面弹出触发器。** 如果没有它,固件会将后续所有的 `IMAGELEN` 视为仍处于打开状态的同一页面的另一个数据块,并且永远不会弹出。 - **每个页面可以跨越多个 `IMAGELEN` 数据块。** 驱动程序将任何超过一次 USB 批量传输大小的 JBIG 流拆分为连续的 `@PJL SET IMAGELEN` 块,它们都属于同一页。Linux 过滤器生成的 JBIG 流较小(无需分块),但固件能够同时处理这两种情况。 - **除第一页外,每一页在首次 `IMAGELEN` 之前都有其专属的 `PAGESTATUS=START` + 完整介质头**(`COPIES`、`MEDIASOURCE`、`MEDIATYPE`、`PAPER`、`PAPERWIDTH`、`PAPERLENGTH`、`RESOLUTION`)。 - **`DOTCOUNT`** 报告页面的总黑色像素数,供打印机用于碳粉寿命估算。 ### 5. 完整的协议结构 ``` ── Job header (once) ────────────────────────────────────────────────────────── ESC%-12345X@PJL\r\n @PJL SET TIMESTAMP=YYYY/MM/DD HH:MM:SS\r\n @PJL SET FILENAME=...\r\n @PJL SET COMPRESS=JBIG\r\n @PJL SET USERNAME=...\r\n @PJL SET COVER=OFF\r\n @PJL SET HOLD=OFF\r\n @PJL SET PAGESTATUS=START\r\n ← covers page 1 @PJL SET COPIES=N\r\n @PJL SET MEDIASOURCE=TRAY1\r\n @PJL SET MEDIATYPE=PLAINRECYCLE\r\n ── Per-page block (repeat for every page) ───────────────────────────────────── [pages 2+ only:] @PJL SET PAGESTATUS=START\r\n @PJL SET COPIES=N\r\n @PJL SET MEDIASOURCE=TRAY1\r\n @PJL SET MEDIATYPE=PLAINRECYCLE\r\n @PJL SET PAPER=\r\n @PJL SET PAPERWIDTH=\r\n @PJL SET PAPERLENGTH=\r\n @PJL SET RESOLUTION=600\r\n @PJL SET IMAGELEN=\r\n @PJL SET DOTCOUNT=\r\n @PJL SET PAGESTATUS=END\r\n ← triggers paper ejection ── End of job (once) ────────────────────────────────────────────────────────── @PJL EOJ\r\n ESC%-12345X\r\n ``` ### 6. 构建 CUPS 过滤器 该过滤器(`rastertoricohjbig.c`)使用了: - **libcups / libcupsimage** — 从标准输入读取 CUPS 光栅流 - **libjbig** (jbigkit 2.1) — 将每一页编码为 JBIG1 BIE - 编码后的 BIE 被缓存在内存中,测量其大小,然后在前面加上 `@PJL SET IMAGELEN=N` 输出到标准输出 PPD 声明了 `*cupsBitsPerColor: 1` 和 `*cupsColorSpace: 3`,因此 CUPS 会直接传递 1 位打包的 K 通道位图——在正常路径下无需进行转换。 ### 7. 开发过程中发现的 Bug **Bug 1 — UEL 后缺少裸 `@PJL\r\n`** Windows 驱动程序在第一个 SET 命令之前会发送 `ESC%-12345X@PJL\r\n`。如果没有这个裸 `@PJL\r\n` 行,打印机将静默丢弃整个打印任务。 **Bug 2 — 缺少 `@PJL SET PAPERLENGTH`** 打印机要求同时提供 `PAPERWIDTH` 和 `PAPERLENGTH`。如果没有 `PAPERLENGTH`,打印机会初始化其引擎(发出噪音 + LED 闪烁),但永远不会吸纸。 **Bug 3 — 错误的 JBIG BIE 选项字节** jbigkit 2.1 将 `jbg_enc_options()` 的 `options` 参数**直接**存储到 BIE 字节 19 中,而不进行位转换。Windows 驱动程序生成的字节 19 = `0x48`。传入 `0x08` 会导致打印机接受任务、预热,然后拒绝进纸。传入 `0x48` 解决了此问题。 **Bug 4 — PPD 缺少光栅格式指令** 如果没有 `*cupsBitsPerColor: 1`、`*cupsColorSpace: 3` 及相关的 PPD 键,CUPS 会向过滤器传递 8 位灰度数据。步幅是按 `(w+7)/8`(假设为 1 位)计算的,但实际数据却宽了 8 倍,导致生成了格式错误的 JBIG 流。 **Bug 5 — 多页:仅打印第一页** 通过抓取 Windows 驱动程序的双页任务发现。固件使用 `@PJL SET PAGESTATUS=END` 作为页面弹出触发器——而不是下一个 `IMAGELEN` 的到来。如果每页的 JBIG 数据之后没有 `PAGESTATUS=END`,后续每个 `IMAGELEN` 块都会被视为同一打开页面的另一个数据块。打印机会将所有页面的数据累积为一个巨大的第 1 页,并且从不将其弹出。在同一次抓包中还发现了其他三个遗漏: - `PAGESTATUS=START` + 完整介质头(`COPIES`、`MEDIASOURCE`、`MEDIATYPE`、`PAPER`、`PAPERWIDTH`、`PAPERLENGTH`、`RESOLUTION`)必须位于除第一页之外的每个页面之前。 - `DOTCOUNT`(黑色像素计数)必须逐页发送,紧接在 `PAGESTATUS=END` 之前。 - 从 CUPS 光栅头获取的 `cupsBytesPerLine` 必须用作读取像素数据时的行步长——手动计算的 `(bpp*w+7)/8` 可能会与 CUPS 的实际行大小产生偏差,导致流错位并使 `cupsRasterReadHeader2` 在处理第 2 页时失败。 ## 故障排除 **`lpinfo -v` 未找到打印机** ``` lsusb | grep -i ricoh ``` **过滤器未被调用(任务静默消失)** ``` ls -la /usr/lib/cups/filter/rastertoricohjbig # 必须是:-rwxr-xr-x root root sudo chmod 755 /usr/lib/cups/filter/rastertoricohjbig sudo chown root:root /usr/lib/cups/filter/rastertoricohjbig ``` **CUPS 显示打印机已停止** ``` sudo cupsenable Ricoh_SP_200_DDST sudo cupsaccept Ricoh_SP_200_DDST ``` **检查 CUPS 错误日志** ``` sudo tail -40 /var/log/cups/error_log ``` **构建失败 — 缺少 jbig.h** ``` # Arch: sudo pacman -S jbigkit # Debian: sudo apt install libjbig-dev # Fedora: sudo dnf install jbigkit-devel # macOS: brew install jbigkit ``` **macOS:过滤器已安装但 CUPS 找不到打印机** ``` # 确保 Homebrew CUPS 正在运行(而不是系统 daemon) brew services restart cups lpinfo -v | grep -i ricoh ``` *2026 年 5 月逆向工程。未使用或分发任何专有代码。* *协议线缆格式文档不受版权限制。*
标签:Awesome, CUPS, DDST, Ghostscript, JBIG, PPD, rastertoricohjbig, Ricoh SP 200, USB协议, 云资产清单, 单色打印机, 客户端加密, 开源驱动, 打印机驱动, 激光打印机, 硬件适配, 系统管理, 编译构建, 逆向工程