Saltar al contenido
Nelkano Emulador multisistema

SDK de Nelkano

Emulación: operaciones y eventos

Usa estas operaciones para pausar, guardar, cambiar la presentación o crear herramientas sobre una partida. Android y escritorio trabajan con el juego abierto; Studio utiliza datos simulados.

Consultar la partida abierta

const state = await host.emulation.snapshot(); if (!state) return; // No hay partida abierta. const session = state.session;

Declara emulation.read. La respuesta incluye gameId, systemId, paused, capabilities, slots, canSave y display. En display encontrarás fullscreen, stretch, screenMode y selectedSlot. Consulta esas capacidades antes de ofrecer una operación.

Pausar, guardar y cambiar la pantalla

await host.emulation.setPaused(session, true); // Pausar. await host.emulation.setPaused(session, false); // Reanudar. await host.emulation.reset(session); await host.emulation.setFullscreen(session, true); await host.emulation.setStretch(session, true); await host.emulation.setScreenMode(session, 1); await host.emulation.selectSlot(session, 2); await host.saves.save(session, 2); await host.saves.load(session, 2);
  • emulation.control: pausar, reanudar y reiniciar.
  • presentation.player: pantalla completa, estirar, pantalla seleccionada y slot.
  • saves.write: guardar un estado.
  • saves.load: cargar un estado existente.

screenMode admite 0 (Ambas), 1 (Superior) y 2 (Inferior), en motores con DUAL_SCREEN.

Los slots manuales van del 0 al 5. El 6 es el autoguardado: puedes cargarlo, pero saves.save no permite escribir en él. Comprueba canSave y capabilities; algunos motores no admiten estados.

SNES, PSP y GameCube utilizan estas mismas operaciones. Para cargar necesitas el mismo juego y un formato de estado compatible. Las pruebas de Switch con Dynarmic pertenecen al core de desarrollo PC; Switch está excluido de las apps Android y Desktop. Los perfiles y botones del editor no habilitan un core ni sus estados.

Abrir o continuar un juego

await host.library.launch(gameId); // Abre el juego sin cargar un estado de Nelkano. await host.library.launch(gameId, 6); // Desde el autoguardado.

Requiere library.launch. El gameId debe existir en la biblioteca y, si indicas un slot, ese estado debe estar guardado. Sin slot se abre el juego sin cargar un estado de Nelkano; el progreso interno se recupera desde los menús del juego. No se trabaja con rutas de ROM.

Ficha del juego y estados guardados (ejemplo).
Ficha del juego y estados guardados (ejemplo).

Añadir un botón de pausa

Declara emulation.read y emulation.control. Publica el botón en contributions y responde a su pulsación en dispatch:

async contributions(context) { if (context.surface !== 'emulation' || !context.session) return []; return [{ moduleId: 'example.pause', id: 'pause', slot: 'PLAYER_SYSTEM_BUTTON', label: 'Pausa', actionId: 'pause', icon: 'assets/pause.png', payload: { controlId: 'sys-pause' } }]; }, async dispatch(action, payload, context) { if (action !== 'pause') return null; const state = await host.emulation.snapshot(); if (!state || state.session.id !== context.session?.id || state.session.generation !== context.session?.generation) { throw new Error('Esta partida ya terminó.'); } await host.emulation.setPaused(context.session, !state.paused); return null; }

Incluye el PNG en files. controlId reutiliza sys-pause y la posición elegida en el Editor. Si lo omites, creas otro botón. Evita que varios módulos publiquen simultáneamente el mismo control.

Al guardar se captura la imagen del motor, sin los controles. En NDS y 3DS, las miniaturas muestran la pantalla superior aunque estés viendo otra. Los estados anteriores sin captura la obtienen al volver a guardarse.

Solicitar autoguardados

await host.saves.setAutoSave(true); await host.saves.setAutoSave(false);

Requiere saves.autosave. La app sobrescribe el slot 6 cada 60 segundos de juego sin pausa, cuando el motor lo permite. Los slots manuales se conservan.

Cada módulo tiene su propia solicitud: basta una activa para mantener el autoguardado. Al desactivar o desinstalar el módulo se retira la suya. Guarda tu preferencia y aplícala de nuevo en activate. El intervalo, el destino y la cantidad de copias no son configurables desde esta API.

