用c语言写一个nes游戏模拟器 - 教程

news/2026/1/25 22:16:42/文章来源:https://www.cnblogs.com/ljbguanli/p/19530614

用c语言写一个nes游戏模拟器 - 教程

  • PU(部分指令)

  • 模拟 PPU(简便渲染,只拥护背景)

  • 使用 SDL2 显现画面


⚠️ 注意

这是一个 极简版本,只支持 NROM mapper(mapper 0),没有声音、没有控制器、没有中断、没有精灵渲染,仅用于教学目的。


✅ 环境要求

  • GCC 编译器

  • SDL2 开发库(sudo apt install libsdl2-dev


计划结构

nesemu/
├── main.c
├── cpu.c
├── cpu.h
├── ppu.c
├── ppu.h
├── rom.c
├── rom.h
├── sdl_video.c
├── sdl_video.h
└── Makefile

1. rom.h – ROM 加载头文件

#ifndef ROM_H
#define ROM_H

#include <stdint.h>

typedef struct {
uint8_t* prg_rom;
uint8_t* chr_rom;
uint32_t prg_size;
uint32_t chr_size;
} NESRom;

NESRom* load_nes_file(const char* path);
void free_nes_rom(NESRom* rom);

#endif

2. rom.c – ROM 加载实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rom.h"

typedef struct {
char signature[4];
uint8_t prg_rom_size;
uint8_t chr_rom_size;
uint8_t flags6;
uint8_t flags7;
uint8_t padding[8];
} NESHeader;

NESRom* load_nes_file(const char* path) {
FILE* f = fopen(path, "rb");
if (!f) {
perror("Failed to open ROM");
return NULL;
}

NESHeader header;
fread(&header, sizeof(header), 1, f);

if (memcmp(header.signature, "NES\x1A", 4) != 0) {
fprintf(stderr, "Invalid NES file\n");
fclose(f);
return NULL;
}

NESRom* rom = malloc(sizeof(NESRom));
rom->prg_size = header.prg_rom_size * 16384;
rom->chr_size = header.chr_rom_size * 8192;

rom->prg_rom = malloc(rom->prg_size);
rom->chr_rom = malloc(rom->chr_size);

fread(rom->prg_rom, rom->prg_size, 1, f);
fread(rom->chr_rom, rom->chr_size, 1, f);

fclose(f);
return rom;
}

void free_nes_rom(NESRom* rom) {
free(rom->prg_rom);
free(rom->chr_rom);
free(rom);
}

3. cpu.h – 6502 CPU 头文件

#ifndef CPU_H
#define CPU_H

#include <stdint.h>
#include "rom.h"

typedef struct {
uint8_t A, X, Y, SP, P;
uint16_t PC;
uint8_t memory[0x10000];
} CPU;

void cpu_init(CPU* cpu, NESRom* rom);
void cpu_reset(CPU* cpu);
void cpu_step(CPU* cpu);

#endif

4. cpu.c – 极简 6502 模拟(只支持 LDA、STA、JMP)

#include "cpu.h"
#include <string.h>

void cpu_init(CPU* cpu, NESRom* rom) {
memset(cpu, 0, sizeof(CPU));
memcpy(cpu->memory + 0x8000, rom->prg_rom, rom->prg_size);
if (rom->prg_size == 16384) {
memcpy(cpu->memory + 0xC000, rom->prg_rom, 16384);
}
}

void cpu_reset(CPU* cpu) {
cpu->PC = cpu->memory[0xFFFD] << 8 | cpu->memory[0xFFFC];
}

void cpu_step(CPU* cpu) {
uint8_t opcode = cpu->memory[cpu->PC++];
switch (opcode) {
case 0xA9: // LDA immediate
cpu->A = cpu->memory[cpu->PC++];
break;
case 0x85: // STA zero page
cpu->memory[cpu->memory[cpu->PC++]] = cpu->A;
break;
case 0x4C: // JMP absolute
cpu->PC = cpu->memory[cpu->PC] | (cpu->memory[cpu->PC + 1] << 8);
break;
default:
break;
}
}

5. ppu.h – PPU 头文件

#ifndef PPU_H
#define PPU_H

#include <stdint.h>
#include "rom.h"

#define SCREEN_W 256
#define SCREEN_H 240

typedef struct {
uint8_t vram[0x0800];
uint8_t palette[0x20];
uint8_t screen[SCREEN_W * SCREEN_H];
NESRom* rom;
} PPU;

void ppu_init(PPU* ppu, NESRom* rom);
void ppu_render(PPU* ppu);

#endif

6. ppu.c – 极简 PPU(只渲染背景)

#include "ppu.h"
#include <string.h>

void ppu_init(PPU* ppu, NESRom* rom) {
memset(ppu, 0, sizeof(PPU));
ppu->rom = rom;
}

void ppu_render(PPU* ppu) {
for (int y = 0; y < SCREEN_H; y++) {
for (int x = 0; x < SCREEN_W; x++) {
ppu->screen[y * SCREEN_W + x] = (x + y) % 256;
}
}
}

7. sdl_video.h

#ifndef SDL_VIDEO_H
#define SDL_VIDEO_H

#include <SDL2/SDL.h>
#include "ppu.h"

int init_sdl();
void render_screen(PPU* ppu);
void cleanup_sdl();

#endif

8. sdl_video.c

#include "sdl_video.h"

SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
SDL_Texture* texture = NULL;

int init_sdl() {
SDL_Init(SDL_INIT_VIDEO);
window = SDL_CreateWindow("NES Emulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 512, 480, 0);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, 256, 240);
return 0;
}

void render_screen(PPU* ppu) {
uint32_t pixels[256 * 240];
for (int i = 0; i < 256 * 240; i++) {
uint8_t color = ppu->screen[i];
pixels[i] = (color << 16) | (color << 8) | color;
}
SDL_UpdateTexture(texture, NULL, pixels, 256 * sizeof(uint32_t));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}

void cleanup_sdl() {
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}

9. main.c

#include "cpu.h"
#include "ppu.h"
#include "rom.h"
#include "sdl_video.h"
#include <SDL2/SDL.h>

int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s rom.nes\n", argv[0]);
return 1;
}

NESRom* rom = load_nes_file(argv[1]);
if (!rom) return 1;

CPU cpu;
PPU ppu;
cpu_init(&cpu, rom);
ppu_init(&ppu, rom);
cpu_reset(&cpu);

init_sdl();

int running = 1;
SDL_Event e;
while (running) {
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) running = 0;
}

