This page looks best with JavaScript enabled

Reverse engineering CTK-2500 App Mode 1/3

 ·  ☕ 19 min read
    🏷️

Introduction

This post is an introductory post about diving into reversing code for connecting to Casio CTK-2500 using a 3.5MM jack in an attempt to upload songs to it using a PC. The Casio CTK-2500 is hereinafter referred to as keyboard or device interchangeably.

Background

I had a Casio CTK-2500 lying around so I thought I would tinker with it and explore additional features.
I play the keyboard now and then and was intrigued by functionality that I had never tried before Chordana play.

Chordana Play

Quick google search resulted in a Chordana app for Android and iOS which allows you to load a song into the system, the way to connect the Casio to program it was using a 3.5MM audio cable, which piqued my interest.

Found out that there’s no app for a PC to load the music using a PC. So I thought why not reverse engineer the app and create a command-line utility to load MIDI files into the keyboard using a PC.

if you read further you will find

  • A high-level analysis of the app in question
  • Reverse engineered code from Chordana app and native library that it uses.
  • Extracted region of interest in a codebase that might enable us to write a utility to utilize the 3.5MM jack on my PC to load songs into the keyboard.

Casio Control Panel

Tools of the trade

Tools that I will be using to reverse engineer the code base and figure out a region of interests

  • Brain
  • IDA Pro (With a decompiler) (To decompile and reverse native binary that’s used in the android application)
  • Online APK Decompiler - This converts APK into jar files that can be fed into JD-GUI
  • JD-GUI for viewing decompiled APK files. (JD-GUI requires JRE to function so you have to have it before moving forward.)

High-Level Analysis of Chordana App

Installing Chordana Play App on your Android/iOS device and playing around with it.

After using it for a while I came to the following conclusions

  • A predefined song can be loaded into the app or a new midi file can be loaded using the song menu
  • To transfer the song to Casio first I need to set up something called a keyboard link, To set up the keyboard link I need to put our device into APP mode, this can be done on CTK-2500 by pressing and holding the APP button. App button and then opening the app and turning on Keyboard Link as you can see in the above video.
    After that, you can click on Transfer song and begin to transfer the song to the device using audio in cable. fascinating.

Getting the APK and decompiling it

First I would search for the Chordana Play APK since Chordana Play is the only app that is capable of putting the device into app mode and load midi files into it.
A quick google search gets me to Chordana play APK Pure lands me here, I download the APK, upload it to Online APK Decompiler and get the resulting zip file back.
After loading the zip file in JD-GUI, I start by exploring the source code, if you have worked with Android before (or haven’t). A folder named sources will surely pique your interest.

let’s browse through some code that I found along the way

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

    public void linkOff() {
        if (nGetKeyboardLinkSw() == 1) { // check some param
            nPushKLRing(146); // push something somewhere?
            nPushKLRing(nGetChecksum(146)); // push checksum of the thing?
            nPushKLRing(240);
            nPushKLRing(nGetChecksum(240));
            try {
                TimeUnit.MICROSECONDS.sleep(50000); // Sleep?
            } catch (Exception unused) {
            }
        }
        nSetKeyboardLinkSw(0); // set something off
        stopKeyboardLinkTimer();
    }

    public void linkOn() {
        stopPreKeyboardLinkTimer(); // stop previous timers?
        nSetInLevelSetting(0); // 
        nRestoreHarmonyPartRange();
        nSetKeyboardLinkSw(1); // set keyboard link on?
        nPushKLRing(129); // send to some buffer
        nPushKLRing(nGetChecksum(129)); // send checksum?
        nPushKLRing(134);
        nPushKLRing((char) nGetKBDataType());
        nPushKLRing(nGetChecksum((char) (((char) nGetKBDataType()) + 129)));
        startKeyboardLinkTimer(); // start keep alive timer?
    }

