NES emulator ported to roblox luau inspired/based on Mesen2
This is very performant and able to run Kirby Adventure with ~150 FPS ( 13th Gen Intel® Core™ i7-13620H )
also Donkey Kong Classics with ~220 FPS.
But for low-end devices, it still runs pretty smoothly. ( ~30-70 FPS )
Moreover, you should upload your audio files as your own
The audio files you can download in the Sounds folder
Your device should have native code generation to make this run effectively.
This uses EditableImage so you need ID verification for this.
- 2-player support.
- zapper support ( or mouse ).
- Full noise sequences, pulses (with extra ones), triangles for sounds.
- PPU dot-based
- Almost cycle-accurate CPU
- No DMC channels (sorry!)
Supported Mappers (59)
0- NROM1- MMC1 / SxROM2- UxROM (UNROM/UOROM)3- CNROM4- MMC3 / TxROM5- MMC5 / ExROM7- AxROM / AOROM9- MMC2 / PxROM10- MMC4 / FxROM13- CPROM37- MMC3 multicart variant47- MMC3 multicart variant66- GxROM94- UN1ROM105- NES-EVENT (MMC1)118- TxSROM (TKSROM/TLSROM)155- MMC1A variant180- UxROM (inverted)185- CNROM security / CHR-disable variant
19- Namco 163 family76- NAMCOT-344688- Namco 108 variant95- NAMCOT-3425154- NAMCOT-3453206- Namco 108 / Namco 118 / MIMIC-1 / DxROM210- Namco 175 / Namco 340
21- VRC2 / VRC4 family22- VRC2 / VRC4 family23- VRC2 / VRC4 family24- VRC625- VRC2 / VRC4 family26- VRC627- VRC2 / VRC4 family73- VRC375- VRC185- VRC7
67- Sunsoft-368- Sunsoft-469- FME-789- Sunsoft-2 on Sunsoft-3 board93- Sunsoft-2 on Sunsoft-3R board184- Sunsoft-1
30- UNROM 512
11- Color Dreams71- Camerica / Codemasters79- NINA-03 / NINA-06116- Huang / Gouder SOMARI-P163- Nanjing FC-001
You can download the ROMs in the internet and convert it into rbxmx using my website! or testing pregenerated roms in test
--!native
--!optimize 2
--!strict
local UserInputService = game:GetService'UserInputService'
local AssetService = game:GetService'AssetService'
local RunService = game:GetService'RunService'
local GuiService = game:GetService'GuiService'
-- Import the LuauNES module.
local LuauNES = require'@game/ReplicatedStorage/Shared/LuauNES'
local Rom = require'@game/ReplicatedStorage/Roms/DonkeyKong' -- Import your rom too
-- UI element that displays the NES output.
local Display = script.Parent.Display
-- Size is defined by the emulator (typically 256x240 for NES).
local Screen = AssetService:CreateEditableImage{ Size = LuauNES.size }
Display.ImageContent = Content.fromObject(Screen)
-- Cache the WritePixelsBuffer function for faster repeated calls.
local WritePixels = Screen.WritePixelsBuffer
-- Load the NES ROM into the emulator.
LuauNES.load(Rom)
-- Key mapping for player 1.
local keyMapP1 = {
-- Face buttons
[Enum.KeyCode.X] = LuauNES.Buttons.A,
[Enum.KeyCode.Z] = LuauNES.Buttons.B,
-- System buttons
[Enum.KeyCode.LeftShift] = LuauNES.Buttons.Select,
[Enum.KeyCode.Return] = LuauNES.Buttons.Start,
-- Directional pad
[Enum.KeyCode.Up] = LuauNES.Buttons.Up,
[Enum.KeyCode.Down] = LuauNES.Buttons.Down,
[Enum.KeyCode.Left] = LuauNES.Buttons.Left,
[Enum.KeyCode.Right] = LuauNES.Buttons.Right,
}
-- Key mapping for player 2.
local keyMapP2 = {
-- Face buttons
[Enum.KeyCode.J] = LuauNES.Buttons.A,
[Enum.KeyCode.K] = LuauNES.Buttons.B,
-- System buttons
[Enum.KeyCode.RightShift] = LuauNES.Buttons.Select,
[Enum.KeyCode.RightControl] = LuauNES.Buttons.Start,
-- Directional pad
[Enum.KeyCode.W] = LuauNES.Buttons.Up,
[Enum.KeyCode.S] = LuauNES.Buttons.Down,
[Enum.KeyCode.A] = LuauNES.Buttons.Left,
[Enum.KeyCode.D] = LuauNES.Buttons.Right,
}
local function updateZapper()
local pos = UserInputService:GetMouseLocation()
local guiSize = Display.AbsoluteSize
local guiPos = Display.AbsolutePosition + Vector2.new(0, GuiService.TopbarInset.Height)
local x = (pos.X - guiPos.X) / guiSize.X * 256
local y = (pos.Y - guiPos.Y) / guiSize.Y * 240
LuauNES.point(x // 1, y // 1) -- Update zapper position
end
-- Accumulator allows the emulator to step multiple frames if Roblox lags.
local accumulator = 0
-- This is used as the main emulator update loop.
local function onRendered(dt: number)
-- NES runs at ~60.0988 FPS, so we need to accumulate it.
accumulator += dt
while accumulator >= 0.01663926733 do
-- Render a frame from the emulator.
local frame = LuauNES.render()
-- If a frame was produced, write it into the EditableImage.
if frame then
WritePixels(Screen, Vector2.zero, LuauNES.size, frame)
end
-- Remove the processed frame time from the accumulator.
accumulator -= 0.01663926733
end
end
-- Handles press events.
local function Pressed(input: InputObject)
-- Check if the key pressed by player 1
local btn = keyMapP1[input.KeyCode]
if btn then LuauNES.press(btn, 1) end
-- Check if the key pressed by player 2
local btn = keyMapP2[input.KeyCode]
if btn then LuauNES.press(btn, 2) end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
LuauNES.trigger() -- Triggered zapper
end
end
-- Handles release events.
local function Released(input: InputObject)
-- Check if the key released by player 1
local btn = keyMapP1[input.KeyCode]
if btn then LuauNES.release(btn, 1) end
-- Check if the key released by player 2
local btn = keyMapP2[input.KeyCode]
if btn then LuauNES.release(btn, 2) end
end
-- Connect events to the handler.
UserInputService.InputBegan:Connect(Pressed)
UserInputService.InputEnded:Connect(Released)
-- PostSimulation runs once per simulation step,
-- so the emulator work is done before PreRender
-- and does not block rendering as much.
-- it's a good practice to do this way
RunService.PostSimulation:Connect(onRendered)
RunService.PreRender:Connect(updateZapper)