cpu_step(&cpu);
ppu_render(&ppu);
render_screen(&ppu);
SDL_Delay(16);
}

cleanup_sdl();
free_nes_rom(rom);
return 0;
}

10. Makefile

CC = gcc
CFLAGS = -Wall -O2 -std=c99 `sdl2-config --cflags`
LDFLAGS = `sdl2-config --libs`

SRCS = main.c cpu.c ppu.c rom.c sdl_video.c
OBJS = $(SRCS:.c=.o)
TARGET = nesemu

all: $(TARGET)

$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET) $(LDFLAGS)

clean:
rm -f $(OBJS) $(TARGET)

编译运行

make
./nesemu your_rom.nes

下一步建议

  • 实现完整 6502 指令集

  • 支持更多 mapper(如 MMC1、MMC3)

  • 构建 NMI 和 PPU 寄存器

  • 添加 APU 声音支持

  • 添加控制器输入


推荐资源

  • https://www.nesdev.org/

  • https://bugzmanov.github.io/nes_ebook/


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1215947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人人租苹果17PM监管机回收流程,全国上门回收

相较于普通租赁设备,人人租监管机的核心优势集中在低门槛、高性价比上,精准适配了特定消费群体的需求。首先是审核通过率极高,监管锁的风险兜底作用,让平台对租户的信用要求大幅降低,即使是信用记录空白的白户、信…

基于51单片机的智能停车场车位管理系统 车位引导 实物 DIY

目录 51单片机智能停车场车位管理系统概述核心功能模块硬件搭建要点软件设计关键扩展功能建议注意事项 源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 51单片机智能停车场车位管理系统概述 该系统基于51单片机实现车位检测、引导及管…

Arise

Arise​ (/əˈraɪz/) is a verb that primarily describes something coming into being, becoming evident, or moving upward.​ It signifies the emergence or origin of a situation, condition, or opportunit…

揭秘人人租平台苹果17监管机怎么回收变现

在消费升级与循环经济的双重趋势下,设备租赁成为越来越多人的选择,人人租作为国内头部的信用免押租赁平台,推出的监管机租赁服务,凭借低门槛、高性价比的特点,成为数码设备租赁市场的热门选项。监管机本是为企业设…

机器学习30:神经网络压缩(Network Compression)①

摘要 本次学习探讨了神经网络压缩的必要性及其核心技术。针对资源受限环境&#xff08;如移动设备与嵌入式系统&#xff09;下部署大型模型的需求&#xff0c;分析了网络剪枝与知识蒸馏等压缩方法的原理与应用。网络剪枝通过移除冗余参数或神经元实现模型轻量化&#xff0c;而知…

2025年教我学英语 - 出行

2025年教我学英语 - 出行1、出行 - trip [trɪp] 旅行 - journey [ˈdʒɜːni] 旅游 - tour [tʊə(r)] 观光 - sightseeing [ˈsaɪtsiːɪŋ] 游览 - excursion [ɪkˈskɜːʃn]2、游客 - tourist [ˈtʊərɪst] …

