ぺんぎんさんのおうち

日本語勉強中のドイツ産ペンギンがいろんなことを書く

近況

近況 - ぺんぎんさんのおうち

 

高●専プロコンが終わった数日後に東京でCODEBLUEの学生スタッフをした.

その週末はUECに行ったり. 家はまだ決まってない. 2月に借りてもいいよと許可が出た.

 

東京行った次の週はkernel/VMに参加するため金沢へ. 初北陸.

お魚が本当に美味しかった. また来たい.

 

 

お前いつになったら単位出すんだよ協会に言うぞ 

"嫌な事件だったね, 卒業単位が1つまだ足りないんだろ."

 

学科長に聞いてみた. すぐに返事(や決定)はできないらしく.

「頼む, 1(か2)週間だけ待ってくれ!」

とのこと. 

 

待てねえなぁ~

 

 

 

果たして, 俺は卒業することができるのか.

 

 

 

 

 

そういえばファミコンエミュの更新が止まってますが, 飽きてないですよ. ちゃんと勉強してます. 記事書くのに時間がかかってるだけです. 3日目は少し多めになりそうですね.

第29回高専プログラミングコンテスト参加記.final

近況 - ぺんぎんさんのおうち

 

阿南高専主催の高専プログラミングコンテストに参加しました.

補助学生, 参加した高専の先生方, そして選手の皆さんお疲れ様です. 

 

 

言いたいことがあるんだよ

つっても明日は普通に授業あるし今週提出のレポートもあるし眠いし, かといって後日書くのも怠いので寝る前に書いてます. 

 

何やってたかとか技術的な話は別の機会にするとして... えっ, 僕って何やってたんですかね. わかりません. 適当に写真撮ってました. 終わりです.

 

個人的に会いたかった人たちに会えたのでよかったです. あとはpixivのブースでもっと話を聞けていればよかったなと.

 

 

交流会

楽しかったダルォ. 遠くの席の人は聞こえてないと思いますが, シーンとしたときめっちゃ拍手してたり騒いでたの僕です. すまん●.

 

 

阿波踊り

えっ, 阿波踊りにこんなのあったっけ...??? ってなった.

 

 

 

 

 

最後の参加が地元開催でよかったですね. 非常に楽しかったです. 

それでは.

 

 

 

Golangでファミコンエミュを作る 2日目

2日目です. 前回はこれ 

Golangでファミコンエミュを作る 1日目 - ぺんぎんさんのおうち

 

ちょっと雑談

ところでソースコードですが, 最終的にはGitHubにあげようと思ってます. 

"n日で作るファミコンエミュレータ" なので30日OS本を倣って01_day, 02_day, ... みたいな感じで管理していこうかな〜とか考えたんですが, なにせ僕が何もわかっていなくて日によっては解説だけで終わっちゃいそうなんですよ. うーん..

 

いきなり完成してるコード見せるよりかは, 少しずつ足されていってる感じを見てもらった方が良さそうですね. 

ということでリポジトリを作りました.  

github.com

READMEとかまだちゃんと整備してないのでもう少しお待ちを.

 

(あ, あとこれ毎日投稿できるわけではないので)

 

 

ここから本題

さて前回はNESファイルのヘッダーを読み込んで各値を表示したところで終わりました.  念のため出力結果を再掲します. 

 

Constant : 4E45531A
PRG ROM SIZE : 2
CHR ROM SIZE : 1
Flags6 : 00000001
Flags7 : 00000000
PRG RAM SIZE : 0
Flags9 : 00000000
Flags10 : 00000000

Flagsは今後のために二進8桁で表示しています. 

 

フラグはひとまず置いておいて, この結果からわかるのは

プログラム部分が 2 * 16 KB = 32 KB

スプライト部分が 1 * 8 KB = 8 KB

ということです. 

 

なんとなくreadNextBytesを使って32 KBと8 KBの読み込みをすればいいのかな〜と気づきますね. 

ただ, それぞれのROMのサイズがわかっていても, 途中で別のデータが含まれている可能性があるのでまだ読み込みをするのは早いです. もし何か含まれていたらその分だけズレて読み込んでしまいますからね.

ではどうするかというと, NESファイルフォーマットを確認します. 

INES - Nesdev wiki

 

前回はヘッダー部分だけを見ましたが, 今回はNESファイル全体のフォーマットに注目します.  

 

  1. Header (16 bytes)
  2. Trainer, if present (0 or 512 bytes)
  3. PRG ROM data (16384 * x bytes)
  4. CHR ROM data, if present (8192 * y bytes)
  5. PlayChoice INST-ROM, if present (0 or 8192 bytes)
  6. PlayChoice PROM, if present (16 bytes Data, 16 bytes CounterOut)

こうなってました.

最初の16bytesがヘッダーなのは前回確認しました.

CHR ROM以降は無視するとしてPRG ROMの前に何かやっかいなのがいます.  

 

Trainer というセクションが 0 or 512 bytes です. あるかもしれないし, ないかもしれない. (面倒ですね)

どこかに情報がないものかと探していると, Flags6に書いてありました. 

 

