编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

    • 库的解读

    • 专栏博客

    • 智能硬件

      • README
      • 实体卡开发和实践
        • 02.读取卡号规范
          • 2.1 读取卡号UID
          • 2.2 大端和小端
          • 2.3 转化十六进制
          • 2.4 实际场景
        • 02.M1卡开发和实践
          • 2.1 什么是M1卡
          • 2.2 M1卡数据结构
          • 2.3 扇区密码结构
          • 2.4 扇区数据块结构
          • 1.5 M1卡片操作步骤
          • 1.6 M1卡运作机理
          • 02.常见思路和做法
          • 2.1 M1开发实践思路
          • 其他参考博客
        • 01.CPU实体卡概念
        • 02.CPU卡操作流程
          • 2.1 如何使用CPU卡
          • 2.1 读卡器交互流程
        • 03.CPU卡读卡流程
          • 3.1 读CPU卡流程
          • 3.2 写CPU卡流程
        • 04.APDU指令数据
          • 4.1 APDU基础概念
          • 4.2 APDU指令类型
          • 4.3 APDU指令结构
          • 4.4 APDU通信过程
          • 4.4.1 发送指令详细剖析
          • 4.4.2 响应指令详细解析
          • 4.5 APDU错误处理
          • 1.2 读卡器与卡交互指令
          • 参考博客
          • 2.6 CPU发卡流程
      • 继电器和韦根协议
      • 设备串口通信实践
      • USB开发设计实践
      • UVC相机设计实践
      • 原生相机采集图片库
      • 蓝牙开发业务实践
  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

  • Apps
  • Android提升进阶
  • 智能硬件
杨充
2025-07-24
目录

实体卡开发和实践

# M1实体卡读写库

# 目录介绍

  • 01.理解实体卡概念
  • 02.读取卡号规范
    • 2.1 读取卡号UID
    • 2.2 大端和小端
    • 2.3 转化十六进制
    • 2.4 实际场景
  • 02.M1卡开发和实践
    • 2.1 什么是M1卡
    • 2.2 M1卡数据结构
    • 2.3 扇区密码结构
    • 2.4 扇区数据块结构
  • 03.CPU卡开发和实践

# 02.读取卡号规范

# 2.1 读取卡号UID

# 2.2 大端和小端

大端序(Big-Endian):

  • 高位字节存储在低地址,低位字节存储在高地址。
  • 例如,十六进制值 C1 84 FA 1E 在大端序中直接表示为 C1 84 FA 1E。

小端序(Little-Endian):

  • 低位字节存储在低地址,高位字节存储在高地址。
  • 例如,十六进制值 C1 84 FA 1E 在小端序中表示为 1E FA 84 C1。

如何判断数据是大端序还是小端序

  • 卡片规范:Mifare 卡和 CPU 卡的规范中通常会明确数据的存储顺序。例如,Mifare Classic 卡的 UID 通常是大端序。
  • 协议文档: 阅读卡片的技术文档或协议规范,了解数据的存储格式。
  • 实际测试:通过一些NFC刷卡工具,通过读取数据并分析其含义,判断是大端序还是小端序。

# 2.3 转化十六进制

# 2.4 实际场景

示例:读取 Mifare 卡数据并转换。Mifare 卡的 UID 号是一个 32 位的十六进制卡号。这个是刷卡读卡,uid是是卡片生产商给卡标记的一个物理卡号。

假设从 Mifare 卡读取到一个数据块,内容为 C1:84:FA:1E,需要根据存储顺序进行解析:

  • 如果数据是大端序,则直接解析为 C1:84:FA:1E。转化为十六进制是:C184FA1E
  • 如果数据是小端序,则解析为 1E:FA:84:C1。转化为十六进制是:1EFA84C1

# 02.M1卡开发和实践

# 2.1 什么是M1卡

  • M1卡是什么东西
    • M1芯片,是指菲利浦下属子公司恩智浦出品的芯片缩写,全称为NXP Mifare1系列,常用的有S50及S70两种型号。
    • S70与S50主要不同在于卡片的扇区分布和容量不同,M1 S50容量为8KBit,而M1 S70的容量达到了32KBit。
  • M1卡卡片介绍
    • 以M1 S50卡为案例,M1 S50(以下简称M1)卡片,卡的序列号是全球唯一的,不可修改的32bit数据,M1卡片容量为8KBit。
    • 分为:16个扇区,每个扇区4块,每块可存放16个字节byte数据(每个扇区的第4块存放密钥和权限控制字节),可以通过读卡器向卡中不同区域写入相关信息,各扇区独立使用并访问密钥独立,互不干扰。
    • 在读取M1卡片数据时,M1卡片只能够按块进行读取,读取扇区数据时,需要循环四次读取该扇区各块数据信息。
  • M1卡片访问权限
    • M1卡片每个扇区有两个访问密钥:KeyA和KeyB,可以通过权限控制位设定对数据访问及密钥修改时所需要的密钥。
    • 具体看:https://blog.csdn.net/ftswsfb/article/details/100183478

# 2.2 M1卡数据结构

  • 扇区和块的含义
    • Mifare S50把1K字节的容量分为16个扇区(Sector0-Sector15),每个扇区包括4个数据块(Block0-Block3,我们也将16个扇区的64个块按绝对地址编号为0~63)
    • 每个数据块包含16个字节(Byte0-Byte15),4(个数据块)*16(块字节)) * 16(个扇区)= 1024
  • M1卡数据存储结构
    • image
  • M1卡数据存储结构说明
    • M1卡分为16个扇区,每个扇区4块(块0~3),共64块,按块号编址为0~63。
    • 第 0 扇区的块 0(即绝对地址 0 块),它用于存放厂商代码,已经固化,不可更改。注意:第0块不能进行读写操作!
    • 每个扇区的块 0、块 1、块 2 为数据块,可用于存贮数据。数据块可作两种应用:第一是用作一般的数据保存,可以进行读、写操作;第二是用作数据值,可以进行初始化值、加值、减值、读值操作。
    • 每个扇区的块 3 为控制块,包括了密码 A (6个字节)、存取控制(4个字节)、密码 B(6个字节)。
    • 每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制。

# 2.3 扇区密码结构

  • 每个扇区的块3为控制块,包括了密码A、存取控制、密码B。具体结构如下:
    • 密码A(6字节) 存取控制(4字节) 密码B(6字节)。在我们实际开发中用的是密码A
    • image
  • 每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制。
    • 存取控制为4个字节,共32位,扇区中的每个块(包括数据块和控制块)的存取条件是由密码和存取控制共同决定的,在存取控制中每个块都有相应的三个控制位
    • image
    • 例如C1x0中的C代码这是控制位,1代表第一位,0代表是块0的控制位。
  • M1卡密码机制。是M1卡最为复杂的地方,在存取控制中每个块都有相应的三个控制位,它们的定义如下
    块0:   C10   C20   C30
    块1:   C11   C21   C31
    块2:   C12   C22   C32
    块3:   C13   C23   C33
    
    1
    2
    3
    4
  • 一个扇区的三个数据块,我们可以利用密码机制对它们分别进行权限控制。数据块(块0、块1、块2)的存取控制如下:
    • image
    • 例如:当块0的存取控制位C10 C20 C30=100时,验证密码A或密码B正确后可读;验证密码B正确后可写;不能进行加值、减值操作。

# 2.4 扇区数据块结构

  • 数据块(块0、块1、块2)的存取控制如下
    • image
    • KeyA|B 表示密码A或密码B,Never表示任何条件下不能实现
    • 例如:当块0的存取控制位C10 C20 C30=1 0 0时,验证密码A或密码B正确后可读;验证密码B正确后可写;不能进行加值、减值操作。
  • 控制块(块3)的存取控制与数据块(块0、1、2)不同,它的存取控制如下
    • image
    • 例如:当块3的存取控制位C13 C23 C33=1 0 0时,表示:
    • 密码A:不可读,验证KEYB正确后可写(更改)。存取控制:不可写,验证KEYA或KEYB正确后可读。密码B:不可读,验证KEYB正确后可写。

