Пример #1
0
        /// <summary>
        /// Writes BND0 data to a BinaryWriterEx.
        /// </summary>
        protected override void Write(BinaryWriterEx bw)
        {
            bw.BigEndian = false;
            bw.WriteASCII("BND\0");

            if (Lite)
            {
                bw.ReserveInt32("FileSize");
                bw.WriteInt32(Files.Count);
                bw.WriteInt32(0);
            }
            else
            {
                bw.WriteInt32(0xF7FF);
                bw.WriteInt32(0xD3);
                bw.ReserveInt32("FileSize");
                bw.WriteInt32(Files.Count);
                bw.WriteInt32(0);

                bw.WriteByte(Flag1);
                bw.WriteByte(Flag2);
                bw.WriteByte(3);
                bw.WriteByte(0);

                bw.WriteInt32(0);
                bw.WriteInt32(0);
            }

            for (int i = 0; i < Files.Count; i++)
            {
                Files[i].Write(bw, Lite, i);
            }

            for (int i = 0; i < Files.Count; i++)
            {
                File file = Files[i];
                bw.Pad(0x20);

                bw.FillInt32($"FileOffset{i}", (int)bw.Position);
                if (Lite)
                {
                    bw.WriteInt32(file.Bytes.Length + 4);
                    bw.WriteBytes(file.Bytes);
                }
                else
                {
                    bw.WriteBytes(file.Bytes);
                }
            }

            bw.FillInt32("FileSize", (int)bw.Position);
        }
Пример #2
0
                internal void WriteValue(BinaryWriterEx bw, object value)
                {
                    switch (Type)
                    {
                    case ParamType.aob: bw.WriteBytes((byte[])value); break;

                    case ParamType.b: bw.WriteBoolean((bool)value); break;

                    case ParamType.u8:
                    case ParamType.x8: bw.WriteByte((byte)value); break;

                    case ParamType.s8: bw.WriteSByte((sbyte)value); break;

                    case ParamType.u16:
                    case ParamType.x16: bw.WriteUInt16((ushort)value); break;

                    case ParamType.s16: bw.WriteInt16((short)value); break;

                    case ParamType.u32:
                    case ParamType.x32: bw.WriteUInt32((uint)value); break;

                    case ParamType.s32: bw.WriteInt32((int)value); break;

                    case ParamType.u64:
                    case ParamType.x64: bw.WriteUInt64((ulong)value); break;

                    case ParamType.s64: bw.WriteInt64((long)value); break;

                    case ParamType.f32: bw.WriteSingle((float)value); break;

                    case ParamType.f64: bw.WriteDouble((double)value); break;

                    default: throw new Exception($"Invalid ParamTemplate ParamType: {Type.ToString()}");
                    }
                }
Пример #3
0
            internal void WriteData(BinaryWriterEx bw, int animIndex, int eventIndex, TAEFormat format)
            {
                CopyParametersToBytes(bw.BigEndian);

                bw.FillVarint($"EventDataOffset{animIndex}:{eventIndex}", bw.Position);
                bw.WriteInt32(Type);

                if (format != TAEFormat.DS1)
                {
                    bw.WriteInt32(Unk04);
                }

                if (format == TAEFormat.SDT && Type == 943)
                {
                    bw.WriteVarint(0);
                }
                else
                {
                    bw.WriteVarint(bw.Position + (bw.VarintLong ? 8 : 4));
                }

                bw.WriteBytes(ParameterBytes);

                if (format != TAEFormat.DS1)
                {
                    bw.Pad(0x10);
                }
            }
Пример #4
0
 public void Write(BinaryWriterEx bw)
 {
     bw.WriteASCII(Name);
     bw.WriteInt32(Bytes.Length);
     bw.WriteInt32(Count);
     bw.WriteInt32(0);
     bw.WriteBytes(Bytes);
 }
