VOGONS


Writing a patch in ASM *SOLVED*

Topic actions

Reply 20 of 44, by Akuma

User metadata
Rank Member
Rank
Member
ripsaw8080 wrote:

The most obvious issue is that INT 16 is not "unhooked" before the program terminates; but that would only causes a crash after termination when the program's memory block is released, leaving the INT 16 handler code likely to be overwritten by the next program executed.

So next program hangs because it cannot return ? Or next program hangs because it waits on the former iret ?

ripsaw8080 wrote:

A more subtle issue is that INT 21/35 is changing the value of ES, and INT 21/4B uses ES:BX as a pointer to the child program's parameter block. So, you should have a PUSH CS / POP ES or so after the ES value has been stored in memory.

I will look into this 😁

ripsaw8080 wrote:

Those are the issues that I notice at a glance, not necessarily the only issues.

BTW, when the subject matter is applicable to DOS programming in general and not just for DOSBox, such topics are best posted in Milliways. 😉

I have no problem if the thread gets moved to where it belongs, but I cant do this myself.

cyclone3d wrote:

What about using one of those old DOS Game cheater programs that you load up before your game. Then you load the game and press a hotkey sequence and it drops to the game cheater and lets you edit memory values and then go back to the game.

Generally used for changing lives, ammo, etc, but I would think it would work for patching as well.

I think some of them might have let you save profiles.. don't remember though. I haven't used one in years.

I can make it work all sorts of ways, but I wanted to do this in assembly. Thanks though 😁

Reply 21 of 44, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie
Akuma wrote:
ripsaw8080 wrote:

The most obvious issue is that INT 16 is not "unhooked" before the program terminates; but that would only causes a crash after termination when the program's memory block is released, leaving the INT 16 handler code likely to be overwritten by the next program executed.

So next program hangs because it cannot return ? Or next program hangs because it waits on the former iret ?

no, the INT 0x16 now points into trash so when its called, system hangs. you need to re-hook it to its original value before you quit your app

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 22 of 44, by pantercat

User metadata
Rank Newbie
Rank
Newbie

Well, this is not my code 😉

I have written:

lea bx, EXEC_INFO

and not:

mov bx, EXEC_INFO

LEA != MOV.

LEA (or PUSH and POP, as ripsaw8080 said) should eliminate 2nd issue. Maybe with nasm you must write:

lea bx, [EXEC_INFO]

But well, I'm not an expert, but if you want to use my code as a base, my advice is: compile my code with no modifications first. My code follows A86 syntax, so get A86, put A86.COM and A86.LIB and WHATEVER.ASM in the same directory as "La Colmena CGA/EGA/Hercules" and compile with:

A86 WHATEVER.ASM

Then run WHATEVER.COM and when you see it works, then have fun modifying it as much as you want. But remember: I'm just a learner. 😀

PS: make sure you've La Colmena CGA/EGA/Hercules/VGA-16 version and NOT VGA-256 version. The bytes to modify will be in different memory addresses.

Reply 23 of 44, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

LEA BX loads the effective address into BX rather than the value at that address, it does not change ES. LES BX would change ES, but it is not really applicable in this case, so the push/pop is the simple solution.

Reply 24 of 44, by pantercat

User metadata
Rank Newbie
Rank
Newbie

I see why ES should be preserved, but even if ES is destroyed, the loader works nevertheless. Even if I comment entire EXEC_INFO it still works 😕 , so I'm leaning toward DOSBox doing some magic and requiring only DS:DX pointing to the ASCIIZ filename. I bet this wouldn't end well in pure DOS 🤣

Anyway, Akuma, here is a revised full source of the LOADER. Thank you all for your comments.

;; A86 LOADER.ASM
;; La Colmena CGA/EGA/Hercules/VGA-16 loader

SIZE EQU 1024
OVERLAY segment para 'code'
assume cs:OVERLAY, ds:OVERLAY
org 100h

START: jmp INITCODE

OLDINT dw 0,0

NEWINT proc far
cmp ah, 1
jnz EXIT

