NimotsuKun for PV-1000

ゲームプログラマになる前に覚えておきたい技術

http://www.shuwasystem.co.jp/support/7980html/2118.html

f:id:tanam:20190522085507p:plain

サンプルプログラムの荷物君をz88dkでビルドしてPV-1000で動かしてみます。

>zcc +pv1000 main.c -create-app

main.c

/*

  Nimotsu Kun for PV-1000
                               by tanam [2019]

*/

#include "games.h"

typedef int bool;
typedef int Object;
typedef unsigned char u8;
typedef unsigned short u16;
#define true 1
#define false 0
#define KEY_UP1     MOVE_UP
#define KEY_DOWN1   MOVE_DOWN
#define KEY_LEFT1   MOVE_LEFT
#define KEY_RIGHT1  MOVE_RIGHT
#define KEY_A       MOVE_FIRE1
#define KEY_B       MOVE_FIRE2

unsigned char *screen=0xB800;
u16 game[224];

/* #壁 _空間 .ゴール oブロック p人 */
const char* gStageData = 
"\
########\n\
# .. p #\n\
# oo   #\n\
#      #\n\
########\n\
        \n\
        0\
 ###### \n\
#.    # \n\
# o o # \n\
# op# # \n\
#   # # \n\
#.###.# \n\
### ### 0";

#define gStageWidth 8
#define gStageHeight 7

enum OBJ {
	OBJ_SPACE,
	OBJ_WALL,
	OBJ_GOAL,
	OBJ_BLOCK,
	OBJ_BLOCK_ON_GOAL,
	OBJ_MAN,
	OBJ_MAN_ON_GOAL,

	OBJ_UNKNOWN,
};

Object state[56];

void screen_set_char(u8 xts, u8 yts, u8 xte, u8 yte, u16 *map)
{
	int i, j, k;
	k=0;
	for (i = 0; i < 24; i++)
		for (j = 0; j < 32; j++)
			if (j > xts && j < xte && i > yts && i < yte)
				screen[(i+1)*32+j+1]=map[8*(i-1)+j-xts-1];
	return;
}

/* 関数プロトタイプ */
void initialize( Object* state, char* stageData );
void draw( Object* state, int w, int h );
int update( Object* state, u16 input, int w, int h );
bool checkClear( Object* state, int w, int h );

int main(){
	int stage;
 	clg();
	for (stage=0; stage<2; stage++) {
RESET:
		initialize( state, gStageData+stage*63); /* ステージ初期化 */
		/* メインループ */
		while ( true ){
			/* まず描画 */
			draw( state, gStageWidth, gStageHeight );
			/* クリアチェック */
			if ( checkClear(state, gStageWidth, gStageHeight ) ){
				break; /* クリアチェック */
			}
			/* 更新 */
			if (-1 == update( state, joystick(0), gStageWidth, gStageHeight )){
				goto RESET;
			}
		}
	}
	return 0;
}

void initialize( Object* state, char* stageData ){
	const char* d = stageData; /* 読み込みポインタ */
	int x = 0;
	int y = 0;
	while ( *d != '0' ){ /* NULL文字でない間 */
		Object t; /* 特に意味はないが使う回数が多い変数に私は良くtを使う。temporaryの略。たぶんよくない習慣だが、無駄に長い名前にして読みにくいのも困り物だろう。 */
		switch ( *d ){
			case '#': t = OBJ_WALL; break;
			case ' ': t = OBJ_SPACE; break;
			case 'o': t = OBJ_BLOCK; break;
			case 'O': t = OBJ_BLOCK_ON_GOAL; break;
			case '.': t = OBJ_GOAL; break;
			case 'p': t = OBJ_MAN; break;
			case 'P': t = OBJ_MAN_ON_GOAL; break;
			case '\n': x = 0; ++y; t = OBJ_UNKNOWN; break; /* 改行処理 */
			default: t = OBJ_UNKNOWN; break;
		}
		++d;
		if ( t != OBJ_UNKNOWN ){ /* 知らない文字なら無視するのでこのif文がある */
			state[y * gStageWidth + x ] = t; /* 書き込み */
			++x;
		}
	}
	return;
}

void draw(Object* state, int width, int height ){
        int x, y;
 	char font[] = {' ', '#', '.', 'o', 'O', 'p', 'P'};
/*	u16 font[] = {10,11,12,13,14,15,16}; /* Object列挙の順 */
	for ( y = 0; y < height; ++y ){
		for (x=0; x < width; ++x ){
			Object o = state[ y * width + x ];
			game[y * gStageWidth + x] = font[ o ];
		}
	}
	screen_set_char(0, 0, 9, 8, game);
}

/* 第一引数はほかの関数ではstateとしているが、あまりに頻繁に使うので
短いsで済ませている。w,hもそれぞれwidth,heightである。 */
int update( Object* s, u16 input, int w, int h ){
	/* 移動差分に変換(dはdifferenceでもdeltaでもお好きな方の略だと思って欲しい) */
	int dx = 0; 
	int dy = 0;
	int i = -1;
        int x, y, tx, ty, p, tp, tx2, ty2, tp2;
	switch ( input ){
		case KEY_LEFT1: dx = -1; break;
		case KEY_RIGHT1: dx = 1; break;
		case KEY_UP1: dy = -1; break;
		case KEY_DOWN1: dy = 1; break;
		case KEY_A: return -1;
	}
	/* 人座標を検索 */
	for ( i = 0; i < w * h; ++i ){
		if ( s[ i ] == OBJ_MAN || s[ i ] == OBJ_MAN_ON_GOAL ){
			break;
		}
	}
	x = i % w; /* xは幅で割ったあまり */
	y = i / w; /* yは幅で割った商 */

	/* 移動後座標(tに意味はない。ごめんなさい) */
	tx = x + dx;
	ty = y + dy;
	/* 座標の最大最小チェック。外れていれば不許可 */
	if ( tx < 0 || ty < 0 || tx >= w || ty >= h ){
		return;
	}
	/* A.その方向が空白またはゴール。人が移動。 */
	p = y*w + x; /* 人位置 */
	tp = ty*w + tx; /* ターゲット位置(TargetPosition) */
	if ( s[ tp ] == OBJ_SPACE || s[ tp ] == OBJ_GOAL ){
		s[ tp ] = ( s[ tp ] == OBJ_GOAL ) ? OBJ_MAN_ON_GOAL : OBJ_MAN; /* ゴールならゴール上の人に */
		s[ p ] = ( s[ p ] == OBJ_MAN_ON_GOAL ) ? OBJ_GOAL : OBJ_SPACE; /* もともとゴール上ならゴールに */
	/* B.その方向が箱。その方向の次のマスが空白またはゴールであれば移動。 */
	}else if ( s[ tp ] == OBJ_BLOCK || s[ tp ] == OBJ_BLOCK_ON_GOAL ){
		/* 2マス先が範囲内かチェック */
		tx2 = tx + dx;
		ty2 = ty + dy; 
		if ( tx2 < 0 || ty2 < 0 || tx2 >= w || ty2 >= h ){ /* 押せない */
			return;
		}

		tp2 = ( ty + dy )*w + ( tx + dx ); /* 2マス先 */
		if ( s[ tp2 ] == OBJ_SPACE || s[ tp2 ] == OBJ_GOAL ){
			/* 順次入れ替え */
			s[ tp2 ] = ( s[ tp2 ] == OBJ_GOAL ) ? OBJ_BLOCK_ON_GOAL : OBJ_BLOCK;
			s[ tp ] = ( s[ tp ] == OBJ_BLOCK_ON_GOAL ) ? OBJ_MAN_ON_GOAL : OBJ_MAN;
			s[ p ] = ( s[ p ] == OBJ_MAN_ON_GOAL ) ? OBJ_GOAL : OBJ_SPACE;
		}
	}
	return 0;
}

/* ブロックのみがなければクリアしている。 */
bool checkClear(Object* s, int width, int height ){
        int i;
	for (i = 0; i < width*height; ++i ){
		if ( s[ i ] == OBJ_BLOCK ){
			return false;
		}
	}
	return true;
}

ADAMem DOS and ADAMem SDL v1.81

Visual Studio 2008でADAMem SDLをビルドしてみました。
ADAMem DOS and ADAMem SDL v1.81 - Page 2 - ColecoVision / Adam - AtariAge Forums

AdamemSDL.c

	Mouse_Init ();
//	keyboardmode=(EmuMode)? 1:0;
	keyboardmode=0;

Z80IO.h

;
//#if (__GNUC__ <= 3 && __GNUC__MINOR__ <= 4) \
//	|| (__GNUC__ >= 2 && __GNUC__MINOR__ >= 7)
//#define FASTCALL        __attribute__ ((regparm(3)))
//#ifdef INLINE_MEM
//#define INLINE_MEM_GNU
//#endif
//#else
#define FASTCALL
//#endif

SVI-328の拡張スロットにMSXカートリッジをつないでみる

SVI-328の拡張スロットに、MSXカートリッジをつなぐ方法を考えてみました。

f:id:tanam:20190223152451p:plain

以下の回路図から転記してみました。

http://www.hardwarebook.info/Spectravideo_SVI318/328_Expansion_Bus

http://d4.princess.ne.jp/msx/datas/slot.html

       SVI-328       MSX
PIN 1  +5V           PIN45  +5V
PIN 2 /CTRL2        NC
PIN 3 +12V          PIN48  +12V
PIN 4 -12V          PIN50  -12V
PIN 5  /CTRL1        NC
PIN 6  /WAIT         PIN 7  /WAIT
PIN 7  /RST          PIN15 /RST
PIN 8  CLK           PIN42 CLK
PIN 9  A15           PIN18  A15
PIN10  A14           PIN25  A14
PIN11  A13           PIN26  A13
PIN12  A12           PIN23  A12
PIN13  A11           PIN19  A11
PIN14  A10           PIN20  A10
PIN15  A9            PIN17  A9
PIN16 A8            PIN24  A8
PIN17 A7            PIN21  A7
PIN18 A6            PIN22  A6
PIN19 A5            PIN31  A5
PIN20  A4            PIN32  A4
PIN21  A3            PIN29  A3
PIN22  A2            PIN32  A2
PIN23  A1            PIN27  A1
PIN24  A0            PIN28  A0
PIN25  /RFSH         PIN 6 /RFSH
PIN26 /EXCSR        NC
PIN27 /M1           PIN 9 /M1
PIN28 /EXCSW        NC
PIN29 /WR           PIN13  /WR
PIN30  /MREQ         PIN12  /MREQ
PIN31  /IORQ         PIN11  /IORQ
PIN32  /RD           PIN14  /RD
PIN33  D0            PIN34  D0
PIN34  D1            PIN33  D1
PIN35  D2            PIN36  D2
PIN36  D3            PIN35  D3
PIN37  D4            PIN38  D4
PIN38  D5            PIN37  D5
PIN39  D6            PIN40  D6
PIN40  D7            PIN39  D7
PIN41 CSOUND        PIN49  CSOUND
PIN42 /INT          PIN 8  /INT
PIN43 /RAMDIS       NC
PIN44 /ROMDIS       NC
PIN45 /BK32         NC
PIN46  /BK31         NC
PIN47  /BK22         NC
PIN48  /BK21         NC
PIN49  GND           PIN41  GND
PIN50  GND           PIN43  GND

SVI-328でSG-1000を動かす

モリーマップが同じため比較的かんたんなパッチで動きます。

f:id:tanam:20190220220611p:plain

SG-1000のメモリマップはROMが0000-7FFF、RAMがC000-C3FF
SVI-328のメモリマップはROMが0000-7FFF、RAMが8000-FFFF

VDPとジョイパッドのポート番号を修正して行きます。ただしサウンドは音源チップが異なるため鳴りません。

D3 BE→80
D3 BF→81
DB BE→84
DB BF→85

guzzler.sg.pat

* Patch for Guzzler.sg *
* Made by バイナリ書き替え君 Version 2.03 *
*== TARGET_FILE ================
FILENAME Guzzler.sg
* FileSize: 8192 bytes
* LastMod : 1996/12/24 23:32:00
*===============================
00000001: ED 31
00000002: 56 00
00000003: 31 C1
00000004: 00 ED
00000005: C1 56
0000003B: BF 85
00000129: BF 81
0000016C: BF 81
00000170: BF 81
0000017C: BF 81
00000180: BF 81
0000018B: BF 81
0000018E: BF 81
00000191: BE 80
0000019C: BE 80
000001A5: BF 81
000001A8: BF 81
0000169F: 3A 3E
000016A0: 09 0E
000016A1: C0 D3
000016A2: B7 88
000016A3: CA DB
000016A4: 0B 90
000016A5: 17 47
000016A6: 3E DB
000016A7: 07 98
000016A8: D3 F6
000016A9: DE EF
000016AA: DB C3
000016AB: DC 0B
000016AC: B7 17
0000170B: DB 00
0000170C: DC A0

