#psx #vulkan #emulator #rust #游戏和图形

应用 梯形

基于vulkano渲染的PSX模拟器

3个版本

0.1.2 2024年1月29日
0.1.1 2024年1月25日
0.1.0 2024年1月25日

#64模拟器

每月 22次下载

MIT 协议

2MB
13K SLoC

梯形

Build status dependency status license Crates.io trapezoid

梯形 是一个从头开始构建的 PSX/PS1 模拟器,使用 Rust 构建。

这是一个为了娱乐并体验模拟硬件以及将它们连接在一起的个人项目。

展示

demo

构建和安装

安装

您可以使用 trapezoidcrates.io 使用 cargo 安装。

cargo install trapezoid

构建

如果您想体验最新的开发版本,您可以自己构建 trapezoid

cargo build --release

没有优化,模拟器会运行得很慢,这就是为什么我们在 debug 配置文件中有 opt-level = 2

模拟器核心

模拟器核心作为库实现,位于 trapezoid-core,这个库是模拟器核心,包含所有组件。您可以将核心提取出来,围绕它构建前端,或者将其用作服务器。

有关更多信息,请查看 trapezoid-core 的文档。

前端

控制

前端实现有自己的控制映射,如果您决定直接使用 trapezoid-core,则可以配置它。

键盘

键盘 PSX控制器
Enter Start
Backspace Select
Num1 L1
Num2 L2
Num3 L3
Num0 R1
Num9 R2
Num8 R3
W Up
S Down
D Right
A Left
I Triangle
K X
L Circle
J Square

调试

trapezoid 内置了一个强大的调试器,可以帮助调试游戏并访问数据。

这是一个基于CLI的调试器,可以通过按下 / (正斜杠) 键激活,它将暂停模拟并激活调试器。

您将得到一个提示

CPU>

调试器使用 rustyline 并具有自动完成功能

调试器寻址和变量

在任何使用术语 <addr> 的地方,它可以是十六进制地址,也可以是变量名。

有两种变量类型

  • $ 开头的是寄存器,例如 $t0 是寄存器 t0 等...
  • @ 开头的是特殊硬件寄存器,如 @TIMER0_TARGET,它是定时器 0 目标寄存器。

您可以使用缩写补全了解这些寄存器。只需开始输入 $@ 然后按 tab 键。

调试器命令

h

打印帮助信息

CPU> h
h - help
r - print registers
c - continue
s - step
so - step-over
su - step-out
tt - enable trace
tf - disbale trace
stack [0xn] - print stack [n entries in hex]
bt/[limit] - print backtrace [top `limit` entries]
b <addr> - set breakpoint
rb <addr> - remove breakpoint
bw <addr> - set write breakpoint
rbw <addr> - remove write breakpoint
br <addr> - set read breakpoint
rbr <addr> - remove read breakpoint
lb - list breakpoints
m[32/16/8] <addr> - print content of memory (default u32)
p <addr>/<$reg> - print address or register value
i/[n] [addr] - disassemble instructions
hook_add <cmd[;cmd]> - add hook/s commands
hook_clear - clear all hooks
hook_list - list all hooks
hook_setting [<break_type>[=true/false]] - change when the hooks are executed
r

打印寄存器(例如来自随机游戏中的随机点)

CPU> r
Registers:
pc: 8004A648    at: 80060000
hi: 00000000    lo: 009941F4
v0: 00003178    s0: 54042275
v1: FFFFFFFF    s1: 0000015B
a0: 00003179    s2: 0000008F
a1: 00008000    s3: 00000000
a2: 00000000    s4: 00000002
a3: 00000000    s5: 00000000
t0: 39937A40    s6: 00000000
t1: 00000000    s7: 00000000
t2: 00000000    t8: 00000000
t3: F9A700FE    t9: 801FFEE0
t4: 0000F159    k0: 8004A600
t5: 801A1D9C    k1: 00006418
t6: 00000001    gp: 8005F17C
t7: 00000003    sp: 801FFE78
fp: 801FFFF8    ra: 8004A540
c

继续仿真

s

执行一条指令然后停止

so

执行一条指令然后停止,如果指令是函数调用,它将执行函数并在调用后的下一条指令处停止。

例如,如果代码是这样的

0x1000: jal 0x8004A648
0x1004: _nop            ; delay slot
0x1008: nop

并且程序计数器(PC)位于 0x1000,那么 so 将执行 jal 并在 0x1008 处停止。

su

将仿真继续到当前函数返回。

它将在函数调用后的下一条指令处停止。

tt

启用跟踪,这将打印执行指令,由于它打印所有指令,因此会减慢仿真速度。

示例输出

CPU> tt
Instruction trace: true
CPU> c
80000080: lui k0, 0x0000
80000084: addiu k0, k0, 0x0C80
80000088: jr k0
8000008C: _nop
00000C80: nop
00000C84: nop
00000C88: addiu k0, zero, 0x0100
00000C8C: lw k0, 0x0008(k0)
00000C90: nop
00000C94: lw k0, 0x0000(k0)
00000C98: nop
...
tf

禁用跟踪

stack

打印栈内容,您可以指定要打印的条目数,默认为 10

CPU> stack
Stack: SP=0x801FFC90
    8001273C
    8001273C
    00000002
    00000000
    00000000
    800C82AC
    00000012
    00000001
    00000000
    800143AC
bt

打印回溯,您可以指定要打印的条目数,默认为整个回溯

