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)); }
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)); }
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)); }
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)); }
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); }