-1

I'm doing a bit manipulating routine to encode/decode mp3 files. However, I will need to encode them using C#, and decode them using Delphi XE 10. I wrote the code that encodes/decodes them, and it works when executed on the same platform (if I encode / decode in C#, the mp3 plays, and if I encode / decode in Delphi the mp3 plays) , but if I try to decode an mp3 that was encoded through C#, the mp3 doesn't play. When doing this process only with string, it worked, so it's probably when I tried to apply it to the MemoryStream. At the end, I've edited the post, and added the functions that I used the encode/decode only strings, and I can successfully encode in C#, and decode in Delphi. I changed the hash string, but it's similar to the one used in the samples

Below is the code in C#:

private void btnCodificarArquivos_Click(object sender, EventArgs e)
{
    Encoding ANSI = Encoding.GetEncoding(1252);
    byte[] hash = ANSI.GetBytes(@"laki#~~2p3fijo3ij881*2f-|-  a`asso`wpeofi#jdJD92jd9jdfjl2@38d8d");
    string directory;
    string originalExtension;
    string finalExtension;
    if (cbxCodificar.Checked)
    {
            originalExtension = "mp3";
            finalExtension = "mpc";
    }
    else
    {
            originalExtension = "mpc";
            finalExtension = "mp3";
    }
    directory = txbFrase.Text.Trim();
    if (!Directory.Exists(directory))
            throw new Exception("Directory does not exist => " + directory);
    var files = Directory.GetFiles(directory, "*." + originalExtension);
    foreach (var file in files)
    {
        using (FileStream streamFile = new FileStream(file, FileMode.Open, FileAccess.ReadWrite))
        {
            using (MemoryStream codedMemory = new MemoryStream())
            {
                streamFile.CopyTo(codedMemory);
                byte[] fileArray = codedMemory.ToArray();

                byte[] output = new byte[fileArray.Length];
                for (int i = 0; i < fileArray.Length; i++)
                    output[i] = (byte)(fileArray[i] ^ ~hash[(i + 1) % hash.Length]);
                using (MemoryStream memoryCodificado = new MemoryStream(output))
                {
                    string fileDestination = Path.ChangeExtension(file, "." + finalExtension);
                    if (File.Exists(fileDestination))
                        File.Delete(fileDestination);
                    using (FileStream arquivoFinal = new FileStream(fileDestination, FileMode.Create, FileAccess.ReadWrite))
                    {
                        memoryCodificado.Position = 0;
                        memoryCodificado.CopyTo(arquivoFinal);
                    }
                }
            }
        }
    }
}

}

Below is the code in Delphi:

procedure TfrmEncodeDecode.btnCodificarArquivoClick(Sender: TObject);
const
  Hash: string = 'laki#~~2p3fijo3ij881*2f-|-  a`asso`wpeofi#jdJD92jd9jdfjl2@38d8d';
var
  counter, i: Integer;
  bufferStream : ^Byte;
  streamSize: Int64;
  streamFile: TFileStream;
  listFiles: TStringDynArray;
  codedMemory: TMemoryStream;
  directory, originalExtension, finalExtension, fileDestination: string;
begin
  if (cbxConverter.Checked) then
  begin
    originalExtension := 'mp3';
    finalExtension := 'mpc';
  end
  else
  begin
    originalExtension := 'mpc';
    finalExtension := 'mp3';
  end;
  directory := String(edtFrase.Text).Trim;
  if (not TDirectory.Exists(directory, true)) then
    raise Exception.Create('Diretório não existe => ' + directory);
  listFiles := TDirectory.GetFiles(directory, '*.' + originalExtension);
  if (Length(listFiles) > 0) then
  begin
    for counter := 0 to Pred(Length(listFiles)) do
    begin
      streamFile := TFileStream.Create(listFiles[counter], fmOpenRead, fmShareExclusive);
      try
        codedMemory := TMemoryStream.Create;
        try
          codedMemory.CopyFrom(streamFile, streamFile.Size);
          fileDestination := TPath.ChangeExtension(listFiles[counter], '.' + finalExtension);
          if (TFile.Exists(fileDestination, true)) then
            TFile.Delete(fileDestination);
          streamSize := codedMemory.Size;
          bufferStream := codedMemory.Memory;
          i := 0;
          while (i < streamSize) do
          begin
            bufferStream^ := Byte(ord (bufferStream^) xor not (Ord (Hash[I mod Length (Hash) + 1])));
            Inc(bufferStream);
            Inc(i);
          end;
          codedMemory.SaveToFile(fileDestination);
        finally
          codedMemory.Free;
        end;
      finally
        streamFile.Free;
      end;
    end;
  end;