Responder a los cambios de la partida

Con emulation.read, onEvent recibe session-started, session-paused, session-resumed, session-closed, session-reset, save-created, save-loaded y display-changed.

async onEvent(event) { if (event.type === 'save-created') { console.log('Guardado', event.slot, event.automatic); } return null; }

Los avisos incluyen session y gameId. Los de guardado añaden slot y automatic, que es true para el autoguardado. Consulta snapshot si necesitas el estado actualizado. Estos eventos no se emiten por fotograma.

Manejar sesiones y errores

Utiliza la sesión recibida en la acción. La app comprueba su id y generation y rechaza referencias de una partida anterior. Captura los errores para explicar al usuario qué operación falló.

Studio permite probar las llamadas y miniaturas simuladas. Para comprobar que un estado recupera el juego correctamente, pruébalo en la app de destino. library.setCover puede utilizar su captura como portada.

Leer memoria y recursos con gameRuntime

const active = await host.emulation.snapshot(); if (!active || active.systemId !== "gba") return; const result = await host.gameRuntime.read(active.session, { ranges: [{address: 0x02000000, length: 16}] }); // result.ranges[0].data contiene los bytes en Base64.

host.gameRuntime.read() admite hasta 32 rangos y 64 KiB por petición; cada rango puede tener hasta 32 KiB. patch() recibe writes con address, data y expected. Solo funciona en adaptadores con escritura y compara los bytes esperados antes de aplicar el cambio.

Los permisos de estas operaciones son:

  • read: emulation.memory.read.
  • patch: emulation.memory.write.
  • rom, romSearch y frame: emulation.resources.
  • Checkpoints y proyectos: emulation.handoff.
  • subscribe: runtime.events.
  • overlay: presentation.overlay.

ROM, imagen y avisos periódicos

  • rom: lee {offset, length}.
  • romSearch: busca {pattern, offset?, end?, alignment?, maximum?}; pattern es Base64.
  • frame: recibe {width, height} y devuelve {width, height, argb}. Es una imagen CPU reducida, ARGB de 32 bits en little endian y Base64, de hasta 160 píxeles por eje y 16 384 píxeles en total. No exporta a la galería.
  • subscribe(session, true): activa runtime-tick, por defecto cada 250 ms. Puedes pedir {intervalMs:100}; el host lo limita a 100–1000 ms. Una operación larga puede retrasar el aviso. Usa subscribe(session, false) para retirarlo.

Superponer imágenes y etiquetas

overlay(session, nodes) publica hasta 32 PNG y etiquetas en coordenadas del frame. Respeta los límites de imagen anunciados. Una lista vacía limpia la capa; cerrar la sesión o desactivar el módulo también la retira.

Crear un checkpoint privado

checkpoint conserva un estado privado del módulo y su autor. checkpointRead lo lee por bloques; checkpointPatch crea otro comprobando expected y las regiones RAM del motor. restore exige el mismo juego. No aparece entre los slots manuales.

Trabajar con una copia de RPG Maker

projectWrite y projectRead utilizan una copia privada del proyecto. Admiten Save/, Graphics/Characters/ y Graphics/Trainers/, con bloques de hasta 32 KiB y archivos de hasta 2 MiB. projectLaunch abre esa copia.

invoke llama a un método sin argumentos de Game o de una constante RGSS terminada en Bridge, mediante {receiver, method}. El juego debe ofrecer ese puente. external configura el transporte de mensajes RGSS. Estas operaciones requieren emulation.handoff.

Save/ es una ruta lógica de la copia privada. El host admite los archivos RGSS relativos a la raíz o a Save/ y lee la copia más reciente. El proyecto importado original se conserva.

Antes de invocar funciones de una copia privada, consulta projectActive(session, {gameId}). Su respuesta {active} confirma si esa partida está usando la copia de tu módulo, sin revelar su ruta.

Hacer que un sprite siga la cámara

Añade id y camera al nodo de overlay. Con camera, x e y son coordenadas del mundo; x e y dentro de camera indican el origen de cámara observado al preparar el nodo. El host compara ese origen con los registros del fotograma y ajusta el dibujo al desplazarse el mapa.

await host.gameRuntime.overlay(session, [{ id: "jugador-2", x: 620, y: 180, width: 16, height: 32, image: pngBase64, label: "Mi amigo", camera: {xAddress: 0x04000014, yAddress: 0x04000016, x: 500, y: 100, modulus: 512} }]);

