private void ProcessCarPartTable(Chunk chunk) { if (chunk.Size % 0xC != 0) { throw new InvalidDataException("Malformed car part table chunk"); } DebugTiming.BeginTiming("ProcessCarPartTable"); Progress?.Report("Processing car parts"); List <AaronCarPartAttribute> attributes = new List <AaronCarPartAttribute>(); var allCollections = _carPartService.GetCarPartCollections(); Dictionary <int, AaronCarPartCollection> collectionsDict = allCollections.ToDictionary(c => allCollections.IndexOf(c), c => c); while (Stream.Position < chunk.EndOffset) { DBCarPart dbCarPart = BinaryHelpers.ReadStruct <DBCarPart>(Reader); var part = new AaronCarPartRecord(); part.Name = HashResolver.Resolve(dbCarPart.Hash); part.Attributes = this.PrepareAttributes(dbCarPart); attributes.AddRange(part.Attributes); //_carPartService.GetCarPartCollections() collectionsDict[dbCarPart.CarIndex].Parts.Add(part); //_carPartService.GetCarPartCollectionByIndex(dbCarPart.CarIndex).Parts.Add(part); } var uniqueAttribs = attributes.DistinctBy(a => a.GetHashCode()).Count(); DebugTiming.EndTiming("ProcessCarPartTable"); // we can discard internal attribute data now _attributeOffsetTables.Clear(); _attributes.Clear(); }
private void GenerateCarPartData(IProgress <string> progress = null) { progress?.Report("Generating car part data"); // The index table is structured in 3 steps: // 1. Take the part array as it would appear in [04 46 03 00] // 2. Sort the array by part hash // 3. Sort the sorted array by the index of each part in the original array // aka .OrderBy().ThenBy() // The list of car part collections, ordered in ascending order by their hash. var sortedCollectionList = _carPartService.GetCarPartCollections() .DistinctBy(c => c.Hash) .OrderByDescending(c => c.Priority) .ThenBy(c => c.Hash) .ToList(); // The entire list of car parts, ordered as they would appear in [04 46 03 00]. var partArray = sortedCollectionList.SelectMany(c => c.Parts).ToList(); progress?.Report("Generating part index table"); BeginChunk(0x3460E); { // The mapping of part object->array index. Dictionary <AaronCarPartRecord, int> carPartIndexDictionary = new Dictionary <AaronCarPartRecord, int>(); for (int i = 0; i < partArray.Count; i++) { carPartIndexDictionary.Add(partArray[i], i); } foreach (var partPair in carPartIndexDictionary.OrderBy(pair => pair.Key.Hash).ThenBy(pair => pair.Value)) { Writer.Write(partPair.Key.Hash); Writer.Write(partPair.Value); } } EndChunk(); progress?.Report("Generating CARINFO_CARPART chunk"); BeginChunk(0x80034602); { // The list of all part attributes. var attributes = partArray.OrderBy(p => p.Hash).SelectMany(p => p.Attributes).ToList(); // The list of UNIQUE part attributes. var distinctAttributes = attributes.DistinctBy(a => a.GetHashCode()).ToList(); // The list of racer cars. var racerList = _carService.GetCarsByType(CarUsageType.Racing); // The list of strings. var strings = _carPartService.GetStrings() .Concat(distinctAttributes.SelectMany(a => a.Strings)) .Where(s => !string.IsNullOrEmpty(s)) .Distinct() .ToList(); { // Generate header BeginChunk(0x34603); Stream.Seek(8, SeekOrigin.Current); var carPartPack = new CarPartPack(); carPartPack.Version = 6; carPartPack.NumParts = partArray.Count; // [04 46 03 00] carPartPack.NumAttributes = distinctAttributes.Count; // [05 46 03 00] carPartPack.NumModelTables = racerList.Count; // [0A 46 03 00] carPartPack.NumTypeNames = sortedCollectionList.Count; // [0B 46 03 00] BinaryHelpers.WriteStruct(Writer, carPartPack); EndChunk(); } // A mapping of string hash codes to relative offsets within the string table. Dictionary <int, int> stringOffsets = new Dictionary <int, int>(); { // Generate string table BeginChunk(0x34606); var startChunkPos = Stream.Position; foreach (var s in strings) { var hash = s.GetHashCode(); var streamPosition = (int)(Stream.Position - startChunkPos); if (streamPosition % 4 != 0) { throw new Exception("Something went horribly wrong"); } stringOffsets.Add(hash, streamPosition); Writer.WriteAlignedString(s); } EndChunk(); } // A mapping of attribute hash codes to offsets within the attribute table. Dictionary <int, int> attributeIndexDictionary = new Dictionary <int, int>(); for (var index = 0; index < distinctAttributes.Count; index++) { if (index > ushort.MaxValue) { throw new Exception("Congratulations. You've managed to have too many unique attributes defined. Good job! Time to start deleting things."); } var aaronCarPartAttribute = distinctAttributes[index]; attributeIndexDictionary.Add(aaronCarPartAttribute.GetHashCode(), index); } Dictionary <uint, int> partTags = new Dictionary <uint, int>(); int partTag = 0; { // Generate attribute offset tables BeginChunk(0x3460C); var distinctParts = partArray.DistinctBy(p => p.Hash).ToList(); foreach (var aaronCarPartRecord in distinctParts) { partTags[aaronCarPartRecord.Hash] = partTag; Writer.Write((ushort)aaronCarPartRecord.Attributes.Count); foreach (var aaronCarPartAttribute in aaronCarPartRecord.Attributes) { Writer.Write((ushort)attributeIndexDictionary[aaronCarPartAttribute.GetHashCode()]); } partTag += aaronCarPartRecord.Attributes.Count + 1; } NextAlignment(4); EndChunk(); } { // Generate attributes table BeginChunk(0x34605); foreach (var aaronCarPartAttribute in distinctAttributes) { var newAttrib = new CarPartAttribute(); newAttrib.NameHash = aaronCarPartAttribute.Hash; switch (newAttrib.NameHash) { case 0xB1027477: case 0x46B79643: case 0xFD35FE70: case 0x7D65A926: if (aaronCarPartAttribute.Strings.Count > 1) { throw new Exception("Invalid string reference attribute data!"); } if (aaronCarPartAttribute.Strings.Count == 0 || aaronCarPartAttribute.Strings[0] == "") { newAttrib.iParam = -1; } else { newAttrib.iParam = (int)(stringOffsets[aaronCarPartAttribute.Strings[0].GetHashCode()] / 4); } //newAttrib.iParam = stringOffsetTable[] //attribList[i].Strings.Add(AaronCarPartManager.Get().PartNames[attribList[i].Data.iParam * 4]); break; case 0xFE613B98: ushort offs1 = 0xFFFF; ushort offs2 = 0xFFFF; if (aaronCarPartAttribute.Strings.Count > 0 && aaronCarPartAttribute.Strings[0] != "") { int offs1i = stringOffsets[aaronCarPartAttribute.Strings[0].GetHashCode()] >> 2; if (offs1i > ushort.MaxValue) { throw new Exception("string 1 out of bounds"); } offs1 = (ushort)offs1i; } if (aaronCarPartAttribute.Strings.Count > 1 && aaronCarPartAttribute.Strings[1] != "") { int offs2i = stringOffsets[aaronCarPartAttribute.Strings[1].GetHashCode()] >> 2; if (offs2i > ushort.MaxValue) { throw new Exception("string 2 out of bounds"); } offs2 = (ushort)offs2i; } newAttrib.iParam = (offs2 << 16) | offs1; //attribList[i].Strings.Add(AaronCarPartManager.Get().PartNames[(attribList[i].Data.iParam & 0xFFFF) * 4]); //attribList[i].Strings.Add(AaronCarPartManager.Get().PartNames[((attribList[i].Data.iParam >> 16) & 0xFFFF) * 4]); break; case 0x8C185134: if (aaronCarPartAttribute.Value is string s) { newAttrib.uParam = Hashing.FilteredBinHash(s); } else { newAttrib.uParam = Convert.ToUInt32(aaronCarPartAttribute.Value); } break; case 0x9239CF16: newAttrib.uParam = Convert.ToUInt16(aaronCarPartAttribute.Value); //CarPartID cpi = CarPartID.TryParse() //if (Enum.TryParse((string) aaronCarPartAttribute.Value, out CarPartID cpi)) //{ // ushort us = (ushort) cpi; // newAttrib.iParam = us << 8; //} //else //{ // throw new Exception(); //} break; default: if (aaronCarPartAttribute.Value is bool b) { newAttrib.iParam = b ? 1 : 0; } else if (aaronCarPartAttribute.Value is long u) { newAttrib.uParam = unchecked ((uint)u); } else if (aaronCarPartAttribute.Value is double f) { newAttrib.fParam = Convert.ToSingle(f); } else { throw new Exception(); } break; } BinaryHelpers.WriteStruct(Writer, newAttrib); } EndChunk(); } { // Generate interior data BeginChunk(0x3460A); { var lodLevels = new[] { 'A', 'B', 'C', 'D', 'E' }; foreach (var aaronCarRecord in racerList) { Writer.Write(0xFFFF0000); foreach (var lodLevel in lodLevels) { var str = $"{aaronCarRecord.CarTypeName}_KIT00_INTERIORHI_{lodLevel}"; Writer.Write(Hashing.BinHash(str)); for (int i = 0; i <= 10; i++) { Writer.Write(0xFFFFFFFF); } } } } EndChunk(); } { // Generate collection hash list BeginChunk(0x3460B); foreach (var aaronCarPartCollection in sortedCollectionList) { Writer.Write(aaronCarPartCollection.Hash); } EndChunk(); } { // Generate car part array BeginChunk(0x34604); for (var index = 0; index < sortedCollectionList.Count; index++) { var aaronCarPartCollection = sortedCollectionList[index]; foreach (var aaronCarPartRecord in aaronCarPartCollection.Parts) { var dbcp = new DBCarPart(); dbcp.CarIndex = (short)index; dbcp.Hash = aaronCarPartRecord.Hash; dbcp.AttributeTableOffset = partTags[dbcp.Hash]; BinaryHelpers.WriteStruct(Writer, dbcp); } } EndChunk(); } } EndChunk(); }
public string GenerateProject(string directory) { AaronProject aaronProject = new AaronProject(); aaronProject.Version = AaronProjectVersion; aaronProject.CarsDirectory = Path.Combine(directory, "Cars"); aaronProject.CarPartsDirectory = Path.Combine(directory, "CarParts"); aaronProject.PresetCarsDirectory = Path.Combine(directory, "PresetCars"); aaronProject.PresetSkinsDirectory = Path.Combine(directory, "PresetSkins"); aaronProject.DataTablesDirectory = Path.Combine(directory, "DataTables"); aaronProject.OutputFilePath = Path.Combine(directory, "bin", "GlobalC.lzc"); aaronProject.SlotOverrideData = _carPartService.GetSlotOverrideData(); Directory.CreateDirectory(directory); Directory.CreateDirectory(aaronProject.CarsDirectory); Directory.CreateDirectory(aaronProject.CarPartsDirectory); Directory.CreateDirectory(aaronProject.PresetCarsDirectory); Directory.CreateDirectory(aaronProject.PresetSkinsDirectory); Directory.CreateDirectory(aaronProject.DataTablesDirectory); Directory.CreateDirectory(Path.Combine(directory, "bin")); var projectPath = Path.Combine(directory, "project.aproj"); File.WriteAllText(projectPath, Serialization.Serialize(aaronProject)); DebugTiming.BeginTiming("ExportCars"); foreach (var aaronCarRecord in _carService.GetCars()) { File.WriteAllText(Path.Combine(aaronProject.CarsDirectory, aaronCarRecord.CarTypeName + ".json"), Serialization.Serialize(aaronCarRecord)); } DebugTiming.EndTiming("ExportCars"); DebugTiming.BeginTiming("ExportCarParts"); { var carPartCollections = _carPartService.GetCarPartCollections(); foreach (var carPartCollection in carPartCollections) { var fileName = Path.Combine(aaronProject.CarPartsDirectory, $"{carPartCollection.Name}.json"); var serialized = Serialization.Serialize(carPartCollection); File.WriteAllText(fileName, serialized); } } DebugTiming.EndTiming("ExportCarParts"); DebugTiming.BeginTiming("ExportPresets"); foreach (var aaronPresetCar in _presetCarService.GetPresetCars()) { File.WriteAllText( Path.Combine(aaronProject.PresetCarsDirectory, $"{aaronPresetCar.PresetName}.xml"), PresetConverter.ConvertAaronPresetToServerXML(aaronPresetCar).DataContractSerializeObject()); } DebugTiming.EndTiming("ExportPresets"); DebugTiming.BeginTiming("ExportPresetSkins"); foreach (var aaronPresetSkinRecord in _presetSkinService.GetPresetSkins()) { File.WriteAllText( Path.Combine(aaronProject.PresetSkinsDirectory, $"{aaronPresetSkinRecord.PresetName}.json"), Serialization.Serialize(aaronPresetSkinRecord)); } DebugTiming.EndTiming("ExportPresetSkins"); DebugTiming.BeginTiming("ExportDataTables"); foreach (var aaronDataTable in _dataTableService.GetDataTables()) { File.WriteAllText( Path.Combine(aaronProject.DataTablesDirectory, $"{aaronDataTable.Name}.json"), Serialization.Serialize(aaronDataTable)); } DebugTiming.EndTiming("ExportDataTables"); File.WriteAllText(Path.Combine(directory, "strings.json"), Serialization.Serialize(_carPartService.GetStrings())); return(projectPath); }