Few other timer tasks that were fascinating.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    /* renamed from: jp.co.casio.chordanaplay.lib.Manager.KeyboardLinkManager$LevelSettingTimerTask */
    class LevelSettingTimerTask extends TimerTask {
        LevelSettingTimerTask() {
        }

        public void run() {
            if (KeyboardLinkManager.this.nGetInLevelSetting() == 1) {
                KeyboardLinkManager.this.nPushKLRing(254);
                KeyboardLinkManager.this.nPushKLRing(KeyboardLinkManager.this.nGetChecksum(254));
            }
        }
    }

    /* renamed from: jp.co.casio.chordanaplay.lib.Manager.KeyboardLinkManager$PreCasioLinkTask */
    class PreCasioLinkTask extends TimerTask {
        PreCasioLinkTask() {
        }

        public void run() {
            if (KeyboardLinkManager.this.nGetInLevelSetting() == 1) {
                KeyboardLinkManager.this.nPushKLRing(254);
                KeyboardLinkManager.this.nPushKLRing(KeyboardLinkManager.this.nGetChecksum(254));
            }
        }
    }

    /* renamed from: jp.co.casio.chordanaplay.lib.Manager.KeyboardLinkManager$TransferTimerTask */
    class TransferTimerTask extends TimerTask {
        TransferTimerTask() {
        }

        public void run() {
            if (KeyboardLinkManager.this.nGetTransRepeat() == 1 && KeyboardLinkManager.this.nGetInTransRepeat() >= 1 && KeyboardLinkManager.this.nGetKeyboardLinkNRTSw() == 0) {
                KeyboardLinkManager.this.transferSongData(true);
                KeyboardLinkManager.this.nSetInTransRepeat(KeyboardLinkManager.this.nGetInTransRepeat() - 1);
            }
            if (KeyboardLinkManager.this.nGetInPreTransfer() == 1) {
                KeyboardLinkManager.this.nPushKLRing(254);
                KeyboardLinkManager.this.nPushKLRing(KeyboardLinkManager.this.nGetChecksum(254));
            }
        }
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    public void startKeyboardLinkTimer() {
        if (this.timerForCasioLink == null) {
            this.timerForCasioLink = new Timer();
            this.mCasioLinkTask = new CasioLinkTimerTask();
            this.timerForCasioLink.scheduleAtFixedRate(this.mCasioLinkTask, 0, 100);
        }
    }
    class CasioLinkTimerTask extends TimerTask {
        private int icntt = 0;

        CasioLinkTimerTask() {
        }

        public void run() {
            if (KeyboardLinkManager.this.nGetKeyboardLinkSw() != 1 || KeyboardLinkManager.this.nGetSendActiveSensing() != 1) {
                return;
            }
            if (KeyboardLinkManager.this.nIsPlaying()) {
                int i = this.icntt;
                this.icntt = i + 1;
                if (i == 3) {
                    KeyboardLinkManager.this.nPushKLRing(129); // send some keep alive message to keep in app mode? 
                    KeyboardLinkManager.this.nPushKLRing(KeyboardLinkManager.this.nGetChecksum(129)); // send keep alive message.
                    this.icntt = 0;
                    return;
                }
                return;
            }
            KeyboardLinkManager.this.nPushKLRing(129);
            KeyboardLinkManager.this.nPushKLRing(KeyboardLinkManager.this.nGetChecksum(129));
        }
    }

song transfer routine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    public boolean transferScreenInitialize() {
        boolean nRhythmInitialize = nRhythmInitialize();
        if (nRhythmInitialize) {
            nSetInPreTransfer(1);
            startTransferTimer();
        }
        return nRhythmInitialize;
    }

    public void transferScreenTerminate() {
        nSetInPreTransfer(0);
        stopTransferTimer();
        if (nIsPlaying()) {
            playRhythm();
        }
        nResetPosition();
        MusicalStaffManager.getInstance().draw();
    }

    public void transferSongData(boolean z) {
        nSetInPreTransfer(0);
        int i = 3500;
        if (!z) {
            stopKeyboardLinkTimer();
            if (nGetDataWaveLengthType() != 2) {
                i = 1750;
            }
        }
        nPushKLRing(128);
        nPushKLRing(nGetChecksum(128));
        nSetKeyboardLinkNRTSw(1);
        nSetKeyboardLinkNRTpush(1);
        if (nGetTransRepeat() == 1 && !z) {
            nSetInTransRepeat(20);
        }
        for (long j = 0; j < ((long) nGetDataSize()); j++) {
            nPushKLRing(nGetEachSongData(j)); // push data to some buffer I guess
            try {
                TimeUnit.MICROSECONDS.sleep((long) i);
            } catch (Exception unused) {
            }
        }
        nSetKeyboardLinkNRTpush(0);
    }

Lot of native functions? Where could these be from?

I found a native library called libsssg.so that was being used and all of these native functions were defined inside that.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
    private native void nChangeDataType(int i);
    private native void nDownAppVolume();
    private native int nGetAppVolume();
    private native void nGetBeat(int[] iArr, int[] iArr2);
    private native int nGetCalibrationStep();
    /* access modifiers changed from: private */
    public native char nGetChecksum(char c);
    private native int nGetDataSize();
    private native int nGetDataWaveLengthType();
    private native int nGetDoModeCheck();
    private native char nGetEachSongData(long j);
    /* access modifiers changed from: private */
    public native int nGetInLevelSetting();
    /* access modifiers changed from: private */
    public native int nGetInPreTransfer();
    /* access modifiers changed from: private */
    public native int nGetInTransRepeat();
    private native int nGetKBDataType();
    /* access modifiers changed from: private */
    public native int nGetKeyboardLinkNRTSw();
    /* access modifiers changed from: private */
    public native int nGetKeyboardLinkSw();
    private native int nGetKeyboardLinkTestSw();
    private native int nGetLevelLimitOn();
    private native int nGetLevelLimitValue();
    private native int nGetRecommendedRhythmIndex();
    private native int nGetSNstart();
    /* acces modifiers changed from: private */
    public native int nGetSendActiveSensing();
    private native double nGetTargetOutputLevel();
    private native double nGetTempo()
    /* access modifiers changed from: private */
    public native int nGetTransRepeat();
    private native float nGetTransferTime();
    private native int nGetUseAppVolume();
    private native void nInitKeyboardLink();
    private native boolean nIsCurrentSong();
    /* access modifiers changed from: private */
    public native boolean nIsPlaying();
    private native void nPlayRhythm();
    /* access modifiers changed from: private */
    public native void nPlayThread();
    private native char nPopKLRing();
    /* access modifiers changed from: private */
    public native void nPushKLRing(char c);
    private native void nResetAppVolume();
    private native void nResetPosition();
    private native void nResetStepForSN();
    private native void nRestoreHarmonyPartRange();
    private native boolean nRhythmInitialize();
    private native void nSelectRhythm(int i);
    private native void nSetAmpofKeyboardLinkData(short s);
    private native void nSetAppVolume(int i);
    private native void nSetChannelForSN(int i);
    private native int nSetDeviceVolume(float f);
    private native void nSetFreqForSN(int i);
    private native int nSetInLevelSetting(int i);
    private native void nSetInPreTransfer(int i);
    /* access modifiers changed from: private */
    public native void nSetInTransRepeat(int i);
    private native void nSetKBDataType(int i);
    private native void nSetKeyboardLinkNRTSw(int i);
    private native void nSetKeyboardLinkNRTpush(int i);
    private native void nSetKeyboardLinkSw(int i);
    private native void nSetLevelLimitOn(int i);
    private native void nSetPolyLimit(boolean z);
    private native void nSetSNstart(int i);
    private native void nSetUseAppVolume(int i);
    private native void nSetWaveAmp(int i);
    private native void nTerminateTransferScreen();
    private native void nUpAppVolume();

Analysis of Native library used

Analysis of the C++ library used.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int __fastcall Java_jp_co_casio_chordanaplay_lib_Manager_KeyboardLinkManager_nPushKLRing(int a1, int a2, unsigned __int8 a3)
{
  return push_KLRing(a3); // lets see what push_KLRing does?
}

char __fastcall push_KLRing(char result)
{
  signed int v1; // r1@1

  v1 = iKLRingWptr + 1; // some ring pointer?
  ucKeyboardLinkRing[iKLRingWptr] = result; //ucKeyboardLinkRing looks like some data structure to store data
  iKLRingWptr = v1 % 1024; // oh this thing loops around, defenitely a ring buffer.
  return result;
}


int __fastcall Java_jp_co_casio_chordanaplay_lib_Manager_KeyboardLinkManager_nGetChecksum(int a1, int a2, unsigned __int8 a3)
{
  return get_Checksum(a3);
}

int __fastcall get_Checksum(int a1)
{
  return -a1 & 0x7F; // ok checksum is easy, just and with 0x7F
}

int __fastcall Java_jp_co_casio_chordanaplay_lib_Manager_KeyboardLinkManager_nGetEachSongData(int a1, int a2, int a3)
{
  return get_EachSongData(a3);
}

int __fastcall get_EachSongData(int a1)
{
  return ucBuffCmp[a1]; // what is this ucBuffCmp??
}

Few questions after going through the above code

  • What is ucKeyboardLinkRing??
  • What is ucBuffCmp??
  • What is nGetDataSize??
1
2
3
4
5
int Java_jp_co_casio_chordanaplay_lib_Manager_KeyboardLinkManager_nGetDataSize()
{
  return lDataSize; // oh interesting some variable, lDataSize, if I can find xrefs to these, things might get interesting. 
}

Chordana app ida
Chordana app ida

Interesting lDataSize is set inside rhyythmInitialize

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int rhythmInitialize()
{
  int v0; // r0@1
  float v6; // [sp+4h] [bp-24h]@1
  int v7; // [sp+8h] [bp-20h]@1
  int v8; // [sp+Ch] [bp-1Ch]@1

  v0 = getRecommendedRhythmIndex();
  set_RhythmSelect(v0);
  *(_DWORD *)(ply + 156) = 0;
  get_SongAttributeForKeaboard(currentSong, &v6, &v7, &v8);
  __asm
  {
    VLDR            S13, [SP,#0x28+var_24]
    VCVT.F64.S32    D7, S13
  }
  lDataSize = 0LL;
  __asm { VSTR            D7, [R3] }
  beatB = v7;
  beatBD = v8;
  proc_PolyLimit(currentSong);
  lDataSize = get_SongDataForKeaboard_v02(currentSong, &lUncompressedDataSize); // <---  See this is interesting, lets dig deeper into This code, I think I am on the right track.
  return proc_ResumePolyLimit(currentSong);
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
size_t __fastcall get_SongDataForKeaboard_v02(int a1, size_t *a2)
{
  int v2; // r7@1
  size_t *v3; // r8@1
  int v4; // r6@1
  void *v5; // r0@1
  int v6; // r6@1
  int v7; // r0@2
  char *v8; // r3@3
  int v9; // r10@3
  int v10; // r9@3
  int v11; // r0@3
  char *v12; // r10@3
  int v13; // r9@3
  size_t v14; // r7@3
  unsigned int v15; // r11@3
  char v16; // r0@3
  char v17; // r0@3
  _BYTE *v18; // r3@3
  signed int v19; // r3@5
  int v20; // r1@5
  size_t v21; // r10@8
  char v22; // r0@8
  char v23; // r0@8
  char v24; // r0@8
  char v25; // r0@8
  unsigned int v26; // r1@8
  _BYTE *v27; // r11@8
  unsigned int v28; // r0@8
  FILE *v29; // r8@8
  FILE *v30; // r4@10
  size_t result; // r0@12
  char s; // [sp+14h] [bp-42Ch]@12
  int v33; // [sp+414h] [bp-2Ch]@1

  v2 = a1;
  v3 = a2;
  v4 = *(_DWORD *)(a1 + 2972) / 5;
  v33 = _stack_chk_guard;
  v5 = memset(&ucBuffSrc, 0, 0x88B8u);
  iSongFKDataType = get_KBDataType(v5);
  ucBuffSrc = iSongFKDataType;
  unk_382B5D5 = v4;
  unk_382B5D2 = v4 >> 31;
  unk_382B5D3 = v4 >> 16;
  unk_382B5D1 = iBeatDenomiOfTime | 8 * iBeatOfTime;
  unk_382B5D4 = BYTE1(v4);
  v6 = set_RhythmControlForKeyboard(&unk_382B5D8, v2);
  unk_382B5D6 = BYTE1(v6);
  unk_382B5D7 = v6;
  if ( get_KBDataType(v6) )
    v7 = set_MelodyForKeyboard((char *)&ucBuffSrc + v6 + 10, v2, 1);
  else
    v7 = set_ChordForKeyboard((char *)&ucBuffSrc + v6 + 10);
  v8 = (char *)&ucBuffSrc + v6;
  v9 = v6 + v7;
  v10 = v7;
  v8[9] = v7;
  v8[8] = BYTE1(v7);
  v11 = set_MelodyForKeyboard((char *)&ucBuffSrc + v6 + v7 + 12, v2, 0);
  v12 = (char *)&ucBuffSrc + v9;
  v13 = v10 + v11;
  v12[11] = v11;
  v12[10] = BYTE1(v11);
  v14 = v13 + v6 + 12;
  v15 = dtcmp_compress(&ucBuffSrc, v14, &ucBuffCmp[28], 40000); // Interesting some compression function, but I see finally where ucBuffCmp is being populated.
  ucBuffCmp[0] = -128;
  v16 = get_Checksum(128);
  ucBuffCmp[6] = v15 & 0x7F;
  ucBuffCmp[3] = (v15 >> 21) & 0x7F;
  ucBuffCmp[4] = (v15 >> 14) & 0x7F;
  ucBuffCmp[5] = (v15 >> 7) & 0x7F;
  ucBuffCmp[2] = -126;
  ucBuffCmp[1] = v16;
  v17 = get_Checksum((v15 & 0x7F) - 126 + ((v15 >> 21) & 0x7F) + ((v15 >> 14) & 0x7F) + ((v15 >> 7) & 0x7F));
  ucBuffCmp[8] = -125;
  ucBuffCmp[9] = 8;
  v18 = &ucBuffCmp[9];
  ucBuffCmp[7] = v17;
  do
    (v18++)[1] = 32;
  while ( v18 != &ucBuffCmp[17] );
  v19 = 10;
  v20 = cSongTitleLCD;
  do
  {
    if ( !*(_BYTE *)(v20 + v19 - 10) )
      break;
    ucBuffCmp[v19] = *(_BYTE *)(v20 + v19 - 10);
    ++v19;
  }
  while ( v19 != 18 );
  v21 = v15 + 32;
  v22 = get_Checksum((unsigned __int8)(ucBuffCmp[9]
                                     + ucBuffCmp[8]
                                     + ucBuffCmp[10]
                                     + ucBuffCmp[11]
                                     + ucBuffCmp[12]
                                     + ucBuffCmp[13]
                                     + ucBuffCmp[14]
                                     + ucBuffCmp[15]
                                     + ucBuffCmp[16]
                                     + ucBuffCmp[17]));
  ucBuffCmp[19] = -123;
  ucBuffCmp[18] = v22;
  ucBuffCmp[20] = iRhythmSelect;
  v23 = get_Checksum((unsigned __int8)(iRhythmSelect - 123));
  ucBuffCmp[22] = -121;
  ucBuffCmp[21] = v23;
  ucBuffCmp[23] = (iBeatPerMinute[0] >> 7) & 0x7F;
  ucBuffCmp[24] = iBeatPerMinute[0] & 0x7F;
  v24 = get_Checksum((unsigned __int8)(ucBuffCmp[23] + (iBeatPerMinute[0] & 0x7F) - 121));
  ucBuffCmp[26] = -124;
  ucBuffCmp[25] = v24;
  v25 = get_Checksum(132);
  v26 = v15;
  v27 = &ucBuffCmp[v15];
  ucBuffCmp[27] = v25;
  v28 = get_crc32(&ucBuffCmp[28], v26);
  v27[29] = v28 >> 16;
  v27[31] = v28;
  v27[28] = BYTE3(v28);
  v27[30] = BYTE1(v28);
  *v3 = v14;
  v29 = fopen((const char *)&cSongDataDumpFileName1, (const char *)&unk_8CCD8);
  if ( v29 )
  {
    fwrite(&ucBuffSrc, 1u, v14, v29);
    fclose(v29);
  }
  v30 = fopen((const char *)&cSongDataDumpFileName2, (const char *)&unk_8CCD8);
  if ( v30 )
  {
    fwrite(ucBuffCmp, 1u, v21, v30);
    fclose(v30);
  }
  sprintf(&s, "* Song %s, All size (Compress) %ld, Not comp size %ld", cSongTitle, v21, v13 + 6);
  put_LogToFile(&s);
  result = v21;
  if ( v33 != _stack_chk_guard )
    _stack_chk_fail(v21);
  return result;
}

So finally I have some clue of where and what ucBuffCmp is used for. I am yet to dig deeper into this, so the next articles will dive deeper into what ucBuffCmp is and what is the function, but I guess it is some kind of compressed buffer that is sent over to the keyboard, looking from the code.

So I now know what ucBuffCmp(at least something about it) and what nGetDataSize is, lets find out what ucKeyboardLinkRing is used for?

Points of interest after going through the code

1
 v20 = cSongTitleLCD; // this is in get_SongDataForKeaboard_v02, so if I can manipulate cSongTitleLCD I can change what's displayed on the LCD that's good to know.

By finding xrefs on ucKeyboardLinkRing I stumbled upon this function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int set_keyboardlinkparam()
{
  void *v0; // r3@1
  int result; // r0@3
  _BYTE *v2; // r3@3

  v0 = &unk_3840BC7;
  do
  {
    *((_BYTE *)v0 + 1) = 0;
    v0 = (char *)v0 + 1;
  }
  while ( v0 != &unk_3840D2F );
  result = 0;
  iUnsentPos = -1;
  iUnsentLength = iNullLength + 10 * iBitLength;
  v2 = &unk_3844D7F;
  do
    (v2++)[1] = 0;
  while ( v2 != &ucKeyboardLinkRing[255] );
  iKLRingWptr = 0;
  iKLRingRptr = 0;
  return result;
}
// confirms this is a ring structure.

Doing xrefs on iKLRingWPtr landed me to

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// This looks like transfer routine.
int __fastcall proc_KeyboardLinkTransfer(int a1, int a2)
{
  int v2; // r9@1
  int v3; // r5@1
  int result; // r0@1
  int v5; // r0@8

  v2 = a1;
  v3 = a2;
  iDestinationPos = 0;
  result = set_zerodata();
  if ( iUnsentPos >= 0 && v3 > iUnsentLength )
    result = copy_Data(v2, v3);
  if ( v3 > iDestinationPos )
  {
    if ( iKLRingWptr == iKLRingRptr )
    {
LABEL_10:
      result = get_KeyboardLinkNRTSw();
      if ( result == 1 )
      {
        result = get_KeyboardLinkNRTpush();
        if ( !result )
          result = set_KeyboardLinkNRTSw();
      }
    }
    else
    {
      while ( 1 )
      {
        v5 = pop_KLRing();
        set_WaveOfOneByte(v5);
        iUnsentPos = 0;
        iUnsentLength = iNullLength + 10 * iBitLength;
        result = copy_Data(v2, v3 - iDestinationPos);
        if ( v3 <= iDestinationPos )
          break;
        if ( iKLRingWptr == iKLRingRptr )
          goto LABEL_10;
      }
    }
  }
  return result;
}

// xref on this landed me onto 
void __fastcall sub_61F14(int a1, int a2, int a3)
{
  int v3; // r5@1
  int v4; // r9@1
  int v5; // r7@1
  void *v6; // r8@2
  int v7; // r0@2
  int v8; // r4@3
  unsigned int *v9; // r5@3
  int v10; // r3@3
  unsigned int v11; // t1@5
  int v12; // r0@6

  v3 = a1;
  v4 = a2;
  v5 = a3;
  if ( get_SNstart() == 1 )
  {
    proc_OutputSignalForSN(v4, v5);
  }
  else
  {
    v6 = malloc(8 * v5);
    memset(v6, 0, 8 * v5);
    v7 = getwaveSxgm1sg(*(_DWORD *)(v3 + 4), v6, v5);
    if ( v5 > 0 )
    {
      v8 = (int)v6 - 8;
      v9 = (unsigned int *)v4;
      v10 = 0;
      do
      {
        v11 = *(_DWORD *)(v8 + 8);
        v8 += 8;
        ++v10;
        *v9 = (v11 >> 8) & 0xFFFF | (*(_DWORD *)(v8 + 4) >> 8 << 16);
        ++v9;
      }
      while ( v10 != v5 );
    }
    v12 = get_UseAppVolume(v7);
    if ( v12 == 1 )
    {
      v12 = get_KeyboardLinkSw(1);
      if ( v12 == 1 )
        v12 = proc_AppVolume2Audio(v4, v5);
    }
    if ( (get_KeyboardLinkSw(v12) == 1
       || get_KeyboardLinkTestSw() == 1
       || get_KeyboardLinkNRTSw() == 1
       || get_InLevelSetting() == 1
       || get_InPreTransfer() == 1)
      && get_HeadPhoneInserted() == 1 )
    {
      proc_KeyboardLinkTransfer(v4, v5);
      j_j_free(v6);
    }
    else
    {
      j_j_free(v6);
    }
  }
}

// and doing xref onto this landed me onto
_DWORD *__fastcall createSgComposition(int a1, int a2)
{
  int v2; // r5@1
  int v3; // r6@1
  _DWORD *v4; // r4@1

  v2 = a1;
  v3 = a2;
  v4 = malloc(8u);
  if ( v4 )
  {
    *v4 = 1092830567;
    v4[1] = createSxgm1sg(v2, v3);
    if ( createNativeaudio((int)sub_61F14, (int)v4) )
      startNativeaudio();
  }
  return v4;
}

// createNativeAudio looks interesting.
int __fastcall createNativeaudio(int a1, int a2)
{
  int v2; // r8@1
  int v3; // r9@6
  int v4; // r6@6
  int v5; // r2@6
  int v6; // r2@11
  int result; // r0@13
  int v8; // [sp+14h] [bp-6Ch]@6
  signed int v9; // [sp+18h] [bp-68h]@6
  int *v10; // [sp+1Ch] [bp-64h]@6
  int *v11; // [sp+20h] [bp-60h]@6
  int v12; // [sp+24h] [bp-5Ch]@6
  int v13; // [sp+28h] [bp-58h]@6
  int *v14; // [sp+2Ch] [bp-54h]@6
  int v15; // [sp+30h] [bp-50h]@6
  int v16; // [sp+34h] [bp-4Ch]@6
  int v17; // [sp+38h] [bp-48h]@6
  int v18; // [sp+3Ch] [bp-44h]@6
  signed int v19; // [sp+40h] [bp-40h]@6
  int v20; // [sp+44h] [bp-3Ch]@6
  signed int v21; // [sp+48h] [bp-38h]@6
  signed int v22; // [sp+4Ch] [bp-34h]@6
  signed int v23; // [sp+50h] [bp-30h]@6
  signed int v24; // [sp+54h] [bp-2Ch]@6
  signed int v25; // [sp+58h] [bp-28h]@6
  signed int v26; // [sp+5Ch] [bp-24h]@6

  v2 = a2;
  dword_D36B0 = a1;
  dword_D36B4 = a2;
  if ( slCreateEngine(&dword_D46B8, 0, 0)
    || (**(int (__cdecl ***)(_DWORD, _DWORD))dword_D46B8)(dword_D46B8, 0)
    || (*(int (__cdecl **)(int, _DWORD))(*(_DWORD *)dword_D46B8 + 12))(dword_D46B8, SL_IID_ENGINE)
    || (*(int (__cdecl **)(int, int *))(*(_DWORD *)dword_D46BC + 28))(dword_D46BC, &dword_D46C0)
    || (**(int (__cdecl ***)(_DWORD, _DWORD))dword_D46C0)(dword_D46C0, 0)
    || (v3 = SL_IID_BUFFERQUEUE,
        v4 = SL_IID_VOLUME,
        v8 = -2147481667,
        v9 = 2,
        v15 = 0,
        v10 = &v8,
        v12 = 4,
        v20 = 2,
        v21 = 2,
        v22 = 44100000,
        v23 = 16,
        v16 = SL_IID_BUFFERQUEUE,
        v17 = SL_IID_VOLUME,
        v24 = 16,
        v25 = 3,
        v26 = 2,
        v14 = &v12,
        v13 = dword_D46C0,
        v11 = &v20,
        v18 = 1,
        v19 = 1,
        (*(int (__cdecl **)(int, int *, int **))(*(_DWORD *)dword_D46BC + 8))(dword_D46BC, &dword_D46C4, &v10))
    || (**(int (__cdecl ***)(_DWORD, _DWORD, _DWORD))dword_D46C4)(dword_D46C4, 0, v5)
    || (*(int (__cdecl **)(int, _DWORD, int *))(*(_DWORD *)dword_D46C4 + 12))(dword_D46C4, SL_IID_PLAY, &dword_D46C8)
    || (*(int (__cdecl **)(int, int, int *))(*(_DWORD *)dword_D46C4 + 12))(dword_D46C4, v3, &dword_D46CC)
    || (*(int (__cdecl **)(int, int (*)(), _DWORD))(*(_DWORD *)dword_D46CC + 12))(dword_D46CC, sub_23B48, 0)
    || (*(int (__cdecl **)(_DWORD, int, int *))(*(_DWORD *)dword_D46C4 + 12))(dword_D46C4, v4, &dword_D46D0)
    || (**(int (__cdecl ***)(_DWORD, _DWORD, _DWORD))dword_D46C8)(dword_D46C8, 3, v6) )
  {
    result = 0;
  }
  else
  {
    result = v2;
  }
  return result;
}

// Looks like some sound device initialization routine.

Solutions to creating a library.

  • Reverse engineer whole code of libsssg.so and rewrite the required parts in C++
  • Use an ARM device(Phone, raspberry pi) to use the library as-is. (Still will require documenting most of the functions. easier than rewriting.)
Share on

NTAuthority
WRITTEN BY
NTAuthority
RE Enthusiast