Jsm.File.Reader.cs 4.6 KB

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