end;

EDIT

Complementing the question, below is the code for the string encode/decode. Doing this for the string, I can encode it in C# and decode it Delphi without a problem:

private string EncodeDecode(string str)
{
    Encoding ANSI = Encoding.GetEncoding(1252);
    byte[] hash = ANSI.GetBytes(@"laki#~~2p3fijo3ij881*2f-|-  a`asso`wpeofi#jdJD92jd9jdfjl2@38d8d");
    byte[] input = ANSI.GetBytes(str);
    byte[] output = new byte[input.Length];
    for (int i = 0; i < input.Length; i++)
        output[i] = (byte)(input[i] ^ ~hash[(i + 1) % hash.Length]);
    return ANSI.GetString(output);
}

and below is the Delphi code

function TfrmEncodeDecode.EncodeDecode(palavra: AnsiString): AnsiString;
const
  Hash: string = 'laki#~~2p3fijo3ij881*2f-|-  a`asso`wpeofi#jdJD92jd9jdfjl2@38d8d';
var
  I: Integer;
begin
  Result := palavra;
  for I := 1 to Length (Result) do
    Result[I] := AnsiChar (ord (Result[I]) xor not (Ord (Hash[I mod Length (Hash) + 1])));
end;

EDIT

After Ken`s comment, I changed the Delphi side so that the hash is converted to an array of bytes, as the code below shows, and now the mp3 actually does play when encoded in C# and decoded in Delphi, but the song sounds like a scratched record when it plays. Below is the new Delphi code:

procedure TfrmEncodeDecode.btnCodificarArquivoClick(Sender: TObject);
const
  Hash: string = 'laki#~~2p3fijo3ij881*2f-|-  a`asso`wpeofi#jdJD92jd9jdfjl2@38d8d';
var
  counter, i: Integer;
  bufferStream : ^Byte;
  streamSize: Int64;
  aHash: TArray<Byte>;
  streamFile: TFileStream;
  listFiles: TStringDynArray;
  codedMemory: TMemoryStream;
  directory, originalExtension, finalExtension, fileDestination: string;
begin
  aHash := TEncoding.ANSI.GetBytes(Hash);
  if (cbxConverter.Checked) then
  begin
    originalExtension := 'mp3';
    finalExtension := 'mpc';
  end
  else
  begin
    originalExtension := 'mpc';
    finalExtension := 'mp3';
  end;
  directory := String(edtFrase.Text).Trim;
  if (not TDirectory.Exists(directory, true)) then
    raise Exception.Create('Diretório não existe => ' + directory);
  listFiles := TDirectory.GetFiles(directory, '*.' + originalExtension);
  if (Length(listFiles) > 0) then
  begin
    for counter := 0 to Pred(Length(listFiles)) do
    begin
      streamFile := TFileStream.Create(listFiles[counter], fmOpenRead, fmShareExclusive);
      try
        codedMemory := TMemoryStream.Create;
        try
          codedMemory.CopyFrom(streamFile, streamFile.Size);
          fileDestination := TPath.ChangeExtension(listFiles[counter], '.' + finalExtension);
          if (TFile.Exists(fileDestination, true)) then
            TFile.Delete(fileDestination);
          streamSize := codedMemory.Size;
          bufferStream := codedMemory.Memory;
          i := 0;
          while (i < streamSize) do
          begin
            bufferStream^ := Byte(ord (bufferStream^) xor not (Ord (aHash[I mod Length (aHash) + 1])));
            Inc(bufferStream);
            Inc(i);
          end;
          codedMemory.SaveToFile(fileDestination);
        finally
          codedMemory.Free;
        end;
      finally
        streamFile.Free;
      end;
    end;
  end;
end;

I've checked each of the 62 bytes in the array, comparing the C# values with the Delphi values, and they match up perfectly, so it's not an encoding problem when converting the string to the Byte array

Pascal
  • 2,944
  • 7
  • 49
  • 78
  • 2
    *it doesn't work* is a totally meaningless problem description. How **specifically** does it *not work*? You need to [edit] your question and clearly state the problem you're having with the blocks of code you've posted. You've not explained anything yet. – Ken White Sep 28 '16 at 00:23
  • When I try to play the mp3 encoded in C# after decoded in Delphi , it doesn't play. It woks with simple strings, but now with files. If I encode / decode in Delphi, the mp3 plays. If I encode / decode in C#, the mp3 plays – Pascal Sep 28 '16 at 00:27
  • 1
    As I said, [edit] your question and **clearly explain the problem** in the question itself. Burying it in the comments isn't any more useful than saying it *doesn't work*. – Ken White Sep 28 '16 at 00:29
  • Ok. I just retyped what's on top of my question. I'll try to clarify it better. – Pascal Sep 28 '16 at 00:31
  • You're also aware that Delphi `String` is Unicode, which means it's not a single byte per character? Your `string` hash in Delphi is not the same as your `byte[]` hash in C#. They're not *similar to the same*, because one is an array of single bytes and the other is not. – Ken White Sep 28 '16 at 00:32
  • I've edited the question. No, I didn't realize that. I will check into that. Thank you – Pascal Sep 28 '16 at 00:37
  • @KenWhite I've posted the code for the string encode/decode that works across C#/Delphi. There, I use a AnsiChar. I just changed to Byte, due to how I'm handling the Stream. I hope that it's more clear. Thank you for your time – Pascal Sep 28 '16 at 01:29
  • It works when I only manipulate string. When I manipulate the Stream, it doesn't. The mp3 doesn't play. – Pascal Sep 28 '16 at 01:36
  • And as I said, you should not be using string or AnsiString. You should be working with bytes in all cases. MP3 data is not **string**. It's binary, and should be treated as such. (And even in your *working* string code, it's still wrong. You still have Hash declared as **string**, which as I've also said before is Unicode. Again, `Hash: string` is Unicode (SizeOf(Char) <> 1), while `Byte []` is always an array of single bytes. – Ken White Sep 28 '16 at 01:39
  • 1
    Learn to debug. Think about the problem. Both programs operate on the same input yet produce different output. Inspect each program at various points of execution and determine where they diverge. – David Heffernan Sep 28 '16 at 06:38
  • `aHash: TArray` uses zero based indexing. You are still using 1 based indexing as if it were a string. – Tom Brunberg Sep 28 '16 at 07:55
  • @DavidHeffernan I totally agree. That's why I've checked all the element in the array, and I've debugged the conversion of several. I posted on Stack because and wasn't unable to find examples of such routine in the Internet after a lot of research. I've being at this for days, and that's why I posted as well. Thank you for your feedback – Pascal Sep 28 '16 at 10:48
  • @TomBrunberg when I get the Length? Good point. I'll do some more debugging. Thank you for your feedback. I've being at it for a couple of days, and I'm on a deadline. That's why I seeked some help,since I don't usually do this type of routine. Thank you very much – Pascal Sep 28 '16 at 10:52
  • So at which point in the process do the two programs diverge? – David Heffernan Sep 28 '16 at 10:53
  • @DavidHeffernan I haven't being able to identify it. I've never done this type of byte manipulating in Delphi, and I thought it was something on that process. I'm heading to work, and I'll do some more tests there, based on Tom's feedback. Thank you for your time and feedback. – Pascal Sep 28 '16 at 10:56
  • 1
    What you need to do is to get each program to emit their intermediate variables, arrays to some output channel. e.g. a text file. Then compare them. Then see where there is divergence. Then work out why. This is pretty routine and mundane debugging. For sure we could work it out just by reading the code (I'm sure it wouldn't take me long) but it would be much better for you to learn these crucial debugging skills. – David Heffernan Sep 28 '16 at 11:00
  • @DavidHeffernan I'll try that. Thank you once more – Pascal Sep 28 '16 at 11:03
  • 1
    @DavidHeffernan thanks so much for your tip on how to debug this type of situation. It's much more noble to teach a man how to fish than to hand him a cooked fish! I was able to find the answer, which was as Tom pointed out. I'll post the correct code as the answer. Thanks again – Pascal Sep 29 '16 at 01:47
  • @TomBrunberg nailed it. Together with David`s tip, I was able to get it working. Thank you very much. – Pascal Sep 29 '16 at 01:47
  • @KenWhite with your tip, put together with Tom's and David's, it got me going so that I could find the problem, and learned how to debug this type of situation. Thank you again for your time and patience. – Pascal Sep 29 '16 at 01:49

