iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)

news2024/11/14 3:18:55

没玩过NES游戏的童年,可能不是80后的童年。我们小时候是从玩FC开始接触游戏机的,那时真的是红极一时啊,我上初中时还省吃俭用买了一台小霸王,暑假里把电视机都给打爆了!那时任天堂单是FC机的主机的发售收入就超过全美的电视台的收入的总和,在人们的心目中扎下了任天堂的这个招牌。

前言

1983年7月15日,由日本任天堂株式会社(原本是生产日式扑克即“花札”)的宫本茂先生领导开发的一种第三代家用电子游戏机:FC,全称:Family Computer,也称作:Famicom;在欧美发售时则被称为nes,全称:Nintendo Entertainment System;在中国大陆、台湾和香港等地,因其外壳为红白两色,所以人们俗称其为“红白机”,正式进入市场销售,并于后来取得了巨大成功,由此揭开了家用电子游戏机遍布世界任何角落,电子游戏全球大普及的序幕。

什么是InfNES?

一款NES游戏模拟器。InfoNES可以很容易地被移植到各个平台,作者是Martin Freij。他是一位瑞典的程序员和游戏爱好者,于2002年开发了infoNES模拟器。infoNES是一个基于NES(任天堂娱乐系统)的模拟器,旨在让人们能够在计算机上玩经典的NES游戏。

InfoNES具备良好的可移植性,它将与环境有关的内容都清出了软件内核,并且单独集合于一个InfoNES_System.h中,我们要做的就是实现这里提到的各种函数,再把InfoNES加入到我们的工程中一起编译。

最近成功实现了USB接口的FC手柄驱动,使得在imx6ull开发板玩游戏具有可玩性,这里将这个移植过程记录下来。如果对NES模拟器的源码实现感兴趣,infoNES也是个不错的研究对象,代码结构清晰,可以让你了解到如何模拟实现k6502这款经典cpu的,加深对计算机体系结构的理解。

接下来让我们重温下经典,缅怀下童年吧!

池塘外的迷路书上,知鸟在声声叫着夏天......,伴随着优美的歌声,仿佛穿越回来了,少年。

完成以下操作,让你即刻拥有款移动游戏机,实现童年时的梦想。

很早之前我在imax283平台上移植过infoNES,那时我的github仓地址是:

https://github.com/yongzhena/infoNES

这次直接拉取下来用,只是修改下joypad手柄驱动的代码就可以完美运行啦。

移植过程

整个移植过程主要涉及三部分,显示、声音输出和usb手柄支持。前两个直接拉取上面的我的仓直接就具备了,这里着重介绍下USB手柄驱动支持。

基于fb0的LCD显示

在InfoNES_System_Linux.cpp文件中修改。显示这块儿实现两个函数,一个是lcd_fb_init,一个是lcd_fb_display_px。

