独孤残云 发表于 2009-11-2 22:45:57

【模拟器代码问题】模拟器如何通过代码控制Rom的背景音乐

求助:模拟器源码中通过哪段代码控制Rom背景音乐的播放?
PS:看过一些模拟器的源码,大概都分为APU、PPU、NES那样几个版块。请大侠告知是哪个模块。感激不尽~~

慵懒悠悠 发表于 2009-11-2 22:57:37

看了下VirtualNes的源码,是APU那部分,全称应该是Audio Process Unit

独孤残云 发表于 2009-11-8 09:56:34

能否再详细一些呢?本人比较熟悉上层的编程思想,但对于NES的底层算法机制基本是彻头彻尾的外行。
楼上的大侠可否帮我准确定位一下,是哪个.cpp文件中的哪个函数,感激不尽~~
这里有相应的模拟器源码,就当送给大侠了~~
http://kenkao.qupan.com/5096520.html

krizal 发表于 2009-11-8 11:31:10

原帖由 独孤残云 于 2009-11-8 09:56 发表 http://bbs.emu618.com/forum/images/common/back.gif
能否再详细一些呢?本人比较熟悉上层的编程思想,但对于NES的底层算法机制基本是彻头彻尾的外行。
楼上的大侠可否帮我准确定位一下,是哪个.cpp文件中的哪个函数,感激不尽~~
这里有相应的模拟器源码,就当送给大侠 ...
聲音部分(Audoi Process Unit = APU):
.\NES\APU.cpp
.\NES\APU.h


影像處理部份(Picture Processing Unit = PPU):
.\NES\PPU.cpp
.\NES\PPU.h

如果原碼用C跟ASM混搭也不錯

湘西小人物 发表于 2009-11-8 13:03:26

楼上是高手 哈哈

独孤残云 发表于 2009-11-8 14:38:21

可以的话,希望可以得到krizal团长大人或者哪位大侠的详细赐教,就是指Apu模块下这些函数的具体含义和作用。我对这些NES机制的算法很感兴趣。
感激不尽~~

独孤残云 发表于 2009-11-8 14:47:50

这是我手中源码Apu.cpp的内容,敬请赐教:
(由于很多专用术语和算法机理都不明白,所以看不大懂……)
//////////////////////////////////////////////////////////////////////////
//                                                                      //
//      NES APU core                                                    //
//                                                         Norix      //
//                                             written   2002/06/27 //
//                                             last modify ----/--/-- //
//////////////////////////////////////////////////////////////////////////
#include "DebugOut.h"
#include "App.h"
#include "Config.h"

#include "nes.h"
#include "mmu.h"
#include "cpu.h"
#include "ppu.h"
#include "rom.h"
#include "apu.h"

// Volume adjust
// Internal sounds
#define        RECTANGLE_VOL        (0x0F0)
#define        TRIANGLE_VOL        (0x130)
#define        NOISE_VOL        (0x0C0)
#define        DPCM_VOL        (0x0F0)
// Extra sounds
#define        VRC6_VOL        (0x0F0)
#define        VRC7_VOL        (0x130)
#define        FDS_VOL                (0x0F0)
#define        MMC5_VOL        (0x0F0)
#define        N106_VOL        (0x088)
#define        FME7_VOL        (0x130)

APU::APU( NES* parent )
{
        exsound_select = 0;

        nes = parent;
        internal.SetParent( parent );

        last_data = last_diff = 0;

        ZEROMEMORY( m_SoundBuffer, sizeof(m_SoundBuffer) );

        ZEROMEMORY( lowpass_filter, sizeof(lowpass_filter) );
        ZEROMEMORY( &queue, sizeof(queue) );
        ZEROMEMORY( &exqueue, sizeof(exqueue) );

        for( INT i = 0; i < 16; i++ ) {
                m_bMute = TRUE;
        }
}

APU::~APU()
{
}

void        APU::SetQueue( INT writetime, WORD addr, BYTE data )
{
        queue.data.time = writetime;
        queue.data.addr = addr;
        queue.data.data = data;
        queue.wrptr++;
        queue.wrptr&=QUEUE_LENGTH-1;
        if( queue.wrptr == queue.rdptr ) {
                DEBUGOUT( "queue overflow.\n" );
        }
}

