Skip to content

Commit

Permalink
feat: add support for launching with an initial state
Browse files Browse the repository at this point in the history
  • Loading branch information
arianrhodsandlot committed Mar 4, 2024
1 parent b135a67 commit db8c926
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 15 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- add `beforeLaunch` and `onLaunch` parameters to allow us to hook into the launching process
- add `getBrowserFS` method for accessing the corresponding `BFSEmscriptenFS` object of the emulator
- add a new method `sendCommand` for sending commands to RetroArch
- add support for launching with an initial state

## [0.8.1](https://github.com/arianrhodsandlot/nostalgist/compare/v0.8.0...v0.8.1) - 2024-02-24

Expand Down
3 changes: 2 additions & 1 deletion demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ let nostalgist: Nostalgist
let state: Awaited<ReturnType<Nostalgist['saveState']>>

async function nes() {
nostalgist = await Nostalgist.nes('flappybird.nes')
nostalgist = await Nostalgist.nes({ rom: 'flappybird.nes' })
}

async function megadrive() {
Expand Down Expand Up @@ -129,6 +129,7 @@ document.body.addEventListener('click', async function listener({ target }) {
await handler()
target.blur()
}
Object.assign(window, { nostalgist })
})

// @ts-expect-error debug code
Expand Down
40 changes: 30 additions & 10 deletions src/emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ export class Emulator {
return fileName.slice(0, fileName.lastIndexOf('.'))
}

private get stateFileName() {
private get stateFileDirectory() {
const { core } = this.options
const coreFullName = coreInfoMap[core.name].corename
if (!coreFullName) {
throw new Error(`invalid core name: ${core.name}`)
}
return join(raUserdataDir, 'states', coreFullName, `${this.romBaseName}.state`)
return join(raUserdataDir, 'states', coreFullName)
}

private get stateFileName() {
return join(this.stateFileDirectory, `${this.romBaseName}.state`)
}

private get stateThumbnailFileName() {
Expand Down Expand Up @@ -278,7 +282,7 @@ export class Emulator {
private async setupFileSystem() {
const { Module } = this.getEmscripten()
const { FS, PATH, ERRNO_CODES } = Module
const { rom, bios } = this.options
const { rom, bios, state } = this.options

const browserFS = createEmscriptenFS({ FS, PATH, ERRNO_CODES })
this.browserFS = browserFS
Expand All @@ -290,6 +294,9 @@ export class Emulator {
if (bios.length > 0) {
FS.mkdirTree(raSystemDir)
}
if (state) {
FS.mkdirTree(this.stateFileDirectory)
}

// a hack used for waiting for wasm's instantiation.
// it's dirty but it works
Expand All @@ -302,10 +309,21 @@ export class Emulator {
waitTime += 5
}

await Promise.all([
const filePromises: Promise<void>[] = []
filePromises.push(
...rom.map((file) => this.writeBlobToDirectory({ ...file, directory: raContentDir })),
...bios.map((file) => this.writeBlobToDirectory({ ...file, directory: raSystemDir })),
])
)
if (state) {
const statePromise = this.writeBlobToDirectory({
fileName: `${this.romBaseName}.state.auto`,
fileContent: state,
directory: this.stateFileDirectory,
})
filePromises.push(statePromise)
}
await Promise.all(filePromises)

this.checkIsAborted()
}

Expand Down Expand Up @@ -393,11 +411,13 @@ export class Emulator {
private runMain() {
this.checkIsAborted()
const { Module } = this.getEmscripten()
const raArgs: string[] = []
const { rom } = this.options
if (rom.length > 0) {
const [{ fileName }] = rom
raArgs.push(join(raContentDir, fileName))
const { arguments: raArgs = [] } = Module
if (!Module.arguments) {
const { rom } = this.options
if (rom.length > 0) {
const [{ fileName }] = rom
raArgs.push(join(raContentDir, fileName))
}
}

Module.callMain(raArgs)
Expand Down
2 changes: 2 additions & 0 deletions src/nostalgist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ export class Nostalgist {
const {
size = 'auto',
respondToGlobalEvents = true,
state,
waitForInteraction,
signal,
beforeLaunch,
Expand Down Expand Up @@ -485,6 +486,7 @@ export class Nostalgist {
rom,
bios,
shader,
state,
respondToGlobalEvents,
retroarchConfig,
retroarchCoreConfig,
Expand Down
1 change: 1 addition & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isAbsoluteUrl, path } from './utils'
const defaultRetroarchConfig: RetroArchConfig = {
menu_driver: 'rgui',
notification_show_when_menu_is_alive: true,
savestate_auto_load: true,
savestate_thumbnail_enable: true,
stdin_cmd_enable: true,
video_shader_enable: true,
Expand Down
1 change: 1 addition & 0 deletions src/types/emulator-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface EmulatorOptions {
rom: { fileName: string; fileContent: Blob }[]
bios: { fileName: string; fileContent: Blob }[]
shader: { fileName: string; fileContent: Blob }[]
state?: Blob | undefined

respondToGlobalEvents: boolean

Expand Down
2 changes: 2 additions & 0 deletions src/types/nostalgist-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ export interface NostalgistOptions {
*/
bios?: NostalgistOptionsFiles

state?: Blob

respondToGlobalEvents?: boolean

/**
Expand Down
4 changes: 2 additions & 2 deletions src/types/retroarch-config/skeleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export interface RetroArchSkeletonConfig {
* The path is $SRAM_PATH.auto.
* RetroArch will automatically load any savestate with this path on startup if savestate_auto_load is set.
*/
savestate_auto_save: string
savestate_auto_load: string
savestate_auto_save: boolean
savestate_auto_load: boolean

/**
* Path to a libretro implementation.
Expand Down
9 changes: 7 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,22 @@ function isPrimitive(obj: unknown) {
return primitevTypes.includes(type)
}

function isPlainObject(obj: any) {
const { constructor } = obj
return constructor === Object || !constructor
}

function mergeSourceToTarget(target: any, source: any) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
const targetValue = target[key]
const sourceValue = source[key]
if (isPrimitive(sourceValue)) {
if (isPrimitive(sourceValue) || !isPlainObject(sourceValue)) {
target[key] = sourceValue
} else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
target[key] = [...targetValue, ...sourceValue]
} else {
target[key] = isPrimitive(targetValue) ? {} : target[key]
target[key] = isPrimitive(targetValue) || !isPlainObject(targetValue) ? {} : target[key]
merge(target[key], sourceValue)
}
}
Expand Down

0 comments on commit db8c926

Please sign in to comment.