nes4j是使用java语言实现任天堂红白机模拟器,主要包括CPU、 PPU和APU三部分组成.其中PPU是红白机 实现难度最大的一个模块,理解起来有点困难.
nes4j
├── app UI模块(javafx)
├── bin 模拟器核心模块(CPU/PPU/APU)
└── document 开发文档
git clone https://gitee.com/navigatorCode/nes4j.git
mvn run
更多卡带Mapper正在实现中,敬请期待。
如果你觉得当前游戏输出程序无法满足你的需求,你可以给我们提PR,我们会尽可能满足你的需求,另外一种方法就是你自己引入nes4j-bin模块自己实现 游戏视屏和音频输出
- Apache Maven
<dependency>
<groupId>cn.navclub</groupId>
<artifactId>nes4j-bin</artifactId>
<version>1.0.5</version>
</dependency>
- Gradle(groovy)
implementation group: 'cn.navclub', name: 'nes4j-bin', version: '1.0.6'
or
implementation 'cn.navclub:nes4j-bin:1.0.6'
- Gradle(Kotlin)
implementation("cn.navclub:nes4j-bin:1.0.6")
- GameWorld.java
import cn.navclub.nes4j.bin.NesConsole;
import cn.navclub.nes4j.bin.io.JoyPad;
import cn.navclub.nes4j.bin.ppu.Frame;
public class GameWorld {
public NES create() {
NesConsole console = NesConsole.Builder
.newBuilder()
//nes游戏rom
.file(file)
//音频输出程序
.player(JavaXAudio.class)
//Game loop 回调
.gameLoopCallback(GameWorld.this::gameLoopCallback)
.build();
try {
//一旦当前方法被调用将会阻塞当前线程直到游戏结束或者异常发生
console.execute();
} catch (Exception e) {
//todo 当异常发生当前游戏立即停止
}
}
//当PPU输出一帧视屏时回调该函数
private void gameLoopCallback(Frame frame, JoyPad joyPad, JoyPad joyPad1) {
}
}
- JavaXAudio.java
@SuppressWarnings("all")
public class JavaXAudio implements Player {
private final byte[] sample;
private final byte[] buffer;
private final Line.Info info;
private final AudioFormat format;
private final SourceDataLine line;
private int ldx;
//Current fill index
private int index;
private final Thread thread;
private volatile boolean stop;
private final static int SAMPLE_SIZE = 55;
private static final LoggerDelegate log = LoggerFactory.logger(JavaXAudio.class);
public JavaXAudio(Integer sampleRate) throws LineUnavailableException {
this.sample = new byte[SAMPLE_SIZE];
this.buffer = new byte[SAMPLE_SIZE];
this.thread = new Thread(this::exec);
this.format = new AudioFormat(sampleRate, 8, 1, false, false);
this.info = new DataLine.Info(SourceDataLine.class, format);
this.line = (SourceDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
this.thread.start();
}
@Override
public void output(byte sample) {
this.buffer[this.index] = sample;
this.index++;
if (this.index == SAMPLE_SIZE) {
this.index = 0;
System.arraycopy(this.buffer, 0, this.sample, 0, SAMPLE_SIZE);
LockSupport.unpark(this.thread);
}
this.index %= SAMPLE_SIZE;
}
private void exec() {
while (!this.stop) {
LockSupport.park();
this.line.write(this.sample, 0, SAMPLE_SIZE);
}
}
@Override
public void stop() {
this.stop = true;
LockSupport.unpark(this.thread);
this.line.close();
}
@Override
public void reset() {
this.index = 0;
}
}
我们强烈欢迎有兴趣的开发者参与到项目建设中来,同时欢迎大家对项目提出宝贵意见建议和功能需求,项目正在积极开发,欢迎 PR 👏。
目前市场上绝大部分游戏版权为任天堂所有,请勿在未取得任天堂授权的情况下私自分发游戏. 如果因此引发的任何侵权行为均与本软件无关.如果本软件中设计任何侵权素材请发送邮件到[email protected]通知我删除对应侵权素材.
如果你也想编写自己的模拟器或想了解模拟器内部结构,以下资源可以给你提供一些模拟器基础知识:
主界面 -> 工具 -> 调试
程序内存快照 (内存)
为了方便程序调试开发,模拟器内部会不断新增自定义指令。
- LOG($FF)日志输出指令
LOG = $FF
NULL = 0
.segment "STARTUP"
start:
.byte LOG,"ra=\{c.a},rx={c.x},ry={c.y}",NULL
sei
clc
lda #$80
sta PPU_CTRL ;Enable val flag
jmp waitvbl
...
字符串支持类字符串模板功能,仅支持内置变量例如上述代码中的c.a、c.x、c.y等等
变量 | 描述 |
---|---|
c.a | CPU累计寄存器 |
c.x | CPU X寄存器 |
c.y | CPU Y寄存器 |
c.s | CPU 栈指针 |
后期考虑新增PPU和APU、模拟器相关寄存器变量。
名称 | 描述 |
---|---|
Jetbrain | 免费提供全套集成开发环境 |
NES forum | 提供技术支持 |