SVI-3x8エミュレータをつくる その4

以下を取り込んでディスクに対応しました。

http://www.acc.umu.se/~yarin/sviemu/

memory_ex.h

/*
	Common Source Code Project
	SVI-3x8

	Origin : src/vm/msx/memory.h

	modified by tanam
	Date   : 2018.12.09-

	[ memory ]
*/

#ifndef _MEMORY_EX_H_
#define _MEMORY_EX_H_

#include "../vm.h"
#include "../../emu.h"
#include "../device.h"

#define SIG_MEMORY_SEL	0

#if defined(FDD_PATCH_SLOT)
class DISK;
#endif

#define MAX_TAPE_LEN 524288

// memory bus
class MEMORY_EX : public DEVICE
{
private:
	uint8_t* wbank[8];
	uint8_t* rbank[8];
	uint8_t wdmy[0x2000];
	uint8_t rdmy[0x2000];
	uint8_t bio[0x8000];  /* BANK01 */
	uint8_t ram[0x8000];  /* BANK02 */
	uint8_t rom[0x8000];  /* BANK11 */
	uint8_t r12[0x8000];  /* BANK12 */
	uint8_t r21[0x8000];  /* BANK21 */
	uint8_t r22[0x8000];  /* BANK22 */
	uint8_t r31[0x8000];  /* BANK31 */
	uint8_t r32[0x8000];  /* BANK32 */
	bool inserted;
	bool play;
	uint8_t strig;
#if defined(FDD_PATCH_SLOT)
	DISK* disk[MAX_DRIVE];
	DEVICE *d_fdpat;
	bool access[MAX_DRIVE];
#endif
	int count;
	int done;
	int tapePos;
	int tapeLen;
	byte tapedata[MAX_TAPE_LEN];

public:
	MEMORY_EX(VM_TEMPLATE* parent_vm, EMU* parent_emu) : DEVICE(parent_vm, parent_emu)
	{
		set_device_name(_T("Memory Bus"));
	}
	~MEMORY_EX() {}
	
	// common functions
	void initialize();
	void reset();
	void write_data8(uint32_t addr, uint32_t data);
	uint32_t read_data8(uint32_t addr);
	uint32_t fetch_op(uint32_t addr, int* wait);
	bool process_state(FILEIO* state_fio, bool loading);
	void write_signal(int id, uint32_t data, uint32_t mask);
	uint32_t read_io8(uint32_t addr);
	void write_io8(uint32_t addr, uint32_t data);
	// unique functions
	void open_cart(const _TCHAR *file_path);
	void close_cart();
	bool load_cart(const _TCHAR *file_path/*, uint8_t *rom*/);
	bool is_cart_inserted()
	{
		return inserted;
	}
	bool play_tape(const _TCHAR* file_path);
//	bool rec_tape(const _TCHAR* file_path);
	void close_tape();
#if defined(FDD_PATCH_SLOT)
	void release();
	void set_context_fdd_patch(DEVICE *device)
	{
		d_fdpat = device;
	}
	void open_disk(int drv, const _TCHAR* file_path, int bank);
	void close_disk(int drv);
	bool is_disk_inserted(int drv);
	void is_disk_protected(int drv, bool value);
	bool is_disk_protected(int drv);
#endif
	bool is_tape_inserted()
	{
		return play;
	}
	const _TCHAR* get_message()
	{
		if (play) return "Play";
		else return "Stop";
	}
};

#endif

memory_ex.cpp

/*
	Common Source Code Project
	SVI-3x8

	Origin : src/vm/msx/memory_ex.cpp

	modified by tanam
	Date   : 2018.12.09-

	[ memory ]
*/

#include "memory_ex.h"
#if defined(FDD_PATCH_SLOT)
#include "../disk.h"
#define MSX_SECTOR_SIZE 512
char dskPath[64]="";		/* Disk image path                 	*/
/** Floppy drive *********/
unsigned char fdc_drive = 0;
unsigned char fdc_head = 0;
unsigned char fdc_density = 0;
unsigned char fdc_status = 0;
unsigned char svi_disk_heads[2];
unsigned char svi_disk_tracks=40;
unsigned char svi_UseDisk = 0;

#ifndef UINT16
#define UINT16  unsigned short int
#endif

#ifndef UINT8
#define UINT8   unsigned char
#endif

#define MAX_DRIVES	2	/* we support 'only' four drives in MESS */

#define WD179X_IRQ_CLR  0
#define WD179X_IRQ_SET  1
#define WD179X_DRQ_CLR  2
#define WD179X_DRQ_SET  3

#define DEN_FM_LO	0	/* this is used by TRS-80 (but not working) */
#define DEN_FM_HI	1
#define DEN_MFM_LO	2	/* and this one is the one that works */
#define DEN_MFM_HI	3	/* There were no HD disks back then ;) */

#define REAL_FDD		((void*)-1)

#define FDC_STEP_RATE   0x03    /* Type I additional flags */
#define FDC_STEP_VERIFY 0x04    /* verify track number */
#define FDC_STEP_HDLOAD 0x08    /* load head */
#define FDC_STEP_UPDATE 0x10    /* update track register */

#define FDC_RESTORE     0x00    /* Type I commands */
#define FDC_SEEK        0x10
#define FDC_STEP        0x20
#define FDC_STEP_IN     0x40
#define FDC_STEP_OUT    0x60

#define FDC_MASK_TYPE_I         (FDC_STEP_HDLOAD|FDC_STEP_VERIFY|FDC_STEP_RATE)

/* Type I commands status */
#define STA_1_BUSY      0x01    /* controller is busy */
#define STA_1_IPL       0x02    /* index pulse */
#define STA_1_TRACK0    0x04    /* track 0 detected */
#define STA_1_CRC_ERR   0x08    /* CRC error */
#define STA_1_SEEK_ERR  0x10    /* seek error */
#define STA_1_HD_LOADED 0x20    /* head loaded */
#define STA_1_WRITE_PRO 0x40    /* floppy is write protected */
#define STA_1_NOT_READY 0x80    /* controller not ready */

/* Type II and III additional flags */
#define FDC_DELETED_AM  0x01    /* read/write deleted address mark */
#define FDC_SIDE_CMP_T  0x02    /* side compare track data */
#define FDC_15MS_DELAY  0x04    /* delay 15ms before command */
#define FDC_SIDE_CMP_S  0x08    /* side compare sector data */
#define FDC_MULTI_REC   0x10    /* only for type II commands */

/* Type II commands */
#define FDC_READ_SEC    0x80    /* read sector */
#define FDC_WRITE_SEC   0xA0    /* write sector */

#define FDC_MASK_TYPE_II        (FDC_MULTI_REC|FDC_SIDE_CMP_S|FDC_15MS_DELAY|FDC_SIDE_CMP_T|FDC_DELETED_AM)

/* Type II commands status */
#define STA_2_BUSY      0x01
#define STA_2_DRQ       0x02
#define STA_2_LOST_DAT  0x04
#define STA_2_CRC_ERR   0x08
#define STA_2_REC_N_FND 0x10
#define STA_2_REC_TYPE  0x20
#define STA_2_WRITE_PRO 0x40
#define STA_2_NOT_READY 0x80

#define FDC_MASK_TYPE_III       (FDC_SIDE_CMP_S|FDC_15MS_DELAY|FDC_SIDE_CMP_T|FDC_DELETED_AM)

/* Type III commands */
#define FDC_READ_DAM    0xc0    /* read data address mark */
#define FDC_READ_TRK    0xe0    /* read track */
#define FDC_WRITE_TRK   0xf0    /* write track (format) */

/* Type IV additional flags */
#define FDC_IM0         0x01    /* interrupt mode 0 */
#define FDC_IM1         0x02    /* interrupt mode 1 */
#define FDC_IM2         0x04    /* interrupt mode 2 */
#define FDC_IM3         0x08    /* interrupt mode 3 */

#define FDC_MASK_TYPE_IV        (FDC_IM3|FDC_IM2|FDC_IM1|FDC_IM0)

/* Type IV commands */
#define FDC_FORCE_INT   0xd0    /* force interrupt */

typedef struct {
	UINT8	 track;
	UINT8	 sector;
	UINT8	 status;
}	SECMAP;

typedef struct {
	void   (* callback)(int event);   /* callback for IRQ status */
	UINT8	unit;				/* unit number if image_file == REAL_FDD */
	UINT8	tracks; 			/* maximum # of tracks */
	UINT8	heads;				/* maximum # of heads */
	UINT8	density;			/* FM/MFM, single / double density */
	UINT16	offset; 			/* track 0 offset */
	UINT8	first_sector_id;		/* id of first sector */
	UINT8	sec_per_track;			/* sectors per track */
	UINT16	sector_length;			/* sector length (byte) */

	UINT8	head;				/* current head # */
	UINT8	track;				/* current track # */
	UINT8	track_reg;			/* value of track register */
	UINT8	direction;			/* last step direction */
	UINT8	sector; 			/* current sector # */
	UINT8	sector_dam; 			/* current sector # to fake read DAM command */
	UINT8	data;				/* value of data register */
	UINT8	command;			/* last command written */

	UINT8	read_cmd;			/* last read command issued */
	UINT8	write_cmd;			/* last write command issued */

	UINT8	status; 			/* status register */
	UINT8	status_drq; 			/* status register data request bit */
	UINT8	status_ipl; 			/* status register toggle index pulse bit */
	UINT8	busy_count; 			/* how long to keep busy bit set */

	UINT8	buffer[6144];			/* I/O buffer (holds up to a whole track) */
	UINT8	dam_list[256][4];		/* list of data address marks while formatting */
	int 	dam_data[256];			/* offset to data inside buffer while formatting */
	int 	dam_cnt;			/* valid number of entries in the dam_list */
	UINT8	*fmt_sector_data[256];	/* pointer to data after formatting a track */
    	int     data_offset;            /* offset into I/O buffer */
	int 	data_count; 			/* transfer count from/into I/O buffer */

	const char *image_name; 		/* file name for disc image */
	void	*image_file;			/* file handle for disc image */
	int 	mode;					/* open mode == 0 read only, != 0 read/write */
	unsigned long image_size;		/* size of image file */

	UINT16	dir_sector; 			/* directory track for deleted DAM */
	UINT16	dir_length; 			/* directory length for deleted DAM */

	SECMAP	*secmap;

}	WD179X;

/* structure describing a double density track */
#define TRKSIZE_DD      6144
static UINT8 track_DD[][2] = {
	{16, 0x4e}, 	/* 16 * 4E (track lead in)				 */
	{ 8, 0x00}, 	/*	8 * 00 (pre DAM)					 */
	{ 3, 0xf5}, 	/*	3 * F5 (clear CRC)					 */

	{ 1, 0xfe}, 	/* *** sector *** FE (DAM)				 */
	{ 1, 0x80}, 	/*	4 bytes track,head,sector,seclen	 */
	{ 1, 0xf7}, 	/*	1 * F7 (CRC)						 */
	{22, 0x4e}, 	/* 22 * 4E (sector lead in) 			 */
	{12, 0x00}, 	/* 12 * 00 (pre AM) 					 */
	{ 3, 0xf5}, 	/*	3 * F5 (clear CRC)					 */
	{ 1, 0xfb}, 	/*	1 * FB (AM) 						 */
	{ 1, 0x81}, 	/*	x bytes sector data 				 */
	{ 1, 0xf7}, 	/*	1 * F7 (CRC)						 */
	{16, 0x4e}, 	/* 16 * 4E (sector lead out)			 */
	{ 8, 0x00}, 	/*	8 * 00 (post sector)				 */
	{ 0, 0x00}, 	/* end of data							 */
};