例如,我们目前在回溯的 59 级别,但我们只打印前 10 个条目

CPU> bt/10
#59:      80012E24
#58:      000019B8
#57:      00000E28
#56:      8004AAB0
#55:      8004A888
#54:      8004AAB0
#53:      8004A888
#52:      000019B8
#51:      00000E28
#50:      000019B8

这里显示的地址是返回地址,例如,查看第一个 80012E24,让我们打印它之前的 2 条指令。

我们将获得调用指令,延迟槽,以及回溯中的返回地址。

CPU> i 80012E1C
0x80012E1C: jal 0x0004AF1 => 0x80012BC4
0x80012E20: _addiu a0, zero, 0xFFFF
0x80012E24: lui v0, 0x8006

这意味着我们现在位于函数 0x80012BC4 内。

b

在地址上设置断点,地址为十六进制,0x 前缀是可选的。这将触发当执行地址时。

CPU> b 80012E24
Breakpoint added: 0x80012E24
rb

移除断点

CPU> rb 80012E24
Breakpoint removed: 0x80012E24
bw

在地址上设置写入断点,地址为十六进制,0x 前缀是可选的。这将触发当写入地址时。

CPU> bw 80012E24
Write breakpoint added: 0x80012E24
rbw

移除写入断点

CPU> rbw 80012E24
Write breakpoint removed: 0x80012E24
br

在地址上设置读取断点,地址为十六进制,0x 前缀是可选的。这将触发当从地址读取时(也执行,因为我们正在从该地址读取)

CPU> br 80012E24
Read breakpoint added: 0x80012E24
rbr

移除读取断点

CPU> rbr 80012E24
Read breakpoint removed: 0x80012E24
lb

列出所有断点

CPU> lb
Breakpoint: 0x80012E24
Write Breakpoint: 0x80012E24
Read Breakpoint: 0x80012E24
m

打印内存内容,您可以指定读取的大小和读取次数,默认为 1 u32

CPU> m 80012E24
0x80012E24: 0x3C028006
CPU> m32 80012E24
0x80012E24: 0x3C028006
CPU> m32/4 80012E24
0x80012E24: 0x3C028006
0x80012E28: 0x8C427FB0
0x80012E2C: 0x00000000
0x80012E30: 0x1440FFED
CPU> m8/4 80012E24
0x80012E24: 0x06
0x80012E25: 0x80
0x80012E26: 0x02
0x80012E27: 0x3C
CPU> m16/4 80012E24
0x80012E24: 0x8006
0x80012E26: 0x3C02
0x80012E28: 0x7FB0
0x80012E2A: 0x8C42

CPU> m @GPU_STAT        ; reading gpu status register easily
0x1F801814: 0x5404220A
p

打印寄存器或内存地址的值

这仅对 CPU 寄存器有用,至少目前是这样,没有表达式求值

CPU> p $t0
0x00005688
CPU> p @GPU_STAT
0x1F801814
CPU> p 12345678
0x12345678
i

反汇编指令,您可以指定要反汇编的指令数,默认为 1 条在当前程序计数器(PC)位置

CPU> i
0x80000084: addiu k0, k0, 0x0C80
CPU> i/10
0x80000084: addiu k0, k0, 0x0C80
0x80000088: jr k0
0x8000008C: _nop
0x80000090: nop
0x80000094: nop
0x80000098: nop
0x8000009C: nop
0x800000A0: lui t0, 0x0000
0x800000A4: addiu t0, t0, 0x05C4
0x800000A8: jr t0
CPU> i 800000A0
0x800000A0: lui t0, 0x0000

钩子

调试器允许创建 钩子,这些是命令,任何上述命令都可以在特定事件上执行。可以通过使用 hook_setting 命令来配置这些事件。

CPU> hook_setting
Hooks will be executed on the following breakpoints:
  step: false
  step_over: false
  step_out: false
  instruction_breakpoint: false
  read_breakpoint: false
  write_breakpoint: false

默认情况下,钩子没有绑定到任何事件。

但是可以使用 hook_setting 来设置它们何时执行。

CPU> hook_setting step,instruction_breakpoint=true,step_out=false
Hooks will be executed on the following breakpoints:
  step: true
  step_over: false
  step_out: false
  instruction_breakpoint: true
  read_breakpoint: false
  write_breakpoint: false

这将使钩子在 stepinstruction_breakpoint 事件上启用,并在 step_out 事件上禁用,其余保持不变。

hook_add

我们可以通过 hook_add 添加钩子,它将在事件触发时执行。

CPU> hook_add r;i/20
Hook added: r
Hook added: i/20

这将在事件上添加两个要执行的命令,ri/20r 将打印寄存器,而 i/20 将从 PC 汇编 20 条指令。

hook_clear

清除所有钩子

hook_list

列出所有钩子

CPU> hook_list
r
i/20

VRAM

我们可以查看原始 VRAM 状态,可以将其视为 1024x512 像素的图像。

这可以通过键盘按钮 v 触发。

vram

贡献和待办事项

请查阅 trapezoid-core 获取有关仿真器相关待办事项的更多信息。

还可以查看 问题

非常感谢任何贡献。谢谢!

许可证

本项目采用 MIT 许可证。

NES 是任天堂公司的一个产品和/或商标。任天堂公司与 Plastic 或其作者没有任何关联。

参考文献

PSX 组件的大部分文档可以在 consoledev 网站上找到

依赖项

~47–79MB
~1.5M SLoC