void NinSnesScanner::SearchForNinSnesFromARAM (RawFile* file)
{
	NinSnesVersion version = NINSNES_NONE;

	std::wstring basefilename = RawFile::removeExtFromPath(file->GetFileName());
	std::wstring name = file->tag.HasTitle() ? file->tag.title : basefilename;

	// get section pointer address
	uint32_t ofsIncSectionPtr;
	uint8_t addrSectionPtr;
	uint16_t konamiBaseAddress = 0xffff;
	if (file->SearchBytePattern(ptnIncSectionPtr, ofsIncSectionPtr)) {
		addrSectionPtr = file->GetByte(ofsIncSectionPtr + 3);
	}
	// DERIVED VERSIONS
	else if (file->SearchBytePattern(ptnIncSectionPtrGD3, ofsIncSectionPtr)) {
		uint8_t konamiBaseAddressPtr = file->GetByte(ofsIncSectionPtr + 16);
		addrSectionPtr = file->GetByte(ofsIncSectionPtr + 3);
		konamiBaseAddress = file->GetShort(konamiBaseAddressPtr);
	}
	else if (file->SearchBytePattern(ptnIncSectionPtrYSFR, ofsIncSectionPtr)) {
		addrSectionPtr = file->GetByte(ofsIncSectionPtr + 3);
	}
	else {
		return;
	}

	// BEGIN DYNAMIC PATTERN DEFINITIONS

	//; Kirby Super Star SPC
	//; set initial value to section pointer
	//08e4: f5 ff 38  mov   a,$38ff+x
	//08e7: fd        mov   y,a
	//08e8: f5 fe 38  mov   a,$38fe+x
	//08eb: da 30     movw  $30,ya
	char ptnInitSectionPtrBytes[] =
		"\xf5\xff\x38\xfd\xf5\xfe\x38\xda"
		"\x30";
	ptnInitSectionPtrBytes[8] = addrSectionPtr;
	BytePattern ptnInitSectionPtr(
		ptnInitSectionPtrBytes
		,
		"x??xx??x"
		"x"
		,
		9);

	//; Yoshi's Island SPC
	//; set initial value to section pointer
	//06f0: 1c        asl   a
	//06f1: 5d        mov   x,a
	//06f2: f5 8f ff  mov   a,$ff8f+x
	//06f5: fd        mov   y,a
	//06f6: d0 03     bne   $06fb
	//06f8: c4 04     mov   $04,a
	//06fa: 6f        ret
	//06fb: f5 8e ff  mov   a,$ff8e+x
	//06fe: da 40     movw  $40,ya
	char ptnInitSectionPtrBytesYI[] =
		"\x1c\x5d\xf5\x8f\xff\xfd\xd0\x03"
		"\xc4\x04\x6f\xf5\x8e\xff\xda\x40";
	ptnInitSectionPtrBytesYI[15] = addrSectionPtr;
	BytePattern ptnInitSectionPtrYI(
		ptnInitSectionPtrBytesYI
		,
		"xxx??xxx"
		"x?xx??xx"
		,
		16);

	//; Super Mario World SPC
	//; set initial value to section pointer
	//0b5f: 1c        asl   a
	//0b60: fd        mov   y,a
	//0b61: f6 5e 13  mov   a,$135e+y
	//0b64: c4 40     mov   $40,a
	//0b66: f6 5f 13  mov   a,$135f+y
	//0b69: c4 41     mov   $41,a
	char ptnInitSectionPtrBytesSMW[] =
		"\x1c\xfd\xf6\x5e\x13\xc4\x40\xf6"
		"\x5f\x13\xc4\x41";
	ptnInitSectionPtrBytesSMW[6] = addrSectionPtr;
	ptnInitSectionPtrBytesSMW[11] = addrSectionPtr + 1;
	BytePattern ptnInitSectionPtrSMW(
		ptnInitSectionPtrBytesSMW
		,
		"xxx??xxx"
		"??xx"
		,
		12);

	// DERIVED VERSIONS

	//; Gradius 3 SPC
	//08b7: 5d        mov   x,a
	//08b8: f5 fc 11  mov   a,$11fc+x
	//08bb: f0 ee     beq   $08ab
	//08bd: fd        mov   y,a
	//08be: f5 fb 11  mov   a,$11fb+x
	//08c1: da 40     movw  $40,ya            ; song metaindex ptr
	char ptnInitSectionPtrBytesGD3[] =
		"\x5d\xf5\xfc\x11\xf0\xee\xfd\xf5"
		"\xfb\x11\xda\x40";
	ptnInitSectionPtrBytesGD3[11] = addrSectionPtr;
	BytePattern ptnInitSectionPtrGD3(
		ptnInitSectionPtrBytesGD3
		,
		"xx??x?xx"
		"??xx"
		,
		12);

	//; Yoshi's Safari SPC
	//1488: fd        mov   y,a
	//1489: f7 48     mov   a,($48)+y
	//148b: c4 4c     mov   $4c,a
	//148d: fc        inc   y
	//148e: f7 48     mov   a,($48)+y
	//1490: c4 4d     mov   $4d,a
	char ptnInitSectionPtrBytesYSFR[] =
		"\xfd\xf7\x48\xc4\x4c\xfc\xf7\x48"
		"\xc4\x4d";
	ptnInitSectionPtrBytesYSFR[4] = addrSectionPtr;
	ptnInitSectionPtrBytesYSFR[9] = addrSectionPtr + 1;
	BytePattern ptnInitSectionPtrYSFR(
		ptnInitSectionPtrBytesYSFR
		,
		"xx?xxxx?"
		"xx"
		,
		10);

	// END DYNAMIC PATTERN DEFINITIONS

	// ACQUIRE SEQUENCE LIST ADDRESS:
	// find the initialization code of the section pointer,
	// and acquire the sequence list address
	uint32_t ofsInitSectionPtr;
	uint32_t addrSongList;
	// STANDARD VERSIONS
	if (file->SearchBytePattern(ptnInitSectionPtr, ofsInitSectionPtr)) {
		addrSongList = file->GetShort(ofsInitSectionPtr + 5);
	}
	else if (file->SearchBytePattern(ptnInitSectionPtrYI, ofsInitSectionPtr)) {
		addrSongList = file->GetShort(ofsInitSectionPtr + 12);
	}
	else if (file->SearchBytePattern(ptnInitSectionPtrSMW, ofsInitSectionPtr)) {
		addrSongList = file->GetShort(ofsInitSectionPtr + 3);
	}
	// DERIVED VERSIONS
	else if (file->SearchBytePattern(ptnInitSectionPtrGD3, ofsInitSectionPtr)) {
		addrSongList = file->GetShort(ofsInitSectionPtr + 8);
		if (konamiBaseAddress == 0xffff) {
			// Parodius Da! does not have base address
			konamiBaseAddress = 0;
		}
	}
	else if (file->SearchBytePattern(ptnInitSectionPtrYSFR, ofsInitSectionPtr)) {
		uint8_t addrSongListPtr = file->GetByte(ofsInitSectionPtr + 2);

		//; Yoshi's Safari SPC
		//0886: 8f 00 48  mov   $48,#$00
		//0889: 8f 1e 49  mov   $49,#$1e
		char ptnInitSongListPtrBytesYSFR[] =
			"\x8f\x00\x48\x8f\x1e\x49";
		ptnInitSongListPtrBytesYSFR[2] = addrSongListPtr;
		ptnInitSongListPtrBytesYSFR[5] = addrSongListPtr + 1;
		BytePattern ptnInitSongListPtrYSFR(
			ptnInitSongListPtrBytesYSFR
			,
			"x?xx?x"
			,
			6);

		uint32_t ofsInitSongListPtr;
		if (file->SearchBytePattern(ptnInitSongListPtrYSFR, ofsInitSongListPtr)) {
			addrSongList = file->GetByte(ofsInitSongListPtr + 1) | (file->GetByte(ofsInitSongListPtr + 4) << 8);
		}
	}
	else {
		return;
	}

	// ACQUIRE VOICE COMMAND LIST & DETECT ENGINE VERSION
	// (Minor classification for derived versions should come later as far as possible)

	// find a branch for voice command,
	// and acquire the following info for standard engines.
	// 
	// - First voice command (usually $e0)
	// - Voice command address table
	// - Voice command length table

	uint32_t ofsBranchForVcmd;
	uint8_t firstVoiceCmd = 0;
	uint16_t addrVoiceCmdAddressTable;
	uint16_t addrVoiceCmdLengthTable;
	// DERIVED VERSIONS
	if (file->SearchBytePattern(ptnJumpToVcmdYSFR, ofsBranchForVcmd)) {
		addrVoiceCmdAddressTable = file->GetShort(ofsBranchForVcmd + 9);

		uint32_t ofsReadVcmdLength;
		if (file->SearchBytePattern(ptnReadVcmdLengthYSFR, ofsReadVcmdLength)) {
			firstVoiceCmd = file->GetByte(ofsReadVcmdLength + 2);
			addrVoiceCmdLengthTable = file->GetShort(ofsReadVcmdLength + 7);

			if (firstVoiceCmd == 0xe0) {
				version = NINSNES_TOSE;
			}
		}
		else {
			return;
		}
	}
	else {
		// STANDARD VERSION
		if (file->SearchBytePattern(ptnBranchForVcmdReadahead, ofsBranchForVcmd)) {
			firstVoiceCmd = file->GetByte(ofsBranchForVcmd + 5);
		}
		else if (file->SearchBytePattern(ptnBranchForVcmd, ofsBranchForVcmd)) {
			// this search often finds a wrong code, but some games still need it (for example, Human games)
			firstVoiceCmd = file->GetByte(ofsBranchForVcmd + 1);
		}
		else {
			return;
		}

		uint32_t ofsJumpToVcmd;

		// find a jump to address_table[cmd * 2]
		if (file->SearchBytePattern(ptnJumpToVcmd, ofsJumpToVcmd)) {
			addrVoiceCmdAddressTable = file->GetShort(ofsJumpToVcmd + 7) + ((firstVoiceCmd * 2) & 0xff);
			addrVoiceCmdLengthTable = file->GetShort(ofsJumpToVcmd + 14) + (firstVoiceCmd & 0x7f);

			// false-positive needs to be fixed in later classification
			version = NINSNES_STANDARD;
		}
		else if (file->SearchBytePattern(ptnJumpToVcmdSMW, ofsJumpToVcmd)) {
			// search vcmd length table as well
			uint32_t ofsReadVcmdLength;
			if (file->SearchBytePattern(ptnReadVcmdLengthSMW, ofsReadVcmdLength)) {
				addrVoiceCmdAddressTable = file->GetShort(ofsJumpToVcmd + 5) + ((firstVoiceCmd * 2) & 0xff);
				addrVoiceCmdLengthTable = file->GetShort(ofsReadVcmdLength + 9) + firstVoiceCmd;
				version = NINSNES_EARLIER;
			}
			else {
				return;
			}
		}
		// DERIVED VERSIONS
		else if (file->SearchBytePattern(ptnJumpToVcmdCTOW, ofsJumpToVcmd)) {
			// Human Games: Clock Tower, Firemen, S.O.S. (Septentrion)
			addrVoiceCmdAddressTable = file->GetShort(ofsJumpToVcmd + 10);
			addrVoiceCmdLengthTable = file->GetShort(ofsJumpToVcmd + 17);
			version = NINSNES_HUMAN;
		}
		else {
			return;
		}
	}

	// TRY TO GRAB NOTE VOLUME/DURATION TABLE
	uint32_t ofsDispatchNote = 0;
	uint16_t addrDurRateTable = 0;
	uint16_t addrVolumeTable = 0;
	std::vector<uint8_t> durRateTable;
	std::vector<uint8_t> volumeTable;
	if (file->SearchBytePattern(ptnDispatchNoteYI, ofsDispatchNote)) {
		addrDurRateTable = file->GetShort(ofsDispatchNote + 6);
		for (uint8_t offset = 0; offset < 8; offset++) {
			durRateTable.push_back(file->GetByte(addrDurRateTable + offset));
		}

		addrVolumeTable = file->GetShort(ofsDispatchNote + 16);
		for (uint8_t offset = 0; offset < 16; offset++) {
			volumeTable.push_back(file->GetByte(addrVolumeTable + offset));
		}
	}
	else if (file->SearchBytePattern(ptnDispatchNoteGD3, ofsDispatchNote)) {
		addrDurRateTable = file->GetShort(ofsDispatchNote + 6);
		for (uint8_t offset = 0; offset < 8; offset++) {
			durRateTable.push_back(file->GetByte(addrDurRateTable + offset));
		}

		addrVolumeTable = file->GetShort(ofsDispatchNote + 16);
		for (uint8_t offset = 0; offset < 16; offset++) {
			volumeTable.push_back(file->GetByte(addrVolumeTable + offset));
		}
	}
	else if (file->SearchBytePattern(ptnDispatchNoteYSFR, ofsDispatchNote)) {
		addrDurRateTable = file->GetShort(ofsDispatchNote + 16);
		for (uint8_t offset = 0; offset < 8; offset++) {
			durRateTable.push_back(file->GetByte(addrDurRateTable + offset));
		}

		addrVolumeTable = file->GetShort(ofsDispatchNote + 4);
		for (uint8_t offset = 0; offset < 16; offset++) {
			volumeTable.push_back(file->GetByte(addrVolumeTable + offset));
		}
	}

	// CLASSIFY DERIVED VERSIONS (fix false-positive)
	if (version == NINSNES_STANDARD)
	{
		if (konamiBaseAddress != 0xffff) {
			version = NINSNES_KONAMI;
		}
		else if (file->SearchBytePattern(ptnDispatchNoteLEM, ofsDispatchNote)) {
			if (firstVoiceCmd == 0xe0) {
				version = NINSNES_LEMMINGS;
			}
			else {
				version = NINSNES_UNKNOWN;
			}
		}
		else {
			const uint8_t STD_VCMD_LEN_TABLE[27] = { 0x01, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x01, 0x02, 0x03, 0x01, 0x03, 0x03, 0x00, 0x01, 0x03, 0x00, 0x03, 0x03, 0x03, 0x01 };
			if (firstVoiceCmd == 0xe0 && file->MatchBytes(STD_VCMD_LEN_TABLE, addrVoiceCmdLengthTable, sizeof(STD_VCMD_LEN_TABLE))) {
				if (addrVoiceCmdAddressTable + sizeof(STD_VCMD_LEN_TABLE) * 2 == addrVoiceCmdLengthTable) {
					uint32_t ofsWriteVolume;
					if (file->SearchBytePattern(ptnWriteVolumeKSS, ofsWriteVolume)) {
						version = NINSNES_HAL;
					}
					else {
						version = NINSNES_STANDARD;
					}
				}
				else {
					// compatible design, but customized anyway
					version = NINSNES_STANDARD;

					uint32_t ofsRD1VCmd_FA_FE;
					uint32_t ofsRD2VCmdInstrADSR;
					if (file->SearchBytePattern(ptnRD1VCmd_FA_FE, ofsRD1VCmd_FA_FE)) {
						version = NINSNES_RD1;
					}
					else if (file->SearchBytePattern(ptnRD2VCmdInstrADSR, ofsRD2VCmdInstrADSR)) {
						// Marvelous
						version = NINSNES_RD2;
					}
				}
			}
			else {
				version = NINSNES_UNKNOWN;

				uint32_t ofsIntelliVCmdFA;
				if (file->SearchBytePattern(ptnIntelliVCmdFA, ofsIntelliVCmdFA)) {
					// Intelligent Systems
					if (file->SearchBytePattern(ptnDispatchNoteFE3, ofsDispatchNote)) {
						const uint8_t INTELLI_FE3_VCMD_LEN_TABLE[40] = { 0x01, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x01, 0x02, 0x03, 0x01, 0x03, 0x03, 0x00, 0x01, 0x03, 0x00, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x02, 0x02 };
						if (firstVoiceCmd == 0xd6 && file->MatchBytes(INTELLI_FE3_VCMD_LEN_TABLE, addrVoiceCmdLengthTable, sizeof(INTELLI_FE3_VCMD_LEN_TABLE))) {
							version = NINSNES_INTELLI_FE3;
						}
					}
					else if (file->SearchBytePattern(ptnDispatchNoteFE4, ofsDispatchNote)) {
						const uint8_t INTELLI_FE4_VCMD_LEN_TABLE[36] = { 0x01, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x01, 0x02, 0x03, 0x01, 0x03, 0x03, 0x00, 0x01, 0x03, 0x00, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01 };
						if (firstVoiceCmd == 0xda && file->MatchBytes(INTELLI_FE4_VCMD_LEN_TABLE, addrVoiceCmdLengthTable, sizeof(INTELLI_FE4_VCMD_LEN_TABLE))) {
							version = NINSNES_INTELLI_FE4;
						}
					}
					else {
						const uint8_t INTELLI_TA_VCMD_LEN_TABLE[36] = { 0x01, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x01, 0x02, 0x03, 0x01, 0x03, 0x03, 0x00, 0x01, 0x03, 0x00, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01 };
						if (firstVoiceCmd == 0xda && file->MatchBytes(INTELLI_TA_VCMD_LEN_TABLE, addrVoiceCmdLengthTable, sizeof(INTELLI_TA_VCMD_LEN_TABLE))) {
							version = NINSNES_INTELLI_TA;
						}
					}
				}
			}
		}
	}

	// GUESS SONG COUNT
	// Test Case: Star Fox, Kirby Super Star, Earthbound
	// Note that the result sometimes exceeds the real length
	uint8_t songListLength = 1;
	uint16_t addrSectionListCutoff = 0xffff;
	// skip index 0, it's not a part of table in most games
	for (uint8_t songIndex = 1; songIndex <= 0x7f; songIndex++) {
		uint32_t addrSectionListPtr = addrSongList + songIndex * 2;
		if (addrSectionListPtr >= addrSectionListCutoff) {
			break;
		}

		uint16_t firstSectionPtr = file->GetShort(addrSectionListPtr);
		if (version == NINSNES_KONAMI) {
			firstSectionPtr += konamiBaseAddress;
		}
		if (firstSectionPtr == 0) {
			continue;
		}
		if ((firstSectionPtr & 0xff00) == 0 || firstSectionPtr == 0xffff) {
			break;
		}
		if (firstSectionPtr >= addrSectionListPtr) {
			addrSectionListCutoff = min(addrSectionListCutoff, firstSectionPtr);
		}

		uint16_t addrFirstSection = file->GetShort(firstSectionPtr);
		if (addrFirstSection < 0x0100) {
			// usually it does not appear
			// probably it's broken
			continue;
		}

		if (version == NINSNES_KONAMI) {
			addrFirstSection += konamiBaseAddress;
		}
		if (addrFirstSection + 16 > 0x10000) {
			break;
		}

		bool hasIllegalTrack = false;
		for (uint8_t trackIndex = 0; trackIndex < 8; trackIndex++) {
			uint16_t addrTrackStart = file->GetShort(addrFirstSection + trackIndex * 2);
			if (addrTrackStart != 0) {
				if (addrTrackStart == 0xffff) {
					hasIllegalTrack = true;
					break;
				}

				if (version == NINSNES_KONAMI) {
					addrTrackStart += konamiBaseAddress;
				}

				if ((addrTrackStart & 0xff00) == 0 || addrTrackStart == 0xffff) {
					hasIllegalTrack = true;
					break;
				}
			}
		}
		if (hasIllegalTrack) {
			break;
		}

		songListLength = songIndex + 1;
	}

	// GUESS CURRENT SONG NUMBER
	uint8_t guessedSongIndex = 0xff;

	// scan for a song that contains the current section
	// (note that the section pointer points to the "next" section actually, in most cases)
	uint16_t addrCurrentSection = file->GetShort(addrSectionPtr);
	if (addrCurrentSection >= 0x0100 && addrCurrentSection < 0xfff0) {
		uint8_t songIndexCandidate = 0xff;

		for (uint8_t songIndex = 0; songIndex <= songListLength; songIndex++) {
			uint32_t addrSectionListPtr = addrSongList + songIndex * 2;
			if (addrSectionListPtr == 0 || addrSectionListPtr == 0xffff) {
				continue;
			}

			uint16_t firstSectionPtr = file->GetShort(addrSectionListPtr);
			if (version == NINSNES_KONAMI) {
				firstSectionPtr += konamiBaseAddress;
			}
			if (firstSectionPtr > addrCurrentSection) {
				continue;
			}

			uint16_t curAddress = firstSectionPtr;
			if ((addrCurrentSection % 2) == (curAddress % 2)) {
				uint8_t sectionCount = 0; // prevent overrun of illegal data
				while (curAddress >= 0x0100 && curAddress < 0xfff0 && sectionCount < 32) {
					uint16_t addrSection = file->GetShort(curAddress);
					if (version == NINSNES_KONAMI) {
						addrSection += konamiBaseAddress;
					}

					if (curAddress == addrCurrentSection) {
						songIndexCandidate = songIndex;
						break;
					}

					if ((addrSection & 0xff00) == 0) {
						// section list end / jump
						break;
					}

					curAddress += 2;
					sectionCount++;
				}

				if (songIndexCandidate != 0xff) {
					break;
				}
			}
		}

		if (songIndexCandidate != 0xff) {
			guessedSongIndex = songIndexCandidate;
		}
	}

	if (guessedSongIndex == 0xff) {
		return;
	}
	
	// load the song
	uint16_t addrSongStart = file->GetShort(addrSongList + guessedSongIndex * 2);
	if (version == NINSNES_KONAMI) {
		addrSongStart += konamiBaseAddress;
	}

	NinSnesSeq* newSeq = new NinSnesSeq(file, version, addrSongStart, 0, volumeTable, durRateTable, name);
	newSeq->konamiBaseAddress = konamiBaseAddress;
	if (!newSeq->LoadVGMFile()) {
		delete newSeq;
		return;
	}

	// skip unknown instruments
	if (version == NINSNES_UNKNOWN) {
		return;
	}

	// scan for instrument table
	uint32_t ofsLoadInstrTableAddressASM;
	uint32_t addrInstrTable;
	uint16_t spcDirAddr = 0;
	if (file->SearchBytePattern(ptnLoadInstrTableAddress, ofsLoadInstrTableAddressASM)) {
		addrInstrTable = file->GetByte(ofsLoadInstrTableAddressASM + 7) | (file->GetByte(ofsLoadInstrTableAddressASM + 10) << 8);
	}
	else if (file->SearchBytePattern(ptnLoadInstrTableAddressSMW, ofsLoadInstrTableAddressASM)) {
		addrInstrTable = file->GetByte(ofsLoadInstrTableAddressASM + 3) | (file->GetByte(ofsLoadInstrTableAddressASM + 6) << 8);
	}
	// DERIVED VERSIONS
	else if (version == NINSNES_HUMAN) {
		if (file->SearchBytePattern(ptnLoadInstrTableAddressCTOW, ofsLoadInstrTableAddressASM)) {
			addrInstrTable = file->GetByte(ofsLoadInstrTableAddressASM + 7) | (file->GetByte(ofsLoadInstrTableAddressASM + 10) << 8);
		}
		else if (file->SearchBytePattern(ptnLoadInstrTableAddressSOS, ofsLoadInstrTableAddressASM)) {
			addrInstrTable = file->GetByte(ofsLoadInstrTableAddressASM + 1) | (file->GetByte(ofsLoadInstrTableAddressASM + 4) << 8);
		}
		else {
			return;
		}
	}
	else if (version == NINSNES_TOSE) {
		if (file->SearchBytePattern(ptnLoadInstrTableAddressYSFR, ofsLoadInstrTableAddressASM)) {
			spcDirAddr = file->GetByte(ofsLoadInstrTableAddressASM + 3) << 8;
			addrInstrTable = file->GetByte(ofsLoadInstrTableAddressASM + 10) | (file->GetByte(ofsLoadInstrTableAddressASM + 13) << 8);
		}
		else {
			return;
		}
	}
	else {
		return;
	}

	// scan for DIR address
	if (spcDirAddr == 0) {
		uint32_t ofsSetDIR;
		if (file->SearchBytePattern(ptnSetDIR, ofsSetDIR)) {
			spcDirAddr = file->GetByte(ofsSetDIR + 4) << 8;
		}
		else if (file->SearchBytePattern(ptnSetDIRYI, ofsSetDIR)) {
			spcDirAddr = file->GetByte(ofsSetDIR + 1) << 8;
		}
		else if (file->SearchBytePattern(ptnSetDIRSMW, ofsSetDIR)) {
			spcDirAddr = file->GetByte(ofsSetDIR + 9) << 8;
		}
		// DERIVED VERSIONS
		else if (file->SearchBytePattern(ptnSetDIRCTOW, ofsSetDIR)) {
			spcDirAddr = file->GetByte(ofsSetDIR + 3) << 8;
		}
		else {
			return;
		}
	}

	uint16_t konamiTuningTableAddress = 0;
	uint8_t konamiTuningTableSize = 0;
	if (version == NINSNES_KONAMI) {
		uint32_t ofsInstrVCmd;
		if (file->SearchBytePattern(ptnInstrVCmdGD3, ofsInstrVCmd)) {
			uint16_t konamiAddrTuningTableLow = file->GetShort(ofsInstrVCmd + 10);
			uint16_t konamiAddrTuningTableHigh = file->GetShort(ofsInstrVCmd + 14);

			if (konamiAddrTuningTableHigh > konamiAddrTuningTableLow &&
				konamiAddrTuningTableHigh - konamiAddrTuningTableLow <= 0x7f)
			{
				konamiTuningTableAddress = konamiAddrTuningTableLow;
				konamiTuningTableSize = konamiAddrTuningTableHigh - konamiAddrTuningTableLow;
			}
		}
	}

	NinSnesInstrSet * newInstrSet = new NinSnesInstrSet(file, version, addrInstrTable, spcDirAddr);
	newInstrSet->konamiTuningTableAddress = konamiTuningTableAddress;
	newInstrSet->konamiTuningTableSize = konamiTuningTableSize;
	if (!newInstrSet->LoadVGMFile()) {
		delete newInstrSet;
		return;
	}
}
Example #2
0
void NinSnesScanner::SearchForNinSnesFromARAM (RawFile* file)
{
	NinSnesVersion version = NINSNES_NONE;

	UINT ofsLoadInstrTableAddressASM;
	UINT ofsSetDIRASM;
	UINT addrInstrTable;

	std::wstring basefilename = RawFile::removeExtFromPath(file->GetFileName());
	std::wstring name = file->tag.HasTitle() ? file->tag.title : basefilename;

	// get section pointer address
	uint32_t ofsIncSectionPtr = 0;
	uint8_t addrSectionPtr;
	if (file->SearchBytePattern(ptnIncSectionPtr, ofsIncSectionPtr))
	{
		addrSectionPtr = file->GetByte(ofsIncSectionPtr + 3);
	}
	else
	{
		return;
	}

	// BEGIN DYNAMIC PATTERN DEFINITIONS

	//; Kirby Super Star SPC
	//; set initial value to section pointer
	//08e4: f5 ff 38  mov   a,$38ff+x
	//08e7: fd        mov   y,a
	//08e8: f5 fe 38  mov   a,$38fe+x
	//08eb: da 30     movw  $30,ya
	char ptnInitSectionPtrBytes[] =
		"\xf5\xff\x38\xfd\xf5\xfe\x38\xda"
		"\x30";
	ptnInitSectionPtrBytes[8] = addrSectionPtr;
	BytePattern ptnInitSectionPtr(
		ptnInitSectionPtrBytes
		,
		"x??xx??x"
		"?"
		,
		9);

	//; Yoshi's Island SPC
	//; set initial value to section pointer
	//06f0: 1c        asl   a
	//06f1: 5d        mov   x,a
	//06f2: f5 8f ff  mov   a,$ff8f+x
	//06f5: fd        mov   y,a
	//06f6: d0 03     bne   $06fb
	//06f8: c4 04     mov   $04,a
	//06fa: 6f        ret
	//06fb: f5 8e ff  mov   a,$ff8e+x
	//06fe: da 40     movw  $40,ya
	char ptnInitSectionPtrBytesYI[] =
		"\x1c\x5d\xf5\x8f\xff\xfd\xd0\x03"
		"\xc4\x04\x6f\xf5\x8e\xff\xda\x40";
	ptnInitSectionPtrBytesYI[15] = addrSectionPtr;
	BytePattern ptnInitSectionPtrYI(
		ptnInitSectionPtrBytesYI
		,
		"xxx??xxx"
		"x?xx??x?"
		,
		16);

	// END DYNAMIC PATTERN DEFINITIONS

	// find the initialization code of the section pointer,
	// and acquire the sequence list address
	uint32_t ofsInitSectionPtr = 0;
	uint32_t addrSongList = 0;
	if (file->SearchBytePattern(ptnInitSectionPtr, ofsInitSectionPtr))
	{
		addrSongList = file->GetShort(ofsInitSectionPtr + 5);
	}
	else if (file->SearchBytePattern(ptnInitSectionPtrYI, ofsInitSectionPtr))
	{
		addrSongList = file->GetShort(ofsInitSectionPtr + 12);
	}
	else
	{
		return;
	}

	// TODO: version detection
	version = NINSNES_STANDARD;

	// guess current song number
	// TODO: add heuristic search
	int8_t guessedSongIndex = -1;
	switch (version)
	{
	case NINSNES_STANDARD:
		guessedSongIndex = file->GetByte(0xf4);
		break;

	default:
		guessedSongIndex = 1;
		break;
	}

	// load the song
	uint16_t addrSongStart = file->GetShort(addrSongList + guessedSongIndex * 2);
	NinSnesSeq* newSeq = new NinSnesSeq(file, version, addrSongStart);
	if (!newSeq->LoadVGMFile())
	{
		delete newSeq;
		return;
	}

	// scan for instrument table
	if (file->SearchBytePattern(ptnLoadInstrTableAddress, ofsLoadInstrTableAddressASM))
	{
		addrInstrTable = file->GetByte(ofsLoadInstrTableAddressASM + 7) | (file->GetByte(ofsLoadInstrTableAddressASM + 10) << 8);
	}
	else
	{
		return;
	}

	// scan for DIR address
	uint8_t spcDIR;
	if (file->SearchBytePattern(ptnSetDIR, ofsSetDIRASM))
	{
		spcDIR = file->GetByte(ofsSetDIRASM + 4);
	}
	else if (file->SearchBytePattern(ptnSetDIRYI, ofsSetDIRASM))
	{
		spcDIR = file->GetByte(ofsSetDIRASM + 1);
	}
	else
	{
		return;
	}

	NinSnesInstrSet * newInstrSet = new NinSnesInstrSet(file, addrInstrTable, spcDIR << 8);
	if (!newInstrSet->LoadVGMFile())
	{
		delete newInstrSet;
		return;
	}

#if 0
	// BEGIN STANDARD VERSION DETECTION

	uint8_t firstVoiceCmd = 0;
	uint16_t addrVoiceCmdAddressTable = 0;
	uint16_t addrVoiceCmdLengthTable = 0;

	// find a branch for voice command,
	// and acquire the following info for standard engines.
	// 
	// - First voice command (usually $e0)
	// - Voice command address table
	// - Voice command length table

	uint32_t ofsBranchForVcmd = 0;
	uint32_t ofsJumpToVcmd = 0;
	if (file->SearchBytePattern(ptnBranchForVcmd, ofsBranchForVcmd))
	{
		firstVoiceCmd = file->GetByte(ofsBranchForVcmd + 1);

		// find a jump to address_table[cmd * 2]
		if (file->SearchBytePattern(ptnJumpToVcmd, ofsJumpToVcmd))
		{
			addrVoiceCmdAddressTable = file->GetShort(ofsJumpToVcmd + 7) + ((firstVoiceCmd * 2) & 0xff);
			addrVoiceCmdLengthTable = file->GetShort(ofsJumpToVcmd + 14) + (firstVoiceCmd & 0x7f);
			version = NINSNES_STANDARD;
		}
	}

	// END STANDARD VERSION DETECTION
#endif
}