Msd.Reader.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. namespace FF8
  6. {
  7. public static partial class Msd
  8. {
  9. public static class Reader
  10. {
  11. public static IReadOnlyList<String> FromStream(Stream input, System.Text.Encoding encoding)
  12. {
  13. if (input.Position != 0)
  14. throw new NotSupportedException($"Cannot read data from the middle of the stream. Position: {input.Position}/{input.Length}");
  15. Byte[] buff = new Byte[input.Position];
  16. return FromBytes(buff, encoding);
  17. }
  18. public static IReadOnlyList<String> FromBytes(Byte[] buff, System.Text.Encoding encoding)
  19. {
  20. List<String> monologues = new List<String>();
  21. Int32 bufferSize = buff.Length;
  22. if (bufferSize < 4)
  23. return monologues;
  24. unsafe
  25. {
  26. fixed (Byte* ptr = &buff[0])
  27. {
  28. SByte* textPtr = (SByte*)(ptr);
  29. ReadMessages(textPtr, bufferSize, monologues, encoding);
  30. }
  31. }
  32. return monologues;
  33. }
  34. private static unsafe void ReadMessages(SByte* textPtr, Int32 bufferSize, List<String> messages, System.Text.Encoding encoding)
  35. {
  36. Int32* offsets = (Int32*)textPtr;
  37. Int32 count = GetMessageNumber(bufferSize, offsets);
  38. if (count == 0)
  39. return;
  40. messages.Capacity = count;
  41. Int32 currentOffset = offsets[0];
  42. for (Int32 i = 1; i < count; i++)
  43. {
  44. Int32 nextOffset = offsets[i];
  45. if (nextOffset == currentOffset)
  46. {
  47. messages.Add(String.Empty);
  48. continue;
  49. }
  50. Int32 messageLength = nextOffset - currentOffset - 1;
  51. String message = ReadMessage(textPtr, currentOffset, messageLength, bufferSize, encoding);
  52. messages.Add(message);
  53. currentOffset = nextOffset;
  54. }
  55. Int32 lastMessageLength = bufferSize - currentOffset - 1;
  56. String lastMessage = ReadMessage(textPtr, currentOffset, lastMessageLength, bufferSize, encoding);
  57. messages.Add(lastMessage);
  58. }
  59. private static unsafe Int32 GetMessageNumber(Int32 bufferSize, Int32* offsets)
  60. {
  61. Int32 dataOffset = offsets[0];
  62. Int32 count = dataOffset / 4;
  63. if (dataOffset % 4 != 0)
  64. throw new InvalidDataException($"The offset to the beginning of the text data also determines the number of lines in the file and must be a multiple of 4. Occured: {dataOffset} mod 4 = {dataOffset % 4}");
  65. if (count < 0)
  66. throw new InvalidDataException($"Unexpected negative value occured: {dataOffset}. Expected positive offset to the text data.");
  67. if (dataOffset > bufferSize)
  68. throw new InvalidDataException($"Invalid data offset ({dataOffset}) is out of bounds ({bufferSize}).");
  69. return count;
  70. }
  71. private static unsafe String ReadMessage(SByte* textPtr, Int32 currentOffset, Int32 messageLength, Int32 bufferSize, System.Text.Encoding encoding)
  72. {
  73. if (currentOffset + messageLength > bufferSize)
  74. throw new InvalidDataException($"Invalid text info (Offset: {currentOffset}, Size: {messageLength}) is out of bounds ({bufferSize}).");
  75. String message = new String(textPtr, currentOffset, messageLength, encoding);
  76. var lastCharacter = textPtr[currentOffset + messageLength];
  77. if (lastCharacter != 0 /* {End} */ && lastCharacter != 2 /* {Line} */)
  78. throw new InvalidDataException($"Text must be a null-terminated string. Occured text (Offset: {currentOffset}, Size: {messageLength}): {message}");
  79. return message;
  80. }
  81. }
  82. }
  83. }