Requiere emulation.memory.read y presentation.overlay. Se admiten los registros GBA alineados de 16 bits entre 0x04000010 y 0x0400001e, y los registros GB/GBC SCY/SCX en 0xff42/0xff43. Utiliza los registros que entregue el adaptador: si faltan en el frame, el nodo se oculta.

modulus debe ser una potencia de dos entre 2 y 65536. Las coordenadas de cámara se limitan a ±65536. Sin camera, el nodo usa coordenadas de pantalla. Un id estable conserva su identidad y suaviza los desplazamientos remotos entre posiciones.

Comprobar si se puede pausar

Durante una conexión online en espera, activa o recuperándose, la partida debe avanzar: el host oculta Pausa y rechaza la pausa hasta desconectar. Studio reproduce esta regla con su conexión ficticia.

Consulta pauseAllowed en host.emulation.snapshot(). Si es false, setPaused(session, true) devuelve false y la partida continúa. Si falta el campo en un host anterior, trátalo como permitido. Reanudar sigue disponible.

Modificar el comportamiento del juego

Utiliza host.gameMod para los espacios de memoria, escrituras por lotes y reglas. GBA ofrece memoria y ROM temporal; Studio las simula. host.gameRuntime conserva sus operaciones específicas. El capítulo Modificar un juego explica capacidades, permisos, conflictos y retirada del módulo.

RPG Maker XP/RGSS y MV/MZ usan los guardados internos del juego y no admiten guardar, cargar ni autoguardar estados de emulación. Los estados anteriores se conservan como archivos, pero no aparecen en Guardados o Continuar.

Transferir juegos y progreso

host.transfers intercambia contenido entre Android y escritorio. Declara library.transfer para juegos y saves.transfer para progreso. Cada permiso permite exportar e importar su categoría. Implementa la conexión y la interfaz de usuario; el host valida los datos y los incorpora a la biblioteca.

Exportar

  1. Consulta capabilities() para obtener disponibilidad y cuotas.
  2. Llama a inspect({gameId}). Recibes contentFingerprint, savesRevision, saveKinds, plataforma y limitaciones.
  3. Crea una copia estable con exportGame({gameId}) o exportSaves({gameId}). La respuesta es {id,format,kind,gameId,title,systemId,size,sha256}.
  4. Lee bloques con read({id,offset,length}), que devuelve dataBase64.
  5. Libera la exportación con close({id}).

Importar

  1. beginImport({kind,gameId,size,sha256}) crea una recepción privada.
  2. Envía bloques consecutivos con write({id,offset,dataBase64}) y comprueba el total recibido.
  3. commit({id,expectedRevision?}) verifica tamaño, SHA-256, manifiesto y hashes antes de publicar el contenido.

La respuesta incluye la identidad, importedGame, importedSaves (número de slots) e importedSaveFiles (total de archivos de progreso). El éxito consume la recepción. Si falla, puedes reintentar o descartarla con abort({id}). close y abort devuelven false si el identificador ya no existe o no es tuyo.

Reemplazar progreso existente

Obtén la revisión en el dispositivo de destino con inspect y envíala como expectedRevision. El host comprueba que no haya cambiado mientras preparabas la transferencia. Sin revisión solo se importa progreso si aún no existe.

Se reemplaza el conjunto completo: también se eliminan los archivos de progreso ausentes del paquete. Explica la sustitución al usuario y resuelve los conflictos. Si ya existe el juego con contenido diferente, se rechaza la importación sin sobrescribirlo.

Qué contiene una transferencia

nelkano-transfer/1 es un ZIP de datos con manifest.json y data/. Es independiente del paquete ejecutable .nelkmod. Conserva el gameId original y verifica la identidad del sistema, la entrada y el índice de archivos con tamaños y hashes.

Los juegos incluyen ROM, disco y pistas o proyecto completo, también sus directorios vacíos. El progreso incluye slots 0–6, capturas, batería/SRAM, carátula, archivos nativos por juego y Save de RGSS. Conserva los requisitos de módulos y autores de los guardados. Los datos privados de otros módulos quedan fuera.

Cerrar la partida y comprobar compatibilidad

Inspeccionar, preparar una exportación y publicar una importación requieren la sesión cerrada. Puedes recibir bloques o leer una exportación ya preparada mientras se usa la app. Tras una interrupción, el host completa o revierte la publicación antes de abrir otra partida.

La transferencia conserva bytes; no convierte estados entre motores. El destino necesita un formato compatible y algunos guardados internos dependen de la plataforma o versión. Consulta la inspección y el manifiesto. Los guardados MV/MZ de WebView quedan fuera. Los paquetes sintéticos de Studio solo se importan en Studio.

Límites de transferencia

  • Bloques de hasta 65 536 bytes.
  • Hasta 64 GiB de archivo y contenido expandido; 100 000 archivos y manifiesto de hasta 32 MiB.
  • Dos operaciones abiertas por módulo y ocho globales; caducan tras diez minutos sin actividad.
  • Se rechazan rutas inseguras, enlaces, duplicados por mayúsculas, miembros extra y tamaños o hashes incorrectos.

El espacio y la velocidad dependen del dispositivo. Las transferencias largas tienen su propio límite de comunicación entre procesos; no se sujetan al de cinco segundos del código JavaScript.

Capturar una imagen o grabar un clip

  1. Consulta host.media.capabilities() para saber si la sesión entrega imagen, audio y captura CPU o de superficie.
  2. Usa capture({session,screen?,region?,format?}) para capturar en PNG o JPEG, con resolución completa o recorte.
  3. Lee el resultado por bloques de hasta 64 KiB con read({id,offset,length}).
  4. Libéralo con release({id}).

startRecording({session,fps?,maxSeconds?}), recording({id}) y stopRecording({id}) graban un AVI con MJPEG y PCM si el motor entrega audio. Los límites son 30 fps, 300 segundos y 1 GiB. export({id}) abre el selector de documentos para guardarlo y remove({id}) lo descarta.

Se graba el juego, sin escritorio, micrófono ni cámara. Pasar al fondo termina la grabación; revocar el módulo elimina sus recursos. Cada resultado conserva el permiso y la generación de sesión con los que se creó.

Ajustar las etiquetas superpuestas

runtime.overlay admite textos de hasta 4096 caracteres. Puedes indicar textWidth en píxeles del juego, fontSize de 8 a 48 sp, maxLines de 1 a 16 y textColor/backgroundColor con formato #AARRGGBB. Omitir estos campos conserva la presentación anterior.

Analizar y traducir texto de un juego

  1. Consulta host.translation.capabilities() para conocer los formatos y proveedores.
  2. projects() recupera los proyectos de tu módulo, también tras reiniciar la app.
  3. Inicia el análisis con analyze({gameId}). Usa job({jobId}) para seguirlo o cancel({jobId}) para cancelarlo.
  4. Lee las entradas por páginas con entries({projectId,offset?,limit?}) y guarda ediciones con update({projectId,resourceId,textId,translation,expectedRevision}).

translate({projectId,provider,sourceLanguage,targetLanguage,limit?}) prepara un borrador automático. También admite traducir {text,provider,sourceLanguage,targetLanguage,context?} sin crear un proyecto. Con la partida cerrada, build({projectId,title?,allowPartial?}) reconstruye y registra una copia independiente del juego. remove({projectId}) elimina el proyecto privado.

Se admiten los formatos de texto reconocidos de GBA/NDS. Android utiliza ML Kit y descarga los modelos al solicitarlos. Escritorio permite analizar, editar y reconstruir; puedes incorporar traducciones de un servicio externo mediante update, pero no incluye traducción automática local.

Esta API no incluye OCR. Comprueba el espacio disponible, las fuentes y la maquetación en el juego reconstruido.

Aplicar un shader a la imagen

Declara graphics.shaders y consulta host.graphics.capabilities({session}). shader({session,id,source,uniforms?}) aplica el efecto y devuelve {id,revision}; removeShader({session,id}) devuelve {removed} al retirarlo.

La fuente SkSL/AGSL utiliza uniform shader content, uniform float2 resolution y half4 main(float2 p). Los uniforms adicionales deben ser floats finitos. Se admite un efecto y propietario por sesión, hasta 16 KiB de fuente y 16 uniforms adicionales, sin bucles for, while o do.

