No artigo, descreverei como tornar o emulador NES controlado remotamente e um servidor para enviar comandos a ele remotamente.

Por que isso é necessário?
Alguns emuladores de vários consoles de jogos, incluindo o
Fceux , permitem escrever e executar scripts personalizados no Lua. Mas Lua é uma linguagem ruim para escrever programas sérios. É uma linguagem para chamar funções escritas em C. Os autores dos emuladores o usam apenas devido à leveza e facilidade de incorporação. A emulação precisa requer muitos recursos do processador, e a velocidade da emulação anterior era um dos principais objetivos dos autores, e se eles lembrassem da possibilidade de ações de script, isso não ocorreria em primeiro lugar.
Agora, o poder do processador médio é suficiente para emular o NES. Por que não usar linguagens de script poderosas como Python ou JavaScript em emuladores?
Infelizmente, nenhum dos emuladores populares do NES tem a capacidade de usar esses ou outros idiomas. Encontrei apenas um projeto
Nintaco pouco conhecido, que também é baseado no kernel do Fceux, por algum motivo reescrito em Java. Decidi adicionar a capacidade de escrever scripts em Python para controlar o emulador.
Meu resultado é a Prova de Conceito da capacidade de controlar o emulador, ele não finge velocidade ou confiabilidade, mas funciona. Eu fiz isso por mim mesmo, mas como a questão de como controlar o emulador usando scripts é
bastante comum , coloquei o código-fonte no
github .
Como isso funciona
No lado do emulador
O emulador do Fceux
já inclui várias bibliotecas Lua incluídas na
forma de código compilado . Um deles é o
LuaSocket . Está mal documentado, mas consegui encontrar um exemplo de código de trabalho na
coleção de scripts do Xkeeper0 . Ele usou soquetes para controlar o emulador através do Mirc. Na verdade, o código que abre o soquete tcp é:
function connect(address, port, laddress, lport) local sock, err = socket.tcp() if not sock then return nil, err end if laddress then local res, err = sock:bind(laddress, lport, -1) if not res then return nil, err end end local res, err = sock:connect(address, port) if not res then return nil, err end return sock end sock2, err2 = connect("127.0.0.1", 81) sock2:settimeout(0)
Este é um soquete de baixo nível que recebe e envia dados por 1 byte.
No emulador do Fceux, o loop principal do script Lua se parece com o seguinte:
function main() while true do
Uma verificação de dados do soquete:
function passiveUpdate() local message, err, part = sock2:receive("*all") if not message then message = part end if message and string.len(message)>0 then
O código é bastante simples - os dados são lidos no soquete e, se o próximo comando for detectado, ele será analisado e executado. A análise e a execução são organizadas usando
corotina (corotinas) - este é um conceito poderoso da linguagem Lua para pausar e continuar a execução do código.
E mais uma coisa importante sobre os scripts Lua no Fceux - a emulação pode ser temporariamente interrompida a partir do script. Como organizar a execução contínua do código Lua e executá-lo novamente com um comando recebido do soquete? Isso não seria possível, mas existe uma capacidade pouco documentada de chamar o código Lua, mesmo quando a emulação é interrompida (obrigado por
apontar para ele):
gui.register(passiveUpdate)
Com ele, você pode parar e continuar emulando dentro do
passiveUpdate - dessa forma, você pode organizar a instalação de pontos de interrupção do emulador por meio de um soquete.
Comando do lado do servidor
Eu uso um protocolo de texto RPC baseado em JSON muito simples. O servidor serializa o nome da função e os argumentos em uma sequência JSON e a envia pelo soquete. Além disso, a execução do código é interrompida até o emulador responder com uma linha para concluir a execução do comando. A resposta conterá os campos "
FUNCTIONNAME_finished " e o resultado da função.
A ideia é implementada na classe
syncCall :
class syncCall: @classmethod def waitUntil(cls, messageName): """cycle for reading data from socket until needed message was read from it. All other messages will added in message queue""" while True: cmd = messages.parseMessages(asyncCall.waitAnswer(), [messageName])
Com esta classe, os métodos Lua do emulador Fceux podem ser agrupados em classes Python:
class emu: @classmethod def poweron(cls): return syncCall.call("emu.poweron") @classmethod def pause(cls): return syncCall.call("emu.pause") @classmethod def unpause(cls): return syncCall.call("emu.unpause") @classmethod def message(cls, str): return syncCall.call("emu.message", str) @classmethod def softreset(cls): return syncCall.call("emu.softreset") @classmethod def speedmode(cls, str): return syncCall.call("emu.speedmode", str)
E então chamado literalmente da mesma maneira que em Lua:
Métodos de retorno de chamada
Em Lua, você pode registrar retornos de chamada - funções que serão chamadas quando uma determinada condição for atendida. Podemos portar esse comportamento para o servidor em Python usando o seguinte truque. Primeiro, salvamos o identificador da função de retorno de chamada escrita em Python e passamos para o código Lua:
class callbacks: functions = {} callbackList = [ "emu.registerbefore_callback", "emu.registerafter_callback", "memory.registerexecute_callback", "memory.registerwrite_callback", ] @classmethod def registerfunction(cls, func): if func == None: return 0 hfunc = hash(func) callbacks.functions[hfunc] = func return hfunc @classmethod def error(cls, e): emu.message("Python error: " + str(e)) @classmethod def checkAllCallbacks(cls, cmd):
O código Lua também salva esse identificador e registra um retorno de chamada Lua regular, que transferirá o controle para o código Python. Em seguida, um segmento separado é criado no código Python, preocupado apenas em verificar se o comando de retorno de chamada de Lua não foi aceito:
def callbacksThread(): cycle = 0 while True: cycle += 1 try: cmd = messages.parseMessages(asyncCall.waitAnswer(), callbacks.callbackList) if cmd:
A etapa final é que, após a execução do retorno de chamada Python, o controle é retornado para Lua usando o
comando "
CALLBACKNAME_finished " para informar ao emulador que o retorno de chamada foi concluído.
Como executar um exemplo
Isso é tudo, você pode enviar comandos do laptop Jupyter no navegador diretamente para o emulador Fceux.
Você pode executar todas as linhas do laptop de exemplo sequencialmente e observar o resultado da execução no emulador.
Exemplo completo:
https://github.com/spiiin/fceux_luaserver/blob/master/FceuxPythonServer.py.ipynbEle contém funções simples, como a leitura de memória:

Exemplos de retorno de chamada mais complexos:

E um script para um jogo específico que permite mover inimigos de
Super Mario Bros. com o mouse:

Vídeo de execução do laptop:
Limitações e aplicações
O script não tem proteção contra tolo e não é otimizado para velocidade de execução - seria melhor usar um protocolo RPC binário em vez de um texto e agrupar mensagens, mas minha implementação não requer compilação. O script pode alternar os contextos de execução de Lua para Python e voltar 500-1000 vezes por segundo no meu laptop. Isso é suficiente para quase qualquer aplicativo, exceto em casos específicos de depuração pixel a pixel ou linha a linha do processador de vídeo, mas o Fceux ainda não permite essas operações de Lua, por isso não importa.
Idéias de aplicação possíveis:
- Como um exemplo da implementação desse controle para outros emuladores e linguagens
- Pesquisa de jogos
- Adicionando truques ou recursos para organizar passagens do TAS
- Inserir ou extrair dados e códigos em jogos
- Aprimorando os recursos dos emuladores - escrevendo depuradores, scripts para gravação e exibição de orientações, bibliotecas de scripts, editores de jogos
- Jogo em rede, controle de jogo usando dispositivos móveis, serviços remotos, joypads ou outros dispositivos de controle, salvamento e correções em serviços em nuvem
- Recursos de emulador cruzado
- Usando Python ou outras bibliotecas de idiomas para análise de dados e controle de jogos (criando bots)
Pilha de tecnologia
Eu usei:
Fceux -
www.fceux.com/web/home.htmlEste é um emulador de NES clássico e a maioria das pessoas o usa. Ele não é atualizado há muito tempo e não é o melhor em recursos, mas continua sendo o emulador padrão para muitos romhackers. Além disso, eu o escolhi porque o suporte ao soquete Lua está integrado e não há necessidade de conectá-lo.
Json.lua -
github.com/spiiin/json.luaEsta é uma implementação JSON em Lua pura. Eu o escolhi porque queria fazer um exemplo que não requer compilação de código. Mas eu ainda tinha que bifurcar a biblioteca, porque algumas das bibliotecas embutidas no Fceux sobrecarregaram a função de biblioteca e interromperam a serialização (minha
solicitação de pool rejeitada ao autor da biblioteca original).
Python 3 -
www.python.orgO servidor Fceux Lua abre o soquete tcp e escuta os comandos recebidos dele. Um servidor que envia comandos para o emulador pode ser implementado em qualquer idioma. Eu escolhi o Python por sua filosofia "Bateria incluída" - a maioria dos módulos está incluída na biblioteca padrão (trabalhando com soquetes e JSON também). O Python também conhece a biblioteca para trabalhar com redes neurais, e eu quero tentar usá-las para criar bots em jogos NES.
Caderno Jupyter -
jupyter.orgO Jupyter Notebook é um ambiente muito legal para a execução interativa do código Python. Com ele, você pode escrever e executar comandos em um editor de planilha dentro do navegador. Também é bom para criar exemplos apresentáveis.
Dexpot -
www.dexpot.deEu usei esse gerenciador de área de trabalho virtual para encaixar a janela do emulador em cima de outras. Isso é muito conveniente ao implantar o servidor em tela cheia para rastreamento instantâneo de alterações na janela do emulador. As ferramentas nativas do Windows não permitem organizar o encaixe de janelas em cima de outras.
Referências
Na verdade,
o repositório do projeto .
Nintaco - Emulador Java NES com Gerenciamento Remoto
Xkeeper0 coleção emu-lua - uma coleção de vários scripts Lua
Mesen é um emulador NES moderno em C # com poderosos recursos de script Lua. Até agora, sem suporte para soquete e controle remoto.
O CadEditor é o meu projeto de um editor de nível universal para o NES e outras plataformas, além de ferramentas poderosas para pesquisar jogos. Eu uso o script e o servidor descritos na publicação para explorar os jogos e adicioná-los ao editor.
Eu gostaria de receber feedback, testes e tentativas de usar o script.