Have you ever wanted to open the main menu, and let the user return to the exact state they were in just before opening it?

Well, now you can!

Here's the function:

Code:
void SaveAndOpenMainMenu(void) {
  int addr;

  // get the address of the syscall table in it
  addr = *(unsigned int *)0x8002007C;

  if (addr < (int)0x80020070)
    return;
  if (addr >= (int)0x81000000)
    return;

  // get the pointer to syscall 1E58 - SwitchToMainMenu
  addr += 0x1E58 * 4;
  if (addr < (int)0x80020070)
    return;
  if (addr >= (int)0x81000000)
    return;

  addr = *(unsigned int *)addr;
  if (addr < (int)0x80020070)
    return;
  if (addr >= (int)0x81000000)
    return;

  // Now addr has the address of the first operation in %1e58

  // Run up to 150 times (300/2). OS 3.60's is 59 instructions, so this should
  // be plenty, but will let it stop if nothing is found
  for (unsigned short *currentAddr = (unsigned short *)addr;
       (unsigned int)currentAddr < ((unsigned int)addr + 300); currentAddr++) {
    // MOV.L GetkeyToMainFunctionReturn Flag, r14
    if (*(unsigned char *)currentAddr != 0xDE)
      continue;

    // MOV #3, 2
    if (*(currentAddr + 1) != 0xE203)
      continue;

    // BSR <SaveAndOpenMainMenu>
    if ((*(unsigned char *)(currentAddr + 2) & 0xF0) != 0xB0)
      continue;

    // MOV.B r2, @r14
    if (*(currentAddr + 3) != 0x2E20)
      continue;

    // BRA <some addr>
    if ((*(unsigned char *)(currentAddr + 4) & 0xF0) != 0xA0)
      continue;

    // NOP
    if (*(currentAddr + 5) != 0x0009)
      continue;

    unsigned short branchInstruction = *(currentAddr + 2);

    // Clear first 4 bits (BSR identifier)
    branchInstruction <<= 4;
    branchInstruction >>= 4;

    // branchInstruction is now the displacement of BSR

    // Create typedef so we can cast the pointer
    typedef void (*voidFunc)(void);

    // JMP to disp*2 + PC + 4
    ((voidFunc)((unsigned int)branchInstruction * 2 +
                (unsigned int)currentAddr + 4 + 4))();

    return;
  }
}


Running this function should reliably open the main menu and allow you to access other apps, whether or not GetKey to main function return is enabled. If the user does not open a different app, but chooses to re-open the one it was previously in, the function will exit and execution continues.

How I found it:
By reverse-engineering (disassembling) the GetKey, GetKeyWait, and SpecialMatrixcodeProcessing syscalls, I have found the OS' internal method of opening the main menu.
A function which I call 'SaveAndOpenMainMenu' is called, which saves state information such as the VRAM, input mode, and frame colour, among other things, then runs another function (which I dub 'OpenMainMenu') and reloads the saved information. Obviously, OpenMainMenu opens the main menu.

Unfortunately, there is no way to access SaveAndOpenMainMenu or OpenMainMenu directly through syscalls. The closest to this is syscall 0x1e58, which does some currently unknown stuff, and then starts an infinite loop, calling SaveAndOpenMainMenu.

This is not very useful as there are many situations where you want to continue execution after opening the menu (not having it infinitely open). It also seems to crash when run on the calculator - this is probably due to the unknown stuff beforehand.

With all of this in mind, I created a function which looks at the assembly of syscall 0x1e58, finds the pointer to SaveAndOpenMainMenu, and runs the function! This works on both the fx-CG 20 and 50 emulators, as well as my OS 3.60 fx-CG 50 AU physical calculator. The function should work on all OS versions, but I don't have access to OS 1.00/2.00 emulators or physical calculators to test it on.

If you want more details, check out my Planete Casio bible folder. In the Syscalls/ folder, you can see the C equivalent of over 110 syscalls/subroutines, including the ones mentioned above. Documenting these and many others are Disassembly.ods/Disassembly.pdf, a spreadsheet and PDF providing the signature of, and information about, many syscalls and other stuff. For example, if you want to know how the called function works, check out SaveAndOpenMainMenu.c and related files.

If you have any questions, feel free to ask.

Thanks for reading,
dr-carlos.
Excellent! When I mentioned dynamic disassembly I didn't realize you'd already done it!