/* structure describing a single density track */
#define TRKSIZE_SD      3172
static UINT8 track_SD[][2] = {
	{16, 0xff}, 	/* 16 * FF (track lead in)				 */
	{ 8, 0x00}, 	/*	8 * 00 (pre DAM)					 */
	{ 1, 0xfc}, 	/*	1 * FC (clear CRC)					 */

	{11, 0xff}, 	/* *** sector *** 11 * FF				 */
	{ 6, 0x00}, 	/*	6 * 00 (pre DAM)					 */
	{ 1, 0xfe}, 	/*	1 * FE (DAM)						 */
	{ 1, 0x80}, 	/*	4 bytes track,head,sector,seclen	 */
	{ 1, 0xf7}, 	/*	1 * F7 (CRC)						 */
	{10, 0xff}, 	/* 10 * FF (sector lead in) 			 */
	{ 4, 0x00}, 	/*	4 * 00 (pre AM) 					 */
	{ 1, 0xfb}, 	/*	1 * FB (AM) 						 */
	{ 1, 0x81}, 	/*	x bytes sector data 				 */
	{ 1, 0xf7}, 	/*	1 * F7 (CRC)						 */
	{ 0, 0x00}, 	/* end of data							 */
};

WD179X *wd[MAX_DRIVES];
static UINT8 drv = 0;

void wd179x_CloseDiskImage(unsigned char DriveNumber)
{
	WD179X *w = wd[DriveNumber];
	
	fclose((FILE *)w->image_file);
	w->image_file = NULL;
	w->image_name = NULL;
}
void wd179x_InitDiskImage(unsigned char DriveNumber, const char *DriveImageFileName)
{
	
	WD179X *w = wd[DriveNumber];

	w->image_name = DriveImageFileName;

	// Open file images
	w->mode = 1;
				
	// Open Read/Write
	w->image_file = fopen(w->image_name, "r+b");
	if( !w->image_file )
	{
		w->mode = 0;
		// Open Read Only
		w->image_file = fopen(w->image_name,"rb");
	}

	w->track = 0;
	w->head = 0;
	w->sector = 0;
}

void wd179x_init(int active)
{
	int i;

    	for (i = 0; i < MAX_DRIVES; i++)
	{
		wd[i] = (WD179X *)malloc(sizeof(WD179X));
		if (!wd[i])
		{
			while (--i >= 0)
			{
				free(wd[i]);
				wd[i] = 0;
			}
			return;
		}
		memset(wd[i], 0, sizeof(WD179X));
		wd[i]->unit = 0;
        	wd[i]->tracks = 40;
		wd[i]->heads = 1;
		wd[i]->density = DEN_MFM_LO;
		wd[i]->offset = 0;
                wd[i]->first_sector_id = 0;
		wd[i]->sec_per_track = 17;
		wd[i]->sector_length = 256;
		wd[i]->head = 0;
		wd[i]->track = 0;
		wd[i]->track_reg = 0;
		wd[i]->direction = 1;
		wd[i]->sector = 0;
		wd[i]->data = 0;
		wd[i]->status = (active) ? STA_1_TRACK0 : 0;
		wd[i]->status_drq = 0;
		wd[i]->status_ipl = 0;
		wd[i]->busy_count = 0;
		wd[i]->data_offset = 0;
		wd[i]->data_count = 0;
		wd[i]->image_name = 0;
		wd[i]->image_size = 0;
		wd[i]->dir_sector = 0;
		wd[i]->dir_length = 0;
		wd[i]->secmap = 0;
	}
}

void wd179x_select_drive(UINT8 drive, UINT8 head, void (*callback) (int))
{
WD179X *w = wd[drive];

	if (drive < MAX_DRIVES)
	{
		drv = drive;
		w->head = head;
		w->status_ipl = STA_1_IPL;
		w->callback = callback;

		if (w->image_file)
		{
			return;
		}
	}
	w->status = STA_1_NOT_READY;
}

void wd179x_stop_drive(void)
{
	int i;

	for (i = 0; i < MAX_DRIVES; i++)
	{
		WD179X *w = wd[i];
		w->busy_count = 0;
		w->status = 0;
		w->status_drq = 0;
		if (w->callback)
			(*w->callback) (WD179X_DRQ_CLR);
		w->status_ipl = 0;
	}
}

void wd179x_read_sectormap(UINT8 drive, UINT8 * tracks, UINT8 * heads, UINT8 * sec_per_track)
{
WD179X *w = wd[drive];
SECMAP *p;
UINT8 head;

    if (!w->secmap)
		w->secmap = (SECMAP *)malloc(0x2200);
	if (!w->secmap) return;
	fseek((FILE *)w->image_file, 0, SEEK_SET);
	fread(w->secmap, 1, 0x2200, (FILE *)w->image_file);
	w->offset = 0x2200;
	w->tracks = 0;
	w->heads = 0;
	w->sec_per_track = 0;
        w->first_sector_id = 0x0ff;
	for (p = w->secmap; p->track != 0xff; p++)
	{
		if (p->track > w->tracks)
			w->tracks = p->track;

                if (p->sector < w->first_sector_id)
                        w->first_sector_id = p->sector;

		if (p->sector > w->sec_per_track)
			w->sec_per_track = p->sector;
		head = (p->status >> 4) & 1;
		if (head > w->heads)
			w->heads = head;
	}
	*tracks = w->tracks++;
	*heads = w->heads++;
	*sec_per_track = w->sec_per_track++;
}

void wd179x_set_geometry(UINT8 density, UINT8 drive, UINT8 tracks, UINT8 heads, UINT8 sec_per_track, UINT16 sector_length, UINT16 dir_sector, UINT16 dir_length, UINT8 first_sector_id)
{
WD179X *w = wd[drive];

	if (drive >= MAX_DRIVES)
	{
		return;
	}

	w->density = density;
    	w->tracks = tracks;
	w->heads = heads;
        w->first_sector_id = first_sector_id;
	w->sec_per_track = sec_per_track;
	w->sector_length = sector_length;
	w->dir_sector = dir_sector;
	w->dir_length = dir_length;

	w->image_size = w->tracks * w->heads * w->sec_per_track * w->sector_length;

        /* calculate greatest power of 2 */
        if (w->image_file == REAL_FDD)
        {
                unsigned long N = 0;
                unsigned long ShiftCount = 0;

                if (N==0)
                {
                        N = (w->sector_length);

                        while ((N & 0x080000000)==0)
                        {
                                N = N<<1;
                                ShiftCount++;
                        }

                        /* get left-shift required to shift 1 to this
                        power of 2 */

                        /* N = 0 for 128, N = 1 for 256, N = 2 for 512 ... */
                        N = (31 - ShiftCount)-7;
                 }
                 else
                 {
                       N = 1;
                 }
        }
}

/* seek to track/head/sector relative position in image file */
static int seek(WD179X * w, UINT8 t, UINT8 h, UINT8 s)
{
unsigned long offset;
SECMAP *p;
UINT8 head;

    if (w->secmap)
	{
		offset = 0x2200;
		for (p = w->secmap; p->track != 0xff; p++)
		{
			if (p->track == t && p->sector == s)
			{
				head = (p->status & 0x10) >> 4;
				if (head == h)
				{
					if (fseek((FILE *)w->image_file, offset, SEEK_SET) < 0)
					{
						return STA_1_SEEK_ERR;
					}
					return 0;
				}
			}
			//offset += 0x100;
			if (p->track==0 && head==0)
				offset += 0x80;
			else
				offset += 0x100;
		}
		return STA_1_SEEK_ERR;
	}

	/* allow two additional tracks */
    if (t >= w->tracks + 2)
	{
		return STA_1_SEEK_ERR;
	}

    if (h >= w->heads)
    {
		return STA_1_SEEK_ERR;
	}

    if (s >= (w->first_sector_id + w->sec_per_track))
	{
		return STA_2_REC_N_FND;
	}

	if ((t==0) && (h==0)) 
     		offset = (s-w->first_sector_id)*128; 
   	else
     		offset = ((t*w->heads+h)*17+s-w->first_sector_id)*256-2048; // (17*256)-(18*128)=2048

	if (offset > w->image_size)
	{
		return STA_1_SEEK_ERR;
	}

	if (fseek((FILE *)w->image_file, offset, SEEK_SET) < 0)
	{
		return STA_1_SEEK_ERR;
	}

	return 0;
}

/* return STA_2_REC_TYPE depending on relative sector */
static int deleted_dam(WD179X * w)
{
unsigned rel_sector = (w->track * w->heads + w->head) * w->sec_per_track + (w->sector-w->first_sector_id);
SECMAP *p;
UINT8 head;

	if (w->secmap)
	{
		for (p = w->secmap; p->track != 0xff; p++)
		{
			if (p->track == w->track && p->sector == w->sector)
			{
				head = (p->status >> 4) & 1;
				if (w->head == head)
					return p->status & STA_2_REC_TYPE;
			}
		}
		return STA_2_REC_N_FND;
	}
	if (rel_sector >= w->dir_sector && rel_sector < w->dir_sector + w->dir_length)
	{
		return STA_2_REC_TYPE;
	}
	return 0;
}

/* calculate CRC for data address marks or sector data */
static void calc_crc(UINT16 * crc, UINT8 value)
{
UINT8 l, h;

	l = value ^ (*crc >> 8);
	*crc = (*crc & 0xff) | (l << 8);
	l >>= 4;
	l ^= (*crc >> 8);
	*crc <<= 8;
	*crc = (*crc & 0xff00) | l;
	l = (l << 4) | (l >> 4);
	h = l;
	l = (l << 2) | (l >> 6);
	l &= 0x1f;
	*crc = *crc ^ (l << 8);
	l = h & 0xf0;
	*crc = *crc ^ (l << 8);
	l = (h << 1) | (h >> 7);
	l &= 0xe0;
	*crc = *crc ^ l;
}

/* read the next data address mark */
static void read_dam(WD179X * w)
{
UINT16 crc = 0xffff;

	w->data_offset = 0;
	w->data_count = 6;
	w->buffer[0] = w->track;
	w->buffer[1] = w->head;
	w->buffer[2] = w->sector_dam;
	w->buffer[3] = w->sector_length >> 8;
	calc_crc(&crc, w->buffer[0]);
	calc_crc(&crc, w->buffer[1]);
	calc_crc(&crc, w->buffer[2]);
	calc_crc(&crc, w->buffer[3]);
	w->buffer[4] = crc & 255;
	w->buffer[5] = crc / 256;
	if (++w->sector_dam == w->sec_per_track)
                w->sector_dam = w->first_sector_id;
	w->status_drq = STA_2_DRQ;
	if (w->callback)
		(*w->callback) (WD179X_DRQ_SET);
	w->status = STA_2_DRQ | STA_2_BUSY;
	w->busy_count = 50;
}

/* read a sector */
static void read_sector(WD179X * w)
{
    w->data_offset = 0;
	w->data_count = w->sector_length;

	/* if a track was just formatted */
	if (w->dam_cnt)
	{
		int i;
		for (i = 0; i < w->dam_cnt; i++)
		{
			if (w->track == w->dam_list[i][0] &&
				w->head == w->dam_list[i][1] &&
				w->sector == w->dam_list[i][2])
			{
				w->data_offset = w->dam_data[i];
				return;
			}
		}
		/* sector not found, now the track buffer is invalid */
		w->dam_cnt = 0;
	}

    /* if this is the real thing */
    if (w->image_file == REAL_FDD)
    {
	int tries = 3;
		do {
			//w->status = osd_fdc_get_sector(w->track, w->head, w->head, w->sector, w->buffer);
			tries--;
		} while (tries && (w->status & (STA_2_REC_N_FND | STA_2_CRC_ERR | STA_2_LOST_DAT)));
		/* no error bits set ? */
		if ((w->status & (STA_2_REC_N_FND | STA_2_CRC_ERR | STA_2_LOST_DAT)) == 0)
		{
			/* start transferring data to the emulation now */
			w->status_drq = STA_2_DRQ;
			if (w->callback)
				(*w->callback) (WD179X_DRQ_SET);
			w->status |= STA_2_DRQ | STA_2_BUSY;
        }
        return;
    }
	else
	if (fread(w->buffer, 1, w->sector_length, (FILE *)w->image_file) != w->sector_length)
	{
		w->status = STA_2_LOST_DAT;
		return;
	}

	w->status_drq = STA_2_DRQ;
	if (w->callback)
		(*w->callback) (WD179X_DRQ_SET);
	w->status = STA_2_DRQ | STA_2_BUSY;
	w->busy_count = 0;
}

