环形弹幕如何瞄准玩家
Elly 的攻击请求一个初始数量为 16 的自机狙环形弹幕。辅助函数计算每颗子弹的方向。同一圈弹幕既可以朝向固定方向,也可以转向玩家。
使用REA查找TH04如何创建一个子弹环并将其瞄准玩家。 解释计算并显示代码。
一个示例提示,原始可执行文件和重建签出可在本地使用。
TH04 用 256 个角度单位表示顺时针转一整圈。16 颗子弹之间各隔 16 个单位:0, 16, 32, …, 240。再加上玩家方向,就能让所有方向一起旋转。
还原源码中的发射模板
bullet_template.angle = 0;
bullet_template.group = BG_RING_AIMED;
bullet_template.count = 16;
bullet_template_tune();
这些线在难度和性能调整之前选择模式。 阅读攻击的来源.
REA 返回什么
我们检查设置每颗子弹速度和角度的函数 bullet_velocity_and_angle_set()。REA 返回指令、伪代码和引用,其中三部分解释了这种弹幕如何生成。
-
在阅读其代码之前检查DOS映像
编程助手→ REAopen_binary {"path": "MAIN.EXE"} inspect_native_load_image {}REA →编程助手 · 选定字段status: "verified" format: "dos-mz" load_segment: 4096 header_bytes: 6144 module_bytes: 150114这项检查确认加载的代码和重定位记录对应提供的可执行文件。上方路径为便于显示而缩短。
-
查看环形弹幕的乘除计算
编程助手→ REA ·analyze_function{"procedure": "0x2cfc8"}REA →编程助手·指令摘录0x2d0eb: MOV AX, SI 0x2d0ed: SHL AX, 0x8 0x2d0f0: MOV DL, byte ptr [0x53af] 0x2d0f9: IDIV BXSI保存子弹索引。移位将它乘以 256,再除以从DS:53AF读取的数量。重建源码把这个字段命名为bullet_template.count。完整指令序列见下文。 -
找到模式转向玩家的位置
相同的函数结果 · 目标摘录0x2d190: SUB AX, word ptr [0x53a6] 0x2d198: SUB AX, word ptr [0x53a4] 0x2d19d: CALLF 0x1000:1da8 0x2d1a2: ADD word ptr [BP + -0x2], AX对应维护的C++group_angle_offset += iatan2( player_pos.cur.y - bullet_template.origin.y, player_pos.cur.x - bullet_template.origin.x );指令操作数显示了两个坐标差,以及一个计算方向的辅助函数。它的返回值加到相对环形角度上。源码名称让这些地址的含义更易读;REA 则帮助助手核对地址背后的原始操作。
从汇编到可读C++
点击每一步,对照原始指令和角度计算。这份摘要展示自机狙环形弹幕的路径;完整源码还处理散射、叠层和随机模式。
0x2cfcf: MOV SI, word ptr [BP + 0x4]
0x2d0eb: MOV AX, SI
0x2d0ed: SHL AX, 0x8
0x2d0f0: MOV DL, byte ptr [0x53af]
0x2d0f4: MOV DH, 0x0
0x2d0f6: PUSH DX
0x2d0f7: CWD
0x2d0f8: POP BX
0x2d0f9: IDIV BX
0x2d0fb: MOV word ptr [BP + -0x2], AX
0x2d18d: MOV AX, [0x4650]
0x2d190: SUB AX, word ptr [0x53a6]
0x2d194: PUSH AX
0x2d195: MOV AX, [0x464e]
0x2d198: SUB AX, word ptr [0x53a4]
0x2d19c: PUSH AX
0x2d19d: CALLF 0x1000:1da8
0x2d1a2: ADD word ptr [BP + -0x2], AX
0x2d1b9: MOV AL, byte ptr [BP + -0x2]
0x2d1bc: ADD AL, byte ptr [0x53ad]
0x2d1c0: MOV [0xbcc8], AL
unsigned char aimed_ring_angle(
int index, int count,
int player_direction,
unsigned char rotation)
{
int angle = index;
angle = (angle * 256) / count;
angle += player_direction;
angle += rotation;
return (unsigned char)angle;
}
01 · 读取子弹索引。 栈参数加载到 SI。在 16 颗子弹的环中,索引从 0 到 15。
02 · 在整圈中等分方向。 SHL AX,8 将索引乘以 256。数量先零扩展为一个字,再由 IDIV 用乘积除以数量。数量为 16 时,每个索引前进 16 个角度单位。
03 · 加上玩家方向。 两次减法得到玩家坐标与原点坐标的差。方向辅助函数在 AX 中返回角度;加上这个值,就能把整圈弹幕转向玩家。
04 · 加上模板旋转角。 ADD AL,[0x53ad] 加上模板中的角度。Elly 的初始模板将它设为零;其他弹幕可以用同一个辅助函数进行旋转。
05 · 用一个字节保存最终角度。 存储指令把 AL 写入子弹生成角度字段。值超过 255 后回绕,与无符号字节转换一致。周围源码还会根据这个角度和速率计算速度分量。
汇编节选展示自机狙环形分支、瞄准计算和最终存储。上面的 C++ 是便于阅读的摘要。下方的编译器检查针对链接项目中维护的源码。
阅读维护的环和瞄准源
angle_offset = (i * BULLET_ANGLE_FULL_TURN) / count;
if (i >= (count - 1)) {
group_complete = true;
}
goto aim_or_no_aim;
aim:
group_angle_offset += iatan2(
(player_pos.cur.y - bullet_template.origin.y),
(player_pos.cur.x - bullet_template.origin.x)
);
no_aim:
vector2_near(
bullet_template.velocity,
(group_angle_offset + bullet_template.angle),
speed
);
bullet_spawn_angle = (group_angle_offset + bullet_template.angle);
检查维护的源
TH04 项目记录的检查使用指定的 Turbo C++ 4.0J 工具链,编译包含这个辅助函数的弹幕生成代码。
2,139匹配字节
编译的子弹生成代码与原始可执行文件的2,139字节部分相匹配。
两个干净的构建
两个单独的构建产生相同的匹配字节。 记录的输入哈希值与此处显示的维护源匹配。
2026年10月3日录得。 阅读编译器检查.
目标身份和分析范围
| 原始目标 | MAIN.EXE,156,258字节,DOS MZ |
|---|---|
| 佣工入境 | 0x2cfc8 在REA的加载图像 |
| 原始文件偏移量 |
0x1e7c8,返回 address_to_file_offset
|
| 记录代码检查 | REA4.1.0*2026年10月7日 |
编译器检查覆盖完整的弹幕添加代码段,包括这个辅助函数。检查还比较它在 map 文件中的贡献,以及重叠的重定位项。
REA 验证了加载段为 0x1000 的静态 DOS 加载映像。地址描述的是分析映像。函数结果在一个 517 字节范围内报告了两段代码,共 503 字节;上方节选的指令都位于这些已报告的代码范围内。
源码名称和编译器检查来自 TH04 检查点 0d72e980。较早的重建分析使用项目自身的脚本;这个页面新增了用 REA 对同一原始目标的检查。游戏文件的身份由仓库清单确定,独立原始转储的来源仍未确定。
图中说明的是角度计算。完整游戏的控制、时序和 PC-98 硬件行为,在重建项目中有各自的运行时检查。
完整的TH04重建
项目用维护中的 C++ 和汇编构建游戏的四个 DOS 程序。除了弹幕生成,源码还覆盖关卡、Boss、渲染、输入、菜单、结局和分数处理。
- 子弹生成源完整的助手,其他模式类型和模板调整。
- DOS构建和运行指南构建源代码并在项目的PC-98仿真器工作流中进行测试。
- 当前项目状态DOS验证和单独的原生x64移植版。