Code:
  // get the pointer to the syscall table
  addr = *(unsigned char *)0x80020071; // get displacement

  addr++;
  addr *= 4;
  addr += 0x80020070;
  addr = *(unsigned int *)addr;

This part isn't strictly needed, as the syscall table address is always located at 0x8002007c. That short section at 0x80020070 is written in assembler (and hasn't changed in 20 years).

I should both play with it and document the addresses for every OS version. This is really useful for me, thanks a ton!
Lephe wrote:
Excellent! When I mentioned dynamic disassembly I didn't realize you'd already done it!


Code:
  // get the pointer to the syscall table
  addr = *(unsigned char *)0x80020071; // get displacement

  addr++;
  addr *= 4;
  addr += 0x80020070;
  addr = *(unsigned int *)addr;

This part isn't strictly needed, as the syscall table address is always located at 0x8002007c. That short section at 0x80020070 is written in assembler (and hasn't changed in 20 years).

I should both play with it and document the addresses for every OS version. This is really useful for me, thanks a ton!


Thanks! You may notice that SaveAndOpenMainMenu is just a rewritten version of simLo's SetGetkeyToMainFunctionReturnFlag. I have tested using 0x8002007C and changed it in my folder and main post.
This broke on OS 3.80, but I have a fix.

Change

Code:

    // BSR <SaveAndOpenMainMenu>
    if (*(unsigned char *)(currentAddr + 2) != 0xB5)
      continue;

to

Code:

    // BSR <SaveAndOpenMainMenu>
    if ((*(unsigned char *)(currentAddr + 2) & 0xF0) != 0xB0)
      continue;


This isn't needed for 3.80, but if you want to be future proof you can also change

Code:

    // BRA <some addr>
    if (*(currentAddr + 4) != 0xAFFB)
      continue;

to

Code:

    // BRA <some addr>
    if ((*(unsigned char *)(currentAddr + 4) & 0xF0) != 0xA0)
      continue;


Final code:

Code:
void SaveAndOpenMainMenu(void) {
  int addr;

  // get the address of the syscall table in it
  addr = *(unsigned int *)0x8002007C;

  if (addr < (int)0x80020070)
    return;
  if (addr >= (int)0x81000000)
    return;

  // get the pointer to syscall 1E58 - SwitchToMainMenu
  addr += 0x1E58 * 4;
  if (addr < (int)0x80020070)
    return;
  if (addr >= (int)0x81000000)
    return;

  addr = *(unsigned int *)addr;
  if (addr < (int)0x80020070)
    return;
  if (addr >= (int)0x81000000)
    return;

  // Now addr has the address of the first operation in %1e58

  // Run up to 150 times (300/2). OS 3.60's is 59 instructions, so this should
  // be plenty, but will let it stop if nothing is found
  for (unsigned short *currentAddr = (unsigned short *)addr;
       (unsigned int)currentAddr < ((unsigned int)addr + 300); currentAddr++) {
    // MOV.L GetkeyToMainFunctionReturn Flag, r14
    if (*(unsigned char *)currentAddr != 0xDE)
      continue;

    // MOV #3, 2
    if (*(currentAddr + 1) != 0xE203)
      continue;

    // BSR <SaveAndOpenMainMenu>
    if ((*(unsigned char *)(currentAddr + 2) & 0xF0) != 0xB0)
      continue;

    // MOV.B r2, @r14
    if (*(currentAddr + 3) != 0x2E20)
      continue;

    // BRA <some addr>
    if ((*(unsigned char *)(currentAddr + 4) & 0xF0) != 0xA0)
      continue;

    // NOP
    if (*(currentAddr + 5) != 0x0009)
      continue;

    unsigned short branchInstruction = *(currentAddr + 2);

    // Clear first 4 bits (BSR identifier)
    branchInstruction <<= 4;
    branchInstruction >>= 4;

    // branchInstruction is now the displacement of BSR

    // Create typedef so we can cast the pointer
    typedef void (*voidFunc)(void);

    // JMP to disp*2 + PC + 4
    ((voidFunc)((unsigned int)branchInstruction * 2 +
                (unsigned int)currentAddr + 4 + 4))();

    return;
  }
}
Looks great, thanks!
I've updated the OP.
I've noticed that syscall 1E5A calls that syscall when the first parameter is 4:


Code:

undefined4 syscall_1E5A(undefined4 param_1)

{
  undefined uVar1;
  undefined4 uVar2;
  int iVar3;
  undefined auStack_18 [16];
  undefined4 local_8;
 
  local_8 = param_1;
  switch(param_1) {
  case 2:
    syscall_08E2();
    FUN_80375906();
    break;
  case 3:
    syscall_08E2();
    FUN_803759aa();
    break;
  case 4:
    SaveAndOpenMainMenu();
    uVar1 = DAT_8c0a3042;
    DAT_8c0a3041 = uVar1;
    memset(auStack_18,0,0x10);
    GetAppName(auStack_18);
    uVar2 = MB_ByteCount("@E-CON4");
    iVar3 = memcmp3("@E-CON4",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    uVar2 = MB_ByteCount("@E-CON5");
    iVar3 = memcmp3("@E-CON5",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    LoadRegisters((register_info *)&DAT_8c0a2ff0,5);
    break;
  case 6:
    DAT_8c0a3040 = 0;
    FUN_8037895c();
    break;
  case 8:
    DAT_8c0a3040 = 0;
    FUN_80376254();
    LoadRegisters((register_info *)&DAT_8c0a2ff0,9);
    break;
  case 9:
    iVar3 = GetKeyboardInputMode();
    if (iVar3 == 1) {
      Keyboard_PutGetKey(0x7536,0);
    }
  case 5:
  case 7:
    DAT_8c0a3040 = 1;
    break;
  case 10:
    DAT_8c0a3040 = 1;
    FUN_803772de();
    LoadRegisters((register_info *)&DAT_8c0a2fa0,0xb);
    break;
  case 0xb:
    DAT_8c0a3040 = 0;
  }
  return local_8;
}


I'll look into calling it via that, hopefully the part after doesn't cause problems
Heath wrote:
I've noticed that syscall 1E5A calls that syscall when the first parameter is 4:


Code:

undefined4 syscall_1E5A(undefined4 param_1)

{
  undefined uVar1;
  undefined4 uVar2;
  int iVar3;
  undefined auStack_18 [16];
  undefined4 local_8;
 
  local_8 = param_1;
  switch(param_1) {
  case 2:
    syscall_08E2();
    FUN_80375906();
    break;
  case 3:
    syscall_08E2();
    FUN_803759aa();
    break;
  case 4:
    SaveAndOpenMainMenu();
    uVar1 = DAT_8c0a3042;
    DAT_8c0a3041 = uVar1;
    memset(auStack_18,0,0x10);
    GetAppName(auStack_18);
    uVar2 = MB_ByteCount("@E-CON4");
    iVar3 = memcmp3("@E-CON4",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    uVar2 = MB_ByteCount("@E-CON5");
    iVar3 = memcmp3("@E-CON5",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    LoadRegisters((register_info *)&DAT_8c0a2ff0,5);
    break;
  case 6:
    DAT_8c0a3040 = 0;
    FUN_8037895c();
    break;
  case 8:
    DAT_8c0a3040 = 0;
    FUN_80376254();
    LoadRegisters((register_info *)&DAT_8c0a2ff0,9);
    break;
  case 9:
    iVar3 = GetKeyboardInputMode();
    if (iVar3 == 1) {
      Keyboard_PutGetKey(0x7536,0);
    }
  case 5:
  case 7:
    DAT_8c0a3040 = 1;
    break;
  case 10:
    DAT_8c0a3040 = 1;
    FUN_803772de();
    LoadRegisters((register_info *)&DAT_8c0a2fa0,0xb);
    break;
  case 0xb:
    DAT_8c0a3040 = 0;
  }
  return local_8;
}


I'll look into calling it via that, hopefully the part after doesn't cause problems


Interesting - this must be new in OS 3.80, as I've never came across it and the disassembly is totally different in OS 3.60 and OS 3.70.
dr-carlos wrote:
Heath wrote:
I've noticed that syscall 1E5A calls that syscall when the first parameter is 4:


Code:

undefined4 syscall_1E5A(undefined4 param_1)

{
  undefined uVar1;
  undefined4 uVar2;
  int iVar3;
  undefined auStack_18 [16];
  undefined4 local_8;
 
  local_8 = param_1;
  switch(param_1) {
  case 2:
    syscall_08E2();
    FUN_80375906();
    break;
  case 3:
    syscall_08E2();
    FUN_803759aa();
    break;
  case 4:
    SaveAndOpenMainMenu();
    uVar1 = DAT_8c0a3042;
    DAT_8c0a3041 = uVar1;
    memset(auStack_18,0,0x10);
    GetAppName(auStack_18);
    uVar2 = MB_ByteCount("@E-CON4");
    iVar3 = memcmp3("@E-CON4",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    uVar2 = MB_ByteCount("@E-CON5");
    iVar3 = memcmp3("@E-CON5",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    LoadRegisters((register_info *)&DAT_8c0a2ff0,5);
    break;
  case 6:
    DAT_8c0a3040 = 0;
    FUN_8037895c();
    break;
  case 8:
    DAT_8c0a3040 = 0;
    FUN_80376254();
    LoadRegisters((register_info *)&DAT_8c0a2ff0,9);
    break;
  case 9:
    iVar3 = GetKeyboardInputMode();
    if (iVar3 == 1) {
      Keyboard_PutGetKey(0x7536,0);
    }
  case 5:
  case 7:
    DAT_8c0a3040 = 1;
    break;
  case 10:
    DAT_8c0a3040 = 1;
    FUN_803772de();
    LoadRegisters((register_info *)&DAT_8c0a2fa0,0xb);
    break;
  case 0xb:
    DAT_8c0a3040 = 0;
  }
  return local_8;
}


I'll look into calling it via that, hopefully the part after doesn't cause problems


Interesting - this must be new in OS 3.80, as I've never came across it and the disassembly is totally different in OS 3.60 and OS 3.70.


I think I'm actually decompilimg version 3.60?
Heath wrote:
dr-carlos wrote:
Interesting - this must be new in OS 3.80, as I've never came across it and the disassembly is totally different in OS 3.60 and OS 3.70.


I think I'm actually decompilimg version 3.60?


Ah, sorry, you're right. Just looked at the disassembly again and it's using BRAF to branch to a different address based on the first parameter, so the individual switch cases don't show up.
Heath wrote:
I've noticed that syscall 1E5A calls that syscall when the first parameter is 4:


Code:

undefined4 syscall_1E5A(undefined4 param_1)

{
  undefined uVar1;
  undefined4 uVar2;
  int iVar3;
  undefined auStack_18 [16];
  undefined4 local_8;
 
  local_8 = param_1;
  switch(param_1) {
  case 2:
    syscall_08E2();
    FUN_80375906();
    break;
  case 3:
    syscall_08E2();
    FUN_803759aa();
    break;
  case 4:
    SaveAndOpenMainMenu();
    uVar1 = DAT_8c0a3042;
    DAT_8c0a3041 = uVar1;
    memset(auStack_18,0,0x10);
    GetAppName(auStack_18);
    uVar2 = MB_ByteCount("@E-CON4");
    iVar3 = memcmp3("@E-CON4",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    uVar2 = MB_ByteCount("@E-CON5");
    iVar3 = memcmp3("@E-CON5",auStack_18,uVar2);
    if (iVar3 == 0) {
      Keyboard_PutKeycode(5,9,0);
    }
    LoadRegisters((register_info *)&DAT_8c0a2ff0,5);
    break;
  case 6:
    DAT_8c0a3040 = 0;
    FUN_8037895c();
    break;
  case 8:
    DAT_8c0a3040 = 0;
    FUN_80376254();
    LoadRegisters((register_info *)&DAT_8c0a2ff0,9);
    break;
  case 9:
    iVar3 = GetKeyboardInputMode();
    if (iVar3 == 1) {
      Keyboard_PutGetKey(0x7536,0);
    }
  case 5:
  case 7:
    DAT_8c0a3040 = 1;
    break;
  case 10:
    DAT_8c0a3040 = 1;
    FUN_803772de();
    LoadRegisters((register_info *)&DAT_8c0a2fa0,0xb);
    break;
  case 0xb:
    DAT_8c0a3040 = 0;
  }
  return local_8;
}


I'll look into calling it via that, hopefully the part after doesn't cause problems


So I've tried calling it and it works, but when called a second time seems to crash. Let me know if you find anything otherwise.

I've also tried syscall 0x1E59 (which calls 0x1E5A) and this just infinitely opens the menu (i.e. it opens, and if the app is re-entered it just re-opens the menu), just like exiting the app.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement