Extract splite from NES file in Rust

懲りずにエミュレータを作りたいと考えている。
ファミコンエミュレータの創り方 - Hello, World!編 - - Qiita
HelloWorldするために、NESファイルを読み込みたい。


Qiitaの記事にもあるようにウォーミングアップとしてスプライトを表示したい。
GitHub - katakanan/nes-rom-reader

グラフィックライブラリ選び

NES本体自体も同じグラフィックライブラリで描くと思うので慎重選びたい。

OpenGLをラップしたようなcrateを利用しているプロジェクトもあるが、少しばかり速度を犠牲にしても、描写周りで消耗したくなかったので、ゲームエンジンライブラリを検討した。
(完成後に速さがたりなかったら入れ替えることも検討したい)

ゲームエンジンライブラリ選び?

Amethystとpistonとggezというのが検索上位だったが、Amethystもggezもゲームループを意識する感じでは無さそうなのが逆に邪魔になるかもというのと、サンプルプログラムが一瞬で動いたこともあって、piston_windowsで行くことにした。

またpiston_windowは使ったことのあるimage crateに依存しており、
image::ImageBufferをTextureに突っ込んでそのまま描写できて便利ということなので、それを使う。

カセット読み込み

INES - Nesdev wiki
INESのフォーマットは、少なくともmapper0だけに限れば、とても単純だと思う。

16バイト読んで指定バイトをサイズとしてprogram romとcharacter romを読む。

let mut header = [0u8; 16];

match f.read(&mut header) {
	Ok(read_size) => {
		if read_size != 16 {
			return Err(Error::new(ErrorKind::Other, "Failed to read header"));
		}
	}
	Err(_) => {
		return Err(Error::new(ErrorKind::Other, "Failed to read header"));
	}
}

let magic =
	String::from_utf8(header[0..3].to_vec()).unwrap_or("Magic is wrong!".to_string());

if magic != "NES" {
	return Err(Error::new(ErrorKind::Other, magic));
}

let prgrom_size = (header[4] as usize) * 1024 * 16;
let chrrom_size = (header[5] as usize) * 1024 * 8

エラーハンドリングはかなり適当でとにかく動けばヨシ!

スプライト

スプライト情報があれば、それを順番に表示する。

実際には4色セットを示すパレット番号が存在して、
メモリ上のパレット領域にパレット番号が格納されている。
描写時に描写位置に対応するパレット領域のアドレスからパレット番号を読み込んで、
その番号に対応する4色を使ってスプライトを色づけるが、
HelloWorldでは0~2色目は黒、3色目は白とした。

NES研究室 - サンプル
のHelloWorldのスプライトを表示したもの
f:id:katakanan:20210606213234p:plain

Nestest.nesのスプライトを表示したもの
f:id:katakanan:20210724195028p:plain

よさそう。
吸出し装置はnestestが通ったりしたら必要になると思う。