santiontanon / castlemaster2-disassembly

Disassembly of the original 1990 Castle Master II: The Crypt ZX Spectrum game

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Some insights into how the SFX_data is used?

neuromancer opened this issue · comments

I'm slowly investigating how Freescape SFX sounds are produced in the ZX Spectrum releases, in order to re-implement them in ScummVM. Ghidra produced the following code for the first part of the play_SFX function from ZX Spectrum release of Dark Side (I have renamed some temporary variables and memory locations):

void play_SFX(INT8 id)

{
  byte bVar1;
  byte soundType;
  byte bVar2;
  char sound_size;
  ushort uVar3;
  char cVar5;
  undefined uVar6;
  char *sound_ptr;
  short sVar7;
  byte bVar9;
  UINT16 UVar8;
  undefined1 *SFT_temp_struct;
  byte abVar10 [3];
  char cVar4;
  
  disableMaskableInterrupts();
  uVar3 = (ushort)(byte)((id + -1) * '\x04');
  DAT_ram_bf67 = *(UINT16 *)(SFX_table + uVar3 + 1);
  SFX_temp_struct_ptr[0] = SFX_table[uVar3 + 3];
  SFT_temp_struct = SFX_temp_struct_ptr;
  soundType = SFX_data[(ushort)(byte)SFX_table[uVar3] * 4];
  sound_ptr = SFX_data + (ushort)(byte)SFX_table[uVar3] * 4 + 1;
  if ((soundType & 0x80) == 0) {
    DAT_ram_bf70 = DAT_ram_bf70 & 0xff00;
    original_sound_ptr = sound_ptr;
    DAT_ram_bf6f = soundType;
    while( true ) {
      while( true ) {
        sound_size = *sound_ptr;
        SFT_temp_struct[1] = sound_size;
        SFT_temp_struct[2] = sound_ptr[1];
        SFT_temp_struct[3] = sound_ptr[2];
        do {
          abVar10 = a_times_hl_signed(SFT_temp_struct[3],0xd0);
          a_hl_divided_by_de_signed(abVar10[2],abVar10._0_2_,DAT_ram_bf67);
          abVar10 = a_times_hl_signed('\a',DAT_ram_bf67);
          sVar7 = abVar10._0_2_ + -0x1e;
          if (sVar7 < 0) {
            sVar7 = 1;
          }
          play_SFX_beep((UINT8)((ushort)sVar7 >> 8),(UINT8)sVar7);
          uVar6 = 0;
          if ((SFT_temp_struct[2] & 0x80) != 0) {
            uVar6 = 0xff;
          }
          DAT_ram_bf67 = DAT_ram_bf67 + CONCAT11(uVar6,SFT_temp_struct[2]) & 0xfff;
          sound_size = sound_size + -1;
        } while (sound_size != '\0');
        SFT_temp_struct[5] = SFT_temp_struct[5] + '\x01';
        if (SFT_temp_struct[5] == SFT_temp_struct[4]) break;
        sound_ptr = original_sound_ptr + (ushort)(byte)SFT_temp_struct[5] * 3;
      }
      sound_size = *SFT_temp_struct;
      *SFT_temp_struct = sound_size + -1;
      sound_ptr = original_sound_ptr;
      if ((char)(sound_size + -1) == '\0') break;
      SFT_temp_struct[5] = '\0';
    }
  }
...

Any idea or discussion on exactly how this works will be very useful. The second part of the function contains some self-modifying code, but let's avoid that for now 😱

Oh wow, Ghidra even produced C code that represents the assembler code?! impressive! I'll take a look! I didn't go into detail into the SFX routine in my disassembly, as I was mostly interested on the 3d stuff. But I'll take a look and will report back what I learn!

I have some good news: two of the main branches in play_SFX are already reimplemented in scummvm, so they shouldn't be a problem (except perhaps for some little adjustment to get sounds matching in the emulation). The ones that I already have (with all the variables names that I needed) are here:

void play_SFX(INT8 id) {
  ...
  uVar2 = (ushort)(byte)((id + -1) * '\x04');
  uint16_sound_value = *(UINT16 *)(SFX_table + uVar2 + 1);
  SFX_temp_struct_ptr[0] = SFX_table[uVar2 + 3];
  SFX_temp_struct = SFX_temp_struct_ptr;
  soundType = SFX_data[(ushort)(byte)SFX_table[uVar2] * 4];
  sound_ptr = SFX_data + (ushort)(byte)SFX_table[uVar2] * 4 + 1;
  if ((soundType & 0x80) == 0) {
    SFX_temp_struct_ptr[4] = soundType;
    SFX_temp_struct_ptr._5_2_ = SFX_temp_struct_ptr._5_2_ & 0xff00;
    original_sound_ptr = sound_ptr;
    while( true ) {
      while( true ) {
        sound_size = *sound_ptr;
        SFX_temp_struct[1] = sound_size;
        SFX_temp_struct[2] = sound_ptr[1];
        SFX_temp_struct[3] = sound_ptr[2];
        do {
          abVar9 = a_times_hl_signed(SFX_temp_struct[3],0xd0);
          aUVar10 = a_hl_divided_by_de_signed(abVar9[2],abVar9._0_2_,uint16_sound_value);
          abVar9 = a_times_hl_signed('\a',uint16_sound_value);
          UVar5 = abVar9._0_2_ - 0x1e;
          if ((short)UVar5 < 0) {
            UVar5 = 1;
          }
          play_SFX_beep(aUVar10._0_2_ + 1,UVar5);
          uVar4 = 0;
          if ((SFX_temp_struct[2] & 0x80) != 0) {
            uVar4 = 0xff;
          }
          uint16_sound_value = uint16_sound_value + CONCAT11(uVar4,SFX_temp_struct[2]) & 0xfff;
          sound_size = sound_size + -1;
        } while (sound_size != '\0');
        SFX_temp_struct[5] = SFX_temp_struct[5] + '\x01';
        if (SFX_temp_struct[5] == SFX_temp_struct[4]) break;
        sound_ptr = original_sound_ptr + (ushort)(byte)SFX_temp_struct[5] * 3;
      }
      sound_size = *SFX_temp_struct;
      *SFX_temp_struct = sound_size + -1;
      sound_ptr = original_sound_ptr;
      if ((char)(sound_size + -1) == '\0') break;
      SFX_temp_struct[5] = '\0';
    }
  }
  else {
    SFX_temp_struct = SFX_temp_struct_ptr;
    repetitions = 7;
    do {
      SFX_temp_struct = SFX_temp_struct + 1;
      *SFX_temp_struct = *sound_ptr;
      sound_ptr = sound_ptr + 1;
      repetitions = repetitions + -1;
    } while (repetitions != 0);
    repetitions = SFX_temp_struct_ptr._1_2_;
    UVar5 = uint16_sound_value;
    sound_size = SFX_temp_struct_ptr[0];
    if ((soundType & 0x7f) == 1) {
      do {
        do {
          play_SFX_beep(SFX_temp_struct_ptr._3_2_,UVar5);
          repetitions = repetitions + -1;
          UVar5 = UVar5 + SFX_temp_struct_ptr._5_2_;
        } while ((byte)((byte)repetitions | (byte)((ushort)repetitions >> 8)) != 0);
        sound_size = sound_size + -1;
        repetitions = SFX_temp_struct_ptr._1_2_;
        UVar5 = uint16_sound_value;
      } while (sound_size != '\0');
    }

These cases are easy since the code calls play_SFX_beep which can be easily mapped to the generation of certain frecuency for some duration. However, the next two branches, either (soundType & 0x7f) == 2 and the else branch are still a mystery to me. Ghidra decompiles them as:

    else if ((soundType & 0x7f) == 2) {
      uRamc0de = 0x1041;
      uRamc0e0 = 0xfe;
      if ((SFX_temp_struct_ptr[4] != '\0') && (uRamc0de = 0x1043, SFX_temp_struct_ptr[4] != '\x0 2'))
      {
        uRamc0de = 0x545;
        uRamc0e0 = 0xfd20;
      }
                    /* WARNING: Read-only address (ram,0xc0de) is written */
                    /* WARNING: Read-only address (ram,0xc0e0) is written */
      repetitions = CONCAT11(SFX_temp_struct_ptr[0],SFX_temp_struct_ptr[1]);
      sVar6 = SFX_temp_struct_ptr._3_2_ << 8;
      ULA_PORT = DAT_ram_6d30;
      sound_size = SFX_temp_struct_ptr[2];
      if (id == '\x02') {
        ULA_PORT = DAT_ram_6d31;
      }
      do {
        cVar3 = sound_size;
        do {
          cVar3 = cVar3 + -1;
        } while (cVar3 != '\0');
        bVar8 = (byte)((ushort)sVar6 >> 8);
        soundType = bVar8;
        do {
          soundType = soundType - 1;
        } while (soundType != 0);
        ULA_PORT = (ULA_PORT | 0x18) & 0xf;
        repetitions = repetitions + -1;
        sound_size = SFX_temp_struct_ptr[5] + sound_size;
        sVar6 = (ushort)bVar8 << 8;
      } while (repetitions != 0);
    }
    else {
      do {
        soundType = 0;
        uVar2 = SFX_temp_struct_ptr._1_2_;
        do {
          repetitions = (((ushort)soundType * 0x100 + (ushort)soundType * -2) -
                        (ushort)((ushort)soundType * 0x100 < (ushort)soundType)) + (uVar2 & 0xff) ;
          bVar8 = (byte)repetitions;
          bVar7 = (byte)((ushort)repetitions >> 8);
          bVar1 = bVar8 - bVar7;
          soundType = bVar1;
          if (bVar7 <= bVar8) {
            bVar1 = bVar1 - 1;
            soundType = bVar1;
          }
          do {
            bVar1 = bVar1 - 1;
          } while (bVar1 != 0);
          cVar3 = (char)(uVar2 >> 8) + -1;
          uVar2 = CONCAT11(cVar3,(char)uVar2);
        } while (cVar3 != '\0');
        sound_size = sound_size + -1;
      } while (sound_size != '\0');
      ULA_PORT = DAT_ram_6d30;
    }
  }

However it seems that the decompiled code is incorrect!. Using the fuse debug we can easily see that the ULA_PORT is written with different values (e.g. sounds on and off) while here they look to be only written with a constant value, which look very strange. So any discussion on these will be very useful, in particular, it will be essential to know the frequency and duration of each sound used, so this can be replicated using the speaker emulator.