# 1.5 M1卡片操作步骤

  • 命令由读写器发出,根据相应区读写条件受数字控制单元的控制。
    • image
  • 第一步:复位应答(发送,有的也叫寻卡)
    • 卡上电复位后,通过发送request应答码(ATQA 符合ISO/IEC 14443A),能够回应读写器向天线范围内所有卡发出的request 命令。
    • REQA:PCD发出,用于寻找用于类型A PICC的工作场。命令:26H。寻找到卡片,会收到两个字节的应答数据(ATQA)。
    • WAKE_UP:PCD发出,使已进入HALT状态的PICC回到READY状态,再次参与防冲突和选择规程。命令:52H
  • 第二步:防冲突(SEL1 = 93H,SEL2 = 95H,SEL3 = 97H)
    • 在防冲突循环中,读回一张卡的序列号。如果在读写器的工作范围内有几张卡,它们可以通过唯一序列号区分开来,并可选定以进行下一步交易。未被选定的卡转入待命状态,等候新的request命令。
    • 命令组成:选择代码SEL(1byte) + 有效位数目NVB(1byte)+ 根据NVB的值,UID Cln的0到40个数据位。只要NVB没有规定40个有效位,若PICC保持在READY状态中,该命令就被称为ANTICOLLISON命令。
    • 如果PICC已发送了完整的UID,则它从READY状态转换到ACTIVE状态并在其SAK-响应中指出UID完整。否则,PICC保持在READY状态并且该PCD应该递归串联级别启动一个新的防冲突环。
  • 第三步:选择卡片(SEL1 = 93H,SEL2 = 95H,SEL3 = 97H)
    • 读写器通过select card命令选定一张卡以进行认证和存储器相关操作。该卡返回选定应答码(ATS= 08h),明确所选卡的卡型。
    • 命令组成:选择代码SEL(1byte)+ 有效数目NVB(1byte)+ UID CLn 40个数据位(NVB = 70H)+ CRC_A
  • 第四步:三轮认证
    • 选卡后,读写器指定后续读写的存储器位置,并用相应密钥进行三轮认证。认证成功后,所有的存储器操作都是加密的。
  • 第五步:业务操作(存储器操作)
    • 密钥修改;访问权限修改;读写块数据
  • 第六步:停止
    • PCD发出HALT命令,使PICC进入HALT状态。
  • 附:防冲突技术
    • 正常情况下,读卡器在某一时刻只能对磁场中的一张射频卡进行读写操作,但是当多张卡片同时进入读卡器的射频场时,读卡器需要选出唯一一张卡片进行读写操作,这就是防冲突。
    • 常见的非接触式卡片中放冲突机制主要有以下三种:面向比特的放冲突机制(ISO 14443a使用)、面向时隙的防冲突机制(ISO 14443b使用)、位与时隙相结合的放冲突机制(ISO 14693使用)。

# 1.6 M1卡运作机理

  • M1卡运作的机理流程如下:
    • 连接读写器 → 寻卡 → 识别卡(获取卡序列号) → 从多卡中选一张卡 → 向卡中缓冲区装载密码 → 验证密码 → 进行读写 → 关闭连接
  • 如果概括来说的话,主要也就五部分:
    • 开关连接、寻卡、验证密码、读取、关闭(归位)。
  • 基本操作规则。如下所示:
    • (1)程序开始,调用rf_init函数初始化串口。
    • (2)寻卡,调用rf_card;
    • (3)对单个扇区操作,顺次执行A~D;A、选定要操作的扇区;B、装载密码,调用rf_load_key;C、验证密码,调用rf_authentication;D、进行操作,包括读写及值操作。E、调用rf_halt。
    • (4)关闭串口,调用rf_exit。程序正常退出或因错误退出之前,要使用函数;否则再次执行初始化串口时将出错。

# 02.常见思路和做法

# 2.1 M1开发实践思路

  • M1开发实践思路步骤如下
    • 第一步:获取卡类型。程序开始,开始初始化串口。然后开始寻卡操作。
    • 第二步:获取卡号。不可更改的序列号,保证了每张卡的唯一性。
    • 第三步:M1扇区读写。读写卡操作分为两个步骤:密码校验、读写卡。只有对当前要读写的块数据所在的扇区进行密码校验之后才能进行接下来的读写操作。
    • 第四步:M1重置操作

# 其他参考博客

  • NFC M1卡读写
    • https://www.jianshu.com/p/4f0c9012bd17
  • M1卡介绍
    • https://blog.csdn.net/lpb914/article/details/123655450
  • Mifare 1卡(M1卡、IC卡)读写操作及工作原理整理
    • https://blog.csdn.net/qq_32348883/article/details/126603790

# 01.CPU实体卡概念

什么是CPU实体卡,有哪些技术规范?

# 02.CPU卡操作流程

# 2.1 如何使用CPU卡

如何去操作CPU卡,手机去感应非接触式CPU卡一般都是通过NFC,想用手机去感应身份证有两个前提:一你手机支持NFC功能,二你得知道对操作身份证的指令。

