Secret keyboard sequence

Page 1/2
| 2

By thegeps

Paragon (1187)

thegeps's picture

05-01-2020, 01:50

Hi, which is the best way to implement keyboard sequence acquiring? I mean, enter a keyword in a title screen to activate cheatmode or music test, for example. I've implemented cheats in Freedom Fighter so I'll be able to test the game without make a perfect play all the times, but I don't wanna compile the source everytime to activate them. So I decide to use a keyword sequence and add music test and maybe some other easter eggs...

Login or register to post comments

By sd_snatcher

Prophet (3659)

sd_snatcher's picture

05-01-2020, 03:02

If you want to read keywords, the BIOS function pair CHSNS and CHGET are probably your best choice over SNSMAT. Because they will interpret the keyboard layout internally, while SNSMAT will give you just only the raw button presses and then you would have to deal with the many different layouts yourself.

By NYYRIKKI

Enlighted (6067)

NYYRIKKI's picture

05-01-2020, 06:47

Let me help:

CHSNS   EQU     #9C
CHGET   EQU     #9F


KEYWORD_SCAN:   ; Insert call to this to your H.TIMI (#FD9F) interrupt handler

        CALL    CHSNS
        RET     Z
        CALL    CHGET
        CP      #D             ;RETURN
        JR      Z,.CHECK_COMMANDS

        CALL    .UPPER_CASE

        LD      HL,BUFFER_END-1
        LD      DE,(BUFFER_POINTER)
        LD      (DE),A
        INC     DE
        XOR     A
        LD      (DE),A
        SBC     HL,DE
        RET     Z
        LD      (BUFFER_POINTER),DE
        RET


.CHECK_COMMANDS:
	LD	HL,.CMDS        ; Table with valid commands
        CALL    .CHECK_BUFFER
	LD	HL,BUFFER_START
        LD      (BUFFER_POINTER),HL
        LD      (HL),0
        RET

.CHECK_BUFFER:
	LD	DE,BUFFER_START
.CHECK_LOOP:
        LD	A,(DE)
	CPI
	JR	NZ,.TO_NEXT_CMD	; Not equal
	INC	DE
	AND	A
	JR	NZ,.CHECK_LOOP	; No end of instruction name, go checking
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
        POP     HL
        PUSH    DE
        JP      (HL)

.TO_NEXT_CMD:
	LD	C,0FFH
	XOR	A
	DEC     HL
	CPIR			; Skip to end of instruction name
	INC	HL
	INC	HL		; Skip address
	CP	(HL)
	JP	NZ,.CHECK_BUFFER; Not end of table, go checking
	RET

.UPPER_CASE:
        CP      "a"
        RET     C
        CP      "z"+1
        RET     NC
        AND     %11011111
        RET

;---------------------------
.CMDS:

; List of available instructions (as ASCIIZ) and execute address (as word)

	DEFB	"BEEP",0
	DEFW	#C0            ; Say beep

	DEFB	"MUSIC TEST",0
	DEFW	MUSIC_TEST_ROUTINE

	DEFB    "I CHEAT",0
	DEFW    ENABLE_ENDLESS_LIFES

	DEFB	0               ; No more commands

 ;---------------------------

BUFFER_POINTER:
        DEFW    BUFFER_START

BUFFER_START:
        DEFS    16
BUFFER_END:

By thegeps

Paragon (1187)

thegeps's picture

05-01-2020, 17:35

Thenk you, I'll test it tomorrow. Now I'm at a retrogame event, where Freedom Fighter is running on real hardware Smile

By Metalion

Paragon (1625)

Metalion's picture

05-01-2020, 19:48

NYYRIKKI wrote:

Let me help

Nice !
Thank you, I'll save that snippet of code.
Smile

By thegeps

Paragon (1187)

thegeps's picture

08-01-2020, 13:24

I forgot to say that it has to be in ROM, so I had changed it a little and now it works (less elegant than your solution, but I wanted to do it by myself). I didn't know before how to do 'cause I was almost sure that chget could have stop the execution. Then I read again how chget works (after your kindly help ) and understood Smile
Here's my code (tested ^^):

chget:			equ	09fh
chsns:			equ	09ch
beep:			equ	0c0h
buffer_start:		equ	0e906h
buffer_pointer:		equ	0e920h

keyword_scan:   		; Insert call to this to your H.TIMI (#FD9F) interrupt handler
	push	af
        call	chsns
	jp	z,simple_exit
	call	chget
	cp	0dh             ;RETURN
	jp	z,check_commands
	cp	"a"
        jp	c,no_cmd
	cp	"z"+1
	jp	nc,no_cmd

	ld	hl,buffer_start+16
	ld	de,(buffer_pointer)
	ld	(de),a
	sbc	hl,de
        jp	z,no_cmd
	inc	de
	ld	(buffer_pointer),de
simple_exit:
	pop	af
	ret

check_commands:
	ld	hl,cmds       ; Table with valid commands
check_buffer:
	ld	de,buffer_start
check_loop:
	ld	a,(de)
	cp	0
	jp	z,no_cmd
	cp	(hl)
	jp	nz,to_next_cmd	; Not equal
	inc	hl
	inc	de
	ld	a,(hl)
	cp	0
	jp	nz,check_loop	; No end of instruction name, go checking
	inc	hl
	ld	a,(hl)
	cp	1
	call	z,beep
	cp	2
	call	z,music_test_routine
	cp	3
	call	z,enable_cheats
no_cmd:
	ld	hl,buffer_start
	ld	(buffer_pointer),hl
	ld	b,16
erase_word:
	ld	(hl),0
	inc	hl
	djnz	erase_word
	pop	af
	ret

to_next_cmd:
	inc	hl
	ld	a,(hl)
	cp	0
	jp	nz,to_next_cmd
	inc	hl
	inc	hl
	ld	a,(hl)
	cp	255
	jp	z,no_cmd
	jp	check_buffer

cmds:
	db	"beep",0,1
	db	"musictest",0,2
	db	"icheat",0,3
	db	255               ; No more commands

enable_cheats:
	ld	a,1
	ld	(cheat),a
	ret

music_test_routine:
	ret

By NYYRIKKI

Enlighted (6067)

NYYRIKKI's picture

08-01-2020, 15:44

thegeps wrote:

I forgot to say that it has to be in ROM, so I had changed it a little and now it works (less elegant than your solution, but I wanted to do it by myself).

My routine works from ROM just as well as from RAM, but naturally you need to move BUFFER_POINTER, BUFFER_START and BUFFER_END to point to RAM... just as you did on your code... I just added these after the code to demonstrate how BUFFER_POINTER should be initialized. (As you probably realized already, you have to do it in init phase to avoid random addresses to be potentially overwritten)

Quote:

Here's my code (tested ^^):

Quickly reading it, it seems that you have adjusted the input logic a bit and then re-implemented rest of the functionality in somewhat longer & slower way.

In your code I can see at least one potential problem that may trigger when the implementation is complete... If you type "musictest" and hit RETURN the music test part will start... Now if you don't specifically disable this keyword system and you hit RETURN again the music test part will reboot... Although it is not much of a real problem, this is probably not what you wanted... Naturally if you want the keyword system to be active only ie. in title screen and not all the time the game is running then you will not see this problem... you just then need to include a way to disable this routine.

By thegeps

Paragon (1187)

thegeps's picture

08-01-2020, 16:22

yep, I know. The keyword system will be active only during title screen. I'd yet included a way to disable cheats. And as you see, after the table routines are axecuted, the keyword is erased to avoid a second RETURN hit mistake. Well I'll add the no_cmd part at the exit of music_test too, because It will exit from hook to be executed, so it will not return at no_cmd label of this routine.

Now some questions about your code (I want to understand it totally):
1- if the routine has to be in htimi, why haven't you put "push af" in the beginning and "pop af" on exit? I read that ISR is bugged and doesn't do it.This is why I had to get rid of all conditional RET instructions...
2-why put a zero after last char written in buffer? You have the buffer_pointer always reset to 0 but I don't get why...
3-before calling directly from jumptable the routines with "jp (hl)" you copy the address in DE, first lowbyte on E and then HIbyte on D. Then you do : pop hl, push de, jp (hl). I'm not sure ('cause I don't know how this code exactly works) but I think It would have push de (save de,jumptable routine address, in the stack), pop hl (copy from the stack previuos de value in hl), jp (hl)
4-how works jp (hl)? what exactly does? it works like jp or like call? it will automatically ret?

Thank you in advance ^_^

By NYYRIKKI

Enlighted (6067)

NYYRIKKI's picture

08-01-2020, 18:02

thegeps wrote:

yep, I know. The keyword system will be active only during title screen. I'd yet included a way to disable cheats. And as you see, after the table routines are axecuted, the keyword is erased to avoid a second RETURN hit mistake. Well I'll add the no_cmd part at the exit of music_test too, because It will exit from hook to be executed, so it will not return at no_cmd label of this routine.

You either have to disable this routine or put the "no_cmd" part to the beginning of "music_test" because other ways it will detect the keyword from buffer when return is pressed again... Executing it at end of the music_test is too late as user can hit return WHILE the test is executing.

Quote:

Now some questions about your code (I want to understand it totally):
1- if the routine has to be in htimi, why haven't you put "push af" in the beginning and "pop af" on exit? I read that ISR is bugged and doesn't do it.This is why I had to get rid of all conditional RET instructions...

Well, I expected that you have already a htimi handler that saves AF as you probably play music n' stuff... If that is not the case, then you can make one like:

INTHANDLER:
	PUSH AF
	CALL KEYWORD_SCAN
	POP AF
	RET
Quote:

2-why put a zero after last char written in buffer? You have the buffer_pointer always reset to 0 but I don't get why...

Well, just like with ".CMDS" I use ASCIIZ strings in buffer (= Zero terminated ASCII). As you may see, I don't ever clear the buffer, so it may contain random stuff and therefore I need a character to separate valid data from old trash data... You do practically same thing by clearing ALL of the buffer back to 0... but 1 byte is enough as you terminate the check anyway to first 0.

Quote:

3-before calling directly from jumptable the routines with "jp (hl)" you copy the address in DE, first lowbyte on E and then HIbyte on D. Then you do : pop hl, push de, jp (hl). I'm not sure ('cause I don't know how this code exactly works) but I think It would have push de (save de,jumptable routine address, in the stack), pop hl (copy from the stack previuos de value in hl), jp (hl)
4-how works jp (hl)? what exactly does? it works like jp or like call? it will automatically ret?

The "JP (HL)" is a bit nasty instruction as it has "wrong syntax" compared to what it does... the correct syntax would be "JP HL"... Practically if you do POP HL & JP (HL), it has same effect as with RET (except that the return address is then also placed to HL)... Now what I do here is therefore just swapping the execution order... I first POP the return address that was placed there by "CALL .CHECK_BUFFER", then I replace that return address with routine starting address that I got from ".CMDS" table. How ever as I explained at the top, I need to reset the buffer BEFORE the routine is executed, so I jump to the address that was POP:ed (routine after the CALL that resets the buffer) and then when that routine ends with RET it will actually start the routine that was read from the table.

By thegeps

Paragon (1187)

thegeps's picture

08-01-2020, 19:41

Actually my title screen is a black screen with the game name and a "push fire button" write... and this code:

	ld	hl,keyword_scan
	ld	a,l
	ld	(hook+1),a
	ld	a,h
	ld	(hook+2),a
	ld	a,0cdh		;opcode for call
	ld	(hook),a

wait_for_fire:
	xor	a
	call	gttrig
	jp	nz,keyb_select
	ld	a,1
	call	gttrig
	jp	z,wait_for_fire
	ld	a,1
	ld	(player_input),a
	jp	mainscreen_exit
keyb_select:
	xor	a
	ld	(player_input),a
mainscreen_exit:	
	ld	a,0c9h		;opcode for RET
	ld	(hook),a
	call	41h

I was planning to add music simply by add:

	ld	hl,jiffy
	ld	a,(hl)
wait_vblank:
	cp	(hl)
	jp	z,wait_vblank
	call	PLY_AKG_PLAY

By NYYRIKKI

Enlighted (6067)

NYYRIKKI's picture

08-01-2020, 20:16

As long as it is this simple, you are not planning any story telling or animations, it really does not matter how you do this stuff... You can just as well forget the whole interrupt thing and just add "CALL KEYWORD_SCAN" after "CALL PLY_AKG_PLAY" and be done with it... You can add interrupt handler later if your plan becomes more complex.

Page 1/2
| 2