Пример #5
0
 public byte[] Write()
 {
     using (var ms = new MemoryStream())
     {
         var bw = new BinaryWriterEx(false, ms);
         bw.WriteBytes(Data);
         bw.Position = 8;
         bw.WriteInt32(SteamID);
         bw.Position = 0x1098;
         bw.WriteBooleans(OccupiedSlots);
         for (int i = 0; i < 10; i++)
         {
             bw.Position = 0x10A2 + 0x22A * i;
             bw.WriteBytes(SlotData[i]);
         }
         return(bw.FinishBytes());
     }
 }
Пример #6
0
        public void WriteData(BinaryWriterEx bw)
        {
            uint absoluteOffset = (uint)bw.Position;

            bw.FillUInt32("absoffset" + SectionID, absoluteOffset);
            bw.WriteBytes(SectionData);
            while ((bw.Position % 16) != 0)
            {
                bw.WriteByte(0xFF); // 16 byte align
            }

            // Local fixups
            bw.FillUInt32("locoffset" + SectionID, (uint)bw.Position - absoluteOffset);
            foreach (var loc in LocalFixups)
            {
                loc.Write(bw);
            }
            while ((bw.Position % 16) != 0)
            {
                bw.WriteByte(0xFF); // 16 byte align
            }

            // Global fixups
            bw.FillUInt32("globoffset" + SectionID, (uint)bw.Position - absoluteOffset);
            foreach (var glob in GlobalFixups)
            {
                glob.Write(bw);
            }
            while ((bw.Position % 16) != 0)
            {
                bw.WriteByte(0xFF); // 16 byte align
            }

            // Virtual fixups
            bw.FillUInt32("virtoffset" + SectionID, (uint)bw.Position - absoluteOffset);
            foreach (var virt in VirtualFixups)
            {
                virt.Write(bw);
            }
            while ((bw.Position % 16) != 0)
            {
                bw.WriteByte(0xFF); // 16 byte align
            }

            bw.FillUInt32("expoffset" + SectionID, (uint)bw.Position - absoluteOffset);
            bw.FillUInt32("impoffset" + SectionID, (uint)bw.Position - absoluteOffset);
            bw.FillUInt32("endoffset" + SectionID, (uint)bw.Position - absoluteOffset);
        }
        public void WriteStandardValueTypeToRaw(BinaryWriterEx bw, object value)
        {
            switch (ValueType)
            {
            case "aob": bw.WriteBytes((byte[])value); break;

            case "b": bw.WriteBoolean((bool)value); break;

            case "u8":
            case "x8": bw.WriteByte((byte)value); break;

            case "s8": bw.WriteSByte((sbyte)value); break;

            case "u16":
            case "x16": bw.WriteUInt16((ushort)value); break;

            case "s16": bw.WriteInt16((short)value); break;

            case "u32":
            case "x32": bw.WriteUInt32((uint)value); break;

            case "s32": bw.WriteInt32((int)value); break;

            case "u64":
            case "x64": bw.WriteUInt64((ulong)value); break;

            case "s64": bw.WriteInt64((long)value); break;

            case "f32": bw.WriteSingle((float)value); break;

            case "f64": bw.WriteDouble((double)value); break;

            default: throw new Exception($"Value type '{ValueType}' is not a standard type " +
                                         $"and must be handled manually instead of calling {nameof(XmlStructDefField)}." +
                                         $"{nameof(WriteStandardValueTypeToRaw)}.");
            }
        }
