Exemplo n.º 1
0
        private unsafe string ResolveTailMaterialPath(Human *human, byte *materialFilenameStr)
        {
            int    variant = 1;
            ushort tailId  = human->TailEarId;

            if (human->Race == (byte)Race.Hrothgar)
            {
                variant = human->LipColorFurPattern;
                tailId  = 1;
            }

            var remainingString = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr + 13));

            return(BuildTailMaterialPath(human->RaceSexId, tailId, variant, remainingString));
        }
Exemplo n.º 2
0
        private unsafe string ResolveFaceMaterialPath(Human *human, byte *materialFilenameStr, bool overrideFace)
        {
            ushort raceSexId = 0;
            ushort faceId    = 0;

            if (overrideFace)
            {
                // this is vanilla behavior for slot 13 only, overriding to face 1 and potentially a different race
                // this creates seams when modding aura faces
                raceSexId = GetMaterialRaceSexIdOverride(human->RaceSexId);

                if (raceSexId == (ushort)RaceSexId.HighlanderM || raceSexId == (ushort)RaceSexId.HighlanderF || human->Clan == (byte)Clan.Xaela)
                {
                    faceId = 101;
                }
                else
                {
                    faceId = 1;
                }

                if (human->BodyType == 3)
                {
                    faceId += 90;
                }
            }
            else
            {
                raceSexId = human->RaceSexId;

                faceId = human->FaceId;

                // hrothgar only have one set of "etc" materials and viera only have one set of "fac" materials
                // the game simply checks the string
                if (human->Clan == (byte)Clan.TheLost && *(int *)(materialFilenameStr + 13) == 0x6374655F || // 'cte_'
                    human->Clan == (byte)Clan.Veena && *(int *)(materialFilenameStr + 13) == 0x6361665F)     // 'caf_'
                {
                    faceId -= 100;
                }
            }

            var remainingString = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr + 13));

            return(BuildFaceMaterialPath(raceSexId, faceId, remainingString));
        }
Exemplo n.º 3
0
        private unsafe string ResolveHairMaterialPath(Human *human, byte *materialFilenameStr)
        {
            ushort hairId    = human->HairId;
            ushort raceSexId = human->RaceSexId;

            if (hairId > 115 && hairId <= 200)
            {
                raceSexId = human->Sex == (byte)Sex.Female ? (ushort)RaceSexId.MidlanderF : (ushort)RaceSexId.MidlanderM;
            }
            else if (hairId > 100)
            {
                if (human->RaceSexId != (ushort)RaceSexId.MiqoteM && human->RaceSexId != (ushort)RaceSexId.MiqoteF)
                {
                    raceSexId = human->Sex == (byte)Sex.Female ? (ushort)RaceSexId.MidlanderF : (ushort)RaceSexId.MidlanderM;
                }
            }

            var remainingString = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr + 8));

            return(BuildHairMaterialPath(raceSexId, hairId, remainingString));
        }
Exemplo n.º 4
0
        private unsafe string ResolveBodyMaterialPath(Human *human, byte *materialFilenameStr, bool checkClan, uint slot)
        {
            var variant = human->Race == (byte)Race.Hrothgar ? human->LipColorFurPattern : 1; // Hrothgar have fur variants

            var remainingString = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr + 13));

            // override cases
            if (_plugin.Configuration.EnableSkinOverride && human->BodyType != 3 && RaceMaterials.ContainsKey(human->RaceSexId))
            {
                var rme = RaceMaterials[human->RaceSexId];
                if (rme.Type == MaterialSkinType.RaceVariant || rme.Type == MaterialSkinType.RaceClanVariant)
                {
                    if (rme.Type == MaterialSkinType.RaceVariant)
                    {
#if DEBUG
                        PluginLog.Log($"[Human::ResolveMaterialPath] {(HumanModelSlots)slot} - player-added race variant used for race {(Race)human->Race}");
#endif
                        return(BuildBodyMaterialPath(human->RaceSexId, 1, variant, remainingString));
                    }
                    else if (rme.Type == MaterialSkinType.RaceClanVariant)
                    {
#if DEBUG
                        PluginLog.Log($"[Human::ResolveMaterialPath] {(HumanModelSlots)slot} - player-added race+clan variant used for race {(Race)human->Race}, clan {(Clan)human->Clan}");
#endif
                        return(BuildBodyMaterialPath(human->RaceSexId, human->Clan % 2 == 0 ? 101 : 1, variant, remainingString));
                    }
                }
            }

            // original game implementation
            var overridenRaceSexId = GetMaterialRaceSexIdOverride(human->RaceSexId);

            var bodyId = human->BodyType == 3 ? 91 : 1;
            if (checkClan && human->Clan == (byte)Clan.Xaela)
            {
                bodyId += 100; // Xaela have clan variants
            }
            return(BuildBodyMaterialPath(overridenRaceSexId, bodyId, variant, remainingString));
        }