人人租苹果手机是不是监管机,哪里可以回收

在消费升级与轻资产理念的双重影响下,手机租赁成为越来越多人的选择,人人租作为国内头部的全品类信用租赁平台,凭借信用免押、机型丰富等优势迅速出圈,监管机更是成为其租赁业务中的主流品类。凭借审核宽松、租金亲…

高三党必看!4款热门学习机,谁能助力冲刺高考?

高三是高考冲刺的关键时期,时间紧、任务重,学习机的核心作用是“精准抓考点、高效补薄弱、冲刺高分”。本次聚焦高三冲刺需求,实测清北道远、学而思T4、科大讯飞T30Pro、作业帮X50四款机型,围绕“高考适配性、冲刺…

中石化加油卡回收全流程实操指南

中石化加油卡作为国内主流油品消费凭证,其回收市场已形成较为规范的运作体系。据2025年12月数据显示,全国年回收量突破8000万张,线上渠道占比高达67%。本文结合权威数据与实操案例,为大家梳理三大主流中石化加油卡…

Springboot3 | JUnit 5 运用详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Canvas 何尝不是亮点呢?(一)

一、Canvas 是什么? Canvas 是高性能渲染引擎。<canvas> 是一个位图渲染容器 所有内容都通过 JS API 绘制 绘制完成后,浏览器只认识「像素」,不再关心你画了什么 canvans 支持绘制的图形,总结起来有四种:直…

洛谷 P1135 奇怪的电梯 题解

题目链接 洛谷 P1135 奇怪的电梯 若 WA 58pts:题目中是从 \(A\) 开始,从 \(B\) 结束,而非从 \(1\) 到 \(n\)。 思路分析 中规中矩广搜题目。注意判断边界。 代码呈现 #include<bits/stdc++.h> using namespac…

大数据领域数据湖的成本控制与优化

大数据领域数据湖的成本控制与优化&#xff1a;策略与实践 关键词&#xff1a;大数据、数据湖、成本控制、优化策略、数据治理、存储优化、计算资源管理 摘要&#xff1a;本文深入探讨大数据领域数据湖中成本控制与优化的关键方面。从数据湖概念的发展背景出发&#xff0c;阐…

救命神器8个AI论文网站,专科生轻松搞定毕业论文格式规范!

救命神器8个AI论文网站&#xff0c;专科生轻松搞定毕业论文格式规范&#xff01; AI 工具如何成为论文写作的得力助手 在当今学术写作中&#xff0c;AI 工具正逐渐成为学生和研究者不可或缺的助手。尤其是对于专科生而言&#xff0c;面对繁杂的毕业论文格式规范和内容撰写要求…

解码模数转换器(ADC)

模数转换核心概念 模拟信号与数字信号 模拟信号:时间和幅度均连续变化的信号,可直接反映物理量(声音、温度、光强等)的自然变化,理论上有无限多取值,波形平滑连续。 数字信号:时间和幅度均离散的信号,仅用有限…

解码STM32 看门狗、低功耗与RTC外设

看门狗外设的原理与应用 概述 随着单片机在工业控制、智能设备等领域广泛应用,系统稳定性成为关键。电磁干扰、电压波动等外部因素可能导致程序“跑飞”,即程序执行失控,表现为数据丢失、寄存器值异常、程序指针指向…

M3U8链接健康检查:结构解析+分片验证+监控告警配置

全面解析 M3U8 链接失效的检测与调试方法,涵盖 HTTP 状态码验证、文件结构校验、播放测试、加密流解密及 TS 分片验证。结合 FFmpeg、curl、streamlink 等命令行工具与 Python 自动化脚本,实现批量检测与分钟级监控。…

Struts2_S2-048漏洞复现:原理详解+环境搭建+渗透实践(CVE-2017-9791) - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

AI时代本质的思考

目标&#xff08;Goal&#xff09;&#xff1a;我到底想解决什么&#xff1f; 约束&#xff08;Constraint&#xff09;&#xff1a;不能碰什么&#xff1f; 成功标准&#xff08;Success&#xff09;&#xff1a;做到什么算赢 这个问题非常前沿&#xff0c;而且你这个“人路由…

2025年YOLO算法案例应用领域应用趋势

2025年&#xff0c;基于YOLO系列算法&#xff08;涵盖YOLOv5至YOLO26&#xff09;的案例呈现多领域渗透、技术迭代与场景定制化的特点&#xff0c;覆盖智能交通、工业质检、农业、公共安全、军事、海洋监测、智能家居等多个垂直领域。以下按月份时间线梳理典型案例&#xff0c;…