Flags 6


76543210 | | | | | | | | | | | | | | | +- Mirroring: 0: horizontal (vertical arrangement) (CIRAM A10 = PPU A11) | | | | | | | 1: vertical (horizontal arrangement) (CIRAM A10 = PPU A10) | | | | | | +-- 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory | | | | | +--- 1: 512-byte trainer at $7000-$71FF (stored before PRG data) | | | | +---- 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM ++++----- Lower nybble of mapper number

 

Flags6の第2ビットが1であるときにtrainerが512bytes含まれているようです. 

$7000-$71FF の位置(PRGの前)っていうのが今の所よくわかりませんが, Flags6の値は00000001だったのでとりあえず考えなくて良さそうです(trainerセクションがないので).

 

ただ第0ビットが1なので "Mirroring" が vertical であることはメモしておきましょう.

 

 

ここまでで, NESファイルの構造を簡単に表してみます. 

最初の16bytesがヘッダー, Trainerは含まれていないのでヘッダーの直後にPRG-ROMが32 KB, その後ろにCHR-ROMが8 KBです.

 

| Header [16 bytes] | PRG ROM [32 KB] | CHR ROM [8 KB]

 

ではNESの読み込みをします.  

読み込むバイト数はわかっているので, readNextBytesを使って

 

programROM := readNextBytes(f, int(header.PRG_ROM_SIZE) * 0x4000)
characterROM := readNextBytes(f, int(header.CHR_ROM_SIZE) * 0x2000)

それぞれのROMサイズは構造体でuint8として定義していたので, intにキャストしてあげないと怒られちゃいます. 

 

uint8が 0 ~ 255 なので * 0x4000 は許されないっぽいですね. * 0x2000 も同様なのでキャストします. 

 

読み込まれたバイト数を確認します.

fmt.Printf("Program ROM size : %x\n", len(programROM))
fmt.Printf("Chatacter ROM size : %x\n", len(characterROM))

 

### 実行結果 ###

Program ROM size : 8000
Chatacter ROM size : 2000

2 * 0x4000 = 0x8000

1 * 0x2000 = 0x2000

なのでちゃんと読み込みができています.

ちなみにCHR-ROMを読み込んだあとに1バイト読み込もうとすると「もう読み込めるものがないよ(EOF)~」とエラーが出ます. 

 

やはり読み込んだからにはROMの中身を見てみたいですよね. 見ますか. 

以下のコードを足して実行するとバイト列がぶわーっと出てきます.

fmt.Printf("Program ROM : %x\n", programROM)
fmt.Printf("Character ROM : %x\n", characterROM)

 

ぶわーと出てきても, それが本当にプログラムROMとキャラクターROMとして正しいのかってわかりませんよね. "ほんまにあっとるんか?" と疑問に思うわけです.

 

実はこのサンプルNESをダウンロードしたときに "character.chr" というファイルも付いてきていて, これはキャラクターROM部分だけを切り取ったものです.

 

見比べると最初に0が並んでいて, 途中から"1c 3e 3e 3e ..." となっているのでおそらく正しいです. 

 

ここまででヘッダーとプログラムとキャラクターのバイト列の切り出しができました. 

あとはプログラムROMを読んでバイナリのfetchとdecode, 画面の描画でHello Worldの表示ができそうな気がしますね.

 

言うは易し行うは難し

 

まだ先は長いはずです. 他にもやることはたくさんあります多分. 

とりあえずサンプルNESを読み込んでPRG-ROMの表示をしたので, バイト列を眺めながら多分こんな実装が必要なんだろうな〜という想像をします.

 

 

今日はもう少し進めようと思っていましたが, 予想より帰宅が遅くなってしまい時間が取れなかったのでこのあたりで終わります.

この週末から来週にかけて予定が多めなので進捗は出せないと思うので隙間の時間でドキュメントとか読みます.

 

それでは. 

 

Golangでファミコンエミュを作る 1日目

1日目です. 前回はこれ Golangでファミコンエミュを作る 0日目 - ぺんぎんさんのおうち (投稿したのさっきですけど)

 

今回はHello Worldを動かすサンプルNESの読み込みをやっていきます.

 

※ ヘッダーとHeaderの表記揺れがあります. 特に違いはありませんがプログラムが絡んでくるような場合には"Header"を, そうでない場合には"ヘッダー"を使うようにしています(例外もありますが気にしないでください).

 

 

NESファイルはここでダウンロードできます.

NES研究室 - サンプル

 

とりあえずファイル読み込みをします. が, バイナリファイルの扱い方がわからずいきなり壁にぶつかってしまいました. 

NESファイルの先頭にはヘッダーがあるはずなので, ヘッダー分のバイト数を読み込んだ上で構造体か何かで保持しておきたいです. 

 

調べていると以下のような記事が見つかりました. 

www.jonathan-petitcolas.com

バイナリファイルを読んでバイト列から構造体へ変換する方法が記されていました. 

 

 

ヘッダーの解析

ヘッダーにはNESファイルのどの位置にプログラムがあってどの位置にキャラクターがあるのか.. といった情報が書いてあります. ヘッダーの解析をしましょう.

 

 

