当前位置: 首页 > news >正文

用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.dtcms.com/a/511619.html

相关文章:

  • RTCM消息
  • 网络营销从网站建设开始搜索引擎优化的主要特征
  • 2025 年中国医疗行业 OA 办公系统使用情况调研报告
  • 亚信安全连续九年登顶身份和访问管理软件第一,终端安全领跑
  • 中石油工程建设公司网站二手书网站的建设规模
  • 使用 Go + govcl 实现 Windows 资源管理器快捷方式管理器
  • golang/java每日3题
  • 智能数字毫秒表的应用场景介绍,数字毫秒仪 智能毫秒表
  • 【设计模式】工厂模式(Factory)
  • 峰峰专业做网站珠海集团网站建设
  • vue实现打印PDF文档
  • 使用 Python 将 PDF 转成 Excel:高效数据提取的自动化之道
  • 神经网络初次学习收获
  • clickhouse学习笔记(一)基础概念与架构
  • 做网站的业务分析wordpress 国外免费主题
  • [人工智能-大模型-34]:模型层技术 - 通俗易懂的语言阐述Transformer架构
  • 推广你公司网站wordpress静态路由
  • 2017年下半年试题三:论无服务器架构及其应用
  • 内置线程池的核心参数分析配置
  • vim及其模式的操作
  • ESP32学习笔记(基于IDF):SmartConfig一键配网
  • 黑马商城day4-微服务02
  • 哪些网站可以找到做海报的素材浙江建设厅考试成绩查询
  • Python定时爬取新闻网站头条:从零到一的自动化实践
  • 纯CSS实现多种背景图案:渐变条纹、蓝图网格、波点与棋盘效果全解析(附 Sass Mixin 封装)
  • Linux相关概念和易错知识点(48)(epoll的底层原理、epoll的工作模式、反应堆模式)
  • 植物网站设计方案如何查网站是哪家公司做的
  • Vue 2 响应式系统常见问题与解决方案(包含_demo以下划线开头命名的变量导致响应式丢失问题)
  • [人工智能-大模型-33]:模型层技术 - 大模型的神经网络架构
  • MySQL 从库延迟 10 小时——磁盘静默错误引发的惨案