比如读取身份的指令,知道指令后你就可以开发个手机app然后进行感应读取身份。

# 2.1 读卡器交互流程

读卡器APDU流程,接触过的读卡器基本都下面这些步骤,基本相同,不同的是命令发送和数据解析

  • ICC(接触读取):1.打开阅读器;2.对卡片上电;3.获取ATR(接触卡状态检测,是否存在卡片)和协议;4.APDU通讯;5.下电;6.关闭阅读器
  • PICC(非接触读取):1.打开阅读器;2.寻找卡片;3.APDU通讯;4.关闭阅读器

# 03.CPU卡读卡流程

# 3.1 读CPU卡流程

第一步:选择目录(目录ID为十六进制1680)

发送 00 A4 00 00 02 16 80,返回:目录信息+90 00表示成功

第二步:选择文件(目录ID为十进制00 01,如果写的是1号文件)

发送 00 A4 00 00 02 00 01,返回:90 00表示成功

第三步:取随机数

发送 00 84 00 00 04,返回:4字节随机码+90 00表示成功

第四步:认证文件密码(用该文件的只读密码,文件1的只读密码用的是第((文件号1-1)*2=0)号密码,下面指令的第3个字节为0)

发送 00 82 00 00 08 与随机码换算后的密码信息,返回:90 00表示成功

第五步:读数据(读出从00 00 字节开始的2个字节数据)

发送 00 B0 00 00 02

# 3.2 写CPU卡流程

第一步:选择目录(目录ID为十六进制1680)

发送 00 A4 00 00 02 16 80,返回:目录信息+90 00表示成功

第二步:选择文件(目录ID为十进制00 01,如果写的是1号文件)

发送 00 A4 00 00 02 00 01,返回:90 00表示成功

第三步:取随机数

发送 00 84 00 00 04,返回:4字节随机码+90 00表示成功

第四步:认证文件密码(用该文件的读写密码,文件1的读写密码用的是第((文件号1-1)*2+1=1)号密码,下面指令的第3个字节为0)

发送 00 82 00 01 08 与随机码换算后的密码信息,返回:90 00表示成功

第五步:写数据(写入从00 00 字节开始的2个字节数据AA BB)

发送 00 D6 00 00 02 AA BB,返回:90 00表示成功

# 04.APDU指令数据

# 4.1 APDU基础概念

APDU是什么东西:ISO 7816-4规范有定义APDU(= ApplicationProtocol data unit, 是智能卡与智能卡读卡器之间传送的信息单元, (给智能卡发送的命令)指令)

APDU(Application Protocol Data Unit)指令数据是在智能卡领域中用于通信的一种数据格式。APDU 指令数据通常用于在读取或写入智能卡时与卡片进行通信。

APDU提供了一种标准的、协议无关的方法,用于智能卡和终端设备进行通信。它是智能卡系统的核心组件,广泛应用于银行卡、身份证、健康卡等各种应用场景。

进行APDU通讯的前提是:

  • 有读卡器(必选)和对应的SDK通讯包(可选)
  • 知道IC卡的APDU命令

# 4.2 APDU指令类型

APDU 指令数据可以分为两种类型:命令指令(Command APDU)和响应指令(Response APDU)。命令指令用于向智能卡发送指令,请求执行某种操作;响应指令则是智能卡对命令指令的响应,包含执行结果或返回的数据。

# 4.3 APDU指令结构

APDU 指令数据通常由两部分组成:指令头(Header)和数据域(Data Field)。指令头包含了指令类型、指令参数和指令长度等信息,而数据域则包含了具体的数据内容。

APDU 指令数据的结构通常遵循 ISO/IEC 7816 标准。命令指令包含 CLA(类别)、INS(指令代码)、P1(参数1)、P2(参数2)、LC(数据长度)、Data 等字段;响应指令包含响应状态字(SW1-SW2)和响应数据等字段。

APDU是智能卡与智能卡读卡器之间传送的信息单元

CLA INS P1 P2 Lc Data Le
指令类别 指令码 参数P1 参数P1 Data的长度(不超过255个字节) 返回数据 表示响应时回答的数据字节数,0表是需要最大可能长度(不超过255个字节)

