@@ -1472,11 +1472,13 @@ public async Task<long> CopyFile(long originalOffset, XivDataFile originalDataFi
1472
1472
/// <summary>
1473
1473
/// Writes a new block of data to the given data file, without changing
1474
1474
/// 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.
1475
1477
/// </summary>
1476
1478
/// <param name="importData"></param>
1477
1479
/// <param name="dataFile"></param>
1478
1480
/// <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 )
1480
1482
{
1481
1483
// Perform basic validation.
1482
1484
if ( importData == null || importData . Length < 8 )
@@ -1490,18 +1492,54 @@ public async Task<uint> WriteToDat(byte[] importData, XivDataFile dataFile)
1490
1492
throw new Exception ( "Attempted to write Invalid data to DAT files." ) ;
1491
1493
}
1492
1494
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
+
1493
1510
long filePointer = 0 ;
1494
1511
await _lock . WaitAsync ( ) ;
1495
1512
try
1496
1513
{
1497
1514
// 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
+
1499
1532
var datPath = Path . Combine ( _gameDirectory . FullName , $ "{ dataFile . GetDataFileName ( ) } { DatExtension } { datNum } ") ;
1500
1533
1501
1534
// Copy the data into the file.
1502
1535
using ( var bw = new BinaryWriter ( File . OpenWrite ( datPath ) ) )
1503
1536
{
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
+ }
1505
1543
1506
1544
// Make sure we're starting on an actual accessible interval.
1507
1545
while ( ( bw . BaseStream . Position % 256 ) != 0 )
@@ -1557,11 +1595,19 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
1557
1595
}
1558
1596
1559
1597
var doSave = false ;
1560
- if ( index == null || modList == null )
1598
+ if ( index == null || modList == null )
1561
1599
{
1562
1600
doSave = true ;
1563
1601
}
1564
1602
1603
+
1604
+ if ( doSave )
1605
+ {
1606
+ modList = _modding . GetModList ( ) ;
1607
+ }
1608
+ var mod = modList . Mods . FirstOrDefault ( x => x . fullPath == internalFilePath ) ;
1609
+
1610
+
1565
1611
string itemName = "Unknown" ;
1566
1612
string category = "Unknown" ;
1567
1613
if ( referenceItem == null )
@@ -1589,8 +1635,41 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
1589
1635
category = referenceItem . GetModlistItemCategory ( ) ;
1590
1636
}
1591
1637
1638
+ var size = fileData . Length ;
1639
+ if ( size % 256 != 0 )
1640
+ {
1641
+ size += 256 - ( size % 256 ) ;
1642
+ }
1643
+
1592
1644
// 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
+ }
1594
1673
1595
1674
1596
1675
// Update the Index files.
@@ -1608,13 +1687,8 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
1608
1687
1609
1688
var fileType = BitConverter . ToInt32 ( fileData , 4 ) ;
1610
1689
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 ) ;
1617
1690
1691
+ // Update the Mod List file.
1618
1692
1619
1693
if ( mod == null )
1620
1694
{
@@ -1632,7 +1706,7 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
1632
1706
} ;
1633
1707
mod . data . modOffset = retOffset ;
1634
1708
mod . data . originalOffset = ( fileAdditionMod ? retOffset : longOriginal ) ;
1635
- mod . data . modSize = fileData . Length ;
1709
+ mod . data . modSize = size ;
1636
1710
mod . data . dataType = fileType ;
1637
1711
mod . enabled = true ;
1638
1712
mod . modPack = null ;
@@ -1648,7 +1722,7 @@ public async Task<long> WriteModFile(byte[] fileData, string internalFilePath, s
1648
1722
mod . data . modOffset = retOffset ;
1649
1723
mod . enabled = true ;
1650
1724
mod . modPack = null ;
1651
- mod . data . modSize = fileData . Length ;
1725
+ mod . data . modSize = size ;
1652
1726
mod . data . dataType = fileType ;
1653
1727
mod . name = itemName ;
1654
1728
mod . category = category ;
@@ -1714,5 +1788,67 @@ public static long OffsetCorrection(int datNum, long offset)
1714
1788
var ret = offset - ( 16 * datNum ) ;
1715
1789
return ret ;
1716
1790
}
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
+ }
1717
1853
}
1718
1854
}
0 commit comments