Introduzione
La maggior parte delle CPU moderne è compatibile perché condivide la stessa ISA1. L'ISA più comune oggi è x86_64, un'estensione a 64 bit della vecchia architettura x86. La necessità di operazioni più complesse e veloci, soprattutto nell'aritmetica in virgola mobile, ha portato a estensioni come SSE2 e AVX3.
Queste estensioni comprendono diverse versioni che permettono al processore di interpretare insiemi differenti di opcode. SSE arriva alla versione 4.2, con una versione 4a esclusiva di AMD. AVX arriva ad AVX-512, ma l'insieme di estensioni più comune nelle CPU COTS4 è AVX-2.
Durante la compilazione del software è possibile impostare un flag che stabilisce se il codice macchina prodotto sarà compatibile con un insieme specifico di estensioni. Questa scelta può offrire prestazioni migliori, perché le estensioni possono eseguire determinate operazioni usando meno cicli della CPU.
Tuttavia, CPU più vecchie, perfettamente in grado di eseguire software moderno, potrebbero essere state introdotte sul mercato prima che le estensioni più recenti fossero disponibili. Di conseguenza, il software compilato con queste istruzioni più nuove non può essere eseguito affatto su di esse.
Rilevare il supporto delle estensioni
La descrizione di queste estensioni è inclusa nei flag CPUID. Il modo più semplice per verificare se la CPU supporta istruzioni specifiche è tramite CPU-Z:
Puoi anche provare semplicemente a eseguire il software. Potrebbe mostrare un popup che dice che non è compatibile, oppure bloccarsi silenziosamente. Controlla il Visualizzatore eventi di Windows per il codice di errore 0xC000001D, che indica il tentativo di eseguire un'istruzione illegale.
Processo di patching
Descriverò il processo di patching che ho usato per aggirare queste istruzioni nel gioco Days Gone. È il mio metodo e non è necessariamente ottimale, ma funziona. Anche gli acquirenti legittimi del gioco non riuscivano a eseguirlo su processori leggermente più vecchi: eseguendo il patching di alcune istruzioni sono riuscito a renderlo compatibile.
L'esecuzione del gioco provoca un CTD5 immediato e il processo scompare dal Task Manager. Non ci sono avvisi né indicazioni del problema.
Configurare il debugger
Carica il gioco in x64dbg e imposta un breakpoint su EXCEPTION_ILLEGAL_INSTRUCTION. Questa eccezione viene generata quando il processore incontra un opcode sconosciuto:
Esegui il gioco finché non viene raggiunto questo breakpoint. Potresti incontrare altri breakpoint: continua finché non arrivi a quello dell'istruzione non valida. Processori diversi possono avere insiemi diversi di estensioni disponibili e quindi essere in grado di interpretare opcode diversi.
Osserva come la barra di stato in basso indichi che l'esecuzione si è fermata sul breakpoint EXCEPTION_ILLEGAL_INSTRUCTION. Ora siamo su un opcode non supportato.
Primo tentativo: NOP
Di solito provo prima ad applicare NOP6 all'istruzione (clic destro → Binary → Fill with NOPs). Può funzionare oppure no, a seconda dell'istruzione e del software. I giochi usano spesso queste istruzioni per decodificare contenuti video, come titoli di coda o filmati.
Tuttavia, applicare semplicemente NOP all'istruzione può compromettere il software. In questo caso dobbiamo sostituire queste istruzioni con equivalenti che usino opcode disponibili sulla nostra CPU.
Trovare istruzioni equivalenti
Una ricerca dell'istruzione pminuw mi ha portato a questa utile risorsa, che fornisce sostituti compatibili con SSE2:
Implementazione PMINUW compatibile con SSE2
; xmm0 = ingresso|uscita ; xmm1 = ingresso ; xmm7 = temporaneo movdqa xmm7, xmm0 ; Sposta xmm0 nel temporaneo psubusw xmm7, xmm1 ; Sottrazione con saturazione senza segno psubw xmm0, xmm7 ; Sottrazione (senza saturazione; qui non ci sarebbe overflow)
Questo significa che possiamo sostituire l'istruzione con queste tre per ottenere compatibilità con SSE2. L'istruzione attuale, però, è di soli 5 byte (66:0F383AD1) e per la sostituzione ci servono 3×4 = 12 byte. Come possiamo farli entrare?
Usare code cave
Possiamo saltare a un'altra posizione che possa contenere queste istruzioni e poi tornare indietro per continuare l'esecuzione. Per prima cosa calcoliamo lo spazio necessario:
movdqa : 4 byte
psubusw : 4 byte
psubw : 4 byte
jmp : ≤5 byte
Ci servono circa 17 byte di spazio libero. Cerca una "code cave", cioè un'area inutilizzata nella sezione eseguibile. Cerca un pattern di 17 byte CC consecutivi oppure scorri per trovare un'area vuota:
Apri il primo risultato e scrivi le istruzioni sostitutive. Alla fine, aggiungi un jmp che torni all'istruzione successiva a quella sostituita. Ricordati di adeguare i registri e di verificare che il registro temporaneo non sia usato altrove:
Completare la patch
Sostituisci l'istruzione originale con un jmp alla nostra code cave:
Questo è tutto. Ripeti finché non vengono raggiunti altri breakpoint. Per Days Gone, ho sostituito 12 istruzioni pminuw e 2 pmaxud. Per alcune istruzioni non sono riuscito a trovare sostituti semplici, quindi ho semplicemente applicato NOP: ha funzionato.
Riepilogo dei passaggi
- Identifica il problema: controlla CPU-Z per gli insiemi di istruzioni supportati oppure cerca il codice
0xC000001Dnel Visualizzatore eventi di Windows - Configura il debugger: carica l'eseguibile in x64dbg e imposta un breakpoint su
EXCEPTION_ILLEGAL_INSTRUCTION - Trova le istruzioni non supportate: esegui fino al breakpoint per identificare l'opcode problematico
- Prova prima NOP: talvolta è sufficiente riempire l'istruzione con NOP
- Trova istruzioni equivalenti: cerca alternative compatibili con SSE2 per le istruzioni SSE4.x
- Individua le code cave: cerca spazio vuoto (byte
CC) nell'eseguibile - Applica la patch alla code cave: scrivi le istruzioni sostitutive seguite da un salto indietro
- Sostituisci l'istruzione originale: modificala affinché salti alla tua code cave
- Ripeti: continua finché tutte le istruzioni illegali non sono state patchate
Giochi che richiedono istruzioni SSE4.1/4.2
Elenco compilato da mafia47 (alcuni giochi richiedono anche AVX)
| Gioco | Stato |
|---|---|
| Sunset Overdrive | Ancora nessuna correzione |
| Journey | Correzione: disabilitare l'interpolazione del tessuto (solo versione più recente) |
| Metal Slug XX | Ancora nessuna correzione |
| Beyond: Two Souls | Giocabile con Intel SDE7 |
| Detroit: Become Human | Correzione disponibile |
| Control Ultimate Edition | Ancora nessuna correzione |
| Yakuza 3, 4, 5, 6 | Ancora nessuna correzione |
| Yakuza: Like a Dragon | Ancora nessuna correzione |
| Horizon Zero Dawn | Correzione solo per Steam v1.0.9.3 |
| Days Gone | Correzione solo per v1.0 |
| Little Nightmares | Giocabile con Intel SDE |
| Unravel Two | Giocabile con Intel SDE |
| Death Stranding | Ancora nessuna correzione |
| Cyberpunk 2077 | Correzione per GOG v1.22 (richiede test) |
| Red Dead Redemption 2 | Correzione solo per la versione crackata |
| Nioh 2 | Ancora nessuna correzione |
| God Eater 3 | Ancora nessuna correzione |
| Assassin's Creed Origins | Correzione per v1.51 (non per CPU Phenom) |
| Assassin's Creed Odyssey | Correzione per v1.5.3 (non per CPU Phenom) |
| Assassin's Creed Valhalla | Ancora nessuna correzione |
| Immortals Fenyx Rising | Ancora nessuna correzione |
| Far Cry 5 | Corretto dagli sviluppatori nell'ultima versione; nessuna correzione per le versioni vecchie |
| Dead Rising 4 | Ancora nessuna correzione |
| Crash Bandicoot 4 | Ancora nessuna correzione |
| Saints Row The Third Remastered | Ancora nessuna correzione |
| DOOM Eternal | Correzione solo per exe senza Denuvo (non per CPU Phenom) |
| Need for Speed Heat | Ancora nessuna correzione |
| NBA 2K21 | Ancora nessuna correzione |
| Shadow Man Remastered | Giocabile con Intel SDE |
| Biomutant | Ancora nessuna correzione |
| Outriders | Ancora nessuna correzione |
| Puyo Puyo Tetris 2 | Corretto dagli sviluppatori |
| Diablo II Resurrected | Ancora nessuna correzione |
Considerazioni finali
Sono sicuro che esistano approcci migliori, automatizzando con script oppure usando plugin specializzati per debugger. Sto anche lavorando a un emulatore generico che potrebbe aiutarci a continuare a usare CPU più vecchie ma perfettamente capaci: sarebbe ideale perché l'emulazione JIT non modifica il software originale ed emulerebbe solo le istruzioni necessarie, con un overhead (si spera) minimo.
Footnotes
-
Instruction Set Architecture è l'insieme di istruzioni che un processore può comprendere ed eseguire. ↩
-
Streaming SIMD Extensions; SIMD significa Single Instruction, Multiple Data e consente operazioni parallele su più elementi di dati. ↩
-
Advanced Vector Extension fornisce registri più larghi (a 256 e 512 bit) per una capacità di elaborazione parallela ancora maggiore. ↩
-
Commercial Off-The-Shelf indica hardware di consumo standard, anziché processori specializzati o personalizzati. ↩
-
Crash To Desktop: l'applicazione termina bruscamente senza alcuna finestra di errore. ↩
-
NOP (No Operation) è un'istruzione che non fa nulla. Riempire il codice con NOP ne rimuove di fatto la funzionalità, mantenendo l'allineamento dei byte. ↩
-
Intel Software Development Emulator può emulare istruzioni mancanti, ma con un notevole overhead prestazionale. ↩