diff --git a/Shockky.SourceGeneration/InstructionGenerator.cs b/Shockky.SourceGeneration/InstructionGenerator.cs index 9a77072..3ec0556 100644 --- a/Shockky.SourceGeneration/InstructionGenerator.cs +++ b/Shockky.SourceGeneration/InstructionGenerator.cs @@ -71,7 +71,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) writer.WriteLine($"namespace {InstructionNamespace};"); writer.WriteLine(); - + WriteInstructionReadSyntax(writer, instructions); context.AddSource($"{InstructionNamespace}.IInstruction.Read.g.cs", writer.ToString()); diff --git a/Shockky/IO/Compression/ZLibShockwaveReader.cs b/Shockky/IO/Compression/ZLibShockwaveReader.cs index 42ef3bd..2a500a1 100644 --- a/Shockky/IO/Compression/ZLibShockwaveReader.cs +++ b/Shockky/IO/Compression/ZLibShockwaveReader.cs @@ -4,7 +4,7 @@ namespace Shockky.IO; -public sealed class ZLibShockwaveReader(Stream innerStream, bool isBigEndian, bool leaveOpen) +public sealed class ZLibShockwaveReader(Stream innerStream, bool isBigEndian, bool leaveOpen) : BinaryReader(new ZLibStream(innerStream, CompressionMode.Decompress, leaveOpen)) { private readonly bool _isBigEndian = isBigEndian; diff --git a/Shockky/IO/ShockwaveReader.cs b/Shockky/IO/ShockwaveReader.cs index 7cbe264..c8f96cb 100644 --- a/Shockky/IO/ShockwaveReader.cs +++ b/Shockky/IO/ShockwaveReader.cs @@ -7,6 +7,7 @@ using Shockky.Resources; using System.Buffers; +using System.Numerics; namespace Shockky.IO; @@ -236,7 +237,7 @@ public unsafe IResource ReadCompressedResource(AfterburnerMapEntry entry, Reader try { Span decompressedData = entry.DecompressedLength <= StackallocThreshold ? - stackalloc byte[StackallocThreshold] : + stackalloc byte[StackallocThreshold] : (rentedBuffer = ArrayPool.Shared.Rent(entry.DecompressedLength)); decompressedData = decompressedData.Slice(0, entry.DecompressedLength); diff --git a/Shockky/Lingo/Instructions/IInstruction.cs b/Shockky/Lingo/Instructions/IInstruction.cs index 187ea5c..d185d99 100644 --- a/Shockky/Lingo/Instructions/IInstruction.cs +++ b/Shockky/Lingo/Instructions/IInstruction.cs @@ -11,7 +11,7 @@ public partial interface IInstruction /// The value representing the instruction. /// OPCode OP { get; } - + /// /// The immediate operand encoded in the instruction. /// diff --git a/Shockky/Lingo/LingoFunction.cs b/Shockky/Lingo/LingoFunction.cs index f8d88b6..e0fafaa 100644 --- a/Shockky/Lingo/LingoFunction.cs +++ b/Shockky/Lingo/LingoFunction.cs @@ -16,11 +16,12 @@ public class LingoFunction : IShockwaveItem public LingoFunction() { + Bytecode = []; Arguments = new List(); Locals = new List(); - BytesPerLine = Array.Empty(); + BytesPerLine = []; } - public LingoFunction(ref ShockwaveReader input, ReaderContext context) + public LingoFunction(ref ShockwaveReader input) { EnvironmentIndex = input.ReadInt16LittleEndian(); EventKind = (LingoEventFlags)input.ReadInt16LittleEndian(); @@ -28,10 +29,10 @@ public LingoFunction(ref ShockwaveReader input, ReaderContext context) Bytecode = new byte[input.ReadInt32LittleEndian()]; int bytecodeOffset = input.ReadInt32LittleEndian(); - Arguments.Capacity = input.ReadInt16LittleEndian(); + Arguments = new List(input.ReadInt16LittleEndian()); int argumentsOffset = input.ReadInt32LittleEndian(); - Locals.Capacity = input.ReadInt16LittleEndian(); + Locals = new List(input.ReadInt16LittleEndian()); int localsOffset = input.ReadInt32LittleEndian(); short globalsCount = input.ReadInt16LittleEndian(); //v5 @@ -46,7 +47,7 @@ public LingoFunction(ref ShockwaveReader input, ReaderContext context) //if version > 0x800 StackHeight = input.ReadInt32LittleEndian(); - int handlerEndOffset = input.Position; + int bodyEndOffset = input.Position; input.Position = bytecodeOffset; input.ReadBytes(Bytecode); @@ -63,7 +64,13 @@ public LingoFunction(ref ShockwaveReader input, ReaderContext context) Locals.Add(input.ReadInt16LittleEndian()); } - throw new NotImplementedException(nameof(LingoFunction)); + input.Position = lineOffset; + for (int i = 0; i < BytesPerLine.Length; i++) + { + BytesPerLine[i] = input.ReadByte(); + } + + input.Position = bodyEndOffset; } public int GetBodySize(WriterOptions options) diff --git a/Shockky/Lingo/LingoLiteral.cs b/Shockky/Lingo/LingoLiteral.cs index 8b1ae62..1a549b4 100644 --- a/Shockky/Lingo/LingoLiteral.cs +++ b/Shockky/Lingo/LingoLiteral.cs @@ -5,8 +5,8 @@ namespace Shockky.Lingo; // TODO: Attempt to rewrite this again, not happy. public sealed class LingoLiteral : IShockwaveItem, IEquatable { - public VariantKind Kind { get; set; } - public object Value { get; set; } + public VariantKind Kind { get; } + public object Value { get; } public LingoLiteral(VariantKind kind, object value) { @@ -66,6 +66,6 @@ public static LingoLiteral Read(ref ShockwaveReader input, VariantKind entryKind public override int GetHashCode() { - throw new NotImplementedException(); + return HashCode.Combine(Kind, Value); } } diff --git a/Shockky/Resources/AfterBurner/FileGzipEmbeddedImage.cs b/Shockky/Resources/AfterBurner/FileGzipEmbeddedImage.cs index 71108f0..76fad2a 100644 --- a/Shockky/Resources/AfterBurner/FileGzipEmbeddedImage.cs +++ b/Shockky/Resources/AfterBurner/FileGzipEmbeddedImage.cs @@ -7,7 +7,7 @@ public static class FileGzipEmbeddedImage { // TODO: Tidy up more. public static IDictionary ReadResources( - ref ShockwaveReader input, ReaderContext context, + ref ShockwaveReader input, ReaderContext context, AfterburnerMap afterburnerMap, FileCompressionTypes compressionTypes) { int chunkStart = input.Position; @@ -22,8 +22,8 @@ public static IDictionary ReadResources( input.Position = chunkStart + entry.Offset; // TODO: Support more compression types: font maps, sounds. - IResource resource = compressionTypes.CompressionTypes[entry.CompressionTypeIndex].Id.Equals(ZLib.MoaId) ? - input.ReadCompressedResource(entry, context) + IResource resource = compressionTypes.CompressionTypes[entry.CompressionTypeIndex].Id.Equals(ZLib.MoaId) ? + input.ReadCompressedResource(entry, context) : IResource.Read(ref input, context, entry.Kind, entry.Length); resources.Add(index, resource); @@ -32,7 +32,7 @@ public static IDictionary ReadResources( } private static bool TryReadInitialLoadSegment( - ref ShockwaveReader input, ReaderContext context, + ref ShockwaveReader input, ReaderContext context, AfterburnerMap afterburnerMap, Dictionary resources) { // First entry in the AfterburnerMap must be ILS. diff --git a/Shockky/Resources/AfterBurner/FileVersion.cs b/Shockky/Resources/AfterBurner/FileVersion.cs index fe30bc3..32c4d66 100644 --- a/Shockky/Resources/AfterBurner/FileVersion.cs +++ b/Shockky/Resources/AfterBurner/FileVersion.cs @@ -9,7 +9,7 @@ public sealed class FileVersion : IResource, IShockwaveItem public DirectorVersion Version { get; set; } public string VersionString { get; set; } - public FileVersion(ref ShockwaveReader input, ReaderContext context) + public FileVersion(scoped ref ShockwaveReader input, ReaderContext context) { int versionMaybeTooForgot = input.Read7BitEncodedInt(); if (versionMaybeTooForgot < 0x401) return; diff --git a/Shockky/Resources/Cast/CastMemberMetadata.cs b/Shockky/Resources/Cast/CastMemberMetadata.cs index 53d21ee..b2041cc 100644 --- a/Shockky/Resources/Cast/CastMemberMetadata.cs +++ b/Shockky/Resources/Cast/CastMemberMetadata.cs @@ -151,7 +151,7 @@ private void ReadProperty(ref ShockwaveReader input, int index, int length) } } - + public int GetBodySize(WriterOptions options) { diff --git a/Shockky/Resources/Cast/CastMemberProperties.cs b/Shockky/Resources/Cast/CastMemberProperties.cs index eab3c32..827bdd5 100644 --- a/Shockky/Resources/Cast/CastMemberProperties.cs +++ b/Shockky/Resources/Cast/CastMemberProperties.cs @@ -37,7 +37,7 @@ private IMemberProperties ReadTypeProperties(ref ShockwaveReader input, ReaderCo MemberKind.Script => new ScriptCastProperties(ref input, context), MemberKind.RichText => new RichTextCastProperties(ref input), MemberKind.Transition => new TransitionCastProperties(ref input, context), - MemberKind.Xtra => new XtraCastProperties(ref input, context), + // TODO: MemberKind.Xtra => new XtraCastProperties(ref input, context), _ => new UnknownCastProperties(ref input, dataLength) }; diff --git a/Shockky/Resources/IResource.cs b/Shockky/Resources/IResource.cs index 565a2d5..5e6c3cd 100644 --- a/Shockky/Resources/IResource.cs +++ b/Shockky/Resources/IResource.cs @@ -4,14 +4,14 @@ namespace Shockky.Resources; public interface IResource { - abstract OsType Kind { get; } + OsType Kind { get; } - public static IResource Read(ref ShockwaveReader input, ReaderContext context) + public static IResource Read(scoped ref ShockwaveReader input, ReaderContext context) { var header = new ResourceHeader(ref input); return Read(ref input, context, header.Kind, header.Length); } - public static IResource Read(ref ShockwaveReader input, ReaderContext context, OsType kind, int length) + public static IResource Read(scoped ref ShockwaveReader input, ReaderContext context, OsType kind, int length) { ReadOnlySpan chunkSpan = input.ReadBytes(length); var chunkInput = new ShockwaveReader(chunkSpan, input.ReverseEndianness); diff --git a/Shockky/Resources/Lingo/LingoScript.cs b/Shockky/Resources/Lingo/LingoScript.cs index 2dfae71..30d277e 100644 --- a/Shockky/Resources/Lingo/LingoScript.cs +++ b/Shockky/Resources/Lingo/LingoScript.cs @@ -1,4 +1,7 @@ -using Shockky.IO; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; +using Shockky.IO; using Shockky.Lingo; namespace Shockky.Resources; @@ -32,8 +35,25 @@ public sealed class LingoScript : IShockwaveItem, IResource /// public LingoEventFlags EventFlags { get; set; } + public List EventHandlerIndices { get; set; } + public List Properties { get; set; } + public List Globals { get; set; } + + /// + /// Represents all lingo handlers in this script. + /// + public List Functions { get; set; } + + public List Literals { get; } + public LingoScript() - { } + { + EventHandlerIndices = new List(); + Properties = new List(); + Globals = new List(); + Functions = new List(); + Literals = new List(); + } public LingoScript(ref ShockwaveReader input) { input.ReverseEndianness = true; @@ -62,25 +82,67 @@ public LingoScript(ref ShockwaveReader input) FactoryNameIndex = input.ReadInt16LittleEndian(); - int eventHandlerCount = input.ReadInt16LittleEndian(); + EventHandlerIndices = new List(input.ReadInt16LittleEndian()); int eventHandlerIndexOffset = input.ReadInt32LittleEndian(); EventFlags = (LingoEventFlags)input.ReadInt32LittleEndian(); - short propertiesCount = input.ReadInt16LittleEndian(); + Properties = new List(input.ReadInt16LittleEndian()); int propertiesOffset = input.ReadInt32LittleEndian(); - short globalsCount = input.ReadInt16LittleEndian(); + Globals = new List(input.ReadInt16LittleEndian()); int globalsOffset = input.ReadInt32LittleEndian(); - short functionsCount = input.ReadInt16LittleEndian(); + Functions = new List(input.ReadInt16LittleEndian()); int functionsOffset = input.ReadInt32LittleEndian(); - short literalsCount = input.ReadInt16LittleEndian(); + Literals = new List(input.ReadInt16LittleEndian()); int literalsOffset = input.ReadInt32LittleEndian(); int literalDataLength = input.ReadInt32LittleEndian(); int literalDataOffset = input.ReadInt32LittleEndian(); + + input.Position = propertiesOffset; + for (int i = 0; i < Properties.Capacity; i++) + { + Properties.Add(input.ReadInt16LittleEndian()); + } + + input.Position = globalsOffset; + for (int i = 0; i < Globals.Capacity; i++) + { + Globals.Add(input.ReadInt16LittleEndian()); + } + + input.Position = functionsOffset; + for (int i = 0; i < Functions.Capacity; i++) + { + Functions.Add(new LingoFunction(ref input)); + } + + input.Position = literalsOffset; + var literalEntries = new (VariantKind Kind, int Offset)[Literals.Capacity]; // TODO: Stackalloc/ArrayPool + for (int i = 0; i < Literals.Capacity; i++) + { + literalEntries[i].Kind = (VariantKind)input.ReadInt32LittleEndian(); + literalEntries[i].Offset = input.ReadInt32LittleEndian(); + } + + input.Position = literalDataOffset; + for (int i = 0; i < Literals.Capacity; i++) + { + (VariantKind kind, int offset) = literalEntries[i]; + + Literals.Add(LingoLiteral.Read(ref input, kind, literalDataOffset + offset)); + + Debug.Assert(literalDataOffset + literalDataLength >= input.Position); + } + + input.Position = eventHandlerIndexOffset; + for (int i = 0; i < EventHandlerIndices.Capacity; i++) + { + EventHandlerIndices.Add(input.ReadInt16LittleEndian()); + } } public static int GetHeaderSize() diff --git a/Shockky/ShockwaveFile.cs b/Shockky/ShockwaveFile.cs index 509566b..7718987 100644 --- a/Shockky/ShockwaveFile.cs +++ b/Shockky/ShockwaveFile.cs @@ -17,22 +17,25 @@ public ShockwaveFile() Resources = new Dictionary(); } - public void Load(ReadOnlySpan data) + public static ShockwaveFile Read(string path) => Read(File.ReadAllBytes(path)); + public static ShockwaveFile Read(ReadOnlySpan data) { + var file = new ShockwaveFile(); var input = new ShockwaveReader(data); - Metadata = new FileMetadata(ref input); - input.ReverseEndianness = Metadata.IsBigEndian; - if (Metadata.Codec is CodecKind.FGDM or CodecKind.FGDC) + file.Metadata = new FileMetadata(ref input); + input.ReverseEndianness = file.Metadata.IsBigEndian; + + if (file.Metadata.Codec is CodecKind.FGDM or CodecKind.FGDC) { if (IResource.Read(ref input, default) is not FileVersion fileVersion) throw new InvalidDataException(); - + ReaderContext readerContext = new(fileVersion.Version); if (IResource.Read(ref input, readerContext) is not FileCompressionTypes compressionTypes) throw new InvalidDataException(); - + if (IResource.Read(ref input, readerContext) is not AfterburnerMap afterburnerMap) throw new InvalidDataException(); @@ -41,9 +44,9 @@ public void Load(ReadOnlySpan data) if (fgeiHeader.Kind is not OsType.FGEI) throw new InvalidDataException(); - Resources = FileGzipEmbeddedImage.ReadResources(ref input, readerContext, afterburnerMap, compressionTypes); + file.Resources = FileGzipEmbeddedImage.ReadResources(ref input, readerContext, afterburnerMap, compressionTypes); } - else if (Metadata.Codec is CodecKind.MV93) + else if (file.Metadata.Codec is CodecKind.MV93) { if (IResource.Read(ref input, default) is not IndexMap initialMap) throw new InvalidDataException($"Failed to read {nameof(IndexMap)}"); @@ -63,17 +66,10 @@ public void Load(ReadOnlySpan data) continue; input.Position = entry.Offset; - Resources.Add(i, IResource.Read(ref input, readerContext)); + file.Resources.Add(i, IResource.Read(ref input, readerContext)); } } - } - - public static ShockwaveFile Load(string path) - { - ReadOnlySpan data = File.ReadAllBytes(path); - var shockwaveFile = new ShockwaveFile(); - shockwaveFile.Load(data); - return shockwaveFile; + return file; } }