从《绝地求生》逆向提取虚幻引擎4 SDK:IDA Pro与Cheat Engine实战指南
1. 项目概述从游戏到引擎的逆向之路最近在逆向分析圈里一个经久不衰的话题就是如何从热门游戏中提取出游戏引擎的软件开发工具包。这听起来像是个黑魔法但实际上它是一套结合了静态分析与动态调试的严谨技术流程。今天要聊的就是以《绝地求生》这款使用虚幻引擎4开发的游戏为例手把手带你走一遍从零开始利用IDA Pro和Cheat Engine这两款神器逆向出其底层UE4 SDK的完整过程。这不仅仅是“找基址”那么简单它涉及到对游戏引擎内存布局、运行时对象管理机制以及C逆向工程的深度理解。对于开发者而言无论是想学习顶尖游戏的架构设计还是为游戏模组开发、安全研究或自动化测试铺路掌握这套方法都极具价值。整个过程就像考古我们通过外部的“地表现象”游戏进程使用工具向下挖掘最终还原出埋藏在底层的“建筑蓝图”引擎SDK。你需要对C有一定的了解熟悉指针、虚函数表、RTTI等概念并且有使用调试器的基本经验。不用担心即使你是逆向新手只要跟着步骤走也能窥见其中门道。我会附上关键的思路和代码片段但更重要的是理解每一步“为什么”要这么做。2. 核心思路与工具链解析2.1 为什么选择PUBG和UE4《绝地求生》是一个近乎完美的逆向学习样本。首先它使用虚幻引擎4开发而UE4的源代码虽然部分开源但其与游戏项目结合后的具体实现、内存中的对象布局尤其是经过游戏开发者定制后的GNames、GObjects等全局管理器的实际形态是闭源的。其次PUBG客户端体积庞大结构复杂包含了完整的UE4游戏逻辑逆向它相当于对一个大型的、商业级的UE4项目进行黑盒分析实战意义很强。最后由于其持久的热度社区中有大量相关的讨论和零散信息便于交叉验证但同时也意味着游戏会频繁更新偏移地址会变这就要求我们的方法必须是原理性的而非依赖固定的偏移值。2.2 工具选型IDA Pro与Cheat Engine的黄金组合工欲善其事必先利其器。我们的核心工具链是IDA Pro和Cheat Engine它们分别承担静态和动态分析的重任。IDA Pro静态分析的基石IDA Pro是逆向工程的行业标准用于对游戏主二进制文件进行反汇编和静态分析。我们的主要目标是定位关键符号即使游戏发布了一些关键的引擎函数名称或字符串可能会以调试符号或特定字符串的形式残留。我们可以通过搜索字符串、交叉引用等方式定位如“GObjects”、“GNames”、“StaticFindObject”等关键引擎函数或全局变量的地址。分析函数逻辑理解关键函数如UObject::ProcessEvent、UObject::FindFunction的汇编实现推断其参数和调用约定这对于后续编写SDK生成脚本至关重要。识别RTTI信息C的运行时类型信息是还原类继承关系的关键。IDA可以帮助我们分析.rdata段找到类型描述符进而映射出类的层次结构。Cheat Engine动态调试的利器Cheat Engine远不止是一个“修改器”。它的内存查看、调试器、指针扫描和结构体分析功能在动态分析中无可替代实时内存观察直接查看游戏运行时GObjects和GNames数组在内存中的实际地址和内容验证静态分析的结果。定位全局管理器通过访问已知游戏对象比如一个特定的APlayerController利用CE的指针扫描功能反向追踪到管理它的全局对象数组GObjects的地址。分析对象结构附加调试器后可以下断点跟踪对象创建、函数调用的全过程直观地看到UObject、UClass等核心引擎对象在内存中的布局。辅助工具与脚本PythonIDA Pro内置了Python解释器我们将编写Python脚本来自动化SDK的Dump过程。这是整个项目的“大脑”。UE4 Dumper模板/项目网络上存在一些开源的UE4 SDK Dumper项目例如一些知名的GitHub仓库。它们提供了基础的代码框架和思路。我们的工作不是照搬而是理解其原理并根据PUBG的实际情况进行适配和修正特别是处理引擎版本差异和偏移量问题。注意所有工具请从官方网站或可信渠道获取。使用这些工具分析自己拥有合法拷贝的软件是学习逆向技术的常见方式但务必遵守最终用户许可协议切勿用于作弊或破坏游戏公平性。3. 逆向实战定位核心引擎结构3.1 第一步寻找GNames和GObjects这是整个逆向过程的“起手式”。GNames是引擎存储所有FName字符串的全局表GObjects是存储所有UObject实例的全局数组。找到它们我们就拿到了遍历游戏所有对象和名称的钥匙。静态分析IDA Pro用IDA加载TslGame.exePUBG的主程序。分析完成后在字符串窗口搜索一些明显的引擎字符串比如/Script/CoreUObject、GObjects、GNames。虽然这些符号在发布版本中通常被剥离但有时在字符串引用或特定函数中会有残留。更可靠的方法是搜索特征码。通过分析不同版本UE4的源码或已有的Dump可以知道访问GObjects的汇编指令模式。例如它可能通过一个固定的偏移从某个寄存器或指令中加载地址。我们可以用IDA的二进制搜索功能寻找类似48 8B 0D ?? ?? ?? ??mov rcx, [ripoffset]这样的指令模式其目标地址可能就是GObjects。这需要一些经验和尝试。动态验证Cheat Engine启动游戏和CE将CE附加到游戏进程。在CE的内存浏览器中尝试跳转到静态分析找到的疑似地址。如果它是真正的GObjects其内存内容应该是一个指向数组指针的指针而这个数组里存放着大量的指针这些指针指向的内存区域开头会有规律的结构比如虚表指针。指针扫描法这是一个更强大的动态方法。在游戏中找到一个你容易识别的对象地址比如通过搜索健康值找到玩家角色指针。然后对这个地址进行指针扫描。在结果中你会看到成千上万个指向该地址的指针链。仔细分析这些链寻找一个被大量不同对象共同引用的地址。这个地址很可能就是GObjects数组的基址因为所有UObject都在这个数组中被管理。GNames也可以用类似思路通过扫描对特定字符串的引用来定位。3.2 第二步分析UObject与UClass内存布局找到GObjects后我们需要理解其中每个元素即UObject*所指向的结构。基础结构在内存中选中一个UObject的地址在CE中将其添加到地址列表并手动定义一个结构体进行分析。一个最简化的UObject通常包含VfTable虚函数表指针位于对象偏移0的位置。这是C对象的标志。ObjectFlags表示对象状态的标志位。InternalIndex该对象在GObjects数组中的索引。ClassPrivate指向该对象所属的UClass的指针。这是关键字段通过它我们可以知道这个对象是什么类型。NamePrivate对象名称的索引或指针与GNames关联。OuterPrivate指向包含该对象的“外部”对象包Package。探索UClassUClass本身也是一个UObject但它包含了类的元数据。双击ClassPrivate指针跳转到UClass对象。这里你会找到SuperStruct指向父类的指针。这是构建类继承树的核心。Children/ChildProperties指向属性和函数的链表头。FuncMap函数名称到函数对象的映射在某些版本中。使用CE的结构体分析功能CE可以帮你自动分析一个地址区域的结构。对一个UClass地址使用“分析数据结构”CE会尝试识别出其中的指针、字符串、数组等这能极大加快我们理解内存布局的速度。你需要将分析结果与已知的UE4源码结构如UObjectBase、UField、UStruct、UClass的定义进行比对和印证。3.3 第三步修正偏移量与版本适配PUBG使用的UE4版本可能与你参考的源码或Dumper模板的版本不同。引擎内部类的成员变量偏移量Offset可能会随版本更新而改变。直接使用错误的偏移量会导致脚本读取到错误的数据进而生成无效的SDK。如何发现和修正偏移量关键偏移量最重要的偏移量包括UObject::ClassPrivate、UStruct::SuperStruct、UField::Next、UClass::Children、UFunction::Func函数执行地址等。动态比对法在CE中你已经有了实际的内存对象。记录下关键字段在你分析的对象中的偏移位置例如ClassPrivate在对象起始地址后的第0x10字节。将这个值与你的Dumper脚本中硬编码的偏移量常量进行对比。如果不一致就需要修改脚本中的常量定义。特征值验证法通过读取UClass的NamePrivate与GNames表核对看是否能正确解析出预期的类名如“Object”、“Class”、“Function”。如果解析出的名字是乱码或错误的很可能就是NamePrivate的偏移量不对。迭代测试修正一个偏移量后重新运行Dumper脚本的一部分功能比如只打印前100个对象的类名观察输出是否变得合理。这是一个需要耐心反复调试的过程。4. 编写SDK Dumper脚本4.1 脚本框架设计我们的Python脚本主要运行在IDA Pro的环境中因为它需要直接访问反汇编后的二进制数据来解析某些函数。脚本的核心逻辑流程如下初始化定义修正后的偏移量常量。通过特征码扫描或硬编码在动态分析确定后的方式获取GNames和GObjects的当前地址。遍历GObjects从GObjects地址开始读取数组大小然后循环遍历每个元素UObject*。类型过滤与分类对于每个UObject通过其ClassPrivate指针找到它的UClass再通过GNames解析出该UClass的名称。根据名称我们将对象分类为UClass、UFunction、UProperty或FProperty取决于版本等。构建类继承树对于每个UClass对象读取其SuperStruct指针从而建立起“子类-父类”的关系链最终可以还原出完整的类继承体系。收集属性和函数对于每个UClass遍历其Children链表。链表中的每个元素可能是UProperty描述成员变量或UFunction描述成员函数。我们需要解析它们的名称、类型、偏移量对于属性、参数和返回类型对于函数。输出SDK将收集到的所有信息以一种可用的格式通常是C头文件.hpp输出到文件中。输出需要包含类的继承关系、成员变量和成员函数的声明。4.2 关键代码片段解析以下是一些核心环节的伪代码和思路读取GNames中的字符串def get_fname_from_index(name_index): # 假设 GNames 地址已获得 name_pool_chunk read_memory(GNames (name_index // 0x4000) * 8) # 计算块地址 name_entry_ptr name_pool_chunk (name_index % 0x4000) * 8 name_entry read_memory(name_entry_ptr, 16) # 读取FNameEntry结构 string_length name_entry[0] 0xFF string_data read_string(name_entry_ptr 2, string_length) # 读取字符串 return string_data.decode(utf-8, errorsignore)注意FName的存储结构块大小、条目大小可能随引擎版本变化上述0x4000和8是示例需要根据动态分析结果调整。遍历UClass的Children链表def dump_properties_and_functions(class_ptr): current_child read_memory(class_ptr ChildrenOffset) # ChildrenOffset需要修正 while current_child ! 0: child_class_ptr read_memory(current_child ClassPrivateOffset) child_class_name get_class_name(child_class_ptr) if child_class_name Property: # 或 FProperty property_size read_memory(current_child PropertySizeOffset) property_offset read_memory(current_child Offset_InternalOffset) property_name get_object_name(current_child) print(f {property_name}; // Offset: 0x{property_offset:X}, Size: 0x{property_size:X}) elif child_class_name Function: function_name get_object_name(current_child) func_ptr read_memory(current_child FuncOffset) # 函数执行地址偏移 print(f void {function_name}(); // Func: 0x{func_ptr:X}) # 移动到链表下一个元素 current_child read_memory(current_child NextOffset) # NextOffset需要修正处理继承和生成头文件需要维护一个字典以类名为键存储其父类名、属性列表和函数列表。在所有数据收集完毕后按照继承关系从基类到派生类排序输出确保在C头文件中父类的定义出现在子类之前。4.3 难点与解决方案类型信息缺失Dump出的属性只有偏移量和大小没有准确的C类型如int32、FString、TArray。解决方案通过分析属性的UClass或UStruct名称来推断。例如一个指向UClass*的属性其内部可能是一个UObject*类型的属性名称为“Class”。更高级的Dumper会尝试解析UProperty的PropertyClass和PropertyFlags来获得更精确的类型信息。函数签名还原逆向出完整的函数参数和返回类型极其困难。UFunction对象内部有ParamsSize、ReturnValueOffset以及一个Children链表来存放参数属性。解析这个链表可以还原参数列表但将参数属性映射回具体的类型名如FVector同样面临类型信息缺失的问题。通常初版SDK中函数可能只声明为void或使用最基本的类型。引擎版本碎片化不同小版本的UE4偏移量可能不同。最稳健的方法是让脚本支持“偏移量配置文件”。在动态分析确定偏移量后将其保存到一个JSON或INI文件中脚本运行时加载这个配置文件。这样当游戏更新后你只需要重新确定一次偏移量并更新配置文件而无需修改脚本核心逻辑。5. 生成SDK后的验证与应用5.1 SDK的验证与整理脚本运行后会生成一堆.hpp文件。这还不是一个可用的SDK需要人工进行大量的整理和验证工作去除引擎内部类生成的SDK会包含大量游戏根本用不到的引擎内部管理类。你需要根据游戏逻辑筛选出与游戏性相关的类如APlayerController、ACharacter、AActor、UWidget等。修正类型和前置声明自动生成的类型可能是模糊的。你需要根据上下文将TArray的模板参数补全为自定义结构体添加前置声明解决头文件之间的循环依赖。手动补充缺失信息对于一些关键的、但脚本未能成功导出的类或成员需要结合动态调试CE和静态分析IDA进行手动添加。例如通过CE查看一个APlayerController实例的内存对照反汇编代码手动确定其重要成员变量的偏移量。编译测试将整理后的SDK头文件放入一个新的DLL项目中尝试包含这些头文件并编译。编译器会暴露出所有的类型错误、缺失声明等问题这是最好的验证方式。你可能需要经历多轮“生成-整理-编译-修正”的循环。5.2 应用场景一个简单的功能示例假设我们通过SDK知道了APlayerController类中有一个Rotation属性控制视角旋转。现在我们可以编写一个外部程序或DLL注入来读取或修改它。使用CE手动验证在CE中找到APlayerController实例的地址。根据SDK中定义的偏移量比如Rotation在PlayerController偏移0x3E0的位置在CE的内存浏览器中查看该地址0x3E0处的值。修改这个值比如Yaw值观察游戏中的视角是否发生变化。如果变了说明偏移量是正确的。编写简易读写代码// 假设我们已经通过外部方式获得了 PlayerController 对象的地址 uintptr_t pPlayerController ...; // 根据SDK定义的结构简化版 struct FRotator { float Pitch, Yaw, Roll; }; class APlayerController { // ... 其他成员 FRotator Rotation; // 假设偏移量是 0x3E0 }; // 类型转换并访问 auto* pc reinterpret_castAPlayerController*(pPlayerController); float currentYaw pc-Rotation.Yaw; pc-Rotation.Yaw 10.0f; // 向右微调视角这只是一个最简单的内存读写示例。更复杂的应用包括调用游戏对象的方法需要组装虚函数调用、创建新的游戏对象、修改游戏逻辑等这些都需要对SDK和游戏内存模型有更深的理解。5.3 常见问题与排查技巧脚本运行崩溃或读取到垃圾数据检查偏移量这是最常见的原因。99%的问题源于错误的偏移量常量。回头用CE仔细核对每一个关键偏移。检查地址有效性确保脚本读取的GObjects、GNames地址是有效的。可以在脚本中添加断言如果读取到的数组大小异常大比如几十亿说明基址错了。处理内存分页确保你尝试读取的进程内存地址具有可读权限。在Windows下可以使用VirtualQueryEx函数查询。生成的类继承关系混乱或缺失验证SuperStruct偏移用CE查看几个有明确继承关系的类如AActor和APawn确认SuperStruct指针是否正确指向了父类。检查链表遍历逻辑Children和Next指针的遍历逻辑是否正确是否因为版本差异链表结构发生了变化比如可能是双向链表函数地址无效UFunction中的Func指针指向的可能是引擎解释字节码的入口而不是直接的机器码。对于Blueprint函数尤其如此。对于C实现的函数这个指针可能更有用。需要区分对待。游戏更新后一切失效这是常态。我们的方法核心是流程而不是数据。游戏更新后你需要重复“定位全局管理器-修正偏移量”的步骤。如果之前将偏移量设计成了可配置的那么更新工作会快很多。建立一个自己的“偏移量笔记”文档非常重要。整个逆向UE引擎SDK的过程是一个将动态观察、静态推理和自动化脚本紧密结合的系统工程。它没有一成不变的答案更像是在与游戏编译后的二进制进行一场对话。每一次成功的Dump都是对游戏引擎运行时机制的一次深刻理解。这份生成的SDK将成为你深入探索游戏世界底层的一幅宝贵地图。记住重点不在于拿到最终那一堆头文件而在于掌握绘制这幅地图的能力。