static int lcd_fb_init()
{
	//如果使用 mmap 打开方式 必须是 读定方式
	fb_fd = open("/dev/fb0", O_RDWR);
	if(-1 == fb_fd)
	{
		printf("cat't open /dev/fb0 \n");
		return -1;
	}
	//获取屏幕参数
	if(-1 == ioctl(fb_fd, FBIOGET_VSCREENINFO, &var))
	{
		close(fb_fd);
		printf("cat't ioctl /dev/fb0 \n");
		return -1;
	}
	
	//计算参数
	px_width     = var.bits_per_pixel /8;
	line_width   = var.xres * px_width;
	screen_width = var.yres * line_width;
	lcd_width    = var.xres;
	lcd_height   = var.yres;
	
	printf("fb width:%d height:%d pixel:%d \n", lcd_width, lcd_height,px_width*8);

	fb_mem = (unsigned char *)mmap(NULL, screen_width, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
	if(fb_mem == (void *)-1)
	{
		close(fb_fd);
		printf("cat't mmap /dev/fb0 \n");
		return -1;
	}
	//清屏
	memset(fb_mem, 0 , screen_width);
	return 0;
}
static int lcd_fb_display_px(WORD color, int x, int y)
{
	unsigned char  *pen8;
	unsigned short *pen16;
	pen8 = (unsigned char *)(fb_mem + y*line_width + x*px_width);
	pen16 = (unsigned short *)pen8;
	*pen16 = color;
	
	return 0;
}

以下的实现注意zoom_x_tab,zoom_y_tab这两项。它的作用是对像素做了全屏和放大处理。 源码里的make_zoom_tab()就是干这个用。如果觉得屏幕很大,放大后颗粒感很重,能否再优化?这里是个可能的优化方向。

/*===================================================================*/
/*                                                                   */
/*      InfoNES_LoadFrame() :                                        */
/*           Transfer the contents of work frame on the screen       */
/*                                                                   */
/*===================================================================*/
unsigned short ChColor(unsigned short color)
{
	return (color>>3)<<4|(color&0x001f);
}

void InfoNES_LoadFrame()
{
	int x,y;
	int line_width;
	WORD wColor,R,G,B,Gr;
	
	//修正 
	if(0 < fb_fd)
	{
		for (y = 0; y < lcd_height; y++ )
		{
			line_width = zoom_y_tab[y] * NES_DISP_WIDTH;
			for (x = 0; x < lcd_width; x++ )
			{
				wColor = ChColor(WorkFrame[line_width  + zoom_x_tab[x]]);
				lcd_fb_display_px(wColor, x, y);
			}
		}
	}
	
	  /*16 bit per pixel*/
	 /* Exchange 16-bit to 256 gray */
	 /*
     for (y = 0; y < NES_DISP_HEIGHT; y++ )
     {
         for (x = 0; x < NES_DISP_WIDTH; x++ )
         {
             //wColor = WorkFrame[y * lcd_width  + x ];
			  wColor = WorkFrame[ ( y << 8 ) + x ];
			  R = ( ( wColor & 0x7c00 ) >>7 );
              G = ( ( wColor & 0x03e0 ) >>2 );
              B = ( ( wColor & 0x001f ) <<3 );            
              //Gr= ( ( 9798*R + 19235*G + 3735*B)>>15);
              wColor=(WORD)((B<<16)|(G<<8)|R);
             lcd_fb_display_px(wColor, x, y);
         }
     }
	 */  
}

基于Alsa的声音支持

实现这个声音支持的前提是,板子上得有基于alsa框架的音频驱动且功能正常。否则以下这些实现里需要全部留空,不用实现。

/*===================================================================*/
/*                                                                   */
/*        InfoNES_SoundInit() : Sound Emulation Initialize           */
/*                                                                   */
/*===================================================================*/
void InfoNES_SoundInit( void )
{
	
}


/*===================================================================*/
/*                                                                   */
/*        InfoNES_SoundOpen() : Sound Open                           */
/*                                                                   */
/*===================================================================*/
int InfoNES_SoundOpen( int samples_per_sync, int sample_rate )
{
	// sample_rate 采样率 44100
	// samples_per_sync  735
	// 采样率 / 8 * 声道数 = 44100 / 8 * 1 = 5512.5
	// 8位 声音
	/*
	声道数 1
    采样率 44100
    采样位数 8
    每次播放块大小(NES  APU 每次生成一块)735
	*/
	unsigned int rate      = sample_rate;
	snd_pcm_hw_params_t *hw_params;
	
	if(0 > snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) 
	{
		printf("snd_pcm_open err\n");
		return -1;
	}
	printf("snd_pcm_open ok!\nsamples_per_sync=%d,sample_rate=%d\n",samples_per_sync,sample_rate);
	
	if(0 > snd_pcm_hw_params_malloc(&hw_params))
	{
		printf("snd_pcm_hw_params_malloc err\n");
		return -1;
	}
	
	if(0 > snd_pcm_hw_params_any(playback_handle, hw_params))
	{
		printf("snd_pcm_hw_params_any err\n");
		return -1;
	}
	if(0 > snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) 
	{
		printf("snd_pcm_hw_params_any err\n");
		return -1;
	}

	//16bit PCM 数据
	
	if(0 > snd_pcm_hw_params_set_format(playback_handle, hw_params, SND_PCM_FORMAT_U8))
	{
		printf("snd_pcm_hw_params_set_format err\n");
		return -1;
	}
	
	if(0 > snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &rate, 0)) 
	{
		printf("snd_pcm_hw_params_set_rate_near err\n");
		return -1;
	}

	//单声道 非立体声
	if(0 > snd_pcm_hw_params_set_channels(playback_handle, hw_params, 1))
	{
		printf("snd_pcm_hw_params_set_channels err\n");
		return -1;
	}

	if(0 > snd_pcm_hw_params(playback_handle, hw_params)) 
	{
		printf("snd_pcm_hw_params err\n");
		return -1;
	}
	
	snd_pcm_hw_params_free(hw_params);
	
	if(0 > snd_pcm_prepare(playback_handle)) 
	{
		printf("snd_pcm_prepare err\n");
		return -1;
	}
	
	return 1;
}