1 Answers1

1

With the great help of Ken, Tom and David in the comments, I was able to debug it, and find the root cause in the code. Below is the C# and Delphi code for anyone that needs this type of bit handling information on MemoryStream's.

    private void btnCodificarArquivos_Click(object sender, EventArgs e)
    {
        Encoding ANSI = Encoding.GetEncoding(1252);
        byte[] hash = ANSI.GetBytes(@"laki#~~2p3fijo3ij881*2f-|-  a`asso`wpeofi#jdJD92jd9jdfjl2@38d8d");
        string directory;
        string originalExtension;
        string finalExtension;
        string debugFileName;
        TextWriter debugFile = null;
        if (cbxCodificar.Checked)
        {
            originalExtension = "mp3";
            finalExtension = "mpc";
        }
        else
        {
            originalExtension = "mpc";
            finalExtension = "mp3";
        }
        directory = txbFrase.Text.Trim();
        if (!Directory.Exists(directory))
            throw new Exception("Directory does not exist => " + directory);
        var files = Directory.GetFiles(directory, "*." + originalExtension);
        foreach (var file in files)
        {
            using (FileStream streamFile = new FileStream(file, FileMode.Open, FileAccess.ReadWrite))
            {
                using (MemoryStream codedMemory = new MemoryStream())
                {
                    debugFileName = Path.ChangeExtension(file, ".txt");
                    if (cbxDebugar.Checked)
                    {
                        debugFile = new StreamWriter(debugFileName);
                    }
                    streamFile.CopyTo(codedMemory);
                    byte[] fileArray = codedMemory.ToArray();

                    byte[] output = new byte[fileArray.Length];
                    for (int i = 0; i < fileArray.Length; i++)
                    {
                        output[i] = (byte)(fileArray[i] ^ ~hash[(i + 1) % hash.Length]);
                        if (cbxDebugar.Checked)
                            debugFile.WriteLine(output[i]);
                    }
                    using (MemoryStream memoryCodificado = new MemoryStream(output))
                    {
                        string fileDestination = Path.ChangeExtension(file, "." + finalExtension);
                        if (File.Exists(fileDestination))
                            File.Delete(fileDestination);
                        using (FileStream arquivoFinal = new FileStream(fileDestination, FileMode.Create, FileAccess.ReadWrite))
                        {
                            memoryCodificado.Position = 0;
                            memoryCodificado.CopyTo(arquivoFinal);
                        }
                    }
                    if (cbxDebugar.Checked)
                    {
                        debugFile.Flush();
                        debugFile.Close();
                        debugFile = null;
                    }
                }
            }
        }
    }