相关新闻

为什么你在公司能成事,独处时却一事无成?

为什么你在公司能成事,独处时却一事无成?

——从脚手架、意识形态到情绪战争的三个维度解剖你有没有发现一个悖论: 同一个人,同一颗大脑,同一套意志力。 在公司里,你能连续加班三个月交付一个复杂项目,能在状态极差的情况下完成高质量的方案,能在截…

2026/10/15 19:24:10 阅读更多 →
非线性控制中的Lipschitz条件:从数学基础到工程实践

非线性控制中的Lipschitz条件:从数学基础到工程实践

1. 项目概述:为什么非线性控制绕不开Lipschitz条件?如果你刚开始接触非线性控制理论,可能会被一堆数学条件搞得头大,其中“Lipschitz条件”绝对是出场率最高的明星之一。它不像“稳定性”、“能控性”那样直观,名字听起…

2026/10/14 21:22:50 阅读更多 →
数字系统时钟电路设计全解析:从晶振到RTC的工程实践

数字系统时钟电路设计全解析:从晶振到RTC的工程实践

1. 项目概述:从“心跳”到“脉搏”的精准掌控在数字系统的世界里,时钟信号就像是整个电路的心脏,它每一次精准的跳动,都驱动着逻辑单元有序地工作。无论是你手机里处理信息的SoC,还是工控设备中稳定运行的FPGA&#xf…