Exemplo n.º 5
0
        private unsafe byte *ResolveMaterialPathDetour(Human *human, byte *outStrBuf, ulong bufSize, uint slot, byte *materialFilenameStr)
        {
#if DEBUG
            var materialFilename = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr));
            PluginLog.Log($"hook call => Human::ResolveMaterialPath(this={(long)human:X}, outStrBuf={(long)outStrBuf:X}, bufSize={bufSize}, slot={slot}, materialFilenameStr={materialFilename})");
#endif

            var outStr = "";

            switch (slot)
            {
            // equipment: head/body/arms/legs/feet
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
                if (materialFilenameStr[8] == 0x62)     // 'b' eg "mt_c0101b0001_a.mtrl" - yes this is how the game checks if a material is skin material
                {
                    outStr = ResolveBodyMaterialPath(human, materialFilenameStr, true, slot);
                }
                else
                {
                    var eqData = ((ChangedEquipData *)human->ChangedEquipData)[slot];
                    if (eqData.VariantID == 0)
                    {
                        return(null);    // game behavior
                    }
                    var fn = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr));
                    outStr = BuildEquipmentMaterialPath(eqData.SetID, eqData.VariantID, fn);
                }
                break;

            // equipment: ears/neck/wrist/rfinger/lfinger
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
                var equipData = ((ChangedEquipData *)human->ChangedEquipData)[slot];
                if (equipData.VariantID == 0)
                {
                    return(null);    // game behavior
                }
                var filename = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr));
                outStr = BuildAccessoryMaterialPath(equipData.SetID, equipData.VariantID, filename);
                break;

            // hair
            case 10:
                outStr = ResolveHairMaterialPath(human, materialFilenameStr);
                break;

            // face
            case 11:
                outStr = ResolveFaceMaterialPath(human, materialFilenameStr, false);
                break;

            // viera: ear, other races: tail
            case 12:
                if (human->Race == (byte)Race.Viera)
                {
                    var remainingString = Marshal.PtrToStringAnsi(new IntPtr(materialFilenameStr + 13));
                    outStr = BuildEarMaterialPath(human->RaceSexId, human->TailEarId, remainingString);
                }
                else
                {
                    outStr = ResolveTailMaterialPath(human, materialFilenameStr);
                }
                break;

            // note that for cases 13/14/15 the body model loaded is not the same race necessarily but something up the tree, there are hardcoded functions for this in the game exe
            // body model 2 (5 for aura)
            case 13:
                if (materialFilenameStr[8] == 0x66)     // 'f'
                {
                    if (_plugin.Configuration.FixGameBehavior)
                    {
                        outStr = ResolveFaceMaterialPath(human, materialFilenameStr, false);
                    }
                    else
                    {
                        outStr = ResolveFaceMaterialPath(human, materialFilenameStr, true);
                    }
                }
                else
                {
                    outStr = ResolveBodyMaterialPath(human, materialFilenameStr, true, slot);
                }
                break;

            // body model 2
            case 14:
                outStr = ResolveBodyMaterialPath(human, materialFilenameStr, true, slot);
                break;

            // body model 3
            case 15:
                // the game's version doesn't check the clan here and always loads b0001's skin even when the clan is xaela (who have their own skin)
                // unsure if this creates seam issues but its an easy fix anyway
                if (_plugin.Configuration.FixGameBehavior)
                {
                    outStr = ResolveBodyMaterialPath(human, materialFilenameStr, true, slot);
                }
                else
                {
                    outStr = ResolveBodyMaterialPath(human, materialFilenameStr, false, slot);
                }
                break;
            }

            var outStrBytes = System.Text.Encoding.ASCII.GetBytes(outStr);
            var strLen      = outStr.Length > (int)bufSize - 1 ? (int)bufSize - 1 : outStr.Length; // this should never happen, but the game uses sprintf_s so we'll be safe too
            Marshal.Copy(outStrBytes, 0, new IntPtr(outStrBuf), strLen);
            outStrBuf[strLen] = 0x00;                                                              // GetBytes doesnt result in a null-terminated string

            return(outStrBuf);
        }