here is Delphi's code:

procedure TfrmEncodeDecode.btnCodificarArquivoClick(Sender: TObject);
const
  Hash: string = 'laki#~~2p3fijo3ij881*2f-|-  a`asso`wpeofi#jdJD92jd9jdfjl2@38d8d';
var
  counter, i: Integer;
  bufferStream : ^Byte;
  streamSize: Int64;
  aHash: TArray<Byte>;
  debugFile: TextFile;
  streamFile: TFileStream;
  listFiles: TStringDynArray;
  codedMemory: TMemoryStream;
  directory, originalExtension, finalExtension, fileDestination, debugFileName: string;
begin
  aHash := TEncoding.ANSI.GetBytes(Hash);
  if (cbxConverter.Checked) then
  begin
    originalExtension := 'mp3';
    finalExtension := 'mpc';
  end
  else
  begin
    originalExtension := 'mpc';
    finalExtension := 'mp3';
  end;
  directory := String(edtFrase.Text).Trim;
  if (not TDirectory.Exists(directory, true)) then
    raise Exception.Create('Diretório não existe => ' + directory);
  listFiles := TDirectory.GetFiles(directory, '*.' + originalExtension);
  if (Length(listFiles) > 0) then
  begin
    for counter := 0 to Pred(Length(listFiles)) do
    begin
      streamFile := TFileStream.Create(listFiles[counter], fmOpenRead, fmShareExclusive);
      try
        codedMemory := TMemoryStream.Create;
        try
          codedMemory.CopyFrom(streamFile, streamFile.Size);
          fileDestination := TPath.ChangeExtension(listFiles[counter], '.' + finalExtension);
          if (TFile.Exists(fileDestination, true)) then
            TFile.Delete(fileDestination);
          debugFileName := TPath.ChangeExtension(listFiles[counter], '.txt');
          if (TFile.Exists(debugFileName, true)) then
            TFile.Delete(debugFileName);
          streamSize := codedMemory.Size;
          bufferStream := codedMemory.Memory;
          i := 0;
          if (cbxDebugar.Checked) then
          begin
            AssignFile(debugFile, debugFileName);
            ReWrite(debugFile);
          end;
          try
            while (i < streamSize) do
            begin
              bufferStream^ := Byte(ord (bufferStream^) xor not (Ord (aHash[(I+1) mod Length (aHash)])));
              if (cbxDebugar.Checked) then
                 Writeln(debugFile, bufferStream^);
              Inc(bufferStream);
              Inc(i);
            end;
          finally
            if (cbxDebugar.Checked) then
            begin
              Flush(debugFile);
              CloseFile(debugFile);
            end;
          end;
          codedMemory.SaveToFile(fileDestination);
        finally
          codedMemory.Free;
        end;
      finally
        streamFile.Free;
      end;
    end;
  end;
end;
Pascal
  • 2,944
  • 7
  • 49
  • 78