Skip to content

Commit 4e0199c

Browse files
committed
- Added Raw Model Copy Functionality.
- Batched model copying into a transaction for speed and safety.
1 parent 06ab3f7 commit 4e0199c

File tree

3 files changed

+130
-28
lines changed

3 files changed

+130
-28
lines changed

xivModdingFramework/Models/FileTypes/Mdl.cs

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4407,14 +4407,29 @@ public async Task AddRacialModel(int setId, string slot, XivRace newRace, string
44074407
/// <param name="originalPath"></param>
44084408
/// <param name="newPath"></param>
44094409
/// <returns></returns>
4410-
public async Task<long> CopyModel(string originalPath, string newPath, string source)
4410+
public async Task<long> CopyModel(string originalPath, string newPath, string source, bool copyTextures = false)
44114411
{
44124412
var _dat = new Dat(_gameDirectory);
4413-
var model = await GetModel(originalPath);
4414-
var xMdl = await GetRawMdlData(originalPath);
4413+
var _index = new Index(_gameDirectory);
4414+
var _modding = new Modding(_gameDirectory);
4415+
4416+
var fromRoot = await XivCache.GetFirstRoot(originalPath);
4417+
var toRoot = await XivCache.GetFirstRoot(newPath);
4418+
4419+
IItem item = null;
4420+
if (toRoot != null)
4421+
{
4422+
item = toRoot.GetFirstItem();
4423+
}
4424+
4425+
var df = IOUtil.GetDataFileFromPath(originalPath);
4426+
4427+
var index = await _index.GetIndexFile(df);
4428+
var modlist = await _modding.GetModListAsync();
44154429

4416-
var root = await XivCache.GetFirstRoot(newPath);
4417-
var item = root.GetFirstItem();
4430+
var offset = index.Get8xDataOffset(originalPath);
4431+
var xMdl = await GetRawMdlData(originalPath, false, offset);
4432+
var model = TTModel.FromRaw(xMdl);
44184433

44194434
var originalRace = IOUtil.GetRaceFromPath(originalPath);
44204435
var newRace = IOUtil.GetRaceFromPath(newPath);
@@ -4426,56 +4441,127 @@ public async Task<long> CopyModel(string originalPath, string newPath, string so
44264441
ModelModifiers.FixUpSkinReferences(model, newPath);
44274442
}
44284443

4429-
var _imc = new Imc(_gameDirectory);
4430-
4431-
var imcEntries = await _imc.GetEntries(await root.GetImcEntryPaths());
4432-
4433-
var materialSets = new HashSet<byte>();
4434-
imcEntries.ForEach(x => materialSets.Add(x.MaterialSet));
4435-
44364444
// Language is irrelevant here.
44374445
var _mtrl = new Mtrl(_gameDirectory, IOUtil.GetDataFileFromPath(newPath), XivLanguage.None);
44384446

44394447
// Get all variant materials.
4440-
var materialPaths = await GetReferencedMaterialPaths(originalPath, -1, false, false);
4448+
var materialPaths = await GetReferencedMaterialPaths(originalPath, -1, false, false, index, modlist);
4449+
44414450

44424451
var _raceRegex = new Regex("c[0-9]{4}");
44434452

4453+
Dictionary<string, string> validNewMaterials = new Dictionary<string, string>();
4454+
HashSet<string> copiedPaths = new HashSet<string>();
44444455
// Update Material References and clone materials.
4445-
foreach(var material in materialPaths)
4456+
foreach (var material in materialPaths)
44464457
{
4447-
var newMtrlPath = _raceRegex.Replace(material, "c" + newRace.GetRaceCode());
4448-
if (newMtrlPath == material) continue;
44494458

4459+
// Get the new path.
4460+
var path = RootCloner.UpdatePath(fromRoot, toRoot, material);
4461+
4462+
// Adjust race code entries if needed.
4463+
if (toRoot.Info.PrimaryType == XivItemType.equipment || toRoot.Info.PrimaryType == XivItemType.accessory)
4464+
{
4465+
path = _raceRegex.Replace(path, "c" + newRace.GetRaceCode());
4466+
}
4467+
4468+
// Get file names.
44504469
var io = material.LastIndexOf("/", StringComparison.Ordinal);
4451-
var baseMatName = material.Substring(io, material.Length - io);
4470+
var originalMatName = material.Substring(io, material.Length - io);
4471+
4472+
io = path.LastIndexOf("/", StringComparison.Ordinal);
4473+
var newMatName = path.Substring(io, path.Length - io);
4474+
44524475

4453-
io = newMtrlPath.LastIndexOf("/", StringComparison.Ordinal);
4454-
var newMatName = newMtrlPath.Substring(io, newMtrlPath.Length - io);
44554476
// Time to copy the materials!
44564477
try
44574478
{
4458-
var mtrlData = await _dat.GetType2Data(material, false);
4459-
var compressedData = await _dat.CreateType2Data(mtrlData);
4460-
await _dat.WriteModFile(compressedData, newMtrlPath, source, item);
4479+
offset = index.Get8xDataOffset(material);
4480+
var mtrl = await _mtrl.GetMtrlData(offset, material, 11);
4481+
4482+
if (copyTextures)
4483+
{
4484+
for(int i = 0; i < mtrl.TexturePathList.Count; i++)
4485+
{
4486+
var tex = mtrl.TexturePathList[i];
4487+
var ntex = RootCloner.UpdatePath(fromRoot, toRoot, tex);
4488+
if (toRoot.Info.PrimaryType == XivItemType.equipment || toRoot.Info.PrimaryType == XivItemType.accessory)
4489+
{
4490+
ntex = _raceRegex.Replace(ntex, "c" + newRace.GetRaceCode());
4491+
}
4492+
4493+
mtrl.TexturePathList[i] = ntex;
4494+
4495+
await _dat.CopyFile(tex, ntex, source, true, item, index, modlist);
4496+
}
4497+
}
4498+
4499+
mtrl.MTRLPath = path;
4500+
await _mtrl.ImportMtrl(mtrl, item, source, index, modlist);
4501+
4502+
if(!validNewMaterials.ContainsKey(newMatName))
4503+
{
4504+
validNewMaterials.Add(newMatName, path);
4505+
}
4506+
copiedPaths.Add(path);
4507+
44614508

44624509
// Switch out any material references to the material in the model file.
4463-
foreach(var m in model.MeshGroups)
4510+
foreach (var m in model.MeshGroups)
44644511
{
4465-
if(m.Material == baseMatName)
4512+
if(m.Material == originalMatName)
44664513
{
44674514
m.Material = newMatName;
44684515
}
44694516
}
4517+
44704518
} catch(Exception ex)
44714519
{
44724520
// Hmmm. The original material didn't exist. This is pretty not awesome, but I guess a non-critical error...?
44734521
}
44744522
}
44754523

4524+
if (Imc.UsesImc(toRoot) && Imc.UsesImc(fromRoot))
4525+
{
4526+
var _imc = new Imc(XivCache.GameInfo.GameDirectory);
4527+
4528+
var toEntries = await _imc.GetEntries(await toRoot.GetImcEntryPaths(), false, index, modlist);
4529+
var fromEntries = await _imc.GetEntries(await fromRoot.GetImcEntryPaths(), false, index, modlist);
4530+
4531+
var toSets = toEntries.Select(x => x.MaterialSet).Where(x => x != 0).ToList();
4532+
var fromSets = fromEntries.Select(x => x.MaterialSet).Where(x => x != 0).ToList();
4533+
4534+
if(fromSets.Count > 0 && toSets.Count > 0)
4535+
{
4536+
var vReplace = new Regex("/v[0-9]{4}/");
4537+
4538+
// Validate that sufficient material sets have been created at the destination root.
4539+
foreach(var mkv in validNewMaterials)
4540+
{
4541+
var validPath = mkv.Value;
4542+
foreach(var msetId in toSets)
4543+
{
4544+
var testPath = vReplace.Replace(validPath, "/v" + msetId.ToString().PadLeft(4, '0') + "/");
4545+
var copied = copiedPaths.Contains(testPath);
4546+
4547+
// Missing a material set, copy in the known valid material.
4548+
if(!copied)
4549+
{
4550+
await _dat.CopyFile(validPath, testPath, source, true, item, index, modlist);
4551+
}
4552+
}
4553+
}
4554+
}
4555+
}
4556+
4557+
44764558
// Save the final modified mdl.
44774559
var data = await MakeNewMdlFile(model, xMdl);
4478-
var offset = await _dat.WriteModFile(data, newPath, source, item);
4560+
offset = await _dat.WriteModFile(data, newPath, source, item, index, modlist);
4561+
4562+
await _index.SaveIndexFile(index);
4563+
await _modding.SaveModListAsync(modlist);
4564+
44794565
return offset;
44804566
}
44814567

xivModdingFramework/Mods/RootCloner.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ namespace xivModdingFramework.Mods
2525
{
2626
public static class RootCloner
2727
{
28+
29+
public static bool IsSupported(XivDependencyRoot root)
30+
{
31+
if (root.Info.PrimaryType == XivItemType.equipment) return true;
32+
if (root.Info.PrimaryType == XivItemType.accessory) return true;
33+
if (root.Info.PrimaryType == XivItemType.human && root.Info.SecondaryType == XivItemType.hair) return true;
34+
35+
return false;
36+
}
37+
2838
/// <summary>
2939
/// Copies the entirety of a given root to a new root.
3040
/// </summary>
@@ -34,6 +44,11 @@ public static class RootCloner
3444
/// <returns>Returns a Dictionary of all the file conversion</returns>
3545
public static async Task<Dictionary<string, string>> CloneRoot(XivDependencyRoot Source, XivDependencyRoot Destination, string ApplicationSource, int singleVariant = -1, string saveDirectory = null, IProgress<string> ProgressReporter = null, IndexFile index = null, ModList modlist = null, ModPack modPack = null)
3646
{
47+
if(!IsSupported(Source) || !IsSupported(Destination))
48+
{
49+
throw new InvalidDataException("Cannot clone unsupported root.");
50+
}
51+
3752

3853
if (ProgressReporter != null)
3954
{
@@ -544,7 +559,7 @@ private static async Task<ItemMetadata> GetCachedMetadata(IndexFile index, ModLi
544559
const string CommonPath = "chara/common/";
545560
private static readonly Regex RemoveRootPathRegex = new Regex("chara\\/[a-z]+\\/[a-z][0-9]{4}(?:\\/obj\\/[a-z]+\\/[a-z][0-9]{4})?\\/(.+)");
546561

547-
private static string UpdatePath(XivDependencyRoot Source, XivDependencyRoot Destination, string path)
562+
internal static string UpdatePath(XivDependencyRoot Source, XivDependencyRoot Destination, string path)
548563
{
549564
// For common path items, copy them to our own personal mimic of the path.
550565
if (path.StartsWith(CommonPath))

xivModdingFramework/Variants/FileTypes/Imc.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,15 @@ public async Task<FullImcInfo> GetFullImcInfo(IItemModel item, IndexFile index =
176176
/// </summary>
177177
/// <param name="pathsWithOffsets"></param>
178178
/// <returns></returns>
179-
public async Task<List<XivImc>> GetEntries(List<string> pathsWithOffsets, bool forceDefault = false)
179+
public async Task<List<XivImc>> GetEntries(List<string> pathsWithOffsets, bool forceDefault = false, IndexFile index = null, ModList modlist = null)
180180
{
181181
var entries = new List<XivImc>();
182182
var dat = new Dat(_gameDirectory);
183183

184184
var lastPath = "";
185185
byte[] imcByteData = new byte[0];
186186

187+
187188
foreach (var combinedPath in pathsWithOffsets)
188189
{
189190
var binaryMatch = _binaryOffsetRegex.Match(combinedPath);
@@ -198,7 +199,7 @@ public async Task<List<XivImc>> GetEntries(List<string> pathsWithOffsets, bool f
198199
// Only reload this data if we need to.
199200
if (path != lastPath)
200201
{
201-
imcByteData = await dat.GetType2Data(path, forceDefault);
202+
imcByteData = await dat.GetType2Data(path, forceDefault, index, modlist);
202203
}
203204
lastPath = path;
204205

0 commit comments

Comments
 (0)