cmp Word Ptr [0097], 0d75h
jnz EXIT

mov Word Ptr [0097], 9090h

EXIT:
jmp DWord Ptr cs:[OLDINT]
NEWINT endp

INITCODE:
mov sp, SIZE
mov bx, SIZE/16
mov ah, 4ah
int 21h

mov ax, 3516h
int 21h
mov OLDINT[0], bx
mov OLDINT[2], es

push cs
pop es

mov ax,2516h
lea dx, NEWINT
int 21h

lea bx, EXEC_INFO
mov DWord Ptr [bx], 0
mov DWord Ptr [bx+2], 80h
mov Word Ptr [bx+4], cs
mov Word Ptr [bx+6], 5ch
mov Word Ptr [bx+8], cs
mov Word Ptr [bx+0ah], 6ch
mov Word Ptr [bx+0ch], cs

lea dx, FILENAME
mov ax, 4b00h
int 21h

mov dx, OLDINT[0]
mov ds, OLDINT[2]
mov ax,2516h
int 21h
Show last 12 lines

push cs
pop ds
mov ax, 4c00h
int 21h

FILENAME db "COLMENA.EXE",0
EXEC_INFO db 22 DUP (0)

OVERLAY ends

end START

Keep in mind that this instruction:

mov Word Ptr [0097], 9090h

is MOVing bytes in DS:[0097]. It's ok in this case, because when mov is executed the bytes you want to patch are in DS:[0097]. But this is not always true. You usually have to figure out the segment where you want to patch looking at the values that were pushed on the stack. You can do something like:

push bp
mov bp, sp
push es
push ax
mov ax,[bp+6h] ; more info later
mov es, ax

cmp Word Ptr es:[032b], 0374h
jne ALREADYPATCHED

mov Word Ptr es:[032b], 03ebh

ALREADYPATCHED:
pop ax
pop es
pop bp
...
EXIT:
jmp DWord Ptr cs:[OLDINT]
NEWINT endp

Hint: you can change data view to SS:BP (DOSBox debugger -> d ss:bp)

but maybe it's quicker just to put some instructions like:
mov ax,[bp]
mov ax,[bp+2h]
mov ax,[bp+4h]
mov ax,[bp+6h]
...

and step into them looking at AX value.

Let's say you're looking for segment where bytes 74 03 are. Let's say you step in mov ax,[bp+6h] and AX=03C0. You already know the offset, say it's 032b, so you'll go to 3c0:32b (DOSBox debugger -> d 3c0:32b) and if you see 7403 congrats, you have found the segment pushed on the stack, you can code mov ax,[bp+6h]

Last edited by pantercat on 2019-11-10, 11:06. Edited 1 time in total.

Reply 25 of 44, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

Well, it's true that few games care about finding environment variables, but the most significant potential problem with a bad pointer to the parameter block comes when the child program releases its environment block on termination. The effect might not be noticeable (e.g. crash, hang, or error) depending on what happens to be in memory where ES:BX is pointing, but arbitrary behavior should be avoided of course. 😉

Reply 26 of 44, by akuma667

User metadata
Rank Newbie
Rank
Newbie

Sorry guys, I had an unfortunate accident, my password database got corrupted amongst other things
and I have no backup 😊 No important data is lost except for that. *phew*

So I will contact an admin and ask how to go about this, just letting everyone know
as I appreciate your feedback. If you PM me and I don't respond, this is why.

This might take a while, we'll see.

Reply 27 of 44, by akuma667

User metadata
Rank Newbie
Rank
Newbie

I think I found the problem, its probably me and my coding but I have that feeling...

When I convert:

A86: jmp dword ptr cs:[INTORIG]  
into
NASM: jmp dword [cs:INTORIG]

After compiling:

A86: jmp dword ptr cs:[INTORIG]  ---> 2E FF 2E 66 01
to
NASM: jmp dword [cs:INTORIG] ---> 2E 66 FF 26 67 01

When I convert:

A86: jmp dword ptr cs:[INTORIG]  
into
NASM: jmp WORD [cs:INTORIG]

