N64: Recompilado é uma ferramenta para os binários N64 estaticamente recompensados no código C que podem ser compilados para qualquer plataforma. Isso pode ser usado para portas ou ferramentas, bem como para simular comportamentos significativamente mais rápidos do que os intérpretes ou a recompilação dinâmica. Mais amplamente, ele pode ser usado em qualquer contexto em que você queira executar parte de um binário N64 em um ambiente independente.
Este não é o primeiro projeto que usa recompilação estática nos binários do console de jogos. Um exemplo bem conhecido é o Jamulator, que tem como alvo binários de NES. Além disso, este nem é o primeiro projeto a aplicar a recompilação estática a projetos relacionados ao N64: a recompilação estática da IDO recompila o compilador IDO SGI IRIX em sistemas modernos para facilitar a decompilação correspondente dos jogos N64. Este projeto funciona de maneira semelhante ao projeto de recompiação estática da IDO, e esse projeto foi minha principal inspiração para fazer isso.
O recompilador funciona aceitando uma lista de símbolos e metadados ao lado do binário com o objetivo de dividir o binário de entrada em funções que são recompiladas individualmente em uma função C, denominada de acordo com os metadados.
As instruções são processadas um a um e o código C correspondente é emitido à medida que cada um é processado. Esta tradução é muito literal para manter a complexidade baixa. Por exemplo, a instrução addiu $r4, $r4, 0x20
, que adiciona 0x20
ao valor de 32 bits nos baixos bytes de registro $r4
e armazena o sinal estendido de 64 bits em $r4
, é recompensado em ctx->r4 = ADD32(ctx->r4, 0X20);
A instrução jal
(Jump-and-Link) é recompilada diretamente em uma chamada de função, e as instruções j
ou b
(saltos e galhos incondicionais) que podem ser identificados como otimizações de chamada cauda também são recompilados em chamadas de função. Os slots de atraso de ramificação são tratados duplicando as instruções conforme necessário. Existem outros comportamentos específicos para certas instruções, como o recompilador tentando transformar uma instrução jr
em uma instrução de caixa de comutação, se ele puder dizer que está sendo usado com uma tabela de salto. O recompilador foi testado principalmente em binários construídos com compiladores antigos do MIPS (por exemplo, MIPS GCC 2.7.2 e IDO), bem como o clang moderno direcionando MIPs. O MIPS moderno GCC pode tropeçar no recompilador devido a certas otimizações que pode fazer, mas esses casos provavelmente podem ser evitados definindo sinalizadores de compilação específicos.
Toda função de saída criada pelo recompilador é atualmente emitida em seu próprio arquivo. Uma opção pode ser fornecida no futuro para agrupar funções em arquivos de saída, o que deve ajudar a melhorar os tempos de construção da saída do recompilador, reduzindo a E/S do arquivo no processo de construção.
A saída do recompilador pode ser compilada com qualquer compilador C (testado com MSVC, GCC e CLANG). Espera -se que a saída seja usada com um tempo de execução que possa fornecer a funcionalidade necessária e as implementações de macro para executá -la. É fornecido um tempo de execução no N64ModernRuntime, que pode ser visto em ação no projeto Zelda 64: recompilado.
As sobreposições estaticamente vinculadas e relocáveis podem ser tratadas por esta ferramenta. Em ambos os casos, a ferramenta emite pesquisas de função para o registro de pular e link (ou seja, ponteiros de função ou funções virtuais) que o tempo de execução fornecido pode implementar usando qualquer tipo de tabela de pesquisa. Por exemplo, a instrução jalr $25
seria recompilada como LOOKUP_FUNC(ctx->r25)(rdram, ctx);
O tempo de execução pode manter uma lista de quais seções do programa são carregadas e em qual endereço elas estão para determinar em qual função executar sempre que uma pesquisa for acionada durante o tempo de execução.
Para sobreposições relocáveis, a ferramenta modificará instruções suportadas que possuem dados de realocação ( lui
, addiu
, Carregar e armazenar instruções) emitindo uma macro extra que permite o tempo de execução para realocar o campo de valor imediato da instrução. Por exemplo, a instrução lui $24, 0x80C0
em uma seção que inicia no endereço 0x80BFA100
com uma realocação contra um símbolo com um endereço de 0x80BFA730
será recompilado como ctx->r24 = S32(RELOC_HI16(1754, 0X630) << 16);
, onde 1754 é o índice desta seção. O tempo de execução pode implementar as macros Reloc_Hi16 e Reloc_LO16 para lidar com a modificação do imediato com base no endereço carregado atual da seção.
O suporte para realocações para o mapeamento TLB está chegando no futuro, o que adicionará a capacidade de fornecer uma lista de realocações MIPS32 para que o tempo de execução possa se mudar em carga. A combinação disso com a funcionalidade usada para sobreposições realocáveis deve permitir a execução da maioria do código mapeado TLB sem incorrer em uma penalidade de desempenho em cada acesso à RAM.
O recompilador é configurado fornecendo um arquivo TOML para configurar o comportamento do recompilador, que é o único argumento fornecido ao recompilador. O TOML é onde você especifica os caminhos de arquivo de entrada e saída, bem como opcionalmente, eliminam funções específicas, ignoram a recompilação de funções específicas e patchem instruções únicas no binário de destino. Também existe uma funcionalidade planejada para poder emitir ganchos na saída do recompilador, adicionando -os às seções Toml ( [[patches.func]]
e [[patches.hook]]
do Toml vinculado abaixo), mas isso é atualmente não implementado. A documentação sobre todas as opções que o recompilador oferece não está disponível no momento, mas um exemplo que TOML pode ser encontrado no projeto Zelda 64: Recompiled aqui.
Atualmente, a única maneira de fornecer os metadados necessários é passar um arquivo ELF para esta ferramenta. A maneira mais fácil de obter um ELF é estabelecer uma desmontagem ou descompilação do binário alvo, mas haverá suporte para fornecer os metadados por meio de um formato personalizado para ignorar a necessidade de fazê -lo no futuro.
Essa ferramenta também pode ser configurada para recompilar no modo "Saída de arquivo único" por meio de uma opção no toml de configuração. Isso emitirá todas as funções no elfo fornecido em um único arquivo de saída. O objetivo deste modo é poder compilar versões corrigidas de funções do binário de destino.
Esse modo pode ser combinado com a funcionalidade fornecida por quase todos os ligantes (LD, LLD, MSVC's Link.exe etc.) para substituir as funções da saída original do recompilador por versões modificadas. Esses vinculadores procuram apenas símbolos em uma biblioteca estática se ainda não foram encontrados em um arquivo de entrada anterior; portanto, fornecer os patches recompilados para o vinculador antes de fornecer a saída original do recompilador resultará em patches tendo prioridade sobre funções com os mesmos nomes da saída original do recompilador.
Isso economiza uma quantidade tremenda de tempo enquanto itera em patches para o binário alvo, pois você pode ignorar o reencontro do recompilador no binário alvo, além de compilar a saída original do recompilador. Um exemplo de uso deste modo de saída de arquivo único para esse fim pode ser encontrado no projeto Zelda 64: recompilado aqui, com o correspondente Makefile que é usado para criar o elfo para esses patches aqui.
O microcódigo RSP também pode ser recompilado com esta ferramenta. Atualmente, não há suporte para recompilar sobreposições de RSP, mas pode ser adicionado no futuro, se desejar. Documentação sobre como usar essa funcionalidade estará em breve.
Este projeto pode ser construído com o CMake 3.20 ou acima e um compilador C ++ que suporta C ++ 20. Este repositório usa submódulos Git, portanto, certifique-se de clonar recursivamente ( git clone --recurse-submodules
) ou inicializar os submódulos recursivamente após a clonagem ( git submodule update --init --recursive
). A partir daí, o edifício é idêntico a qualquer outro projeto CMake, por exemplo, execute cmake
na pasta de construção de destino e aponte -o para a raiz deste repositório, depois execute cmake --build .
a partir dessa pasta de destino.