1
Hi to all,
I had some fun in the last days while investigating the USER SUBROUTINE of the Neo Geo MVS system (https://wiki.neogeodev.org/index.php?title=USER_subroutine).
The goal was to find out the System BIOS reactions on a coin throw-in and how to integrate this into C code.

I have created three program loops - DEMO. TITLE and GAME:

DEMO mode (blue background)
In the DEMO mode usally some game play, the game title and a highscore list is shown until a coin has been inserted into the MVS machine.
When the demo program is finished and no coin has been inserted, the game software gives the control back to the BIOS. If it is a single-slot system
the BIOS will restart the DEMO mode again. If it is an multi-slot MVS system the BIOS will switch to the DEMO of the next game in the next game slot.
Depending on the cabinet settings the user can switch through all game demos by pressing the SELECT_BUTTON or JOYSTICK_DOWN.

TITLE mode (green background)
When a coin has been inserted, the BIOS exits the DEMO mode and switches to the TITLE mode where the game title is displayed and 1 credit is added
to the player. In this stage the user can still switch to an other game in multi-slot MVS systems by pressing SELECT_BUTTON or JOYSTICK_DOWN.
Also, it is possible for the user to insert additional coins to gain more credits. If the user presses the START_BUTTON, 1 credit is decremented and the
BIOS exits the TITLE mode and switches to the GAME mode. When the TITLE mode is started the BIOS starts a timer (BIOS_COMPULSION_TIMER)
which forces a game start even if the user DID NOT press the START_BUTTON. This timer can be adjusted in the cabinet settings by the cabinet owner
(SETTING UP THE SOFT DIP > SETTING UP THE CABINET).

GAME mode (yellow background)
In the GAME mode the game is started and the user can insert coins anytime to gain more credits. The following part doesn't work in my
code because it is possible for the user to press the START_BUTTON anytime and anytime a credit is decremented. In the actual code the user can exit
the GAME mode by pressing the A_BUTTON and the game software gives the control back to the BIOS. The BIOS switches to the DEMO mode if there is
no credit left or to the TITLE mode if there are at least one credit left.

But it should work like this: If a game is lost a CONTINUE option is shown which counts down from 10 to 0. If the user presses the START_BUTTON in this
stage (only then and not during the game play) 1 credit is decremented and the game continues, if not a "GAME OVER" message/screen
is displayed and the game software gives the control back to the BIOS.

Does somebody have an idea how to disable the START_BUTTON during the gameplay?

Here is a video of the current state:



And this is the C code:
#include <stdio.h>
#include <stdlib.h>
#include <input.h>
#include <DATlib.h>
#include "externs.h"

typedef struct bkp_ram_info {
 								WORD debug_dips;
 								BYTE stuff[254];
								//256 bytes backup block
 							}
bkp_ram_info;
bkp_ram_info bkp_data;

BYTE p1,p2,ps,p1e,p2e;

const int hexdec_to_dec_tbl[160] =
{
     0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0,  0,  0,  0,  0, // 15
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,  0,  0,  0,  0,  0,  0, // 31
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,  0,  0,  0,  0,  0,  0, // 47
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,  0,  0,  0,  0,  0,  0, // 63
    40, 41, 42, 43, 44, 45, 46, 47, 48, 49,  0,  0,  0,  0,  0,  0, // 79
    50, 51, 52, 53, 54, 55, 56, 57, 58, 59,  0,  0,  0,  0,  0,  0, // 95
    60, 61, 62, 63, 64, 65, 66, 67, 68, 69,  0,  0,  0,  0,  0,  0, // 111
    70, 71, 72, 73, 74, 75, 76, 77, 78, 79,  0,  0,  0,  0,  0,  0, // 127
    80, 81, 82, 83, 84, 85, 86, 87, 88, 89,  0,  0,  0,  0,  0,  0, // 143
    90, 91, 92, 93, 94, 95, 96, 97, 98, 99,  0,  0,  0,  0,  0,  0, // 159
};

// Demo Loop ///////////////////////////////////////////////////////////////////////////////////////////

void demo()
{
	int demo_duration=1200;

	volMEMWORD(0x401ffe)=0x7022; // background color
	volMEMWORD(0x400002)=0x79BB; // fix layer font color
	volMEMWORD(0x400004)=0x7022; // fix layer background color

	LSPCmode=0x900;

	initGfx();
	clearFixLayer();
	clearSprites(1, 381);

	SCClose();

	fixPrintf( 2, 3,0,0,"USER subroutine 0.01");
	fixPrintf( 2, 4,0,0,"------------------------------------");

	volMEMBYTE(0x10FDAF)=0x01; // sets BIOS-USER-MODE to 1 (Title/Demo),
	volMEMBYTE(0x10FDB6)=0x00; // sets BIOS-PLAYER-MOD1 (Player 1 status) to 0 (Never played)

	do{
		wait_vblank();
		SCClose();

		demo_duration-=1;
		if(demo_duration==0)
		{
			demo_duration=1200;
			asm("jmp 0xC00444"); // BIOSF_SYSTEM_RETURN tells the system the demo loop has been finished, will switch to the next game in multi-slot systems
		}

		fixPrintf( 2, 6,0,0,"DEMONSTRATION LOOP");
		fixPrintf( 2, 7,0,0,"(restarts in %02d sec)", demo_duration/60);

		fixPrintf( 2,10,0,0,"INSERT COIN (P1)");

		fixPrintf( 2,14,0,0,"CREDITS PLAYER 1 : %02d", hexdec_to_dec_tbl[volMEMBYTE(0xD00034)]);	// credit counter player 1
		fixPrintf( 2,15,0,0,"COIN TIMER       : %02d", volMEMBYTE(0xD00038));	// counts down 30 frames after coin throw-in

		fixPrintf( 2,18,0,0,"BIOS-USER-REQUEST: %02d", volMEMBYTE(0x10FDAE));	// 0 = Init, 1 = Boot animation, 2 = Demo, 3 = Game (set by SYSTEM BIOS)
		fixPrintf( 2,19,0,0,"BIOS-USER-MODE   : %02d", volMEMBYTE(0x10FDAF));	// Used by the game to tell what it's doing: 0:Init/Boot animation, 1:Title/Demo, 2:Game
		fixPrintf( 2,20,0,0,"BIOS-PLAYER-MOD1 : %02d", volMEMBYTE(0x10FDB6));	// Player 1 status. 0:Never played, 1:Playing, 2:Continue option being displayed, 3:Game over

	}while(!(hexdec_to_dec_tbl[volMEMBYTE(0xD00034)]>0)); // exit loop if credit counter is bigger than zero
}


// Title Loop ///////////////////////////////////////////////////////////////////////////////////////////

void title()
{

	volMEMWORD(0x401ffe)=0x1351; // background color
	volMEMWORD(0x400002)=0x6BDA; // fix layer font color
	volMEMWORD(0x400004)=0x1351; // fix layer background color

	LSPCmode=0x900;

	initGfx();
	clearFixLayer();
	clearSprites(1, 381);

	SCClose();

	fixPrintf( 2, 3,0,0,"USER subroutine 0.01");
	fixPrintf( 2, 4,0,0,"------------------------------------");

	volMEMBYTE(0x10FDAF)=0x01; // sets BIOS-USER-MODE to 1 (Title/Demo),
	volMEMBYTE(0x10FDB6)=0x00; // sets BIOS-PLAYER-MOD1 (Player 1 status) to 0 (Never played)

	do{
		wait_vblank();
		SCClose();

		fixPrintf( 2, 6,0,0,"TITLE SCREEN LOOP");

		fixPrintf( 2,10,0,0,"PRESS START (P1)");
		fixPrintf( 2,11,0,0,"BIOS-COMPULSION-TIMER: %02d", hexdec_to_dec_tbl[volMEMBYTE(0x10FDDA)]); // BIOS_COMPULSION_TIMER - forced start when counter reaches zero

		fixPrintf( 2,14,0,0,"CREDITS PLAYER 1 : %02d", hexdec_to_dec_tbl[volMEMBYTE(0xD00034)]);	// credit counter player 1
		fixPrintf( 2,15,0,0,"COIN TIMER       : %02d", volMEMBYTE(0xD00038));	// counts down 30 frames after coin throw-in

		fixPrintf( 2,18,0,0,"BIOS-USER-REQUEST: %02d", volMEMBYTE(0x10FDAE));	// 0 = Init, 1 = Boot animation, 2 = Demo, 3 = Game (set by SYSTEM BIOS)
		fixPrintf( 2,19,0,0,"BIOS-USER-MODE   : %02d", volMEMBYTE(0x10FDAF));	// Used by the game to tell what it's doing: 0:Init/Boot animation, 1:Title/Demo, 2:Game
		fixPrintf( 2,20,0,0,"BIOS-PLAYER-MOD1 : %02d", volMEMBYTE(0x10FDB6));	// Player 1 status. 0:Never played, 1:Playing, 2:Continue option being displayed, 3:Game over


	}while(!(hexdec_to_dec_tbl[volMEMBYTE(0x10FDDA)]==0)); // exit loop if BIOS-COMPULSION-TIMER is equal to zero
}

// Game Loop ///////////////////////////////////////////////////////////////////////////////////////////

void game()
{
	volMEMWORD(0x401ffe)=0x5872; // background color
	volMEMWORD(0x400002)=0x4ED9; // fix layer font color
	volMEMWORD(0x400004)=0x5872; // fix layer background color

	LSPCmode=0x900;

	initGfx();
	clearFixLayer();
	clearSprites(1, 381);

	SCClose();

	fixPrintf( 2, 3,0,0,"USER subroutine 0.01");
	fixPrintf( 2, 4,0,0,"------------------------------------");

	volMEMBYTE(0x10FDAF)=0x02; // change BIOS-USER-MODE to 2 (Game), it stops the BIOS_COMPULSION_TIMER
	volMEMBYTE(0x10FDB6)=0x01; // sets Player 1 status to playing
	volMEMBYTE(0x10FEC5)=0x01; // stops the bios calling command 3 twice after game over if credits are already in the system (skip attract mode)

	do{
		wait_vblank();
		SCClose();

		p1=volMEMBYTE(P1_CURRENT); // read P1 inputs
	//	ps=volMEMBYTE(PS_CURRENT);

		if(p1&JOY_A)
		{
			asm("jmp 0xC00444"); // BIOSF_SYSTEM_RETURN
		}

		fixPrintf( 2, 6,0,0,"GAME PLAY LOOP");
		fixPrintf( 2, 7,0,0,"FRAMES: %06d",	DAT_frameCounter);

		fixPrintf( 2,10,0,0,"PRESS A-BUTTON (P1) TO EXIT");
		fixPrintf( 2,11,0,0,"BIOS-COMPULSION-TIMER: %06d", hexdec_to_dec_tbl[volMEMBYTE(0x10FDDA)]); // BIOS_COMPULSION_TIMER

		fixPrintf( 2,14,0,0,"CREDITS PLAYER 1 : %02d", hexdec_to_dec_tbl[volMEMBYTE(0xD00034)]);	// credit counter player 1
		fixPrintf( 2,15,0,0,"COIN TIMER       : %02d", hexdec_to_dec_tbl[volMEMBYTE(0xD00038)]);	// counts down 30 frames after coin throw-in

		fixPrintf( 2,18,0,0,"BIOS-USER-REQUEST: %02d", volMEMBYTE(0x10FDAE));	// 0 = Init, 1 = Boot animation, 2 = Demo, 3 = Game (set by SYSTEM BIOS)
		fixPrintf( 2,19,0,0,"BIOS-USER-MODE   : %02d", volMEMBYTE(0x10FDAF));	// Used by the game to tell what it's doing: 0:Init/Boot animation, 1:Title/Demo, 2:Game
		fixPrintf( 2,20,0,0,"BIOS-PLAYER-MOD1 : %02d", volMEMBYTE(0x10FDB6));	// Player 1 status. 0:Never played, 1:Playing, 2:Continue option being displayed, 3:Game over


	}while(!(p1&JOY_A));  // exit loop if BUTTON A is pressed
}

// Main Loop ///////////////////////////////////////////////////////////////////////////////////////////

int main(void)
{
	while(1)
	{
		if(volMEMBYTE(0xD00034)==0){demo();} // switch to demo if credits are zero
		title();
		game();
	}
}
2
That's not at all how it's supposed to be.

You have to edit the asm header file:
_ENTRY_USER: |;* Setup stack pointer and 'system' pointer |;stack is set up by bios |;* Reset watchdog move.b d0, 0x300001 |;* Flush interrupts move.b #7, 0x3C000C move.l #0, TInextTable |;* Enable interrupts move.w #0x2000,sr jmp USER _ENTRY_COIN_SOUND: jmp COIN_SOUND rts _ENTRY_PLAYER_START: jmp PLAYER_START rts _ENTRY_DEMO_END: jmp DEMO_END rts
Then your main.c needs the 4 event handlers: void USER(void) { //USER //process USER_REQUEST //return to bios control __asm__ ( "jmp 0xc00444 \n" //SYSTEM_RETURN ); } void PLAYER_START(void) { //process/update START_FLAG } void DEMO_END(void) { //end demo (clear display / update status for sram save / whatever needed) } void COIN_SOUND(void) { //play coin sound }
There's no more main() in this config, everything runs from thoses 4 entry points.
3
Hi HPMAN,

thanks a lot for your code - it has pointed me into the right direction grin
Blocking the P1/P2 Start button works now (during game play and game over) also the "coin sound" and "demo end" routine.
Could you please have a look on the following code and tell me your opinion?

Here is a video of the actual state:



And here is the code:

crt0_cart.s
********************* Vector Definitions *********************
_IRQ1	=	DAT_TIfunc
_IRQ2	= DAT_vblankTI
_IRQ3	=	_irq3_handler
_ENTRY_USER	= _entry_user
_ENTRY_PLAYER_START	= _entry_player_start
_ENTRY_DEMO_END	= _entry_demo_end |; MVS game switch
_ENTRY_COIN_SOUND	= _entry_coin_sound |; coin sound

************************ Definitions *************************
_NGH	=	0x7777
_PROGRAM_SIZE	=	0x00100000
_WRK_BCKP_AREA = bkp_data
_WRK_BCKP_AREA_SIZE = 0x0100	|;256bytes
_EYE_CATCHER = 0x02	/*;eye catcher (0-common 1-custom 2-off)*/
_EYE_CATCHER_TILES = 0x01 /*;eye catcher start tiles (upper bits, 0x01 => 0x0100)*/

	.include	"common_crt0_cart.s"

* Names MUST be 16 characters long
*           <---------------->
JPconfig:
	.ascii	"HELLO WORLD JP  "
	.long	0xFFFFFFFF
	.word	0x0364
	.byte	0x14, 0x13, 0x24, 0x01

NAconfig:
	.ascii	"HELLO WORLD US  "
	.long	0xFFFFFFFF
	.word	0x0364
	.byte	0x14, 0x13, 0x24, 0x01
	
EUconfig:
	.ascii	"HELLO WORLD EU  "
	.word	0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
	.end

common_crt0_cart.s
	* ++====================================================================++
	* || common_crt0_cart.s - C Run Time Startup Code for Neo Geo Cartridge	||
	* ++--------------------------------------------------------------------++
	* || $Id: common_crt0_cart.s,v 1.5 2001/07/13 14:46:31 fma Exp $		||
	* ++--------------------------------------------------------------------++
	* || This is the startup code needed by programs compiled with GCC		||
	* ++--------------------------------------------------------------------||
	* || BGM: Guitar Vader - S.P.Y.											||
	* ++====================================================================++

_ZERO_DIVIDE	=	0x00c00426
_CHK_CMD	=	0x00c00426
_TRAPV_CMD	=	0x00c00426
_NPC_1010	=	0x00c00426
_NPC_1111	=	0x00c00426
_IRQ4	=	0x00c00426
_IRQ5	=	0x00c00426
_IRQ6	=	0x00c00426
_IRQ7	=	0x00c00426

********************** Exported Symbols **********************
	.globl	_start
	.globl	atexit

********************** Imported Symbols **********************
	.globl	__do_global_ctors
	.globl	__do_global_dtors
	.globl	main
	.globl	memset
	.globl	__bss_start	
	.globl	_end

********************** Program Start *************************

** NOTE: Cartridge systems have swapped IRQ1 and IRQ2

	.org	0x0000

	.long	0x0010f300	|;reset stack ptr
	.long	0x00c00402	/*;reset ptr*/
	.long	0x00c00408	/*;bus error*/
	.long	0x00c0040e	/*;address error*/

	.long	0x00c00414	/*;illegal instruction*/
	.long	_ZERO_DIVIDE	/*;division by 0*/
	.long	_CHK_CMD	/*;CHK command*/
	.long	_TRAPV_CMD	/*;TRAPV command*/

	.long	0x00c0041a	/*;illegal privilege*/
	.long	0x00c00420	/*;trace exception handling*/
	.long	_NPC_1010	/*;no package command (1010)*/
	.long	_NPC_1111	/*;no package command (1111)*/

	.long	0x00c00426, 0x00c00426, 0x00c00426	/*;unused*/
	.long	0x00c0042c	/*;uninitialized interrupt*/

	.long	0x00c00426,	0x00c00426,	0x00c00426,	0x00c00426	/*;unused*/
	.long	0x00c00426,	0x00c00426,	0x00c00426,	0x00c00426	/*;unused*/
	.long	0x00c00432	/*;virtual interrupt*/
	|; 0x64
	.long _IRQ2, _IRQ1, _IRQ3, _IRQ4, _IRQ5, _IRQ6, _IRQ7	/*;4-7 unused*/

__security_code:
	.long 0x76004a6d, 0x0a146600, 0x003c206d, 0x0a043e2d
	.long 0x0a0813c0, 0x00300001, 0x32100c01, 0x00ff671a
	.long 0x30280002, 0xb02d0ace, 0x66103028, 0x0004b02d
	.long 0x0acf6606, 0xb22d0ad0, 0x67085088, 0x51cfffd4
	.long 0x36074e75, 0x206d0a04, 0x3e2d0a08, 0x3210e049
	.long 0x0c0100ff, 0x671a3010, 0xb02d0ace, 0x66123028
	.long 0x0002e048, 0xb02d0acf, 0x6606b22d, 0x0ad06708
	.long 0x588851cf, 0xffd83607
	.word 0x4e75

	.org 0x100
	.ascii	"NEO-GEO\0"
	.word	_NGH
	.long		_PROGRAM_SIZE
	.long		_WRK_BCKP_AREA
	.word	_WRK_BCKP_AREA_SIZE
	
	.byte	_EYE_CATCHER
	.byte	_EYE_CATCHER_TILES
	
	.long	JPconfig
	.long	NAconfig
	.long	EUconfig
	
	jmp	_ENTRY_USER
	jmp	_ENTRY_PLAYER_START
	jmp	_ENTRY_DEMO_END
	jmp	_ENTRY_COIN_SOUND

_irq3_handler:
	move.w	#1, 0x3C000C
_dummy_exc_handler:
	rte
atexit:	|;Dummy atexit (does nothing for now)
	moveq	#0, d0
_dummy_config_handler:
	rts

_irq1_handler:	|;Standard IRQ1 handler
	move.w	#2, 0x3C000C
	rte

	.org	0x182
	.long	__security_code

_dummyTIdata:
	.word 0x0000

|;* Entry point of our program
_start:
	|;* Setup stack pointer and 'system' pointer
	|;lea		0x10F300,a7
	|;lea		0x108000,a5

	|;* Reset watchdog
	move.b	d0, 0x300001
	
	|;* Flush interrupts
	move.b	#7, 0x3C000C

	move.l #0, TInextTable
	
	|;* Enable interrupts
	move.w	#0x2000,sr
	
	|;* Initialize BSS section
	move.l	#_end, d0
	sub.l	#__bss_start, d0
	move.l	d0, -(a7)
	clr.l	-(a7)
	pea		__bss_start
	jbsr	memset
	
	jsr	0xc004c2	|; FIX_CLEAR
	jsr	0xc004c8	|; LSP_1st
	
	|;* Jump to main
	|;* jbsr main

	|;* Call global destructors
	jbsr	__do_global_dtors

	|;* For cart systems, infinite loop
9:
	jmp	9b(pc)


_entry_user:
	|;* Setup stack pointer and 'system' pointer
	|;* stack is set up by bios

	|;* Reset watchdog
	move.b	d0, 0x300001
	
	|;* Flush interrupts
	move.b	#7, 0x3C000C

	move.l #0, TInextTable
	
	|;* Enable interrupts
	move.w	#0x2000,sr

	jmp	USER

_entry_player_start:
	jmp PLAYER_START
	rts
	
_entry_demo_end:
	jmp DEMO_END
	rts
	
_entry_coin_sound:
	jmp COIN_SOUND
	rts

main.c
#include <stdio.h>
#include <stdlib.h>
#include <input.h>
#include <DATlib.h>
#include "externs.h"

typedef struct bkp_ram_info {
 								WORD debug_dips;
 								BYTE stuff[254];
								//256 bytes backup block
 							}
bkp_ram_info;
bkp_ram_info bkp_data;

BYTE p1,p2,ps,p1e,p2e;

const int hexdec_to_dec_tbl[160] =
{
     0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0,  0,  0,  0,  0, // 15
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,  0,  0,  0,  0,  0,  0, // 31
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,  0,  0,  0,  0,  0,  0, // 47
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,  0,  0,  0,  0,  0,  0, // 63
    40, 41, 42, 43, 44, 45, 46, 47, 48, 49,  0,  0,  0,  0,  0,  0, // 79
    50, 51, 52, 53, 54, 55, 56, 57, 58, 59,  0,  0,  0,  0,  0,  0, // 95
    60, 61, 62, 63, 64, 65, 66, 67, 68, 69,  0,  0,  0,  0,  0,  0, // 111
    70, 71, 72, 73, 74, 75, 76, 77, 78, 79,  0,  0,  0,  0,  0,  0, // 127
    80, 81, 82, 83, 84, 85, 86, 87, 88, 89,  0,  0,  0,  0,  0,  0, // 143
    90, 91, 92, 93, 94, 95, 96, 97, 98, 99,  0,  0,  0,  0,  0,  0, // 159
};

void USER(void)
{
	int demo_timer=600;
	int continue_timer=0;
	int game_over_timer=0;
	int flash_timer=0;

	LSPCmode=0x900;

	initGfx();
	clearFixLayer();
	clearSprites(1, 381);

	SCClose();

	while(1)
	{
		wait_vblank();
		SCClose();
		p1=volMEMBYTE(P1_CURRENT); // read P1 inputs

		flash_timer+=1;
		if(flash_timer == 60) flash_timer=0;

		// DEMO MODE /////////////////////////////////////////////////////////////////////////////////////////

		if(volMEMBYTE(0x10FDAE)==2 && volMEMBYTE(0x10FDAF)==1) // user_request=2, user_mode=1
		{
			volMEMWORD(0x401ffe)=0x7022; // background color
			volMEMWORD(0x400002)=0x79BB; // fix layer font color
			volMEMWORD(0x400004)=0x7022; // fix layer background color

			fixPrintf( 2, 4,0,0,"============ DEMO MODE ============ ");
			fixPrintf( 2, 5,0,0,"TIMER: %02d",	demo_timer/60);

			if(flash_timer>30)	fixPrintf(14, 9,0,0,"INSERT COIN");
			else				fixPrintf(14, 9,0,0,"           ");

			demo_timer-=1;
			if(demo_timer==0)
			{
				break; // exit loop and call BIOSF_SYSTEM_RETURN (tells the system that the demo has been finished)
			}
		}

		// TITLE MODE ////////////////////////////////////////////////////////////////////////////////////////

		if(volMEMBYTE(0x10FDAE)==3 && volMEMBYTE(0x10FDAF)==1) // user_request=3, user_mode=1
		{
			volMEMWORD(0x401ffe)=0x1351; // background color
			volMEMWORD(0x400002)=0x6BDA; // fix layer font color
			volMEMWORD(0x400004)=0x1351; // fix layer background color

			fixPrintf( 2, 4,0,0,"============ TITLE MODE ============");
			fixPrintf(18, 7,0,0,"%02d", hexdec_to_dec_tbl[volMEMBYTE(0x10FDDA)]);

			if(flash_timer>30)	fixPrintf(14, 9,0,0,"PRESS START");
			else				fixPrintf(14, 9,0,0,"           ");
		}

		// GAME MODE /////////////////////////////////////////////////////////////////////////////////////////

		if(volMEMBYTE(0x10FDAE)==3 && volMEMBYTE(0x10FDAF)==2) // user_request=3, user_mode=2
		{
			volMEMWORD(0x401ffe)=0x5872; // background color
			volMEMWORD(0x400002)=0x4ED9; // fix layer font color
			volMEMWORD(0x400004)=0x5872; // fix layer background color

			fixPrintf( 2, 4,0,0,"============ GAME MODE ============ ");
			fixPrintf(15, 9,0,0,"PRESS (A)");

			if(p1&JOY_A)
			{
				continue_timer=600;
			}

			if(continue_timer>0)
			{
				if(continue_timer==600){volMEMBYTE(0x10FDB6)=0x02;} // sets BIOS-PLAYER-MOD1 to 2 (P1 Continue Option)

				continue_timer-=1;

				fixPrintf(15, 6,0,0,"CONTINUE?");
				fixPrintf(18, 7,0,0,"%02d", continue_timer/60);
				if(volMEMBYTE(0xD00034)>0)  fixPrintf(14, 9,0,0,"PRESS START");	// credits are available
				if(volMEMBYTE(0xD00034)==0) fixPrintf(14, 9,0,0,"INSERT COIN");	// no credits available

				if(continue_timer==0) // player has not pressed START, start game_over_timer
				{
					game_over_timer=300;	// start game_over_timer
					clearFixLayer();		// clear message
				}

				if(volMEMBYTE(0x10FDB6)==1 ) // BIOS-PLAYER-MOD1 = 1 >> player has pressed START - continue game
				{
					continue_timer=0;	// reset timer
					clearFixLayer();	// clear message
				}
			}

			if(game_over_timer>0)
			{
				if(game_over_timer==300){volMEMBYTE(0x10FDB6)=0x03;} // sets BIOS-PLAYER-MOD1 to 3 (P1 Game Over)

				game_over_timer-=1;
				fixPrintf(15, 9,0,0,"GAME OVER");
				fixPrintf(18,10,0,0,"%02d", game_over_timer/60);

				if(game_over_timer==0)
				{
					volMEMBYTE(0x10FDB6)=0x00;	 // sets BIOS-PLAYER-MOD1 to 0 (P1 Never Played)
					break;						 // exit loop and call BIOSF_SYSTEM_RETURN (will restart the demo)
				}
			}
		}

		fixPrintf( 2,14,0,0,"CREDITS: %02d", hexdec_to_dec_tbl[volMEMBYTE(0xD00034)]);

		fixPrintf( 2,23,0,0,"START FLAG : %02d", volMEMBYTE(0x10FDB4));			// indicates which player has pressed the START_BUTTON (P1=1, P2=2)
		fixPrintf( 2,24,0,0,"BIOS-USER-REQUEST: %02d", volMEMBYTE(0x10FDAE));	// 0 = Init, 1 = Boot animation, 2 = Demo, 3 = Game (set by SYSTEM BIOS)
		fixPrintf( 2,25,0,0,"BIOS-USER-MODE   : %02d", volMEMBYTE(0x10FDAF));	// Used by the game to tell what it's doing: 0:Init/Boot animation, 1:Title/Demo, 2:Game
		fixPrintf( 2,26,0,0,"BIOS-PLAYER-MOD1 : %02d", volMEMBYTE(0x10FDB6));	// Player 1 status. 0:Never played, 1:Playing, 2:Continue option being displayed, 3:Game over
		fixPrintf( 2,27,0,0,"BIOS-COMPULSION-TIMER: %02d", hexdec_to_dec_tbl[volMEMBYTE(0x10FDDA)]); // timer for forced game start
		fixPrintf( 2,28,0,0,"FRAMES: %06d",	DAT_frameCounter);
	}

	__asm__ ("jmp 0xc00444 \n"); // BIOSF_SYSTEM_RETURN - return to bios control
}

void PLAYER_START(void) // is called by the BIOS if user has pressed the START_BUTTON
{
	//process/update START_FLAG

	if(volMEMBYTE(0x10FDB6)==3) // BIOS-PLAYER-MOD1 = 3 >> Player 1 is in the GAME OVER state, pressing START is disabled (to avoid unwanted credit decrement)
	{
		if(volMEMBYTE(0x10FDB4)==1) volMEMBYTE(0x10FDB4)=0x00; // deactivate P1 START_BUTTON
		if(volMEMBYTE(0x10FDB4)==2) volMEMBYTE(0x10FDB4)=0x00; // deactivate P2 START_BUTTON
	}

	if(volMEMBYTE(0x10FDB6)==2) // BIOS-PLAYER-MOD1 = 2 >> Continue option being displayed, pressing START is allowed
	{
		volMEMBYTE(0x10FDB6)=0x00; // sets BIOS-PLAYER-MOD1 to 0 (P1 Never Played) to decrement 1 credit (set it directly to 1 will not work)
	}

	if(volMEMBYTE(0x10FDB6)==1) // BIOS-PLAYER-MOD1 = 1 >> Player 1 is currently playing, pressing START is disabled (to avoid unwanted credit decrement)
	{
		if(volMEMBYTE(0x10FDB4)==1) volMEMBYTE(0x10FDB4)=0x00; // deactivate P1 START_BUTTON
		if(volMEMBYTE(0x10FDB4)==2) volMEMBYTE(0x10FDB4)=0x00; // deactivate P2 START_BUTTON
	}

	if(volMEMBYTE(0x10FDB6)==0) // BIOS-PLAYER-MOD1 = 0 >> Player 1 has started the game for the first time
	{
		volMEMBYTE(0x10FDAF)=0x02;	// set BIOS-USER-MODE to 2 (GAME), it stops the BIOS_COMPULSION_TIMER
		volMEMBYTE(0x10FDB6)=0x01;	// set BIOS-PLAYER-MOD1 to 1 (P1 Playing), it deactivates the START_BUTTON
		clearFixLayer(); 			// clear TITLE screen
	}

}

void DEMO_END(void) // is called by the BIOS if user has inserted a COIN and exits the DEMO
{
	//end demo (clear display / update status for sram save / whatever needed)
	// fixPrintf(15, 4,0,0,"DEMO END");
	clearFixLayer();
}

void COIN_SOUND(void) // is called by the BIOS if user has inserted a COIN
{
	(*((PBYTE)0x320000)) = (0x14); //play coin sound
}

4
Looks ok, lacks USER command 0.

Also credit count must be read from a different place depending of the system mode (dev/retail, mvs/home)
5
Hi HPMAN,

thank you a lot for checking the code - I am very happy that the USER subroutine now works as intended. boing
I will add "USER command 0" to the code when I have investigated how to deal with the software dips.

Could you please explain some more details about the location of the credit counter which depends on the system mode ?
According to the neogeodev wiki the credit counters are located at 0xD00034 to 0xD00035, thats why I have read the data from this location.
But it seems both counters (for Player 1 and Player 2) are aggregated in a "BCD" format. Does BCD mean "Binary Coded Decimal" and if yes, is there a way to display it properly?

Sources:
"$D00034 to $D00035 "Internal" credit counters for player 1 and player 2 (BCD)" (https://wiki.neogeodev.org/index.php?title=Backup_RAM)
"Credits can be read through $D00034 and $D00035." (https://wiki.neogeodev.org/index.php?title=CREDIT_DOWN)
6
To convert a byte from BCD to decimal :
x = ((x >> 4) * 10) + (x & 0xF);
avatarZeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo
7
If I'm not mistaken, the check order is:

Dev mode? 0x10fe00 - 0x10fe01 (probably due tu dev being made & debugged on home systems)
Retail MVS? 0xd00034 - 0xd00035
Home (last case) is wherever you put your own counter.


Also account for difference is US and jp/euro modes.

US: display 2 credit counters, pressing 2P start starts a single player game on 2P side
JP/Euro: singe credit counter, pressing 2P starts a 2 player game.
8
@Zerosquare
Thank you for that code snipped - will try it out smile

@HPMAN
Thank you for the additional information, it caused an interesting exploration into the world of country codes. wink
I didn't knew before that only the US bios versions have seperate coin counters for Player 1 and Player 2.
Seems that the thematic is more complex than I thought before - will post updates as soon as available.
9
Hi,

in the current version the user subroutine detects the bios version (MVS/AES) and bios country code ( (JAP/USA/EUR)
and acts accordingly to it (MVS mode with shared or independent credit counters, different P1/P2 START button
behavoir, AES mode with "virtual" credits). The routine now can handle two players (single or simultaneous game play).
Also, I have added user_request=0 to the code and now I can read the data from the bios soft dips, load default highscore
data and save new highscore data to the MVS backup RAM. Overall, I am pretty happy with it and just want to say again:
Thank you HPMAN sun

Here is a video of the actual status:

Great work. smile
GREAT JOB!
Hi to all,
I have added "development mode credit counters" to the program which are needed when Universe Bios on AES (Home) systems "simulates" MVS (Arcade) mode.
Also, "Free Play mode" on MVS systems (hard dip switch 6 active) is now handled correctly (boots into AES mode with default or previously saved MVS soft dip data ).

I have tested the USER SUBROUTINE program with any available BIOS in different emulators and I can confirm that it works on the following "real" systems:
MVS MV-2F - Universe Bios 3.2
MVS MV-2F - EUR stock bios
MVS MV-1C - UniBios 3.1
MVS MV-1C - EUR stock bios
MVS MV-1FZ - EUR stock bios
AES - UniBios 3.1

The program is missing a memory card save function and it needs a routine for handling highscore saves (score sorting, user input of 3-letter initials) but anyway
here is the code of the actual version. Feel free to use this code for your game dev projects.

http://www.neohomebrew.com/downloads/main.c
http://www.neohomebrew.com/downloads/crt0_cart.s
http://www.neohomebrew.com/downloads/common_crt0_cart.s
Thanks for sharing
Just want to share that I have received a bugfix from Jeff (NeoBitz) which prevents loading the title screen twice if the game loop is finished
but still credits available (will cause a screen flicker, like shown at minute 2:18 in the third video). To fix this issue you need to add a
BIOS_COMPULSION_FLAG at the start of the "Title Mode":
volMEMBYTE(0x10FEC5)=0x01; // BIOS_COMPULSION_FLAG
Thx NeoHomeBrew
Hi, does somebody have the externs.h file?
avatar
Hi,
the externs.h file is generated by DATlib (buildchar.exe) and contains the variable names of graphics (inside of pictureInfo, spriteInfo, paletteInfo) and includes the .h files for animations.
Because the USER SUBROUTINE demo don't use any graphics including the externs.h is not really needed and can be commented out.
Thanks, but when I comment th line, i get errors "called object is not a function" on all wait_vblank() s.
avatar
If you use DATlib 0.2 the function wait_vblank(); has been replaced with waitVBlank(); -- maybe this solves the problem?