After compiling:

A86: jmp dword ptr cs:[INTORIG]  ---> 2E FF 26 66 01
to
NASM: jmp WORD [cs:INTORIG] ---> 2E FF 2E 66 01

When I manually patch 2E into 26 , all starts working 😁
(because it changed WORD into DWORD and gets the same length as the A86 compiled version.

So is this my syntax error, or is this a bug ?

Reply 28 of 44, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

the "2E 66 FF 26 67 01" first 0x66 is a 32bit size override.

I use "jmp far [cs: xxxxxx]" in nasm instead of jump dword

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 29 of 44, by akuma667

User metadata
Rank Newbie
Rank
Newbie
BloodyCactus wrote:

the "2E 66 FF 26 67 01" first 0x66 is a 32bit size override.

I use "jmp far [cs: xxxxxx]" in nasm instead of jump dword

That's it! 😁

My syntax error it was: when using 'jmp far [cs:xxxxx[' all works like it should.
I will mark this thread solved when I regain access to my account.

Thanks BloodyCactus, you just gave me the last piece of the puzzle 😁

Reply 30 of 44, by akuma667

User metadata
Rank Newbie
Rank
Newbie
pantercat wrote:
Well, this is not my code ;) […]
Show full quote

Well, this is not my code 😉

I have written:

lea bx, EXEC_INFO

and not:

mov bx, EXEC_INFO

LEA != MOV.

Actually it is your code 😉

You have written:

lea bx, EXEC_INFO

and I converted it to:

mov bx, EXEC_INFO

A86:LEA != A86:MOV , but A86:LEA == NASM:MOV following http://left404.com/2011/01/04/converting-x86- … masm-to-nasm-3/ and since I get the exact binary this way its a proper conversion afaict.

But the code was extremely helpful, thanks again for that 😁
.

Reply 32 of 44, by Akuma

User metadata
Rank Member
Rank
Member

Just out of curiosity what if the address of the patch falls outside the max offset of FFFFh ?
eg: cs=0 segment=cs offset=10000h

You have to point the segment value to a location where the offset can reach it.
How would one make this work 😕 ?

Reply 34 of 44, by Akuma

User metadata
Rank Member
Rank
Member

I searched and read some stuff today but its still confusing.
So I need to assign a base address to ds ?
I want cs+10000h, so 1000h/10h=1000h

mov bx,1000h
mov ds,bx ?
mov byte [ds:1h],'A' 😕

Reply 35 of 44, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie

you need to know its posistion from a register value called in your ISR.

so something like

push ds
mov ax,ds
add ax,0x1000
mov ds,ax
mov b[0x0001] = 'A'
pop ds

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 36 of 44, by Akuma

User metadata
Rank Member
Rank
Member

Well I had some time again to start coding a bit, but I'm stuck again.
All I want to do is: give a function a parameter 😁, but I have that feeling I'm missing some vital piece of information.

%macro mfunction 1
push %1
call function
%endmacro

I push a value onto the stack, but so does the call.
But I need the first value that was pushed.

So what is a proper way ? I came up with these
1.

function:
pop cx
pop bx
push cx
ret

2.

function:
mov bx,sp
mov bx,[ss:bx+2]
ret

3.

function:
add sp,2
pop bx
sub sp,4
ret

4.

function:
mov cx,sp
pop bx
pop bx
mov sp,cx
ret

Reply 37 of 44, by ripsaw8080

User metadata
Rank DOSBox Author
Rank
DOSBox Author

If you can't or don't want to simply pass the value in a register or in a memory variable, the standard method is to use BP in the subroutine:

push bp
mov bp,sp
mov bx,[bp+offset_to_word_value_on_stack]
...
pop bp
ret

The reason BP is the standard is because its default segment is SS and has to be overridden to use some other segment. SP is also sometimes adjusted to make storage space on the stack for a subroutine's temporary data. Then there is the ENTER/LEAVE construct, which you can look up online, used for similar purpose.