El shader solo filtra los píxeles del juego. En Android requiere API 33 y una salida CPU; en escritorio utiliza Skia. Las superficies nativas separadas no ofrecen ese postprocesamiento. Las capturas conservan la imagen original del motor. Cerrar la sesión o revocar el módulo retira el efecto.

Cambiar resolución y texturas

Resolución interna

Con graphics.options, llama a host.graphics.options({session,resolutionScale}); devuelve {resolutionScale}. GameCube admite 1×, 2× y 4× en Android y escritorio. Se vuelven a dibujar las primitivas, conservando la memoria y los estados que utiliza el juego.

La superficie máxima es 2560×2112, con hasta dos copias visuales escaladas de 720×576. Otros tamaños mantienen su salida nativa. La imagen de mayor resolución solo se muestra si coincide con el framebuffer que presenta el juego y la CPU no lo ha modificado.

Activar escala o reemplazos en GameCube desactiva Vulkan y utiliza el rasterizador software. Al retirarlos puede recuperar la selección anterior. Prueba el rendimiento y la imagen en cada juego.

Identificar texturas

graphics.textures permite reemplazos PNG en GameCube, PS1 y PSP. Se conservan la VRAM y las lecturas originales del juego. textureIds({session,offset?,limit?}) devuelve {items:[{textureId,width,height}],total,offset}. La observación empieza al consultar y conserva hasta 512 identidades SHA-256 basadas en formato, dimensiones, datos y paleta.

  • PS1: identifica atlas de 256×256 con profundidad y CLUT. El reemplazo utiliza un pase visual RGB555 a resolución nativa, conservando máscaras y semitransparencia. El display de 24 bits y las primitivas que solapan su página o paleta utilizan la ruta original. Las escrituras del juego invalidan el reemplazo visual, incluso si repiten el valor; las copias de framebuffer no lo transportan.
  • PSP: utiliza un pase software separado, con el nivel base del reemplazo. Mientras está activo deshabilita las rutas GPU, SIMD y paralelas.

PS1 y PSP anuncian resolución 1×: un reemplazo de textura no aumenta la resolución de la geometría.

Cargar un reemplazo

  1. Prepara un PNG con dimensiones que sean múltiplos enteros uniformes de la textura observada.
  2. Inicia la carga con beginTexture({session,textureId,width,height,size,sha256}).
  3. Envía bloques consecutivos con writeTexture({id,offset,dataBase64}).
  4. Termina con commitTexture({id}); el host verifica hash y dimensiones.

textures({session}) lista tus reemplazos y removeTexture({session,textureId}) los retira. Límites: 2048×2048, 16 MiB por PNG, bloques de 64 KiB, 64 reemplazos y 128 MiB de píxeles en el banco del motor. Las cargas caducan a los diez minutos. Otro módulo no puede sustituir un recurso activo y no se genera una pirámide mip HD. Revocar el módulo o cerrar la sesión retira los recursos.

Capturar el micrófono

Declara media.microphone. Consulta host.microphone.capabilities(), inicia con start({sampleRate?,blockMs?,maxSeconds?}), recoge bloques con read({id,maxBlocks?}) y termina con stop({id}). Android solicita RECORD_AUDIO; escritorio utiliza los permisos de audio del sistema.

El audio es PCM16LE mono, a 16/24/48 kHz, con bloques de 20/40/100 ms en Base64 y número de secuencia. Se admite una captura activa y hasta cinco minutos. Lee a tiempo: droppedBlocks indica los bloques perdidos.

Pasar al fondo, revocar el permiso o cerrar cancela la captura y las lecturas pendientes. El reconocimiento de voz debe implementarlo tu módulo.

Filtrar el audio del juego

Declara audio.dsp y consulta host.audioDsp.capabilities({session}). list({session}) devuelve tus cadenas de filtros. set({session,id,expectedRevision,sampleRate,stages}) crea o actualiza una cadena comprobando expectedRevision.

Las etapas admitidas son {kind:'gain',gain}, {kind:'biquad',b0,b1,b2,a1,a2} y {kind:'fir',coefficients}. La respuesta indica si se aplicó el cambio, su revisión y la cadena resultante. remove({session,id,expectedRevision}) la retira. El host comprueba valores finitos, estabilidad, coste y saturación PCM16.