/*===================================================================*/
/*                                                                   */
/*        InfoNES_SoundClose() : Sound Close                         */
/*                                                                   */
/*===================================================================*/
void InfoNES_SoundClose( void )
{
	snd_pcm_close(playback_handle);
}


/*===================================================================*/
/*                                                                   */
/*            InfoNES_SoundOutput() : Sound Output 5 Waves           */
/*                                                                   */
/*===================================================================*/
void InfoNES_SoundOutput( int samples, BYTE *wave1, BYTE *wave2, BYTE *wave3, BYTE *wave4, BYTE *wave5 )
{
	
	int i;
	int ret;
	unsigned char wav;
	unsigned char *pcmBuf = (unsigned char *)malloc(samples);
	
	//printf("InfoNES_SoundOutput,samples=%d\n",samples);
	//printf("\n");
	for (i=0; i <samples; i++)
	{
		wav = (wave1[i] + wave2[i] + wave3[i] + wave4[i] + wave5[i]) / 5;
		//单声道 8位数据
		pcmBuf[i] = wav;
		//printf("%02x",wav);
	}
	//printf("\n");
	ret = snd_pcm_writei(playback_handle, pcmBuf, samples);
	if(-EPIPE == ret)
    {
        snd_pcm_prepare(playback_handle);
    }
	free(pcmBuf);
	return ;
}

USB手柄支持

接下来这块儿是介绍的重点,实现usb手柄驱动的支持。这样才有可玩性啊。我买的这款USB的游戏手柄很便宜,也很容易买到。如果你的USB手柄不是这款,那么实现驱动支持的原理也是类似的,万变不离宗,只是键值对应关系跟我的可能不一样,实测改下即可。

关于USB游戏手柄的驱动支持,参见我的上篇博文:iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄_特立独行的猫a的博客-CSDN博客

不想按上文总结的重新编译内核的话,可以把驱动单独编译成模块动态加载进去。

这里介绍下让infoNES支持usb手柄需要做哪些移植。

按键键值测试小程序

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h> 


#define _EV_KEY         0x01    /* button pressed/released */
#define _EV_ABS         0x03    
#define _EV_MSC         0x04   

int main() {
    printf("hello,usb hid joystick key test\n");
    int fd = open("/dev/input/event3", O_RDONLY);
    struct input_event e;
    while(1) {
        read(fd, &e, sizeof(e));
        switch(e.type) {
            case _EV_KEY:
                printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);
                break;
            case _EV_ABS:
                printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);
                break;
            case _EV_MSC:
            printf("type: %d, code: %d,value: %d, time: %d\n", e.type, e.code,e.value, e.time);
            break;
            default:
                if(e.type != 0){
                printf("type:%d, code: %d,value: %d, time: %d\n",e.type, e.code,e.value, e.time);
                }
        }
    }
    close(fd);
    return 0;
}

joypad_input.cpp文件修改

主要是USBjoypadGet()接口的实现,要跟FC手柄的键值对应上。

