Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        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("");
            }
        }
Exemplo n.º 3
0
        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("");
            }
        }
Exemplo n.º 4
0
        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();
        }
Exemplo n.º 5
0
        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;
            }
        }