bfirsh / jsnes

A JavaScript NES emulator.
https://jsnes.org
Apache License 2.0
6.1k stars 825 forks source link

Problems loading .nes files in Node.js #113

Open BenShelton opened 6 years ago

BenShelton commented 6 years ago

I'm trying to load an NES rom using jsnes in a node environment. I'm loading the .nes file by doing:

// ascii encoding as suggested by the test/nes.spec.js file
const file = fs.readFileSync('nes/roms/mario3.nes', { encoding: 'ascii' })
nes.loadROM(file)
nes.frame()

No errors occur, but looking at the frame buffer I get a frame of a black border & grey background over and over each time I run nes.frame(). Something is happening, I still get frames & a framerate but they are all the same, as if it's not loading properly.

Interestingly, loading the same rom in a browser environment works fine. Using nes.toJSON(), saving the output to a .json file and then importing that with the below in node also works:

const save = JSON.parse(fs.readFileSync('nes/mario3save.json'))
nes.fromJSON(save)
// OR
nes.loadROM(save.romData)

Both the above work (with some minor graphical glitches using the first option as covered in #16 )

Is it just the encoding causing a problem? Dropping the ascii encoding option causes an error reading the header (rom.js:84 - data.charCodeAt is not a function) and using other encodings cause similar errors.

bfirsh commented 6 years ago

Hm! Interesting. Honestly, I’ve never tested Node properly so it’d be nice to get this working and documented.

Here is what I did in the tests: https://github.com/bfirsh/jsnes/blob/master/test/nes.spec.js

Slight variation on what you are doing. Does that work or is it the same issue?

BenShelton commented 6 years ago

Yeh the test is where I got the idea for how to load the files, the only difference between that method and mine is sync vs async file loading. It’s strange because it appears to be running and the test will pass, but no frames are generated. I’ll keep looking, the way the browser loads files seems to work fine without specifying conversion to ascii so I’ll look into recreating that kind of loading in Node.

bfirsh commented 6 years ago

Hm! Yes odd. Sounds like the test is broken in that case. It’d be nice to compare some frame output in the test to make sure it’s doing the right thing.

When I have some time I’ll look into this too...

brianushman commented 6 years ago

I'm using Angular 4 which is hosted in node.js and this is what I'm using (mostly taken from @bfirsh web jsnes project)

loadBinary(path, callback) {
    var req = new XMLHttpRequest();
    req.open("GET", path);
    req.overrideMimeType("text/plain; charset=x-user-defined");
    req.addEventListener('load', function() {
      if (req.status === 200) {
        callback(null, this.responseText);
      } else {
        callback(new Error(req.statusText));
      }
    });
    req.onerror = function() {
      callback(new Error(req.statusText));
    };
    req.send();
  }

Hope this is able to help you get a step or two closer to resolving

BenShelton commented 6 years ago

Finally figured it out, you have to specify encoding as binary and then it will be properly loaded. So the proper way to load is:

const file = fs.readFileSync('file/to/rom.nes', { encoding: 'binary' })
nes.loadROM(file)
nes.frame()

The default utf8 encoding loads the rom successfully but then errors on an unknown opcode (at the moment it crashes due to issue #24) Setting ascii encoding loads the rom successfully and frames are run but produces a constant grey screen (some sort of softlock)

I'll raise a pull request to add this info to the Readme to make it clear.

BenShelton commented 6 years ago

Ideally there should be a way to detect if the wrong type of encoding is used, it seems at the moment the ROM loader is very lenient in parsing & checking the file is valid, meaning a variety of encodings can bypass it even though the actual data is invalid.

In the meantime I've added a test to check the output is correct over a few frames, it should help diagnose this in the future.

RossComputerGuy commented 5 years ago

I'm also having this issue but I'm writing an emulator for os.js Code:

    const loadFile = async file => {
        if(intv > -1) clearInterval(intv);
        nes.crashMessage = null;
        const data = await core.make("osjs/vfs").readfile(file);
        if(!data.toString().startsWith("NES")) return core.make("osjs/dialog","alert",{ message: "File is not a valid NES rom!" },(btn, value) => {});
        nes.reset();
        nes.loadROM(Buffer.from(data).toString("binary"));
        intv = setInterval(() => {
            if(nes.cpu.crash) {
                clearInterval(intv);
                intv = -1;
                return;
            }
            nes.frame();
        },100);
    };

I defined stop myself but it caused more issues.