static int USBjoypadGet(void)
{
	/**
	 * FC手柄 bit 键位对应关系 真实手柄中有一个定时器,处理 连A  连B 
	 * 0  1   2       3       4    5      6     7
	 * A  B   Select  Start  Up   Down   Left  Right
	 */
	//因为 USB 手柄每次只能读到一位键值 所以要有静态变量保存上一次的值
	static unsigned char joypad = 0;
	struct input_event e;
	if(0 < read (USBjoypad_fd, &e, sizeof(e)))
	{
		if(0x3 == e.type)
		{
			/*
			上:
			value:0 type:0x3 code:0x1
			value:127 type:0x3 code:0x1
			*/
			if(0 == e.value && 0x1 == e.code)
			{
				joypad |= 1<<4;
                printf("Up\n");
			}
			
			/*下:
			value:255 type:0x3 code:0x1
			value:127 type:0x3 code:0x1
			*/
			if(255 == e.value && 0x1 == e.code)
			{
				joypad |= 1<<5;
                printf("Down\n");
			}
			//松开
			if(127 == e.value && 0x1 == e.code)
			{
				joypad &= ~(1<<4 | 1<<5);
			}
			
			/*左:
			value:0 type:0x3 code:0x0
			value:127 type:0x3 code:0x0
			*/
			if(0 == e.value && 0 == e.code)
			{
				joypad |= 1<<6;
                printf("Left\n");
			}
			
			/*右:
			value:255 type:0x3 code:0x0
			value:127 type:0x3 code:0x0
			*/
			if(255 == e.value && 0 == e.code)
			{
				joypad |= 1<<7;
                printf("Right\n");
			}
			//松开
			if(127 == e.value && 0 == e.code)
			{
				joypad &= ~(1<<6 | 1<<7);
			}
		}

		if(0x1 == e.type)
		{
			/*选择:
			value:0x1 type:0x1 code:296
			value:0x0 type:0x1 code:296
			*/
			if(0x1 == e.value && 296 == e.code)
			{
				joypad |= 1<<2;
                printf("Select\n");
			}
			if(0x0 == e.value && 296 == e.code)
			{
				joypad &= ~(1<<2);
			}
			
			/*开始:
			value:0x1 type:0x1 code:297
			value:0x0 type:0x1 code:297
			*/
			if(0x1 == e.value && 297 == e.code)
			{
				joypad |= 1<<3;
                printf("Start\n");
			}
			if(0x0 == e.value && 297 == e.code)
			{
				joypad &= ~(1<<3);
			}

			/*A
			value:0x1 type:0x1 code:288
			value:0x0 type:0x1 code:288
			*/
			if(0x1 == e.value && 288 == e.code)
			{
				joypad |= 1<<0;
                printf("A\n");
			}
			if(0x0 == e.value && 288 == e.code)
			{
				joypad &= ~(1<<0);
			}

			/*B
			value:0x1 type:0x1 code:289
			value:0x0 type:0x1 code:289
			*/
			if(0x1 == e.value && 289 == e.code)
			{
				joypad |= 1<<1;
                printf("B\n");
			}
			if(0x0 == e.value && 289 == e.code)
			{
				joypad &= ~(1<<1);
			}

			/*X
			value:0x1 type:0x1 code:290
			value:0x0 type:0x1 code:290
			*/
			if(0x1 == e.value && 290 == e.code)
			{
				joypad |= 1<<0;
                printf("X\n");
			}
			if(0x0 == e.value && 290 == e.code)
			{
				joypad &= ~(1<<0);
			}

			/*Y
			value:0x1 type:0x1 code:291
			value:0x0 type:0x1 code:291
		 	*/
		 	if(0x1 == e.value && 291 == e.code)
			{
				joypad |= 1<<1;
                printf("Y\n");
			}
			if(0x0 == e.value && 291 == e.code)
			{
				joypad &= ~(1<<1);
			}
		}
		return joypad;
	}
	return -1;
}

完整实现

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/input.h> 

#define JOYPAD_DEV "/dev/joypad"
#define USB_JS_DEV "/dev/input/event3"


typedef struct JoypadInput{
	int (*DevInit)(void);
	int (*DevExit)(void);
	int (*GetJoypad)(void);
	struct JoypadInput *ptNext;
	pthread_t tTreadID;     /* 子线程ID */
}T_JoypadInput, *PT_JoypadInput;

//全局变量通过互斥体访问
static unsigned char g_InputEvent;

static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;

static int joypad_fd;
static int USBjoypad_fd;
static PT_JoypadInput g_ptJoypadInputHead;


static void *InputEventTreadFunction(void *pVoid)
{
	/* 定义函数指针 */
	int (*GetJoypad)(void);
	GetJoypad = (int (*)(void))pVoid;

	while (1)
	{
		//因为有阻塞所以没有输入时是休眠
		g_InputEvent = GetJoypad();
		//有数据时唤醒
		pthread_mutex_lock(&g_tMutex);
		/*  唤醒主线程 */
		pthread_cond_signal(&g_tConVar);
		pthread_mutex_unlock(&g_tMutex);
	}
}

