Nostalgist.js

repository·main·Indexed 21 days ago

https://github.com/arianrhodsandlot/nostalgist

A JavaScript library that allows developers to programmatically run RetroArch emulators (such as NES and Sega Genesis) within web browsers using Emscripten builds. It provides methods for launching emulators, configuring global options, and accessing low-level Emscripten features like the virtual file system (FS), the Module object, and the emulator canvas element.

Tokens
24K
Snippets
83
Records
128
Agent score
75%

What's inside nostalgist

  1. How the internal `name` property is inferred

    main

    Every resolvable file is assigned an internal name property used by Nostalgist.js to identify the file. This property is critical when using files as bios in Nostalgist.launch, or when using resolveBios and resolveShader.

    Inference rules:

    • URL/Fetch: The name is inferred from the URL path.
    • File Object: The name is taken from the File.name property.
    • Custom Object: If you provide an object with fileName and fileContent, the fileName property is used as the name.
  2. How Nostalgist.js launches an emulator

    main

    Launching an emulator in Nostalgist.js follows a three-step lifecycle:

    1. Load Emulator and ROM: Nostalgist.js fetches the emulator core (typically an Emscripten build from RetroArch) and the ROM file. By default, it uses cores from the retroarch-emscripten-build repository via jsDelivr. It also supports loading homebrew games from the retrobrews project.
    2. Prepare Virtual File System: The loaded emulator and ROM files are written into a virtual file system.
    3. Launch RetroArch: RetroArch is initialized using the prepared virtual file system and a specified HTML <canvas> element.

    Once running, Nostalgist.js can interact with RetroArch by reading/writing to the virtual file system, calling Emscripten-exported functions, or sending commands via the RetroArch Network Control Interface.

  3. Compare Nostalgist.js with EmulatorJS

    main

    When choosing an emulation library, you might consider EmulatorJS.

    EmulatorJS is a feature-rich JavaScript library for browser-based emulation that includes many custom cores and a user-friendly interface, with strong support for mobile touch browsers.

    Nostalgist.js may be a better choice if you require a library that fits more seamlessly into modern frontend development workflows.

  4. Use resolvable files in Nostalgist.js

    main

    Since v0.12.0, Nostalgist.js supports resolvable files, a flexible way to pass file data (like ROMs, BIOS, or emulator cores) to the library. Instead of requiring a specific type, you can provide various formats which the library will automatically parse and load.

    Supported Formats

    • URL-based: URL strings, URL objects, or Request objects. The library uses fetch to load the content and infers the filename from the URL path.
    • Raw Content: A string representing the actual file content (useful for emulator core JavaScript files).
    • Web API Objects: Response, Blob, ArrayBuffer, or Uint8Array objects.
    • Custom Objects: An object containing fileName and fileContent properties. The fileContent can be any of the other supported formats.
    • Async/Functional: Functions or Promises that return any of the above formats. These are unwrapped recursively (e.g., a function returning a Promise that resolves to a Blob).
    // Examples of different resolvable file formats
    
    // 1. URL string
    'http://www.example.com/roms/contra.nes'
    
    // 2. URL object
    new URL('/roms/contra.nes', 'http://www.example.com')
    
    // 3. Request object
    new Request('http://www.example.com/roms/contra.nes')
    
    // 4. String representing content
    'var Module=typeof Module!=="undefined"?Module:{};...'
    
    // 5. Object with fileName and fileContent
    {
      fileName: 'contra.nes',
      fileContent: 'http://www.example.com/roms/contra.nes'
    }
    
    // 6. Function returning a value
    function () {
      return 'http://www.example.com/roms/contra.nes'
    }
    
    // 7. Promise wrapping a Blob
    (async function() {
      const response = await fetch('http://www.example.com/roms/contra.nes')
      return await response.blob()
    })()
  5. Control multiple players with pressDown()

    main

    To control a player other than player 1, you must ensure that key bindings for that player are configured in the retroarchConfig option during the initial launch. Nostalgist uses these key bindings to simulate the controls.

    Example of launching with player 2 key bindings:

    await Nostalgist.launch({
      retroarchConfig: {
        input_player2_down: 'num3',
        input_player2_left: 'num2',
        input_player2_right: 'num4',
        input_player2_up: 'num1',
      },
      /* ...other options */
    })
  6. Launch the emulator manually using launchEmulator()

    main

    When initializing a Nostalgist instance with the option runEmulatorManually: true, the emulator will not start automatically. You must explicitly call the launchEmulator() method on the instance to start the emulator.

    Use this pattern when you need to perform setup tasks or wait for specific application states before the emulator becomes visible to the user.

    // the emulator will not be launched automatically
    const nostalgist = await Nostalgist.nes({
      rom: 'flappybird.nes',
      runEmulatorManually: true
    })
    
    // the emulator is going to be launched here
    await nostalgist.launchEmulator()
  7. Start the emulator with Nostalgist.start()

    main

    To launch the emulator, use the start() method on the instance returned by Nostalgist.prepare().

    Important: You must always use start() in combination with Nostalgist.prepare(). While prepare() handles the heavy lifting of setting up the core and ROM (which may take time), start() is the lightweight call that actually launches the emulator instance.

    // 1. Prepare the environment (this may take a long time)
    const nostalgist = await Nostalgist.prepare({
      core: 'fceumm',
      rom: 'flappybird.nes',
    })
    
    // 2. Launch the emulator on demand
    const startButton = document.querySelector('.start-button')
    startButton.addEventListener('click', async () => {
      await nostalgist.start()
    })
  8. Control multiple players using retroarchConfig

    main

    To control a player other than player 1, you must define key bindings for that player in the retroarchConfig option during the Nostalgist.launch() call. Nostalgist uses these key bindings to simulate the controls.

    await Nostalgist.launch({
      retroarchConfig: {
        input_player2_down: 'num3',
        input_player2_left: 'num2',
        input_player2_right: 'num4',
        input_player2_up: 'num1',
      },
      /* ...other options */
    })
  9. Use Nostalgist.js via CDN

    main

    You can include Nostalgist.js directly in your HTML using a <script> tag. This makes the Nostalgist global variable available in your code.

    Alternatively, you can use the ESM version via a module script to import Nostalgist explicitly.

    <!-- Standard Script Tag -->
    <script src='https://unpkg.com/nostalgist'></script>
    <script src='https://cdn.jsdelivr.net/npm/nostalgist'></script>
    
    <!-- ESM Module Import -->
    <script type='module'>
      import { Nostalgist } from 'https://esm.run/nostalgist';
      import { Nostalgist } from 'https://cdn.skypack.dev/nostalgist';
    </script>
  10. Use `Nostalgist.prepare` to separate file loading from launching

    main

    Use Nostalgist.prepare to pre-load all required emulator files (core and ROM) before the user interacts with the page. This is useful for avoiding browser autoplay restrictions and ensuring that when the user triggers the game launch (e.g., via a button click), the emulator starts immediately without waiting for network requests.

    To use this pattern:

    1. Call await Nostalgist.prepare(options) to begin fetching files.
    2. Once the promise resolves, you have an emulator instance.
    3. Call await instance.start() inside a user-initiated event listener (like a click event) to launch the game.
    // 1. Pre-load files (this may take time)
    const nostalgist = await Nostalgist.prepare({
      core: 'fceumm',
      rom: 'flappybird.nes',
    })
    
    // 2. Wait for user interaction to launch
    const startButton = document.querySelector('.start-button')
    startButton.addEventListener('click', async () => {
      // 3. The emulator launches instantly because files are already loaded
      await nostalgist.start()
    })