/* read an entire track */
static void read_track(WD179X * w)
{
UINT8 *psrc;					   /* pointer to track format structure */
UINT8 *pdst;					   /* pointer to track buffer */
int cnt;					   /* number of bytes to fill in */
UINT16 crc;					   /* id or data CRC */
UINT8 d;						   /* data */
UINT8 t = w->track;			   /* track of DAM */
UINT8 h = w->head;			   /* head of DAM */
UINT8 s = w->sector_dam;		   /* sector of DAM */
UINT16 l = w->sector_length;	   /* sector length of DAM */
int i;

	for (i = 0; i < w->sec_per_track; i++)
	{
		w->dam_list[i][0] = t;
		w->dam_list[i][1] = h;
		w->dam_list[i][2] = i;
		w->dam_list[i][3] = l >> 8;
	}

	pdst = w->buffer;

	if (w->density)
	{
		psrc = track_DD[0];	   /* double density track format */
		cnt = TRKSIZE_DD;
	}
	else
	{
		psrc = track_SD[0];	   /* single density track format */
		cnt = TRKSIZE_SD;
	}

	while (cnt > 0)
	{
		if (psrc[0] == 0)	   /* no more track format info ? */
		{
			if (w->dam_cnt < w->sec_per_track) /* but more DAM info ? */
			{
				if (w->density)/* DD track ? */
					psrc = track_DD[3];
				else
					psrc = track_SD[3];
			}
		}

		if (psrc[0] != 0)	   /* more track format info ? */
		{
			cnt -= psrc[0];	   /* subtract size */
			d = psrc[1];

			if (d == 0xf5)	   /* clear CRC ? */
			{
				crc = 0xffff;
				d = 0xa1;	   /* store A1 */
			}

			for (i = 0; i < *psrc; i++)
				*pdst++ = d;   /* fill data */

			if (d == 0xf7)	   /* store CRC ? */
			{
				pdst--;		   /* go back one byte */
				*pdst++ = crc & 255;	/* put CRC low */
				*pdst++ = crc / 256;	/* put CRC high */
				cnt -= 1;	   /* count one more byte */
			}
			else if (d == 0xfe)/* address mark ? */
			{
				crc = 0xffff;   /* reset CRC */
			}
			else if (d == 0x80)/* sector ID ? */
			{
				pdst--;		   /* go back one byte */
				t = *pdst++ = w->dam_list[w->dam_cnt][0]; /* track number */
				h = *pdst++ = w->dam_list[w->dam_cnt][1]; /* head number */
				s = *pdst++ = w->dam_list[w->dam_cnt][2]; /* sector number */
				l = *pdst++ = w->dam_list[w->dam_cnt][3]; /* sector length code */
				w->dam_cnt++;
				calc_crc(&crc, t);	/* build CRC */
				calc_crc(&crc, h);	/* build CRC */
				calc_crc(&crc, s);	/* build CRC */
				calc_crc(&crc, l);	/* build CRC */
				l = (l == 0) ? 128 : l << 8;
			}
			else if (d == 0xfb)// data address mark ?
			{
				crc = 0xffff;   // reset CRC
			}
			else if (d == 0x81)// sector DATA ?
			{
				pdst--;		   /* go back one byte */
				if (seek(w, t, h, s) == 0)
				{
					if (fread(pdst, 1, l, (FILE *)w->image_file) != l)
					{
						w->status = STA_2_CRC_ERR;
						return;
					}
				}
				else
				{
					w->status = STA_2_REC_N_FND;
					return;
				}
				for (i = 0; i < l; i++)	// build CRC of all data
					calc_crc(&crc, *pdst++);
				cnt -= l;
			}
			psrc += 2;
		}
		else
		{
			*pdst++ = 0xff;	   /* fill track */
			cnt--;			   /* until end */
		}
	}

	w->data_offset = 0;
	w->data_count = (w->density) ? TRKSIZE_DD : TRKSIZE_SD;

	w->status_drq = STA_2_DRQ;
	if (w->callback)
		(*w->callback) (WD179X_DRQ_SET);
	w->status |= STA_2_DRQ | STA_2_BUSY;
	w->busy_count = 0;
}

/* write a sector */
static void write_sector(WD179X * w)
{
	if (w->image_file == REAL_FDD)
	{
		return;
	}
    if (w->mode == 0)
	{
		w->status = STA_2_WRITE_PRO;
	}
	else
	{
		w->status = seek(w, w->track, w->head, w->sector);
		if (w->status == 0)
		{
			if (fwrite(w->buffer, 1, w->data_offset, (FILE *)w->image_file) != w->data_offset)
				w->status = STA_2_LOST_DAT;
		}
	}
}

/* write an entire track by extracting the sectors */
static void write_track(WD179X * w)
{
UINT8 *f;
int cnt;

	w->dam_cnt = 0;
    if (w->image_file != REAL_FDD && w->mode == 0)
    {
        w->status = STA_2_WRITE_PRO;
        return;
    }

	memset(w->dam_list, 0xff, sizeof(w->dam_list));
	memset(w->dam_data, 0x00, sizeof(w->dam_data));

	f = w->buffer;
    cnt = (w->density) ? TRKSIZE_DD : TRKSIZE_SD;

	do
	{
		while ((--cnt > 0) && (*f != 0xfe))	/* start of DAM ?? */
			f++;

		if (cnt > 4)
		{
		int seclen;
			cnt -= 5;
			f++;			   /* skip FE */
			w->dam_list[w->dam_cnt][0] = *f++;	  /* copy track number */
			w->dam_list[w->dam_cnt][1] = *f++;	  /* copy head number */
			w->dam_list[w->dam_cnt][2] = *f++;	  /* copy sector number */
			w->dam_list[w->dam_cnt][3] = *f++;	  /* copy sector length */
			/* sector length in bytes */
			seclen = 128 << w->dam_list[w->dam_cnt][3];
			/* search start of DATA */
			while ((--cnt > 0) && (*f != 0xf9) && (*f != 0xfa) && (*f != 0xfb))
				f++;
			if (cnt > seclen)
			{
				cnt--;
				/* skip data address mark */
                f++;
                /* set pointer to DATA to later write the sectors contents */
				w->dam_data[w->dam_cnt] = (int)(f - w->buffer);
				w->dam_cnt++;
				f += seclen;
				cnt -= seclen;
			}
        }
	} while (cnt > 0);

	if (w->image_file == REAL_FDD)
	{
		w->status = 0;
    }
	else
	{
		/* now put all sectors contained in the format buffer */
		for (cnt = 0; cnt < w->dam_cnt; cnt++)
		{
			w->status = seek(w, w->track, w->head, w->dam_list[cnt][2]);
			if (w->status == 0)
			{
				if (fwrite(&w->buffer[w->dam_data[cnt]],1, w->sector_length, (FILE *)w->image_file) != w->sector_length)
				{
					w->status = STA_2_LOST_DAT;
					return;
				}
			}
		}
	}
}


/* read the FDC status register. This clears IRQ line too */
UINT8 wd179x_status_r(void)
{
WD179X *w = wd[drv];
int result = w->status;

	if (w->callback)
		(*w->callback) (WD179X_IRQ_CLR);
	if (w->busy_count)
	{
		if (!--w->busy_count)
			w->status &= ~STA_1_BUSY;
	}
/* eventually toggle index pulse bit */
	w->status ^= w->status_ipl;
/* eventually set data request bit */
	w->status |= w->status_drq;

	return result;
}

/* read the FDC track register */
UINT8 wd179x_track_r(void)
{
WD179X *w = wd[drv];

	return w->track_reg;
}

/* read the FDC sector register */
UINT8 wd179x_sector_r(void)
{
WD179X *w = wd[drv];

	return w->sector;
}

/* read the FDC data register */
UINT8 wd179x_data_r(void)
{
WD179X *w = wd[drv];

	if (w->data_count > 0)
	{
		w->status &= ~STA_2_DRQ;
		if (--w->data_count <= 0)
		{
			/* clear busy bit */
			w->status &= ~STA_2_BUSY;
			/* no more setting of data request bit */
			w->status_drq = 0;
			if (w->callback)
				(*w->callback) (WD179X_DRQ_CLR);
			if (w->image_file != REAL_FDD)
			{
				/* read normal or deleted data address mark ? */
				w->status |= deleted_dam(w);
			}
			/* generate an IRQ */
			if (w->callback)
				(*w->callback) (WD179X_IRQ_SET);
		}
		w->data = w->buffer[w->data_offset++];
	}
	return w->data;
}

/* write the FDC command register */
void wd179x_command_w(UINT8 data)
{
WD179X *w = wd[drv];

	if ((data | 1) == 0xff)	   /* change single/double density ? */
	{
		/* only supports FM/LO and MFM/LO */
		w->density = (data & 1) ? DEN_MFM_LO : DEN_FM_LO;
		return;
	}

	if ((data & ~FDC_MASK_TYPE_IV) == FDC_FORCE_INT)
	{
		w->data_count = 0;
		w->data_offset = 0;
		w->status &= ~(STA_2_DRQ | STA_2_BUSY);
		w->status_drq = 0;
		if (w->callback)
			(*w->callback) (WD179X_DRQ_CLR);
		w->status_ipl = 0;
		if (w->callback)
			(*w->callback) (WD179X_IRQ_CLR);
		w->busy_count = 0;
		return;
	}

	if (data & 0x80)
	{
		w->status_ipl = 0;

		if ((data & ~FDC_MASK_TYPE_II) == FDC_READ_SEC)
		{
			w->read_cmd = data;
            		w->command = data & ~FDC_MASK_TYPE_II;
			w->status = seek(w, w->track, w->head, w->sector);
			if (w->status == 0)
				read_sector(w);
			return;
		}

		if ((data & ~FDC_MASK_TYPE_II) == FDC_WRITE_SEC)
		{
			w->write_cmd = data;
			w->command = data & ~FDC_MASK_TYPE_II;
			w->data_offset = 0;
			w->data_count = w->sector_length;
			w->status_drq = STA_2_DRQ;
			if (w->callback)
				(*w->callback) (WD179X_DRQ_SET);
			w->status = STA_2_DRQ | STA_2_BUSY;
			w->busy_count = 0;
			return;
		}

		if ((data & ~FDC_MASK_TYPE_III) == FDC_READ_TRK)
		{
			w->command = data & ~FDC_MASK_TYPE_III;
			w->status = seek(w, w->track, w->head, w->sector);
			if (w->status == 0)
				read_track(w);
			return;
		}

		if ((data & ~FDC_MASK_TYPE_III) == FDC_WRITE_TRK)
		{
			w->command = data & ~FDC_MASK_TYPE_III;
			w->data_offset = 0;
			w->data_count = (w->density) ? TRKSIZE_DD : TRKSIZE_SD;
			w->status_drq = STA_2_DRQ;
			if (w->callback)
				(*w->callback) (WD179X_DRQ_SET);
			w->status = STA_2_DRQ | STA_2_BUSY;
			w->busy_count = 0;
			return;
		}

		if ((data & ~FDC_MASK_TYPE_III) == FDC_READ_DAM)
		{
			w->status = seek(w, w->track, w->head, w->sector);
			if (w->status == 0)
				read_dam(w);
			return;
		}

        return;
	}


	if ((data & ~FDC_MASK_TYPE_I) == FDC_RESTORE)
	{
		/* simulate seek time busy signal */
		w->busy_count = w->track * ((data & FDC_STEP_RATE) + 1);
		/* if it is a real floppy, issue a recal command */
        if (w->image_file == REAL_FDD)
		{
			w->track = 0;//osd_fdc_recal(&w->track);
		}
		else
		{
			w->track = 0;	 /* set track number 0 */
		}
		w->track_reg = w->track;
    }

	if ((data & ~FDC_MASK_TYPE_I) == FDC_SEEK)
	{
	UINT8 newtrack = w->data;
		/* if it is a real floppy, issue a seek command */
        /* simulate seek time busy signal */
		w->busy_count = abs(newtrack - w->track) * ((data & FDC_STEP_RATE) + 1);
        if (w->image_file == REAL_FDD)
			w->track = newtrack;//osd_fdc_seek(newtrack, &w->track);
		else
			w->track = newtrack;	/* get track number from data register */
		w->track_reg = w->track;
	}

	if ((data & ~(FDC_STEP_UPDATE | FDC_MASK_TYPE_I)) == FDC_STEP)
	{
		/* if it is a real floppy, issue a step command */
        /* simulate seek time busy signal */
		w->busy_count = ((data & FDC_STEP_RATE) + 1);
		if (w->image_file == REAL_FDD)
            		w->track += w->direction;//osd_fdc_step(w->direction, &w->track);
		else
			w->track += w->direction;	/* adjust track number */
	}

	if ((data & ~(FDC_STEP_UPDATE | FDC_MASK_TYPE_I)) == FDC_STEP_IN)
	{
        w->direction = +1;
		/* simulate seek time busy signal */
		w->busy_count = ((data & FDC_STEP_RATE) + 1);
		/* if it is a real floppy, issue a step command */
        if (w->image_file == REAL_FDD)
			w->track += w->direction;//osd_fdc_step(w->direction, &w->track);
		else
			w->track += w->direction;	/* adjust track number */
	}

	if ((data & ~(FDC_STEP_UPDATE | FDC_MASK_TYPE_I)) == FDC_STEP_OUT)
	{
        w->direction = -1;
		/* simulate seek time busy signal */
		w->busy_count = ((data & FDC_STEP_RATE) + 1);
		/* if it is a real floppy, issue a step command */
        if (w->image_file == REAL_FDD)
			w->track += w->direction;//osd_fdc_step(w->direction, &w->track);
		else
			w->track += w->direction;	/* adjust track number */
	}

	if (w->busy_count)
		w->status = STA_1_BUSY;

/* toggle index pulse at read */
	w->status_ipl = STA_1_IPL;

	if (w->track >= w->tracks)
		w->status |= STA_1_SEEK_ERR;

	if (w->track == 0)
		w->status |= STA_1_TRACK0;

    if (w->mode == 0)
        w->status |= STA_1_WRITE_PRO;

    if (data & FDC_STEP_UPDATE)
		w->track_reg = w->track;

	if (data & FDC_STEP_HDLOAD)
		w->status |= STA_1_HD_LOADED;

	if (data & FDC_STEP_VERIFY)
		if (w->track_reg != w->track)
			w->status |= STA_1_SEEK_ERR;
}