Пример #8
0
                internal void WriteDefaultValue(BinaryWriterEx bw)
                {
                    if (ValueToAssert != null)
                    {
                        WriteAssertValue(bw);
                    }
                    else if (DefaultValue == null)
                    {
                        switch (Type)
                        {
                        case ParamType.aob:
                            for (int i = 0; i < AobLength; i++)
                            {
                                bw.WriteByte(0);
                            }
                            break;

                        case ParamType.b:
                        case ParamType.u8:
                        case ParamType.x8: bw.WriteByte(0); break;

                        case ParamType.s8: bw.WriteSByte(0); break;

                        case ParamType.u16:
                        case ParamType.x16: bw.WriteUInt16(0); break;

                        case ParamType.s16: bw.WriteInt16(0); break;

                        case ParamType.u32:
                        case ParamType.x32: bw.WriteUInt32(0); break;

                        case ParamType.s32: bw.WriteInt32(0); break;

                        case ParamType.u64:
                        case ParamType.x64: bw.WriteUInt64(0); break;

                        case ParamType.s64: bw.WriteInt64(0); break;

                        case ParamType.f32: bw.WriteSingle(0); break;

                        case ParamType.f64: bw.WriteDouble(0); break;

                        default: throw new Exception($"Invalid ParamTemplate ParamType: {Type.ToString()}");
                        }
                    }
                    else
                    {
                        switch (Type)
                        {
                        case ParamType.aob:
                            var assertAob = (byte[])DefaultValue;
                            bw.WriteBytes(assertAob);
                            break;

                        case ParamType.b:
                        case ParamType.u8:
                        case ParamType.x8: bw.WriteByte((byte)DefaultValue); break;

                        case ParamType.s8: bw.WriteSByte((sbyte)DefaultValue); break;

                        case ParamType.u16:
                        case ParamType.x16: bw.WriteUInt16((ushort)DefaultValue); break;

                        case ParamType.s16: bw.WriteInt16((short)DefaultValue); break;

                        case ParamType.u32:
                        case ParamType.x32: bw.WriteUInt32((uint)DefaultValue); break;

                        case ParamType.s32: bw.WriteInt32((int)DefaultValue); break;

                        case ParamType.u64:
                        case ParamType.x64: bw.WriteUInt64((ulong)DefaultValue); break;

                        case ParamType.s64: bw.WriteInt64((long)DefaultValue); break;

                        case ParamType.f32: bw.WriteSingle((float)DefaultValue); break;

                        case ParamType.f64: bw.WriteDouble((double)DefaultValue); break;

                        default: throw new Exception($"Invalid ParamTemplate ParamType: {Type.ToString()}");
                        }
                    }
                }
