// Mucom88 -> Midi Converter
// -------------------------
// Written by Valley Bell, 09 March 2015
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory.h>
#ifndef __CST_STDTYPE_H__
#define __CST_STDTYPE_H__
#ifdef HAVE_STDINT
#include <stdint.h>
typedef uint8_t UINT8;
typedef int8_t INT8;
typedef uint16_t UINT16;
typedef int16_t INT16;
typedef uint32_t UINT32;
typedef int32_t INT32;
typedef uint64_t UINT64;
typedef int64_t INT64;
#else // ! HAVE_STDINT
// typedefs to use MAME's (U)INTxx types (copied from MAME\src\ods\odscomm.h)
// 8-bit values
typedef unsigned char UINT8;
typedef signed char INT8;
// 16-bit values
typedef unsigned short UINT16;
typedef signed short INT16;
// 32-bit values
#ifndef _WINDOWS_H
typedef unsigned int UINT32;
typedef signed int INT32;
// 64-bit values
#ifdef _MSC_VER
typedef unsigned __int64 UINT64;
typedef signed __int64 INT64;
#else
__extension__ typedef unsigned long long UINT64;
__extension__ typedef signed long long INT64;
#endif
#endif // _WINDOWS_H
#endif // HAVE_STDINT
#endif // __CST_STDTYPE_H__
#define INLINE static __inline
int main( int argc, char * argv[ ] ) ;
void ConvertMucom2MID( void ) ;
INLINE UINT8 MucomVol2Mid( UINT8 TrkMode, UINT8 Vol, UINT8 PanBoost) ;
INLINE double FMVol2DB( UINT8 Vol) ;
INLINE double PSGVol2DB( UINT8 Vol) ;
INLINE double DeltaTVol2DB( UINT8 Vol) ;
INLINE UINT8 DB2Mid( double DB) ;
INLINE UINT32 Tempo2Mid( UINT8 TempoVal) ;
static void WriteEvent( UINT8* Buffer, UINT32* Pos, UINT32* Delay, UINT8 Evt, UINT8 Val1, UINT8 Val2) ;
static void WriteMidiValue( UINT8* Buffer, UINT32* Pos, UINT32 Value) ;
INLINE UINT16 ReadLE16( const UINT8* Data) ;
INLINE void WriteBE16( UINT8* Buffer, UINT16 Value) ;
INLINE void WriteBE32( UINT8* Buffer, UINT32 Value) ;
#define USE_VELOCITY 1
#define NO_NOTESTOP 0
static UINT32 SeqSize;
static UINT8* SeqData;
static UINT32 MidSize;
static UINT8* MidData;
static UINT16 MIDI_RES;
static UINT16 NUM_LOOPS;
int main( int argc, char * argv[ ] )
{
FILE* hFile;
//char FileName[0x10];
if ( argc <= 2 )
{
printf ( "Usage: mucom2mid input.bin output.mid\n " ) ; #ifdef _DEBUG
_getch( ) ;
#endif
return 0 ;
}
MIDI_RES = 24 ;
//MIDI_RES = 0x20;
NUM_LOOPS = 2 ;
hFile
= fopen ( argv
[ 1 ] , "rb" ) ; if ( hFile == NULL)
return 1 ;
fseek ( hFile
, 0 , SEEK_END
) ;
SeqData
= ( UINT8
* ) malloc ( SeqSize
) ; fseek ( hFile
, 0 , SEEK_SET
) ; fread ( SeqData
, 0x01 , SeqSize
, hFile
) ;
printf ( "Converting %s ...\n " , argv
[ 1 ] ) ; MidSize = 0x20000 ;
MidData
= ( UINT8
* ) malloc ( MidSize
) ; ConvertMucom2MID( ) ;
hFile
= fopen ( argv
[ 2 ] , "wb" ) ; if ( hFile == NULL)
{
printf ( "Error saving %s!\n " , argv
[ 2 ] ) ; }
else
{
fwrite ( MidData
, 0x01 , MidSize
, hFile
) ; }
MidData = NULL;
SeqData = NULL;
#ifdef _DEBUG
#endif
return 0 ;
}
typedef struct _track_header
{
UINT16 StartOfs;
UINT16 LoopOfs;
} TRK_HDR;
#define SEQ_BASEOFS 0x0005
void ConvertMucom2MID( void )
{
static const UINT8 NOTE_ARRAY[ 0x10 ] =
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
// C C# D D# E -- F F# G G# A A# B -- -- --
{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 255 , 255 , 255 , 255 } ;
static const UINT8 RHYTHM_NOTES[ 0x06 ] =
{ 0x24 , 0x26 , 0x33 , 0x2A , 0x2D , 0x25 } ;
UINT8 Mucom88Win;
UINT16 SeqSize;
UINT16 TrkHdrPos;
UINT16 FMInsPos;
UINT16 SSGInsPos;
TRK_HDR TrkHdrs[ 11 ] ;
UINT16 SeqPos;
UINT32 MidPos;
UINT8 TrkCnt;
UINT8 CurTrk;
UINT32 MidTrkBase;
UINT32 CurDly;
UINT8 MidChn;
UINT8 TrkEnd;
UINT8 MstLoopCnt;
UINT8 TrkMode; // 00 - FM, 01 - SSG
INT8 NoteMove;
UINT8 CurCmd;
UINT8 CurNote;
UINT8 CurNoteVol;
UINT8 CurChnVol;
UINT8 ChnPanOn;
UINT8 LastNote;
UINT8 HoldNote;
UINT8 NoteStop;
UINT8 CurRhythmMask;
UINT8 CurRhythmOn;
UINT8 LoopIdx;
UINT8 LoopStkIdx;
UINT8 LoopCnt[ 0x10 ] ;
UINT8 TempByt;
INT16 TempPos;
UINT32 TempLng;
TrkHdrPos = 0x0000 ;
if ( ! memcmp ( SeqData
, "MUB8" , 4 ) ) {
printf ( "Detected Mucom88win header.\n " ) ; Mucom88Win = 1 ;
TrkHdrPos = ReadLE16( & SeqData[ 4 ] ) + 5 ;
}
else
{
Mucom88Win = 0 ;
for ( SeqPos = 0x00 ; SeqPos < 0x08 ; SeqPos ++ )
{
TempPos = ReadLE16( & SeqData[ SeqPos + 0x01 ] ) ;
if ( TempPos == 0x002F )
{
TrkHdrPos = SeqPos;
break ;
}
}
}
if ( ! TrkHdrPos)
{
printf ( "Unable to find sequence header!\n " ) ; return ;
}
printf ( "Sequence header found at %04X.\n " , TrkHdrPos
) ;
TrkCnt = 11 ;
SeqPos = 0x01 ;
FMInsPos = ReadLE16( & SeqData[ TrkHdrPos] ) ;
SSGInsPos = ReadLE16( & SeqData[ TrkHdrPos+ 2 ] ) ;
SeqPos = TrkHdrPos;
MidPos = 0x00 ;
WriteBE32( & MidData[ MidPos] , 0x4D546864 ) ;
MidPos += 0x04 ; // 'MThd' Signature
WriteBE32( & MidData[ MidPos] , 0x00000006 ) ;
MidPos += 0x04 ; // Header Size
WriteBE16( & MidData[ MidPos] , 0x0001 ) ;
MidPos += 0x02 ; // Format: 1
WriteBE16( & MidData[ MidPos] , 1 + TrkCnt) ;
MidPos += 0x02 ; // Tracks (+1 for tempo)
WriteBE16( & MidData[ MidPos] , MIDI_RES) ;
MidPos += 0x02 ; // Ticks per Quarter
#if 1
WriteBE32( & MidData[ MidPos] , 0x4D54726B ) ;
MidPos += 0x04 ; // 'MTrk' Signature
WriteBE32( & MidData[ MidPos] , 0x00000000 ) ;
MidPos += 0x04 ; // Track Size
MidTrkBase = MidPos;
CurDly = 0 ;
TempLng = Tempo2Mid( SeqData[ SeqPos] ) ; // default tempo value
WriteEvent( MidData, & MidPos, & CurDly, 0xFF , 0x51 , 0x03 ) ;
WriteBE32( & MidData[ MidPos - 0x01 ] , TempLng) ;
MidData[ MidPos - 0x01 ] = 0x03 ; // write again, because the above instruction overwrote it
MidPos += 0x03 ;
SeqPos ++;
WriteEvent( MidData, & MidPos, & CurDly, 0xFF , 0x2F , 0x00 ) ;
WriteBE32( & MidData[ MidTrkBase - 0x04 ] , MidPos - MidTrkBase) ; // write Track Length
#endif
for ( CurTrk = 0 ; CurTrk < TrkCnt; CurTrk ++, SeqPos += 0x04 )
{
TrkHdrs[ CurTrk] .StartOfs = ReadLE16( & SeqData[ SeqPos + 0x00 ] ) ;
if ( TrkHdrs[ CurTrk] .StartOfs )
TrkHdrs[ CurTrk] .StartOfs += TrkHdrPos;
TrkHdrs[ CurTrk] .LoopOfs = ReadLE16( & SeqData[ SeqPos + 0x02 ] ) ;
if ( TrkHdrs[ CurTrk] .LoopOfs )
TrkHdrs[ CurTrk] .LoopOfs += TrkHdrPos;
}
SeqSize = ReadLE16( & SeqData[ SeqPos] ) + TrkHdrPos;
SeqPos += 0x02 ;
for ( CurTrk = 0 ; CurTrk < TrkCnt; CurTrk ++ )
{
WriteBE32( & MidData[ MidPos] , 0x4D54726B ) ; // write 'MTrk'
MidPos += 0x08 ;
MidTrkBase = MidPos;
SeqPos = TrkHdrs[ CurTrk] .StartOfs ;
if ( CurTrk < 3 ) // FM 1..3
{
TrkMode = 0 ;
MidChn = CurTrk;
}
else if ( CurTrk < 6 ) // SSG 1..3
{
TrkMode = 1 ;
MidChn = 10 + CurTrk - 3 ;
}
else if ( CurTrk == 6 ) // ADPCMA Rhythm
{
TrkMode = 3 ;
MidChn = 9 ;
}
else if ( CurTrk == 10 ) // ADPCMB/DeltaT
{
TrkMode = 2 ;
MidChn = 9 ;
}
else
{
TrkMode = 0 ;
MidChn = 3 + CurTrk - 7 ;
}
printf ( "Track %u ...\n " , CurTrk
) ;
CurDly = 0 ;
TrkEnd = ( SeqPos == 0x0000 ) ;
MstLoopCnt = 0x00 ;
NoteMove = TrkMode ? + 12 : 0 ;
//WriteEvent(MidData, &MidPos, &CurDly, 0xB0 | MidChn, 0x65, 0x00); // RPN MSB: 0
//WriteEvent(MidData, &MidPos, &CurDly, 0xB0 | MidChn, 0x64, 0x00); // RPN LSB: 0
//WriteEvent(MidData, &MidPos, &CurDly, 0xB0 | MidChn, 0x06, 12); // Data MSB - set Pitch Bend Range
CurNote = 0xFF ;
LastNote = 0xFF ;
CurNoteVol = 0x7F ;
CurChnVol = 0x00 ;
ChnPanOn = 0x00 ;
HoldNote = 0x00 ;
NoteStop = 0 ;
CurRhythmMask = 0x00 ;
CurRhythmOn = 0x00 ;
LoopStkIdx = 0x00 ;
LoopIdx = 0x00 ;
while ( ! TrkEnd)
{
if ( ! MstLoopCnt && SeqPos == TrkHdrs[ CurTrk] .LoopOfs )
{
MstLoopCnt ++;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x6F , 0x00 ) ;
}
CurCmd = SeqData[ SeqPos] ;
SeqPos ++;
if ( CurCmd == 0x00 )
{
if ( TrkHdrs[ CurTrk] .LoopOfs )
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x6F , MstLoopCnt) ;
if ( TrkHdrs[ CurTrk] .LoopOfs && MstLoopCnt < NUM_LOOPS)
{
SeqPos = TrkHdrs[ CurTrk] .LoopOfs ;
MstLoopCnt ++;
}
else
{
TrkEnd = 0x01 ;
}
}
else if ( CurCmd < 0x80 )
{
TempByt = SeqData[ SeqPos] ;
SeqPos ++;
CurNote = NOTE_ARRAY[ TempByt & 0x0F ] ;
if ( CurNote == 0xFF )
printf ( "Warning: Invalid Note %02X!\n " , TempByt
) ; CurNote += ( TempByt >> 4 ) * 12 ;
CurNote += NoteMove + 12 ;
if ( TrkMode == 3 )
{
if ( ! HoldNote)
{
for ( TempByt = 0x00 ; TempByt < 0x06 ; TempByt ++ )
{
if ( CurRhythmOn & ( 1 << TempByt) )
{
CurRhythmOn &= ~( 1 << TempByt) ;
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, RHYTHM_NOTES[ TempByt] , 0x00 ) ;
}
}
for ( TempByt = 0x00 ; TempByt < 0x06 ; TempByt ++ )
{
if ( CurRhythmMask & ( 1 << TempByt) )
{
CurRhythmOn |= ( 1 << TempByt) ;
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, RHYTHM_NOTES[ TempByt] , CurNoteVol) ;
}
}
}
}
else if ( LastNote != CurNote || ! HoldNote)
{
if ( HoldNote)
{
if ( CurDly >= 1 )
{
CurDly --;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x41 , 0x7F ) ;
CurDly ++;
}
else
{
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x41 , 0x7F ) ;
}
}
if ( LastNote != 0xFF )
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, LastNote, 0x00 ) ;
if ( CurNote != 0xFF )
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, CurNote, CurNoteVol) ;
LastNote = CurNote;
if ( HoldNote)
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x41 , 0x00 ) ;
}
HoldNote = 0x00 ;
CurDly += CurCmd;
if ( NoteStop && CurDly > NoteStop && SeqData[ SeqPos] != 0xFD )
{
CurDly -= NoteStop;
if ( TrkMode == 3 )
{
for ( TempByt = 0x00 ; TempByt < 0x06 ; TempByt ++ )
{
if ( CurRhythmOn & ( 1 << TempByt) )
{
CurRhythmOn &= ~( 1 << TempByt) ;
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, RHYTHM_NOTES[ TempByt] , 0x00 ) ;
}
}
}
else if ( LastNote != 0xFF )
{
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, LastNote, 0x00 ) ;
LastNote = 0xFF ;
}
CurDly += NoteStop;
}
}
else if ( CurCmd < 0xF0 )
{
if ( ! HoldNote && LastNote != 0xFF )
{
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, LastNote, 0x00 ) ;
LastNote = 0xFF ;
}
HoldNote = 0x00 ;
CurDly += CurCmd & 0x7F ;
}
else
{
switch ( CurCmd)
{
case 0xF0 : // Set Instrument
if ( TrkMode == 3 && Mucom88Win)
{
// set Rhythm Mask
CurRhythmMask = SeqData[ SeqPos] ;
}
else
{
TempByt = SeqData[ SeqPos] & 0x7F ;
WriteEvent( MidData, & MidPos, & CurDly, 0xC0 | MidChn, TempByt, 0x00 ) ;
}
SeqPos ++;
break ;
case 0xF1 : // Set Volume
CurChnVol = SeqData[ SeqPos] ;
TempByt = MucomVol2Mid( TrkMode, CurChnVol, ChnPanOn) ;
if ( ! USE_VELOCITY)
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x07 , TempByt) ;
else
CurNoteVol = TempByt;
SeqPos ++;
if ( TrkMode == 3 )
SeqPos += 0x06 ;
break ;
case 0xF2 : // Detune
//WriteEvent(MidData, &MidPos, &CurDly, 0xB0 | MidChn, 0x6E, CurCmd & 0x0F);
//WriteEvent(MidData, &MidPos, &CurDly, 0xB0 | MidChn, 0x26, SeqData[SeqPos]);
TempPos = ReadLE16( & SeqData[ SeqPos] ) ;
TempPos = 0x2000 - ( TempPos << 5 ) ;
WriteEvent( MidData, & MidPos, & CurDly, 0xE0 | MidChn, TempPos & 0x7F , ( TempPos >> 7 ) & 0x7F ) ;
SeqPos += 0x03 ;
break ;
case 0xF3 :
if ( TrkMode == 3 )
{
// set Rhythm Mask
CurRhythmMask = SeqData[ SeqPos] ;
}
else
{
// Note Stop/Echo Volume?
NoteStop = SeqData[ SeqPos] ;
//if (TrkMode == 0 && NoteStop >= 4)
// NoteStop = 0;
if ( NO_NOTESTOP)
NoteStop = 0 ;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x29 , SeqData[ SeqPos] ) ;
}
SeqPos ++;
break ;
case 0xF4 : // Modulation
if ( ! SeqData[ SeqPos + 0x00 ] )
{
// Set Modulation
TempPos = ReadLE16( & SeqData[ SeqPos + 0x03 ] ) ;
TempByt = SeqData[ SeqPos + 0x05 ] ;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x10 , SeqData[ SeqPos + 0x01 ] ) ;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x11 , SeqData[ SeqPos + 0x02 ] ) ;
if ( ! TrkMode)
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x12 , TempPos & 0x7F ) ;
else
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x12 , ( TempPos/ 8 ) & 0x7F ) ;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x13 , TempByt & 0x7F ) ;
if ( TempPos < 0 )
TempPos = - TempPos;
TempLng = ( TempPos * TempPos) / 8 ;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x01 , TempLng) ;
SeqPos += 0x06 ;
}
else
{
// Disable Modulation
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x01 , 0x00 ) ;
SeqPos ++;
}
break ;
case 0xF5 : // Loop Start
TempPos = ReadLE16( & SeqData[ SeqPos] ) ; // get offset of Loop Count
LoopStkIdx ++;
if ( SeqPos + TempPos < SeqSize)
LoopCnt[ LoopStkIdx] = SeqData[ SeqPos + TempPos] ;
else
LoopCnt[ LoopStkIdx] = 0x00 ;
SeqPos += 0x02 ;
break ;
case 0xF6 : // Loop End
TempByt = SeqData[ SeqPos + 0x01 ] ;
if ( ! LoopCnt[ LoopStkIdx] )
LoopCnt[ LoopStkIdx] = TempByt;
SeqPos += 0x02 ;
TempPos = ReadLE16( & SeqData[ SeqPos] ) ;
LoopCnt[ LoopStkIdx] --;
if ( LoopCnt[ LoopStkIdx] )
{
SeqPos -= TempPos;
}
else
{
LoopStkIdx --;
SeqPos += 0x02 ;
}
break ;
case 0xF7 : // FM3 special mode
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x6E , CurCmd & 0x0F ) ;
SeqPos ++;
break ;
case 0xF8 :
case 0xF9 : // Pan
if ( ( Mucom88Win && CurCmd == 0xf8 ) || ( ! Mucom88Win && CurCmd == 0xf9 ) )
{
TempByt = SeqData[ SeqPos] ;
if ( TrkMode == 3 ) // rhythm mode works differently
{
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x26 , TempByt) ;
TempByt = 0x00 ;
}
TempByt &= 0x03 ;
if ( TempByt == 0x01 ) // right speaker
TempByt = 0x7F ;
else if ( TempByt == 0x02 ) // left speaker
TempByt = 0x00 ;
else // both speakers
TempByt = 0x40 ;
ChnPanOn = ( TempByt == 0x40 ) ? 0x00 : 0x01 ;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x0A , TempByt) ;
TempByt = MucomVol2Mid( TrkMode, CurChnVol, ChnPanOn) ;
if ( ! USE_VELOCITY)
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x07 , TempByt) ;
else
CurNoteVol = TempByt;
SeqPos ++;
break ;
}
else
{
// seems to just write a communication byte
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x6E , CurCmd & 0x0F ) ;
SeqPos ++;
break ;
}
case 0xFA : // Register Write
if ( TrkMode == 1 )
{
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x6E , CurCmd & 0x0F ) ;
SeqPos += 0x06 ;
}
else // YM2203/2608 Register Write
{
if ( SeqData[ SeqPos + 0x00 ] == 0x26 ) // Timer B - change Tempo
{
TempLng = Tempo2Mid( SeqData[ SeqPos + 0x01 ] ) ;
WriteEvent( MidData, & MidPos, & CurDly, 0xFF , 0x51 , 0x03 ) ;
WriteBE32( & MidData[ MidPos - 0x01 ] , TempLng) ;
MidData[ MidPos - 0x01 ] = 0x03 ; // write again, because the above instruction overwrote it
MidPos += 0x03 ;
}
else
{
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 50 , SeqData[ SeqPos + 0x00 ] ) ;
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 51 , SeqData[ SeqPos + 0x01 ] ) ;
}
SeqPos += 0x02 ;
}
break ;
case 0xFB : // Change Volume
CurChnVol += SeqData[ SeqPos] ;
TempByt = MucomVol2Mid( TrkMode, CurChnVol, ChnPanOn) ;
if ( ! USE_VELOCITY)
{
if ( SeqData[ SeqPos] != 0xFB )
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x07 , TempByt) ;
}
else
CurNoteVol = TempByt;
SeqPos ++;
break ;
case 0xFC :
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x6E , CurCmd & 0x0F ) ;
//printf("Unknown command %02X at %04X!\n", CurCmd, SeqPos - 0x01);
//getchar();
SeqPos += 0x03 ;
break ;
case 0xFD : // Hold
HoldNote = 0x01 ;
break ;
case 0xFE : // Loop Exit
TempPos = ReadLE16( & SeqData[ SeqPos] ) ; // get offset of Loop Exit Counter
TempByt = SeqData[ SeqPos + TempPos] ;
if ( LoopCnt[ LoopStkIdx] == 1 )
{
LoopCnt[ LoopStkIdx] = 0 ;
LoopStkIdx --;
SeqPos += TempPos + 0x04 ;
}
else
{
SeqPos += 0x02 ;
}
break ;
case 0xFF : // Master Loop Start??
if ( Mucom88Win)
{
TempByt = SeqData[ SeqPos] ;
SeqPos ++;
switch ( TempByt)
{
case 0xf0 : // PCM volume mode ('vm')
SeqPos++;
break ;
case 0xf1 : // PSG hardware support (mucom88 v1.5 / music lalf 1.0 only)
case 0xf2 :
SeqPos++;
break ;
case 0xf3 : // Reverb enable
case 0xf4 : // Reverb mode
case 0xf5 : // Reverb switch
SeqPos++;
break ;
default :
printf ( "unknown extra command %02x at %04x\n " , TempByt
, SeqPos
) ; break ;
}
WriteEvent( MidData, & MidPos, & CurDly, 0xB0 | MidChn, 0x71 , TempByt & 0x0F ) ;
}
//WriteEvent(MidData, &MidPos, &CurDly, 0xB0 | MidChn, 0x6F, 0x00);
//SeqPos += 0x02;
break ;
}
}
}
if ( LastNote != 0xFF )
WriteEvent( MidData, & MidPos, & CurDly, 0x90 | MidChn, LastNote, 0x00 ) ;
WriteEvent( MidData, & MidPos, & CurDly, 0xFF , 0x2F , 0x00 ) ;
WriteBE32( & MidData[ MidTrkBase - 0x04 ] , MidPos - MidTrkBase) ; // write Track Length
}
MidSize = MidPos;
return ;
}
INLINE UINT8 MucomVol2Mid( UINT8 TrkMode, UINT8 Vol, UINT8 PanBoost)
{
double DBVol;
if ( TrkMode == 0 )
DBVol = FMVol2DB( Vol) ;
else if ( TrkMode == 1 )
DBVol = PSGVol2DB( Vol) ;
else if ( TrkMode == 2 )
DBVol = DeltaTVol2DB( Vol) ;
else if ( TrkMode == 3 )
DBVol = DeltaTVol2DB( Vol * 4 ) ;
else
return Vol;
if ( PanBoost)
DBVol -= 3.0 ;
return DB2Mid( DBVol) ;
}
INLINE double FMVol2DB( UINT8 Vol)
{
// Mucom uses a FM volume lookup table to map its volume value to 8/3 FM steps. (2 db steps)
// The table contains 20 values and looks like this:
// 36 33 30 2D 2A 28 25 22 20 1D 1A 18 15 12 10 0D 0A 08 05 02
#if 0
UINT8 FmVol;
if ( Vol < 20 )
FmVol = ( 20 - Vol) * 8 / 3 ;
else
FmVol = 0 ;
return FmVol * - 0.75 ;
#else
if ( Vol < 20 )
return ( 20 - Vol) * - 2.0 ;
else
return 0 ;
#endif
}
INLINE double PSGVol2DB( UINT8 Vol)
{
if ( Vol > 0x0F )
return 0.0 ;
else if ( Vol > 0x00 )
return ( 0x0F - Vol) * - 3.0 ; // AY8910 volume is 3 db per step
else
return - 999 ;
}
INLINE double DeltaTVol2DB( UINT8 Vol)
{
//return log(Vol / 255.0) / log(2.0) * 6.0;
return log ( Vol
/ 255.0 ) * 8.65617024533378 + 6.0 ; // boost its volume }
INLINE UINT8 DB2Mid( double DB)
{
DB += 6.0 ;
if ( DB > 0.0 )
DB = 0.0 ;
return ( UINT8
) ( pow ( 10.0 , DB
/ 40.0 ) * 0x7F + 0.5 ) ; }
INLINE UINT32 Tempo2Mid( UINT8 TempoVal)
{
// Note: The tempo value is the value of YM Timer B.
// higher value = higher tick frequency = higher tempo
// Base Clock = 2 MHz
// Prescaler: 6 * 12
// internal Timer Countdown: (100h - value) * 10h
// Timer Frequency: Clock / (Countdown * Prescaler)
double TicksPerSec;
UINT16 TmrVal;
TmrVal = ( 0x100 - TempoVal) << 4 ;
TicksPerSec = 2000000.0 / ( 6 * 12 * TmrVal) ;
return ( UINT32) ( 500000 * MIDI_RES / TicksPerSec + 0.5 ) ;
}
static void WriteEvent( UINT8* Buffer, UINT32* Pos, UINT32* Delay, UINT8 Evt, UINT8 Val1, UINT8 Val2)
{
if ( ! ( Evt & 0x80 ) )
return ;
WriteMidiValue( Buffer, Pos, * Delay) ;
* Delay = 0x00 ;
switch ( Evt & 0xF0 )
{
case 0x80 :
case 0x90 :
case 0xA0 :
case 0xB0 :
case 0xE0 :
MidData[ * Pos + 0x00 ] = Evt;
MidData[ * Pos + 0x01 ] = Val1;
MidData[ * Pos + 0x02 ] = Val2;
* Pos += 0x03 ;
break ;
case 0xC0 :
case 0xD0 :
MidData[ * Pos + 0x00 ] = Evt;
MidData[ * Pos + 0x01 ] = Val1;
* Pos += 0x02 ;
break ;
case 0xF0 : // for Meta Event: Track End
MidData[ * Pos + 0x00 ] = Evt;
MidData[ * Pos + 0x01 ] = Val1;
MidData[ * Pos + 0x02 ] = Val2;
* Pos += 0x03 ;
break ;
default :
break ;
}
return ;
}
static void WriteMidiValue( UINT8* Buffer, UINT32* Pos, UINT32 Value)
{
UINT8 ValSize;
UINT8* ValData;
UINT32 TempLng;
UINT32 CurPos;
ValSize = 0x00 ;
TempLng = Value;
do
{
TempLng >>= 7 ;
ValSize ++;
} while ( TempLng) ;
ValData = & Buffer[ * Pos] ;
CurPos = ValSize;
TempLng = Value;
do
{
CurPos --;
ValData[ CurPos] = 0x80 | ( TempLng & 0x7F ) ;
TempLng >>= 7 ;
} while ( TempLng) ;
ValData[ ValSize - 1 ] &= 0x7F ;
* Pos += ValSize;
return ;
}
INLINE UINT16 ReadLE16( const UINT8* Data)
{
return ( Data[ 0x00 ] << 0 ) | ( Data[ 0x01 ] << 8 ) ;
}
INLINE void WriteBE16( UINT8* Buffer, UINT16 Value)
{
Buffer[ 0x00 ] = ( Value & 0xFF00 ) >> 8 ;
Buffer[ 0x01 ] = ( Value & 0x00FF ) >> 0 ;
return ;
}
INLINE void WriteBE32( UINT8* Buffer, UINT32 Value)
{
Buffer[ 0x00 ] = ( Value & 0xFF000000 ) >> 24 ;
Buffer[ 0x01 ] = ( Value & 0x00FF0000 ) >> 16 ;
Buffer[ 0x02 ] = ( Value & 0x0000FF00 ) >> 8 ;
Buffer[ 0x03 ] = ( Value & 0x000000FF ) >> 0 ;
return ;
}
Ly8gTXVjb204OCAtPiBNaWRpIENvbnZlcnRlcgovLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCi8vIFdyaXR0ZW4gYnkgVmFsbGV5IEJlbGwsIDA5IE1hcmNoIDIwMTUKCiNpbmNsdWRlIDxzdGRpby5oPgojaW5jbHVkZSA8c3RkbGliLmg+CiNpbmNsdWRlIDxtYXRoLmg+CiNpbmNsdWRlIDxtZW1vcnkuaD4KI2lmbmRlZiBfX0NTVF9TVERUWVBFX0hfXwojZGVmaW5lIF9fQ1NUX1NURFRZUEVfSF9fCgojaWZkZWYgSEFWRV9TVERJTlQKCiNpbmNsdWRlIDxzdGRpbnQuaD4KCnR5cGVkZWYgdWludDhfdAlVSU5UODsKdHlwZWRlZiAgaW50OF90CSBJTlQ4Owp0eXBlZGVmIHVpbnQxNl90CVVJTlQxNjsKdHlwZWRlZiAgaW50MTZfdAkgSU5UMTY7CnR5cGVkZWYgdWludDMyX3QJVUlOVDMyOwp0eXBlZGVmICBpbnQzMl90CSBJTlQzMjsKdHlwZWRlZiB1aW50NjRfdAlVSU5UNjQ7CnR5cGVkZWYgIGludDY0X3QJIElOVDY0OwoKI2Vsc2UJLy8gISBIQVZFX1NURElOVAoKLy8gdHlwZWRlZnMgdG8gdXNlIE1BTUUncyAoVSlJTlR4eCB0eXBlcyAoY29waWVkIGZyb20gTUFNRVxzcmNcb2RzXG9kc2NvbW0uaCkKLy8gOC1iaXQgdmFsdWVzCnR5cGVkZWYgdW5zaWduZWQgY2hhcgkJVUlOVDg7CnR5cGVkZWYgICBzaWduZWQgY2hhciAJCSBJTlQ4OwoKLy8gMTYtYml0IHZhbHVlcwp0eXBlZGVmIHVuc2lnbmVkIHNob3J0CQlVSU5UMTY7CnR5cGVkZWYgICBzaWduZWQgc2hvcnQJCSBJTlQxNjsKCi8vIDMyLWJpdCB2YWx1ZXMKI2lmbmRlZiBfV0lORE9XU19ICnR5cGVkZWYgdW5zaWduZWQgaW50CQlVSU5UMzI7CnR5cGVkZWYgICBzaWduZWQgaW50CQkgSU5UMzI7CgovLyA2NC1iaXQgdmFsdWVzCiNpZmRlZiBfTVNDX1ZFUgp0eXBlZGVmIHVuc2lnbmVkIF9faW50NjQJVUlOVDY0Owp0eXBlZGVmICAgc2lnbmVkIF9faW50NjQJIElOVDY0OwojZWxzZQpfX2V4dGVuc2lvbl9fIHR5cGVkZWYgdW5zaWduZWQgbG9uZyBsb25nCVVJTlQ2NDsKX19leHRlbnNpb25fXyB0eXBlZGVmICAgc2lnbmVkIGxvbmcgbG9uZwkgSU5UNjQ7CiNlbmRpZgojZW5kaWYJLy8gX1dJTkRPV1NfSAoKI2VuZGlmCS8vIEhBVkVfU1RESU5UCgojZW5kaWYJLy8gX19DU1RfU1REVFlQRV9IX18KCiNkZWZpbmUgSU5MSU5FCXN0YXRpYyBfX2lubGluZQoKaW50IG1haW4oaW50IGFyZ2MsIGNoYXIqIGFyZ3ZbXSk7CnZvaWQgQ29udmVydE11Y29tMk1JRCh2b2lkKTsKSU5MSU5FIFVJTlQ4IE11Y29tVm9sMk1pZChVSU5UOCBUcmtNb2RlLCBVSU5UOCBWb2wsIFVJTlQ4IFBhbkJvb3N0KTsKSU5MSU5FIGRvdWJsZSBGTVZvbDJEQihVSU5UOCBWb2wpOwpJTkxJTkUgZG91YmxlIFBTR1ZvbDJEQihVSU5UOCBWb2wpOwpJTkxJTkUgZG91YmxlIERlbHRhVFZvbDJEQihVSU5UOCBWb2wpOwpJTkxJTkUgVUlOVDggREIyTWlkKGRvdWJsZSBEQik7CklOTElORSBVSU5UMzIgVGVtcG8yTWlkKFVJTlQ4IFRlbXBvVmFsKTsKCnN0YXRpYyB2b2lkIFdyaXRlRXZlbnQoVUlOVDgqIEJ1ZmZlciwgVUlOVDMyKiBQb3MsIFVJTlQzMiogRGVsYXksIFVJTlQ4IEV2dCwgVUlOVDggVmFsMSwgVUlOVDggVmFsMik7CnN0YXRpYyB2b2lkIFdyaXRlTWlkaVZhbHVlKFVJTlQ4KiBCdWZmZXIsIFVJTlQzMiogUG9zLCBVSU5UMzIgVmFsdWUpOwpJTkxJTkUgVUlOVDE2IFJlYWRMRTE2KGNvbnN0IFVJTlQ4KiBEYXRhKTsKSU5MSU5FIHZvaWQgV3JpdGVCRTE2KFVJTlQ4KiBCdWZmZXIsIFVJTlQxNiBWYWx1ZSk7CklOTElORSB2b2lkIFdyaXRlQkUzMihVSU5UOCogQnVmZmVyLCBVSU5UMzIgVmFsdWUpOwoKCiNkZWZpbmUgVVNFX1ZFTE9DSVRZCTEKI2RlZmluZSBOT19OT1RFU1RPUAkJMAoKc3RhdGljIFVJTlQzMiBTZXFTaXplOwpzdGF0aWMgVUlOVDgqIFNlcURhdGE7CnN0YXRpYyBVSU5UMzIgTWlkU2l6ZTsKc3RhdGljIFVJTlQ4KiBNaWREYXRhOwoKc3RhdGljIFVJTlQxNiBNSURJX1JFUzsKc3RhdGljIFVJTlQxNiBOVU1fTE9PUFM7CgppbnQgbWFpbihpbnQgYXJnYywgY2hhciogYXJndltdKQp7CglGSUxFKiBoRmlsZTsKCS8vY2hhciBGaWxlTmFtZVsweDEwXTsKCglpZiAoYXJnYyA8PSAyKQoJewoJCXByaW50ZigiVXNhZ2U6IG11Y29tMm1pZCBpbnB1dC5iaW4gb3V0cHV0Lm1pZFxuIik7CiNpZmRlZiBfREVCVUcKCQlfZ2V0Y2goKTsKI2VuZGlmCgkJcmV0dXJuIDA7Cgl9CgoJTUlESV9SRVMgPSAyNDsKCS8vTUlESV9SRVMgPSAweDIwOwoJTlVNX0xPT1BTID0gMjsKCgloRmlsZSA9IGZvcGVuKGFyZ3ZbMV0sICJyYiIpOwoJaWYgKGhGaWxlID09IE5VTEwpCgkJcmV0dXJuIDE7CgoJZnNlZWsoaEZpbGUsIDAsIFNFRUtfRU5EKTsKCVNlcVNpemUgPSBmdGVsbChoRmlsZSk7CgoJU2VxRGF0YSA9IChVSU5UOCopbWFsbG9jKFNlcVNpemUpOwoJZnNlZWsoaEZpbGUsIDAsIFNFRUtfU0VUKTsKCWZyZWFkKFNlcURhdGEsIDB4MDEsIFNlcVNpemUsIGhGaWxlKTsKCglmY2xvc2UoaEZpbGUpOwoKCXByaW50ZigiQ29udmVydGluZyAlcyAuLi5cbiIsIGFyZ3ZbMV0pOwoJTWlkU2l6ZSA9IDB4MjAwMDA7CglNaWREYXRhID0gKFVJTlQ4KiltYWxsb2MoTWlkU2l6ZSk7CglDb252ZXJ0TXVjb20yTUlEKCk7CgoJaEZpbGUgPSBmb3Blbihhcmd2WzJdLCAid2IiKTsKCWlmIChoRmlsZSA9PSBOVUxMKQoJewoJCXByaW50ZigiRXJyb3Igc2F2aW5nICVzIVxuIiwgYXJndlsyXSk7Cgl9CgllbHNlCgl7CgkJZndyaXRlKE1pZERhdGEsIDB4MDEsIE1pZFNpemUsIGhGaWxlKTsKCQlmY2xvc2UoaEZpbGUpOwoJfQoJZnJlZShNaWREYXRhKTsKCU1pZERhdGEgPSBOVUxMOwoJZnJlZShTZXFEYXRhKTsKCVNlcURhdGEgPSBOVUxMOwoKCXByaW50ZigiRG9uZS5cbiIpOwoKI2lmZGVmIF9ERUJVRwoJZ2V0Y2hhcigpOwojZW5kaWYKCglyZXR1cm4gMDsKfQoKdHlwZWRlZiBzdHJ1Y3QgX3RyYWNrX2hlYWRlcgp7CglVSU5UMTYgU3RhcnRPZnM7CglVSU5UMTYgTG9vcE9mczsKfSBUUktfSERSOwojZGVmaW5lIFNFUV9CQVNFT0ZTCTB4MDAwNQoKdm9pZCBDb252ZXJ0TXVjb20yTUlEKHZvaWQpCnsKCXN0YXRpYyBjb25zdCBVSU5UOCBOT1RFX0FSUkFZWzB4MTBdID0KCSAgICAvLwkwMCAgMDEgIDAyICAwMyAgMDQgIDA1ICAwNiAgMDcgIDA4ICAwOSAgMEEgIDBCICAwQyAgMEQgIDBFICAwRgoJICAgIC8vCUMgICBDIyAgRCAgIEQjICBFICAgLS0gIEYgICBGIyAgRyAgIEcjICBBICAgQSMgIEIgICAtLSAgLS0gIC0tCgl7CSAwLCAgMSwgIDIsICAzLCAgNCwgIDUsICA2LCAgNywgIDgsICA5LCAxMCwgMTEsMjU1LDI1NSwyNTUsMjU1fTsKCXN0YXRpYyBjb25zdCBVSU5UOCBSSFlUSE1fTk9URVNbMHgwNl0gPQoJezB4MjQsIDB4MjYsIDB4MzMsIDB4MkEsIDB4MkQsIDB4MjV9OwoJVUlOVDggTXVjb204OFdpbjsKCVVJTlQxNiBTZXFTaXplOwoJVUlOVDE2IFRya0hkclBvczsKCVVJTlQxNiBGTUluc1BvczsKCVVJTlQxNiBTU0dJbnNQb3M7CglUUktfSERSIFRya0hkcnNbMTFdOwoJVUlOVDE2IFNlcVBvczsKCVVJTlQzMiBNaWRQb3M7CglVSU5UOCBUcmtDbnQ7CglVSU5UOCBDdXJUcms7CglVSU5UMzIgTWlkVHJrQmFzZTsKCVVJTlQzMiBDdXJEbHk7CglVSU5UOCBNaWRDaG47CglVSU5UOCBUcmtFbmQ7CglVSU5UOCBNc3RMb29wQ250OwoJVUlOVDggVHJrTW9kZTsJLy8gMDAgLSBGTSwgMDEgLSBTU0cKCUlOVDggTm90ZU1vdmU7CgoJVUlOVDggQ3VyQ21kOwoKCVVJTlQ4IEN1ck5vdGU7CglVSU5UOCBDdXJOb3RlVm9sOwoJVUlOVDggQ3VyQ2huVm9sOwoJVUlOVDggQ2huUGFuT247CglVSU5UOCBMYXN0Tm90ZTsKCVVJTlQ4IEhvbGROb3RlOwoJVUlOVDggTm90ZVN0b3A7CglVSU5UOCBDdXJSaHl0aG1NYXNrOwoJVUlOVDggQ3VyUmh5dGhtT247CgoJVUlOVDggTG9vcElkeDsKCVVJTlQ4IExvb3BTdGtJZHg7CglVSU5UOCBMb29wQ250WzB4MTBdOwoKCVVJTlQ4IFRlbXBCeXQ7CglJTlQxNiBUZW1wUG9zOwoJVUlOVDMyIFRlbXBMbmc7CgoJVHJrSGRyUG9zID0gMHgwMDAwOwoJaWYgKCFtZW1jbXAoU2VxRGF0YSwgIk1VQjgiLCA0KSkKCXsKCQlwcmludGYoIkRldGVjdGVkIE11Y29tODh3aW4gaGVhZGVyLlxuIik7CgkJTXVjb204OFdpbiA9IDE7CgkJVHJrSGRyUG9zID0gUmVhZExFMTYoJlNlcURhdGFbNF0pICsgNTsKCX0KCWVsc2UKCXsKCQlNdWNvbTg4V2luID0gMDsKCQlmb3IgKFNlcVBvcyA9IDB4MDA7IFNlcVBvcyA8IDB4MDg7IFNlcVBvcyArKykKCQl7CgkJCVRlbXBQb3MgPSBSZWFkTEUxNigmU2VxRGF0YVtTZXFQb3MgKyAweDAxXSk7CgkJCWlmIChUZW1wUG9zID09IDB4MDAyRikKCQkJewoJCQkJVHJrSGRyUG9zID0gU2VxUG9zOwoJCQkJYnJlYWs7CgkJCX0KCQl9Cgl9CglpZiAoISBUcmtIZHJQb3MpCgl7CgkJcHJpbnRmKCJVbmFibGUgdG8gZmluZCBzZXF1ZW5jZSBoZWFkZXIhXG4iKTsKCQlyZXR1cm47Cgl9CglwcmludGYoIlNlcXVlbmNlIGhlYWRlciBmb3VuZCBhdCAlMDRYLlxuIiwgVHJrSGRyUG9zKTsKCglUcmtDbnQgPSAxMTsKCVNlcVBvcyA9IDB4MDE7CglGTUluc1BvcyA9IFJlYWRMRTE2KCZTZXFEYXRhW1Rya0hkclBvc10pOwoJU1NHSW5zUG9zID0gUmVhZExFMTYoJlNlcURhdGFbVHJrSGRyUG9zKzJdKTsKCglTZXFQb3MgPSBUcmtIZHJQb3M7CgoJTWlkUG9zID0gMHgwMDsKCVdyaXRlQkUzMigmTWlkRGF0YVtNaWRQb3NdLCAweDRENTQ2ODY0KTsKCU1pZFBvcyArPSAweDA0OwkvLyAnTVRoZCcgU2lnbmF0dXJlCglXcml0ZUJFMzIoJk1pZERhdGFbTWlkUG9zXSwgMHgwMDAwMDAwNik7CglNaWRQb3MgKz0gMHgwNDsJLy8gSGVhZGVyIFNpemUKCVdyaXRlQkUxNigmTWlkRGF0YVtNaWRQb3NdLCAweDAwMDEpOwoJTWlkUG9zICs9IDB4MDI7CS8vIEZvcm1hdDogMQoJV3JpdGVCRTE2KCZNaWREYXRhW01pZFBvc10sIDErVHJrQ250KTsKCU1pZFBvcyArPSAweDAyOwkvLyBUcmFja3MgKCsxIGZvciB0ZW1wbykKCVdyaXRlQkUxNigmTWlkRGF0YVtNaWRQb3NdLCBNSURJX1JFUyk7CglNaWRQb3MgKz0gMHgwMjsJLy8gVGlja3MgcGVyIFF1YXJ0ZXIKCiNpZiAxCglXcml0ZUJFMzIoJk1pZERhdGFbTWlkUG9zXSwgMHg0RDU0NzI2Qik7CglNaWRQb3MgKz0gMHgwNDsJLy8gJ01UcmsnIFNpZ25hdHVyZQoJV3JpdGVCRTMyKCZNaWREYXRhW01pZFBvc10sIDB4MDAwMDAwMDApOwoJTWlkUG9zICs9IDB4MDQ7CS8vIFRyYWNrIFNpemUKCU1pZFRya0Jhc2UgPSBNaWRQb3M7CglDdXJEbHkgPSAwOwoKCVRlbXBMbmcgPSBUZW1wbzJNaWQoU2VxRGF0YVtTZXFQb3NdKTsJLy8gZGVmYXVsdCB0ZW1wbyB2YWx1ZQoJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEZGLCAweDUxLCAweDAzKTsKCVdyaXRlQkUzMigmTWlkRGF0YVtNaWRQb3MgLSAweDAxXSwgVGVtcExuZyk7CglNaWREYXRhW01pZFBvcyAtIDB4MDFdID0gMHgwMzsJLy8gd3JpdGUgYWdhaW4sIGJlY2F1c2UgdGhlIGFib3ZlIGluc3RydWN0aW9uIG92ZXJ3cm90ZSBpdAoJTWlkUG9zICs9IDB4MDM7CglTZXFQb3MgKys7CgoJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEZGLCAweDJGLCAweDAwKTsKCVdyaXRlQkUzMigmTWlkRGF0YVtNaWRUcmtCYXNlIC0gMHgwNF0sIE1pZFBvcyAtIE1pZFRya0Jhc2UpOwkvLyB3cml0ZSBUcmFjayBMZW5ndGgKI2VuZGlmCgoJZm9yIChDdXJUcmsgPSAwOyBDdXJUcmsgPCBUcmtDbnQ7IEN1clRyayArKywgU2VxUG9zICs9IDB4MDQpCgl7CgkJVHJrSGRyc1tDdXJUcmtdLlN0YXJ0T2ZzID0gUmVhZExFMTYoJlNlcURhdGFbU2VxUG9zICsgMHgwMF0pOwoJCWlmIChUcmtIZHJzW0N1clRya10uU3RhcnRPZnMpCgkJCVRya0hkcnNbQ3VyVHJrXS5TdGFydE9mcyArPSBUcmtIZHJQb3M7CgkJVHJrSGRyc1tDdXJUcmtdLkxvb3BPZnMgPSBSZWFkTEUxNigmU2VxRGF0YVtTZXFQb3MgKyAweDAyXSk7CgkJaWYgKFRya0hkcnNbQ3VyVHJrXS5Mb29wT2ZzKQoJCQlUcmtIZHJzW0N1clRya10uTG9vcE9mcyArPSBUcmtIZHJQb3M7Cgl9CglTZXFTaXplID0gUmVhZExFMTYoJlNlcURhdGFbU2VxUG9zXSkgKyBUcmtIZHJQb3M7CglTZXFQb3MgKz0gMHgwMjsKCglmb3IgKEN1clRyayA9IDA7IEN1clRyayA8IFRya0NudDsgQ3VyVHJrICsrKQoJewoJCVdyaXRlQkUzMigmTWlkRGF0YVtNaWRQb3NdLCAweDRENTQ3MjZCKTsJLy8gd3JpdGUgJ01UcmsnCgkJTWlkUG9zICs9IDB4MDg7CgkJTWlkVHJrQmFzZSA9IE1pZFBvczsKCgkJU2VxUG9zID0gVHJrSGRyc1tDdXJUcmtdLlN0YXJ0T2ZzOwoJCWlmIChDdXJUcmsgPCAzKQkJCS8vIEZNIDEuLjMKCQl7CgkJCVRya01vZGUgPSAwOwoJCQlNaWRDaG4gPSBDdXJUcms7CgkJfQoJCWVsc2UgaWYgKEN1clRyayA8IDYpCS8vIFNTRyAxLi4zCgkJewoJCQlUcmtNb2RlID0gMTsKCQkJTWlkQ2huID0gMTAgKyBDdXJUcmsgLSAzOwoJCX0KCQllbHNlIGlmIChDdXJUcmsgPT0gNikJLy8gQURQQ01BIFJoeXRobQoJCXsKCQkJVHJrTW9kZSA9IDM7CgkJCU1pZENobiA9IDk7CgkJfQoJCWVsc2UgaWYgKEN1clRyayA9PSAxMCkJLy8gQURQQ01CL0RlbHRhVAoJCXsKCQkJVHJrTW9kZSA9IDI7CgkJCU1pZENobiA9IDk7CgkJfQoJCWVsc2UKCQl7CgkJCVRya01vZGUgPSAwOwoJCQlNaWRDaG4gPSAzICsgQ3VyVHJrIC0gNzsKCQl9CgkJcHJpbnRmKCJUcmFjayAldSAuLi5cbiIsIEN1clRyayk7CgoJCUN1ckRseSA9IDA7CgkJVHJrRW5kID0gKFNlcVBvcyA9PSAweDAwMDApOwoJCU1zdExvb3BDbnQgPSAweDAwOwoJCU5vdGVNb3ZlID0gVHJrTW9kZSA/ICsxMiA6IDA7CgkJLy9Xcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4NjUsIDB4MDApOwkvLyBSUE4gTVNCOiAwCgkJLy9Xcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4NjQsIDB4MDApOwkvLyBSUE4gTFNCOiAwCgkJLy9Xcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4MDYsIDEyKTsJLy8gRGF0YSBNU0IgLSBzZXQgUGl0Y2ggQmVuZCBSYW5nZQoKCQltZW1zZXQoTG9vcENudCwgMHgwMCwgMHgxMCk7CgkJQ3VyTm90ZSA9IDB4RkY7CgkJTGFzdE5vdGUgPSAweEZGOwoJCUN1ck5vdGVWb2wgPSAweDdGOwoJCUN1ckNoblZvbCA9IDB4MDA7CgkJQ2huUGFuT24gPSAweDAwOwoJCUhvbGROb3RlID0gMHgwMDsKCQlOb3RlU3RvcCA9IDA7CgkJQ3VyUmh5dGhtTWFzayA9IDB4MDA7CgkJQ3VyUmh5dGhtT24gPSAweDAwOwoJCUxvb3BTdGtJZHggPSAweDAwOwoJCUxvb3BJZHggPSAweDAwOwoJCXdoaWxlKCEgVHJrRW5kKQoJCXsKCQkJaWYgKCEgTXN0TG9vcENudCAmJiBTZXFQb3MgPT0gVHJrSGRyc1tDdXJUcmtdLkxvb3BPZnMpCgkJCXsKCQkJCU1zdExvb3BDbnQgKys7CgkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4NkYsIDB4MDApOwoJCQl9CgoJCQlDdXJDbWQgPSBTZXFEYXRhW1NlcVBvc107CgkJCVNlcVBvcyArKzsKCQkJaWYgKEN1ckNtZCA9PSAweDAwKQoJCQl7CgkJCQlpZiAoVHJrSGRyc1tDdXJUcmtdLkxvb3BPZnMpCgkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDZGLCBNc3RMb29wQ250KTsKCQkJCWlmIChUcmtIZHJzW0N1clRya10uTG9vcE9mcyAmJiBNc3RMb29wQ250IDwgTlVNX0xPT1BTKQoJCQkJewoJCQkJCVNlcVBvcyA9IFRya0hkcnNbQ3VyVHJrXS5Mb29wT2ZzOwoJCQkJCU1zdExvb3BDbnQgKys7CgkJCQl9CgkJCQllbHNlCgkJCQl7CgkJCQkJVHJrRW5kID0gMHgwMTsKCQkJCX0KCQkJfQoJCQllbHNlIGlmIChDdXJDbWQgPCAweDgwKQoJCQl7CgkJCQlUZW1wQnl0ID0gU2VxRGF0YVtTZXFQb3NdOwoJCQkJU2VxUG9zICsrOwoJCQkJQ3VyTm90ZSA9IE5PVEVfQVJSQVlbVGVtcEJ5dCAmIDB4MEZdOwoJCQkJaWYgKEN1ck5vdGUgPT0gMHhGRikKCQkJCQlwcmludGYoIldhcm5pbmc6IEludmFsaWQgTm90ZSAlMDJYIVxuIiwgVGVtcEJ5dCk7CgkJCQlDdXJOb3RlICs9IChUZW1wQnl0ID4+IDQpICogMTI7CgkJCQlDdXJOb3RlICs9IE5vdGVNb3ZlICsgMTI7CgoJCQkJaWYgKFRya01vZGUgPT0gMykKCQkJCXsKCQkJCQlpZiAoISBIb2xkTm90ZSkKCQkJCQl7CgkJCQkJCWZvciAoVGVtcEJ5dCA9IDB4MDA7IFRlbXBCeXQgPCAweDA2OyBUZW1wQnl0ICsrKQoJCQkJCQl7CgkJCQkJCQlpZiAoQ3VyUmh5dGhtT24gJiAoMSA8PCBUZW1wQnl0KSkKCQkJCQkJCXsKCQkJCQkJCQlDdXJSaHl0aG1PbiAmPSB+KDEgPDwgVGVtcEJ5dCk7CgkJCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweDkwIHwgTWlkQ2huLCBSSFlUSE1fTk9URVNbVGVtcEJ5dF0sIDB4MDApOwoJCQkJCQkJfQoJCQkJCQl9CgkJCQkJCWZvciAoVGVtcEJ5dCA9IDB4MDA7IFRlbXBCeXQgPCAweDA2OyBUZW1wQnl0ICsrKQoJCQkJCQl7CgkJCQkJCQlpZiAoQ3VyUmh5dGhtTWFzayAmICgxIDw8IFRlbXBCeXQpKQoJCQkJCQkJewoJCQkJCQkJCUN1clJoeXRobU9uIHw9ICgxIDw8IFRlbXBCeXQpOwoJCQkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHg5MCB8IE1pZENobiwgUkhZVEhNX05PVEVTW1RlbXBCeXRdLCBDdXJOb3RlVm9sKTsKCQkJCQkJCX0KCQkJCQkJfQoJCQkJCX0KCQkJCX0KCQkJCWVsc2UgaWYgKExhc3ROb3RlICE9IEN1ck5vdGUgfHwgISBIb2xkTm90ZSkKCQkJCXsKCQkJCQlpZiAoSG9sZE5vdGUpCgkJCQkJewoJCQkJCQlpZiAoQ3VyRGx5ID49IDEpCgkJCQkJCXsKCQkJCQkJCUN1ckRseSAtLTsKCQkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHg0MSwgMHg3Rik7CgkJCQkJCQlDdXJEbHkgKys7CgkJCQkJCX0KCQkJCQkJZWxzZQoJCQkJCQl7CgkJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4NDEsIDB4N0YpOwoJCQkJCQl9CgkJCQkJfQoKCQkJCQlpZiAoTGFzdE5vdGUgIT0gMHhGRikKCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweDkwIHwgTWlkQ2huLCBMYXN0Tm90ZSwgMHgwMCk7CgkJCQkJaWYgKEN1ck5vdGUgIT0gMHhGRikKCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweDkwIHwgTWlkQ2huLCBDdXJOb3RlLCBDdXJOb3RlVm9sKTsKCQkJCQlMYXN0Tm90ZSA9IEN1ck5vdGU7CgoJCQkJCWlmIChIb2xkTm90ZSkKCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDQxLCAweDAwKTsKCQkJCX0KCQkJCUhvbGROb3RlID0gMHgwMDsKCgkJCQlDdXJEbHkgKz0gQ3VyQ21kOwoJCQkJaWYgKE5vdGVTdG9wICYmIEN1ckRseSA+IE5vdGVTdG9wICYmIFNlcURhdGFbU2VxUG9zXSAhPSAweEZEKQoJCQkJewoJCQkJCUN1ckRseSAtPSBOb3RlU3RvcDsKCQkJCQlpZiAoVHJrTW9kZSA9PSAzKQoJCQkJCXsKCQkJCQkJZm9yIChUZW1wQnl0ID0gMHgwMDsgVGVtcEJ5dCA8IDB4MDY7IFRlbXBCeXQgKyspCgkJCQkJCXsKCQkJCQkJCWlmIChDdXJSaHl0aG1PbiAmICgxIDw8IFRlbXBCeXQpKQoJCQkJCQkJewoJCQkJCQkJCUN1clJoeXRobU9uICY9IH4oMSA8PCBUZW1wQnl0KTsKCQkJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4OTAgfCBNaWRDaG4sIFJIWVRITV9OT1RFU1tUZW1wQnl0XSwgMHgwMCk7CgkJCQkJCQl9CgkJCQkJCX0KCQkJCQl9CgkJCQkJZWxzZSBpZiAoTGFzdE5vdGUgIT0gMHhGRikKCQkJCQl7CgkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHg5MCB8IE1pZENobiwgTGFzdE5vdGUsIDB4MDApOwoJCQkJCQlMYXN0Tm90ZSA9IDB4RkY7CgkJCQkJfQoJCQkJCUN1ckRseSArPSBOb3RlU3RvcDsKCQkJCX0KCQkJfQoJCQllbHNlIGlmIChDdXJDbWQgPCAweEYwKQoJCQl7CgkJCQlpZiAoISBIb2xkTm90ZSAmJiBMYXN0Tm90ZSAhPSAweEZGKQoJCQkJewoJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHg5MCB8IE1pZENobiwgTGFzdE5vdGUsIDB4MDApOwoJCQkJCUxhc3ROb3RlID0gMHhGRjsKCQkJCX0KCQkJCUhvbGROb3RlID0gMHgwMDsKCgkJCQlDdXJEbHkgKz0gQ3VyQ21kICYgMHg3RjsKCQkJfQoJCQllbHNlCgkJCXsKCQkJCXN3aXRjaChDdXJDbWQpCgkJCQl7CgkJCQljYXNlIDB4RjA6CS8vIFNldCBJbnN0cnVtZW50CgkJCQkJaWYgKFRya01vZGUgPT0gMyAmJiBNdWNvbTg4V2luKQoJCQkJCXsKCQkJCQkJLy8gc2V0IFJoeXRobSBNYXNrCgkJCQkJCUN1clJoeXRobU1hc2sgPSBTZXFEYXRhW1NlcVBvc107CgkJCQkJfQoJCQkJCWVsc2UKCQkJCQl7CgkJCQkJCVRlbXBCeXQgPSBTZXFEYXRhW1NlcVBvc10gJiAweDdGOwoJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QzAgfCBNaWRDaG4sIFRlbXBCeXQsIDB4MDApOwoJCQkJCX0KCQkJCQlTZXFQb3MgKys7CgkJCQkJYnJlYWs7CgkJCQljYXNlIDB4RjE6CS8vIFNldCBWb2x1bWUKCQkJCQlDdXJDaG5Wb2wgPSBTZXFEYXRhW1NlcVBvc107CgkJCQkJVGVtcEJ5dCA9IE11Y29tVm9sMk1pZChUcmtNb2RlLCBDdXJDaG5Wb2wsIENoblBhbk9uKTsKCQkJCQlpZiAoISBVU0VfVkVMT0NJVFkpCgkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgwNywgVGVtcEJ5dCk7CgkJCQkJZWxzZQoJCQkJCQlDdXJOb3RlVm9sID0gVGVtcEJ5dDsKCQkJCQlTZXFQb3MgKys7CgkJCQkJaWYgKFRya01vZGUgPT0gMykKCQkJCQkJU2VxUG9zICs9IDB4MDY7CgkJCQkJYnJlYWs7CgkJCQljYXNlIDB4RjI6CS8vIERldHVuZQoJCQkJCS8vV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDZFLCBDdXJDbWQgJiAweDBGKTsKCQkJCQkvL1dyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgyNiwgU2VxRGF0YVtTZXFQb3NdKTsKCQkJCQlUZW1wUG9zID0gUmVhZExFMTYoJlNlcURhdGFbU2VxUG9zXSk7CgkJCQkJVGVtcFBvcyA9IDB4MjAwMCAtIChUZW1wUG9zIDw8IDUpOwoJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhFMCB8IE1pZENobiwgVGVtcFBvcyAmIDB4N0YsIChUZW1wUG9zID4+IDcpICYgMHg3Rik7CgkJCQkJU2VxUG9zICs9IDB4MDM7CgkJCQkJYnJlYWs7CgkJCQljYXNlIDB4RjM6CgkJCQkJaWYgKFRya01vZGUgPT0gMykKCQkJCQl7CgkJCQkJCS8vIHNldCBSaHl0aG0gTWFzawoJCQkJCQlDdXJSaHl0aG1NYXNrID0gU2VxRGF0YVtTZXFQb3NdOwoJCQkJCX0KCQkJCQllbHNlCgkJCQkJewoJCQkJCQkvLyBOb3RlIFN0b3AvRWNobyBWb2x1bWU/CgkJCQkJCU5vdGVTdG9wID0gU2VxRGF0YVtTZXFQb3NdOwoJCQkJCQkvL2lmIChUcmtNb2RlID09IDAgJiYgTm90ZVN0b3AgPj0gNCkKCQkJCQkJLy8JTm90ZVN0b3AgPSAwOwoJCQkJCQlpZiAoTk9fTk9URVNUT1ApCgkJCQkJCQlOb3RlU3RvcCA9IDA7CgkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgyOSwgU2VxRGF0YVtTZXFQb3NdKTsKCQkJCQl9CgkJCQkJU2VxUG9zICsrOwoJCQkJCWJyZWFrOwoJCQkJY2FzZSAweEY0OgkvLyBNb2R1bGF0aW9uCgkJCQkJaWYgKCEgU2VxRGF0YVtTZXFQb3MgKyAweDAwXSkKCQkJCQl7CgkJCQkJCS8vIFNldCBNb2R1bGF0aW9uCgkJCQkJCVRlbXBQb3MgPSBSZWFkTEUxNigmU2VxRGF0YVtTZXFQb3MgKyAweDAzXSk7CgkJCQkJCVRlbXBCeXQgPSBTZXFEYXRhW1NlcVBvcyArIDB4MDVdOwoJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4MTAsIFNlcURhdGFbU2VxUG9zICsgMHgwMV0pOwoJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4MTEsIFNlcURhdGFbU2VxUG9zICsgMHgwMl0pOwoJCQkJCQlpZiAoISBUcmtNb2RlKQoJCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDEyLCBUZW1wUG9zICYgMHg3Rik7CgkJCQkJCWVsc2UKCQkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgxMiwgKFRlbXBQb3MvOCkgJiAweDdGKTsKCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDEzLCBUZW1wQnl0ICYgMHg3Rik7CgoJCQkJCQlpZiAoVGVtcFBvcyA8IDApCgkJCQkJCQlUZW1wUG9zID0gLVRlbXBQb3M7CgkJCQkJCVRlbXBMbmcgPSAoVGVtcFBvcyAqIFRlbXBQb3MpIC8gODsKCgkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgwMSwgVGVtcExuZyk7CgkJCQkJCVNlcVBvcyArPSAweDA2OwoJCQkJCX0KCQkJCQllbHNlCgkJCQkJewoJCQkJCQkvLyBEaXNhYmxlIE1vZHVsYXRpb24KCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDAxLCAweDAwKTsKCQkJCQkJU2VxUG9zICsrOwoJCQkJCX0KCQkJCQlicmVhazsKCQkJCWNhc2UgMHhGNToJLy8gTG9vcCBTdGFydAoJCQkJCVRlbXBQb3MgPSBSZWFkTEUxNigmU2VxRGF0YVtTZXFQb3NdKTsJLy8gZ2V0IG9mZnNldCBvZiBMb29wIENvdW50CgkJCQkJTG9vcFN0a0lkeCArKzsKCQkJCQlpZiAoU2VxUG9zICsgVGVtcFBvcyA8IFNlcVNpemUpCgkJCQkJCUxvb3BDbnRbTG9vcFN0a0lkeF0gPSBTZXFEYXRhW1NlcVBvcyArIFRlbXBQb3NdOwoJCQkJCWVsc2UKCQkJCQkJTG9vcENudFtMb29wU3RrSWR4XSA9IDB4MDA7CgkJCQkJU2VxUG9zICs9IDB4MDI7CgkJCQkJYnJlYWs7CgkJCQljYXNlIDB4RjY6CS8vIExvb3AgRW5kCgkJCQkJVGVtcEJ5dCA9IFNlcURhdGFbU2VxUG9zICsgMHgwMV07CgkJCQkJaWYgKCEgTG9vcENudFtMb29wU3RrSWR4XSkKCQkJCQkJTG9vcENudFtMb29wU3RrSWR4XSA9IFRlbXBCeXQ7CgkJCQkJU2VxUG9zICs9IDB4MDI7CgoJCQkJCVRlbXBQb3MgPSBSZWFkTEUxNigmU2VxRGF0YVtTZXFQb3NdKTsKCQkJCQlMb29wQ250W0xvb3BTdGtJZHhdIC0tOwoJCQkJCWlmIChMb29wQ250W0xvb3BTdGtJZHhdKQoJCQkJCXsKCQkJCQkJU2VxUG9zIC09IFRlbXBQb3M7CgkJCQkJfQoJCQkJCWVsc2UKCQkJCQl7CgkJCQkJCUxvb3BTdGtJZHggLS07CgkJCQkJCVNlcVBvcyArPSAweDAyOwoJCQkJCX0KCQkJCQlicmVhazsKCQkJCWNhc2UgMHhGNzogLy8gRk0zIHNwZWNpYWwgbW9kZQoJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHg2RSwgQ3VyQ21kICYgMHgwRik7CgkJCQkJU2VxUG9zICsrOwoJCQkJCWJyZWFrOwoJCQkJY2FzZSAweEY4OgoJCQkJY2FzZSAweEY5OgkvLyBQYW4KCQkJCQlpZigoTXVjb204OFdpbiAmJiBDdXJDbWQgPT0gMHhmOCkgfHwgKCFNdWNvbTg4V2luICYmIEN1ckNtZCA9PSAweGY5KSkKCQkJCQl7CgkJCQkJCVRlbXBCeXQgPSBTZXFEYXRhW1NlcVBvc107CgkJCQkJCWlmIChUcmtNb2RlID09IDMpCS8vIHJoeXRobSBtb2RlIHdvcmtzIGRpZmZlcmVudGx5CgkJCQkJCXsKCQkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgyNiwgVGVtcEJ5dCk7CgkJCQkJCQlUZW1wQnl0ID0gMHgwMDsKCQkJCQkJfQoJCQkJCQlUZW1wQnl0ICY9IDB4MDM7CgkJCQkJCWlmIChUZW1wQnl0ID09IDB4MDEpCS8vIHJpZ2h0IHNwZWFrZXIKCQkJCQkJCVRlbXBCeXQgPSAweDdGOwoJCQkJCQllbHNlIGlmIChUZW1wQnl0ID09IDB4MDIpCS8vIGxlZnQgc3BlYWtlcgoJCQkJCQkJVGVtcEJ5dCA9IDB4MDA7CgkJCQkJCWVsc2UJLy8gYm90aCBzcGVha2VycwoJCQkJCQkJVGVtcEJ5dCA9IDB4NDA7CgkJCQkJCUNoblBhbk9uID0gKFRlbXBCeXQgPT0gMHg0MCkgPyAweDAwIDogMHgwMTsKCgkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgwQSwgVGVtcEJ5dCk7CgkJCQkJCVRlbXBCeXQgPSBNdWNvbVZvbDJNaWQoVHJrTW9kZSwgQ3VyQ2huVm9sLCBDaG5QYW5Pbik7CgkJCQkJCWlmICghIFVTRV9WRUxPQ0lUWSkKCQkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHgwNywgVGVtcEJ5dCk7CgkJCQkJCWVsc2UKCQkJCQkJCUN1ck5vdGVWb2wgPSBUZW1wQnl0OwoJCQkJCQlTZXFQb3MgKys7CgkJCQkJCWJyZWFrOwoJCQkJCX0KCQkJCQllbHNlCgkJCQkJewoJCQkJCQkvLyBzZWVtcyB0byBqdXN0IHdyaXRlIGEgY29tbXVuaWNhdGlvbiBieXRlCgkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgMHg2RSwgQ3VyQ21kICYgMHgwRik7CgkJCQkJCVNlcVBvcyArKzsKCQkJCQkJYnJlYWs7CgkJCQkJfQoJCQkJY2FzZSAweEZBOgkvLyBSZWdpc3RlciBXcml0ZQoJCQkJCWlmIChUcmtNb2RlID09IDEpCgkJCQkJewoJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4NkUsIEN1ckNtZCAmIDB4MEYpOwoJCQkJCQlTZXFQb3MgKz0gMHgwNjsKCQkJCQl9CgkJCQkJZWxzZQkvLyBZTTIyMDMvMjYwOCBSZWdpc3RlciBXcml0ZQoJCQkJCXsKCQkJCQkJaWYgKFNlcURhdGFbU2VxUG9zICsgMHgwMF0gPT0gMHgyNikJLy8gVGltZXIgQiAtIGNoYW5nZSBUZW1wbwoJCQkJCQl7CgkJCQkJCQlUZW1wTG5nID0gVGVtcG8yTWlkKFNlcURhdGFbU2VxUG9zICsgMHgwMV0pOwoJCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEZGLCAweDUxLCAweDAzKTsKCQkJCQkJCVdyaXRlQkUzMigmTWlkRGF0YVtNaWRQb3MgLSAweDAxXSwgVGVtcExuZyk7CgkJCQkJCQlNaWREYXRhW01pZFBvcyAtIDB4MDFdID0gMHgwMzsJLy8gd3JpdGUgYWdhaW4sIGJlY2F1c2UgdGhlIGFib3ZlIGluc3RydWN0aW9uIG92ZXJ3cm90ZSBpdAoJCQkJCQkJTWlkUG9zICs9IDB4MDM7CgkJCQkJCX0KCQkJCQkJZWxzZQoJCQkJCQl7CgkJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDUwLCBTZXFEYXRhW1NlcVBvcyArIDB4MDBdKTsKCQkJCQkJCVdyaXRlRXZlbnQoTWlkRGF0YSwgJk1pZFBvcywgJkN1ckRseSwgMHhCMCB8IE1pZENobiwgNTEsIFNlcURhdGFbU2VxUG9zICsgMHgwMV0pOwoJCQkJCQl9CgkJCQkJCVNlcVBvcyArPSAweDAyOwoJCQkJCX0KCQkJCQlicmVhazsKCQkJCWNhc2UgMHhGQjoJLy8gQ2hhbmdlIFZvbHVtZQoJCQkJCUN1ckNoblZvbCArPSBTZXFEYXRhW1NlcVBvc107CgkJCQkJVGVtcEJ5dCA9IE11Y29tVm9sMk1pZChUcmtNb2RlLCBDdXJDaG5Wb2wsIENoblBhbk9uKTsKCQkJCQlpZiAoISBVU0VfVkVMT0NJVFkpCgkJCQkJewoJCQkJCQlpZiAoU2VxRGF0YVtTZXFQb3NdICE9IDB4RkIpCgkJCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4MDcsIFRlbXBCeXQpOwoJCQkJCX0KCQkJCQllbHNlCgkJCQkJCUN1ck5vdGVWb2wgPSBUZW1wQnl0OwoJCQkJCVNlcVBvcyArKzsKCQkJCQlicmVhazsKCQkJCWNhc2UgMHhGQzoKCQkJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4QjAgfCBNaWRDaG4sIDB4NkUsIEN1ckNtZCAmIDB4MEYpOwoJCQkJCS8vcHJpbnRmKCJVbmtub3duIGNvbW1hbmQgJTAyWCBhdCAlMDRYIVxuIiwgQ3VyQ21kLCBTZXFQb3MgLSAweDAxKTsKCQkJCQkvL2dldGNoYXIoKTsKCQkJCQlTZXFQb3MgKz0gMHgwMzsKCQkJCQlicmVhazsKCQkJCWNhc2UgMHhGRDoJLy8gSG9sZAoJCQkJCUhvbGROb3RlID0gMHgwMTsKCQkJCQlicmVhazsKCQkJCWNhc2UgMHhGRToJLy8gTG9vcCBFeGl0CgkJCQkJVGVtcFBvcyA9IFJlYWRMRTE2KCZTZXFEYXRhW1NlcVBvc10pOwkvLyBnZXQgb2Zmc2V0IG9mIExvb3AgRXhpdCBDb3VudGVyCgkJCQkJVGVtcEJ5dCA9IFNlcURhdGFbU2VxUG9zICsgVGVtcFBvc107CgkJCQkJaWYgKExvb3BDbnRbTG9vcFN0a0lkeF0gPT0gMSkKCQkJCQl7CgkJCQkJCUxvb3BDbnRbTG9vcFN0a0lkeF0gPSAwOwoJCQkJCQlMb29wU3RrSWR4IC0tOwoJCQkJCQlTZXFQb3MgKz0gVGVtcFBvcyArIDB4MDQ7CgkJCQkJfQoJCQkJCWVsc2UKCQkJCQl7CgkJCQkJCVNlcVBvcyArPSAweDAyOwoJCQkJCX0KCQkJCQlicmVhazsKCQkJCWNhc2UgMHhGRjoJLy8gTWFzdGVyIExvb3AgU3RhcnQ/PwoJCQkJCWlmKE11Y29tODhXaW4pCgkJCQkJewoJCQkJCQlUZW1wQnl0ID0gU2VxRGF0YVtTZXFQb3NdOwoJCQkJCQlTZXFQb3MgKys7CgkJCQkJCXN3aXRjaChUZW1wQnl0KQoJCQkJCQl7CgkJCQkJCWNhc2UgMHhmMDogLy8gUENNIHZvbHVtZSBtb2RlICgndm0nKQoJCQkJCQkJU2VxUG9zKys7CgkJCQkJCQlicmVhazsKCQkJCQkJY2FzZSAweGYxOiAvLyBQU0cgaGFyZHdhcmUgc3VwcG9ydCAobXVjb204OCB2MS41IC8gbXVzaWMgbGFsZiAxLjAgb25seSkKCQkJCQkJY2FzZSAweGYyOgoJCQkJCQkJU2VxUG9zKys7CgkJCQkJCQlicmVhazsKCQkJCQkJY2FzZSAweGYzOiAvLyBSZXZlcmIgZW5hYmxlCgkJCQkJCWNhc2UgMHhmNDogLy8gUmV2ZXJiIG1vZGUKCQkJCQkJY2FzZSAweGY1OiAvLyBSZXZlcmIgc3dpdGNoCgkJCQkJCQlTZXFQb3MrKzsKCQkJCQkJCWJyZWFrOwoJCQkJCQlkZWZhdWx0OgoJCQkJCQkJcHJpbnRmKCJ1bmtub3duIGV4dHJhIGNvbW1hbmQgJTAyeCBhdCAlMDR4XG4iLCBUZW1wQnl0LCBTZXFQb3MpOwoJCQkJCQkJYnJlYWs7CgkJCQkJCX0KCQkJCQkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDcxLCBUZW1wQnl0ICYgMHgwRik7CgkJCQkJfQoJCQkJCS8vV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEIwIHwgTWlkQ2huLCAweDZGLCAweDAwKTsKCQkJCQkvL1NlcVBvcyArPSAweDAyOwoJCQkJCWJyZWFrOwoJCQkJfQoJCQl9CgkJfQoJCWlmIChMYXN0Tm90ZSAhPSAweEZGKQoJCQlXcml0ZUV2ZW50KE1pZERhdGEsICZNaWRQb3MsICZDdXJEbHksIDB4OTAgfCBNaWRDaG4sIExhc3ROb3RlLCAweDAwKTsKCgkJV3JpdGVFdmVudChNaWREYXRhLCAmTWlkUG9zLCAmQ3VyRGx5LCAweEZGLCAweDJGLCAweDAwKTsKCQlXcml0ZUJFMzIoJk1pZERhdGFbTWlkVHJrQmFzZSAtIDB4MDRdLCBNaWRQb3MgLSBNaWRUcmtCYXNlKTsJLy8gd3JpdGUgVHJhY2sgTGVuZ3RoCgl9CglNaWRTaXplID0gTWlkUG9zOwoKCXJldHVybjsKfQoKSU5MSU5FIFVJTlQ4IE11Y29tVm9sMk1pZChVSU5UOCBUcmtNb2RlLCBVSU5UOCBWb2wsIFVJTlQ4IFBhbkJvb3N0KQp7Cglkb3VibGUgREJWb2w7CgoJaWYgKFRya01vZGUgPT0gMCkKCQlEQlZvbCA9IEZNVm9sMkRCKFZvbCk7CgllbHNlIGlmIChUcmtNb2RlID09IDEpCgkJREJWb2wgPSBQU0dWb2wyREIoVm9sKTsKCWVsc2UgaWYgKFRya01vZGUgPT0gMikKCQlEQlZvbCA9IERlbHRhVFZvbDJEQihWb2wpOwoJZWxzZSBpZiAoVHJrTW9kZSA9PSAzKQoJCURCVm9sID0gRGVsdGFUVm9sMkRCKFZvbCAqIDQpOwoJZWxzZQoJCXJldHVybiBWb2w7CglpZiAoUGFuQm9vc3QpCgkJREJWb2wgLT0gMy4wOwoJcmV0dXJuIERCMk1pZChEQlZvbCk7Cn0KCklOTElORSBkb3VibGUgRk1Wb2wyREIoVUlOVDggVm9sKQp7CgkvLyBNdWNvbSB1c2VzIGEgRk0gdm9sdW1lIGxvb2t1cCB0YWJsZSB0byBtYXAgaXRzIHZvbHVtZSB2YWx1ZSB0byA4LzMgRk0gc3RlcHMuICgyIGRiIHN0ZXBzKQoJLy8gVGhlIHRhYmxlIGNvbnRhaW5zIDIwIHZhbHVlcyBhbmQgbG9va3MgbGlrZSB0aGlzOgoJLy8gMzYgMzMgMzAgMkQgMkEgMjggMjUgMjIgMjAgMUQgMUEgMTggMTUgMTIgMTAgMEQgMEEgMDggMDUgMDIKI2lmIDAKCVVJTlQ4IEZtVm9sOwoKCWlmIChWb2wgPCAyMCkKCQlGbVZvbCA9ICgyMCAtIFZvbCkgKiA4IC8gMzsKCWVsc2UKCQlGbVZvbCA9IDA7CglyZXR1cm4gRm1Wb2wgKiAtMC43NTsKI2Vsc2UKCWlmIChWb2wgPCAyMCkKCQlyZXR1cm4gKDIwIC0gVm9sKSAqIC0yLjA7CgllbHNlCgkJcmV0dXJuIDA7CiNlbmRpZgp9CgpJTkxJTkUgZG91YmxlIFBTR1ZvbDJEQihVSU5UOCBWb2wpCnsKCWlmIChWb2wgPiAweDBGKQoJCXJldHVybiAwLjA7CgllbHNlIGlmIChWb2wgPiAweDAwKQoJCXJldHVybiAoMHgwRiAtIFZvbCkgKiAtMy4wOwkvLyBBWTg5MTAgdm9sdW1lIGlzIDMgZGIgcGVyIHN0ZXAKCWVsc2UKCQlyZXR1cm4gLTk5OTsKfQoKSU5MSU5FIGRvdWJsZSBEZWx0YVRWb2wyREIoVUlOVDggVm9sKQp7CgkvL3JldHVybiBsb2coVm9sIC8gMjU1LjApIC8gbG9nKDIuMCkgKiA2LjA7CglyZXR1cm4gbG9nKFZvbCAvIDI1NS4wKSAqIDguNjU2MTcwMjQ1MzMzNzggKyA2LjA7CS8vIGJvb3N0IGl0cyB2b2x1bWUKfQoKSU5MSU5FIFVJTlQ4IERCMk1pZChkb3VibGUgREIpCnsKCURCICs9IDYuMDsKCWlmIChEQiA+IDAuMCkKCQlEQiA9IDAuMDsKCXJldHVybiAoVUlOVDgpKHBvdygxMC4wLCBEQiAvIDQwLjApICogMHg3RiArIDAuNSk7Cn0KCklOTElORSBVSU5UMzIgVGVtcG8yTWlkKFVJTlQ4IFRlbXBvVmFsKQp7CgkvLyBOb3RlOiBUaGUgdGVtcG8gdmFsdWUgaXMgdGhlIHZhbHVlIG9mIFlNIFRpbWVyIEIuCgkvLyBoaWdoZXIgdmFsdWUgPSBoaWdoZXIgdGljayBmcmVxdWVuY3kgPSBoaWdoZXIgdGVtcG8KCgkvLyBCYXNlIENsb2NrID0gMiBNSHoKCS8vIFByZXNjYWxlcjogNiAqIDEyCgkvLyBpbnRlcm5hbCBUaW1lciBDb3VudGRvd246ICgxMDBoIC0gdmFsdWUpICogMTBoCgkvLyBUaW1lciBGcmVxdWVuY3k6IENsb2NrIC8gKENvdW50ZG93biAqIFByZXNjYWxlcikKCWRvdWJsZSBUaWNrc1BlclNlYzsKCVVJTlQxNiBUbXJWYWw7CgoJVG1yVmFsID0gKDB4MTAwIC0gVGVtcG9WYWwpIDw8IDQ7CglUaWNrc1BlclNlYyA9IDIwMDAwMDAuMCAvICg2ICogMTIgKiBUbXJWYWwpOwoJcmV0dXJuIChVSU5UMzIpKDUwMDAwMCAqIE1JRElfUkVTIC8gVGlja3NQZXJTZWMgKyAwLjUpOwp9CgoKCnN0YXRpYyB2b2lkIFdyaXRlRXZlbnQoVUlOVDgqIEJ1ZmZlciwgVUlOVDMyKiBQb3MsIFVJTlQzMiogRGVsYXksIFVJTlQ4IEV2dCwgVUlOVDggVmFsMSwgVUlOVDggVmFsMikKewoJaWYgKCEgKEV2dCAmIDB4ODApKQoJCXJldHVybjsKCglXcml0ZU1pZGlWYWx1ZShCdWZmZXIsIFBvcywgKkRlbGF5KTsKCSpEZWxheSA9IDB4MDA7CgoJc3dpdGNoKEV2dCAmIDB4RjApCgl7CgljYXNlIDB4ODA6CgljYXNlIDB4OTA6CgljYXNlIDB4QTA6CgljYXNlIDB4QjA6CgljYXNlIDB4RTA6CgkJTWlkRGF0YVsqUG9zICsgMHgwMF0gPSBFdnQ7CgkJTWlkRGF0YVsqUG9zICsgMHgwMV0gPSBWYWwxOwoJCU1pZERhdGFbKlBvcyArIDB4MDJdID0gVmFsMjsKCQkqUG9zICs9IDB4MDM7CgkJYnJlYWs7CgljYXNlIDB4QzA6CgljYXNlIDB4RDA6CgkJTWlkRGF0YVsqUG9zICsgMHgwMF0gPSBFdnQ7CgkJTWlkRGF0YVsqUG9zICsgMHgwMV0gPSBWYWwxOwoJCSpQb3MgKz0gMHgwMjsKCQlicmVhazsKCWNhc2UgMHhGMDoJLy8gZm9yIE1ldGEgRXZlbnQ6IFRyYWNrIEVuZAoJCU1pZERhdGFbKlBvcyArIDB4MDBdID0gRXZ0OwoJCU1pZERhdGFbKlBvcyArIDB4MDFdID0gVmFsMTsKCQlNaWREYXRhWypQb3MgKyAweDAyXSA9IFZhbDI7CgkJKlBvcyArPSAweDAzOwoJCWJyZWFrOwoJZGVmYXVsdDoKCQlicmVhazsKCX0KCglyZXR1cm47Cn0KCnN0YXRpYyB2b2lkIFdyaXRlTWlkaVZhbHVlKFVJTlQ4KiBCdWZmZXIsIFVJTlQzMiogUG9zLCBVSU5UMzIgVmFsdWUpCnsKCVVJTlQ4IFZhbFNpemU7CglVSU5UOCogVmFsRGF0YTsKCVVJTlQzMiBUZW1wTG5nOwoJVUlOVDMyIEN1clBvczsKCglWYWxTaXplID0gMHgwMDsKCVRlbXBMbmcgPSBWYWx1ZTsKCWRvCgl7CgkJVGVtcExuZyA+Pj0gNzsKCQlWYWxTaXplICsrOwoJfSB3aGlsZShUZW1wTG5nKTsKCglWYWxEYXRhID0gJkJ1ZmZlclsqUG9zXTsKCUN1clBvcyA9IFZhbFNpemU7CglUZW1wTG5nID0gVmFsdWU7CglkbwoJewoJCUN1clBvcyAtLTsKCQlWYWxEYXRhW0N1clBvc10gPSAweDgwIHwgKFRlbXBMbmcgJiAweDdGKTsKCQlUZW1wTG5nID4+PSA3OwoJfSB3aGlsZShUZW1wTG5nKTsKCVZhbERhdGFbVmFsU2l6ZSAtIDFdICY9IDB4N0Y7CgoJKlBvcyArPSBWYWxTaXplOwoKCXJldHVybjsKfQoKSU5MSU5FIFVJTlQxNiBSZWFkTEUxNihjb25zdCBVSU5UOCogRGF0YSkKewoJcmV0dXJuIChEYXRhWzB4MDBdIDw8IDApIHwgKERhdGFbMHgwMV0gPDwgOCk7Cn0KCklOTElORSB2b2lkIFdyaXRlQkUxNihVSU5UOCogQnVmZmVyLCBVSU5UMTYgVmFsdWUpCnsKCUJ1ZmZlclsweDAwXSA9IChWYWx1ZSAmIDB4RkYwMCkgPj4gODsKCUJ1ZmZlclsweDAxXSA9IChWYWx1ZSAmIDB4MDBGRikgPj4gMDsKCglyZXR1cm47Cn0KCklOTElORSB2b2lkIFdyaXRlQkUzMihVSU5UOCogQnVmZmVyLCBVSU5UMzIgVmFsdWUpCnsKCUJ1ZmZlclsweDAwXSA9IChWYWx1ZSAmIDB4RkYwMDAwMDApID4+IDI0OwoJQnVmZmVyWzB4MDFdID0gKFZhbHVlICYgMHgwMEZGMDAwMCkgPj4gMTY7CglCdWZmZXJbMHgwMl0gPSAoVmFsdWUgJiAweDAwMDBGRjAwKSA+PiAgODsKCUJ1ZmZlclsweDAzXSA9IChWYWx1ZSAmIDB4MDAwMDAwRkYpID4+ICAwOwoKCXJldHVybjsKfQ==