Skip to content

Commit 7428d28

Browse files
committed
- Added functionality for writing to dat files at specified offsets
- Added functionality for calculating open space in (modified) dat files. - Added functionality for re-using the existing modded dat slot if it's sufficiently large to store the new file. - Added functionality for re-using open space in DAT files when doing singleton imports.
1 parent fa4fc02 commit 7428d28

File tree

2 files changed

+194
-19
lines changed

2 files changed

+194
-19
lines changed

xivModdingFramework/Mods/FileTypes/TTMP.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,11 @@ await Task.Run(async () =>
494494
Dictionary<string, int> FileTypes = new Dictionary<string, int>();
495495

496496

497+
var _modding = new Modding(XivCache.GameInfo.GameDirectory);
498+
var modList = _modding.GetModList();
499+
497500
// 0 - Extract the MPD file.
498-
using(var zf = ZipFile.Read(modPackDirectory.FullName))
501+
using (var zf = ZipFile.Read(modPackDirectory.FullName))
499502
{
500503
progress.Report((0, 0, "Unzipping TTMP File..."));
501504
var mpd = zf.Entries.First(x => x.FileName.EndsWith(".mpd"));
@@ -515,6 +518,16 @@ await Task.Run(async () =>
515518
}
516519
}
517520

521+
Dictionary<string, Mod> modsByFile = new Dictionary<string, Mod>();
522+
523+
foreach (var mod in modList.Mods)
524+
{
525+
if (!modsByFile.ContainsKey(mod.fullPath))
526+
{
527+
modsByFile.Add(mod.fullPath, mod);
528+
}
529+
}
530+
518531
// 1 - Copy all the mod data to the DAT files.
519532
var count = 0;
520533
progress.Report((0, 0, "Writing new mod data to DAT files..."));
@@ -527,7 +540,28 @@ await Task.Run(async () =>
527540
binaryReader.BaseStream.Seek(modJson.ModOffset, SeekOrigin.Begin);
528541
var data = binaryReader.ReadBytes(modJson.ModSize);
529542
var df = IOUtil.GetDataFileFromPath(modJson.FullPath);
530-
var offset = await dat.WriteToDat(data, df);
543+
544+
var size = data.Length;
545+
if (size % 256 != 0)
546+
{
547+
size += (256 - (size % 256));
548+
}
549+
550+
Mod mod = null;
551+
if (modsByFile.ContainsKey(modJson.FullPath))
552+
{
553+
mod= modsByFile[modJson.FullPath];
554+
}
555+
556+
uint offset = 0;
557+
558+
if(mod != null && mod.data.modSize >= size)
559+
{
560+
offset = await dat.WriteToDat(data, df, mod.data.modOffset);
561+
} else
562+
{
563+
offset = await dat.WriteToDat(data, df);
564+
}
531565
DatOffsets.Add(modJson.FullPath, offset);
532566

533567
var dataType = BitConverter.ToInt32(data, 4);
@@ -596,8 +630,6 @@ await Task.Run(async () =>
596630
progress.Report((count, totalFiles, "Updating Mod List file..."));
597631

598632
// Update the Mod List file.
599-
var _modding = new Modding(XivCache.GameInfo.GameDirectory);
600-
var modList = _modding.GetModList();
601633

602634
// TODO - Probably need to look at keying this off more than just the name.
603635
var modPackExists = modList.ModPacks.Any(modpack => modpack.name == modsJson[0].ModPackEntry.name);
@@ -621,6 +653,13 @@ await Task.Run(async () =>
621653
var fileType = FileTypes[file];
622654
var df = IOUtil.GetDataFileFromPath(file);
623655

656+
657+
var size = json.ModSize;
658+
if (size % 256 != 0)
659+
{
660+
size += 256 - (size % 256);
661+
}
662+
624663
if (mod == null)
625664
{
626665
// Determine if this is an original game file or not.
@@ -636,7 +675,7 @@ await Task.Run(async () =>
636675
data = new Data()
637676
};
638677

639-
mod.data.modSize = json.ModSize;
678+
mod.data.modSize = size;
640679
mod.data.modOffset = longOffset;
641680
mod.data.originalOffset = (fileAdditionMod ? longOffset : longOriginal);
642681
mod.data.dataType = fileType;
@@ -654,7 +693,7 @@ await Task.Run(async () =>
654693
mod.data.originalOffset = longOffset;
655694
}
656695

657-
mod.data.modSize = json.ModSize;
696+
mod.data.modSize = size;
658697
mod.data.modOffset = longOffset;
659698
mod.enabled = true;
660699
mod.modPack = modPack;

xivModdingFramework/SqPack/FileTypes/Dat.cs

Lines changed: 149 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,11 +1472,13 @@ public async Task<long> CopyFile(long originalOffset, XivDataFile originalDataFi
14721472
/// <summary>
14731473
/// Writes a new block of data to the given data file, without changing
14741474
/// the indexes. Returns the raw-index-style offset to the new data.
1475+
///
1476+
/// A target offset of 0 or negative will append to the end of the first data file with space.
14751477
/// </summary>
14761478
/// <param name="importData"></param>
14771479
/// <param name="dataFile"></param>
14781480
/// <returns></returns>
1479-
public async Task<uint> WriteToDat(byte[] importData, XivDataFile dataFile)
1481+
public async Task<uint> WriteToDat(byte[] importData, XivDataFile dataFile, long targetOffset = 0)
14801482
{
14811483
// Perform basic validation.
14821484
if (importData == null || importData.Length < 8)
@@ -1490,18 +1492,54 @@ public async Task<uint> WriteToDat(byte[] importData, XivDataFile dataFile)
14901492
throw new Exception("Attempted to write Invalid data to DAT files.");
14911493
}
14921494

1495+
long seekPointer = 0;
1496+
if(targetOffset >= 2048)
1497+
{
1498+
seekPointer = (targetOffset >> 7) << 7;
1499+
1500+
// If the space we're told to write to would require modification,
1501+
// don't allow writing to it, because we might potentially write
1502+
// past the end of this safe slot.
1503+
if(seekPointer % 256 != 0)
1504+
{
1505+
seekPointer = 0;
1506+
targetOffset = 0;
1507+
}
1508+
}
1509+
14931510
long filePointer = 0;
14941511
await _lock.WaitAsync();
14951512
try
14961513
{
14971514
// This finds the first dat with space, OR creates one if needed.
1498-
var datNum = await GetFirstDatWithSpace(dataFile, importData.Length, true);
1515+
var datNum = 0;
1516+
if (targetOffset >= 2048)
1517+
{
1518+
datNum = (int)(((targetOffset / 8) & 0xF) >> 1);
1519+
var defaultDats = (await GetUnmoddedDatList(dataFile, true)).Count;
1520+
1521+
if(datNum < defaultDats)
1522+
{
1523+
// Safety check. No writing to default dats, even with an explicit offset.
1524+
datNum = await GetFirstDatWithSpace(dataFile, importData.Length, true);
1525+
targetOffset = 0;
1526+
}
1527+
} else
1528+
{
1529+
datNum = await GetFirstDatWithSpace(dataFile, importData.Length, true);
1530+
}
1531+
14991532
var datPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{DatExtension}{datNum}");
15001533

15011534
// Copy the data into the file.
15021535
using (var bw = new BinaryWriter(File.OpenWrite(datPath)))
15031536
{
1504-
bw.BaseStream.Seek(0, SeekOrigin.End);
1537+
if(targetOffset >= 2048) {
1538+
bw.BaseStream.Seek(seekPointer, SeekOrigin.Begin);
1539+
} else
1540+
{
1541+
bw.BaseStream.Seek(0, SeekOrigin.End);
1542+
}
15051543

15061544
// Make sure we're starting on an actual accessible interval.
15071545
while ((bw.BaseStream.Position % 256) != 0)
@@ -1557,11 +1595,19 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
15571595
}
15581596

15591597
var doSave = false;
1560-
if(index == null || modList == null)
1598+
if (index == null || modList == null)
15611599
{
15621600
doSave = true;
15631601
}
15641602

1603+
1604+
if (doSave)
1605+
{
1606+
modList = _modding.GetModList();
1607+
}
1608+
var mod = modList.Mods.FirstOrDefault(x => x.fullPath == internalFilePath);
1609+
1610+
15651611
string itemName = "Unknown";
15661612
string category = "Unknown";
15671613
if (referenceItem == null)
@@ -1589,8 +1635,41 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
15891635
category = referenceItem.GetModlistItemCategory();
15901636
}
15911637

1638+
var size = fileData.Length;
1639+
if(size % 256 != 0)
1640+
{
1641+
size += 256 - (size % 256);
1642+
}
1643+
15921644
// Update the DAT files.
1593-
var rawOffset = await WriteToDat(fileData, df);
1645+
uint rawOffset = 0;
1646+
1647+
if (mod != null && mod.data.modSize >= size)
1648+
{
1649+
// If our existing mod slot is large enough to hold us, keep using it.
1650+
rawOffset = await WriteToDat(fileData, df, mod.data.modOffset);
1651+
1652+
} else if (index == null)
1653+
{
1654+
// If we're doing a singleton/non-batch update, go ahead and take the time to calculate a free spot.
1655+
1656+
var slots = await Dat.ComputeOpenSlots(df);
1657+
var slot = slots.FirstOrDefault(x => x.Value >= size);
1658+
1659+
if (slot.Key >= 2048)
1660+
{
1661+
rawOffset = await WriteToDat(fileData, df, slot.Key);
1662+
}
1663+
else
1664+
{
1665+
rawOffset = await WriteToDat(fileData, df);
1666+
}
1667+
}
1668+
else
1669+
{
1670+
// If we're doing a batch update, just write to the end of the file.
1671+
rawOffset = await WriteToDat(fileData, df);
1672+
}
15941673

15951674

15961675
// Update the Index files.
@@ -1608,13 +1687,8 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
16081687

16091688
var fileType = BitConverter.ToInt32(fileData, 4);
16101689

1611-
// Update the Mod List file.
1612-
if (doSave)
1613-
{
1614-
modList = _modding.GetModList();
1615-
}
1616-
var mod = modList.Mods.FirstOrDefault(x => x.fullPath == internalFilePath);
16171690

1691+
// Update the Mod List file.
16181692

16191693
if (mod == null)
16201694
{
@@ -1632,7 +1706,7 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
16321706
};
16331707
mod.data.modOffset = retOffset;
16341708
mod.data.originalOffset = (fileAdditionMod ? retOffset : longOriginal);
1635-
mod.data.modSize = fileData.Length;
1709+
mod.data.modSize = size;
16361710
mod.data.dataType = fileType;
16371711
mod.enabled = true;
16381712
mod.modPack = null;
@@ -1648,7 +1722,7 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
16481722
mod.data.modOffset = retOffset;
16491723
mod.enabled = true;
16501724
mod.modPack = null;
1651-
mod.data.modSize = fileData.Length;
1725+
mod.data.modSize = size;
16521726
mod.data.dataType = fileType;
16531727
mod.name = itemName;
16541728
mod.category = category;
@@ -1714,5 +1788,67 @@ public static long OffsetCorrection(int datNum, long offset)
17141788
var ret = offset - (16 * datNum);
17151789
return ret;
17161790
}
1791+
1792+
/// <summary>
1793+
/// Computes a dictionary listing of all the open space in a given dat file (within the modded dats only).
1794+
/// </summary>
1795+
/// <param name="df"></param>
1796+
/// <returns></returns>
1797+
public static async Task<Dictionary<long, long>> ComputeOpenSlots(XivDataFile df)
1798+
{
1799+
var _dat = new Dat(XivCache.GameInfo.GameDirectory);
1800+
var _modding = new Modding(XivCache.GameInfo.GameDirectory);
1801+
1802+
var moddedDats = await _dat.GetModdedDatList(df);
1803+
1804+
var slots = new Dictionary<long, long>();
1805+
var modlist = await _modding.GetModListAsync();
1806+
1807+
var modsByFile = modlist.Mods.Where(x => !String.IsNullOrWhiteSpace(x.fullPath)).GroupBy(x => {
1808+
long offset = x.data.modOffset;
1809+
var rawOffset = offset / 8;
1810+
var datNum = (rawOffset & 0xF) >> 1;
1811+
return (int)datNum;
1812+
});
1813+
1814+
foreach(var kv in modsByFile)
1815+
{
1816+
var file = kv.Key;
1817+
long fileOffsetKey = file << 4;
1818+
1819+
// Order by their offset, ascending.
1820+
var ordered = kv.OrderBy(x => x.data.modOffset);
1821+
1822+
1823+
// Scan through each mod, and any time there's a gap, add it to the listing.
1824+
long lastEndPoint = 2048;
1825+
foreach (var mod in ordered) {
1826+
var fileOffset = (mod.data.modOffset >> 7) << 7;
1827+
1828+
var size = mod.data.modSize;
1829+
if(size <= 0)
1830+
{
1831+
size = await _dat.GetCompressedFileSize(mod.data.modOffset, df);
1832+
}
1833+
1834+
if(size % 256 != 0)
1835+
{
1836+
size += (256 - (size % 256));
1837+
}
1838+
1839+
var slotSize = fileOffset - lastEndPoint;
1840+
if (slotSize > 256)
1841+
{
1842+
var mergedStart = lastEndPoint | fileOffsetKey;
1843+
slots.Add(mergedStart, slotSize);
1844+
}
1845+
1846+
lastEndPoint = fileOffset + size;
1847+
}
1848+
}
1849+
1850+
1851+
return slots;
1852+
}
17171853
}
17181854
}

0 commit comments

Comments
 (0)