/// <summary> /// Gets spoilers from slottype block and sets to cartypeinfo. /// </summary> /// <param name="db">Database of classes.</param> private static void E_Spoilers(Database.Carbon db) { if (db.SlotTypes.Spoilers == null) { return; } var CNameList = new List <string>(); foreach (var car in db.CarTypeInfos.Collections) { CNameList.Add(car.CollectionName); } var AllSlots = db.SlotTypes.Spoilers.GetSpoilers(CNameList); if (AllSlots == null || AllSlots.Count == 0) { return; } foreach (var Slot in AllSlots) { var car = db.CarTypeInfos.FindCollection(Slot.CarTypeInfo); if (car == null) { continue; } car.Spoiler = Slot.Spoiler; car.SpoilerAS = Slot.SpoilerAS; } }
/// <summary> /// Writes all collisions into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static unsafe void I_Collisions(Database.Carbon db, BinaryWriter bw) { bw.Write(Global.Collisions); bw.Write(0xFFFFFFFF); // write temp size int initial_size = (int)bw.BaseStream.Position; // Copy all collisions by the internal names foreach (var info in db.CarTypeInfos.Collections) { if (info.CollisionExternalName == BaseArguments.NULL) continue; uint extkey = Vlt.Hash(info.CollisionExternalName); uint intkey = Vlt.Hash(info.CollisionInternalName); if (db.SlotTypes.Collisions.TryGetValue(intkey, out var collision)) bw.Write(collision.GetData(extkey)); } // Copy all unknown collisions foreach (var collision in db.SlotTypes.Collisions) { if (collision.Value.Unknown) bw.Write(collision.Value.GetData(0)); } // Fix size bw.BaseStream.Position = initial_size - 4; bw.Write((int)bw.BaseStream.Length - initial_size); bw.BaseStream.Position = bw.BaseStream.Length; }
/// <summary> /// Writes all materials into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static void I_Materials(Database.Carbon db, BinaryWriter bw) { foreach (var material in db.Materials.Collections) { bw.Write(material.Assemble()); } }
/// <summary> /// Gets the frontend group, decompresses it if needed, and plugs into Vector. /// </summary> /// <param name="byteptr_t">Pointer to the beginning of frontend group in Global data.</param> /// <param name="length">Length of the block to be read.</param> /// <param name="db">Database to which add classes.</param> private static unsafe void E_FNGroup(byte *byteptr_t, uint length, Database.Carbon db) { // Copy data and decompress var data = new byte[length]; fixed(byte *dataptr_t = &data[0]) { for (int a1 = 0; a1 < data.Length; ++a1) { *(dataptr_t + a1) = *(byteptr_t + a1); } } data = SAT.Decompress(data); var Class = new FNGroup(data, db); // Check whether this FEng class already exists in the database if (Class.Destroy) { return; } if (db.FNGroups.FindCollection(Class.CollectionName) != null) { return; } db.FNGroups.Collections.Add(Class); Bin.Hash(Class.CollectionName); }
/// <summary> /// Saves database data into GlobalA file. /// </summary> /// <param name="GlobalA_dir">Game directory.</param> /// <param name="db">Database of classes.</param> /// <returns>True if success.</returns> public static bool SaveGlobalA(string GlobalA_dir, Database.Carbon db) { GlobalA_dir += @"\GLOBAL\GlobalA.bun"; using (var br = new BinaryReader(new MemoryStream(db._GlobalABUN))) using (var bw = new BinaryWriter(File.Open(GlobalA_dir, FileMode.Create))) { int tpkindex = 0; while (br.BaseStream.Position < br.BaseStream.Length) { // Set Offset, ID and Size values, read starting in the beginning of the file uint WriterSlotOffset = (uint)br.BaseStream.Position; uint WriterSlotID = br.ReadUInt32(); int WriterSlotSize = br.ReadInt32(); // If one of the necessary slots is reached, replace it switch (WriterSlotID) { case 0: uint key = br.ReadUInt32(); br.BaseStream.Position -= 4; if (key == Global.GlobalLib) { br.BaseStream.Position += WriterSlotSize; break; } else { goto default; } case Global.TPKBlocks: while (!db.TPKBlocks[tpkindex].InGlobalA) { ++tpkindex; } I_TPKBlock(db, bw, ref tpkindex); br.BaseStream.Position += WriterSlotSize; break; case Global.FEngFiles: case Global.FNGCompress: br.BaseStream.Position += WriterSlotSize; break; default: bw.Write(WriterSlotID); bw.Write(WriterSlotSize); bw.Write(br.ReadBytes(WriterSlotSize)); break; } } // Write all FEng files I_FNGroup(db, bw); } return(true); }
/// <summary> /// Writes all frontend groups into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static void I_FNGroup(Database.Carbon db, BinaryWriter bw) { for (int a1 = 0; a1 < db.FNGroups.Length; ++a1) { I_GlobalLibBlock(bw); bw.Write(db.FNGroups[a1].Assemble()); } }
// Default constructor: create new texture for memory cast public Texture(string CName, string _TPK, Database.Carbon db) { this.Database = db; this._collection_name = CName; this._parent_TPK = _TPK; this.BinKey = Bin.Hash(CName); this.PaletteOffset = -1; this._padding = 0; }
/// <summary> /// Writes all preset rides into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static void I_PresetRides(Database.Carbon db, BinaryWriter bw) { bw.Write(Global.PresetRides); bw.Write(db.PresetRides.Length * 0x600); foreach (var ride in db.PresetRides.Collections) { bw.Write(ride.Assemble()); } }
/// <summary> /// Writes all preset skins into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static void I_PresetSkins(Database.Carbon db, BinaryWriter bw) { bw.Write(Global.PresetSkins); bw.Write(db.PresetSkins.Length * 0x68); foreach (var skin in db.PresetSkins.Collections) { bw.Write(skin.Assemble()); } }
public unsafe TPKBlock(byte *byteptr_t, int index, Database.Carbon db) { if (index < 0) { this._use_current_cname = true; } this.Database = db; this.Index = index; this.Disassemble(byteptr_t); }
// Default constructor: disassemble preset public unsafe PresetRide(System.IntPtr byteptr_t, string CName, Database.Carbon db) { this.Database = db; this._collection_name = CName; this.data = new byte[0x600]; this.Exists = true; this.Initialize(); this.Disassemble((byte *)byteptr_t); this.Modified = false; }
// Default constructor: disassemble texture public unsafe Texture(byte *byteptr_t, uint offset, uint size, string _TPK, Database.Carbon db) { this.Database = db; this._located_at = (int)offset; this._size_of_block = (int)size; this._parent_TPK = _TPK; this.PaletteOffset = -1; this._padding = 0; this.Disassemble(byteptr_t + this._located_at); }
/// <summary> /// Decompile entire carparts block into separate elements. /// </summary> /// <param name="byteptr_t">Pointer to the beginning of carparts block in Global data.</param> /// <param name="length">Length of the block to be read.</param> /// <param name="db">Database to which add classes.</param> private static unsafe void E_CarParts(byte *byteptr_t, uint length, Database.Carbon db) { uint offset = 0; uint ID = 0; uint size = 0; byte *part5ptr_t = byteptr_t; // pointer to the part5 of the block byte *part6ptr_t = byteptr_t; // pointer to the part6 of the block while (offset < length) { ID = *(uint *)(byteptr_t + offset); size = *(uint *)(byteptr_t + offset + 4); if (offset + size > length) { return; // in case of reading beyong the stream } switch (ID) { case CarParts.Part0: db.SlotTypes.Part0.Data = CPE_Part0(byteptr_t + offset, size + 8); goto default; case CarParts.Part1: db.SlotTypes.Part1.Data = CPE_Part1(byteptr_t + offset, size + 8); goto default; case CarParts.Part2: db.SlotTypes.Part2.Data = CPE_Part2(byteptr_t + offset, size + 8); goto default; case CarParts.Part3: db.SlotTypes.Part3.Data = CPE_Part3(byteptr_t + offset, size + 8); goto default; case CarParts.Part4: db.SlotTypes.Part4.Data = CPE_Part4(byteptr_t + offset, size + 8); goto default; case CarParts.Part5: part5ptr_t = byteptr_t + offset; goto default; case CarParts.Part6: part6ptr_t = byteptr_t + offset; goto default; default: offset += 8 + size; break; } } // Disassemble part5 and part6 CPE_Part56(part5ptr_t, part6ptr_t, db); }
// Default constructor: create new preset public PresetRide(string CName, Database.Carbon db) { this.Database = db; this.CollectionName = CName; this.data = new byte[0x600]; this.MODEL = "SUPRA"; this.Frontend = "supra"; this.Pvehicle = "supra"; this.Initialize(); Map.BinKeys[Bin.Hash(CName)] = CName; this.Modified = true; }
// Default constructor: disassemble cartypeinfo public unsafe CarTypeInfo(IntPtr byteptr_t, string CName, Database.Carbon db) { this.Database = db; this._collection_name = CName; this.OriginalName = CName; this.Disassemble((byte *)byteptr_t); if (this.Index <= (int)eBoundValues.MIN_INFO_CARBON) { this.Deletable = false; } this.Modified = false; }
/// <summary> /// Writes all cartypeinfo into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static void I_CarTypeInfo(Database.Carbon db, BinaryWriter bw) { int index = 0; bw.Write(Global.CarTypeInfo); bw.Write(db.CarTypeInfos.Length * 0xD0 + 8); bw.Write(0x1111111111111111); foreach (var car in db.CarTypeInfos.Collections) { car.Index = index++; bw.Write(car.Assemble()); } }
/// <summary> /// Get the material name, initialize its class and inject into Vector. /// </summary> /// <param name="byteptr_t">Pointer to the beginning of material block in Global data.</param> /// <param name="db">Database to which add classes.</param> private static unsafe void E_Material(byte *byteptr_t, Database.Carbon db) { // Get collection name of the material, starts at 0x14 string CName = ScriptX.NullTerminatedString(byteptr_t + 0x1C, 0x1C); CName = Resolve.GetPathFromCollection(CName); Resolve.GetWindowTintString(CName); Map.BinKeys[Bin.Hash(CName)] = CName; var Class = new Material((IntPtr)byteptr_t, CName, db); db.Materials.Collections.Add(Class); }
/// <summary> /// Extracts slottype block into memory. /// </summary> /// <param name="byteptr_t">Pointer to the ID of spoilerss block in Global data.</param> /// <param name="length">Length of the block to be read (including ID and size).</param> /// <param name="db">Database to which add classes.</param> private static unsafe void E_SlotType(byte *byteptr_t, uint length, Database.Carbon db) { var Data = new byte[length]; fixed(byte *dataptr_t = &Data[0]) { for (int a1 = 0; a1 < length; ++a1) { *(dataptr_t + a1) = *(byteptr_t + a1); } } db.SlotTypes.Spoilers = new Spoilers(Data); }
// Default constructor: create new cartypeinfo public CarTypeInfo(string CName, Database.Carbon db) { this.Database = db; this.CollectionName = CName; this.ManufacturerName = "GENERIC"; this.Deletable = true; this.Modified = true; this.WhatGame = 1; this.WheelOuterRadius = 26; this.WheelInnerRadiusMin = 17; this.WheelInnerRadiusMax = 20; this.DefaultSkinNumber = 1; this.CollisionExternalName = CollectionName; this.CollisionInternalName = "CARRERAGT"; Map.BinKeys[Bin.Hash(CName)] = CName; }
/// <summary> /// Decompile entire preset rides block into Vector of separate elements. /// </summary> /// <param name="byteptr_t">Pointer to the beginning of preset rides block in Global data.</param> /// <param name="length">Length of the block to be read.</param> /// <param name="db">Database to which add classes.</param> private static unsafe void E_PresetRides(byte *byteptr_t, uint length, Database.Carbon db) { uint size = 0x600; for (uint loop = 0; loop < length / size; ++loop) { uint offset = loop * size; // current offset of the preset ride // Get CollectionName string CName = ScriptX.NullTerminatedString(byteptr_t + offset + 0x28, 0x20); CName = Resolve.GetPathFromCollection(CName); Map.BinKeys[Bin.Hash(CName)] = CName; var Class = new PresetRide((IntPtr)(byteptr_t + offset), CName, db); db.PresetRides.Collections.Add(Class); } }
/// <summary> /// Writes all slottype into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static void I_SlotType(Database.Carbon db, BinaryWriter bw) { var SetList = new List <CarSpoilerType>(); // Get all cartypeinfos with non-base spoilers foreach (var info in db.CarTypeInfos.Collections) { if (info.Spoiler != eSpoiler.SPOILER || info.SpoilerAS != eSpoilerAS2.SPOILER_AS2) { var Class = new CarSpoilerType(); Class.CarTypeInfo = info.CollectionName; Class.Spoiler = info.Spoiler; Class.SpoilerAS = info.SpoilerAS; SetList.Add(Class); } } bw.Write(db.SlotTypes.Spoilers.SetSpoilers(SetList)); }
/// <summary> /// Loads English_Global and Labels_Global files and disassembles their blocks. /// </summary> /// <param name="Language_dir">Directory of the game.</param> /// <param name="db">Database of classes.</param> /// <returns>True if success.</returns> public static unsafe bool LoadLanguage(string Language_dir, Database.Carbon db) { Language_dir += @"\LANGUAGES\"; // Get everything from language files try { db._LngGlobal = File.ReadAllBytes(Language_dir + "English_Global.bin"); db._LngLabels = File.ReadAllBytes(Language_dir + "Labels_Global.bin"); Log.Write("Reading data from English_Global.bin..."); Log.Write("Reading data from Labels_Global.bin..."); } catch (Exception e) { while (e.InnerException != null) { e = e.InnerException; } if (Process.MessageShow) { MessageBox.Show($"Error occured: {e.Message}", "Failure", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { Console.WriteLine(e.Message); } return(false); } // Decompress if compressed db._LngGlobal = JDLZ.Decompress(db._LngGlobal); db._LngLabels = JDLZ.Decompress(db._LngLabels); // Use pointers to speed up process fixed(byte *strptr = &db._LngGlobal[0], labptr = &db._LngLabels[0]) { db.STRBlocks.Collections.Add(new STRBlock(strptr, labptr, db._LngGlobal.Length, db._LngLabels.Length, db)); } return(true); }
/// <summary> /// Decompile entire cartypeinfo block into Vector of separate elements. /// </summary> /// <param name="byteptr_t">Pointer to the beginning of cartypeinfo block in Global data.</param> /// <param name="length">Length of the block to be read.</param> /// <param name="db">Database to which add classes.</param> private static unsafe void E_CarTypeInfo(byte *byteptr_t, uint length, Database.Carbon db) { uint size = 0xD0; for (uint loop = 0; loop < length / size; ++loop) { uint offset = 8 + loop * size; // current offset of the cartypeinfo (padding included) // Get CollectionName string CName = ScriptX.NullTerminatedString(byteptr_t + offset, 0x10); CName = Resolve.GetPathFromCollection(CName); Map.BinKeys[Bin.Hash(CName)] = CName; if (!LibColBlockExists) { Map.CollisionMap[Vlt.Hash(CName)] = CName; } var Class = new CarTypeInfo((IntPtr)(byteptr_t + offset), CName, db); db.CarTypeInfos.Collections.Add(Class); } }
/// <summary> /// Saves database data into English_Global file. /// </summary> /// <param name="Language_dir">Game directory.</param> /// <param name="db">Database of classes.</param> /// <returns>True if success.</returns> public static bool SaveLanguage(string Language_dir, Database.Carbon db) { Language_dir += @"\LANGUAGES\"; using (var br = new BinaryReader(new MemoryStream(db._LngGlobal))) using (var bw = new BinaryWriter(File.Open(Language_dir + "English_Global.bin", FileMode.Create))) { while (br.BaseStream.Position < br.BaseStream.Length) { // Set Offset, ID and Size values, read starting in the beginning of the file uint WriterSlotOffset = (uint)br.BaseStream.Position; uint WriterSlotID = br.ReadUInt32(); int WriterSlotSize = br.ReadInt32(); // If one of the necessary slots is reached, replace it switch (WriterSlotID) { case Global.STRBlocks: br.BaseStream.Position += 0xC; var categ = ScriptX.NullTerminatedString(br, 0x10); br.BaseStream.Position = WriterSlotOffset + 8; if (categ == "Global") { bw.Write(db.STRBlocks[0].Assemble()); br.BaseStream.Position += WriterSlotSize; break; } else { goto default; } default: bw.Write(WriterSlotID); bw.Write(WriterSlotSize); bw.Write(br.ReadBytes(WriterSlotSize)); break; } } } using (var br = new BinaryReader(new MemoryStream(db._LngLabels))) using (var bw = new BinaryWriter(File.Open(Language_dir + "Labels_Global.bin", FileMode.Create))) { while (br.BaseStream.Position < br.BaseStream.Length) { // Set Offset, ID and Size values, read starting in the beginning of the file uint WriterSlotOffset = (uint)br.BaseStream.Position; uint WriterSlotID = br.ReadUInt32(); int WriterSlotSize = br.ReadInt32(); // If one of the necessary slots is reached, replace it switch (WriterSlotID) { case Global.STRBlocks: br.BaseStream.Position += 0xC; var categ = ScriptX.NullTerminatedString(br, 0x10); br.BaseStream.Position = WriterSlotOffset + 8; if (categ == "Global") { bw.Write(db.STRBlocks[0].ParseLabels()); br.BaseStream.Position += WriterSlotSize; break; } else { goto default; } default: bw.Write(WriterSlotID); bw.Write(WriterSlotSize); bw.Write(br.ReadBytes(WriterSlotSize)); break; } } } return(true); }
/// <summary> /// Writes TPK block into Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> /// <param name="index">Index of the TPK block in the database</param> private static void I_TPKBlock(Database.Carbon db, BinaryWriter bw, ref int index) { I_GlobalLibBlock(bw); bw.Write(db.TPKBlocks[index++].Assemble()); }
// Default constructor: disassemble material public unsafe Material(IntPtr byteptr_t, string CName, Database.Carbon db) { this.Database = db; this.CollectionName = CName; this.Disassemble((byte *)byteptr_t); }
// Default constructor: create new material public Material(string CName, Database.Carbon db) { this.Database = db; this.CollectionName = CName; Map.BinKeys[Bin.Hash(CName)] = CName; }
// Default constructor: disassemble frontend group public FNGroup(byte[] data, Database.Carbon db) { this.Database = db; this.Disassemble(data); }
/// <summary> /// Writes all car parts into the Global data. /// </summary> /// <param name="db">Database with classes.</param> /// <param name="bw">BinaryWriter for writing data.</param> private static void I_CarParts(Database.Carbon db, BinaryWriter bw) { bw.Write(Global.CarParts); bw.Write(0xFFFFFFFF); // temp size int initial_size = (int)bw.BaseStream.Position; int CarIDOffset = initial_size + 0x30; int PartNumOffset = initial_size + 0x40; int padding = 0; var keylists = new List <uint>(); var Intermid56 = new List <Part56>(); var UsedPart56 = new List <Part56>(); // Copy for processing for (int a1 = 0; a1 < db.SlotTypes.Part56.Count; ++a1) { Intermid56.Add(db.SlotTypes.Part56[a1].MemoryCast()); } // Go through all cartypeinfo, set correct usagetype and keys foreach (var car in db.CarTypeInfos.Collections) { bool CarDoesExist = false; int index = 0; uint ckey = car.BinKey; uint okey = Bin.Hash(car.OriginalName); keylists.Add(ckey); for (index = 0; index < Intermid56.Count; ++index) { if (okey == Intermid56[index].Key) { CarDoesExist = true; break; } } if (CarDoesExist) { if (ckey != okey) { Intermid56[index].Key = ckey; } if (car.Modified && car.UsageType != Intermid56[index].Usage) { Intermid56[index].SetUsage(car.UsageType); } } else { var Class = new Part56(car.CollectionName, (byte)index, car.UsageType); Intermid56.Add(Class); } } // Make new list of only used Part56 to write byte curlength = 0; for (int a1 = 0; a1 < Intermid56.Count; ++a1) { if (Intermid56[a1].IsCar && !keylists.Contains(Intermid56[a1].Key)) { continue; } Intermid56[a1].SetIndex(curlength++); UsedPart56.Add(Intermid56[a1]); } // Write parts 1-4 bw.Write(db.SlotTypes.Part0.Data); bw.Write(db.SlotTypes.Part1.Data); bw.Write(db.SlotTypes.Part2.Data); bw.Write(db.SlotTypes.Part3.Data); bw.Write(db.SlotTypes.Part4.Data); // Write part 5 int part5size = UsedPart56.Count * 4; padding = 0x10 - ((part5size + 8) % 0x10); if (padding == 0x10) { padding = 0; } part5size += padding; bw.Write(CarParts.Part5); bw.Write(part5size); for (int a1 = 0; a1 < UsedPart56.Count; ++a1) { bw.Write(UsedPart56[a1].Key); } for (int a1 = 0; a1 < padding; ++a1) { bw.Write((byte)0); } // Write part 6 int part6size = 0; bw.Write(CarParts.Part6); int size6off = (int)bw.BaseStream.Position; bw.Write(0xFFFFFFFF); // temp size for (int a1 = 0; a1 < UsedPart56.Count; ++a1) { bw.Write(UsedPart56[a1].Data); part6size += UsedPart56[a1].Data.Length; } padding = 0x10 - ((part6size + 8) % 0x10); if (padding == 0x10) { padding = 0; } for (int a1 = 0; a1 < padding; ++a1) { bw.Write((byte)0); } part6size += padding; bw.BaseStream.Position = size6off; bw.Write(part6size); // Quick editing bw.BaseStream.Position = CarIDOffset; bw.Write(UsedPart56.Count); bw.BaseStream.Position = PartNumOffset; bw.Write(part6size / 4); bw.BaseStream.Position = initial_size - 4; bw.Write((int)bw.BaseStream.Length - initial_size); bw.BaseStream.Position = bw.BaseStream.Length; }
/// <summary> /// Loads GlobalA file and disassembles its blocks /// </summary> /// <param name="GlobalA_dir">Directory of the game.</param> /// <param name="db">Database of classes.</param> /// <returns>True if success.</returns> public static unsafe bool LoadGlobalA(string GlobalA_dir, Database.Carbon db) { GlobalA_dir += @"\GLOBAL\GlobalA.bun"; // Get everything from GlobalA.bun try { db._GlobalABUN = File.ReadAllBytes(GlobalA_dir); Log.Write("Reading data from GlobalA.bun..."); } catch (Exception e) { while (e.InnerException != null) { e = e.InnerException; } if (Process.MessageShow) { MessageBox.Show($"Error occured: {e.Message}", "Failure", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { Console.WriteLine(e.Message); } return(false); } // Decompress if compressed db._GlobalABUN = JDLZ.Decompress(db._GlobalABUN); // Use pointers to speed up process fixed(byte *byteptr_t = &db._GlobalABUN[0]) { uint offset = 0; // to calculate current offset uint ID = 0; // to get the ID of the block being read uint size = 0; // to get the size of the block being read while (offset < db._GlobalABUN.Length) { ID = *(uint *)(byteptr_t + offset); // read ID size = *(uint *)(byteptr_t + offset + 4); // read size if (offset + size > db._GlobalABUN.Length) { if (Process.MessageShow) { MessageBox.Show("GlobalA: unable to read beyond the stream.", "Failure"); } else { Console.WriteLine("GlobalA: unable to read beyond the stream."); } return(false); } switch (ID) { case Global.TPKBlocks: int count = db.TPKBlocks.Length; db.TPKBlocks.Collections.Add(new TPKBlock(byteptr_t + offset, count, db)); db.TPKBlocks[count].InGlobalA = true; break; case Global.FEngFiles: case Global.FNGCompress: E_FNGroup(byteptr_t + offset, size + 8, db); break; default: break; } offset += 8 + size; // advance in offset } } return(true); }