/* write the FDC track register */
void wd179x_track_w(UINT8 data)
{
WD179X *w = wd[drv];
	w->track = w->track_reg = data;
}

/* write the FDC sector register */
void wd179x_sector_w(UINT8 data)
{
WD179X *w = wd[drv];

	w->sector = data;
}

/* write the FDC data register */
void wd179x_data_w(UINT8 data)
{
WD179X *w = wd[drv];

	if (w->data_count > 0)
	{
		w->buffer[w->data_offset++] = data;
		if (--w->data_count <= 0)
		{
			w->status_drq = 0;
			if (w->callback)
				(*w->callback) (WD179X_DRQ_CLR);
			if (w->command == FDC_WRITE_TRK)
				write_track(w);
			else
				write_sector(w);
			w->data_offset = 0;
			if (w->callback)
				(*w->callback) (WD179X_IRQ_SET);
		}
	}
	w->data = data;
}

/*************************************************************/
/* Function: svi_LoadDisk                                    */
/* Purpose:  Try to load a disk image                        */
/*************************************************************/
unsigned char svi_LoadDisk(unsigned char disk, char *filename)
{
	unsigned char status = 0;
	char s[256];
	FILE *f;
	int fsize;
	
	if (strstr(filename,".\\")!=filename)
	{
		strcpy(s,dskPath);
		strcat(s,filename);
		if (f=fopen(s,"rb"))
		{
			strcpy(filename,s);
			fclose(f);
		}
	}
	if (!(f=fopen(filename,"rb")))
		return 0;
	fseek(f,0,SEEK_END);
	fsize=ftell(f);
	switch (fsize)
	{
		case 346112 : svi_disk_heads[disk] = 2;
			      status=1;
			      break;
		case 172032 : svi_disk_heads[disk] = 1;
			      status=1;
			      break;
	}
	fclose(f);
	return status;
}

/*************************************************************/
/* Function: svi_fdc_callback                                */
/* Purpose:  Callback routine for the FDC.                   */
/*************************************************************/
void svi_fdc_callback(int param)
{
	switch( param )
	{
		case WD179X_IRQ_CLR:
			fdc_status &= ~0x80;
	        	break;
		case WD179X_IRQ_SET:
			fdc_status |= 0x80;
        		break;
		case WD179X_DRQ_CLR:
			fdc_status &= ~0x40;
			break;
		case WD179X_DRQ_SET:
			fdc_status |= 0x40;
	        	break;
	}
}

/*************************************************************/
/* Function: svi_fdc_disk_motor                              */
/* Purpose:  Floppy disk and motor select.                   */
/*************************************************************/
void svi_fdc_disk_motor(unsigned char data)
{
	unsigned char seldrive = 255;
	
	if (data == 0)
	{
		wd179x_stop_drive();
		return;
	}
	if (data & 2)
	{
		seldrive=1;
	}
	if (data & 1)
	{
		seldrive=0;
	}
	if (seldrive > 3) return;
	fdc_drive = seldrive;
	wd179x_select_drive(fdc_drive, fdc_head, svi_fdc_callback);
}


/*************************************************************/
/* Function: svi_fdc_density_side                            */
/* Purpose:  Floppy density and head select.                 */
/*************************************************************/
void svi_fdc_density_side(unsigned char data)
{
	unsigned char sectors_track;
	unsigned short int sector_size;

	if (data & 2)
		fdc_head = 1;
	else
		fdc_head = 0;

	if (data & 1)
	{
		fdc_density = DEN_FM_LO;
		sectors_track =  18;
		sector_size = 128;
	}
	else
	{
		fdc_density = DEN_MFM_LO;
		sectors_track =  17;
		sector_size = 256;
	}
	wd179x_set_geometry(fdc_density, fdc_drive, svi_disk_tracks, svi_disk_heads[fdc_drive], sectors_track, sector_size, 0, 0, 1);
}

#endif

#define EVENT_CLOCK	0

#define SET_BANK(s, e, w, r) { \
	int sb = (s) >> 13, eb = (e) >> 13; \
	for(int i = sb; i <= eb; i++) { \
		if((w) == wdmy) { \
			wbank[i] = wdmy; \
		} else { \
			wbank[i] = (w) + 0x2000 * (i - sb); \
		} \
		if((r) == rdmy) { \
			rbank[i] = rdmy; \
		} else { \
			rbank[i] = (r) + 0x2000 * (i - sb); \
		} \
	} \
}

bool MEMORY_EX::load_cart(const _TCHAR *file_path)
{
	bool result = false;
	FILEIO* fio = new FILEIO();
	if(fio->Fopen(file_path, FILEIO_READ_BINARY)) {
		memset(rom, 0xff, sizeof(rom));
		fio->Fread(rom, sizeof(rom), 1);
		fio->Fread(r12, sizeof(r12), 1);
		SET_BANK(0x0000, 0x7fff, wdmy, rom);
		SET_BANK(0x8000, 0xffff, ram, ram);
		fio->Fclose();
		result = true;
	}
	delete fio;
	return result;
}

void MEMORY_EX::open_cart(const _TCHAR *file_path)
{
	if(load_cart(file_path)) {
		inserted = true;
	}
}

void MEMORY_EX::close_cart()
{
	SET_BANK(0x0000, 0x7fff, wdmy, bio);
	SET_BANK(0x8000, 0xffff, ram, ram);
	inserted = false;
}

// memory bus
void MEMORY_EX::initialize()
{
	svi_UseDisk = 0;
	memset(bio, 0xff, sizeof(bio));
	memset(rdmy, 0xff, sizeof(rdmy));
	memset(wdmy, 0xff, sizeof(wdmy));
	FILEIO* fio = new FILEIO();
	if((fio->Fopen(create_local_path(_T("SVI318.ROM")), FILEIO_READ_BINARY)) ||
	   (fio->Fopen(create_local_path(_T("SVI328.ROM")), FILEIO_READ_BINARY)) ||
	   (fio->Fopen(create_local_path(_T("SVI328a.ROM")), FILEIO_READ_BINARY))) {
		fio->Fread(bio, sizeof(bio), 1);
		fio->Fclose();
	}
	delete fio;
#if defined(FDD_PATCH_SLOT)
	wd179x_init(1);
	for(int i = 0; i < MAX_DRIVE; i++) {
		disk[i] = new DISK(emu);
		disk[i]->set_device_name(_T("%s/Disk #%d"), this_device_name, i + 1);
		disk[i]->drive_type = DRIVE_TYPE_2DD;
	}
#endif
	close_cart();
	close_tape();
}

#if defined(FDD_PATCH_SLOT)
void MEMORY_EX::release()
{
	for(int i = 0; i < MAX_DRIVE; i++) {
		if(disk[i]) {
			disk[i]->close();
			delete disk[i];
		}
	}
}
#endif

void MEMORY_EX::reset()
{
	if (!inserted) {
		memset(rom, 0xff, sizeof(rom));
		memset(r12, 0xff, sizeof(r12));
	}
	memset(ram, 0, sizeof(ram));
	memset(r21, 0, sizeof(r21));
	memset(r22, 0, sizeof(r22));
	memset(r31, 0, sizeof(r31));
	memset(r32, 0, sizeof(r32));
	memset(tapedata, 0, sizeof(tapedata));
}

void MEMORY_EX::write_data8(uint32_t addr, uint32_t data)
{
	wbank[addr >> 13][addr & 0x1fff] = data;
}

uint32_t MEMORY_EX::read_io8(uint32_t addr)
{
	unsigned char port = addr;
	uint32_t ret=0xff;
	switch (port)
	{
		case 0x30 : if (svi_UseDisk == 1)
				ret=wd179x_status_r();
			    break;

		case 0x31 : if (svi_UseDisk == 1)
				ret=wd179x_track_r();
			    break;

		case 0x32 : if (svi_UseDisk == 1)
				ret=wd179x_sector_r();
			    break;

		case 0x33 : if (svi_UseDisk == 1)
				ret=wd179x_data_r();
			    break;

		case 0x34 : if (svi_UseDisk == 1)
				ret=fdc_status;
			    break;
		default:
				ret=strig;
	}

	return ret;
}

void MEMORY_EX::write_io8(uint32_t addr, uint32_t data)
{
	// Write the value to the appropriate port
	unsigned char port = addr;
	unsigned char value = data;
	switch (port)
	{
		case 0x30 : if (svi_UseDisk == 1)
			    {
				wd179x_command_w(value);
			    	if ((value & ~FDC_MASK_TYPE_I) == FDC_RESTORE)
				    	fdc_status |= 0x80;
			    }
			    break;
	
		case 0x31 : if (svi_UseDisk == 1)
				wd179x_track_w(value);
			    break;

		case 0x32 : if (svi_UseDisk == 1)
				wd179x_sector_w(value);
			    break;

		case 0x33 : if (svi_UseDisk == 1)
				wd179x_data_w(value);
			    break;

		case 0x34 : if (svi_UseDisk == 1)
				svi_fdc_disk_motor(value);
			    break;

		case 0x38 : if (svi_UseDisk == 1)
				svi_fdc_density_side(value);
			    break;
		default:
				strig &= 0xE0;
				data &= 0x1F;
				strig |= data;
	}
}

uint32_t MEMORY_EX::read_data8(uint32_t addr)
{
	addr &= 0xffff;
	if (!play)
		return rbank[addr >> 13][addr & 0x1fff];
	if (addr==0x210a) {
		count=0;
		done=0;
		return 0xAF;
	}
	if (addr==0x210b) {
		strig &= 0x7F;
		return 0x00;
	}
	if (addr==0x210c)
		return 0xc9;
	if (addr==0x21a9)
		return 0x3e;
	if (addr==0x21aa) {
		while ((tapePos<tapeLen)&&(!done))
		{
			switch (tapedata[tapePos++])
			{
				case 0x7F : done=count>=10; break;
				case 0x55 : count++; break;
				default		: count=0; break;
			}
		}
		return tapedata[tapePos++];
	}
	if (addr==0x21ab)
			return 0xc9;
	return rbank[addr >> 13][addr & 0x1fff];
}

