// // ExtensionMethods.cs - extension methods for Unreal Engine functionality // // Author: // Michael Becker // // Copyright (c) 2011-2020 Mike Becker's Software // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System; using UniversalEditor.IO; namespace UniversalEditor.Plugins.UnrealEngine { /// /// Extension methods for Unreal Engine functionality. /// public static class ExtensionMethods { /// /// The Name type is a simple string type. The format does, although, differ between the /// package versions. /// /// /// public static string ReadNAME(this Reader br, int packageVersion) { string value = String.Empty; // Older package versions (<64, original Unreal engine) store the Name type as a // zero-terminated ASCII string; "UT2k3", for example would be stored as: // "U" "T" "2" "k" "3" 0x00 if (packageVersion < 64) { value = br.ReadNullTerminatedString(); } else if (packageVersion >= 512) { uint length = br.ReadUInt32(); value = br.ReadNullTerminatedString(); } else { // Newer packages (>=64, UT engine) prepend the length of the string plus the trailing // zero. Again, "UT2k3" would be now stored as: 0x06 "U" "T" "2" "k" "3" 0x00 value = br.ReadLengthPrefixedString(); if (value.EndsWith("\0")) { value = value.TrimNull(); } else { // do we need this sanity check? throw new InvalidOperationException(); } } return value; } public static void WriteNAME(this Writer bw, string value, int packageVersion) { // Older package versions (<64, original Unreal engine) store the Name type as a // zero-terminated ASCII string; "UT2k3", for example would be stored as: // "U" "T" "2" "k" "3" 0x00 if (packageVersion < 64) { bw.WriteNullTerminatedString(value); } else if (packageVersion >= 512) { bw.WriteUInt32((uint)value.Length); bw.WriteNullTerminatedString(value); } else { // Newer packages (>=64, UT engine) prepend the length of the string plus the trailing // zero. Again, "UT2k3" would be now stored as: 0x06 "U" "T" "2" "k" "3" 0x00 bw.Write7BitEncodedInt32(value.Length + 1); bw.WriteFixedLengthString(value); bw.WriteByte((byte)0); } } /// /// Reads a compact integer from the FileReader. Bytes read differs, so do not make /// assumptions about physical data being read from the stream. (If you have to, get the /// difference of FileReader.BaseStream.Position before and after this is executed.) /// /// An "uncompacted" signed integer. /// There may be better ways to implement this, but this is fast, and it works. public static int ReadINDEX(this Reader br) { int output = 0; bool signed = false; for (int i = 0; i < 5; i++) { byte x = br.ReadByte(); // First byte if (i == 0) { // Bit: X0000000 if ((x & 0x80) > 0) { signed = true; } // Bits: 00XXXXXX output |= (x & 0x3F); // Bit: 0X000000 if ((x & 0x40) == 0) { break; } } // Last byte else if (i == 4) { // Bits: 000XXXXX -- the 0 bits are ignored // (hits the 32 bit boundary) output |= (x & 0x1F) << (6 + (3 * 7)); } // Middle bytes else { // Bits: 0XXXXXXX output |= (x & 0x7F) << (6 + ((i - 1) * 7)); // Bit: X0000000 if ((x & 0x80) == 0) { break; } } } // multiply by negative one here, since the first 6+ bits could be 0 if (signed) { output *= -1; } return (output); } public static void WriteINDEX(this Writer bw, int value) { bw.Write7BitEncodedInt32(value); } } }