Se admiten cuatro cadenas por propietario, dieciséis globales y filtros FIR de hasta 64 coeficientes. Se aplican por propietario e ID. El historial del filtro se reinicia al pausar, cambiar de generación o modificar la configuración; una frecuencia distinta de la declarada omite la cadena.

Solo se filtra el PCM del motor antes de reproducirlo. Los sonidos por órdenes, películas, WebAudio y beep quedan fuera. Grabaciones y observadores reciben el PCM original.

Rebobinar y editar repeticiones

Declara emulation.timeline para utilizar host.timeline. Consulta capabilities({session}): el motor debe ofrecer un estado completo, avance exacto por fotograma y ausencia de efectos externos. La integración inicial es CHIP-8. Un único propietario controla el historial y las repeticiones de la sesión.

Configurar el historial y avanzar

configure({session,historyFrames?,historyBytes?,runaheadFrames?,rewinding?}) configura el historial y status({session}) consulta su posición, pausa y actividad. rewind({session,frames}) retrocede y pausa; rewinding:true mantiene el rebobinado. Runahead admite 0–2 fotogramas especulativos: presenta vídeo adelantado, mantiene el audio confirmado y restaura el estado confirmado.

step({session,frames?,inputs?,commit?}) necesita la partida pausada y admite hasta 120 fotogramas. Cada entrada contiene keys y, opcionalmente, touch. Sin entradas se usa una neutra; una entrada única se repite. commit:false restaura el estado al terminar, sin publicar imagen o audio especulativos. Si esa restauración falla, la sesión queda no disponible.

Grabar y reproducir entradas

startRecording({session}) inicia una grabación, stopRecording({session}) la termina y recording({session,id?,offset?,limit?}) permite leerla por páginas. Incluye las entradas efectivas físicas y de módulos y, cuando está disponible, el hash del estado.

replay({session,id,fromFrame?,toFrame?}) reproduce las entradas y pausa al terminar. seek({session,id?,frame}) reconstruye una posición sin mostrar resultados intermedios. Se registra por fotogramas, sin tiempos inferiores a uno. Reiniciar, cargar un estado externo, cerrar o revocar termina el controlador. release({session}) libera sus datos.

Exportar y editar una repetición

export({session,id?}) devuelve {id,format:'nelkano-replay/1',size,sha256}. Lee los bloques Base64 con read({id,offset,length}). Para importar, utiliza beginImport({session,size,sha256}), write({id,offset,dataBase64}) y commit({session,id}); abort({id}) cancela.

El JSON editable contiene formatVersion:1, coreFormat, romSha256, firstFrame, un estado inicial opaco y las entradas por fotograma. En un editor TAS puedes modificar entradas y retirar los stateSha256 posteriores que hayan dejado de ser válidos. El host verifica ROM, formato, hash y estado inicial antes de restaurar.

Límites: 3600 estados y 64 MiB de historial; dos grabaciones privadas de hasta 36 000 fotogramas y 8 MiB cada una. Una repetición exportada admite 16 MiB y bloques de 64 KiB. Hay dos operaciones abiertas por módulo y ocho globales, con caducidad tras diez minutos sin actividad. La duración del historial depende del tamaño de los estados y del rendimiento.

Mezclar audio externo con la partida

Declara audio.mix para utilizar host.audioMix. Consulta capabilities({session}) para conocer el formato PCM16LE, la frecuencia, los canales y el reloj {frame,epoch,paused}. El frame es la referencia publicada por el host, no necesariamente el VBlank físico del motor. El audio se mezcla antes de los filtros DSP y la reproducción.

  1. Prepara el clip con begin({session,size,sha256,sampleRate,channels}), write({id,offset,dataBase64}) y commit({id}). Su PCM16LE mono o estéreo debe tener la misma frecuencia y canales que el motor; no hay remuestreo automático.
  2. Programa la reproducción con play({session,id,atFrame?,gain?,originalGain?}). Devuelve playbackId y la frontera elegida. Puedes mezclarlo con el original o silenciar este durante el clip.
  3. Consulta status({session}) o detén las reproducciones con stop({session,playbackId?}).
  4. remove({id}) descarta una carga o clip y sus reproducciones.

Se admiten 16 MiB por clip, 32 MiB por propietario, 64 MiB globales, ocho reproducciones programadas y bloques de 64 KiB. Los recursos pertenecen a la sesión y caducan tras diez minutos sin actividad.