BOOL        APU::GetQueue( INT writetime, QUEUEDATA& ret )
{
        if( queue.wrptr == queue.rdptr ) {
                return        FALSE;
        }
        if( queue.data.time <= writetime ) {
                ret = queue.data;
                queue.rdptr++;
                queue.rdptr&=QUEUE_LENGTH-1;
                return        TRUE;
        }
        return        FALSE;
}

void        APU::SetExQueue( INT writetime, WORD addr, BYTE data )
{
        exqueue.data.time = writetime;
        exqueue.data.addr = addr;
        exqueue.data.data = data;
        exqueue.wrptr++;
        exqueue.wrptr&=QUEUE_LENGTH-1;
        if( exqueue.wrptr == exqueue.rdptr ) {
                DEBUGOUT( "exqueue overflow.\n" );
        }
}

BOOL        APU::GetExQueue( INT writetime, QUEUEDATA& ret )
{
        if( exqueue.wrptr == exqueue.rdptr ) {
                return        FALSE;
        }
        if( exqueue.data.time <= writetime ) {
                ret = exqueue.data;
                exqueue.rdptr++;
                exqueue.rdptr&=QUEUE_LENGTH-1;
                return        TRUE;
        }
        return        FALSE;
}

void        APU::QueueClear()
{
        ZEROMEMORY( &queue, sizeof(queue) );
        ZEROMEMORY( &exqueue, sizeof(exqueue) );
}

void        APU::QueueFlush()
{
        while( queue.wrptr != queue.rdptr ) {
                WriteProcess( queue.data.addr, queue.data.data );
                queue.rdptr++;
                queue.rdptr&=QUEUE_LENGTH-1;
        }

        while( exqueue.wrptr != exqueue.rdptr ) {
                WriteExProcess( exqueue.data.addr, exqueue.data.data );
                exqueue.rdptr++;
                exqueue.rdptr&=QUEUE_LENGTH-1;
        }
}

void        APU::SoundSetup()
{
        FLOAT        fClock = nes->nescfg->CpuClock;
        INT        nRate = (INT)Config.sound.nRate;
        internal.Setup( fClock, nRate );
        vrc6.Setup( fClock, nRate );
        vrc7.Setup( fClock, nRate );
        mmc5.Setup( fClock, nRate );
        fds.Setup ( fClock, nRate );
        n106.Setup( fClock, nRate );
        fme7.Setup( fClock, nRate );
}

void        APU::Reset()
{
        ZEROMEMORY( &queue, sizeof(queue) );
        ZEROMEMORY( &exqueue, sizeof(exqueue) );

        elapsed_time = 0;

        FLOAT        fClock = nes->nescfg->CpuClock;
        INT        nRate = (INT)Config.sound.nRate;
        internal.Reset( fClock, nRate );
        vrc6.Reset( fClock, nRate );
        vrc7.Reset( fClock, nRate );
        mmc5.Reset( fClock, nRate );
        fds.Reset ( fClock, nRate );
        n106.Reset( fClock, nRate );
        fme7.Reset( fClock, nRate );

        SoundSetup();
}

void        APU::SelectExSound( BYTE data )
{
        exsound_select = data;
}

BYTE        APU::Read( WORD addr )
{
        return        internal.SyncRead( addr );
}

void        APU::Write( WORD addr, BYTE data )
{
        // $4018偼VirtuaNES屌桳億乕僩
        if( addr >= 0x4000 && addr <= 0x401F ) {
                internal.SyncWrite( addr, data );
                SetQueue( nes->cpu->GetTotalCycles(), addr, data );
        }
}

BYTE        APU::ExRead( WORD addr )
{
BYTE        data = 0;

        if( exsound_select & 0x10 ) {
                if( addr == 0x4800 ) {
                        SetExQueue( nes->cpu->GetTotalCycles(), 0, 0 );
                }
        }
        if( exsound_select & 0x04 ) {
                if( addr >= 0x4040 && addr < 0x4100 ) {
                        data = fds.SyncRead( addr );
                }
        }
        if( exsound_select & 0x08 ) {
                if( addr >= 0x5000 && addr <= 0x5015 ) {
                        data = mmc5.SyncRead( addr );
                }
        }

        return        data;
}