Пример #9
0
        protected override void Write(BinaryWriterEx bw)
        {
            bw.WriteASCII("TAE ");

            bw.BigEndian = BigEndian;

            bw.WriteBoolean(BigEndian);
            bw.WriteByte(0);
            bw.WriteByte(0);

            if (Format == TAEFormat.DS1)
            {
                bw.VarintLong = false;
                bw.WriteByte(0);
            }
            else
            {
                bw.VarintLong = true;
                bw.WriteByte(0xFF);
            }

            if (Format == TAEFormat.DS1)
            {
                bw.WriteInt32(0x1000B);
            }
            else if (Format == TAEFormat.DS3 || Format == TAEFormat.SOTFS)
            {
                bw.WriteInt32(0x1000C);
            }
            else if (Format == TAEFormat.SDT)
            {
                bw.WriteInt32(0x1000D);
            }

            bw.ReserveInt32("FileSize");
            bw.WriteVarint(0x40);
            bw.WriteVarint(1);
            bw.WriteVarint(0x50);
            bw.WriteVarint(Format == TAEFormat.DS1 ? 0x70 : 0x80);

            if (Format == TAEFormat.DS1)
            {
                bw.WriteInt16(2);
                bw.WriteInt16(1);
            }
            else
            {
                bw.WriteVarint(EventBank);
            }

            bw.WriteVarint(0);

            //DeS also
            if (Format == TAEFormat.DS1)
            {
                bw.WriteInt64(0);
                bw.WriteInt64(0);
                bw.WriteInt64(0);
            }

            bw.WriteBytes(Flags);

            if (Format == TAEFormat.SOTFS)
            {
                bw.WriteByte(0);
                bw.WriteByte(1);
            }
            else
            {
                bw.WriteByte(1);
                bw.WriteByte(0);
            }

            for (int i = 0; i < 6; i++)
            {
                bw.WriteByte(0);
            }

            bw.WriteInt32(ID);
            bw.WriteInt32(Animations.Count);
            bw.ReserveVarint("AnimsOffset");
            bw.ReserveVarint("AnimGroupsOffset");
            bw.WriteVarint(Format == TAEFormat.DS1 ? 0x90 : 0xA0);
            bw.WriteVarint(Animations.Count);
            bw.ReserveVarint("FirstAnimOffset");
            if (Format == TAEFormat.DS1)
            {
                bw.WriteInt32(0);
            }
            bw.WriteVarint(1);
            bw.WriteVarint(Format == TAEFormat.DS1 ? 0x80 : 0x90);
            if (Format == TAEFormat.DS1)
            {
                bw.WriteInt64(0);
            }
            bw.WriteInt32(ID);
            bw.WriteInt32(ID);
            bw.WriteVarint(0x50);
            bw.WriteInt64(0);
            bw.WriteVarint(Format == TAEFormat.DS1 ? 0x98 : 0xB0);
            bw.ReserveVarint("SkeletonName");
            bw.ReserveVarint("SibName");

            if (Format != TAEFormat.SOTFS)
            {
                bw.WriteVarint(0);
                bw.WriteVarint(0);
            }

            bw.FillVarint("SkeletonName", bw.Position);
            if (!string.IsNullOrEmpty(SkeletonName))
            {
                bw.WriteUTF16(SkeletonName, true);
                if (Format != TAEFormat.DS1)
                {
                    bw.Pad(0x10);
                }
            }

            bw.FillVarint("SibName", bw.Position);
            if (!string.IsNullOrEmpty(SibName))
            {
                bw.WriteUTF16(SibName, true);
                if (Format != TAEFormat.DS1)
                {
                    bw.Pad(0x10);
                }
            }

            Animations.Sort((a1, a2) => a1.ID.CompareTo(a2.ID));

            var animOffsets = new List <long>(Animations.Count);

            if (Animations.Count == 0)
            {
                bw.FillVarint("AnimsOffset", 0);
            }
            else
            {
                bw.FillVarint("AnimsOffset", bw.Position);
                for (int i = 0; i < Animations.Count; i++)
                {
                    animOffsets.Add(bw.Position);
                    Animations[i].WriteHeader(bw, i);
                }
            }

            bw.FillVarint("AnimGroupsOffset", bw.Position);
            bw.ReserveVarint("AnimGroupsCount");
            bw.ReserveVarint("AnimGroupsOffset");
            int  groupCount = 0;
            long groupStart = bw.Position;

            for (int i = 0; i < Animations.Count; i++)
            {
                int firstIndex = i;
                bw.WriteInt32((int)Animations[i].ID);
                while (i < Animations.Count - 1 && Animations[i + 1].ID == Animations[i].ID + 1)
                {
                    i++;
                }
                bw.WriteInt32((int)Animations[i].ID);
                bw.WriteVarint(animOffsets[firstIndex]);
                groupCount++;
            }
            bw.FillVarint("AnimGroupsCount", groupCount);

            if (groupCount == 0)
            {
                bw.FillVarint("AnimGroupsOffset", 0);
            }
            else
            {
                bw.FillVarint("AnimGroupsOffset", groupStart);
            }

            if (Animations.Count == 0)
            {
                bw.FillVarint("FirstAnimOffset", 0);
            }
            else
            {
                bw.FillVarint("FirstAnimOffset", bw.Position);
                for (int i = 0; i < Animations.Count; i++)
                {
                    Animations[i].WriteBody(bw, i, Format);
                }
            }

            for (int i = 0; i < Animations.Count; i++)
            {
                Animation anim = Animations[i];
                anim.WriteAnimFile(bw, i, Format);
                Dictionary <float, long> timeOffsets = anim.WriteTimes(bw, i, Format);
                List <long> eventHeaderOffsets       = anim.WriteEventHeaders(bw, i, timeOffsets);
                anim.WriteEventData(bw, i, Format);
                anim.WriteEventGroupHeaders(bw, i, Format);
                anim.WriteEventGroupData(bw, i, eventHeaderOffsets, Format);
            }

            bw.FillInt32("FileSize", (int)bw.Position);
        }