Pausar, restaurar, cambiar de generación o formato, pasar al fondo o revocar cancela las reproducciones. Capturas, grabaciones y observadores conservan el PCM original. No permite separar voces, música o efectos que el motor no entregue como pistas distintas; sonidos por órdenes, películas, WebAudio y beep quedan fuera.

Capturar una imagen de cámara

Declara media.camera y utiliza host.camera.capabilities(), list(), start, read y stop. Android solicita CAMERA; escritorio anuncia captura no disponible y Studio utiliza una imagen sintética.

  1. Solicita una cámara anunciada con start({cameraId,width?,height?,fps?,format?}). El valor predeterminado es JPEG a 640×480 y 5 fps; se admiten JPEG/PNG hasta 1280×720 y 15 fps. Comprueba el tamaño real devuelto.
  2. read({id}) fija una instantánea y devuelve un frame o null.
  3. Lee los bytes con read({id,frameId,offset,length}) en bloques de hasta 64 KiB, conservando el mismo frameId durante toda la lectura.

Se admite una captura activa, dos instantáneas por captura y hasta 4 MiB por imagen.

Entregar la imagen al juego

Consulta capabilities({session}) para conocer los puertos. deliver({id,session,port}) necesita además emulation.input y envía la última imagen al puerto compatible.

La integración inicial es la cámara emulada de 3DS: cam1 y cam2 convierten a RGB565/YUYV con el tamaño solicitado por el juego. La imagen externa no se guarda dentro de sus estados. NDS normal no anuncia cámara; la óptica, calibración estéreo y sincronización física de 3DS son parciales.

stop({id}), pasar al fondo o revocar el módulo cierra la captura y retira entregas y solicitudes pendientes.

Elegir un motor externo

Consulta host.engines.capabilities(). list({gameId}) o list({systemId}) lista los proveedores, identidad, versión, selección y compatibilidad de guardados.

select({gameId,engineId}) elige el motor de un juego; select({systemId,engineId}), el de un sistema. Indica solo uno de los dos. engineId null recupera el predeterminado. La elección se aplica al volver a abrir: una partida en marcha conserva su motor.

Controles, opciones y recursos

  • controls({session}) describe los puertos y controles. input({session,port,control,value}) envía valores dentro de sus rangos, incluidos ejes y gatillos.
  • options({session}) devuelve el esquema y los valores. configure aplica opciones o guarda las de la próxima apertura por engineId, respetando tipos, rangos y requisitos de reinicio.
  • resources({engineId,gameId?}) describe los archivos o BIOS necesarios. importResource abre el selector del host sin revelar rutas privadas.
  • importGame({engineId,systemId}) abre el selector y vincula el juego importado al motor. Requiere library.transfer y resources.documents. Indicar systemId resuelve extensiones ambiguas, como ISO.
  • media({session}) y changeMedia({session,slot,contentId?}) consultan y cambian los discos o medios anunciados.

Cada operación exige sus permisos de lectura, control, entrada o recursos.

Guardar el progreso de un motor propio

Los estados manuales admiten hasta 128 MiB y viajan en bloques de 64 KiB. El puerto timeline tiene un límite independiente de 16 MiB. Los estados y el progreso persistente utilizan identificadores de compatibilidad distintos y un espacio de almacenamiento asociado al motor.

Coincidir en el juego no permite cargar el estado de otro motor. Adapta la VFS del core para utilizar los recursos autorizados y el almacenamiento privado del host: escribir solo en su carpeta temporal no conserva el progreso.

Implementar las capacidades de tu motor

La ABI C versionada comprueba que el descriptor corresponda al binario compilado. Memoria, reglas, observación, texturas, resolución, cámara, medios y timeline deben declararse e implementarse con sus callbacks. Si faltan, esas capacidades permanecen no disponibles.

El puente opcional utiliza métodos JSON acotados y bloques para estados e imágenes. Poder ejecutar una ROM o guardar estados no implica admitir escritura de memoria o rebobinado determinista. El ejemplo de GPU no anuncia esos puertos.

El motor entrega PCM al transporte de audio y puede utilizar superficies GPU separadas. Las capturas compuestas requieren su implementación en el adaptador. Consulta las capacidades concretas: una salida GPU no habilita automáticamente shaders, capturas u otras operaciones gráficas.