void MEMORY_EX::write_signal(int id, uint32_t data, uint32_t mask) {
	if (data & 16) {
		if (data & 4) {
			SET_BANK(0x8000, 0xffff, ram, ram);
		} else {
			if (data % 2 == 1) {
				SET_BANK(0x8000, 0xffff, r22, r22);
			} else {
				SET_BANK(0x8000, 0xffff, wdmy,r12);
			}
		}
	} else {
		SET_BANK(0x8000, 0xffff, r32, r32);
	}
	data &= 0x0f;
	if (data==3 || data==7) {
		SET_BANK(0x0000, 0x7fff, r31, r31);
	} else if (data==13) {
		SET_BANK(0x0000, 0x7fff, r21, r21);
	} else {
		if (data % 2 == 1) {
			SET_BANK(0x0000, 0x7fff, wdmy,bio);
		} else {
			SET_BANK(0x0000, 0x7fff, wdmy,rom);
		}
	}
	return;
}

uint32_t MEMORY_EX::fetch_op(uint32_t addr, int* wait)
{
	*wait = 1;
	return read_data8(addr);
}

bool MEMORY_EX::play_tape(const _TCHAR* file_path)
{
	FILEIO* fio = new FILEIO();
	if(fio->Fopen(file_path, FILEIO_READ_BINARY)) {
		fio->Fread(tapedata, sizeof(tapedata), 1);
		tapeLen = fio->Ftell();
		fio->Fclose();
		play = true;
	}
	delete fio;
	strig = 0xBF;
	tapePos=0;
	return true;
}

void MEMORY_EX::close_tape()
{
	memset(tapedata, 0, sizeof(tapedata));
	strig = 0xFF;
	tapePos=0;
	play = false;
}


#if defined(FDD_PATCH_SLOT)

void MEMORY_EX::open_disk(int drv, const _TCHAR* file_path, int bank)
{
	if(drv < MAX_DRIVE) {
//		disk[drv]->open(file_path, bank);
		if (svi_LoadDisk(drv, (char *)file_path) > 0)
		{
			wd179x_InitDiskImage(drv, file_path);
			disk[drv]->inserted = true;
			svi_UseDisk = 1;
		}
	}
}

void MEMORY_EX::close_disk(int drv)
{
	if(drv < MAX_DRIVE && disk[drv]->inserted) {
//		disk[drv]->close();
		wd179x_CloseDiskImage(drv);
		disk[drv]->inserted = false;
		svi_UseDisk = 0; 
	}
}

bool MEMORY_EX::is_disk_inserted(int drv)
{
	if(drv < MAX_DRIVE) {
		return disk[drv]->inserted;
	}
	return false;
}

void MEMORY_EX::is_disk_protected(int drv, bool value)
{
	if(drv < MAX_DRIVE) {
		disk[drv]->write_protected = value;
	}
}

bool MEMORY_EX::is_disk_protected(int drv)
{
	if(drv < MAX_DRIVE) {
		return disk[drv]->write_protected;
	}
	return false;
}

#endif

#define STATE_VERSION	1

bool MEMORY_EX::process_state(FILEIO* state_fio, bool loading)
{
	if(!state_fio->StateCheckUint32(STATE_VERSION)) {
		return false;
	}
	if(!state_fio->StateCheckInt32(this_device_id)) {
		return false;
	}
	state_fio->StateValue(inserted);
	// post process
	if(loading) {
		if(inserted) {
			SET_BANK(0x0000, 0x7fff, wdmy, rom);
			SET_BANK(0x8000, 0xffff, ram, ram);
		} else {
			SET_BANK(0x0000, 0x7fff, wdmy, bio);
			SET_BANK(0x8000, 0xffff, ram, ram);
		}
	}
	return true;
}

msx_ex.h

/*
	Common Source Code Project
	SVI-3x8

	Origin : src/vm/msx/msx.h

	modified by tanam
	Date   : 2018.12.09-

	[ virtual machine ]
*/

#ifndef _MSX_EX_H_
#define _MSX_EX_H_

#if defined(_SVI3X8)
#define DEVICE_NAME		"SPECTRAVIDEO SVI-3x8"
#define CONFIG_NAME		"svi3x8"
#endif

#if defined(_SVI3X8)
#define _MSX1_VARIANTS
#define MAINROM_SLOT	0x00
#define CART1_SLOT	0x01
#define FDD_PATCH_SLOT	0x8B

#endif

// device informations for virtual machine
#define FRAMES_PER_SEC		60
#define LINES_PER_FRAME		262
#define CPU_CLOCKS		3579545
#if defined(_MSX1_VARIANTS)
#define SCREEN_WIDTH		512
#define SCREEN_HEIGHT		384
#define WINDOW_WIDTH_ASPECT	576
#endif
#define TMS9918A_VRAM_SIZE	0x4000
#define TMS9918A_LIMIT_SPRITES
//#if defined(FDD_PATCH_SLOT)
#define MAX_DRIVE		2
//#define SUPPORT_MEDIA_TYPE_1DD
//#define Z80_PSEUDO_BIOS
//#endif
#define HAS_AY_3_8910
// for Flappy Limited '85
#define AY_3_891X_PORT_MODE	0x80

// device informations for win32
#define USE_CART		2
#define USE_TAPE		1
//#if defined(FDD_PATCH_SLOT)
#define USE_FLOPPY_DISK		2
//#endif
#define USE_AUTO_KEY		6
#define USE_AUTO_KEY_RELEASE	10
#define USE_SOUND_VOLUME	7
#define USE_JOYSTICK
#define USE_DEBUGGER
#define USE_STATE
//#define USE_PRINTER
//#define USE_PRINTER_TYPE	4

#include "../../common.h"
#include "../../fileio.h"
#include "../vm_template.h"

#ifdef USE_SOUND_VOLUME
static const _TCHAR *sound_device_caption[] = {
	_T("PSG"), _T("Beep"), _T("CMT (Signal)"),
	_T("Cart#1"), _T("Cart#2"), _T("MSX-MUSIC"), _T("Noise (CMT)"),
};
#endif

class EMU;
class DEVICE;
class EVENT;

//class DATAREC;
class I8255;
class IO;
class NOT;
class AY_3_891X;
class PCM1BIT;
class TMS9918A;
class Z80;

class JOYSTICK;
class KEYBOARD;
class MEMORY_EX;
class SLOT_MAINROM;
class SLOT_CART;
#if defined(USE_PRINTER)
class PRINTER;
#endif
//#if defined(FDD_PATCH_SLOT)
//class SLOT_FDD_PATCH;
//#endif

class VM : public VM_TEMPLATE
{
protected:
//	EMU* emu;
	
	// devices
	EVENT* event;
	
//	DATAREC* drec;
	I8255* pio;
	IO* io;
	NOT* not_remote;
	AY_3_891X* psg;
	PCM1BIT* pcm;
	TMS9918A* vdp;
	Z80* cpu;
	
	JOYSTICK* joystick;
	KEYBOARD* keyboard;
	MEMORY_EX* memory;
	SLOT_MAINROM *slot_mainrom;
	SLOT_CART *slot_cart[1];
//#ifdef USE_PRINTER
//	PRINTER* printer;
//#endif
	
public:
	// ----------------------------------------
	// initialize
	// ----------------------------------------
	
	VM(EMU* parent_emu);
	~VM();
	
	// ----------------------------------------
	// for emulation class
	// ----------------------------------------
	
	// drive virtual machine
	void reset();
	void run();
	double get_frame_rate()
	{
		return FRAMES_PER_SEC;
	}
	
#ifdef USE_DEBUGGER
	// debugger
	DEVICE *get_cpu(int index);
#endif
	
	// draw screen
	void draw_screen();
	
	// sound generation
	void initialize_sound(int rate, int samples);
	uint16_t* create_sound(int* extra_frames);
	int get_sound_buffer_ptr();
#ifdef USE_SOUND_VOLUME
	void set_sound_device_volume(int ch, int decibel_l, int decibel_r);
#endif
	
	// user interface
	void open_cart(int drv, const _TCHAR* file_path);
	void close_cart(int drv);
	bool is_cart_inserted(int drv);
	void play_tape(int drv, const _TCHAR* file_path);
	void rec_tape(int drv, const _TCHAR* file_path);
	void close_tape(int drv);
	bool is_tape_inserted(int drv);
	bool is_tape_playing(int drv);
	bool is_tape_recording(int drv);
	int get_tape_position(int drv);
	const _TCHAR* get_tape_message(int drv);
	void push_play(int drv);
	void push_stop(int drv);
	void push_fast_forward(int drv);
	void push_fast_rewind(int drv);
	void push_apss_forward(int drv) {}
	void push_apss_rewind(int drv) {}

#if defined(FDD_PATCH_SLOT)
	void open_floppy_disk(int drv, const _TCHAR* file_path, int bank);
	void close_floppy_disk(int drv);
	bool is_floppy_disk_inserted(int drv);
	void is_floppy_disk_protected(int drv, bool value);
	bool is_floppy_disk_protected(int drv);
	uint32_t is_floppy_disk_accessed();
#endif

	bool is_frame_skippable();
	
	void update_config();
	bool process_state(FILEIO* state_fio, bool loading);
	
	// ----------------------------------------
	// for each device
	// ----------------------------------------
	
	// devices
	DEVICE* get_device(int id);
//	DEVICE* dummy;
//	DEVICE* first_device;
//	DEVICE* last_device;
};

#endif

msx_ex.cpp

/*
	Common Source Code Project
	SVI-3x8

	Origin : src/vm/msx/msx_ex.cpp

	modified by tanam
	Date   : 2018.12.09-

	[ virtual machine ]
*/

#include "msx_ex.h"
#include "../../emu.h"
#include "../device.h"
#include "../event.h"

//#include "../datarec.h"
#include "../i8255.h"
#include "../io.h"
#include "../noise.h"
#include "../not.h"
#include "../ay_3_891x.h"
#include "../pcm1bit.h"
#include "../tms9918a.h"
#include "../z80.h"

#ifdef USE_DEBUGGER
#include "../debugger.h"
#endif

#include "joystick.h"
#include "keyboard.h"
#include "memory_ex.h"
#ifdef USE_PRINTER
#include "printer.h"
#include "../prnfile.h"
#endif

// ----------------------------------------------------------------------------
// initialize
// ----------------------------------------------------------------------------

VM::VM(EMU* parent_emu) : VM_TEMPLATE(parent_emu)
{
	// create devices
	first_device = last_device = NULL;
	dummy = new DEVICE(this, emu);	// must be 1st device
	event = new EVENT(this, emu);	// must be 2nd device
	
//	drec = new DATAREC(this, emu);
//	drec->set_context_noise_play(new NOISE(this, emu));
//	drec->set_context_noise_stop(new NOISE(this, emu));
//	drec->set_context_noise_fast(new NOISE(this, emu));
	pio = new I8255(this, emu);
	io = new IO(this, emu);
	not_remote = new NOT(this, emu);
	psg = new AY_3_891X(this, emu);
	pcm = new PCM1BIT(this, emu);
	vdp = new TMS9918A(this, emu);
	cpu = new Z80(this, emu);
	
	joystick = new JOYSTICK(this, emu);
	keyboard = new KEYBOARD(this, emu);
	memory = new MEMORY_EX(this, emu);
#ifdef USE_PRINTER
	printer = new PRINTER(this, emu);
#endif
	
	// set contexts
	event->set_context_cpu(cpu);
	event->set_context_sound(psg);
	event->set_context_sound(pcm);
//	event->set_context_sound(drec);
//	event->set_context_sound(drec->get_context_noise_play());
//	event->set_context_sound(drec->get_context_noise_stop());
//	event->set_context_sound(drec->get_context_noise_fast());	
//	drec->set_context_ear(psg, SIG_AY_3_891X_PORT_A, 0x80);
	pio->set_context_port_c(keyboard, SIG_KEYBOARD_COLUMN, 0x0f, 0);
	pio->set_context_port_c(not_remote, SIG_NOT_INPUT, 0x10, 0);
//	not_remote->set_context_out(drec, SIG_DATAREC_REMOTE, 1);
//	pio->set_context_port_c(drec, SIG_DATAREC_MIC, 0x20, 0);
	pio->set_context_port_c(pcm, SIG_PCM1BIT_SIGNAL, 0x80, 0);
	psg->set_context_port_b(memory, SIG_MEMORY_SEL, 0x0f, 0);
	vdp->set_context_irq(cpu, SIG_CPU_IRQ, 1);
	
	joystick->set_context_psg(psg);
	joystick->set_context_memory(memory);
	keyboard->set_context_pio(pio);
	
#ifdef USE_PRINTER
	if(config.printer_type == 0) {  
		printer->set_context_prn(new PRNFILE(this, emu));
	} else {
		printer->set_context_prn(printer);
	}
#endif

	// cpu bus
	cpu->set_context_mem(memory);
	cpu->set_context_io(io);
	cpu->set_context_intr(dummy);
#ifdef USE_DEBUGGER
	cpu->set_context_debugger(new DEBUGGER(this, emu));
#endif
	
	// i/o bus
	io->set_iomap_range_rw(0x30, 0x38, memory);
	io->set_iomap_range_w(0x80, 0x81, vdp);
	io->set_iomap_range_r(0x84, 0x85, vdp);
	io->set_iomap_range_w(0x94, 0x97, pio);
	io->set_iomap_range_r(0x99, 0x9a, pio);
	io->set_iomap_alias_w(0x88, psg, 0);	// PSG ch
	io->set_iomap_alias_w(0x8c, psg, 1);	// PSG data
	io->set_iomap_alias_r(0x90, psg, 1);	// STICK
	io->set_iomap_alias_r(0x98, memory, 1);	// STRIG
#ifdef USE_PRINTER
	io->set_iomap_range_rw(0x10, 0x11, printer);
#endif
	
	// initialize all devices
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->initialize();
	}
}

