分类: 摄影

  • 【摄影】Nikon F90X/N90s F90/N90系列胶片相机串口通信协议逆向项目

    基于尼康官方软件AC-PW,提取其核心 Pcf9032.dll逆向
    由 DeepSeek 4.0 Pro 辅助完成。


    第一章 概述

    1.1 适用范围

    本报告完整描述 F90X/N90s F90/N90 胶片单反相机通过串行接口与计算机通信的底层协议、命令集、寄存器映射、功能编号及所有操作序列。所有内容均来自对官方控制库 Pcf9032.dll 及配套软件 PhotoSecretary的逆向分析,并通过资源提取进行验证。

    1.2 通信架构

    • 物理层:RS-232C 串行通信,5V 电平
    • 波特率:初始 1200 bps,握手后切换至 9600 bps
    • 数据格式:8 数据位,无校验,1 停止位 (8N1)
    • 端口选择:COM1~COM4,由功能 ID 0 设定
    • DLL 文件:Pcf9032.dll(Nikon 相机通信库),由主程序动态加载

    1.3 相机型号识别

    型号标识相机返回字符串地址字节对应型号
    1F90N900x10F90系列/N90(北美发售型号)系列
    2F90xN90s0x20F90X系列/N90S(北美发售型号)系列

    第二章 通信初始化

    2.1 唤醒与型号识别

    1. 打开串口 COM%d,配置为 1200,8N1。
    2. 发送唤醒字节:00(1 字节),等待 200ms,清空接收缓冲区。
    3. 发送型号查询:53 31 30 30 30 05S1000 + ENQ,共 6 字节)。
    4. 接收相机返回的标识字符串(例如 31 30 32 30 46 39 30 58 2F 4E 39 30 53 00 03 06 表示 1020F90X/N90S)。
    5. 根据字符串确定型号:
      • 若包含 F90N90 → 型号 1,地址字节 0x10。
      • 若包含 F90xN90s → 型号 2,地址字节 0x20。

    2.2 波特率升级至 9600

    1. 发送 9 字节命令(型号 1 用 0x10,型号 2 用 0x20):
      01 10 87 05 00 00 00 00 03 或 01 20 87 05 00 00 00 00 03
    2. 等待相机应答 06 00(ACK)。
    3. 将本地串口切换为 9600 bps,等待 200ms 稳定。

    2.3 结束 9600 会话(返回 1200)

    • 发送 04 04(EOT 两次),相机返回 04 04 确认,然后串口恢复到 1200。
    • 若相机自动休眠,波特率也会自动降回 1200。

    第三章 通信帧格式

    校验和仅计算纯数据部分,即从 STX (0x02) 之后的第一个字节开始,到 ETX (0x03) 之前的所有字节,不包括帧头(SOH)、地址、命令码、长度字段等任何头部字节。

    波特率切换等特殊握手指令会使用 0x87 作为操作码,此类特殊指令属于硬编码控制帧,不完全遵循常规寄存器读写帧的载荷结构。

    3.1 标准命令帧(命令码 ≤ 100)

    读请求(无数据)

    偏移 大小 值       说明
    0 1 0x01 帧头 (SOH)
    1 1 ADDR 设备地址 (0x10 或 0x20)
    2 1 0x80 读操作
    3 1 0x00 保留
    4 1 v6 寄存器地址高字节
    5 1 v7 寄存器地址低字节
    6 1 0x00 保留
    7 1 len 期望接收的数据长度
    8 1 0x03 帧尾 (ETX)

    总长 9 字节,无校验。

    写请求(有数据)

    偏移 大小 值       说明
    0    1    0x01     帧头
    1    1    ADDR     设备地址
    2    1    0x81     写操作
    3    1    0x00     保留
    4    1    v6       寄存器地址高字节
    5    1    v7       寄存器地址低字节
    6    1    0x00     保留
    7    1    len      数据长度(实际发送字节数)
    8    1    0x02     数据起始标识 (STX)
    9~   len   DATA    数据体
    9+len  1   CHK     校验和(所有 DATA 字节累加和的低 8 位)
    10+len 1   0x03     帧尾 (ETX)
    11+len 1   0x00     填充

    总长 12 + len 字节。

    3.2 扩展命令帧(命令码 > 100)

    读请求

    偏移 大小 值       说明
    0    1    0x01     帧头
    1    1    ADDR     设备地址
    2    1    0x1B     命令组标识
    3    1    0x90     命令组标识
    4    1    0x82     读操作
    5    1    v6       参数(由命令码决定)
    6    1    len      期望接收长度
    7    1    0x00     保留
    8    1    0x03     帧尾

    总长 9 字节。

    写请求

    偏移 大小 值       说明
    0    1    0x01     帧头
    1    1    ADDR     设备地址
    2    1    0x1B     命令组标识
    3    1    0x90     命令组标识
    4    1    0x81     写操作
    5    1    v6       参数
    6    1    len      数据长度
    7~   len   DATA    数据体
    7+len 1    CHK     校验和
    8+len 1    0x03     帧尾
    9+len 1    0x00     填充

    总长 10 + len 字节。

    3.3 应答帧

    • 短应答(ACK)06 00 表示成功。
    • 特殊同步应答04 04 用于同步。
    • 长应答(数据包)
      • 字节 0:状态码(通常忽略,或为 0x01)
      • 字节 1~(n-2):数据
      • 字节 (n-1):校验和(字节 1 到字节 n-2 的累加和低 8 位)
      • 字节 n:0x03 (ETX)

    3.4 重试机制

    发送命令后,最多重试 4 次(外层循环)× 4 次(内层发送尝试)。每次发送失败等待 200ms,若连续 4 次内层失败则退出内层;内层结束后等待 150ms 再进入下一次外循环。


    第四章 命令码与寄存器地址映射表

    4.1 标准命令码表(≤100)

    从 DLL 函数 sub_1000DB73 的 switch 分支提取。v6v7 以十六进制补码表示(负值转换为无符号字节,例 -3 = 0xFD)。

    命令码v6 (hex)v7 (hex)寄存器地址常用数据长度方向关联功能ID
    0FD19FD191读/写
    1FD17FD172(读)/6(写)读/写
    2FD1AFD1A1读/写
    3FD1CFD1C1读/写
    4FD1BFD1B1读/写
    5FD16FD161读/写
    6FD28FD28113
    7FD26FD26114,15,16
    8FD27FD27114,15,16
    9FD29FD29118
    10FD2BFD2B120
    11FD2AFD2A119
    12FD2DFD2D121
    13FD2EFD2E126
    14FD21FD211
    15FD2CFD2C122,23
    16FE44FE442读/写
    17FEC9/C1FE?1型号相关
    18FD20FD201读/写0x64,0x66
    19FD39FD391读/写
    20FD3AFD3A1读/写0x64,0x66
    21FD3BFD3B1读/写0x64
    22FD3CFD3C2读/写0x64
    23FED0FED01用户设置
    24FDF2FDF22用户设置
    25FDF3FDF31读/写
    26FE94FE942用户设置
    27FE95FE951读/写
    28FE9EFE9E1用户设置
    29FD25FD25117
    30FD36FD363读/写90-95
    31FEC8FEC81用户设置
    32FD33FD333(型号1)读/写0x46-0x52
    33FD34FD341状态检测
    34FD40FD401读/写131
    35FD30FD306(型号2)读/写0x46-0x52
    36FD41FD413读/写
    37FD24FD241状态查询
    38FE50FE501状态检测
    39FE20FE201读/写
    40FEDB/D4FE?1型号相关
    41FD93/8EFD?1型号相关
    42FD9D/90FD?1型号相关
    43FE06FE061
    44FD1FFD1F10x64

    4.2 扩展命令码表(>100)

    从 sub_1000DED3 的 switch 提取,命令码对应 ASCII 字符,v6 为帧内参数。

    命令码ASCIIv6 (hex)常用数据长度方向
    101‘e’003
    102‘f’065
    103‘g’101
    118‘v’701读/写
    119‘w’722读/写
    122‘z’801

    第五章 功能 ID 完整映射

    5.1 设定/读取函数导出索引

    导出序号函数名用途
    1FeatureExists检查功能ID是否有效
    2GetIndexString获取功能选项文本
    3GetMaxIndex获取选项最大索引
    8GetValueBool读取布尔型参数
    9GetValueFloat未实现
    10GetValueIndex读取索引型参数
    11GetValueInteger读取整型参数
    12GetValueString读取字符串参数
    13GetValueStruct读取结构体参数
    14InvalidateReads清除读取缓存
    15SetValueBool设置布尔型参数
    16SetValueFloat未实现
    17SetValueIndex设置索引型参数
    18SetValueInteger设置整型参数
    19SetValueString设置字符串参数
    20SetValueStruct设置结构体参数
    21StartProcess执行动作(拍摄、对焦、删除等)
    23WriteDataToCamera将修改的参数写入相机

    5.2 功能 ID → 命令/寄存器映射

    以下列出全部功能 ID(0~147)的精确技术映射,包括类型、设置/读取函数、所属 WriteDataToCamera 的 case、具体的命令码、寄存器地址(v6, v7)或扩展命令标识,以及在配置块中的位操作说明。

    功能 ID类型设置函数读取函数Case命令码 / 寄存器操作描述
    0IntegerSetValueInteger直接设置全局变量 word_10015218通信端口号 (1~4)
    1未使用
    2StringGetValueString直接返回相机型号字符串指针相机型号字符串 (“F90N90” 或 “F90xN90s”)
    3BoolSetValueBool直接设置 word_100122D4相机型号选择 (0=型号1, 1=型号2)
    4IndexGetValueIndex特殊:读取 word_10012170 数组电子手帐数据读取状态/进度
    5IntegerGetValueInteger直接读取 dword_10012000内部状态值(只读)
    6BoolGetValueBool5通过 sub_10008905 读取 word_100122BC未知布尔状态(只读)
    7~12未使用
    13IndexSetValueIndexGetValueIndex1命令 6 → FD28测光方式 (0=矩阵, 1=中央重点, 2=点)
    14IndexSetValueIndexGetValueIndex1命令 7/8 → FD26, FD27;位操作曝光模式 (0=P,1=S,2=A,3=M,4=CP,5-11=变程序)
    15IndexSetValueIndexGetValueIndex1命令 7/8 → FD26, FD27;位操作曝光模式(与14共用寄存器,不同位)
    16BoolSetValueBoolGetValueBool1命令 7/8 → FD26, FD27;位操作曝光模式扩展布尔
    17IndexSetValueIndexGetValueIndex1命令 29 → FD25快门速度 (B门/定时相关)
    18IndexSetValueIndexGetValueIndex1命令 9 → FD29卷片模式 (0=单张, 1=低速连拍, 2=高速连拍)
    19IndexSetValueIndexGetValueIndex1命令 11 → FD2A闪光同步模式 (0=前帘,1=慢速,2=后帘,3=防红眼)
    20IndexSetValueIndexGetValueIndex1命令 10 → FD2B对焦区域 (0=宽区, 1=点)
    21IndexSetValueIndexGetValueIndex1命令 12 → FD2D曝光补偿范围 (-5.0~+5.0 EV)
    22BoolSetValueBoolGetValueBool1命令 15 → FD2C;位操作DX/手动 ISO 选择
    23IndexSetValueIndexGetValueIndex1命令 15 → FD2CISO 感光度 (0=ISO 6, 1=ISO 8 … 30=ISO 6400)
    24IndexGetValueIndex1命令 15 读回 FD2CISO 扩展读取
    26IndexSetValueIndexGetValueIndex1命令 13 → FD2E闪光补偿/特殊设定
    27~28未使用
    29BoolGetValueBool3word_100123F2(通过 sub_100058C7 读取)MF-26 状态(只读)
    30BoolGetValueBool3word_100123F4(通过 sub_100058C7 读取)MF-26 状态(只读)
    31IndexSetValueIndexGetValueIndex3命令 18,20 等,位操作 FD20, FD3A 等MF-26 曝光模式
    32IndexSetValueIndexGetValueIndex3同上MF-26 曝光模式
    33IndexSetValueIndexGetValueIndex3命令 6 → FD28测光方式 (MF-26)
    34IndexSetValueIndexGetValueIndex3命令 9 → FD29卷片模式 (MF-26)
    35IndexSetValueIndexGetValueIndex3命令 11 → FD2A闪光同步模式 (MF-26)
    36IndexSetValueIndexGetValueIndex3命令 10 → FD2B对焦区域 (MF-26)
    37~39未使用
    40IndexSetValueIndexGetValueIndex1/8命令 1 → FD17用户设置库名称
    41IndexGetValueIndex1/8命令 1/7 读取用户设置库当前选择
    42IndexGetValueIndex8命令 43 → FE06镜头光圈值 (F1~F90)
    43IndexSetValueIndexGetValueIndex8命令 43 → FE06 等镜头光圈值(设定)
    44IndexSetValueIndexGetValueIndex1命令 9 → FD29卷片模式(另一入口)
    45IndexSetValueIndexGetValueIndex8命令 45闪光同步(另一设定)
    46BoolSetValueBoolGetValueBool8命令 15 → FD2C位操作对焦模式 (0=手动, 1=AF 等)
    47IndexGetValueIndex8命令 47ISO 显示(备用)
    48IndexSetValueIndexGetValueIndex8命令 10 → FD2B对焦区域(另一入口)
    49IndexSetValueIndexGetValueIndex8命令 49AF 方式 (0=释放优先, 1=对焦优先, 2=手动)
    50IndexGetValueIndex8命令 50对焦状态选项
    51IndexSetValueIndexGetValueIndex8命令 51对焦状态 (0=前焦,1=后焦,2=合焦,3=无法合焦)
    52IndexSetValueIndexGetValueIndex8命令 52ON/OFF 开关
    53IndexSetValueIndexGetValueIndex8命令 53曝光补偿值 (-20.0~+20.0)
    54IndexSetValueIndexGetValueIndex8命令 54曝光补偿值(另一组)
    55IndexSetValueIndexGetValueIndex8命令 55胶片计数器 (E, 0~99)
    56IndexSetValueIndexGetValueIndex8命令 56电池状态 (0=满电, 1=低电量)
    57IndexGetValueIndex8命令 57特定索引
    58IndexGetValueIndex8命令 58曝光补偿范围 (-5.0~+5.0 EV)
    59IndexSetValueIndexGetValueIndex8命令 59镜头焦距 (5mm~7600mm)
    60IndexSetValueIndexGetValueIndex8命令 60镜头焦距(另一组)
    61IndexSetValueIndexGetValueIndex8命令 61镜头焦距(另一组)
    62IndexGetValueIndex8命令 62光圈值(另一组)
    63IndexGetValueIndex8命令 63特定索引
    64IndexGetValueIndex8命令 64光圈值(另一组)
    65IndexGetValueIndex1/8命令 1 读取用户库读取
    66IndexSetValueIndexGetValueIndex1/8命令 6 → FD28测光方式(另一入口)
    67IndexSetValueIndexGetValueIndex8命令 67闪光同步(另一组)
    68IndexSetValueIndexGetValueIndex8命令 68镜头焦距(另一组)
    69BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)蜂鸣音开关(恢复默认用)
    70 (0x46)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)合焦音开关
    71 (0x47)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)DX 优先设定
    72 (0x48)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)AE/AF 同时锁定
    73 (0x49)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)AF-C 对焦优先
    74 (0x4A)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)AF-S 释放优先
    75 (0x4B)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)AF-S 帧间 AF 驱动
    76 (0x4C)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)0 帧数据印记
    77 (0x4D)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)测光偏差显示
    78 (0x4E)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)A 模式简易曝光补偿
    79 (0x4F)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)(保留)
    80 (0x50)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)闪光灯相关设定
    81 (0x51)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)闪光灯相关设定
    82 (0x52)BoolSetValueBoolGetValueBool4配置块位操作 (命令32/35)闪光灯相关设定
    83IndexSetValueIndexGetValueIndex4配置块 (命令32/35)闪光灯模式
    84IndexSetValueIndexGetValueIndex4配置块 (命令32/35)长时间曝光时间 (4秒~60秒/B门)
    85IndexSetValueIndexGetValueIndex4配置块 (命令32/35)同步释放模式
    86IndexSetValueIndexGetValueIndex4配置块 (命令32/35)(预留)
    87IndexSetValueIndexGetValueIndex4配置块 (命令32/35)自拍/间隔时间
    88StartProcess(0x58) 调用恢复默认设置(操作码)
    89StartProcess(0x89) 调用触发拍摄(操作码)
    90IndexSetValueIndexGetValueIndex7命令 30 → FD36闪光灯/自定义程序点 (快门)
    91IndexSetValueIndexGetValueIndex7命令 30 → FD36闪光灯/自定义程序点 (光圈)
    92IndexSetValueIndexGetValueIndex7命令 30 → FD36同上
    93IndexSetValueIndexGetValueIndex7命令 30 → FD36同上
    94IndexSetValueIndexGetValueIndex7命令 30 → FD36同上
    95IndexSetValueIndexGetValueIndex7命令 30 → FD36同上
    96StartProcess(0x60) 调用闪光灯操作(操作码)
    97IndexGetValueIndex8命令 97微调值
    98IndexSetValueIndexGetValueIndex8/11命令 98微调值 (0.3~2.0)
    99IndexSetValueIndexGetValueIndex8/11命令 99曝光补偿步长 (-3.0~+1.0)
    100Bool8(保留)
    101IntegerGetValueInteger8读取多个配置寄存器配置块值(只读)
    102~105未使用
    106IndexSetValueIndexGetValueIndex9扩展命令 103, 122 等时区城市 (1~24)
    107未使用
    108Index9扩展命令时区城市(备用)
    109IndexSetValueIndexGetValueIndex10扩展命令时差数值 (-10~+12, -11)
    110IndexSetValueIndexGetValueIndex10扩展命令时差数值 (-11)
    111~114未使用
    115IndexGetValueIndex11扩展命令模式选择
    116IndexSetValueIndexGetValueIndex11扩展命令微调值 (另一入口)
    117未使用
    118IndexSetValueIndexGetValueIndex11扩展命令曝光补偿步长 (同 99)
    119IndexSetValueIndexGetValueIndex11扩展命令特殊设定(自拍/闪光相关)
    120~123未使用
    124StructGetValueStruct11扩展命令读取结构体 (2 字段)
    125StructGetValueStruct11扩展命令读取结构体 (3 字段)
    126未使用
    127~12912扩展命令保留
    130StructGetValueStruct11扩展命令读取结构体 (3 字段)
    131IndexSetValueIndexGetValueIndex5命令 34 → FD40数据存储模式 (0/69/78/95)
    132IndexSetValueIndexGetValueIndex5命令 5 → FD34 bit7数据印记 / 特殊设定位
    133命令 133(内部全消去)内部使用
    134IntegerGetMinMaxInteger只读,范围 0-9999胶片编号
    135未使用
    136StartProcess(0x88) 调用指针复位/状态初始化
    137StartProcess(0x89) 调用触发拍摄(同 89)
    138未使用
    139IndexGetIndexString6字符串资源自定义功能名称/选项
    140~145未使用
    146StructGetValueStruct6扩展命令相机信息(型号、序列号等)
    147StructGetValueStruct6/5扩展命令对焦数据读取

    注释

    • 配置块位操作:功能 ID 69~82 的具体位定义已在第七章详细列出。每个布尔值对应配置块(命令32/35)的某一位,通过读-修改-写流程改变。
    • 扩展命令:功能 ID 106~130 使用命令码 >100 的扩展命令(格式 1B 90 cmd v6 len),具体 v6 和 len 见第四章扩展命令表。
    • 操作码:88, 89, 96, 136, 137 等不直接对应设置/读取函数,而是由 StartProcess 分发执行,内部命令序列已在前文各章详述。
    • 表中粗体为软件中直接面向用户的主要功能。
    • 未使用的功能 ID 在 DLL 中返回错误 2(不支持)。
    • 对于131:
    • 这些值直接写入 FD40 寄存器,对应相机的“数据存储模式”0 (0x00) → 不存储数据(Disabled)
    • 69 (0x45) → 存储最小数据量(Minimum)
    • 78 (0x4E) → 存储中间数据量(Intermediate)
    • 95 (0x5F) → 存储最大数据量(Maximum)
      该映射与我们之前整理的 FD40 寄存器功能一致,且与 DLL 资源中对应的字符串(“記憶しない”、“最小情報量記憶”等)吻合。

    第六章 特殊操作流程

    6.1 拍摄(快门释放)

    • 操作码:StartProcess(0x89)
    • DLL 函数:sub_10007BB0 → 型号1调用 sub_10007C4B,型号2调用 sub_10007CE0
    • 命令序列(以型号1为例):
      1. 读 FD20FD24FD40 获取状态。
      2. 构造写命令 01 10 81 00 FD 41 00 01 02 <data> <cs> 03 00,数据为 0x42(’B’)及校验。
      3. 等待 ACK,再读 FD20 和 FD24 完成清理。

    6.2 自动对焦

    • DLL 中未作为独立导出命令,但 EXE 的“AFの実行”菜单调用 StartProcess,其内部可触发对焦操作,对应的相机命令为 01 20 86 00 00 00 00 00 03(也可被直接发送)。

    6.3 用户设置库(保存/调用)

    • 保存到库:StartProcess(0x25) → sub_10005B79
    • 从库读取:StartProcess(0x26) → sub_10005A2EStartProcess(0x27) → sub_10005D47
    • 这些函数内部发送命令 18、20、31、119 以及 sub_1000E07D/sub_1000E183 等位操作,并读写 FD17、FD26、FD27 等寄存器。

    6.4 恢复默认设置

    • StartProcess(0x58)
    • 先将内部变量设为一组出厂默认值(详见代码中的连续赋值),然后调用 sub_100065A7,发送命令 119 或 18,最后写入配置块(命令 32 或 35)和公共配置(命令 20)。

    6.5 胶卷数据读取

    • 入口:sub_10006F09 → 型号1调用 sub_10007042,型号2调用 sub_1000749C
    • 步骤:
      1. 读 FD20FD24FD40 验证状态和存储模式。
      2. 读 FD41 获取帧数 v19 和相关信息。
      3. 根据 v19 计算动态地址:addr = 2 * v19(可能进行回绕修正)。
      4. 调用 sub_10007A14 发送动态读命令,接收数据包。
      5. 数据以 0xFF 作为卷结束标记,ISO 索引在最后。
      6. 解析帧数据(每帧 3/4/6 字节,取决于存储模式),字段包括快门速度、光圈、曝光模式、测光方式、焦距、曝光补偿等。
      7. 发送确认命令 0x42(’B’)完成本次读取。

    6.6 删除胶卷数据

    • 全消去StartProcess(0x1C) → sub_1000A9CD
      • 检查相机内是否有数据,若无则发送命令 133,相机清空整个环形缓冲区。
    • 部分删除(移动读取指针)StartProcess(0x1B) → sub_1000A7E1
      • 发送命令 134,并操作命令 19(FD39)的位 3,使相机内部的待读指针前移,等效于删除已读取的卷。
    • 指针/状态复位StartProcess(0x88) → sub_10008403
      • 型号1:写 FD41 特定值,清除 FED6 的 bit0,清零 FD40。
      • 型号2:读 FD00,写命令 ‘B’,清除 FECE 的 bit0,清零 FD40。

    第七章 配置块位定义

    从 sub_1000694E 提取(以型号1配置块命令32为例):

    • v34 低字节 = a3[10]
    • v34 高字节:
      • bit0: a3[13] 控制
      • bit1: a3[2] 控制
      • bit2: a3[11] 控制
      • bit3: a3[3] 控制
      • bit5: a3[9] 控制
      • bit6: a3[8] 控制
    • v35 低字节:
      • bit2: a3[12] (1或3置1)
      • bit3: a3[12] (2或3置1)
      • bit4: a3[5] 控制
      • bit5: a3[7] 控制
      • bit7: a3[6] 控制

    型号2配置块(命令35)有类似的位定义,详见原始反编译代码。


    第八章 错误码表

    错误码 (hex)含义
    0x19 (25)参数无效
    0x1D (29)数据块读取校验失败
    0x25 (37)对焦扫描初始化失败
    0x28 (40)功能不可用/状态错误
    0x29 (41)功能不支持
    0x32 (50)通用“不支持此操作”
    0x33 (51)参数冲突
    0x34 (52)参数范围错误
    0x35 (53)参数范围错误
    0x37 (55)模式不匹配
    0x45 (69)初始化失败
    -18校验和不匹配
    -27未收到 ACK (0x06)
    -30接收超时
    -35相机状态错误

    所有错误均通过全局变量 dword_10015220 返回。


    第九章 数值编码与资源字符串

    相机参数值到显示文本的映射完全来自 DLL/EXE 资源,无需第三方数据。例如:

    • 快门速度:EXE 资源 17408 开始 "30"",连续列出 Bulb、30″、25″、… 1/8000。
    • 光圈:资源 17664 起 F1、F1.1、… F90。
    • ISO:资源 19968 起 ISO 6、ISO 8、… ISO 6400。
    • 曝光补偿:资源 19712 起 0.0、-0.2、… +20.0。
    • 测光方式:资源 22190 起 “多分区测光”、”中央重点测光”、”点测光”。
    • 曝光模式:资源 17920 起 “P:多程序自动”、”S:快门优先” 等。

    所有字符串均已在 EXE 和 DLL 的资源提取中完整列出,并翻译为中文。


    第十章 与第三方文档的主要差异说明(主要指f90x-serial-documentation/f90x-serial-documentation.md at trunk · antarktikali/f90x-serial-documentation · GitHubgIcon source 项目等)

    • 地址字节:第三方固定使用 0x20,我们根据 DLL 逻辑动态使用 0x10 或 0x20
    • 扩展命令格式:第三方可能省略 0x90,我们严格采用 DLL 的 1B 90 cmd 格式。
    • 环形缓冲区指针:第三方直接操作 FD44/FD46,DLL 通过动态地址计算和 FED6/FECE 等寄存器间接实现,功能等效。
    • 部分只读状态寄存器(如 FE20、FE22)在 DLL 中未出现立即数,但其信息已通过功能 ID 提供,不影响使用。
    • 所有命令码和寄存器映射完全基于 DLL 的 sub_1000DB73sub_1000DED3,比第三方仅列举的片段更为完整和准确。

    附录 A 数据块格式(胶卷记录)

    卷头:2 字节 0x5A58(“ZX”)
         2 字节 元数据
         2 字节 0x0000
         2 字节 卷号(BCD)
    帧数据:N × (3/4/6 字节) 取决于存储模式
         1 字节 0xFF(结束标记)
         1 字节 ISO 索引

    每帧数据字段(6 字节最大模式):快门速度、光圈、曝光模式、测光模式、闪光同步、焦距、曝光补偿、闪光补偿。具体编码与命令寄存器返回的原始值一致,可参照资源字符串表解析。

    补充:

    • 在 sub_10007042 中,读取胶卷数据时使用的动态地址 v28 = 2 * v19,并且有如下回绕处理:
    v15 = 总字节数 + v28;
    if ( v15 >= 0x200u )
        v15 -= 444;   // 回绕修正,444 = 0x1BC
    • 这说明环形缓冲区的大小为 512 (0x200) 字节,当读取指针超过 0x1FF 时,会回绕到 0x200 - 444 = 0x44(实际基址由相机固定)。DLL 内部自动处理了回绕,开发者只需按此逻辑实现即可。
  • 【摄影】穷鬼如何进入毒德大学?

    Leica FOTOS,徕卡公司推出的一款徕卡相机管理软件,其内置了多款LUT供高贵的SL3及Q3使用,让我等穷鬼十分眼馋。那么穷鬼如何迈入毒德大学呢?

    方法一:HALD LUT,受限于HALD 色块图的分辨率,提取的LUT总让我等穷鬼觉得不够德味。

    方法二:反编译Leica FOTOS,我们将会得到纯正的徕卡LUT,但是万恶的资本主义徕卡,在两年前就发现了这个BUG,为了阻止穷鬼不购买SL3就获得富哥们LUT,将软件中的LUT加密,文件也变为了.CUBE.enc后缀。

    但是穷鬼们没有徕卡,有电脑哇↓

    1、分析加密结构
    将 Leica FOTOS 的 APK 解包,寻找资源文件夹,assets\looks\cube中可看到一批 .CUBE.enc 文件。直接查看其编码,开头 Base64 字符串 +] 分隔符 +密文,反编译寻找lut相关的类,基本确认采用AES加密,开头字符串为初始化向量。

    2、寻找密钥
    不管徕卡的程序员是不是外包的,让人遗憾的是他们都没有把Key明文存储。穷鬼们还是要继续努力,进一步发现Key通过JNI 放在底层 libnative-lib.so 动态库中,并通过一个名为 NativeKeyProvider 的类进行调用。

    3、拦截getkey
    安卓人一般用安卓机,碰巧安卓机还是ROOT的,因此穷鬼们可以使用 Frida 对程序进行动态 Hook,直接拦截 getKey() 方法。
    成功 Hook 后,程序会返回实际使用的密钥:
    bGVp■■■■■■■■■■■■■HRfa2V5
    Base64 解码后内容为:
    leica_camera_encrypt_key(好直白)

    4、解密 LUT 文件
    拿到 Key + IV 后,可使用 Python 的 cryptography 库,实现 AES-GCM 解密即可,将 .CUBE.enc 批量还原为标准 .cube 文件。

    最终,我们迈进了毒德大学。

    就是你这个徕卡LUT怎么还是个17x17x17的,嗯,感觉又不够德味了。

  • 【摄影番外】M43难兄难弟(1)——奥之心官方维修体验

    前言:

    最近坏了一颗12-40 F2.8二代港版,使用过程中发送碰撞,镜头卡口与镜身固定螺柱发生断裂,只能送修换部件。考虑到奥之心在国内可怜的占有率,因此选择官方售后进行维修,体验了一次奥之心在国内的送修服务,整体流程倒是非常顺畅,在此做简单分享。

    奥之心官方售后网络:

    奥之心官网维修网络

    奥之心如今的官方维修网络分为三个层级:客户服务中心微型单电维修中心以及微型单电受理中心。其中客户服务中心为官方设立,微型单电维修中心为授权,应该可以简单理解为有资质的第三方,受理中心则会将受理的设备转送到客户服务中心或者维修中心进行修复。

    本次维修选择的是上海客户服务中心,由于其只提供寄修服务,因此在寄送之前需要做好沟通工作。

    维修过程:

    1、咨询客户服务中心,所需维修的镜头是否有备件等条件:

    直接拨打021-61767030转分机8016,咨询工程师。

    2、寄送需要准备的说明文字:

    寄送时候,工程师需要你在其中放一个纸条,写上一些基本信息,主要是联系方式及所要修理的问题,例如——

    联系方式:

    姓名:XX

    电话:XXXXX

    回寄地址:XXXXXXX

    存在的问题:(以下是我当时写的问题)

    1. 拍摄时候发现成像一侧不清晰;
    2. 初步检查发现底部卡口与镜身连接有旷量;
    3. 自行拆解后进一步检查发现两个螺丝柱断裂;
    4. 自行拆解后,螺丝垫片掉落,因此目前顺序与数量不确定是否出错;
    5. 安装回时扭矩过大,镜头卡口两颗螺丝有滑丝情况。

    3、等待售后工程师检测与说明情况:

    寄送到上海客户服务中心,邮费需要自理。

    然后中间可能需要等待3-5个工作日的时间,等待工程师联系你,我当时是等了大概5天,期间可以打上面的021开头电话询问工程师进度,不过一般工程师检测后都会联系你。

    4、确认检测及报价并付款:

    工程师检测后告知镜头存在的问题及需要的维修方式,更换的部件等,然后会给出大致价格。

    而后,奥之心会给你发送一个短信,告知具体的维修价格并附上汇款账户,而后付款。

    5、维修并寄回:

    本次维修时间较快,付款维修后当天傍晚就顺丰发回,注意同样是到付,奥之心不会承担邮费。

    本次维修中寄回的内容包括修复完成的镜头、损坏的部件、奥之心维修清单,其中清单记载物料费用439,人工费用500。

    6、发票问题:

    如果要开票,可以联系工程师,对方会短信发送邮箱,你可以把开票信息发送到邮箱,等待月底后拿到发票。

    总结:

    奥之心虽然作为Other中的Other,但是维修网络依旧在正常运转,而且体验尚可,维修手艺也是不错,作为M43难兄难弟之一,在中国市场的蜜汁定价下还有可靠的售后也属实不易,奥之心真的可以重新考虑以下自己在中国市场的定位和定价,或许还能在Other里绽放一些光芒。

  • 【器材】松下欣然发布M43“新机”——Lumix G97

    原始链接:LUMIX G97: New Compact Hybrid Micro Four Thirds Camera

    The new LUMIX G97 camera balances high performance and simplicity, giving creators the tools to elevate their photography and video skills.

    Superb picture quality

    The 20.3MP CMOS sensor, (估计还是老汤底)combined with the high-performance Image processor, delivers superb image quality with vibrant colors and sharp details. 

    Equipped with LUMIX Photo Style feature, (不是Lut功能)users can fine-tune their images with a variety of color effect options, ensuring every shot matches your creative vision.

    The Live View Composite feature is also included, providing the ability to combines multiple exposures in real time to create stunning light trails, star trails, or illuminated scenes without overexposing the background.

    Smooth performance

    The LUMIX G97’s 5-stop 5-axis Dual I.S.2*1 (防抖没有升级)system ensures unrivalled stability, so your photos and videos remain sharp even in challenging conditions. 

    With 4K PHOTO capabilities, you can capture bursts of high-resolution photos at 30fps, ensuring you never miss a fleeting moment. The perfect shot from burst footage makes this feature ideal for fast-moving subjects or spontaneous scenes.

    Versatile video features

    Record in crisp 4K at 30p with no time limitations*2,(不限时录制好评,但你还是DFD呀) experiment with slow-motion (max.4x) or quick-motion (max.8x) in FHD, and create cinematic content with 12-stops of V-Log L.(有Vlog,但是L) Dedicated headphone and microphone jacks ensure total audio control while recording.

    Intuitive operation and reliable design

    With a 1,840k-dot free-angle LCD and 2,360k-dot OLED Live View Finder*3,(换屏幕了,好评!) the LUMIX G97 makes it easy to frame and focus your shots accurately, even in bright conditions.

    The durable dust/splash-resistant*4 construction is ideal for everyday creators looking for a camera that can reliably handle a variety of situations.

    Built-in Bluetooth® v5.0 and Wi-Fi make sharing and remote control effortless, while USB Type-C charging adds convenience.(先进的蓝牙5.0与Type-c确保这是21世纪20年代的产品)

    Price and availability

    The new LUMIX G97 will be available in late February 2024 for $849.99 for a 12-60mm lens kit (DC-G97MK) at valued channel partners.

    *1 Based on the CIPA standard [Yaw/Pitch direction: focusing distance f=140mm (35mm camera equivalent f=280mm), when H-FSA14140 is used.]

    *2 When the ambient temperature is high, the camera may stop the recording. Wait until the camera cools down.

    *3 35mm camera equivalent

    *4 Dust and Splash Resistant does not guarantee that damage will not occur if this camera is subjected to direct contact with dust and water

    翻译一下:

    松下欣然发布了一款名为G97的“新机”,那么这款与G95长得一毛一样的新机,对比G95有哪些“重大”改进呢?

    1、换装了Type-C!好耶,避免了在欧洲市场卖不下的尴尬。同时Type-C接口支持充电,增强了充电的便利性,这个倒是好评,跟上了时代的步伐。

    2、换装了蓝牙5.0!好耶,新的总比旧的好,对吧?!

    3、拥有了预装的Vlog-L!好耶,Vlog-L也是Vlog!

    4、不限时的视频录制!好耶!但是会过热吗?

    5、换装了184万像素显示屏,应该是与S5M2、G9M2等同款的3英寸屏幕。这个喷不了,屏幕是好屏幕!

    综上,G97基本上就是G95(D)的小修小补强化版本,核心画质目测没有本质区别,发售日期国外定于2025年2月,价格定为1260(非徕卡)套机$849.99,对比一下现在的G95-1260套机美国官网促销售价$699.99,因此G97应该会作为G95的替代产品,保持相同的定位与价格。

    (更新:国行单机身售价4998,和国行目前降了N次价的G9同价,当然松下指导价只能指导着玩,目测还是要跳水)

    所以,松下你这块M43画幅——先进DFD技术——祖传2030万像素传感器到底还要用多久。

  • 【全景图】大疆球形全景合成2:1全景图(等距柱状投影)

    前言:

    全景平台普遍要求提供2:1的等距柱状投影全景图,而大家使用一些老款无人机拍摄的全景图可能没有机内合成功能,比如mini2之类的,因此我们需要一些手段将其进行转换与合成。(目前一些新款无人机都可以直接机内合成了,不过也可以用这个方法,把一些合成有问题的重新做一下编辑。)

    网络上的教程普遍基于PTGui,该软件好用易用且可以很方便的补天。不过该软件是付费软件,虽然可以进行破解,但有没有开源的软件可以进行制作?

    当然有,我们可以使用开源的全景合成软件—— Hugin 对全景图进行合成。

    用到的软件:Hugin(合成)、Photoshop(补天)。

    点击快速跳转步骤:

    步骤:

    1、导入大疆全景图片

    Hugin有三个用户界面,后两个涉及一些复杂操作,我推荐使用第一个简单界面来导入与修正图片。

    直接点击加载图片,选择大疆全景图文件夹,将全景图片全选导入。

    你会看到载入的图片犬牙交错,导入成功后我们可以进入下一步骤。

    2、分析图片,创建2:1全景图,调整轴心

    点击排列,软件会启动脚本,分析各个图片的关联,创建相应的控制点。

    该过程需要耗费一定的时间。

    等待脚本运行完毕,我们可以在预览界面看到排列完成的图片。

    其中的天空有一大片空缺,这里先不用管,因为无人机拍摄时候确实没法向上拍摄,这里的补天我们可以通过PS中的一个简单步骤实现。

    另外,这里需要注意的是我们需要提前指定全景图片的三轴,可以在移动工具中,拖动左侧的全景球来指定相应轴心位置。

    3、导出全景图

    接下来我们导出2:1的全景图。如果直接点击创建全景图像,你会发现分辨率是不对的,因此我们切换界面,使用高级功能导出。

    点击高级界面。

    可以看到全景图的控制点等已经创建完毕,其他的不用动,我们点击该界面的缝合器。

    投影确认是等矩形。然后我们点击计算优化尺寸。

    这时尺寸会重新计算,得到我们需要的足够清晰的输出尺寸。

    点击缝合按钮,脚本开始运行,这时软件会创建一个PTO项目文件,以及多个TIF文件,最终会输出一张合并完成的TIF全景图片文件。

    而后我们对缺失的天空部分进行填充。

    4、补天

    将图片导入Photoshop,使用选区工具框选缺失部分及周围部分天空。

    使用填充工具,对空缺部分进行填充。

    在此过程中我们还可以对图片进行一些色彩调整,完成后导出,我们就得到了一个很棒的2:1全景图,而后可以将其导入到其他全景平台进行内容制作。

    5、效果展示

    此处若出现跨域问题,则需要修改Nginx的配置文件,对类型图片进行请求放行。

    这个全景效果使用了pannellum.js,可以很方便置入网站来进行展示。

    <script src="https://cdn.jsdelivr.net/npm/pannellum/build/pannellum.js"></script>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pannellum/build/pannellum.css">
        <style>
            #panorama {
                width: 100%;
                height: 500px;
            }
        </style>
    </head>
    <body>
        <div id="panorama"></div>
        <script>
            pannellum.viewer('panorama', {
                "type": "equirectangular",
                "panorama": "xxx.jpg", // 图片地址
                "autoLoad": true,
                "compass": true // 显示指南针
            });
        </script>
    </body>

  • 【摄影特辑】青岛2023

    【摄影特辑】青岛2023

    镜头:Panasonic 30mm Macro

    相机:Panasonic G9M2

    后期:Snapseed

    很荣幸,第二张图片还被松下影像翻了牌子

  • 【摄影特辑】粉黛乱子草

    【摄影特辑】粉黛乱子草

    What is Muhlenbergia capillaris?

    粉黛乱子草(Muhlenbergia capillaris)是禾本目、禾本科、乱子草属植物。多年生暖季型草本,株高可达30-90厘米,宽可达60-90厘米。顶端呈拱形,绿色叶片纤细。顶生云雾状粉色花絮,花期9-11月,成片种植可呈现出粉色云雾海洋的壮观景色,景观可由9月份一直持续至11月中旬,观赏效果极佳。

  • 【摄影特辑】自然小精灵

    【摄影特辑】自然小精灵

    自然小精灵们

    同为生命,为生存而拼搏,同一颗星球,一起漂流在浩瀚星河。

  • 【摄影】如何矫正Olympus 9mm F8 Fisheye

    前言

    奥林巴斯9mm F8鱼眼镜头是一个很有趣的玩具镜头,其小巧的体积,搭配不错的中心画质,成为M43相机中的最强镜头盖。

    鱼眼效果虽然有趣,在一些情况下我们还是想要一个标准的广角画面,因此对画面进行矫正就成为“一鱼两吃”的好选择。

    但是由于这枚镜头没有触点,因此我们在松下机身拍摄之后,只能通过电脑对照片进行后处理。问题来了,LR中没有该镜头的对应配置文件,该如何矫正图片?

    这里结合互联网信息,提供三种解决方式:

    1、LR中采用适马镜头配置文件。

    2、使用开源软件——Hugin。

    3、采用一个日本摄影师提供的Python脚本。

    以上方案都可以高效解决矫正问题,得到不错的成片。

    方法一:LR中采用适马镜头配置文件

    采用适马10mm F2.8 EX DC FISHEYE的矫正文件,可以取得不错的矫正效果。

    要注意此操作需要在LR中进行,Ps的Camera Raw插件里的镜头矫正文件不全,效果会有问题。

    方法二:开源软件Hugin

    步骤:

    (1)安装Hugin,打开需要矫正的图片。

    (2)选择全帧鱼眼,9mm,2x倍率。

    (3)在全景缝合中使用直线预测,先进界面下对输出内容进行优化,导出即可。

    方法三:Python脚本自动矫正

    该方法来自一个日本摄影师,我在python3.13下进行了一些调整,目前该代码创建一个GUI,可以实现拖动图片到GUI自动运行。

    存在的问题:边角畸变矫正还是存在一些问题,可以对一些数值进行调整,实现更好的畸变矫正结果。

    import numpy as np
    import cv2
    from PIL import Image
    import piexif
    import tkinter as tk
    from tkinterdnd2 import DND_FILES, TkinterDnD
    
    def process_image(file_path):
        # 定数
        scale = 0.95
        fsave = file_path.replace(".JPG", "_1.JPG").replace(".jpg", "_1.jpg")
        
        # 画像を開く
        image = Image.open(file_path)
        img = np.array(image, dtype=np.uint8)
        h, w = img.shape[:2]
        
        # 収差補正(Greenを拡大)
        green = cv2.resize(img[:,:,1], None, fx=1.0005, fy=1.0005, interpolation=cv2.INTER_CUBIC)
        difx = (green.shape[1] - w) // 2
        dify = (green.shape[0] - h) // 2
        img[:,:,1] = green[dify:dify+h, difx:difx+w]
        
        # 周辺減光補正
        size = max([h, w])  # 幅、高の大きい方を確保
        x = np.linspace(-w/size, w/size, w)
        y = np.linspace(-h/size, h/size, h)  # 長い方の辺が1になるように正規化
        xx, yy = np.meshgrid(x, y)
        r = np.sqrt(xx**2 + yy**2) 
        gain = 0.4 * r + 1   # 減光補正パラメータ(固定値)
        gainmap = np.dstack([gain, gain, gain])    # RGB同じゲイン
        img = np.clip(img * gainmap, 0., 255).astype(np.uint8)
        
        # 歪み補正
        f = max([h, w])
        mtx = np.array([[f,    0.,  w / 2],
                        [0.,   f,   h / 2],
                        [0.,   0.,  1    ]])
        
        # 歪み補正パラメータ(固定値) 
        dist = np.array([-0.63, -0.2, 0, 0, 0.8])
        
        n_mtx = cv2.getOptimalNewCameraMatrix(mtx, dist, (img.shape[1], img.shape[0]), 1)[0]
        map = cv2.initUndistortRectifyMap(mtx, dist, np.eye(3), n_mtx, (img.shape[1], img.shape[0]), cv2.CV_32FC1)
        
        # 拡大+shift
        mapx = map[0] * scale + (1 - scale) * w / 2
        mapy = map[1] * scale + (1 - scale) * h / 2
        img = cv2.remap(img, mapx, mapy, cv2.INTER_CUBIC)
        
        # 4:3 -> 3:2比率への変換 (高さを 8/9する)
        strt_y = h * 1 // 18
        end_y = h * 17 // 18
        img = img[strt_y:end_y, :, :]
        
        # Exif付与
        exif_dict = piexif.load(file_path)
        exif_dict["0th"][piexif.ImageIFD.ImageWidth]  = img.shape[1]
        exif_dict["0th"][piexif.ImageIFD.ImageLength] = img.shape[0]
        
        exif_dict["Exif"][piexif.ExifIFD.FocalLength] = (90, 10)
        exif_dict["Exif"][piexif.ExifIFD.FNumber] = (80, 10)
        exif_dict["Exif"][piexif.ExifIFD.FocalLengthIn35mmFilm] = 18
        exif_dict["Exif"][piexif.ExifIFD.PixelXDimension] = img.shape[1]
        exif_dict["Exif"][piexif.ExifIFD.PixelYDimension] = img.shape[0]
        
        exif_bytes = piexif.dump(exif_dict)
        
        # 保存
        im = Image.fromarray(img)
        im.save(fsave, "JPEG", exif=exif_bytes)
    
        # 显示保存路径和 EXIF 信息
        print(f"图像处理完成,文件已保存到: {fsave}")
    
        # 提取并显示特定 EXIF 信息
        exif_info = exif_dict["Exif"]
        aperture = exif_info.get(piexif.ExifIFD.FNumber, "未知")
        shutter_speed = exif_info.get(piexif.ExifIFD.ExposureTime, "未知")
        iso = exif_info.get(piexif.ExifIFD.ISOSpeedRatings, "未知")
    
        print("\n照片的主要 EXIF 信息:")
        print(f"光圈 (Aperture): {aperture}")
        print(f"快门速度 (Shutter Speed): {shutter_speed}")
        print(f"感光度 (ISO): {iso}")
    
    def drop(event):
        file_paths = event.data.strip('{}').split()  # 处理多个文件
        for file_path in file_paths:
            process_image(file_path)
    
    # 创建主窗口
    root = TkinterDnD.Tk()
    root.title("图片处理")
    root.geometry("400x200")
    
    # 提示信息
    label = tk.Label(root, text="将图片拖放到这里进行处理", padx=10, pady=10)
    label.pack(expand=True, fill=tk.BOTH)
    
    # 注册拖放事件
    root.drop_target_register(DND_FILES)
    root.dnd_bind('<<Drop>>', drop)
    
    # 启动 Tkinter 主循环
    root.mainloop()

    【施工中、、、】