static int RegisterJoypadInput(PT_JoypadInput ptJoypadInput)
{
	PT_JoypadInput tmp;
	if(ptJoypadInput->DevInit())
	{
		return -1;
	}
	//初始化成功创建子线程 将子项的GetInputEvent 传进来
	pthread_create(&ptJoypadInput->tTreadID, NULL, InputEventTreadFunction, (void*)ptJoypadInput->GetJoypad);
	if(! g_ptJoypadInputHead)
	{
		g_ptJoypadInputHead = ptJoypadInput;
	}
	else
	{
		tmp = g_ptJoypadInputHead;
		while(tmp->ptNext)
		{
			tmp = tmp->ptNext;
		}
		tmp->ptNext = ptJoypadInput;
	}
	ptJoypadInput->ptNext = NULL;
	return 0;
}

static int joypadGet(void)
{
	static unsigned char joypad = 0;
	//printf("joypadGet val:\n");
	joypad = read(joypad_fd, 0, 0);
	return joypad;
}

static int joypadDevInit(void)
{
	joypad_fd = open(JOYPAD_DEV, O_RDONLY);
	if(-1 == joypad_fd)
	{
		printf("%s dev not found \r\n", JOYPAD_DEV);
		return -1;
	}
	return 0;
}

static int joypadDevExit(void)
{
	close(joypad_fd);
	return 0;
}

static T_JoypadInput joypadInput = {
	joypadDevInit,
	joypadDevExit,
	joypadGet,
};

static int USBjoypadGet(void)
{
	/**
	 * FC手柄 bit 键位对应关系 真实手柄中有一个定时器,处理 连A  连B 
	 * 0  1   2       3       4    5      6     7
	 * A  B   Select  Start  Up   Down   Left  Right
	 */
	//因为 USB 手柄每次只能读到一位键值 所以要有静态变量保存上一次的值
	static unsigned char joypad = 0;
	struct input_event e;
	if(0 < read (USBjoypad_fd, &e, sizeof(e)))
	{
		if(0x3 == e.type)
		{
			/*
			上:
			value:0 type:0x3 code:0x1
			value:127 type:0x3 code:0x1
			*/
			if(0 == e.value && 0x1 == e.code)
			{
				joypad |= 1<<4;
                printf("Up\n");
			}
			
			/*下:
			value:255 type:0x3 code:0x1
			value:127 type:0x3 code:0x1
			*/
			if(255 == e.value && 0x1 == e.code)
			{
				joypad |= 1<<5;
                printf("Down\n");
			}
			//松开
			if(127 == e.value && 0x1 == e.code)
			{
				joypad &= ~(1<<4 | 1<<5);
			}
			
			/*左:
			value:0 type:0x3 code:0x0
			value:127 type:0x3 code:0x0
			*/
			if(0 == e.value && 0 == e.code)
			{
				joypad |= 1<<6;
                printf("Left\n");
			}
			
			/*右:
			value:255 type:0x3 code:0x0
			value:127 type:0x3 code:0x0
			*/
			if(255 == e.value && 0 == e.code)
			{
				joypad |= 1<<7;
                printf("Right\n");
			}
			//松开
			if(127 == e.value && 0 == e.code)
			{
				joypad &= ~(1<<6 | 1<<7);
			}
		}

		if(0x1 == e.type)
		{
			/*选择:
			value:0x1 type:0x1 code:296
			value:0x0 type:0x1 code:296
			*/
			if(0x1 == e.value && 296 == e.code)
			{
				joypad |= 1<<2;
                printf("Select\n");
			}
			if(0x0 == e.value && 296 == e.code)
			{
				joypad &= ~(1<<2);
			}
			
			/*开始:
			value:0x1 type:0x1 code:297
			value:0x0 type:0x1 code:297
			*/
			if(0x1 == e.value && 297 == e.code)
			{
				joypad |= 1<<3;
                printf("Start\n");
			}
			if(0x0 == e.value && 297 == e.code)
			{
				joypad &= ~(1<<3);
			}

			/*A
			value:0x1 type:0x1 code:288
			value:0x0 type:0x1 code:288
			*/
			if(0x1 == e.value && 288 == e.code)
			{
				joypad |= 1<<0;
                printf("A\n");
			}
			if(0x0 == e.value && 288 == e.code)
			{
				joypad &= ~(1<<0);
			}

			/*B
			value:0x1 type:0x1 code:289
			value:0x0 type:0x1 code:289
			*/
			if(0x1 == e.value && 289 == e.code)
			{
				joypad |= 1<<1;
                printf("B\n");
			}
			if(0x0 == e.value && 289 == e.code)
			{
				joypad &= ~(1<<1);
			}

			/*X
			value:0x1 type:0x1 code:290
			value:0x0 type:0x1 code:290
			*/
			if(0x1 == e.value && 290 == e.code)
			{
				joypad |= 1<<0;
                printf("X\n");
			}
			if(0x0 == e.value && 290 == e.code)
			{
				joypad &= ~(1<<0);
			}

			/*Y
			value:0x1 type:0x1 code:291
			value:0x0 type:0x1 code:291
		 	*/
		 	if(0x1 == e.value && 291 == e.code)
			{
				joypad |= 1<<1;
                printf("Y\n");
			}
			if(0x0 == e.value && 291 == e.code)
			{
				joypad &= ~(1<<1);
			}
		}
		return joypad;
	}
	return -1;
}