Reply 39 of 44, by Akuma

User metadata
Rank Member
Rank
Member

I saw the 3000+ views of my horrible code conversion.
That pushed me to update it quickly before even more people see the horror

Well I think I ironed out most problems 😁

memsize		equ	220h			;memory size 220h/544 bytes
org 0x100 ;this is a .com file
;------------------------------------------------------------------------------
start:
;------------------------------------------------------------------------------
;we reposition the stackpointer memsize,
; set no. pages in bx by dividing: memsize / pagesize (16 bytes) = no. pages
; then we resize our memory usage to the number of pages

mov sp,memsize ;point stackpointer
; to end of memory
mov bx,memsize/010h ;no. pages = memsize / 16
mov ah,04Ah ;resize memory
int 21h ;commit

mov dx,message ;point to message
mov ah,009h ;write string to stdout
int 21h ;commit

;------------------------------------------------------------------------------
;keystroke function uses a lot of stack space and could overwrite the end
; of your program in memory if memsize is set too small

mov ah,000h ;keyboard - get keystroke
int 16h ;commit

;------------------------------------------------------------------------------
;the interrupt vector table contains addresses and we retrieve the address
; from interrupt 16h and save it into the jump address oldint
; do not forget to restore es, because we need it later

mov ah,035h ;get interrupt vector
mov al,016h ;for interrupt 16h
int 21h ;commit
;retuns es:bx=segment:offset
mov word [oldint+1],bx ;write offset to oldint
mov word [oldint+3],es ;write segment to oldint
push cs ;copy cs
pop es ;into es (restores es)

mov ah,025h ;set new interrupt vector
mov al,016h ;for interrupt 016h
mov dx,newint ;ds:dx = new address
int 21h ;commit

;------------------------------------------------------------------------------
;we set the filename (ds:dx) and parameters (es:bx) and then copy
; our own cs segment into the parameter block

mov dx,filename ;filename to load
mov bx,params ;parameter block (es:bx)
mov word [bx+00],cs ;code segment
mov word [bx+02],080h ;command tail offset
mov word [bx+04],cs ;code segment
mov word [bx+06],05Ch ;first FCB
mov word [bx+08],cs ;code segment
mov word [bx+10],06Ch ;second FCB
mov word [bx+12],cs ;code segment
mov ah,04Bh ;exec - load and/or execute
mov al,000h ;load and execute
Show last 44 lines
		int	21h					;commit
push ax ;save return code

;------------------------------------------------------------------------------
;we restore the 16h interrupt (ds:dx) from the old address in oldint

mov ah,025h ;set new interrupt vector
mov al,016h ;for interrupt 016h
mov dx,word [oldint+1] ;copy offset from oldint
mov ds,word [oldint+3] ;copy segment from oldint
int 21h ;commit
push cs ;copy cs
pop ds ;into ds (restores ds)

pop ax ;restore exec return code
mov ah,04Ch ;exit to dos
int 21h

;------------------------------------------------------------------------------
;this is our new interrupt 16h function we point to which jumps to the
; old interrupt afterwards. all registers that change must be saved and
; restored afterwards to allow normal execution of the old interrupt.
; in our case its only the flags.

newint:
pushf ;save flags

cmp ah,001h ;compare if ah=1
jnz exit ;if not exit
cmp word [0097h],00d75h ;compare data to
jnz exit ;if not exit
mov word [0097h],09090h ;write data to
exit:
popf ;restore flags
oldint:
jmp 0:0 ;jump to offset:segment
; and set on runtime
;------------------------------------------------------------------------------
message db 'La Colmena CGA/EGA/Hercules/VGA-16 loader.',0Dh,0Ah,
db '2019,vlan7',0Dh,0Ah,'$'
filename db 'COLMENA.EXE',0 ;filename to load
params times 22 db 000h ;parameter block is 22 bytes
; see int21h=4b documentation

Compile with NASM, and happy coding or whatever the reason is you visited this thread 😉

Last edited by Akuma on 2021-08-03, 16:21. Edited 2 times in total.