Пример #10
0
        public void WriteWithContext(BinaryWriterEx bw, EzSembleContext context)
        {
            bw.WriteASCII(ESDMagicByFormat[FormatType]);
            bw.BigEndian = (FormatType == ESDFormatType.BigEndian32Bit || FormatType == ESDFormatType.BigEndian64Bit);

            context.IsBigEndian = bw.BigEndian;

            bw.WriteInt32(1);
            bw.WriteInt32(DarkSoulsCount);
            bw.WriteInt32(DarkSoulsCount);
            bw.WriteInt32(0x54);
            bw.ReserveInt32("DataSize");
            bw.WriteInt32(6);
            bw.WriteInt32(LongFormat ? 0x48 : 0x2C);
            bw.WriteInt32(1);
            bw.WriteInt32(LongFormat ? 0x20 : 0x10);
            bw.WriteInt32(StateGroups.Count);
            int stateSize = LongFormat ? 0x48 : 0x24;

            bw.WriteInt32(stateSize);
            bw.WriteInt32(StateGroups.Values.Sum(sg => sg.Count + (sg.Count == 1 ? 0 : 1)));
            bw.WriteInt32(LongFormat ? 0x38 : 0x1C);
            bw.ReserveInt32("ConditionCount");
            bw.WriteInt32(LongFormat ? 0x18 : 0x10);
            bw.ReserveInt32("CommandCallCount");
            bw.WriteInt32(LongFormat ? 0x10 : 0x8);
            bw.ReserveInt32("CommandArgCount");
            bw.ReserveInt32("ConditionOffsetsOffset");
            bw.ReserveInt32("ConditionOffsetsCount");
            bw.ReserveInt32("NameBlockOffset");
            bw.WriteInt32(Name == null ? 0 : Name.Length + 1);
            bw.ReserveInt32("UnkOffset1");
            bw.WriteInt32(0);
            bw.ReserveInt32("UnkOffset2");
            bw.WriteInt32(0);

            long dataStart = bw.Position;

            bw.WriteInt32(Unk6C);
            bw.WriteInt32(Unk70);
            bw.WriteInt32(Unk74);
            bw.WriteInt32(Unk78);
            bw.WriteInt32(Unk7C);
            if (LongFormat)
            {
                bw.WriteInt32(0);
            }

            ReserveVarint(bw, LongFormat, "StateGroupsOffset");
            WriteVarint(bw, LongFormat, StateGroups.Count);
            ReserveVarint(bw, LongFormat, "NameOffset");
            WriteVarint(bw, LongFormat, Name == null ? 0 : Name.Length + 1);
            long unkNull = DarkSoulsCount == 1 ? 0 : -1;

            WriteVarint(bw, LongFormat, unkNull);
            WriteVarint(bw, LongFormat, unkNull);

            // Collect and sort all the IDs so everything is definitely in the same order everywhere
            List <long> stateGroupIDs = StateGroups.Keys.ToList();

            stateGroupIDs.Sort();
            var stateIDs = new Dictionary <long, List <long> >();

            foreach (long groupID in stateGroupIDs)
            {
                stateIDs[groupID] = StateGroups[groupID].Keys.ToList();
                stateIDs[groupID].Sort();
            }

            if (StateGroups.Count == 0)
            {
                FillVarint(bw, LongFormat, "StateGroupsOffset", -1);
            }
            else
            {
                FillVarint(bw, LongFormat, "StateGroupsOffset", bw.Position - dataStart);
                foreach (long groupID in stateGroupIDs)
                {
                    WriteVarint(bw, LongFormat, groupID);
                    ReserveVarint(bw, LongFormat, $"StateGroup{groupID}:StatesOffset1");
                    WriteVarint(bw, LongFormat, StateGroups[groupID].Count);
                    ReserveVarint(bw, LongFormat, $"StateGroup{groupID}:StatesOffset2");
                }
            }

            var stateOffsets      = new Dictionary <long, Dictionary <long, long> >();
            var weirdStateOffsets = new List <long[]>();

            foreach (long groupID in stateGroupIDs)
            {
                stateOffsets[groupID] = new Dictionary <long, long>();
                FillVarint(bw, LongFormat, $"StateGroup{groupID}:StatesOffset1", bw.Position - dataStart);
                FillVarint(bw, LongFormat, $"StateGroup{groupID}:StatesOffset2", bw.Position - dataStart);
                long firstStateOffset = bw.Position;
                foreach (long stateID in stateIDs[groupID])
                {
                    stateOffsets[groupID][stateID] = bw.Position - dataStart;
                    StateGroups[groupID][stateID].WriteHeader(context, bw, LongFormat, groupID, stateID);
                }
                if (StateGroups[groupID].Count > 1)
                {
                    weirdStateOffsets.Add(new long[] { firstStateOffset, bw.Position });
                    bw.Position += stateSize;
                }
            }

            // Make a list of every unique condition
            var conditions = new Dictionary <long, List <Condition> >();

            foreach (long groupID in stateGroupIDs)
            {
                conditions[groupID] = new List <Condition>();
                void addCondition(Condition cond)
                {
                    if (!conditions[groupID].Any(c => ReferenceEquals(cond, c)))
                    {
                        conditions[groupID].Add(cond);
                        foreach (Condition subCond in cond.Subconditions)
                        {
                            addCondition(subCond);
                        }
                    }
                }

                foreach (State state in StateGroups[groupID].Values)
                {
                    foreach (Condition cond in state.Conditions)
                    {
                        addCondition(cond);
                    }
                }
            }
            bw.FillInt32("ConditionCount", conditions.Values.Sum(group => group.Count));

            // Yes, I do in fact want this to be keyed by reference
            var conditionOffsets = new Dictionary <Condition, long>();

            foreach (long groupID in stateGroupIDs)
            {
                for (int i = 0; i < conditions[groupID].Count; i++)
                {
                    Condition cond = conditions[groupID][i];
                    cond.MetaRefID = conditionOffsets[cond] = bw.Position - dataStart;
                    cond.WriteHeader(context, bw, LongFormat, groupID, i, stateOffsets[groupID]);
                }
            }

            var commands = new List <CommandCall>();

            foreach (long groupID in stateGroupIDs)
            {
                foreach (long stateID in stateIDs[groupID])
                {
                    StateGroups[groupID][stateID].WriteCommandCalls(context, bw, LongFormat, groupID, stateID, dataStart, commands);
                }
                for (int i = 0; i < conditions[groupID].Count; i++)
                {
                    conditions[groupID][i].WriteCommandCalls(context, bw, LongFormat, groupID, i, dataStart, commands);
                }
            }
            bw.FillInt32("CommandCallCount", commands.Count);
            bw.FillInt32("CommandArgCount", commands.Sum(command => command.Arguments.Count));

            for (int i = 0; i < commands.Count; i++)
            {
                commands[i].WriteArgs(context, bw, LongFormat, i, dataStart);
            }

            bw.FillInt32("ConditionOffsetsOffset", (int)(bw.Position - dataStart));
            int conditionOffsetsCount = 0;

            foreach (long groupID in stateGroupIDs)
            {
                foreach (long stateID in stateIDs[groupID])
                {
                    conditionOffsetsCount += StateGroups[groupID][stateID].WriteConditionOffsets(bw, LongFormat, groupID, stateID, dataStart, conditionOffsets);
                }
                for (int i = 0; i < conditions[groupID].Count; i++)
                {
                    conditionOffsetsCount += conditions[groupID][i].WriteConditionOffsets(bw, LongFormat, groupID, i, dataStart, conditionOffsets);
                }
            }
            bw.FillInt32("ConditionOffsetsCount", conditionOffsetsCount);

            foreach (long groupID in stateGroupIDs)
            {
                for (int i = 0; i < conditions[groupID].Count; i++)
                {
                    conditions[groupID][i].WriteEvaluator(context, bw, LongFormat, groupID, i, dataStart);
                }
            }
            for (int i = 0; i < commands.Count; i++)
            {
                commands[i].WriteBytecode(context, bw, LongFormat, i, dataStart);
            }

            bw.FillInt32("NameBlockOffset", (int)(bw.Position - dataStart));
            if (Name == null)
            {
                FillVarint(bw, LongFormat, "NameOffset", -1);
            }
            else
            {
                bw.Pad(2);
                FillVarint(bw, LongFormat, "NameOffset", bw.Position - dataStart);
                bw.WriteUTF16(Name, true);
            }
            bw.FillInt32("UnkOffset1", (int)(bw.Position - dataStart));
            bw.FillInt32("UnkOffset2", (int)(bw.Position - dataStart));
            bw.FillInt32("DataSize", (int)(bw.Position - dataStart));

            if (DarkSoulsCount == 1)
            {
                bw.Pad(4);
            }
            else if (DarkSoulsCount == 2)
            {
                bw.Pad(0x10);
            }

            foreach (long[] offsets in weirdStateOffsets)
            {
                bw.Position = offsets[0];
                byte[] bytes = new byte[stateSize];
                bw.Stream.Read(bytes, 0, stateSize);
                bw.Position = offsets[1];
                bw.WriteBytes(bytes);
            }

            LastSavedHash = bw.GetMD5HashOfStream();

            Metadata = ESDMetadata.Generate(this);
        }
