Ich fange in der DosBox die Call-Befehle ab, schaue, ob ich eine C-Funktion dazu habe, und rufe dann diese stattdessen auf. Ich vermute, dass das recht ähnlich läuft wie bei BrightEyes. Mit dem Protected Mode hatte ich bisher keine großen Schwierigkeiten, man muss nur aufpassen, da je nach Modus die Operanden- und Adressweiten verschieden sind. Lustigerweise mache ich genau das, was Spice macht, nur bisher nur im Protected-Mode und mit einer C-Ausgabe. Ich jage den Dosbox-Disassembler über den Startpunkt einer Funktion, dieser zerstückelt mir den Byte-Code in die einzelnen Befehle, und diese schiebe ich durch ein Python-Skript, das mir äquivalenten DosBox-C-Code ausgibt.
Ich habe, glaube ich, etwa 99,9% des verwendeten Befehlssatzes implementiert, und damit > 90% des gesamten Riva-Bytecode.
Raus kommt dann so etwas wie das:
(Habe den Ausschnitt nur schnell kopiert, um den aktuellen Stand darzustellen. Die Funktion an sich ist vielleicht nicht so interessant, und generierter Code ist nie schön, aber vielleicht ein Startpunkt )
Als nächstes wollte ich per Graphenanalyse die `goto`s durch `if`s, `while`s und `switch`-Statements ersetzen und mit ähnlicher Analyse eventuell die globalen Register durch lokale Variablen. Das war bisher nur eine Spielerei, und ich weiß gar nicht, was das Ziel ist. Die Performance im DosBox dynamic-cpu Mode (JIT-Übersetzung von DOS-ASM in x64) ist fantastisch, und der Source-Code von Riva scheint ja noch in den Händen von Attic zu sein und nicht verloren, wie bei der Schicksalsklinge. Für stellenweise Analysen, unter anderem, da ich so Breakpoints von Visual Studio aus setzen und interessante Stellen kommentieren kann, ist es aber praktisch.
Ich habe, glaube ich, etwa 99,9% des verwendeten Befehlssatzes implementiert, und damit > 90% des gesamten Riva-Bytecode.
Raus kommt dann so etwas wie das:
(Habe den Ausschnitt nur schnell kopiert, um den aktuellen Stand darzustellen. Die Funktion an sich ist vielleicht nicht so interessant, und generierter Code ist nie schön, aber vielleicht ein Startpunkt )
Code:
void f_1DB2B0()
{
CPU_Push32(reg_ebx);
CPU_Push32(reg_ecx);
CPU_Push32(reg_esi);
CPU_Push32(reg_edi);
CPU_Push32(reg_ebp);
reg_ecx = reg_eax; // 32,32,mov ecx,eax
reg_esi = reg_edx; // 32,32,mov esi,edx
reg_edx = mem_readd_inline(reg_eax + 0x003D); // 32,32,mov edx,[eax+003D]
test_32(reg_edx, reg_edx);
if (FLG_NE) {
goto l1DB2C8;
}
reg_ebx = mem_readd_inline(reg_eax + 0x0031); // 32,32,mov ebx,[eax+0031]
reg_edi = mem_readd_inline(reg_eax + 0x0039); // 32,32,mov edi,[eax+0039]
goto l1DB2CD;
l1DB2C8:
reg_edi = reg_edx; // 32,32,mov edi,edx
reg_ebx = mem_readd_inline(reg_eax + 0x0035); // 32,32,mov ebx,[eax+0035]
l1DB2CD:
reg_eax = mem_readd_inline(reg_ecx + 0x0004); // 32,32,mov eax,[ecx+0004]
mem_writed_inline(reg_ecx + 0x0029, reg_esi); // 32,32,mov [ecx+0029],esi
reg_ebp = mem_readd_inline(reg_eax); // 32,32,mov ebp,[eax]
test_xor_32(reg_edx, reg_edx);
reg_edx = reg_edx ^ reg_edx;
decide_call(mem_readd_inline(reg_ebp + 0x0044), std::nullopt); // call near dword [ebp+0044]
// ABS
reg_eax = mem_readd_inline(reg_ecx + 0x0004); // 32,32,mov eax,[ecx+0004]
reg_ebp = mem_readd_inline(reg_eax); // 32,32,mov ebp,[eax]
test_xor_32(reg_edx, reg_edx);
reg_edx = reg_edx ^ reg_edx;
decide_call(mem_readd_inline(reg_ebp + 0x0010), std::nullopt); // call near dword [ebp+0010]
// ABS
reg_eax = mem_readd_inline(reg_ecx + 0x0004); // 32,32,mov eax,[ecx+0004]
reg_ebp = mem_readd_inline(reg_eax); // 32,32,mov ebp,[eax]
reg_edx = reg_edi; // 32,32,mov edx,edi
decide_call(mem_readd_inline(reg_ebp + 0x0024), std::nullopt); // call near dword [ebp+0024]
// ABS
reg_eax = mem_readd_inline(reg_ecx + 0x0004); // 32,32,mov eax,[ecx+0004]
test_xor_32(reg_ebx, reg_ebx);
reg_ebx = reg_ebx ^ reg_ebx;
test_xor_32(reg_edx, reg_edx);
reg_edx = reg_edx ^ reg_edx;
reg_edi = mem_readd_inline(reg_eax); // 32,32,mov edi,[eax]
reg_bx = mem_readw_inline(reg_esi + 0x0012); // 16,32,mov bx,[esi+0012]
reg_dx = mem_readw_inline(reg_esi + 0x0014); // 16,32,mov dx,[esi+0014]
decide_call(mem_readd_inline(reg_edi + 0x0058), std::nullopt); // call near dword [edi+0058]
// ABS
reg_eax = mem_readd_inline(reg_ecx + 0x0004); // 32,32,mov eax,[ecx+0004]
reg_edx = mem_readd_inline(reg_eax); // 32,32,mov edx,[eax]
decide_call(mem_readd_inline(reg_edx + 0x0028), std::nullopt); // call near dword [edx+0028]
// ABS
reg_ebx = mem_readd_inline(reg_ecx + 0x003D); // 32,32,mov ebx,[ecx+003D]
test_32(reg_ebx, reg_ebx);
if (FLG_E) {
goto l1DB31F;
}
reg_eax = reg_ebx; // 32,32,mov eax,ebx
CPU_Push32(-1);
f_1CA710();
CPU_Pop32(); // CALL // call 001CA710 ($-10c08) REL
mem_writed_inline(reg_ecx + 0x003D, 0x00000000); // 32,32,mov dword [ecx+003D],00000000
l1DB31F:
reg_eax = mem_readd_inline(reg_ecx + 0x0039); // 32,32,mov eax,[ecx+0039]
CPU_Push32(-1);
f_1CA710();
CPU_Pop32(); // CALL // call 001CA710 ($-10c17) REL
mem_writed_inline(reg_ecx + 0x0039, 0x00000000); // 32,32,mov dword [ecx+0039],00000000
mem_writed_inline(reg_ecx + 0x0035, 0x00000000); // 32,32,mov dword [ecx+0035],00000000
reg_eax = mem_readd_inline(reg_ecx + 0x0035); // 32,32,mov eax,[ecx+0035]
mem_writed_inline(reg_ecx + 0x0031, reg_eax); // 32,32,mov [ecx+0031],eax
reg_ebp = CPU_Pop32();
reg_edi = CPU_Pop32();
reg_esi = CPU_Pop32();
reg_ecx = CPU_Pop32();
reg_ebx = CPU_Pop32();
return; // ret
}
Als nächstes wollte ich per Graphenanalyse die `goto`s durch `if`s, `while`s und `switch`-Statements ersetzen und mit ähnlicher Analyse eventuell die globalen Register durch lokale Variablen. Das war bisher nur eine Spielerei, und ich weiß gar nicht, was das Ziel ist. Die Performance im DosBox dynamic-cpu Mode (JIT-Übersetzung von DOS-ASM in x64) ist fantastisch, und der Source-Code von Riva scheint ja noch in den Händen von Attic zu sein und nicht verloren, wie bei der Schicksalsklinge. Für stellenweise Analysen, unter anderem, da ich so Breakpoints von Visual Studio aus setzen und interessante Stellen kommentieren kann, ist es aber praktisch.