2026/10/2 8:50:22 阅读更多 →

最新新闻

多智能体协作系统架构设计与落地:从单体Agent到Agency Agents

多智能体协作系统架构设计与落地:从单体Agent到Agency Agents

最近差不多两个月的时间,我一直泡在一个内部研发辅助工具的重构项目里,核心动作就是把它从单Agent模式改成多智能体协作系统,内部代号就叫Agency Agents。听着有点绕,但整体思路其实很朴素:与其让一个大模型从头到尾包…

2026/10/16 8:24:28 阅读更多 →
0.4B对话模型从零训练实战:数据清洗、分词器与训练配置全解析

0.4B对话模型从零训练实战:数据清洗、分词器与训练配置全解析

1. 为什么一个 0.4B 的对话模型值得从零训一遍先把结论放在前面:本文说的“从零训练”,指的是不加载任何预训练权重、分词器也是自己训练的那种从头开始。模型规模控制在 0.4B 参数附近,训练数据规模在 1B tokens 左右,单张 24G 显…

2026/10/16 8:24:28 阅读更多 →
SpringBoot服装搭配推荐小程序开发实践

SpringBoot服装搭配推荐小程序开发实践

1. 项目背景与核心价值在快节奏的现代生活中,如何高效搭配服装成为许多人的痛点。传统服装推荐系统往往存在推荐结果单一、缺乏个性化适配等问题。这个基于SpringBoot的个性化服装搭配推荐小程序,正是为了解决这些实际问题而生。我去年为一个时尚电商平台…

