2019-10-12 14:57:14 +00:00
|
|
|
|
## Local variables
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|-|-|
|
2019-12-03 21:45:08 +00:00
|
|
|
|
| `DX` | First 8-bit variable declared *if no other function is called*<br />Second 16-bit variable declared *if no other function is called* |
|
2019-10-12 14:57:14 +00:00
|
|
|
|
| `[bp-1]` | First 8-bit variable declared *otherwise* |
|
|
|
|
|
| `SI` | First 16-bit variable declared |
|
2019-12-03 21:45:08 +00:00
|
|
|
|
| `DI` | Second 16-bit variable declared *if other functions are called* |
|
2019-10-12 14:57:14 +00:00
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
| ASM | Declaration sequence in C |
|
|
|
|
|
|----------|---------------------------|
|
|
|
|
|
| `SI` | `int near *var_1;` |
|
|
|
|
|
| `[bp-1]` | `char var_2;` |
|
|
|
|
|
| `[bp-2]` | `char var_3;` |
|
|
|
|
|
|
2019-10-13 18:52:53 +00:00
|
|
|
|
## Signedness
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|-|-|
|
|
|
|
|
| `MOV al, var`<br />`MOV ah, 0`| `var` is *unsigned char* |
|
[Decompilation] [th01] master.lib resident palette function reimplementations
Which store colors as GRB, as suggested by the structure's ID string.
Even master.lib's own functions add an additional XCHG AH, AL
instruction to get colors into and out of this format. MASTER.MAN
suggests that it's some sort of standard on PC-98. It does match the
order of ths hardware's palette register ports, after all.
(0AAh = green, 0ACh = red, 0AEh = blue)
Now we also know why __seg* wasn't used more commonly, as lamented in
c8e8e98. Turbo C++ simply doesn't support a lot of arithmetic on
segment pointers.
And then that undecompilable far call to a function within the same
segment, but inside a different translation unit…
Also, thanks again to Egor for the SCOPY@ hack that debuted in 0460072.
Would have probably struggled with this a lot more without that.
And *then* you realize that TH01 effectively doesn't even use the
resident palette. 😐
And yes, we're procrastinating the whole issue of potentially using
a single translation unit for all three binaries by using a common
segment name, because it *really* isn't that easy.
Completes P0066, funded by Keyblade Wiedling Neko and Splashman.
2020-01-05 15:10:00 +00:00
|
|
|
|
| `MOV al, var`<br />`CBW` | `var` is *char*, `AX` is *int* |
|
2019-10-13 18:52:53 +00:00
|
|
|
|
|
|
|
|
|
## Arithmetic
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|-|-|
|
|
|
|
|
| `ADD [m8], imm8` | Only achievable through a C++ method operating on a member? |
|
|
|
|
|
| `MOV AL, [m8]`<br />`ADD AL, imm8`<br />`MOV [m8], AL` | Opposite; *not* an inlined function |
|
|
|
|
|
|
|
|
|
|
### Arithmetic on a register *after* assigning it to a variable?
|
|
|
|
|
|
|
|
|
|
Assigment is part of the C expression. If it's a comparison, that comparison
|
|
|
|
|
must be spelled out to silence the `Possibly incorrect assignment` warning.
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|-|-|
|
|
|
|
|
| `CALL somefunc`<br />`MOV ??, AX`<br />`OR AX, AX`<br />`JNZ ↑` | `while(( ?? = somefunc() ) != NULL)` |
|
|
|
|
|
|
|
|
|
|
### `SUB ??, imm` vs. `ADD ??, -imm`
|
|
|
|
|
|
|
|
|
|
`SUB` means that `??` is unsigned. Might require suffixing `imm` with `u` in
|
|
|
|
|
case it's part of an arithmetic expression that was promoted to `int`.
|
|
|
|
|
|
|
|
|
|
## `switch` statements
|
|
|
|
|
|
|
|
|
|
* Sequence of the individual cases is identical in both C and ASM
|
|
|
|
|
* Multiple cases with the same offset in the table, to code that doesn't
|
|
|
|
|
return? Code was compiled with `-O`
|
|
|
|
|
|
[Decompilation] [th01] master.lib resident palette function reimplementations
Which store colors as GRB, as suggested by the structure's ID string.
Even master.lib's own functions add an additional XCHG AH, AL
instruction to get colors into and out of this format. MASTER.MAN
suggests that it's some sort of standard on PC-98. It does match the
order of ths hardware's palette register ports, after all.
(0AAh = green, 0ACh = red, 0AEh = blue)
Now we also know why __seg* wasn't used more commonly, as lamented in
c8e8e98. Turbo C++ simply doesn't support a lot of arithmetic on
segment pointers.
And then that undecompilable far call to a function within the same
segment, but inside a different translation unit…
Also, thanks again to Egor for the SCOPY@ hack that debuted in 0460072.
Would have probably struggled with this a lot more without that.
And *then* you realize that TH01 effectively doesn't even use the
resident palette. 😐
And yes, we're procrastinating the whole issue of potentially using
a single translation unit for all three binaries by using a common
segment name, because it *really* isn't that easy.
Completes P0066, funded by Keyblade Wiedling Neko and Splashman.
2020-01-05 15:10:00 +00:00
|
|
|
|
## Function calls
|
|
|
|
|
|
|
|
|
|
### `NOP` insertion
|
|
|
|
|
|
|
|
|
|
Happens for every `far` call to outside of the current translation unit, even
|
|
|
|
|
if both the caller and callee end up being linked into the same code segment.
|
|
|
|
|
|
|
|
|
|
**Certainty:** Seems like there *might* be a way around that, apart from
|
|
|
|
|
temporarily spelling out these calls in ASM until both functions are compiled
|
|
|
|
|
as part of the same translation unit. Found nothing so far, though.
|
|
|
|
|
|
|
|
|
|
### Pushing byte arguments to functions
|
2019-10-12 14:57:14 +00:00
|
|
|
|
|
|
|
|
|
Borland C++ just pushes the entire word. Will cause IDA to mis-identify
|
|
|
|
|
certain local variables as `word`s when they aren't.
|
|
|
|
|
|
2019-10-13 18:52:53 +00:00
|
|
|
|
## Inlining
|
|
|
|
|
|
|
|
|
|
Always worth a try to get rid of a potential macro. Some edge cases don't
|
|
|
|
|
inline optimally though:
|
|
|
|
|
|
|
|
|
|
* Assignments to a pointer in `SI` – that pointer is moved to `DI`,
|
|
|
|
|
[clobbering that register](#clobbering-di). Try a [class method](#C++)
|
|
|
|
|
instead.
|
|
|
|
|
|
[Reverse-engineering] [th04/th05] Player sprite area invalidation
And once again, the TH05 version is un-decompilable. :/ It was pretty
close this time, though, as the entire block between PUSH DI and POP DI
kind of resembles a separate inlined function, in accordance with Turbo
C++'s automatic backup of the DI register, as researched in 7f971a0.
Except that it contains a loop, and Turbo C++ refuses to inline any
function with `do`, `while`, `for`, or `goto`. If it didn't, it would
have totally worked.
Also, yes, C++ class methods are treated identically in this regard.
Oh well. Shot type control functions next, finally!
Completes P0035, funded by zorg.
2019-09-24 18:54:32 +00:00
|
|
|
|
## C++
|
|
|
|
|
|
2019-12-03 21:45:08 +00:00
|
|
|
|
Class methods inline to their ideal representation if all of these are true:
|
|
|
|
|
|
|
|
|
|
* returns `void` || (returns `*this` && is at the first nesting level of
|
|
|
|
|
inlining)
|
|
|
|
|
* takes no parameters || takes only built-in, scalar-type parameters
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
|
|
* A class method (first nesting level) calling an overloaded operator (second
|
|
|
|
|
nesting level) returning `*this` will generate (needless) instructions
|
2019-10-13 19:52:02 +00:00
|
|
|
|
equivalent to `MOV AX, *this`. Thus, any overloaded `=`, `+=`, `-=`, etc.
|
|
|
|
|
operator should always return `void`.
|
[Reverse-engineering] [th04/th05] Player sprite area invalidation
And once again, the TH05 version is un-decompilable. :/ It was pretty
close this time, though, as the entire block between PUSH DI and POP DI
kind of resembles a separate inlined function, in accordance with Turbo
C++'s automatic backup of the DI register, as researched in 7f971a0.
Except that it contains a loop, and Turbo C++ refuses to inline any
function with `do`, `while`, `for`, or `goto`. If it didn't, it would
have totally worked.
Also, yes, C++ class methods are treated identically in this regard.
Oh well. Shot type control functions next, finally!
Completes P0035, funded by zorg.
2019-09-24 18:54:32 +00:00
|
|
|
|
|
|
|
|
|
**Certainty**: See the examples in `9d121c7`. This is what allows us to use
|
|
|
|
|
custom types with overloaded assignment operators, with the resulting code
|
|
|
|
|
generation being indistinguishable from equivalent C preprocessor macros.
|
|
|
|
|
|
2019-12-03 21:45:08 +00:00
|
|
|
|
* Returning *anything else* but `void` or `*this` will first store that result
|
|
|
|
|
in `AX`, leading any branches at the call site to then refer to `AX`.
|
[Reverse-engineering] [th04/th05] Player sprite area invalidation
And once again, the TH05 version is un-decompilable. :/ It was pretty
close this time, though, as the entire block between PUSH DI and POP DI
kind of resembles a separate inlined function, in accordance with Turbo
C++'s automatic backup of the DI register, as researched in 7f971a0.
Except that it contains a loop, and Turbo C++ refuses to inline any
function with `do`, `while`, `for`, or `goto`. If it didn't, it would
have totally worked.
Also, yes, C++ class methods are treated identically in this regard.
Oh well. Shot type control functions next, finally!
Completes P0035, funded by zorg.
2019-09-24 18:54:32 +00:00
|
|
|
|
|
|
|
|
|
**Certainty**: Maybe Borland (not Turbo) C++ has an optimization option
|
|
|
|
|
against it?
|
|
|
|
|
|
2019-09-20 17:53:52 +00:00
|
|
|
|
## Limits of decompilability
|
|
|
|
|
|
[Reverse-engineering] [th04/th05] Player sprite area invalidation
And once again, the TH05 version is un-decompilable. :/ It was pretty
close this time, though, as the entire block between PUSH DI and POP DI
kind of resembles a separate inlined function, in accordance with Turbo
C++'s automatic backup of the DI register, as researched in 7f971a0.
Except that it contains a loop, and Turbo C++ refuses to inline any
function with `do`, `while`, `for`, or `goto`. If it didn't, it would
have totally worked.
Also, yes, C++ class methods are treated identically in this regard.
Oh well. Shot type control functions next, finally!
Completes P0035, funded by zorg.
2019-09-24 18:54:32 +00:00
|
|
|
|
### `MOV BX, SP`-style functions, or others with no standard stack frame
|
2019-09-20 17:53:52 +00:00
|
|
|
|
|
|
|
|
|
These almost certainly weren't compiled from C. By disabling stack frames
|
|
|
|
|
using `#pragma option -k-`, it *might* be possible to still get the exact same
|
|
|
|
|
code out of Turbo C++ – even though it will most certainly look horrible, and
|
|
|
|
|
barely more readable than assembly (or even less so), with tons of inline ASM
|
|
|
|
|
and register pseudovariables. However, it's futile to even try if the function
|
|
|
|
|
contains one of the following:
|
|
|
|
|
|
2019-10-12 14:57:14 +00:00
|
|
|
|
<a id="clobbering-di"></a>
|
|
|
|
|
|
2019-09-20 17:53:52 +00:00
|
|
|
|
* A reference to the `DI` register. In that case, Turbo C++ always inserts a
|
|
|
|
|
`PUSH DI` at the beginning (before the `MOV BX, SP`), and a `POP DI` before
|
|
|
|
|
returning.
|
|
|
|
|
|
|
|
|
|
**Certainty:** Confirmed through reverse-engineering `TCC.EXE`, no way
|
|
|
|
|
around it.
|