
Nestur: NES Emulator in Rust - tosh
https://github.com/spieglt/nestur
======
reificator
> _(Warning: this pipe currently takes you to an empty room, it 's not the
> only one, and I don't know why.)_

If you look into speedruns you'll see that pipe behavior is very strange. One
of the top speedruns involves pushing Mario into blocks in order to move the
camera by something like 10 px, so that when you go down a certain pipe it
goes to the warp zone instead of the bonus area.

Might be worth watching some videos about that speedrun and trying to trace
what's going on in the code to allow that. It might get you closer to
understanding your own pipe issue.

Unfortunately I don't have a link offhand, but search something like `perfect
mario speedrun` and you'll probably find it.

~~~
zeta0134
I'll bet you a nickel something screwy is going on with the JMP Indirect
logic. If it helps, Super Mario Bros. for the NES has been disassembled with
_wonderful_ comments and labels:

[https://gist.github.com/1wErt3r/4048722](https://gist.github.com/1wErt3r/4048722)

At some point during every frame, shortly after the NMI routine completes, the
game ends up on this particular line of code:

[https://gist.github.com/1wErt3r/4048722#file-smbdis-
asm-L929](https://gist.github.com/1wErt3r/4048722#file-smbdis-asm-L929)

This calculates an address based on the current game mode, and then jumps
there. When the player performs any action that changes the level, this mode
cycles a few times while the game code blanks the screen, switches gears, and
loads in new data. The indirect jump and branching logic that drive the game's
mode switching are tricky to get right, and if they're wrong, this little
kernel will break in surprising ways.

When I was troubleshooting my branching routines, I had my emulator pause on
SMB's first indirect jump, then found that position in the disassembled code
and followed along. I mean, you _can_ achieve similar results by unit testing
or working to pass all of blargg's tests, but I found it much more fun to
trace the emulator in a live environment, running "production" code.

~~~
spieglt
> I'll bet you a nickel something screwy is going on with the JMP Indirect
> logic.

You've lost a nickel! It was a problem with my signed-offset branch function
where a bad cast was allowing 0x80 (-128) to overflow. It was really fun
trying to troubleshoot by hooking certain memory accesses and subroutines, but
what wound up fixing it was investigating the crash of Blargg's
`instr_test-v5/rom_singles/11-stack.nes`. Thank you for your help!

------
lilyball
pcwalton wrote a NES emulator in Rust 7 years ago as a demonstration of Rust:
[https://github.com/pcwalton/sprocketnes](https://github.com/pcwalton/sprocketnes)

Also, a nitpick:

> _One line of unsafe (std::mem::transmute:: <u8>() -> i8)_

You don't need unsafe for this, you can do that with an `as` cast.

~~~
SlowRobotAhead
Like the that your hobby might be opening Rust code, searching unsafe just for
the thrills :)

Edit: Jesus kids, learn a little chill it was a joke. Someone didn’t like this
so much they went through and downvoted all my other posts they could. Wow...
that’s real serious.

~~~
zellyn
It was mentioned in the README, and stood out to me as surprising (surely you
don't need unsafe to cast from unsigned to signed bytes?)

~~~
slunk
Without knowing enough about Rust to speak authoritatively... u8 can represent
larger integers than i8 (no sign bit). Surely it's _not_ completely safe.

~~~
p4lindromica
`mem::transmute` is roughly equivalent to a `reinterpret_cast` in C++. It
treats the bits of a u8 as an i8.

In the Rust definition of safety (mutable xor shared, no data races, memory
safety, etc), treating the bits of a u8 as an i8 is safe and can be done with
an `as` cast.

~~~
slunk
Thanks for clarifying that for me!

------
ltriant
I recently wrote a NES emulator in Rust aswell. It was one of the most
fulfilling side projects I've done. I did a brief write-up too:
[https://ltriant.github.io/2019/11/22/nes-
emulator.html](https://ltriant.github.io/2019/11/22/nes-emulator.html)

Kudos to getting to this point. Adding support for more mappers is a nice
incremental thing that can be done at your leisure, and once you get save
states implemented, playing games at your own pace is super fun :)

~~~
mkagenius
ADC = A + V + C

SBC = A - V - (1 - C)

SBC = A + (-1 * (V - (1 - C)))

SBC = A + (-V + (1 - C))

SBC = A + -V + 1 + C

I am not able to follow the mathemagic behind the third line. Is it normal
algebra or its in 1's complement?

~~~
ltriant
Wow, yeah, that's a bit off. And incorrect. It should end up being equivalent
to:

SBC = A - V - 1 + C

Thanks. I'll fix this up :)

~~~
mkagenius
> but the - 1 is taken care of because of the fact that ADC and SBC do the
> opposite thing with the carry flag.

Also, this line is bothersome, they do opposite things but that's why you have
already done (1 - C).

Maybe the fact that -V is actually (!V + 1) (in 2's complement) that eats up
the extra -1 is the correct explanation?

So,

    
    
      SBC = A - V - 1 + C
    
      SBC = A + !V + 1 - 1 + C
    
      SBC = A + !V + C

~~~
ltriant
Ah yeah you’re right. I had a brain fart and got my -V and !V confused.

At the time of writing I was sure I had it right too!

I’ll fix that up. Thanks :)

------
zellyn
I noticed that both this and pcwalton's CPU emulation simply perform the
operation, and increment the cycle count.

I'm curious: are the cycle-accurate reads and writes (and false reads and
writes -- see
[https://github.com/zellyn/a2audit/issues/5](https://github.com/zellyn/a2audit/issues/5))
not necessary for NES emulation?

I've never emulated a NES, only an Apple II. The cycle-accurate reads and
writes are only mostly unnecessary, but they can make a difference, as the
discussion in that linked issue shows.

~~~
monocasa
It's equivalent to Apple II. You can get pretty far with the naive solution of
atomically completing the instruction, and incrementing the cycle count
appropriately, but there'll be some issues until you switch to a cycle
accurate state machine with all the warts (false accesses, etc.)

NES actually gets a bit grosser because the PPU runs (IIRC) five cycles for
every three CPU cycles or something like that, and you need to handle that
case. I've seen some emulators that just run it off of a 15x clock, and
sparsely run cycles on the CPU and PPU (but most of the master cycles don't do
any work).

~~~
klodolph
I think the NES PPU:CPU clocks are 3:1 for NTSC and 16:5 for PAL. The exact
speeds are integer ratios of the color subcarrier frequency. The master clock
runs at 6x the color subcarrier frequency, which gives you 12 different hues
in the color palette.

~~~
monocasa
Oh, yep, that's the ratio.

------
sdrothrock
I wonder if the name "Nestur" is a callback to "Nester" from the "Howard &
Nester" comics in Nintendo Power:
[https://hn.iodized.net/main.htm](https://hn.iodized.net/main.htm)

~~~
spieglt
Actually a nod to
[https://adventuretime.fandom.com/wiki/Neptr](https://adventuretime.fandom.com/wiki/Neptr)

~~~
sdrothrock
Ahh... a neat coincidence anyway. :)

Thanks for letting me know!

------
MBlume
I see an SDL dll file in the repo, and on my mac the build fails with `library
not found for -lSDL2` -- is there something special I need to do to get this
to build, or is it Windows-only?

~~~
MBlume
Ah, `brew install sdl2` fixed it