static int USBjoypadDevInit(void)
{
	USBjoypad_fd = open(USB_JS_DEV, O_RDONLY);
	if(-1 == USBjoypad_fd)
	{
		printf("%s dev not found \r\n", USB_JS_DEV);
		return -1;
	}
	return 0;
}

static int USBjoypadDevExit(void)
{
	close(USBjoypad_fd);
	return 0;
}

static T_JoypadInput usbJoypadInput = {
	USBjoypadDevInit,
	USBjoypadDevExit,
	USBjoypadGet,
};

int InitJoypadInput(void)
{
	int iErr = 0;
	//iErr = RegisterJoypadInput(&joypadInput);
	iErr = RegisterJoypadInput(&usbJoypadInput);
	return iErr;
}

int GetJoypadInput(void)
{
	/* 休眠 */
	pthread_mutex_lock(&g_tMutex);
	pthread_cond_wait(&g_tConVar, &g_tMutex);	

	/* 被唤醒后,返回数据 */
	pthread_mutex_unlock(&g_tMutex);
	return g_InputEvent;
}

编译生成

最后,交叉编译生成可执行文件,放到板子上执行即可,插上USB手柄就可以玩啦,运行不错!还很流畅。需要注意的是,为了支持声音,使用了alsa的头文件并链接了libasound库。需确保你的环境里有这个库,没有的话不支持声音输出,可以去掉这个链接。文末有NES游戏的ROM资源。

makefile脚本

#根据实际路径修改工具链路径
CHAIN_ROOT=/opt/yang/imax6ul/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin
CROSS_COMPILE=$(CHAIN_ROOT)/arm-linux-gnueabihf-

#CHAIN_ROOT= /home/yang/b503/ctools/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin
#CROSS_COMPILE=$(CHAIN_ROOT)/arm-linux-gnueabihf-
#CROSS_COMPILE = 

CC     := $(CROSS_COMPILE)gcc
#CC = arm-poky-linux-gnueabi-gcc
TARBALL = InfoNES08J

# InfoNES
.CFILES =	./../K6502.cpp \
		./../InfoNES.cpp \
		./../InfoNES_Mapper.cpp \
		./../InfoNES_pAPU.cpp \
		./InfoNES_System_Linux.cpp joypad_input.cpp

.OFILES	=	$(.CFILES:.cpp=.o)

CCFLAGS =    -o2 -fsigned-char  -I../
LDFILGS = -lstdc++	-L../libs	# gcc3.x.x

all: InfoNES

InfoNES: $(.OFILES)
	$(CC) $(INCLUDES) -o $@ $(.OFILES) $(LDFILGS) -lm  -lpthread -lasound

.cpp.o:
	$(CC) $(INCLUDES) -c $(CCFLAGS) $*.cpp  -o $@

clean:
	rm -f $(.OFILES) ../*~ ../*/*~ core

cleanall:
	rm -f $(.OFILES) ../*~ ../*/*~ core InfoNES

release: clean all