VM::~VM()
{
	// delete all devices
	for(DEVICE* device = first_device; device;) {
		DEVICE *next_device = device->next_device;
		device->release();
		delete device;
		device = next_device;
	}
}

DEVICE* VM::get_device(int id)
{
	for(DEVICE* device = first_device; device; device = device->next_device) {
		if(device->this_device_id == id) {
			return device;
		}
	}
	return NULL;
}

// ----------------------------------------------------------------------------
// drive virtual machine
// ----------------------------------------------------------------------------

void VM::reset()
{
	// reset all devices
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->reset();
	}
}

void VM::run()
{
	event->drive();
}

// ----------------------------------------------------------------------------
// debugger
// ----------------------------------------------------------------------------

#ifdef USE_DEBUGGER
DEVICE *VM::get_cpu(int index)
{
	if(index == 0) {
		return cpu;
	}
	return NULL;
}
#endif

// ----------------------------------------------------------------------------
// draw screen
// ----------------------------------------------------------------------------

void VM::draw_screen()
{
	vdp->draw_screen();
}

// ----------------------------------------------------------------------------
// soud manager
// ----------------------------------------------------------------------------

void VM::initialize_sound(int rate, int samples)
{
	// init sound manager
	event->initialize_sound(rate, samples);
	
	// init sound gen
	psg->initialize_sound(rate, 3579545, samples, 0, 0);
	pcm->initialize_sound(rate, 8000);
}

uint16_t* VM::create_sound(int* extra_frames)
{
	return event->create_sound(extra_frames);
}

int VM::get_sound_buffer_ptr()
{
	return event->get_sound_buffer_ptr();
}

#ifdef USE_SOUND_VOLUME
void VM::set_sound_device_volume(int ch, int decibel_l, int decibel_r)
{
	if(ch == 0) {
	psg->set_volume(1, decibel_l, decibel_r);
	} else if(ch == 1) {
		pcm->set_volume(0, decibel_l, decibel_r);
//	} else if(ch == 2) {
//		drec->set_volume(0, decibel_l, decibel_r);
//	} else if(ch == 6) {
//		drec->get_context_noise_play()->set_volume(0, decibel_l, decibel_r);
//		drec->get_context_noise_stop()->set_volume(0, decibel_l, decibel_r);
//		drec->get_context_noise_fast()->set_volume(0, decibel_l, decibel_r);
	}
}
#endif

// ----------------------------------------------------------------------------
// user interface
// ----------------------------------------------------------------------------

void VM::open_cart(int drv, const _TCHAR* file_path)
{
	if(drv == 0) {
//		slot_cart[drv]->open_cart(file_path);
//		memory->set_context_slot(MAINROM_SLOT, slot_cart[0]);
		memory->open_cart(file_path);
	}

	reset();
}

void VM::close_cart(int drv)
{
	if(drv == 0) {
//		slot_cart[drv]->close_cart();
//		memory->set_context_slot(MAINROM_SLOT, slot_mainrom);
		memory->close_cart();
	}
	reset();
}

bool VM::is_cart_inserted(int drv)
{
	if(drv == 0) {
//		return slot_cart[drv]->is_cart_inserted();
		return memory->is_cart_inserted();
	} else {
		return false;
	}
}

void VM::play_tape(int drv, const _TCHAR* file_path)
{
	memory->play_tape(file_path);
//	bool remote = drec->get_remote();
	
//	if(drec->play_tape(file_path) && remote) {
//		// if machine already sets remote on, start playing now
//		push_play(drv);
//	}
}

void VM::rec_tape(int drv, const _TCHAR* file_path)
{
//	bool remote = drec->get_remote();
	
//	if(drec->rec_tape(file_path) && remote) {
//		// if machine already sets remote on, start recording now
//		push_play(drv);
//	}
}

void VM::close_tape(int drv)
{
	emu->lock_vm();
	memory->close_tape();
	emu->unlock_vm();
//	drec->set_remote(false);
}

bool VM::is_tape_inserted(int drv)
{
	return memory->is_tape_inserted();
}

bool VM::is_tape_playing(int drv)
{
	return false;
//	return drec->is_tape_playing();
}

bool VM::is_tape_recording(int drv)
{
	return false;
//	return drec->is_tape_recording();
}

int VM::get_tape_position(int drv)
{
	return 0;
//	return drec->get_tape_position();
}

const _TCHAR* VM::get_tape_message(int drv)
{
	return memory->get_message();
}

void VM::push_play(int drv)
{
//	drec->set_remote(false);
//	drec->set_ff_rew(0);
//	drec->set_remote(true);
}

void VM::push_stop(int drv)
{
//	drec->set_remote(false);
}

void VM::push_fast_forward(int drv)
{
//	drec->set_remote(false);
//	drec->set_ff_rew(1);
//	drec->set_remote(true);
}

void VM::push_fast_rewind(int drv)
{
//	drec->set_remote(false);
//	drec->set_ff_rew(-1);
//	drec->set_remote(true);
}

#if defined(FDD_PATCH_SLOT)
void VM::open_floppy_disk(int drv, const _TCHAR* file_path, int bank)
{
	memory->open_disk(drv, file_path, bank);
}

void VM::close_floppy_disk(int drv)
{
	memory->close_disk(drv);
}

bool VM::is_floppy_disk_inserted(int drv)
{
	return memory->is_disk_inserted(drv);
}

void VM::is_floppy_disk_protected(int drv, bool value)
{
	memory->is_disk_protected(drv, value);
}

bool VM::is_floppy_disk_protected(int drv)
{
	return memory->is_disk_protected(drv);
}

uint32_t VM::is_floppy_disk_accessed()
{
	return memory->read_signal(0);
}
#endif

bool VM::is_frame_skippable()
{
	return event->is_frame_skippable();
}

void VM::update_config()
{
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->update_config();
	}
}

#define STATE_VERSION	5

bool VM::process_state(FILEIO* state_fio, bool loading)
{
	if(!state_fio->StateCheckUint32(STATE_VERSION)) {
		return false;
	}
	for(DEVICE* device = first_device; device; device = device->next_device) {
		const char *name = typeid(*device).name() + 6; // skip "class "
		int len = strlen(name);
		
		if(!state_fio->StateCheckInt32(len)) {
			return false;
		}
		if(!state_fio->StateCheckBuffer(name, len, 1)) {
			return false;
		}
		if(!device->process_state(state_fio, loading)) {
			return false;
		}
	}
	return true;
}

SVI-3x8エミュレータをつくる その3

モリーマップを見直して以下に対応しました。

https://www.msx.org/news/en/nyyrikki-msx-rom-loader-svi

msx_ex.cpp

/*
	Common Source Code Project
	SVI-3x8

	Origin : src/vm/msx/msx_ex.cpp

	modified by tanam
	Date   : 2018.12.09-

	[ virtual machine ]
*/

#include "msx_ex.h"
#include "../../emu.h"
#include "../device.h"
#include "../event.h"

#include "../datarec.h"
#include "../i8255.h"
#include "../io.h"
#include "../noise.h"
#include "../not.h"
#include "../ay_3_891x.h"
#include "../pcm1bit.h"
#include "../tms9918a.h"
#include "../z80.h"

#ifdef USE_DEBUGGER
#include "../debugger.h"
#endif

#include "joystick.h"
#include "keyboard.h"
#include "memory_ex.h"
#ifdef USE_PRINTER
#include "printer.h"
#include "../prnfile.h"
#endif

// ----------------------------------------------------------------------------
// initialize
// ----------------------------------------------------------------------------

VM::VM(EMU* parent_emu) : VM_TEMPLATE(parent_emu)
{
	// create devices
	first_device = last_device = NULL;
	dummy = new DEVICE(this, emu);	// must be 1st device
	event = new EVENT(this, emu);	// must be 2nd device
	
	drec = new DATAREC(this, emu);
	drec->set_context_noise_play(new NOISE(this, emu));
	drec->set_context_noise_stop(new NOISE(this, emu));
	drec->set_context_noise_fast(new NOISE(this, emu));
	pio = new I8255(this, emu);
	io = new IO(this, emu);
	not_remote = new NOT(this, emu);
	psg = new AY_3_891X(this, emu);
	pcm = new PCM1BIT(this, emu);
	vdp = new TMS9918A(this, emu);
	cpu = new Z80(this, emu);
	
	joystick = new JOYSTICK(this, emu);
	keyboard = new KEYBOARD(this, emu);
	memory = new MEMORY_EX(this, emu);
#ifdef USE_PRINTER
	printer = new PRINTER(this, emu);
#endif
	
	// set contexts
	event->set_context_cpu(cpu);
	event->set_context_sound(psg);
	event->set_context_sound(pcm);
	event->set_context_sound(drec);
	event->set_context_sound(drec->get_context_noise_play());
	event->set_context_sound(drec->get_context_noise_stop());
	event->set_context_sound(drec->get_context_noise_fast());	
	drec->set_context_ear(psg, SIG_AY_3_891X_PORT_A, 0x80);
	pio->set_context_port_c(keyboard, SIG_KEYBOARD_COLUMN, 0x0f, 0);
	pio->set_context_port_c(not_remote, SIG_NOT_INPUT, 0x10, 0);
	not_remote->set_context_out(drec, SIG_DATAREC_REMOTE, 1);
	pio->set_context_port_c(drec, SIG_DATAREC_MIC, 0x20, 0);
	pio->set_context_port_c(pcm, SIG_PCM1BIT_SIGNAL, 0x80, 0);
	psg->set_context_port_b(memory, SIG_MEMORY_SEL, 0x0f, 0);
	vdp->set_context_irq(cpu, SIG_CPU_IRQ, 1);
	
	joystick->set_context_psg(psg);
	joystick->set_context_memory(memory);
	keyboard->set_context_pio(pio);
	
#ifdef USE_PRINTER
	if(config.printer_type == 0) {  
		printer->set_context_prn(new PRNFILE(this, emu));
	} else {
		printer->set_context_prn(printer);
	}
#endif

	// cpu bus
	cpu->set_context_mem(memory);
	cpu->set_context_io(io);
	cpu->set_context_intr(dummy);
#ifdef USE_DEBUGGER
	cpu->set_context_debugger(new DEBUGGER(this, emu));
#endif
	
	// i/o bus
	io->set_iomap_range_w(0x80, 0x81, vdp);
	io->set_iomap_range_r(0x84, 0x85, vdp);
	io->set_iomap_range_w(0x94, 0x97, pio);
	io->set_iomap_range_r(0x99, 0x9a, pio);
	io->set_iomap_alias_w(0x88, psg, 0);	// PSG ch
	io->set_iomap_alias_w(0x8c, psg, 1);	// PSG data
	io->set_iomap_alias_r(0x90, psg, 1);	// STICK
	io->set_iomap_alias_rw(0x98, memory, 0);	// STRIG
#ifdef USE_PRINTER
	io->set_iomap_range_rw(0x10, 0x11, printer);
#endif
	
	// initialize all devices
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->initialize();
	}
}

VM::~VM()
{
	// delete all devices
	for(DEVICE* device = first_device; device;) {
		DEVICE *next_device = device->next_device;
		device->release();
		delete device;
		device = next_device;
	}
}