2026/10/16 8:24:28 阅读更多 →
手写展开收缩导航菜单:HTML+CSS+JS实现与优化

手写展开收缩导航菜单:HTML+CSS+JS实现与优化

1. 项目缘起与整体设计思路1.1 为什么还要手写一个展开收缩导航菜单前端生态里组件库多如牛毛,随便引入一个UI框架,菜单组件开箱即用。但我在实际带项目和做代码评审时发现一个现象:很多做了两三年前端的人,遇到一个简单的展开收缩…

2026/10/16 8:24:28 阅读更多 →
Spring循环依赖详解:从报错原理到三级缓存与解决方案

Spring循环依赖详解:从报错原理到三级缓存与解决方案

1. 先看它为什么炸了再谈怎么解决循环依赖这个问题,凡是做后端开发、写了几年Spring项目的,基本都绕不开。可能你还没被它坑过,但只要你有朝一日接手一个堆了几年业务、类之间你拽我我扯你的老项目,或者自己写代码时图省事直接在两…

2026/10/16 8:24:28 阅读更多 →
STM32F130ZET6驱动SHT30温湿度传感器:HAL库I2C例程详解

STM32F130ZET6驱动SHT30温湿度传感器:HAL库I2C例程详解

简介:面向STM32F130ZET6开发者的SHT30温湿度传感器HAL库例程,可帮助嵌入式爱好者快速掌握I2C通信与温湿度数据采集,适用于智能家居、环境监测、农业设备、医疗设备等场景。资源共231个文件,压缩包大小约10.22MB,主体包…

2026/10/16 8:23:28 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →