{"id":21981599,"url":"https://github.com/cronokirby/ludus","last_synced_at":"2025-08-28T12:22:52.953Z","repository":{"id":57635348,"uuid":"147112533","full_name":"cronokirby/ludus","owner":"cronokirby","description":"A pluggable NES emulator","archived":false,"fork":false,"pushed_at":"2019-06-08T15:21:58.000Z","size":291,"stargazers_count":31,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-30T05:05:59.266Z","etag":null,"topics":["emulation","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cronokirby.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-02T18:47:39.000Z","updated_at":"2024-03-14T22:48:19.000Z","dependencies_parsed_at":"2022-09-11T21:11:21.371Z","dependency_job_id":null,"html_url":"https://github.com/cronokirby/ludus","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronokirby%2Fludus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronokirby%2Fludus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronokirby%2Fludus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronokirby%2Fludus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cronokirby","download_url":"https://codeload.github.com/cronokirby/ludus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251644842,"owners_count":21620634,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["emulation","rust"],"created_at":"2024-11-29T17:18:35.809Z","updated_at":"2025-04-30T05:06:08.391Z","avatar_url":"https://github.com/cronokirby.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![crates.io](https://img.shields.io/crates/v/ludus.svg)](https://crates.io/crates/ludus)\n[![docs.rs](https://docs.rs/ludus/badge.svg)](https://docs.rs/ludus/)\n\n# ludus\n\n**Ludus** is a crate providing the core logic of an NES emulator. Unlike other\ncrates, **Ludus** is not a standalone application. Instead, **Ludus** is a crate\ndesigned to be easily embedded in an application. For an example of using\n**Ludus** to make a GUI emulator, see\n[ludus-emu](https://github.com/cronokirby/ludus-emu).\n\nThe advantage of being headless is that **Ludus** is easily useable in contexts\noutside of a standalone application. For example, this crate could be used\nto train agents the play NES games, or to generate screenshots from NES games,\nor to generate plots of RAM, etc. By being headless, **Ludus** can be used\nin your own emulator application in whatever way you want.\n\n## Features\n- CPU emulation\n- Video emulation\n- Audio emulation\n- Parsing rom data from `.ines` files.\n- Mappers 0, 1, and 2, so many common games.\n\n## Usage\nLet's first import the main types used in **Ludus**:\n```rust\nuse ludus::*;\n```\n\nThe main emulator type is `Console`. Before we can create a `Console`, we need\na cartridge to play. We can create a `Cart` type by reading an `.ines` file.\n\n```rust\nlet bytes: \u0026[u8] = read_ines_bytes();\nlet cart = Cart::from_bytes(bytes).unwrap();\n```\n\nCreating a cartridge will naturally fail if the ROM data wasn't valid.\n\nOnce we have a cartridge, we can create a console to play this cartridge:\n```rust\nlet console = Console::new(cart, sample_rate);\n```\n\nCreating a console requires a cartridge, as well as a sample rate for the audio\nprocess unit (APU). Normally, if you're using some crate that allows you to play\naudio to a device, you should have access to this sample_rate.\n\nAt any point in time we can reset the console like so:\n```rust\nconsole.reset();\n```\n\nWe can also update the state of the buttons using the `ButtonState` struct:\n```rust\nlet mut buttons = ButtonState::default();\nbuttons.a = true;\nconsole.update_controller(buttons);\n```\n\nNow to actually start doing some emulation, we need to `step` the console forward.\nAnytime we advance emulation however, the APU might generate audio samples, and\nthe PPU might generate video frames. To handle these, we need to provide a device\nthat can handle the audio samples, and a device to handle the video frames.\n\nFor handling audio, we have the `AudioDevice` trait:\n```rust\ntrait AudioDevice {\n    fn push_sample(\u0026mut self, sample: f32)\n}\n```\nA device implementing this trait should be able to receive an audio sample,\nin the range `[-1, 1]` and to audio stuff with that information. The sample rate\npassed to the console determines how often the APU will generate samples and push\nthem to this device.\n\nFor handling video, we have the `VideoDevice` trait:\n```rust\ntrait VideoDevice {\n    fn blit_pixels(\u0026mut self, pixels: \u0026PixelBuffer)\n}\n```\nThis device should be able to receive a frame of pixels, and display that on  \nscreen, or whatever else you might want to do with the video data.\nThe pixelbuffer contains 256x240 ARGB pixels, in row major format.\n\nIf you don't want to handle audio or video, you can simple create an empty struct\nthat does nothing for both traits:\n\n```rust\n#[derive(Clone, Copy)]\npub struct NullDevice;\n\nimpl AudioDevice for NullDevice {\n    fn push_sample(\u0026mut self, sample: f32) {\n    }\n}\n\nimpl VideoDevice for NullDevice {\n    fn blit_pixels(\u0026mut self, pixels: \u0026PixelBuffer) {\n    }\n}\n```\n\nNow that we have the devices set up, we can start doing some emulation.\n\nThe simplest method to advance the console is `step`:\n```rust\npub fn step\u003c'a, A, V\u003e(\u0026'a mut self, audio: \u0026mut A, video: \u0026mut V) -\u003e i32 where\n    A: AudioDevice,\n    V: VideoDevice,\n```\nThis will advance the `Console` forward by one cpu cycle. This is only useful\nif you want to be able to see things advance very very slowly. If you're\nsomething automated, like a bot, you want to use `step_frame` instead, since\nmost games won't even look at input more than once per frame anyways.\n\nThe next method is `step_micros`:\n```rust\npub fn step_micros\u003c'a, A, V\u003e(\n    \u0026'a mut self,\n    audio: \u0026mut A,\n    video: \u0026mut V,\n    micros: u32\n) where\n    A: AudioDevice,\n    V: VideoDevice, \n```\n\nThis method will instead advance the emulator by a certain number of microseconds.\nThis is the most useful method if you're implementing your own GUI and want\nto advance the emulator in some kind of game loop.\n\nAn example of doing such a loop might look like this:\n```rust\nlet mut old = Instant::now();\nloop {\n    let now = Instant::now();\n    let duration = now.duration_since(old);\n    old = now;\n    console.step_micros(audio, video, duration.subsec_micros());\n}\n```\n\nThe final method allows you to advance the emulator by a full frame:\n```rust\npub fn step_frame\u003c'a, A, V\u003e(\u0026'a mut self, audio: \u0026mut A, video: \u0026mut V) where\n    A: AudioDevice,\n    V: VideoDevice,\n```\nThis is useful if you're training a bot, because games will only look at input\nonce per frame. So you'd set input for that frame, then advance once frame, then\nset input, etc. Note that this is not based on *timing* like the other methods,\nbut by waiting for the ppu to reach the end of the current frame.\n\n## Resources\n\nI relied heavily on this very nicely written open source emulator: https://github.com/fogleman/nes.\n\nThis page https://wiki.nesdev.com/w/index.php/NES_reference_guide was and still is my bible as I work on this project; kudos to the many people who've contributed in some way over the years.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcronokirby%2Fludus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcronokirby%2Fludus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcronokirby%2Fludus/lists"}