Пример #11
0
        private static void Parse(string plaintext, BinaryWriterEx bw, int current, ref int next)
        {
            if (current == 0 && plaintext[current] == '.')
            {
                throw new Exception("Cannot start with an abort if previous number is false byte");
            }
            else if (current == 0 && plaintext[current] == '~')
            {
                throw new Exception("Cannot start with a continuation byte thing or whatever");
            }

            // Number literal
            if (plaintext[current] == '-' || char.IsDigit(plaintext[current]))
            {
                // Is subtract and not a literal
                if (plaintext[current] == '-' && (next == plaintext.Length || !char.IsDigit(plaintext[next])))
                {
                    bw.WriteByte(BytesByOperator["-"]);
                }
                else
                {
                    while (next < plaintext.Length && char.IsDigit(plaintext[next]))
                    {
                        next++;
                    }

                    if (next + 1 < plaintext.Length && plaintext[next] == '.' && char.IsDigit(plaintext[next + 1]))
                    {
                        next++;
                        while (next < plaintext.Length && char.IsDigit(plaintext[next]))
                        {
                            next++;
                        }
                    }

                    string str   = plaintext.Substring(current, next - current);
                    double value = double.Parse(str);
                    if (value == Math.Floor(value))
                    {
                        if (value >= -64 && value <= 63)
                        {
                            bw.WriteByte((byte)(value + 64));
                        }
                        else
                        {
                            bw.WriteByte((byte)0x82);
                            bw.WriteInt32((int)value);
                        }
                    }
                    else if (value == (float)value)
                    {
                        bw.WriteByte((byte)0x80);
                        bw.WriteSingle((float)value);
                    }
                    else
                    {
                        bw.WriteByte((byte)0x81);
                        bw.WriteDouble(value);
                    }
                }
            }
            // String literal
            else if (plaintext[current] == '"')
            {
                while (next < plaintext.Length && plaintext[next] != '"')
                {
                    next++;
                }

                if (next == plaintext.Length)
                {
                    throw new Exception("Unclosed string literal");
                }

                string value = plaintext.Substring(current + 1, next - current - 1);
                if (value.Contains('\r') || value.Contains('\n'))
                {
                    throw new Exception("String literals may not contain newlines");
                }

                bw.WriteByte((byte)0xA5);
                bw.WriteBytes(bw.BigEndian ?
                              Encoding.BigEndianUnicode.GetBytes(value + "\0")
                    : Encoding.Unicode.GetBytes(value + "\0"));

                next++;
            }
            // Add
            else if (plaintext[current] == '+')
            {
                bw.WriteByte(BytesByOperator["+"]);
            }
            // Multiply
            else if (plaintext[current] == '*')
            {
                bw.WriteByte(BytesByOperator["*"]);
            }
            // Negate
            else if (plaintext[current] == 'N' || plaintext[current] == 'n')
            {
                bw.WriteByte(BytesByOperator["N"]);
            }
            else if (plaintext[current] == '/')
            {
                // Comment
                if (next < plaintext.Length && plaintext[next] == '/')
                {
                    while (next < plaintext.Length && plaintext[next] != '\n')
                    {
                        next++;
                    }
                    next++;
                }
                // Divide
                else
                {
                    bw.WriteByte(BytesByOperator["/"]);
                }
            }
            else if (plaintext[current] == '<')
            {
                // Less than or equal to
                if (next < plaintext.Length && plaintext[next] == '=')
                {
                    bw.WriteByte(BytesByOperator["<="]);
                    next++;
                }
                // Less than
                else
                {
                    bw.WriteByte(BytesByOperator["<"]);
                }
            }
            else if (plaintext[current] == '>')
            {
                // Set register
                if (next < plaintext.Length && plaintext[next] == '[')
                {
                    if (next + 2 >= plaintext.Length || plaintext[next + 2] != ']')
                    {
                        throw new Exception("Malformed register storage");
                    }
                    if (!"01234567".Contains(plaintext[next + 1]))
                    {
                        throw new Exception("Register must be from 0-7");
                    }

                    bw.WriteByte((byte)(0xA7 + byte.Parse(plaintext[next + 1].ToString())));
                    next += 3;
                }
                // Greater than or equal to
                else if (next < plaintext.Length && plaintext[next] == '=')
                {
                    bw.WriteByte(BytesByOperator[">="]);
                    next++;
                }
                // Greater than
                else
                {
                    bw.WriteByte(BytesByOperator[">"]);
                }
            }
            // Equal to
            else if (plaintext[current] == '=')
            {
                if (next == plaintext.Length || plaintext[next] != '=')
                {
                    throw new Exception("Orphaned = found");
                }

                bw.WriteByte(BytesByOperator["=="]);
                next++;
            }
            // Not equal to
            else if (plaintext[current] == '!')
            {
                if (next == plaintext.Length || plaintext[next] != '=')
                {
                    throw new Exception("Orphaned ! found");
                }

                bw.WriteByte(BytesByOperator["!="]);
                next++;
            }
            // Logical and
            else if (plaintext[current] == '&')
            {
                if (next == plaintext.Length || plaintext[next] != '&')
                {
                    throw new Exception("Orphaned & found");
                }

                bw.WriteByte(BytesByOperator["&&"]);
                next++;
            }
            // Logical or
            else if (plaintext[current] == '|')
            {
                if (next == plaintext.Length || plaintext[next] != '|')
                {
                    throw new Exception("Orphaned | found");
                }

                bw.WriteByte(BytesByOperator["||"]);
                next++;
            }
            // Function call
            else if (plaintext[current] == '(')
            {
                if (next + 1 >= plaintext.Length || plaintext[next + 1] != ')')
                {
                    throw new Exception("Unclosed function call");
                }
                if (!"0123456".Contains(plaintext[next]))
                {
                    throw new Exception("Function call must take 0-6 arguments");
                }

                bw.WriteByte((byte)(0x84 + byte.Parse(plaintext[next].ToString())));
                next += 2;
            }
            // Get register
            else if (plaintext[current] == '[')
            {
                if (next + 2 >= plaintext.Length || plaintext[next + 1] != ']' || plaintext[next + 2] != '>')
                {
                    throw new Exception("Malformed register retrieval");
                }
                if (!"01234567".Contains(plaintext[next]))
                {
                    throw new Exception("Register must be from 0-7");
                }

                bw.WriteByte((byte)(0xAF + byte.Parse(plaintext[next].ToString())));
                next += 3;
            }
            // ~ or .
            else if (BytesByTerminator.ContainsKey(plaintext[current]))
            {
                bw.WriteByte(BytesByTerminator[plaintext[current]]);
            }
            // Unknown opcode
            else if (plaintext[current] == '#')
            {
                if (next + 1 >= plaintext.Length)
                {
                    throw new Exception("Hex literal too short");
                }

                bw.WriteByte(Convert.ToByte(plaintext.Substring(current + 1, 2), 16));
                next += 2;
            }
            // Whitespace
            else if (char.IsWhiteSpace(plaintext[current]))
            {
                while (next < plaintext.Length && char.IsWhiteSpace(plaintext[next]))
                {
                    next++;
                }
            }
            // Uh-oh
            else
            {
                throw new Exception($"Unknown character: {plaintext[current]}");
            }
        }