Giter VIP home page Giter VIP logo

nostalgist's Introduction

Nostalgist.js

nostalgist.js.org badge GitHub badge NPM badge

Nostalgist.js is a JavaScript library that allows you to run emulators of retro consoles, like NES and Sega Genesis, within web browsers.

Tip

Nostalgist.js is for developers. If you are a player and prefer playing games you own in browsers, rather than building something with code, you may want to try another open-source project RetroAssembly.

A Quick Glance

console demo

Website

Checkout nostalgist.js.org for more online examples and documentation.

Features

  • Launch a retro game with RetroArch emulator in a browser

    import { Nostalgist } from 'nostalgist'
    
    await Nostalgist.launch({
      core: 'fceumm',
      rom: 'flappybird.nes',
    })

    Related API: launch

  • Save the state of the game, then load it later

    import { Nostalgist } from 'nostalgist'
    
    const nostalgist = await Nostalgist.nes('flappybird.nes')
    const { state } = await nostalgist.saveState()
    await nostalgist.loadState(state)

    Related APIs: saveState, loadState

  • Customize any RetroArch config before launching

    import { Nostalgist } from 'nostalgist'
    
    const nostalgist = await Nostalgist.launch({
      core: 'fceumm',
      rom: 'flappybird.nes',
      retroarchConfig: {
        rewind_enable: true,
      },
      retroarchCoreConfig: {
        fceumm_turbo_enable: 'Both',
      },
    })

    Related API: launch#retroarchConfig

  • Access low level APIs of Emscripten

    import { Nostalgist } from 'nostalgist'
    
    const rom = 'https://example.com/zelda.sfc'
    const nostalgist = await Nostalgist.snes(rom)
    const FS = nostalgist.getEmscriptenFS()
    FS.readdir('/')

    Related APIs: getEmscriptenModule, getEmscriptenFS

Motivation

Nostalgist.js is built on top of RetroArch Emscripten builds. We love RetroArch to run in browsers because that's portable and convenient. Although there is already an official instance, RetroArch web player, and some third-party ones like webretro, it's still not that easy to launch RetroArch in a browser programmatically.

The purpose of Nostalgist.js is to simplify the process of launching an emulator to play a game, via RetroArch, in browsers. Given a ROM and a core, the game should be launched without any additional configuration.

APIs

Please refer to nostalgist.js.org/apis.

Related

Credits

  • These are the fundamental dependencies of Nostalgist.js.

  • We are using ROMs here for the purpose of demonstration.

    • retrobrews and the authors of the homebrew games.
  • By default, our ROMs and cores are all loaded from this free CDN service.

Showcases

  • RetroAssembly

    A web app with which you can build a custom retro gaming library inside your browser. Game states can be synced with cloud storage services like OneDrive/Google Drive/Dropbox

    It's built on top of Nostalgist.js.

Alternatives

  • EmulatorJS

    Another JavaScript library for emulation inside browsers.

    It's more powerful and more sophisticated. It has many custom cores and has a friendly user interface. It can be used inside mobile browsers with touch support.

    It's not that friendly towards modern frontend development.

Caveat

Caution

  • Nostalgist.js DOES NOT provide any pirated content, like copyrighted ROM files or BIOS files.
  • And it DOES NOT encourage that.

License

MIT

nostalgist's People

Contributors

arianrhodsandlot avatar dagaiguanyu avatar epicnesstwo avatar gauztech avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

nostalgist's Issues

Feature: External/Manual Inputs

Looks like a cool project. One thing I noticed in the instance methods is that there are no manual input methods. Granted, keyboard and gamepad may be enough to control/play the game but a developer might want to have "on screen" controls for things like mobile devices. In addition, a player might want to customize the inputs of a gamepad to their liking or have something like a turbo (when a player doesn't want to mash a button) or toggle (so you don't have to hold down the run button in mario)

If you want to point me in the direction of where the inputs are handled I wouldn't mind taking a look at what I can do

How can i use this?

I want host nostalgist.js on my pc so i can play games from localhost instead of the nostalgist.js website.
I also want to run this on a circuitpython device (it is an raspberry pi pico w).

audiocontext

Hello, I want to change some games to an online version. When synchronizing audio streams, I cannot obtain the audio stream. How can I read the audio stream data in the game?

Access to game's memory

Hey, I could not manage to get access to rom's memory.
I think retroArch provides such feature, could we get this in Nostalgist?

Error when trying to load ROMs from external CORS-enabled server

Hi!
I think that there’s a bug that causes all rom imports from external CORS-enabled domains to fail. It's easy to reproduce this with Archive.org URLs - the Internet Archive allows cross-origin downloads (and they work fine when using EmulatorJS).
I think I can post a public domain / demoscene prod link (Game Boy) for reference :)

either
rom: 'https://archive.org/download/pouet_54175/oh.gb',
or
rom: 'https://archive.org/download/pouet_54175/oh.zip/oh.gb',
(plain http URLs can also be used on archive org)

Failed to launch without user gesture.

This is a repo reproducing this question: https://github.com/daGaiGuanYu/show-me-bugs/tree/nostalgist240207

warn message in console:

The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page.

I don't know whether this can be fixed.
If not, it would be nice to mention it in the documentation.
Developer could show a dialog to tell users "rom download, start now?".
And user's clicking will offer a "user gesture", and this limit are gone.


