Eu gastei horas fazendo algo praticamente inútil.
Eu estava pensando em implementar uma VM que executa jogos feitos com o GameMaker: Studio 1. (Sabia que o ".exe" de jogos feitos pelo GM:S é uma VM? Igual como Java funciona, ele executa bytecode. E é por isso que eu consegui fazer o Droidtale, um port não oficial do Undertale para o Android)
O objetivo era conseguir rodar Undertale (e outros jogos feitos pelo GM:S) em uma VM feita em Kotlin, sendo possível portar a VM para outras plataformas (e assim portando os jogos também).
Claro, é bem mais fácil apenas recompilar o jogo para a plataforma desejada, mas existem os seguintes problemas:
Caso você tenha o GameMaker: Studio com os exports que você deseja...
Caso você tenha (ou não tenha) o GameMaker: Studio sem ter os exports que você deseja...
Por isso decidi fazer uma VM, porque não deve ser tão difícil... né?
Não, não é fácil. E sim MUITO difícil, já que você precisa implementar uma VM que interpreta o código gerado pelo GM:S para executar o código do jogo.
Eu usei as seguintes documentações para fazer a minha VM:
Enquanto estava fazendo o meu projeto, descobri o Luna, um projeto parecido com o meu, que tenta implementar uma VM para jogos do GameMaker: Studio, mas o Luna foi feito para jogos que usam o bytecode das novas versões do GameMaker: Studio 2, enquanto eu estou tentando implementar o bytecode utilizado no GameMaker: Studio 1 (já que o objetivo era emular o Undertale). O Luna é bastante interessante já que o dev consegiu rodar um jogo simples feito no GM:S2! Vale a pena conferir o blog do criador para ver o progresso.
Mas mesmo assim resolvi continuar o meu projeto, afinal, imagina o Undertale rodando no seu navegador? Então fui para a ação e programação, eu reutilizei partes do meu antigo projeto FriskEuphoria, que foi originalmente criado para fazer unpack e pack do data.win
do Undertale e de outros jogos feitos no GameMaker: Studio.
Criei um projeto vazio no GameMaker: Studio 1 com apenas uma sala vazia e um objeto que no "Create" possuia o seguinte código:
show_message("owo whats this???");
show_message("uwu whats this???");
show_message(";w; whats this???");
Como reaproveitei o código do FriskEuphoria, a parte de extrair as strings já tinha sido feita, yay!
Então parti para a parte de fazer unpack da parte do código... que foi difícil, já que era a primeira vez que eu implementei um programa que faz parse e executa bytecode.
E depois de ler MUITA a documentação e muito trial and error, você precisa fazer o seguinte para ler o chunk de "CODE"
CODE
)data.win
aonde está o conteúdo)gml_Object_owo_Create_0
E quando ler o código, cada "instrução" são 32 bits, por exemplo:
A VM do GameMaker: Studio é stack-based (parecido como a VM do Java funciona).
Para você parsear qual op code você está lendo, você precisa pular os 3 primeiros bytes e ler, no exemplo acima, é um op code 0xC0
, que é um push de uma constante.
Depois você volta para o começo da leitura das instruções para ler os 3 primeiros bytes que foram pulados, já que eles possuem informações importantes sobre o op code.
No caso do 0xC0
, você precisa pular os dois primeiros bytes (que servem para nada), você lê o byte que indica o tipo do push (neste caso é 0x06
, que significa que estamos fazendo push de uma "String"), e aí você pula mais um byte (que seria o op code) e leia o próximo byte, que é o ID de referência da String, que, neste caso, é 0x03
...
E olhando no dump de strings, qual é a string que fica na posição 3? owo whats this???
! Então estamos pelo caminho certo!!
E depois precisei parsear as outras instruções, eu acabei implementando as seguintes instruções e tipos:
Ah, e também tive que fazer unpack do chunk FUNC
, já que quando você usa uma função do GameMaker: Studio (exemplo: show_message
) a função é colocada na seção FUNC
e ao usar a instrução de call, ele passa o ID de referência da função.
E, finalmente, fiz a VM executar o código (coloquei para o show_message
mostrar a mensagem no console)... e deu certo!
Claro, se eu implementasse todos os op codes que o GM:S usa e implementar todas as funções do GM:S, seria possível executar qualquer jogo feito no GameMaker: Studio na VM.
E por isso isso que eu fiz é inútil/idiota: É legal como um conceito, mas irá demorar MUITO para implementar uma VM completa que consiga interpretar todos os bytecodes e implementar todas as funções que o GameMaker: Studio possui. Claro, é possível eu apenas implementar os bytecodes e funções que o Undertale utiliza, só para servir como proof of concept... mas quem sabe eu decida fazer isso outro dia.