void        APU::ExWrite( WORD addr, BYTE data )
{
        SetExQueue( nes->cpu->GetTotalCycles(), addr, data );

        if( exsound_select & 0x04 ) {
                if( addr >= 0x4040 && addr < 0x4100 ) {
                        fds.SyncWrite( addr, data );
                }
        }

        if( exsound_select & 0x08 ) {
                if( addr >= 0x5000 && addr <= 0x5015 ) {
                        mmc5.SyncWrite( addr, data );
                }
        }
}

void        APU::Sync()
{
}

void        APU::SyncDPCM( INT cycles )
{
        internal.Sync( cycles );

        if( exsound_select & 0x04 ) {
                fds.Sync( cycles );
        }
        if( exsound_select & 0x08 ) {
                mmc5.Sync( cycles );
        }
}

void        APU::WriteProcess( WORD addr, BYTE data )
{
        // $4018偼VirtuaNES屌桳億乕僩
        if( addr >= 0x4000 && addr <= 0x401F ) {
                internal.Write( addr, data );
        }
}

void        APU::WriteExProcess( WORD addr, BYTE data )
{
        if( exsound_select & 0x01 ) {
                vrc6.Write( addr, data );
        }
        if( exsound_select & 0x02 ) {
                vrc7.Write( addr, data );
        }
        if( exsound_select & 0x04 ) {
                fds.Write( addr, data );
        }
        if( exsound_select & 0x08 ) {
                mmc5.Write( addr, data );
        }
        if( exsound_select & 0x10 ) {
                if( addr == 0x0000 ) {
                        BYTE        dummy = n106.Read( addr );
                } else {
                        n106.Write( addr, data );
                }
        }
        if( exsound_select & 0x20 ) {
                fme7.Write( addr, data );
        }
}

