private AaronCarPartAttribute ConvertAttribute(CarPartAttribute rawAttribute) { AaronCarPartAttribute attribute = new AaronCarPartAttribute(); attribute.Name = HashResolver.Resolve(rawAttribute.NameHash); switch (attribute.Name) { case "TEXTURE": case "LOD_CHARACTERS_OFFSET": case "NAME_OFFSET": this.LoadSingleAttribString(attribute, rawAttribute); break; case "LOD_BASE_NAME": this.LoadDoubleAttribString(attribute, rawAttribute); break; default: //attribute.Value = rawAttribute.iParam; this.SetAttributeValue(attribute, rawAttribute); break; } return(attribute); }
private void LoadSingleAttribString(AaronCarPartAttribute attribute, CarPartAttribute rawAttribute) { if (rawAttribute.uParam != 0xFFFFFFFF) { var str = _stringOffsetDictionary[rawAttribute.uParam * 4]; attribute.Strings.Add(str); } else { attribute.Strings.Add(""); } }
private void LoadDoubleAttribString(AaronCarPartAttribute attribute, CarPartAttribute rawAttribute) { ushort component1 = (ushort)(rawAttribute.uParam & 0xFFFF); ushort component2 = (ushort)((rawAttribute.uParam >> 16) & 0xFFFF); if (component1 != 0xFFFFu) { attribute.Strings.Add(_stringOffsetDictionary[component1 * 4]); } else { attribute.Strings.Add(""); } if (component2 != 0xFFFFu) { attribute.Strings.Add(_stringOffsetDictionary[component2 * 4]); } else { attribute.Strings.Add(""); } }
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(); }
private void SetAttributeValue(AaronCarPartAttribute attribute, CarPartAttribute rawAttribute) { switch (attribute.Name) { case "LOD_NAME_PREFIX_SELECTOR": case "MAX_LOD": case "CV": case "LANGUAGEHASH": case "KITNUMBER": case "MODEL_TABLE_OFFSET": case "MORPHTARGET_NUM": case "0xE80A3B62": case "0xCE7D8DB5": case "0xEB0101E2": case "0x7D29CF3E": case "0xEBB03E66": case "RED": case "GREEN": case "BLUE": case "GLOSS": case "MATTE": case "0x6BA02C05": case "SWATCH": case "0xD68A7BAB": case "PAINTGROUP": case "MAT0": case "MAT1": case "MAT2": case "MAT3": case "MAT4": case "MAT5": case "MAT6": case "MAT7": case "TEXTUREHASH": case "0xC9818DFC": case "MATNAMEA": case "MATNAMEB": case "DAMAGELEVEL": case "0x04B39858": case "GROUPLANGUAGEHASH": case "0x5412A1D9": attribute.Value = rawAttribute.uParam; break; case "PARTID_UPGRADE_GROUP": if (trackedUpgradeGroups.Add(rawAttribute.uParam)) { ushort sp = (ushort)rawAttribute.uParam; Debug.WriteLine("PARTID_UPGRADE_GROUP: {0} ({1})", Convert.ToString(sp & 0xff, 2).PadLeft(8, '0'), rawAttribute.uParam); Debug.WriteLine((CarPartID)(sp >> 8)); } goto default; // attribute.Value = (CarPartID)(rawAttribute.iParam >> 8); // break; case "STOCK": attribute.Value = rawAttribute.iParam == 1; break; case "BLEND": attribute.Value = rawAttribute.fParam; break; default: //if (trackedUnknownAttributes.Add(rawAttribute)) //{ // Debug.WriteLine("Unknown attribute: {0} U {1} I {2} F {3}", attribute.Name, rawAttribute.uParam, rawAttribute.iParam, rawAttribute.fParam); //} attribute.Value = rawAttribute.uParam; break; } }