APDU指令由以下部分组成:

  • CLA(Class):指令类别,用于确定指令所属的指令集,如00表示标准指令集,80表示安全指令集。

  • INS(Instruction):指令代码,用于区分执行的操作,如06表示比较指令,0A表示读二进制文件指令。

  • P1(Parameter 1)和P2(Parameter 2):用于提供有关指令的附加信息,如P1-P2可以指定要读取或写入的数据块的位置、长度、偏移量等。

  • Lc(Length of command data):用于指定指令的数据长度。

  • Data:用于传输指令数据,如读取或写入指定的内存块或文件。

  • APDU响应部分包括两个部分:

  • 响应数据的数据长度(Le):用于指定响应数据的长度,可以在命令中指定也可以由智能卡决定。

  • 响应数据的实际内容:用于传输操作结果。

  • APDU在智能卡和读卡器之间进行通信

  • 包含两个部分:C-APDU(指令)和R-APDU(响应)。C-APDU被发送给智能卡,智能卡处理以后返回一个R-APDU进行响应。

# 4.4 APDU通信过程

# 4.4.1 发送指令详细剖析

发送APDU命令格式分类(报文结构,从终端设备发出的命令和卡片信息必须按照四种格式发送)

  • ① 情形1:CLA INS P1 P2 00

  • ② 情形2:CLA INS P1 P2 Le

  • ③ 情形3:CLA INS P1 P2 Lc Data

  • ④ 情形4:CLA INS P1 P2 Lc Data Le

  • 例子:/send 00A4040006A00000000101

    • 这条指令是一个APDU指令,用于选择与AID(Application Identifier,应用标识符)相对应的应用程序。
  • 具体来说,它的结构如下:

    • CLA(Class):0x00 表示标准指令集
    • INS(Instruction):0xA4 表示选择文件命令
    • P1(Parameter 1):0x04 表示选择应用程序的命令模式
    • P2(Parameter 2):0x00 表示选择应用程序的第一组(或唯一一组)文件,这是应用程序的根文件
    • Lc(Length of command data):0x06 表示接下来有6个字节的数据
    • Data:A00000000101 表示应用标识符(AID),它是一个16进制数字串,用于确定要选择的应用程序
  • 以此为例

    • 发送该APDU指令可以将具有AID为A00000000101的应用程序选择为活动应用程序,并通过通道来与其通信

# 4.4.2 响应指令详细解析

APDU响应报文(响应数据 响应状态码)

  • Data SW1 SW2 如何理解?DATA:返回给用户的数据,即命令的执行结果。SW1、SW2: 返回命令处理的状态。

  • 响应也对应几种情形 ① 情形1:SW1 SW2;② 情形2:Le字节的Data SW1 SW2

  • 例子:第一个响应指令,9FE7398E9000;第二个响应指令,9000

  • 先来看第一个指令:9FE7398E9000

    • 属于Le字节的Data SW1 SW2状态,那么它是什么含义?Le表示响应data,SW1表示命令处理状态;SW2表示命令处理限定。
    • 那么9FE7398E9000拆分,即可Le是9FE7398E,SW1是90,SW2为00,整体意义解读执行成功。
  • 再来看第二个指令:9000

    • 属于SW1 SW2状态,那么它是什么意义?SW1表示命令处理状态;SW2表示命令处理限定。
    • 那么9000拆分,即可SW1是90,SW2为00,整体意义解读是正确执行。

# 4.5 APDU错误处理

# 1.2 读卡器与卡交互指令

  • 1.读卡器与卡交互第一步一般是选择应用。
    • 一张IC卡里面可能有多个应用,所谓应用就是卡片和终端之间的应用协议和相关的数据集,读卡器和卡的交互其实就是和应用的交互,卡的交易其实就是选择某个应用做交易。
    • 目前卡商所生产的卡基本上都是一个应用,即便如此,但根据银联卡的规范,应用选择这个步骤也是必不可少的。
  • 2.第二步根据上面的返回数据做下一步操作,并最终拿到结果
    • 如果是一般卡(非银联卡),响应数据就是想要的数据是直接数据,进行转码就可以达到想要的数据。

# 参考博客

  • IC卡(智能卡)APDU通讯总结
    • https://blog.csdn.net/u011082160/article/details/122637253
  • CUP卡操作指令集
    • https://www.cnblogs.com/gengtongyu/p/15737609.html

# 2.6 CPU发卡流程

  • https://blog.csdn.net/weixin_41485217/article/details/88415850
上次更新: 2026/06/10, 11:13:41
README
继电器和韦根协议

← README 继电器和韦根协议→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式