Header部分に関しては以下のページに詳しく記載されています. 

INES - Nesdev wiki

 

ひとまずHeader部だけを読んでみます.

 The format of the header is as follows:

  • 0-3: Constant $4E $45 $53 $1A ("NES" followed by MS-DOS end-of-file)
  • 4: Size of PRG ROM in 16 KB units
  • 5: Size of CHR ROM in 8 KB units (Value 0 means the board uses CHR RAM)
  • 6: Flags 6
  • 7: Flags 7
  • 8: Size of PRG RAM in 8 KB units (Value 0 infers 8 KB for compatibility; see PRG RAM circuit)
  • 9: Flags 9
  • 10: Flags 10 (unofficial)
  • 11-15: Zero filled

 

0 ~ 3 : 定数 4E 45 53 1A

4 : プログラムROMのサイズ (16 KB 単位)

5 : キャラクター(スプライト)ROMのサイズ(8 KB 単位)

6 : フラグ6

7 : フラグ7

8 : プログラムRAMのサイズ(8 KB 単位)

9 : フラグ9

10 : フラグ10

11 ~ 15 : 0 埋め

 

全体で16bytesですね. 

 

これをGolang側で構造体として次のように定義しておきます.

type Header struct { 
    Constant [4]byte
    PRG_ROM_SIZE uint8
    CHR_ROM_SIZE uint8
    Flags6 uint8
    Flags7 uint8
    PRG_RAM_SIZE uint8
    Flags9 uint8
    Flags10 uint8
    _ [5]byte
}

 

 

これでNESファイルの構造(Header)が簡単にわかるようになりました. 

最初の16bytesを読み込んでHeader構造体に保持してもらいましょう.

 

こうすればプログラムROMのサイズが知りたいときには header.PRG_ROM_SIZE, キャラクターROMのサイズが知りたいときには header.CHR_ROM_SIZE とすることができます. 

 

 

 ヘッダー読み込み用の関数を定義しました.

func readHeader(file *os.File) *Header {
    header := Header{}

    data := readNextBytes(file, int(unsafe.Sizeof(header)))
    buffer := bytes.NewBuffer(data)
    err := binary.Read(buffer, binary.BigEndian, &header)
    if err != nil {
       fmt.Println("binary.Read failed", err)
    }

    return &header
}

readNextBytes()というのが, バイナリを指定したバイト数読み込む関数です.

 

それと各値を確認するための関数も定義しました.

func showHeader(header *Header) {
    fmt.Printf("Constant : %x\n", header.Constant)
    fmt.Printf("PRG ROM SIZE : %x\n", header.PRG_ROM_SIZE)
    fmt.Printf("CHR ROM SIZE : %x\n", header.CHR_ROM_SIZE)
    fmt.Printf("Flags6 : %x\n", header.Flags6)
    fmt.Printf("Flags7 : %x\n", header.Flags7)
    fmt.Printf("PRG RAM SIZE : %x\n", header.PRG_RAM_SIZE)
    fmt.Printf("Flags9 : %x\n", header.Flags9)
    fmt.Printf("Flags10 : %x\n", header.Flags10)

}

 

最後にmain()の実行部分だけを示します.

func main() {
    argc := len(os.Args)
    if argc < 2 {
       fmt.Println("input file name.")
       return
    }

    filename := os.Args[1]
    f, err := os.Open(filename)
    if err != nil {
       fmt.Println("Err : ", err)
    return
    }
    defer f.Close()

    header := readHeader(f)
    showHeader(header)

 

     // 

}

 

実行してみましょう.

## 実行結果 ##

 

Constant : 4e45531a
PRG ROM SIZE : 2
CHR ROM SIZE : 1
Flags6 : 1
Flags7 : 0
PRG RAM SIZE : 0
Flags9 : 0
Flags10 : 0

ちゃんと読み込めてるっぽいですね. ヨシッ

 

次はNESのファイルフォーマットの話と, プログラム部分/スプライト部分の切り分け + なにか進捗があれば.. という予定です. 

 

明日は1限から昼まで講義なので早めに寝ます.

 

Golangでファミコンエミュを作る 0日目

これは前日譚的なやつなので読まなくていいです.

 

 

はじめるきっかけ

yuiki.hatenablog.jp

ゆいきさんがやってたのをみました. かっこいい. 

ので僕もやろうと思いました.  

 

使う言語はタイトルにもあるようにGolangです.

そろそろPython以外の言語にも触れようかなと思ったのと, Golangはアドレスも触れるよ!!というのを聞いたことがあったので採用しました.

"Golangエミュレータ自体も, マジで何もわからん" の状態から始まるのでちょっとずつ勉強しながら作っていきます. 

30日OS本みたいな感じで初学者向け(?)に解説していく予定です.

 

 

まずはHello Worldの表示までを目標に.

動く物ができるのはいつになるやら.  

 

 

今のところはHello WorldのためのサンプルNESファイルを読み込んでヘッダー部分, プログラム部分, スプライト部分に切り分けるところまで(このあたりは1日目に詳しく書きます).