tar:
	( cd ..; \
	tar cvf $(TARBALL).tar ./*; \
	gzip $(TARBALL).tar \
	)

install:
	install ./InfoNES /usr/local/bin

其他资源

NES红白机全屏显示

NES专题——NES游戏机简介_nesfc_金小庭的博客-CSDN博客

V3S移植nes游戏模拟器(附带游戏合集)_v3s编译游戏模拟器_qq_46604211的博客-CSDN博客

任天堂红白机nes游戏简介 任天堂红白机nes游戏简介

资料:内含众多NES的游戏ROM文件及运行模拟器

链接:https://pan.baidu.com/s/1uXAxLKGmKGwZFB3Yraq8gg  提取码:qxcy 

游戏合集并解压,然后改名为游戏名为英文
链接:https://pan.baidu.com/s/16hIWwYQQEX9aOBDG1dVa0A
提取码:asdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/834173.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小白解密ChatGPT大模型训练;Meta开源生成式AI工具AudioCraft

&#x1f989; AI新闻 &#x1f680; Meta开源生成式AI工具AudioCraft&#xff0c;帮助用户创作音乐和音频 摘要&#xff1a;美国公司Meta开源了一款名为AudioCraft的生成式AI工具&#xff0c;可以通过文本提示生成音乐和音频。该工具包含三个核心组件&#xff1a;MusicGen用…

Spring源码面试题

Spring源码面试题 谈谈你对Spring框架的理解? Spring 是一个开源的应用程序框架&#xff0c;它起源于 Rod Johnson 在其著名的 Spring Framework 专著中提出的一个轻量级框架的观念。下面是 Spring 的发展历史&#xff1a; 2002 年&#xff0c;Rod Johnson 发表了他的专著 …

GPT Prompt编写的艺术:如何提高AI模型的表现力

随着AI技术的迅速发展&#xff0c;人工智能模型变得越来越强大&#xff0c;能够协助我们完成各种任务。然而&#xff0c;如何更好地利用AI的能力仍然存在很大的探索空间。在与AI进行交互的过程中&#xff0c;我们主要依赖于Prompt&#xff0c;不管是直接与大模型交互&#xff0…

dlib的安装

由于需要人脸识别&#xff0c;所以需要安装opencv和dlib&#xff0c;OpenCV的安装很顺利&#xff0c;实例也跑的很正常。但dlib的安装却出现了很多坑&#xff0c;而且国内的解决方法都是复制粘贴&#xff0c;一点营养都没有&#xff0c;查了国外资料&#xff0c;终于解决&#…

让Python点亮你的世界:打造专业级编程环境的必备步骤

文章目录 初识pythonpython的安装win系统Linux系统&#xff08;centos7&#xff09; 第一个Python程序常见问题 Python解释器Python开发环境PyCharm的基础使用创建项目修改主题修改默认字体和大小汉化插件翻译软件常用快捷键 初识python Python语言的起源可以追溯到1989年&…

OFCMS代码审计

环境搭建 https://blog.csdn.net/oufua/article/details/82584637 安装后是重启容器 最后 db-config.properties 改成db.properties 修改数据库连接 搭建成功 代码审计 sql注入审计 全局搜索${ 查看没有预编译的sql语句&#xff0c;从而找到sql注入功能点 Ctrlalth 查看函…

AIGC大模型ChatGLM2-6B:国产版chatgpt本地部署及体验

1 ChatGLM2-6B介绍 ChatGLM是清华技术成果转化的公司智谱AI研发的支持中英双语的对话机器人。ChatGLM基于GLM130B千亿基础模型训练&#xff0c;它具备多领域知识、代码能力、常识推理及运用能力&#xff1b;支持与用户通过自然语言对话进行交互&#xff0c;处理多种自然语言任务…

干翻Dubbo系列第八篇:Dubbo直连开发核心三要素概述

文章目录 文章说明 一&#xff1a;Dubbo直连开发概念 1&#xff1a;直连设计中的核心组件 (一)&#xff1a;Provider服务的提供者 (二): Consumer服务的访问者 (三)&#xff1a;网络通信明白图 文章说明 本文内容整理自《孙哥说Dubbo系列视频课程》&#xff0c;孙帅老师…

❤ npm不是内部或外部命令,也不是可运行的程序 或批处理文件

❤ npm不是内部或外部命令,也不是可运行的程序 或批处理文件 cmd或者终端用nvm 安装提示&#xff1a; npm不是内部或外部命令,也不是可运行的程序或批处理文件 原因&#xff08;一&#xff09; 提示这个问题&#xff0c;有可能是Node没有安装&#xff0c;也有可能是没有配置…

【LeetCode】105. 从前序与中序遍历序列构造二叉树 106. 从中序与后序遍历序列构造二叉树

105. 从前序与中序遍历序列构造二叉树 这道题也是经典的数据结构题了&#xff0c;有时候面试题也会遇到&#xff0c;已知前序与中序的遍历序列&#xff0c;由前序遍历我们可以知道第一个元素就是根节点&#xff0c;而中序遍历的特点就是根节点的左边全部为左子树&#xff0c;右…

C高级-day2

思维导图 #!/bin/bash echo "$(head -n 5 /etc/group | tail -1)" mkdir /home/ubuntu/copy cd /home/ubuntu/copy cp /etc/shadow test chown root test chmod o-r,o-w,o-x test#include <myhead.h> //递归实现&#xff0c;输入一个数&#xff0c;输出这个数的…

OpenShift 4 - 可观测性之用 OpenTelemetry+Tempo 实现 Distributed Tracing

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在支持 OpenShift 4.13 的环境中验证 文章目录 技术架构部署 Distributed Tracing 运行环境安装 minio 环境安装 Grafana Tempo 环境 部署测试应用并进行观测跟踪测试应用1测试应用2 参考 技术架构 Tempo …

Vue3 watch监听器

概览&#xff1a;watch监听器的定义以及使用场景。在vue3中的监听器的使用方式&#xff0c;watch的三个参数&#xff0c;以及进一步了解第一个参数可以是一个属性&#xff0c;也可以是一个数组的形式包含多个属性。 watch在vue3和vue2中的使用&#xff1a; vue3中&#xff1a…

互联网+同城上门预约推拿系统-到家理疗服务平台源码

随着互联网技术的不断发展&#xff0c;越来越多的传统行业开始拥抱互联网&#xff0c;实现线上线下融合。推拿按摩作为人们日常保健、治疗疾病的一种方式&#xff0c;也在不断探索与互联网的结合。 本文将介绍基于互联网思维的家庭同城预约推拿系统到家服务平台的源码如何实现…

k8s pod数据存储Volumes

一、说在前面的话 在 Kubernetes 的 Deployment 中&#xff0c;您可以使用多种类型的 Volumes 来管理 Pod 中的数据。 作用是用来共享目录及配置&#xff0c;不用在每个pod里进行配置。 本文主要概述怎么使用HostPath、PersistentVolumeClaim、ConfigMap。 二、k8s有哪些Vol…

嵌入式入门教学——C51

一、前期准备 1、硬件设备 2、软件设备 二、预备知识 1、什么是单片机&#xff1f; 在一片集成电路芯片上集成微处理器、存储器、IO接口电路&#xff0c;从而构成了单芯片微型计算机&#xff0c;及单片机。STC89C52单片机&#xff1a; STC&#xff1a;公司89&#xff1a;所属…

基于图片、无人机、摄像头拍摄进行智能检测功能

根据要求进行无人机拍摄的视频或图片进行智能识别&#xff0c;开发过程需要事项 1、根据图片案例进行标记&#xff0c;进行模型训练 2、视频模型训练 开发语言为python 根据需求功能进行测试结果如下 根据车辆识别标记进行的测试结果截图 测经过查看视频 8月1日

深度学习论文: RepViT: Revisiting Mobile CNN From ViT Perspective及其PyTorch实现

深度学习论文: RepViT: Revisiting Mobile CNN From ViT Perspective及其PyTorch实现 RepViT: Revisiting Mobile CNN From ViT Perspective PDF: https://arxiv.org/pdf/2307.09283.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: https://gith…

DSP学习笔记

间接寻址&#xff08;通过放在辅助寄存器里面&#xff0c;可以对地址包括很多操作&#xff0c;1&#xff0c;-1&#xff0c;/-平移量&#xff0c;辅助寄存器内容的修改是在ARAU0和ARAU1中完成的。分为单操作数和双操作数&#xff0c;有很多模式在ARAU。单操作数间接寻址&#x…

[PyTorch][chapter 46][LSTM -1]

前言&#xff1a; 长短期记忆网络&#xff08;LSTM&#xff0c;Long Short-Term Memory&#xff09;是一种时间循环神经网络&#xff0c;是为了解决一般的RNN&#xff08;循环神经网络&#xff09;存在的长期依赖问题而专门设计出来的。 目录&#xff1a; 背景简介 LSTM C…