void        APU::Process( LPBYTE lpBuffer, DWORD dwSize )
{
INT        nBits = Config.sound.nBits;
DWORD        dwLength = dwSize / (nBits/8);
INT        output;
QUEUEDATA q;
DWORD        writetime;

LPSHORT        pSoundBuf = m_SoundBuffer;
INT        nCcount = 0;

INT        nFilterType = Config.sound.nFilterType;

        if( !Config.sound.bEnable ) {
                ::FillMemory( lpBuffer, dwSize, (BYTE)(Config.sound.nRate==8?128:0) );
                return;
        }

        // Volume setup
        //0:Master
        //1:Rectangle 1
        //2:Rectangle 2
        //3:Triangle
        //4:Noise
        //5:DPCM
        //6:VRC6
        //7:VRC7
        //8:FDS
        //9:MMC5
        // 10:N106
        // 11:FME7
        INT        vol;
        BOOL*        bMute = m_bMute;
        SHORT*        nVolume = Config.sound.nVolume;

        INT        nMasterVolume = bMute?nVolume:0;

        // Internal
        vol[ 0] = bMute?(RECTANGLE_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol[ 1] = bMute?(RECTANGLE_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol[ 2] = bMute?(TRIANGLE_VOL *nVolume*nMasterVolume)/(100*100):0;
        vol[ 3] = bMute?(NOISE_VOL    *nVolume*nMasterVolume)/(100*100):0;
        vol[ 4] = bMute?(DPCM_VOL   *nVolume*nMasterVolume)/(100*100):0;

        // VRC6
        vol[ 5] = bMute?(VRC6_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol[ 6] = bMute?(VRC6_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol[ 7] = bMute?(VRC6_VOL*nVolume*nMasterVolume)/(100*100):0;

        // VRC7
        vol[ 8] = bMute?(VRC7_VOL*nVolume*nMasterVolume)/(100*100):0;

        // FDS
        vol[ 9] = bMute?(FDS_VOL*nVolume*nMasterVolume)/(100*100):0;

        // MMC5
        vol = bMute?(MMC5_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(MMC5_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(MMC5_VOL*nVolume*nMasterVolume)/(100*100):0;

        // N106
        vol = bMute[ 6]?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute[ 7]?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute[ 8]?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute[ 9]?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(N106_VOL*nVolume*nMasterVolume)/(100*100):0;

        // FME7
        vol = bMute?(FME7_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(FME7_VOL*nVolume*nMasterVolume)/(100*100):0;
        vol = bMute?(FME7_VOL*nVolume*nMasterVolume)/(100*100):0;

//        double        cycle_rate = ((double)FRAME_CYCLES*60.0/12.0)/(double)Config.sound.nRate;
        double        cycle_rate = ((double)nes->nescfg->FrameCycles*60.0/12.0)/(double)Config.sound.nRate;

        // CPU僒僀僋儖悢偑儖乕僾偟偰偟傑偭偨帪偺懳嶔張棟
        if( elapsed_time > nes->cpu->GetTotalCycles() ) {
                QueueFlush();
        }

        while( dwLength-- ) {
                writetime = (DWORD)elapsed_time;

                while( GetQueue( writetime, q ) ) {
                        WriteProcess( q.addr, q.data );
                }

                while( GetExQueue( writetime, q ) ) {
                        WriteExProcess( q.addr, q.data );
                }

                // 0-4:internal 5-7:VRC6 8:VRC7 9:FDS 10-12:MMC5 13-20:N106 21-23:FME7
                output = 0;
                output += internal.Process( 0 )*vol;
                output += internal.Process( 1 )*vol;
                output += internal.Process( 2 )*vol;
                output += internal.Process( 3 )*vol;
                output += internal.Process( 4 )*vol;

                if( exsound_select & 0x01 ) {
                        output += vrc6.Process( 0 )*vol;
                        output += vrc6.Process( 1 )*vol;
                        output += vrc6.Process( 2 )*vol;
                }
                if( exsound_select & 0x02 ) {
                        output += vrc7.Process( 0 )*vol;
                }
                if( exsound_select & 0x04 ) {
                        output += fds.Process( 0 )*vol;
                }
                if( exsound_select & 0x08 ) {
                        output += mmc5.Process( 0 )*vol;
                        output += mmc5.Process( 1 )*vol;
                        output += mmc5.Process( 2 )*vol;
                }
                if( exsound_select & 0x10 ) {
                        output += n106.Process( 0 )*vol;
                        output += n106.Process( 1 )*vol;
                        output += n106.Process( 2 )*vol;
                        output += n106.Process( 3 )*vol;
                        output += n106.Process( 4 )*vol;
                        output += n106.Process( 5 )*vol;
                        output += n106.Process( 6 )*vol;
                        output += n106.Process( 7 )*vol;
                }
                if( exsound_select & 0x20 ) {
                        fme7.Process( 3 );        // Envelope & Noise
                        output += fme7.Process( 0 )*vol;
                        output += fme7.Process( 1 )*vol;
                        output += fme7.Process( 2 )*vol;
                }

                output >>= 8;

                if( nFilterType == 1 ) {
                        //儘乕僷僗僼傿儖僞乕TYPE 1(Simple)
                        output = (lowpass_filter+output)/2;
                        lowpass_filter = output;
                } else if( nFilterType == 2 ) {
                        //儘乕僷僗僼傿儖僞乕TYPE 2(Weighted type 1)
                        output = (lowpass_filter+lowpass_filter+output)/3;
                        lowpass_filter = lowpass_filter;
                        lowpass_filter = output;
                } else if( nFilterType == 3 ) {
                        //儘乕僷僗僼傿儖僞乕TYPE 3(Weighted type 2)
                        output = (lowpass_filter+lowpass_filter+lowpass_filter+output)/4;
                        lowpass_filter = lowpass_filter;
                        lowpass_filter = lowpass_filter;
                        lowpass_filter = output;
                } else if( nFilterType == 4 ) {
                        //儘乕僷僗僼傿儖僞乕TYPE 4(Weighted type 3)
                        output = (lowpass_filter+lowpass_filter*2+output)/4;
                        lowpass_filter = lowpass_filter;
                        lowpass_filter = output;
                }

#if        0
                // DC惉暘偺僇僢僩
                {
                static double ave = 0.0, max=0.0, min=0.0;
                double delta;
                delta = (max-min)/32768.0;
                max -= delta;
                min += delta;
                if( output > max ) max = output;
                if( output < min ) min = output;
                ave -= ave/1024.0;
                ave += (max+min)/2048.0;
                output -= (INT)ave;
                }
#endif
#if        1
                // DC惉暘偺僇僢僩(HPF TEST)
                {
//                static        double        cutoff = (2.0*3.141592653579*40.0/44100.0);
                static        double        cutofftemp = (2.0*3.141592653579*40.0);
                double        cutoff = cutofftemp/(double)Config.sound.nRate;
                static        double        tmp = 0.0;
                double        in, out;

                in = (double)output;
                out = (in - tmp);
                tmp = tmp + cutoff * out;

                output = (INT)out;
                }
#endif
#if        0
                // 僗僷僀僋僲僀僘偺彍嫀(AGC TEST)
                {
                INT        diff = abs(output-last_data);
                if( diff > 0x4000 ) {
                        output /= 4;
                } else
                if( diff > 0x3000 ) {
                        output /= 3;
                } else
                if( diff > 0x2000 ) {
                        output /= 2;
                }
                last_data = output;
                }
#endif
                // Limit
                if( output > 0x7FFF ) {
                        output = 0x7FFF;
                } else if( output < -0x8000 ) {
                        output = -0x8000;
                }

                if( nBits != 8 ) {
                        *(SHORT*)lpBuffer = (SHORT)output;
                        lpBuffer += sizeof(SHORT);
                } else {
                        *lpBuffer++ = (output>>8)^0x80;
                }

                if( nCcount < 0x0100 )
                        pSoundBuf = (SHORT)output;

//                elapsedtime += cycle_rate;
                elapsed_time += cycle_rate;
        }

#if        1
        if( elapsed_time > ((nes->nescfg->FrameCycles/24)+nes->cpu->GetTotalCycles()) ) {
                elapsed_time = nes->cpu->GetTotalCycles();
        }
        if( (elapsed_time+(nes->nescfg->FrameCycles/6)) < nes->cpu->GetTotalCycles() ) {
                elapsed_time = nes->cpu->GetTotalCycles();
        }
#else
        elapsed_time = nes->cpu->GetTotalCycles();
#endif
}

// 僠儍儞僱儖偺廃攇悢庢摼僒僽儖乕僠儞(NSF梡)
INT        APU::GetChannelFrequency( INT no )
{
        if( !m_bMute )
                return        0;

        // Internal
        if( no < 5 ) {
                return        m_bMute?internal.GetFreq( no ):0;
        }
        // VRC6
        if( (exsound_select & 0x01) && no >= 0x0100 && no < 0x0103 ) {
                return        m_bMute?vrc6.GetFreq( no & 0x03 ):0;
        }
        // FDS
        if( (exsound_select & 0x04) && no == 0x300 ) {
                return        m_bMute?fds.GetFreq( 0 ):0;
        }
        // MMC5
        if( (exsound_select & 0x08) && no >= 0x0400 && no < 0x0402 ) {
                return        m_bMute?mmc5.GetFreq( no & 0x03 ):0;
        }
        // N106
        if( (exsound_select & 0x10) && no >= 0x0500 && no < 0x0508 ) {
                return        m_bMute?n106.GetFreq( no & 0x07 ):0;
        }
        // FME7
        if( (exsound_select & 0x20) && no >= 0x0600 && no < 0x0603 ) {
                return        m_bMute?fme7.GetFreq( no & 0x03 ):0;
        }
        // VRC7
        if( (exsound_select & 0x02) && no >= 0x0700 && no < 0x0709 ) {
                return        m_bMute?vrc7.GetFreq(no&0x0F):0;
        }
        return        0;
}

// State Save/Load
void        APU::SaveState( LPBYTE p )
{
#ifdef        _DEBUG
LPBYTE        pold = p;
#endif

        // 帪娫幉傪摨婜偝偣傞堊Flush偡傞
        QueueFlush();

        internal.SaveState( p );
        p += (internal.GetStateSize()+15)&(~0x0F);        // Padding

        // VRC6
        if( exsound_select & 0x01 ) {
                vrc6.SaveState( p );
                p += (vrc6.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // VRC7 (not support)
        if( exsound_select & 0x02 ) {
                vrc7.SaveState( p );
                p += (vrc7.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // FDS
        if( exsound_select & 0x04 ) {
                fds.SaveState( p );
                p += (fds.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // MMC5
        if( exsound_select & 0x08 ) {
                mmc5.SaveState( p );
                p += (mmc5.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // N106
        if( exsound_select & 0x10 ) {
                n106.SaveState( p );
                p += (n106.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // FME7
        if( exsound_select & 0x20 ) {
                fme7.SaveState( p );
                p += (fme7.GetStateSize()+15)&(~0x0F);        // Padding
        }

#ifdef        _DEBUG
DEBUGOUT( "SAVE APU SIZE:%d\n", p-pold );
#endif
}

void        APU::LoadState( LPBYTE p )
{
        // 帪娫幉傪摨婜偝偣傞堊偵徚偡
        QueueClear();

        internal.LoadState( p );
        p += (internal.GetStateSize()+15)&(~0x0F);        // Padding

        // VRC6
        if( exsound_select & 0x01 ) {
                vrc6.LoadState( p );
                p += (vrc6.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // VRC7 (not support)
        if( exsound_select & 0x02 ) {
                vrc7.LoadState( p );
                p += (vrc7.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // FDS
        if( exsound_select & 0x04 ) {
                fds.LoadState( p );
                p += (fds.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // MMC5
        if( exsound_select & 0x08 ) {
                mmc5.LoadState( p );
                p += (mmc5.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // N106
        if( exsound_select & 0x10 ) {
                n106.LoadState( p );
                p += (n106.GetStateSize()+15)&(~0x0F);        // Padding
        }
        // FME7
        if( exsound_select & 0x20 ) {
                fme7.LoadState( p );
                p += (fme7.GetStateSize()+15)&(~0x0F);        // Padding
        }
}

krizal 发表于 2009-11-8 17:25:37

原帖由 独孤残云 于 2009-11-8 14:38 发表 http://bbs.emu618.com/forum/images/common/back.gif
可以的话,希望可以得到krizal团长大人或者哪位大侠的详细赐教,就是指Apu模块下这些函数的具体含义和作用。我对这些NES机制的算法很感兴趣。
感激不尽~~
恩 我對模擬器不是很有研究,
雖然要了解源碼內容,可能不是很困難,
不過還是要花時間,個人目前蠻忙碌的。

給你一個朋友的MSN,你可以跟他討論看看,
他本身是程式設計師,也對FC模擬器很有興趣。

MSN我就PM到你的信箱了。

希望你能有所得。

独孤残云 发表于 2009-11-9 13:23:59

呵…… 谢过团长大人~~

李伟 发表于 2009-11-9 16:02:29

团长的朋友都是神,那团长就是神的boss。

krizal 发表于 2009-11-20 13:13:25

原帖由 独孤残云 于 2009-11-9 13:23 发表 http://bbs.emu618.com/forum/images/common/back.gif
呵…… 谢过团长大人~~

哈 不客氣,算是順便幫他找個伴,大家可以一起玩。

krizal 发表于 2009-11-20 13:14:53

原帖由 李伟 于 2009-11-9 16:02 发表 http://bbs.emu618.com/forum/images/common/back.gif
团长的朋友都是神,那团长就是神的boss。
哈 不敢當,我只是個平凡人,
要吃飯喝水,光吸空氣是不會飽的。。。。 :)

慵懒悠悠 发表于 2009-11-20 13:32:35

FC模拟器的部分有个人可以帮你忙
ZYH
QQ:414734306
Mail:zyh-01@126.com

他是ZYH Emulator这个模拟器的作者,只是他用的开发平台是VB,不过就6502的实现原理来说是一样的

独孤残云 发表于 2009-11-27 09:48:23

再次对团长大人和悠悠哥的无私帮助表示感谢~~

krizal 发表于 2009-11-27 19:09:06

原帖由 独孤残云 于 2009-11-27 09:48 发表 http://bbs.emu618.com/forum/images/common/back.gif
再次对团长大人和悠悠哥的无私帮助表示感谢~~
不客氣^_^
页: [1]
查看完整版本: 【模拟器代码问题】模拟器如何通过代码控制Rom的背景音乐