I'm making a little APP (not completed):

  • load nes games from local and the internet.
  • Installable, and offline playing(not completed), and launching by clicking a nes file.

截屏2024-02-07 15 55 07
截屏2024-02-07 15 54 19

Additional libretro cores compatible with Nostalgist

Hi. There are few more cores that work great with Nostalgist (in addition to the ones you have uploaded to https://github.com/arianrhodsandlot/retroarch-emscripten-build/tree/main/retroarch ):

Atari 400 / 800 / XE / XL series
https://github.com/libretro/libretro-atari800
(If you try to compile this one and encounter any errors let me know, I may have some uncommitted changes)

Atari 2600 / VCS
https://github.com/libretro/stella2014-libretro

Amstrad CPC series
https://github.com/libretro/libretro-crocods

To build the above - I cloned the projects and cd its directory, then
emmake make -f Makefile platform=emscripten
then changed the output filename to libretro_emscripten.bc and copied it to RetroArch directory, then (from this dir)
emmake make -f Makefile.emscripten LIBRETRO=corename -j all
(where corename is e.g. stella2014, atari800, crocods)

Apart from that, the EmulatorJS has a working Commodore Amiga core (that I cannot build at the moment due to emsdk issue?):
https://github.com/libretro/libretro-uae

Features: startPaused, getCurrentFrame and onFrame Event

Related to #4, Some useful tools for multiplayer and recording/playback would be related to knowing the frame.

  • { startPaused: true } starting paused would allow a group of players to prepare the games and start at relatively the same time.
  • getCurrentFrame() getting the current frame would be useful for knowing when a player did an action. Good for broadcasting actions to happen in the future or for recording the action to be played later
  • on("frame", ()=>{}) knowing what frame the player is on would be good commanding prepared actions. For my examples, the event happens before the buttons are read.

This might be more work than it's worth but it seems interesting

// Not working Example Multiplayer Client

const controller = new OnscreenController(document.querySelector("#OnscreenController"))

const connection = new WebSocket("/game-room/12345");

const nostalgist = await Nostalgist.launch({
  startPaused: true, // Needed so that all players start around the same time
  core: 'fceumm',
  rom: 'flappybird.nes',
})

await syncWithRoom(connection);
const frameOffset = await getFrameOffset(connection)

var startListener = (event)=>{
  const data = JSON.parse(event.data)
  if(data.action !== "start") return;
  connection.removeEventListener("message", startListener)
  nostalgist.resume();
}
connection.addEventListener("message", startListener)

const ACTIONS_TODO = {};

controller.addEventListener("button", (event)=>{
  const { button, toggleValue } = event.data
  const targetFrame = nostalgist.getFrame() + frameOffset;
  connection.send({
    targetFrame, button, toggleValue
  })
})


// Doesn't have to be event target. just simpler to use it for the example
nostalgist.addEventListener("frame", (event)=>{
  const currentFrame = event.frame;
  const todos = ACTIONS_TODO[currentFrame];
  if(todos === void 0) return;
  delete ACTIONS_TODO[currentFrame]
  todos.forEach((todo)=>{
    nostalgist.press(todo.player, todo.button, todo.toggleValue)
  });
})


connection.addEventListener("message", (event)=>{
  // Handle Input
  const data = JSON.parse(event.data);
  if(data.action !== "input") return;

  const { targetFrame, player, button, toggleValue } = data
  const currentFrame = nostalgist.getFrame()
  if(targetFrame <= currentFrame){
    nostalgist.exit();
    connection.send(JSON.stringify({
      action: "desync"
    }));
    alert("You are out of sync with the room")
    return;
  }
  if(!(targetFrame in ACTIONS_TODO)){
    ACTIONS_TODO[targetFrame] = []
  }
  ACTIONS_TODO[targetFrame].push({ player, button, toggleValue })
})
connection.addEventListener("message", (event)=>{
  const data = JSON.parse(event.data);
  if(data.action !== "desync") return;
  nostalgist.removeController(data.player)
})
// Not working Example Input Recording

const PLAYER = 0;

// Recording

function runRecorder(){
  const controller = new OnscreenController(document.querySelector("#OnscreenController"))

  const nostalgist = await Nostalgist.launch({
    startPaused: true, // Needed so that all players start around the same time
    core: 'fceumm',
    rom: 'flappybird.nes',
  })

  const transcript = [];

  controller.addEventListener("button", (event)=>{
    const { button, toggleValue } = event.data
    transcript.push({ frame: nostalgist.getFrame(), type: "button", button, toggleValue });
    nostalgist.press(PLAYER, button, toggleValue)
  })

  return new Promise((res)=>{
    nostalgist.addEventListener("exit", ()=>{
      transcript.push({ frame: nostalgist.getFrame(), type: "off" })
      res(transcript)
    })
  });
}


// Playback

function playTranscript(transcript){
  var transcriptOffset = 0;
  nostalgist.addEventListener("frame", (event)=>{
    const currentFrame = event.frame;
    for(transcriptOffset; transcriptOffset < transcript.length; transcriptOffset++){
      const action = transcript[transcriptOffset]
      if(action.frame > currentFrame) break;
      switch(action.type){
        case "button": {
          nostalgist.press(PLAYER, action.button, action.toggleValue);
          break;
        }
        case "exit":{
          nostalgist.pause();
          alert("recording finished");
          break;
        }
      }
    }
  })
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.