DEVICE* VM::get_device(int id)
{
	for(DEVICE* device = first_device; device; device = device->next_device) {
		if(device->this_device_id == id) {
			return device;
		}
	}
	return NULL;
}

// ----------------------------------------------------------------------------
// drive virtual machine
// ----------------------------------------------------------------------------

void VM::reset()
{
	// reset all devices
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->reset();
	}
}

void VM::run()
{
	event->drive();
}

// ----------------------------------------------------------------------------
// debugger
// ----------------------------------------------------------------------------

#ifdef USE_DEBUGGER
DEVICE *VM::get_cpu(int index)
{
	if(index == 0) {
		return cpu;
	}
	return NULL;
}
#endif

// ----------------------------------------------------------------------------
// draw screen
// ----------------------------------------------------------------------------

void VM::draw_screen()
{
	vdp->draw_screen();
}

// ----------------------------------------------------------------------------
// soud manager
// ----------------------------------------------------------------------------

void VM::initialize_sound(int rate, int samples)
{
	// init sound manager
	event->initialize_sound(rate, samples);
	
	// init sound gen
	psg->initialize_sound(rate, 3579545, samples, 0, 0);
	pcm->initialize_sound(rate, 8000);
}

uint16_t* VM::create_sound(int* extra_frames)
{
	return event->create_sound(extra_frames);
}

int VM::get_sound_buffer_ptr()
{
	return event->get_sound_buffer_ptr();
}

#ifdef USE_SOUND_VOLUME
void VM::set_sound_device_volume(int ch, int decibel_l, int decibel_r)
{
	if(ch == 0) {
	psg->set_volume(1, decibel_l, decibel_r);
	} else if(ch == 1) {
		pcm->set_volume(0, decibel_l, decibel_r);
	} else if(ch == 2) {
		drec->set_volume(0, decibel_l, decibel_r);
	} else if(ch == 6) {
		drec->get_context_noise_play()->set_volume(0, decibel_l, decibel_r);
		drec->get_context_noise_stop()->set_volume(0, decibel_l, decibel_r);
		drec->get_context_noise_fast()->set_volume(0, decibel_l, decibel_r);
	}
}
#endif

// ----------------------------------------------------------------------------
// user interface
// ----------------------------------------------------------------------------

void VM::open_cart(int drv, const _TCHAR* file_path)
{
	if(drv == 0) {
//		slot_cart[drv]->open_cart(file_path);
//		memory->set_context_slot(MAINROM_SLOT, slot_cart[0]);
		memory->open_cart(file_path);
	}

	reset();
}

void VM::close_cart(int drv)
{
	if(drv == 0) {
//		slot_cart[drv]->close_cart();
//		memory->set_context_slot(MAINROM_SLOT, slot_mainrom);
		memory->close_cart();
	}
	reset();
}

bool VM::is_cart_inserted(int drv)
{
	if(drv == 0) {
//		return slot_cart[drv]->is_cart_inserted();
		return memory->is_cart_inserted();
	} else {
		return false;
	}
}

void VM::play_tape(int drv, const _TCHAR* file_path)
{
	bool remote = drec->get_remote();
	
	if(drec->play_tape(file_path) && remote) {
		// if machine already sets remote on, start playing now
		push_play(drv);
	}
}

void VM::rec_tape(int drv, const _TCHAR* file_path)
{
	bool remote = drec->get_remote();
	
	if(drec->rec_tape(file_path) && remote) {
		// if machine already sets remote on, start recording now
		push_play(drv);
	}
}

void VM::close_tape(int drv)
{
	emu->lock_vm();
	drec->close_tape();
	emu->unlock_vm();
	drec->set_remote(false);
}

bool VM::is_tape_inserted(int drv)
{
	return drec->is_tape_inserted();
}

bool VM::is_tape_playing(int drv)
{
	return drec->is_tape_playing();
}

bool VM::is_tape_recording(int drv)
{
	return drec->is_tape_recording();
}

int VM::get_tape_position(int drv)
{
	return drec->get_tape_position();
}

const _TCHAR* VM::get_tape_message(int drv)
{
	return drec->get_message();
}

void VM::push_play(int drv)
{
	drec->set_remote(false);
	drec->set_ff_rew(0);
	drec->set_remote(true);
}

void VM::push_stop(int drv)
{
	drec->set_remote(false);
}

void VM::push_fast_forward(int drv)
{
	drec->set_remote(false);
	drec->set_ff_rew(1);
	drec->set_remote(true);
}

void VM::push_fast_rewind(int drv)
{
	drec->set_remote(false);
	drec->set_ff_rew(-1);
	drec->set_remote(true);
}

bool VM::is_frame_skippable()
{
	return event->is_frame_skippable();
}

void VM::update_config()
{
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->update_config();
	}
}

#define STATE_VERSION	5

bool VM::process_state(FILEIO* state_fio, bool loading)
{
	if(!state_fio->StateCheckUint32(STATE_VERSION)) {
		return false;
	}
	for(DEVICE* device = first_device; device; device = device->next_device) {
		const char *name = typeid(*device).name() + 6; // skip "class "
		int len = strlen(name);
		
		if(!state_fio->StateCheckInt32(len)) {
			return false;
		}
		if(!state_fio->StateCheckBuffer(name, len, 1)) {
			return false;
		}
		if(!device->process_state(state_fio, loading)) {
			return false;
		}
	}
	return true;
}

memory_ex.h

/*
	Common Source Code Project
	SVI-3x8

	Origin : src/vm/msx/memory.h

	modified by tanam
	Date   : 2018.12.09-

	[ memory ]
*/

#ifndef _MEMORY_EX_H_
#define _MEMORY_EX_H_

#include "../vm.h"
#include "../../emu.h"
#include "../device.h"

#define SIG_MEMORY_SEL	0

// memory bus
class MEMORY_EX : public DEVICE
{
private:
	uint8_t* wbank[8];
	uint8_t* rbank[8];
	uint8_t wdmy[0x2000];
	uint8_t rdmy[0x2000];
	uint8_t bio[0x8000];  /* BANK01 */
	uint8_t ram[0x8000];  /* BANK02 */
	uint8_t rom[0x8000];  /* BANK11 */
	uint8_t r12[0x8000];  /* BANK12 */
	uint8_t r21[0x8000];  /* BANK21 */
	uint8_t r22[0x8000];  /* BANK22 */
	uint8_t r31[0x8000];  /* BANK31 */
	uint8_t r32[0x8000];  /* BANK32 */
	bool inserted;
	uint8_t strig;
	
public:
	MEMORY_EX(VM_TEMPLATE* parent_vm, EMU* parent_emu) : DEVICE(parent_vm, parent_emu)
	{
		set_device_name(_T("Memory Bus"));
		strig=0xff;
	}
	~MEMORY_EX() {}
	
	// common functions
	void initialize();
	void reset();
	void write_data8(uint32_t addr, uint32_t data);
	uint32_t read_data8(uint32_t addr);
	uint32_t fetch_op(uint32_t addr, int* wait);
	bool process_state(FILEIO* state_fio, bool loading);
	void write_signal(int id, uint32_t data, uint32_t mask);
	uint32_t read_io8(uint32_t addr)
	{
		return strig;
	}
	void write_io8(uint32_t addr, uint32_t data)
	{
		strig = data;
	}
	// unique functions
	void open_cart(const _TCHAR *file_path);
	void close_cart();
	bool is_cart_inserted()
	{
		return inserted;
	}
	bool load_cart(const _TCHAR *file_path/*, uint8_t *rom*/);
};

#endif

memory_ex.cpp

/*
	Common Source Code Project
	SVI-3x8

	Origin : src/vm/msx/memory_ex.cpp

	modified by tanam
	Date   : 2018.12.09-

	[ memory ]
*/

#include "memory_ex.h"

#define EVENT_CLOCK	0

#define SET_BANK(s, e, w, r) { \
	int sb = (s) >> 13, eb = (e) >> 13; \
	for(int i = sb; i <= eb; i++) { \
		if((w) == wdmy) { \
			wbank[i] = wdmy; \
		} else { \
			wbank[i] = (w) + 0x2000 * (i - sb); \
		} \
		if((r) == rdmy) { \
			rbank[i] = rdmy; \
		} else { \
			rbank[i] = (r) + 0x2000 * (i - sb); \
		} \
	} \
}

bool MEMORY_EX::load_cart(const _TCHAR *file_path)
{
	bool result = false;
	FILEIO* fio = new FILEIO();
	if(fio->Fopen(file_path, FILEIO_READ_BINARY)) {
		memset(rom, 0xff, sizeof(rom));
		fio->Fread(rom, sizeof(rom), 1);
		fio->Fread(r12, sizeof(r12), 1);
		SET_BANK(0x0000, 0x7fff, wdmy, rom);
		SET_BANK(0x8000, 0xffff, ram, ram);
		fio->Fclose();
		result = true;
	}
	delete fio;
	return result;
}

void MEMORY_EX::open_cart(const _TCHAR *file_path)
{
	if(load_cart(file_path)) {
		inserted = true;
	}
}

void MEMORY_EX::close_cart()
{
	SET_BANK(0x0000, 0x7fff, wdmy, bio);
	SET_BANK(0x8000, 0xffff, ram, ram);
	inserted = false;
}

// memory bus
void MEMORY_EX::initialize()
{
	memset(bio, 0xff, sizeof(bio));
	memset(rdmy, 0xff, sizeof(rdmy));
	memset(wdmy, 0xff, sizeof(wdmy));
	FILEIO* fio = new FILEIO();
	if((fio->Fopen(create_local_path(_T("SVI318.ROM")), FILEIO_READ_BINARY)) ||
	   (fio->Fopen(create_local_path(_T("SVI328.ROM")), FILEIO_READ_BINARY)) ||
	   (fio->Fopen(create_local_path(_T("SVI328a.ROM")), FILEIO_READ_BINARY))) {
		fio->Fread(bio, sizeof(bio), 1);
		fio->Fclose();
	}
	delete fio;	
	close_cart();
}

void MEMORY_EX::reset()
{
	if (!inserted) {
		memset(rom, 0xff, sizeof(rom));
		memset(r12, 0xff, sizeof(r12));
	}
	memset(ram, 0, sizeof(ram));
	memset(r21, 0, sizeof(r21));
	memset(r22, 0, sizeof(r22));
	memset(r31, 0, sizeof(r31));
	memset(r32, 0, sizeof(r32));
	return;
}

void MEMORY_EX::write_data8(uint32_t addr, uint32_t data)
{
	wbank[addr >> 13][addr & 0x1fff] = data;
}

uint32_t MEMORY_EX::read_data8(uint32_t addr)
{

	return rbank[addr >> 13][addr & 0x1fff];
}

void MEMORY_EX::write_signal(int id, uint32_t data, uint32_t mask) {
	if (data & 16) {
		if (data & 4) {
			SET_BANK(0x8000, 0xffff, ram, ram);
		} else {
			if (data % 2 == 1) {
				SET_BANK(0x8000, 0xffff, r22, r22);
			} else {
				SET_BANK(0x8000, 0xffff, rdmy,r12);
			}
		}
	} else {
		SET_BANK(0x8000, 0xffff, r32, r32);
	}
	data &= 0x0f;
	if (data==3 || data==7) {
		SET_BANK(0x0000, 0x7fff, r31, r31);
	} else if (data==13) {
		SET_BANK(0x0000, 0x7fff, r21, r21);
	} else {
		if (data % 2 == 1) {
			SET_BANK(0x0000, 0x7fff, wdmy,bio);
		} else {
			SET_BANK(0x0000, 0x7fff, wdmy,rom);
		}
	}
	return;
}

uint32_t MEMORY_EX::fetch_op(uint32_t addr, int* wait)
{
	*wait = 1;
	return read_data8(addr);
}

#define STATE_VERSION	1

bool MEMORY_EX::process_state(FILEIO* state_fio, bool loading)
{
	if(!state_fio->StateCheckUint32(STATE_VERSION)) {
		return false;
	}
	if(!state_fio->StateCheckInt32(this_device_id)) {
		return false;
	}
	state_fio->StateValue(inserted);
	// post process
	if(loading) {
		if(inserted) {
			SET_BANK(0x0000, 0x7fff, wdmy, rom);
			SET_BANK(0x8000, 0xffff, ram, ram);
		} else {
			SET_BANK(0x0000, 0x7fff, wdmy, bio);
			SET_BANK(0x8000, 0xffff, ram, ram);
		}
	}
	return true;
}