Jsm.File.Reader.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. using OpenVIII.Fields.Scripts.Instructions;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. namespace OpenVIII.Fields.Scripts
  6. {
  7. public static partial class Jsm
  8. {
  9. #region Classes
  10. public static partial class File
  11. {
  12. #region Methods
  13. public static List<GameObject> Read(byte[] data)
  14. {
  15. unsafe
  16. {
  17. fixed (byte* ptr = data)
  18. {
  19. if (ptr == null) return null;
  20. var header = (Header*)ptr;
  21. var areas = (Group*)(ptr + sizeof(Header));
  22. var doors = areas + header->CountAreas;
  23. var modules = doors + header->CountDoors;
  24. var objects = modules + header->CountModules;
  25. var end = objects + header->CountObjects;
  26. var scripts = (Script*)(ptr + header->ScriptsOffset);
  27. var operation = (Operation*)(ptr + header->OperationsOffset);
  28. var groupNumber = end - areas;
  29. var groups = new Group[groupNumber];
  30. for (var group = areas; group < end; group++)
  31. groups[--groupNumber] = *group;
  32. var gameObjects = new List<GameObject>(groups.Length);
  33. foreach (var group in groups.OrderBy(g => g.Label))
  34. {
  35. var objectScripts = new List<GameScript>(group.ScriptsCount + 1);
  36. for (var s = 0; s <= group.ScriptsCount; s++)
  37. {
  38. var scriptLabel = group.Label + s;
  39. var position = scripts->Position;
  40. scripts++;
  41. var count = (ushort)(scripts->Position - position);
  42. var scriptSegment = MakeScript(operation + position, count);
  43. objectScripts.Add(new GameScript(scriptLabel, scriptSegment));
  44. }
  45. gameObjects.Add(new GameObject(group.Label, objectScripts));
  46. }
  47. return gameObjects;
  48. }
  49. }
  50. }
  51. private static unsafe ExecutableSegment MakeScript(Operation* operation, ushort count)
  52. {
  53. var instructions = new List<JsmInstruction>(count / 2);
  54. var stack = new LabeledStack();
  55. var labelBuilder = new LabelBuilder(count);
  56. for (var i = 0; i < count; i++)
  57. {
  58. var opcode = operation->Opcode;
  59. var parameter = operation->Parameter;
  60. operation++;
  61. stack.CurrentLabel = i;
  62. var expression = Expression.TryMake(opcode, parameter, stack);
  63. if (expression != null)
  64. {
  65. stack.Push(expression);
  66. continue;
  67. }
  68. var instruction = JsmInstruction.TryMake(opcode, parameter, stack);
  69. if (instruction == null) throw new NotSupportedException(opcode.ToString());
  70. labelBuilder.TraceInstruction(i, stack.CurrentLabel, new IndexedInstruction(instructions.Count, instruction));
  71. instructions.Add(instruction);
  72. }
  73. if (stack.Count != 0)
  74. throw new InvalidProgramException("Stack unbalanced.");
  75. if (!(instructions.First() is LBL))
  76. throw new InvalidProgramException("Script must start with a label.");
  77. if (!(instructions.Last() is IRET))
  78. throw new InvalidProgramException("Script must end with a return.");
  79. // Switch from opcodes to instructions
  80. var labelIndices = labelBuilder.Commit();
  81. // Merge similar instructions
  82. instructions = InstructionMerger.Merge(instructions, labelIndices);
  83. // Combine instructions to logical blocks
  84. var controls = Control.Builder.Build(instructions);
  85. // Arrange instructions by segments and return root
  86. return Segment.Builder.Build(instructions, controls);
  87. }
  88. #endregion Methods
  89. }
  90. #endregion Classes
  91. }
  92. }