2023-10-28 03:31:09 +08:00
using Microsoft.IO ;
using System ;
using System.Buffers ;
using System.Buffers.Binary ;
2017-09-21 18:33:05 +08:00
using System.Collections.Generic ;
using System.ComponentModel ;
2023-10-28 03:31:09 +08:00
using System.Diagnostics ;
2017-09-21 18:33:05 +08:00
using System.IO ;
using System.IO.Compression ;
2023-10-28 03:31:09 +08:00
using System.IO.Pipes ;
2017-09-21 18:33:05 +08:00
using System.Linq ;
2023-10-28 03:31:09 +08:00
using System.Security.Cryptography.X509Certificates ;
2017-09-21 18:33:05 +08:00
using System.Text ;
2023-10-28 03:31:09 +08:00
using System.Threading ;
2017-09-21 18:33:05 +08:00
using System.Threading.Tasks ;
namespace CodeWalker.GameFiles
{
public class RpfFile
{
public string Name { get ; set ; } //name of this RPF file/package
2023-10-28 03:31:09 +08:00
private string _nameLower ;
public string NameLower
{
get
{
if ( _nameLower = = null )
{
_nameLower = Name . ToLowerInvariant ( ) ;
}
return _nameLower ;
}
set
{
_nameLower = value ;
}
}
2017-09-21 18:33:05 +08:00
public string Path { get ; set ; } //path within the RPF structure
public string FilePath { get ; set ; } //full file path of the RPF
public long FileSize { get ; set ; }
public string LastError { get ; set ; }
public Exception LastException { get ; set ; }
public RpfDirectoryEntry Root { get ; set ; }
public bool IsAESEncrypted { get ; set ; }
public bool IsNGEncrypted { get ; set ; }
//offset in the current file
public long StartPos { get ; set ; }
//header data
public uint Version { get ; set ; }
public uint EntryCount { get ; set ; }
public uint NamesLength { get ; set ; }
public RpfEncryption Encryption { get ; set ; }
//object linkage
public List < RpfEntry > AllEntries { get ; set ; }
public List < RpfFile > Children { get ; set ; }
public RpfFile Parent { get ; set ; }
2018-01-10 11:17:30 +08:00
public RpfBinaryFileEntry ParentFileEntry { get ; set ; }
2017-09-21 18:33:05 +08:00
public BinaryReader CurrentFileReader { get ; set ; } //for temporary use while reading header
public uint TotalFileCount { get ; set ; }
public uint TotalFolderCount { get ; set ; }
public uint TotalResourceCount { get ; set ; }
public uint TotalBinaryFileCount { get ; set ; }
public uint GrandTotalRpfCount { get ; set ; }
public uint GrandTotalFileCount { get ; set ; }
public uint GrandTotalFolderCount { get ; set ; }
public uint GrandTotalResourceCount { get ; set ; }
public uint GrandTotalBinaryFileCount { get ; set ; }
public long ExtractedByteCount { get ; set ; }
2018-01-10 11:17:30 +08:00
public RpfFile ( string fpath , string relpath ) //for a ROOT filesystem RPF
2017-09-21 18:33:05 +08:00
{
FileInfo fi = new FileInfo ( fpath ) ;
Name = fi . Name ;
2017-12-20 07:52:50 +08:00
Path = relpath . ToLowerInvariant ( ) ;
2017-09-21 18:33:05 +08:00
FilePath = fpath ;
FileSize = fi . Length ;
}
2018-01-10 11:17:30 +08:00
public RpfFile ( string name , string path , long filesize ) //for a child RPF
2017-09-21 18:33:05 +08:00
{
Name = name ;
2017-12-20 07:52:50 +08:00
Path = path . ToLowerInvariant ( ) ;
2018-01-10 11:17:30 +08:00
FilePath = path ;
2017-09-21 18:33:05 +08:00
FileSize = filesize ;
}
2019-06-03 15:12:52 +08:00
// Returns string to new path
public string CopyToModsFolder ( out string status )
{
RpfFile parentFile = GetTopParent ( ) ;
string rel_parent_path = parentFile . Path ;
string full_parent_path = parentFile . FilePath ;
2017-09-21 18:33:05 +08:00
2019-06-03 15:12:52 +08:00
if ( rel_parent_path . StartsWith ( @"mods\" ) )
{
status = "already in mods folder" ;
return null ;
}
if ( ! full_parent_path . EndsWith ( rel_parent_path ) )
{
throw new DirectoryNotFoundException ( "Expected full parent path to end with relative path" ) ;
}
string mods_base_path = full_parent_path . Replace ( rel_parent_path , @"mods\" ) ;
string dest_path = mods_base_path + rel_parent_path ;
try
{
File . Copy ( full_parent_path , dest_path ) ;
status = $"copied \" { parentFile . Name } \ " from \"{full_parent_path}\" to \"{dest_path}\"" ;
return dest_path ;
} catch ( IOException e )
{
status = $"unable to copy \" { parentFile . Name } \ " from \"{full_parent_path}\" to \"{dest_path}\": {e.Message}" ;
return null ;
}
}
public bool IsInModsFolder ( )
{
return GetTopParent ( ) . Path . StartsWith ( @"mods\" ) ;
}
public RpfFile GetTopParent ( )
2017-09-21 18:33:05 +08:00
{
RpfFile pfile = this ;
while ( pfile . Parent ! = null )
{
pfile = pfile . Parent ;
}
2019-06-03 15:12:52 +08:00
return pfile ;
}
public string GetPhysicalFilePath ( )
{
return GetTopParent ( ) . FilePath ;
2017-09-21 18:33:05 +08:00
}
private void ReadHeader ( BinaryReader br )
{
CurrentFileReader = br ;
StartPos = br . BaseStream . Position ;
Version = br . ReadUInt32 ( ) ; //RPF Version - GTAV should be 0x52504637 (1380992567)
EntryCount = br . ReadUInt32 ( ) ; //Number of Entries
NamesLength = br . ReadUInt32 ( ) ;
Encryption = ( RpfEncryption ) br . ReadUInt32 ( ) ; //0x04E45504F (1313165391): none; 0x0ffffff9 (268435449): AES
if ( Version ! = 0x52504637 )
{
throw new Exception ( "Invalid Resource - not GTAV!" ) ;
}
2023-10-28 03:31:09 +08:00
byte [ ] entriesdata = ArrayPool < byte > . Shared . Rent ( ( int ) EntryCount * 16 ) ;
byte [ ] namesdata = ArrayPool < byte > . Shared . Rent ( ( int ) NamesLength ) ;
br . BaseStream . Read ( entriesdata , 0 , ( int ) EntryCount * 16 ) ;
br . BaseStream . Read ( namesdata , 0 , ( int ) NamesLength ) ;
2017-09-21 18:33:05 +08:00
switch ( Encryption )
{
2018-01-10 11:17:30 +08:00
case RpfEncryption . NONE : //no encryption
case RpfEncryption . OPEN : //OpenIV style RPF with unencrypted TOC
2017-09-21 18:33:05 +08:00
break ;
case RpfEncryption . AES :
2023-10-28 03:31:09 +08:00
GTACrypto . DecryptAES ( entriesdata , ( int ) EntryCount * 16 ) ;
GTACrypto . DecryptAES ( namesdata , ( int ) NamesLength ) ;
2017-09-21 18:33:05 +08:00
IsAESEncrypted = true ;
break ;
case RpfEncryption . NG :
2023-10-28 03:31:09 +08:00
default :
GTACrypto . DecryptNG ( entriesdata , Name , ( uint ) FileSize , 0 , ( int ) EntryCount * 16 ) ;
GTACrypto . DecryptNG ( namesdata , Name , ( uint ) FileSize , 0 , ( int ) NamesLength ) ;
2017-09-21 18:33:05 +08:00
IsNGEncrypted = true ;
break ;
}
2023-10-28 03:31:09 +08:00
using var entriesrdr = new DataReader ( new MemoryStream ( entriesdata , 0 , ( int ) EntryCount * 16 ) ) ;
using var namesrdr = new DataReader ( new MemoryStream ( namesdata , 0 , ( int ) NamesLength ) ) ;
2017-09-21 18:33:05 +08:00
2023-10-28 03:31:09 +08:00
AllEntries = new List < RpfEntry > ( ( int ) EntryCount ) ;
2017-09-21 18:33:05 +08:00
TotalFileCount = 0 ;
TotalFolderCount = 0 ;
TotalResourceCount = 0 ;
TotalBinaryFileCount = 0 ;
for ( uint i = 0 ; i < EntryCount ; i + + )
{
//entriesrdr.Position += 4;
uint y = entriesrdr . ReadUInt32 ( ) ;
uint x = entriesrdr . ReadUInt32 ( ) ;
entriesrdr . Position - = 8 ;
RpfEntry e ;
if ( x = = 0x7fffff00 ) //directory entry
{
e = new RpfDirectoryEntry ( ) ;
TotalFolderCount + + ;
}
else if ( ( x & 0x80000000 ) = = 0 ) //binary file entry
{
e = new RpfBinaryFileEntry ( ) ;
TotalBinaryFileCount + + ;
TotalFileCount + + ;
}
else //assume resource file entry
{
e = new RpfResourceFileEntry ( ) ;
TotalResourceCount + + ;
TotalFileCount + + ;
}
e . File = this ;
e . H1 = y ;
e . H2 = x ;
e . Read ( entriesrdr ) ;
2023-10-28 03:31:09 +08:00
AllEntries . Add ( e ) ;
}
foreach ( var entry in AllEntries )
{
namesrdr . Position = entry . NameOffset ;
entry . Name = namesrdr . ReadString ( 256 ) ;
if ( entry . Name . Length > 256 )
2023-08-12 16:03:28 +08:00
{
// long names can freeze the RPFExplorer
2023-10-28 03:31:09 +08:00
entry . Name = entry . Name . Substring ( 0 , 256 ) ;
2023-08-12 16:03:28 +08:00
}
2017-09-21 18:33:05 +08:00
2023-10-28 03:31:09 +08:00
if ( entry is RpfResourceFileEntry rfe ) // && string.IsNullOrEmpty(e.Name))
2017-09-21 18:33:05 +08:00
{
2023-10-28 03:31:09 +08:00
rfe . IsEncrypted = rfe . Name . EndsWith ( ".ysc" , StringComparison . OrdinalIgnoreCase ) ; //any other way to know..?
2017-09-21 18:33:05 +08:00
}
}
Root = ( RpfDirectoryEntry ) AllEntries [ 0 ] ;
2017-12-20 07:52:50 +08:00
Root . Path = Path . ToLowerInvariant ( ) ; // + "\\" + Root.Name;
2017-09-21 18:33:05 +08:00
var stack = new Stack < RpfDirectoryEntry > ( ) ;
stack . Push ( Root ) ;
while ( stack . Count > 0 )
{
var item = stack . Pop ( ) ;
2018-01-10 11:17:30 +08:00
int starti = ( int ) item . EntriesIndex ;
int endi = ( int ) ( item . EntriesIndex + item . EntriesCount ) ;
for ( int i = starti ; i < endi ; i + + )
2017-09-21 18:33:05 +08:00
{
RpfEntry e = AllEntries [ i ] ;
2018-01-10 11:17:30 +08:00
e . Parent = item ;
2023-10-28 03:31:09 +08:00
if ( e is RpfDirectoryEntry rde )
2017-09-21 18:33:05 +08:00
{
2023-10-28 03:31:09 +08:00
rde . Path = item . Path + "\\" + rde . Name ;
2017-09-21 18:33:05 +08:00
item . Directories . Add ( rde ) ;
stack . Push ( rde ) ;
}
else if ( e is RpfFileEntry )
{
RpfFileEntry rfe = e as RpfFileEntry ;
2023-10-28 03:31:09 +08:00
rfe . Path = item . Path + "\\" + rfe . Name ;
2017-09-21 18:33:05 +08:00
item . Files . Add ( rfe ) ;
}
}
}
br . BaseStream . Position = StartPos ;
CurrentFileReader = null ;
2023-10-28 03:31:09 +08:00
ArrayPool < byte > . Shared . Return ( entriesdata ) ;
ArrayPool < byte > . Shared . Return ( namesdata ) ;
2017-09-21 18:33:05 +08:00
}
2018-01-10 11:17:30 +08:00
2023-10-28 03:31:09 +08:00
public bool ScanStructure ( Action < string > updateStatus , Action < string > errorLog )
2017-09-21 18:33:05 +08:00
{
2023-10-28 03:31:09 +08:00
using var fileStream = File . OpenRead ( FilePath ) ;
using var br = new BinaryReader ( fileStream ) ;
try
2017-09-21 18:33:05 +08:00
{
2023-10-28 03:31:09 +08:00
return ScanStructure ( br , updateStatus , errorLog ) ;
}
catch ( Exception ex )
{
LastError = ex . ToString ( ) ;
LastException = ex ;
errorLog ? . Invoke ( FilePath + ": " + LastError ) ;
return false ;
2017-09-21 18:33:05 +08:00
}
}
2023-10-28 03:31:09 +08:00
private bool ScanStructure ( BinaryReader br , Action < string > updateStatus , Action < string > errorLog )
2017-09-21 18:33:05 +08:00
{
2023-10-28 03:31:09 +08:00
if ( FilePath = = "update\\update.rpf\\dlc_patch\\patchday1ng\\x64\\patch\\data\\lang\\chinesesimp.rpf" ) return false ;
try
{
ReadHeader ( br ) ;
} catch
{
return false ;
}
2017-09-21 18:33:05 +08:00
GrandTotalRpfCount = 1 ; //count this file..
GrandTotalFileCount = 1 ; //start with this one.
GrandTotalFolderCount = 0 ;
GrandTotalResourceCount = 0 ;
GrandTotalBinaryFileCount = 0 ;
2018-01-10 11:17:30 +08:00
Children = new List < RpfFile > ( ) ;
2017-09-21 18:33:05 +08:00
updateStatus ? . Invoke ( "Scanning " + Path + "..." ) ;
foreach ( RpfEntry entry in AllEntries )
{
try
{
2023-10-28 03:31:09 +08:00
if ( entry is RpfBinaryFileEntry binentry )
2017-09-21 18:33:05 +08:00
{
//search all the sub resources for YSC files. (recurse!)
2023-10-28 03:31:09 +08:00
if ( binentry . Name . EndsWith ( ".rpf" , StringComparison . OrdinalIgnoreCase ) & & binentry . Path . Length < 5000 ) // a long path is most likely an attempt to crash CW, so skip it
2017-09-21 18:33:05 +08:00
{
2018-01-10 11:17:30 +08:00
br . BaseStream . Position = StartPos + ( ( long ) binentry . FileOffset * 512 ) ;
long l = binentry . GetFileSize ( ) ;
2017-09-21 18:33:05 +08:00
2018-01-10 11:17:30 +08:00
RpfFile subfile = new RpfFile ( binentry . Name , binentry . Path , l ) ;
2017-09-21 18:33:05 +08:00
subfile . Parent = this ;
2018-01-10 11:17:30 +08:00
subfile . ParentFileEntry = binentry ;
2017-09-21 18:33:05 +08:00
2023-10-28 03:31:09 +08:00
if ( subfile . ScanStructure ( br , updateStatus , errorLog ) )
{
GrandTotalRpfCount + = subfile . GrandTotalRpfCount ;
GrandTotalFileCount + = subfile . GrandTotalFileCount ;
GrandTotalFolderCount + = subfile . GrandTotalFolderCount ;
GrandTotalResourceCount + = subfile . GrandTotalResourceCount ;
GrandTotalBinaryFileCount + = subfile . GrandTotalBinaryFileCount ;
2017-09-21 18:33:05 +08:00
2023-10-28 03:31:09 +08:00
Children . Add ( subfile ) ;
}
2017-09-21 18:33:05 +08:00
}
else
{
//binary file that's not an rpf...
GrandTotalBinaryFileCount + + ;
GrandTotalFileCount + + ;
}
}
else if ( entry is RpfResourceFileEntry )
{
GrandTotalResourceCount + + ;
GrandTotalFileCount + + ;
}
else if ( entry is RpfDirectoryEntry )
{
GrandTotalFolderCount + + ;
}
}
catch ( Exception ex )
{
2017-12-20 07:52:50 +08:00
errorLog ? . Invoke ( entry . Path + ": " + ex . ToString ( ) ) ;
2017-09-21 18:33:05 +08:00
}
}
2023-10-28 03:31:09 +08:00
return true ;
2017-09-21 18:33:05 +08:00
}
public void ExtractScripts ( string outputfolder , Action < string > updateStatus )
{
FileStream fs = File . OpenRead ( FilePath ) ;
BinaryReader br = new BinaryReader ( fs ) ;
try
{
ExtractScripts ( br , outputfolder , updateStatus ) ;
}
catch ( Exception ex )
{
LastError = ex . ToString ( ) ;
LastException = ex ;
}
br . Close ( ) ;
br . Dispose ( ) ;
fs . Dispose ( ) ;
}
private void ExtractScripts ( BinaryReader br , string outputfolder , Action < string > updateStatus )
{
updateStatus ? . Invoke ( "Searching " + Name + "..." ) ;
ReadHeader ( br ) ;
//List<DataBlock> blocks = new List<DataBlock>();
foreach ( RpfEntry entry in AllEntries )
{
if ( entry is RpfBinaryFileEntry )
{
RpfBinaryFileEntry binentry = entry as RpfBinaryFileEntry ;
2018-01-10 11:17:30 +08:00
long l = binentry . GetFileSize ( ) ;
2017-09-21 18:33:05 +08:00
//search all the sub resources for YSC files. (recurse!)
string lname = binentry . NameLower ;
if ( lname . EndsWith ( ".rpf" ) )
{
2018-01-10 11:17:30 +08:00
br . BaseStream . Position = StartPos + ( ( long ) binentry . FileOffset * 512 ) ;
2017-09-21 18:33:05 +08:00
2018-01-10 11:17:30 +08:00
RpfFile subfile = new RpfFile ( binentry . Name , binentry . Path , l ) ;
2017-09-21 18:33:05 +08:00
subfile . Parent = this ;
2018-01-10 11:17:30 +08:00
subfile . ParentFileEntry = binentry ;
2017-09-21 18:33:05 +08:00
subfile . ExtractScripts ( br , outputfolder , updateStatus ) ;
}
}
else if ( entry is RpfResourceFileEntry )
{
RpfResourceFileEntry resentry = entry as RpfResourceFileEntry ;
string lname = resentry . NameLower ;
if ( lname . EndsWith ( ".ysc" ) )
{
updateStatus ? . Invoke ( "Extracting " + resentry . Name + "..." ) ;
//found a YSC file. extract it!
string ofpath = outputfolder + "\\" + resentry . Name ;
2018-01-10 11:17:30 +08:00
br . BaseStream . Position = StartPos + ( ( long ) resentry . FileOffset * 512 ) ;
2017-09-21 18:33:05 +08:00
if ( resentry . FileSize > 0 )
{
uint offset = 0x10 ;
uint totlen = resentry . FileSize - offset ;
byte [ ] tbytes = new byte [ totlen ] ;
br . BaseStream . Position + = offset ;
br . Read ( tbytes , 0 , ( int ) totlen ) ;
if ( IsAESEncrypted )
{
2023-10-28 03:31:09 +08:00
GTACrypto . DecryptAES ( tbytes ) ;
2017-09-21 18:33:05 +08:00
//special case! probable duplicate pilot_school.ysc
ofpath = outputfolder + "\\" + Name + "___" + resentry . Name ;
}
else
{
2023-10-28 03:31:09 +08:00
GTACrypto . DecryptNG ( tbytes , resentry . Name , resentry . FileSize ) ;
2017-09-21 18:33:05 +08:00
}
try
{
2023-10-28 03:31:09 +08:00
MemoryStream ms = new MemoryStream ( tbytes ) ;
2017-09-21 18:33:05 +08:00
DeflateStream ds = new DeflateStream ( ms , CompressionMode . Decompress ) ;
2023-10-28 03:31:09 +08:00
MemoryStream outstr = recyclableMemoryStreamManager . GetStream ( ) ;
2017-09-21 18:33:05 +08:00
ds . CopyTo ( outstr ) ;
byte [ ] deflated = outstr . GetBuffer ( ) ;
byte [ ] outbuf = new byte [ outstr . Length ] ; //need to copy to the right size buffer for File.WriteAllBytes().
Array . Copy ( deflated , outbuf , outbuf . Length ) ;
bool pathok = true ;
if ( File . Exists ( ofpath ) )
{
ofpath = outputfolder + "\\" + Name + "_" + resentry . Name ;
if ( File . Exists ( ofpath ) )
{
LastError = "Output file " + ofpath + " already exists!" ;
pathok = false ;
}
}
if ( pathok )
{
File . WriteAllBytes ( ofpath , outbuf ) ;
}
}
catch ( Exception ex )
{
LastError = ex . ToString ( ) ;
LastException = ex ;
}
}
}
}
}
}
2023-10-28 03:31:09 +08:00
//public Stream ExtractFileStream(RpfFileEntry entry)
//{
// try
// {
// using (BinaryReader br = new BinaryReader(File.OpenRead(GetPhysicalFilePath())))
// {
// if (entry is RpfBinaryFileEntry binaryFileEntry)
// {
// return ExtractFileBinary(binaryFileEntry, br);
// }
// else if (entry is RpfResourceFileEntry resourceFileEntry)
// {
// return ExtractFileResource(resourceFileEntry, br);
// }
// else
// {
// return null;
// }
// }
// }
// catch (Exception ex)
// {
// LastError = ex.ToString();
// LastException = ex;
// return null;
// }
//}
2017-09-21 18:33:05 +08:00
public byte [ ] ExtractFile ( RpfFileEntry entry )
{
try
{
using ( BinaryReader br = new BinaryReader ( File . OpenRead ( GetPhysicalFilePath ( ) ) ) )
{
2023-10-28 03:31:09 +08:00
if ( entry is RpfBinaryFileEntry binaryFileEntry )
2017-09-21 18:33:05 +08:00
{
2023-10-28 03:31:09 +08:00
return ExtractFileBinary ( binaryFileEntry , br ) ;
2017-09-21 18:33:05 +08:00
}
2023-10-28 03:31:09 +08:00
else if ( entry is RpfResourceFileEntry resourceFileEntry )
2017-09-21 18:33:05 +08:00
{
2023-10-28 03:31:09 +08:00
return ExtractFileResource ( resourceFileEntry , br ) ;
2017-09-21 18:33:05 +08:00
}
else
{
return null ;
}
}
}
catch ( Exception ex )
{
LastError = ex . ToString ( ) ;
LastException = ex ;
return null ;
}
}
public byte [ ] ExtractFileBinary ( RpfBinaryFileEntry entry , BinaryReader br )
{
br . BaseStream . Position = StartPos + ( ( long ) entry . FileOffset * 512 ) ;
2018-01-10 11:17:30 +08:00
long l = entry . GetFileSize ( ) ;
2017-09-21 18:33:05 +08:00
if ( l > 0 )
{
uint offset = 0 ; // 0x10;
uint totlen = ( uint ) l - offset ;
byte [ ] tbytes = new byte [ totlen ] ;
br . BaseStream . Position + = offset ;
br . Read ( tbytes , 0 , ( int ) totlen ) ;
if ( entry . IsEncrypted )
{
if ( IsAESEncrypted )
{
2023-10-28 03:31:09 +08:00
GTACrypto . DecryptAES ( tbytes ) ;
2017-09-21 18:33:05 +08:00
}
else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files)
{
2023-10-28 03:31:09 +08:00
GTACrypto . DecryptNG ( tbytes , entry . Name , entry . FileUncompressedSize ) ;
2017-09-21 18:33:05 +08:00
}
//else
//{ }
}
2023-10-28 03:31:09 +08:00
byte [ ] defl = tbytes ;
2017-09-21 18:33:05 +08:00
if ( entry . FileSize > 0 ) //apparently this means it's compressed
{
2023-10-28 03:31:09 +08:00
defl = DecompressBytes ( tbytes ) ;
2017-09-21 18:33:05 +08:00
}
else
{
}
return defl ;
}
return null ;
}
public byte [ ] ExtractFileResource ( RpfResourceFileEntry entry , BinaryReader br )
{
br . BaseStream . Position = StartPos + ( ( long ) entry . FileOffset * 512 ) ;
if ( entry . FileSize > 0 )
{
uint offset = 0x10 ;
uint totlen = entry . FileSize - offset ;
byte [ ] tbytes = new byte [ totlen ] ;
br . BaseStream . Position + = offset ;
2018-01-10 11:17:30 +08:00
//byte[] hbytes = br.ReadBytes(16); //what are these 16 bytes actually used for?
//if (entry.FileSize > 0xFFFFFF)
//{ //(for huge files, the full file size is packed in 4 of these bytes... seriously wtf)
// var filesize = (hbytes[7] << 0) | (hbytes[14] << 8) | (hbytes[5] << 16) | (hbytes[2] << 24);
//}
2017-09-21 18:33:05 +08:00
br . Read ( tbytes , 0 , ( int ) totlen ) ;
if ( entry . IsEncrypted )
{
if ( IsAESEncrypted )
{
2023-10-28 03:31:09 +08:00
GTACrypto . DecryptAES ( tbytes ) ;
2017-09-21 18:33:05 +08:00
}
else //if (IsNGEncrypted) //assume the archive is set to NG encryption if not AES... (comment: fix for openIV modded files)
{
2023-10-28 03:31:09 +08:00
GTACrypto . DecryptNG ( tbytes , entry . Name , entry . FileSize ) ;
2017-09-21 18:33:05 +08:00
}
//else
//{ }
}
2023-10-28 03:31:09 +08:00
byte [ ] deflated = DecompressBytes ( tbytes ) ;
2017-09-21 18:33:05 +08:00
byte [ ] data = null ;
if ( deflated ! = null )
{
data = deflated ;
}
else
{
entry . FileSize - = offset ;
2023-10-28 03:31:09 +08:00
data = tbytes ;
2017-09-21 18:33:05 +08:00
}
return data ;
}
return null ;
}
public static T GetFile < T > ( RpfEntry e ) where T : class , PackedFile , new ( )
{
T file = null ;
byte [ ] data = null ;
RpfFileEntry entry = e as RpfFileEntry ;
if ( entry ! = null )
{
data = entry . File . ExtractFile ( entry ) ;
}
if ( data ! = null )
{
file = new T ( ) ;
file . Load ( data , entry ) ;
}
return file ;
}
public static T GetFile < T > ( RpfEntry e , byte [ ] data ) where T : class , PackedFile , new ( )
{
T file = null ;
RpfFileEntry entry = e as RpfFileEntry ;
if ( ( data ! = null ) )
{
if ( entry = = null )
{
2018-03-08 09:15:28 +08:00
entry = CreateResourceFileEntry ( ref data , 0 ) ;
2017-09-21 18:33:05 +08:00
}
file = new T ( ) ;
file . Load ( data , entry ) ;
}
return file ;
}
2018-03-08 09:15:28 +08:00
2023-10-28 03:31:09 +08:00
public static T GetFile < T > ( RpfEntry e , Stream data ) where T : class , PackedFileStream , new ( )
{
T file = null ;
RpfFileEntry entry = e as RpfFileEntry ;
if ( ( data ! = null ) )
{
if ( entry = = null )
{
entry = CreateResourceFileEntry ( data , 0 ) ;
}
file = new T ( ) ;
file . Load ( data , entry ) ;
}
return file ;
}
2018-03-08 09:15:28 +08:00
2017-09-21 18:33:05 +08:00
public static T GetResourceFile < T > ( byte [ ] data ) where T : class , PackedFile , new ( )
{
T file = null ;
2018-03-08 09:15:28 +08:00
RpfFileEntry entry = CreateResourceFileEntry ( ref data , 0 ) ;
2017-09-21 18:33:05 +08:00
if ( ( data ! = null ) & & ( entry ! = null ) )
{
2018-05-01 11:20:39 +08:00
data = ResourceBuilder . Decompress ( data ) ;
2017-09-21 18:33:05 +08:00
file = new T ( ) ;
file . Load ( data , entry ) ;
}
return file ;
}
2023-10-28 03:31:09 +08:00
public static void LoadResourceFile < T > ( T file , Stream data , uint ver ) where T : class , PackedFileStream
{
//direct load from a raw, compressed resource file (openIV-compatible format)
RpfResourceFileEntry resentry = CreateResourceFileEntry ( data , ver ) ;
if ( file is GameFile )
{
GameFile gfile = file as GameFile ;
var oldresentry = gfile . RpfFileEntry as RpfResourceFileEntry ;
if ( oldresentry ! = null ) //update the existing entry with the new one
{
oldresentry . SystemFlags = resentry . SystemFlags ;
oldresentry . GraphicsFlags = resentry . GraphicsFlags ;
resentry . Name = oldresentry . Name ;
}
else
{
gfile . RpfFileEntry = resentry ; //just stick it in there for later...
}
}
data = ResourceBuilder . Decompress ( data ) ;
file . Load ( data , resentry ) ;
}
2018-03-08 09:15:28 +08:00
public static void LoadResourceFile < T > ( T file , byte [ ] data , uint ver ) where T : class , PackedFile
{
//direct load from a raw, compressed resource file (openIV-compatible format)
2017-09-21 18:33:05 +08:00
2018-03-08 09:15:28 +08:00
RpfResourceFileEntry resentry = CreateResourceFileEntry ( ref data , ver ) ;
2017-09-21 18:33:05 +08:00
2018-03-08 09:15:28 +08:00
if ( file is GameFile )
{
GameFile gfile = file as GameFile ;
2017-09-21 18:33:05 +08:00
2018-03-08 09:15:28 +08:00
var oldresentry = gfile . RpfFileEntry as RpfResourceFileEntry ;
if ( oldresentry ! = null ) //update the existing entry with the new one
{
oldresentry . SystemFlags = resentry . SystemFlags ;
oldresentry . GraphicsFlags = resentry . GraphicsFlags ;
resentry . Name = oldresentry . Name ;
}
else
{
gfile . RpfFileEntry = resentry ; //just stick it in there for later...
}
}
data = ResourceBuilder . Decompress ( data ) ;
file . Load ( data , resentry ) ;
}
2023-10-28 03:31:09 +08:00
public static RpfResourceFileEntry CreateResourceFileEntry ( Stream stream , uint ver , uint? header = null )
{
var resentry = new RpfResourceFileEntry ( ) ;
using var reader = new BinaryReader ( stream , Encoding . UTF8 , true ) ;
//hopefully this data has an RSC7 header...
uint rsc7 = header ? ? reader . ReadUInt32 ( ) ; // BitConverter.ToUInt32(data, 0);
if ( rsc7 = = 0x37435352 ) //RSC7 header present!
{
int version = reader . ReadInt32 ( ) ; // BitConverter.ToInt32(data, 4);//use this instead of what was given...
resentry . SystemFlags = reader . ReadUInt32 ( ) ; // BitConverter.ToUInt32(data, 8);
resentry . GraphicsFlags = reader . ReadUInt32 ( ) ; //BitConverter.ToUInt32(data, 12);
//if (stream.Length > 16)
//{
// int newlen = stream.Length - 16; //trim the header from the data passed to the next step.
// byte[] newdata = new byte[newlen];
// Buffer.BlockCopy(data, 16, newdata, 0, newlen);
// data = newdata;
//}
//else
//{
// data = null; //shouldn't happen... empty..
//}
}
else
{
//direct load from file without the rpf header..
//assume it's in resource meta format
resentry . SystemFlags = RpfResourceFileEntry . GetFlagsFromSize ( ( int ) stream . Length , 0 ) ;
resentry . GraphicsFlags = RpfResourceFileEntry . GetFlagsFromSize ( 0 , ver ) ;
}
resentry . Name = "" ;
return resentry ;
}
2018-03-08 09:15:28 +08:00
public static RpfResourceFileEntry CreateResourceFileEntry ( ref byte [ ] data , uint ver )
2017-09-21 18:33:05 +08:00
{
var resentry = new RpfResourceFileEntry ( ) ;
2018-03-08 09:15:28 +08:00
//hopefully this data has an RSC7 header...
2017-09-21 18:33:05 +08:00
uint rsc7 = BitConverter . ToUInt32 ( data , 0 ) ;
if ( rsc7 = = 0x37435352 ) //RSC7 header present!
{
int version = BitConverter . ToInt32 ( data , 4 ) ; //use this instead of what was given...
resentry . SystemFlags = BitConverter . ToUInt32 ( data , 8 ) ;
resentry . GraphicsFlags = BitConverter . ToUInt32 ( data , 12 ) ;
2018-03-08 09:15:28 +08:00
if ( data . Length > 16 )
{
int newlen = data . Length - 16 ; //trim the header from the data passed to the next step.
byte [ ] newdata = new byte [ newlen ] ;
Buffer . BlockCopy ( data , 16 , newdata , 0 , newlen ) ;
data = newdata ;
}
2017-09-21 18:33:05 +08:00
//else
//{
// data = null; //shouldn't happen... empty..
//}
}
else
{
//direct load from file without the rpf header..
//assume it's in resource meta format
resentry . SystemFlags = RpfResourceFileEntry . GetFlagsFromSize ( data . Length , 0 ) ;
2018-03-08 09:15:28 +08:00
resentry . GraphicsFlags = RpfResourceFileEntry . GetFlagsFromSize ( 0 , ver ) ;
2017-09-21 18:33:05 +08:00
}
resentry . Name = "" ;
return resentry ;
}
public string TestExtractAllFiles ( )
{
StringBuilder sb = new StringBuilder ( ) ;
ExtractedByteCount = 0 ;
try
{
using ( BinaryReader br = new BinaryReader ( File . OpenRead ( GetPhysicalFilePath ( ) ) ) )
{
foreach ( RpfEntry entry in AllEntries )
{
try
{
LastError = string . Empty ;
LastException = null ;
if ( ! entry . NameLower . EndsWith ( ".rpf" ) ) //don't try to extract rpf's, they will be done separately..
{
if ( entry is RpfBinaryFileEntry )
{
RpfBinaryFileEntry binentry = entry as RpfBinaryFileEntry ;
byte [ ] data = ExtractFileBinary ( binentry , br ) ;
if ( data = = null )
{
if ( binentry . FileSize = = 0 )
{
sb . AppendFormat ( "{0} : Binary FileSize is 0." , entry . Path ) ;
sb . AppendLine ( ) ;
}
else
{
sb . AppendFormat ( "{0} : {1}" , entry . Path , LastError ) ;
sb . AppendLine ( ) ;
}
}
else if ( data . Length = = 0 )
{
sb . AppendFormat ( "{0} : Decompressed output was empty." , entry . Path ) ;
sb . AppendLine ( ) ;
}
else
{
ExtractedByteCount + = data . Length ;
}
}
else if ( entry is RpfResourceFileEntry )
{
RpfResourceFileEntry resentry = entry as RpfResourceFileEntry ;
byte [ ] data = ExtractFileResource ( resentry , br ) ;
if ( data = = null )
{
if ( resentry . FileSize = = 0 )
{
sb . AppendFormat ( "{0} : Resource FileSize is 0." , entry . Path ) ;
sb . AppendLine ( ) ;
}
else
{
sb . AppendFormat ( "{0} : {1}" , entry . Path , LastError ) ;
sb . AppendLine ( ) ;
}
}
else if ( data . Length = = 0 )
{
sb . AppendFormat ( "{0} : Decompressed output was empty." , entry . Path ) ;
sb . AppendLine ( ) ;
}
else
{
ExtractedByteCount + = data . Length ;
}
}
}
}
catch ( Exception ex )
{
LastError = ex . ToString ( ) ;
LastException = ex ;
sb . AppendFormat ( "{0} : {1}" , entry . Path , ex . Message ) ;
sb . AppendLine ( ) ;
}
}
}
}
catch ( Exception ex )
{
LastError = ex . ToString ( ) ;
LastException = ex ;
sb . AppendFormat ( "{0} : {1}" , Path , ex . Message ) ;
sb . AppendLine ( ) ;
return null ;
}
return sb . ToString ( ) ;
}
public List < RpfFileEntry > GetFiles ( string folder , bool recurse )
{
List < RpfFileEntry > result = new List < RpfFileEntry > ( ) ;
2017-12-20 07:52:50 +08:00
string [ ] parts = folder . ToLowerInvariant ( ) . Split ( new [ ] { '\\' } , StringSplitOptions . RemoveEmptyEntries ) ;
2017-09-21 18:33:05 +08:00
RpfDirectoryEntry dir = Root ;
for ( int i = 0 ; i < parts . Length ; i + + )
{
if ( dir = = null ) break ;
dir = FindSubDirectory ( dir , parts [ i ] ) ;
}
if ( dir ! = null )
{
GetFiles ( dir , result , recurse ) ;
}
return result ;
}
public void GetFiles ( RpfDirectoryEntry dir , List < RpfFileEntry > result , bool recurse )
{
if ( dir . Files ! = null )
{
result . AddRange ( dir . Files ) ;
}
if ( recurse )
{
if ( dir . Directories ! = null )
{
for ( int i = 0 ; i < dir . Directories . Count ; i + + )
{
GetFiles ( dir . Directories [ i ] , result , recurse ) ;
}
}
}
}
private RpfDirectoryEntry FindSubDirectory ( RpfDirectoryEntry dir , string name )
{
if ( dir = = null ) return null ;
if ( dir . Directories = = null ) return null ;
for ( int i = 0 ; i < dir . Directories . Count ; i + + )
{
var cdir = dir . Directories [ i ] ;
2017-12-20 07:52:50 +08:00
if ( cdir . Name . ToLowerInvariant ( ) = = name )
2017-09-21 18:33:05 +08:00
{
return cdir ;
}
}
return null ;
}
2023-10-28 03:31:09 +08:00
public static RecyclableMemoryStreamManager recyclableMemoryStreamManager = new RecyclableMemoryStreamManager ( ) ;
2017-09-21 18:33:05 +08:00
public byte [ ] DecompressBytes ( byte [ ] bytes )
{
try
{
using ( DeflateStream ds = new DeflateStream ( new MemoryStream ( bytes ) , CompressionMode . Decompress ) )
{
2023-10-28 03:31:09 +08:00
using ( var outstr = recyclableMemoryStreamManager . GetStream ( "DecompressBytes" , bytes . Length ) )
2017-09-21 18:33:05 +08:00
{
2022-07-30 19:54:21 +08:00
ds . CopyTo ( outstr ) ;
byte [ ] deflated = outstr . GetBuffer ( ) ;
byte [ ] outbuf = new byte [ outstr . Length ] ; //need to copy to the right size buffer for output.
Buffer . BlockCopy ( deflated , 0 , outbuf , 0 , outbuf . Length ) ;
2017-09-21 18:33:05 +08:00
2022-07-30 19:54:21 +08:00
if ( outbuf . Length < = bytes . Length )
{
LastError = "Warning: Decompressed data was smaller than compressed data..." ;
//return null; //could still be OK for tiny things!
}
return outbuf ;
}
2017-09-21 18:33:05 +08:00
}
}
catch ( Exception ex )
{
LastError = "Could not decompress." ; // ex.ToString();
LastException = ex ;
return null ;
}
}
2018-01-10 11:17:30 +08:00
public static byte [ ] CompressBytes ( byte [ ] data ) //TODO: refactor this with ResourceBuilder.Compress/Decompress
{
2023-10-28 03:31:09 +08:00
using ( MemoryStream ms = recyclableMemoryStreamManager . GetStream ( "CompressBytes" , data . Length ) )
2018-01-10 11:17:30 +08:00
{
2022-07-30 19:54:21 +08:00
using ( var ds = new DeflateStream ( ms , CompressionMode . Compress , true ) )
{
ds . Write ( data , 0 , data . Length ) ;
ds . Close ( ) ;
byte [ ] deflated = ms . GetBuffer ( ) ;
byte [ ] outbuf = new byte [ ms . Length ] ; //need to copy to the right size buffer...
Buffer . BlockCopy ( deflated , 0 , outbuf , 0 , outbuf . Length ) ;
return outbuf ;
}
2018-01-10 11:17:30 +08:00
}
}
private void WriteHeader ( BinaryWriter bw )
{
var namesdata = GetHeaderNamesData ( ) ;
NamesLength = ( uint ) namesdata . Length ;
//ensure there's enough space for the new header, move things if necessary
var headersize = GetHeaderBlockCount ( ) * 512 ;
EnsureSpace ( bw , null , headersize ) ;
//entries may have been updated, so need to do this after ensuring header space
var entriesdata = GetHeaderEntriesData ( ) ;
//FileSize = ... //need to make sure this is updated for NG encryption...
switch ( Encryption )
{
case RpfEncryption . NONE : //no encryption
case RpfEncryption . OPEN : //OpenIV style RPF with unencrypted TOC
break ;
case RpfEncryption . AES :
entriesdata = GTACrypto . EncryptAES ( entriesdata ) ;
namesdata = GTACrypto . EncryptAES ( namesdata ) ;
IsAESEncrypted = true ;
break ;
case RpfEncryption . NG :
entriesdata = GTACrypto . EncryptNG ( entriesdata , Name , ( uint ) FileSize ) ;
namesdata = GTACrypto . EncryptNG ( namesdata , Name , ( uint ) FileSize ) ;
IsNGEncrypted = true ;
break ;
default : //unknown encryption type? assume NG.. should never get here!
entriesdata = GTACrypto . EncryptNG ( entriesdata , Name , ( uint ) FileSize ) ;
namesdata = GTACrypto . EncryptNG ( namesdata , Name , ( uint ) FileSize ) ;
break ;
}
2018-01-11 09:10:03 +08:00
//now there's enough space, it's safe to write the header data...
bw . BaseStream . Position = StartPos ;
bw . Write ( Version ) ;
bw . Write ( EntryCount ) ;
bw . Write ( NamesLength ) ;
bw . Write ( ( uint ) Encryption ) ;
2018-01-10 11:17:30 +08:00
bw . Write ( entriesdata ) ;
bw . Write ( namesdata ) ;
WritePadding ( bw . BaseStream , StartPos + headersize ) ; //makes sure the actual file can grow...
}
private static void WritePadding ( Stream s , long upto )
{
int diff = ( int ) ( upto - s . Position ) ;
if ( diff > 0 )
{
s . Write ( new byte [ diff ] , 0 , diff ) ;
}
}
private void EnsureAllEntries ( )
{
if ( AllEntries = = null )
{
//assume this is a new RPF, create the root directory entry
AllEntries = new List < RpfEntry > ( ) ;
Root = new RpfDirectoryEntry ( ) ;
Root . File = this ;
Root . Name = string . Empty ;
Root . Path = Path . ToLowerInvariant ( ) ;
}
if ( Children = = null )
{
Children = new List < RpfFile > ( ) ;
}
//re-build the AllEntries list from the root node.
List < RpfEntry > temp = new List < RpfEntry > ( ) ; //for sorting
AllEntries . Clear ( ) ;
AllEntries . Add ( Root ) ;
Stack < RpfDirectoryEntry > stack = new Stack < RpfDirectoryEntry > ( ) ;
stack . Push ( Root ) ;
while ( stack . Count > 0 )
{
var item = stack . Pop ( ) ;
item . EntriesCount = ( uint ) ( item . Directories . Count + item . Files . Count ) ;
item . EntriesIndex = ( uint ) AllEntries . Count ;
//having items sorted by name is important for the game for some reason. (it crashes otherwise!)
temp . Clear ( ) ;
temp . AddRange ( item . Directories ) ;
temp . AddRange ( item . Files ) ;
temp . Sort ( ( a , b ) = > String . CompareOrdinal ( a . Name , b . Name ) ) ;
foreach ( var entry in temp )
{
AllEntries . Add ( entry ) ;
RpfDirectoryEntry dir = entry as RpfDirectoryEntry ;
if ( dir ! = null )
{
stack . Push ( dir ) ;
}
}
}
EntryCount = ( uint ) AllEntries . Count ;
}
private byte [ ] GetHeaderNamesData ( )
{
MemoryStream namesstream = new MemoryStream ( ) ;
DataWriter nameswriter = new DataWriter ( namesstream ) ;
var namedict = new Dictionary < string , uint > ( ) ;
foreach ( var entry in AllEntries )
{
uint nameoffset ;
string name = entry . Name ? ? "" ;
if ( namedict . TryGetValue ( name , out nameoffset ) )
{
entry . NameOffset = nameoffset ;
}
else
{
entry . NameOffset = ( uint ) namesstream . Length ;
namedict . Add ( name , entry . NameOffset ) ;
nameswriter . Write ( name ) ;
}
}
var buf = new byte [ namesstream . Length ] ;
namesstream . Position = 0 ;
namesstream . Read ( buf , 0 , buf . Length ) ;
return PadBuffer ( buf , 16 ) ;
}
private byte [ ] GetHeaderEntriesData ( )
{
MemoryStream entriesstream = new MemoryStream ( ) ;
DataWriter entrieswriter = new DataWriter ( entriesstream ) ;
foreach ( var entry in AllEntries )
{
entry . Write ( entrieswriter ) ;
}
var buf = new byte [ entriesstream . Length ] ;
entriesstream . Position = 0 ;
entriesstream . Read ( buf , 0 , buf . Length ) ;
return buf ;
}
private uint GetHeaderBlockCount ( ) //make sure EntryCount and NamesLength are updated before calling this...
{
uint headerusedbytes = 16 + ( EntryCount * 16 ) + NamesLength ;
uint headerblockcount = GetBlockCount ( headerusedbytes ) ;
return headerblockcount ;
}
private static byte [ ] PadBuffer ( byte [ ] buf , uint n ) //add extra bytes as necessary to nearest n
{
uint buflen = ( uint ) buf . Length ;
uint newlen = PadLength ( buflen , n ) ;
if ( newlen ! = buflen )
{
byte [ ] buf2 = new byte [ newlen ] ;
Buffer . BlockCopy ( buf , 0 , buf2 , 0 , buf . Length ) ;
return buf2 ;
}
return buf ;
}
private static uint PadLength ( uint l , uint n ) //round up to nearest n bytes
{
uint rem = l % n ;
return l + ( ( rem > 0 ) ? ( n - rem ) : 0 ) ;
}
private static uint GetBlockCount ( long bytecount )
{
uint b0 = ( uint ) ( bytecount & 0x1FF ) ; //511;
uint b1 = ( uint ) ( bytecount > > 9 ) ;
if ( b0 = = 0 ) return b1 ;
return b1 + 1 ;
}
private RpfFileEntry FindFirstFileAfter ( uint block )
{
RpfFileEntry nextentry = null ;
foreach ( var entry in AllEntries )
{
RpfFileEntry fe = entry as RpfFileEntry ;
if ( ( fe ! = null ) & & ( fe . FileOffset > block ) )
{
if ( ( nextentry = = null ) | | ( fe . FileOffset < nextentry . FileOffset ) )
{
nextentry = fe ;
}
}
}
return nextentry ;
}
private uint FindHole ( uint reqblocks , uint ignorestart , uint ignoreend )
{
//find the block index of a hole that can fit the required number of blocks.
//return 0 if no hole found (0 is the header block, it can't be used for files!)
//make sure any found hole is not within the ignore range
//(i.e. area where space is currently being made)
//gather and sort the list of files to allow searching for holes
List < RpfFileEntry > allfiles = new List < RpfFileEntry > ( ) ;
foreach ( var entry in AllEntries )
{
RpfFileEntry rfe = entry as RpfFileEntry ;
if ( rfe ! = null )
{
allfiles . Add ( rfe ) ;
}
}
allfiles . Sort ( ( e1 , e2 ) = > e1 . FileOffset . CompareTo ( e2 . FileOffset ) ) ;
//find the smallest available hole from the list.
uint found = 0 ;
uint foundsize = 0xFFFFFFFF ;
for ( int i = 1 ; i < allfiles . Count ( ) ; i + + )
{
RpfFileEntry e1 = allfiles [ i - 1 ] ;
RpfFileEntry e2 = allfiles [ i ] ;
uint e1cnt = GetBlockCount ( e1 . GetFileSize ( ) ) ;
uint e1end = e1 . FileOffset + e1cnt ;
uint e2beg = e2 . FileOffset ;
if ( ( e2beg > ignorestart ) & & ( e1end < ignoreend ) )
{
continue ; //this space is in the ignore area.
}
if ( e1end < e2beg )
{
uint space = e2beg - e1end ;
if ( ( space > = reqblocks ) & & ( space < foundsize ) )
{
found = e1end ;
foundsize = space ;
}
}
}
return found ;
}
private uint FindEndBlock ( )
{
//find the next available block after all other files (or after header if there's no files)
uint endblock = 0 ;
foreach ( var entry in AllEntries )
{
RpfFileEntry e = entry as RpfFileEntry ;
if ( e ! = null )
{
uint ecnt = GetBlockCount ( e . GetFileSize ( ) ) ;
uint eend = e . FileOffset + ecnt ;
if ( eend > endblock )
{
endblock = eend ;
}
}
}
if ( endblock = = 0 )
{
//must be no files present, end block comes directly after the header.
endblock = GetHeaderBlockCount ( ) ;
}
return endblock ;
}
private void GrowArchive ( BinaryWriter bw , uint newblockcount )
{
uint newsize = newblockcount * 512 ;
if ( newsize < FileSize )
{
return ; //already bigger than it needs to be, can happen if last file got moved into a hole...
}
if ( FileSize = = newsize )
{
return ; //nothing to do... correct size already
}
FileSize = newsize ;
//ensure enough space in the parent if there is one...
if ( Parent ! = null )
{
if ( ParentFileEntry = = null )
{
throw new Exception ( "Can't grow archive " + Path + ": ParentFileEntry was null!" ) ;
}
//parent's header will be updated with these new values.
ParentFileEntry . FileUncompressedSize = newsize ;
ParentFileEntry . FileSize = 0 ; //archives have FileSize==0 in parent...
Parent . EnsureSpace ( bw , ParentFileEntry , newsize ) ;
}
}
private void RelocateFile ( BinaryWriter bw , RpfFileEntry f , uint newblock )
{
//directly move this file. does NOT update the header!
//enough space should already be allocated for this move.
uint flen = GetBlockCount ( f . GetFileSize ( ) ) ;
uint fbeg = f . FileOffset ;
uint fend = fbeg + flen ;
uint nend = newblock + flen ;
if ( ( nend > fbeg ) & & ( newblock < fend ) ) //can't move to somewhere within itself!
{
throw new Exception ( "Unable to relocate file " + f . Path + ": new position was inside the original!" ) ;
}
var stream = bw . BaseStream ;
long origpos = stream . Position ;
long source = StartPos + ( ( long ) fbeg * 512 ) ;
long dest = StartPos + ( ( long ) newblock * 512 ) ;
long newstart = dest ;
long length = ( long ) flen * 512 ;
long destend = dest + length ;
const int BUFFER_SIZE = 16384 ; //what buffer size is best for HDD copy?
var buffer = new byte [ BUFFER_SIZE ] ;
while ( length > 0 )
{
stream . Position = source ;
int i = stream . Read ( buffer , 0 , ( int ) Math . Min ( length , BUFFER_SIZE ) ) ;
stream . Position = dest ;
stream . Write ( buffer , 0 , i ) ;
source + = i ;
dest + = i ;
length - = i ;
}
WritePadding ( stream , destend ) ; //makes sure the stream can grow if necessary
stream . Position = origpos ; //reset this just to be nice
f . FileOffset = newblock ;
//if this is a child RPF archive, need to update its StartPos...
var child = FindChildArchive ( f ) ;
if ( child ! = null )
{
2018-01-12 12:19:32 +08:00
child . UpdateStartPos ( newstart ) ;
2018-01-10 11:17:30 +08:00
}
}
private void EnsureSpace ( BinaryWriter bw , RpfFileEntry e , long bytecount )
{
//(called with null entry for ensuring header space)
uint blockcount = GetBlockCount ( bytecount ) ;
uint startblock = e ? . FileOffset ? ? 0 ; //0 is always header block
uint endblock = startblock + blockcount ;
RpfFileEntry nextentry = FindFirstFileAfter ( startblock ) ;
while ( nextentry ! = null ) //just deal with relocating one entry at a time.
{
//move this nextentry to somewhere else... preferably into a hole otherwise at the end
//if the RPF needs to grow, space needs to be ensured in the parent rpf (if there is one)...
//keep moving further entries until enough space is gained.
if ( nextentry . FileOffset > = endblock )
{
break ; //already enough space for this entry, don't go further.
}
uint entryblocks = GetBlockCount ( nextentry . GetFileSize ( ) ) ;
uint newblock = FindHole ( entryblocks , startblock , endblock ) ;
if ( newblock = = 0 )
{
//no hole was found, move this entry to the end of the file.
newblock = FindEndBlock ( ) ;
GrowArchive ( bw , newblock + entryblocks ) ;
}
//now move the file contents and update the entry's position.
RelocateFile ( bw , nextentry , newblock ) ;
//move on to the next file...
nextentry = FindFirstFileAfter ( startblock ) ;
}
if ( nextentry = = null )
{
//last entry in the RPF, so just need to grow the RPF enough to fit.
//this could be the header (for an empty RPF)...
uint newblock = FindEndBlock ( ) ;
GrowArchive ( bw , newblock + ( ( e ! = null ) ? blockcount : 0 ) ) ;
}
//changing a file's size (not the header size!) - need to update the header..!
//also, files could have been moved. so always update the header if we aren't already
if ( e ! = null )
{
WriteHeader ( bw ) ;
}
}
private void InsertFileSpace ( BinaryWriter bw , RpfFileEntry entry )
{
//to insert a new entry. find space in the archive for it and assign the FileOffset.
uint blockcount = GetBlockCount ( entry . GetFileSize ( ) ) ;
entry . FileOffset = FindHole ( blockcount , 0 , 0 ) ;
if ( entry . FileOffset = = 0 )
{
entry . FileOffset = FindEndBlock ( ) ;
GrowArchive ( bw , entry . FileOffset + blockcount ) ;
}
EnsureAllEntries ( ) ;
WriteHeader ( bw ) ;
}
private void WriteNewArchive ( BinaryWriter bw , RpfEncryption encryption )
{
var stream = bw . BaseStream ;
Encryption = encryption ;
Version = 0x52504637 ; //'RPF7'
IsAESEncrypted = ( encryption = = RpfEncryption . AES ) ;
IsNGEncrypted = ( encryption = = RpfEncryption . NG ) ;
StartPos = stream . Position ;
EnsureAllEntries ( ) ;
WriteHeader ( bw ) ;
FileSize = stream . Position - StartPos ;
}
private void UpdatePaths ( RpfDirectoryEntry dir = null )
{
//recursively update paths, including in child RPFs.
if ( dir = = null )
{
Root . Path = Path . ToLowerInvariant ( ) ;
dir = Root ;
}
foreach ( var file in dir . Files )
{
file . Path = dir . Path + "\\" + file . NameLower ;
RpfBinaryFileEntry binf = file as RpfBinaryFileEntry ;
2023-10-28 03:31:09 +08:00
if ( ( binf ! = null ) & & file . Name . EndsWith ( ".rpf" , StringComparison . OrdinalIgnoreCase ) )
2018-01-10 11:17:30 +08:00
{
RpfFile childrpf = FindChildArchive ( binf ) ;
if ( childrpf ! = null )
{
childrpf . Path = binf . Path ;
childrpf . FilePath = binf . Path ;
childrpf . UpdatePaths ( ) ;
}
else
{ } //couldn't find child RPF! problem..!
}
}
foreach ( var subdir in dir . Directories )
{
2023-10-28 03:31:09 +08:00
subdir . Path = dir . Path + "\\" + subdir . Name ;
2018-01-10 11:17:30 +08:00
UpdatePaths ( subdir ) ;
}
}
2018-01-11 09:10:03 +08:00
public RpfFile FindChildArchive ( RpfFileEntry f )
2018-01-10 11:17:30 +08:00
{
RpfFile c = null ;
if ( Children ! = null )
{
foreach ( var child in Children ) //kinda messy, but no other option really...
{
if ( child . ParentFileEntry = = f )
{
c = child ;
break ;
}
}
}
return c ;
}
2018-01-12 12:19:32 +08:00
public long GetDefragmentedFileSize ( )
{
//this represents the size the file would be when fully defragmented.
uint blockcount = GetHeaderBlockCount ( ) ;
foreach ( var entry in AllEntries )
{
var fentry = entry as RpfFileEntry ;
if ( fentry ! = null )
{
blockcount + = GetBlockCount ( fentry . GetFileSize ( ) ) ;
}
}
return ( long ) blockcount * 512 ;
}
private void UpdateStartPos ( long newpos )
{
StartPos = newpos ;
if ( Children ! = null )
{
//make sure children also get their StartPos updated!
foreach ( var child in Children )
{
if ( child . ParentFileEntry = = null ) continue ; //shouldn't really happen...
var cpos = StartPos + ( long ) child . ParentFileEntry . FileOffset * 512 ;
2018-02-26 20:49:06 +08:00
child . UpdateStartPos ( cpos ) ;
2018-01-12 12:19:32 +08:00
}
}
}
2018-01-10 11:17:30 +08:00
public static RpfFile CreateNew ( string gtafolder , string relpath , RpfEncryption encryption = RpfEncryption . OPEN )
{
//create a new, empty RPF file in the filesystem
//this will assume that the folder the file is going into already exists!
string fpath = gtafolder ;
fpath = fpath . EndsWith ( "\\" ) ? fpath : fpath + "\\" ;
fpath = fpath + relpath ;
if ( File . Exists ( fpath ) )
{
throw new Exception ( "File " + fpath + " already exists!" ) ;
}
File . Create ( fpath ) . Dispose ( ) ; //just write a placeholder, will fill it out later
RpfFile file = new RpfFile ( fpath , relpath ) ;
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
using ( var bw = new BinaryWriter ( fstream ) )
{
file . WriteNewArchive ( bw , encryption ) ;
}
}
return file ;
}
public static RpfFile CreateNew ( RpfDirectoryEntry dir , string name , RpfEncryption encryption = RpfEncryption . OPEN )
{
//create a new empty RPF inside the given parent RPF directory.
string namel = name . ToLowerInvariant ( ) ;
RpfFile parent = dir . File ;
string fpath = parent . GetPhysicalFilePath ( ) ;
string rpath = dir . Path + "\\" + namel ;
if ( ! File . Exists ( fpath ) )
{
throw new Exception ( "Root RPF file " + fpath + " does not exist!" ) ;
}
RpfFile file = new RpfFile ( name , rpath , 512 ) ; //empty RPF is 512 bytes...
file . Parent = parent ;
file . ParentFileEntry = new RpfBinaryFileEntry ( ) ;
RpfBinaryFileEntry entry = file . ParentFileEntry ;
entry . Parent = dir ;
entry . FileOffset = 0 ; //InsertFileSpace will update this
entry . FileSize = 0 ;
entry . FileUncompressedSize = ( uint ) file . FileSize ;
entry . EncryptionType = 0 ;
entry . IsEncrypted = false ;
entry . File = parent ;
entry . Path = rpath ;
entry . Name = name ;
dir . Files . Add ( entry ) ;
parent . Children . Add ( file ) ;
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
using ( var bw = new BinaryWriter ( fstream ) )
{
parent . InsertFileSpace ( bw , entry ) ;
fstream . Position = parent . StartPos + entry . FileOffset * 512 ;
file . WriteNewArchive ( bw , encryption ) ;
}
}
return file ;
}
public static RpfDirectoryEntry CreateDirectory ( RpfDirectoryEntry dir , string name )
{
//create a new directory inside the given parent dir
RpfFile parent = dir . File ;
2018-01-11 09:10:03 +08:00
string namel = name . ToLowerInvariant ( ) ;
2018-01-10 11:17:30 +08:00
string fpath = parent . GetPhysicalFilePath ( ) ;
string rpath = dir . Path + "\\" + namel ;
if ( ! File . Exists ( fpath ) )
{
throw new Exception ( "Root RPF file " + fpath + " does not exist!" ) ;
}
RpfDirectoryEntry entry = new RpfDirectoryEntry ( ) ;
entry . Parent = dir ;
entry . File = parent ;
entry . Path = rpath ;
entry . Name = name ;
foreach ( var exdir in dir . Directories )
{
2023-10-28 03:31:09 +08:00
if ( exdir . Name . Equals ( entry . Name , StringComparison . OrdinalIgnoreCase ) )
2018-01-10 11:17:30 +08:00
{
throw new Exception ( "RPF Directory \"" + entry . Name + "\" already exists!" ) ;
}
}
dir . Directories . Add ( entry ) ;
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
using ( var bw = new BinaryWriter ( fstream ) )
{
parent . EnsureAllEntries ( ) ;
parent . WriteHeader ( bw ) ;
}
}
return entry ;
}
2023-10-28 03:31:09 +08:00
public static RpfFileEntry CreateFileEntry ( string name , string path , Stream data )
{
//this should only really be used when loading a file from the filesystem.
RpfFileEntry e ;
using var reader = new BinaryReader ( data , Encoding . UTF8 , true ) ;
uint rsc7 = ( reader . BaseStream . Length > 4 ) ? reader . ReadUInt32 ( ) : 0 ;
if ( rsc7 = = 0x37435352 ) //RSC7 header present! create RpfResourceFileEntry and decompress data...
{
e = RpfFile . CreateResourceFileEntry ( data , 0 ) ; //"version" should be loadable from the header in the data..
data = ResourceBuilder . Decompress ( data ) ;
}
else
{
var be = new RpfBinaryFileEntry
{
FileSize = ( uint ) ( data ? . Length ? ? 0 ) ,
FileUncompressedSize = ( uint ) ( data ? . Length ? ? 0 ) ,
} ;
e = be ;
}
e . Name = name ;
e . Path = path ;
return e ;
}
public static RpfFileEntry CreateFileEntry ( string name , string path , ref byte [ ] data )
{
//this should only really be used when loading a file from the filesystem.
RpfFileEntry e = null ;
uint rsc7 = ( data ? . Length > 4 ) ? BitConverter . ToUInt32 ( data , 0 ) : 0 ;
if ( rsc7 = = 0x37435352 ) //RSC7 header present! create RpfResourceFileEntry and decompress data...
{
e = RpfFile . CreateResourceFileEntry ( ref data , 0 ) ; //"version" should be loadable from the header in the data..
data = ResourceBuilder . Decompress ( data ) ;
}
else
{
var be = new RpfBinaryFileEntry ( ) ;
be . FileSize = ( uint ) data ? . Length ;
be . FileUncompressedSize = be . FileSize ;
e = be ;
}
e . Name = name ;
e . Path = path ;
return e ;
}
2018-02-26 20:49:06 +08:00
public static RpfFileEntry CreateFile ( RpfDirectoryEntry dir , string name , byte [ ] data , bool overwrite = true )
2018-01-10 11:17:30 +08:00
{
2018-02-26 20:49:06 +08:00
if ( overwrite )
{
foreach ( var exfile in dir . Files )
{
2023-10-28 03:31:09 +08:00
if ( exfile . Name . Equals ( name , StringComparison . OrdinalIgnoreCase ) )
2018-02-26 20:49:06 +08:00
{
//file already exists. delete the existing one first!
//this should probably be optimised to just replace the existing one...
//TODO: investigate along with ExploreForm.ReplaceSelected()
DeleteEntry ( exfile ) ;
break ;
}
}
}
//else fail if already exists..? items with the same name allowed?
RpfFile parent = dir . File ;
2018-01-10 11:17:30 +08:00
string fpath = parent . GetPhysicalFilePath ( ) ;
2023-10-28 03:31:09 +08:00
string namel = name . ToLowerInvariant ( ) ;
2018-01-11 09:10:03 +08:00
string rpath = dir . Path + "\\" + namel ;
2018-01-10 11:17:30 +08:00
if ( ! File . Exists ( fpath ) )
{
throw new Exception ( "Root RPF file " + fpath + " does not exist!" ) ;
}
RpfFileEntry entry = null ;
uint len = ( uint ) data . Length ;
2018-01-11 09:10:03 +08:00
bool isrpf = false ;
2020-02-10 19:24:51 +08:00
bool isawc = false ;
2018-01-11 09:10:03 +08:00
uint hdr = 0 ;
if ( len > = 16 )
{
hdr = BitConverter . ToUInt32 ( data , 0 ) ;
}
if ( hdr = = 0x37435352 ) //'RSC7'
2018-01-10 11:17:30 +08:00
{
//RSC header is present... import as resource
var rentry = new RpfResourceFileEntry ( ) ;
rentry . SystemFlags = BitConverter . ToUInt32 ( data , 8 ) ;
rentry . GraphicsFlags = BitConverter . ToUInt32 ( data , 12 ) ;
rentry . FileSize = len ;
if ( len > = 0xFFFFFF )
{
//just....why
//FileSize = (buf[7] << 0) | (buf[14] << 8) | (buf[5] << 16) | (buf[2] << 24);
data [ 7 ] = ( byte ) ( ( len > > 0 ) & 0xFF ) ;
data [ 14 ] = ( byte ) ( ( len > > 8 ) & 0xFF ) ;
data [ 5 ] = ( byte ) ( ( len > > 16 ) & 0xFF ) ;
data [ 2 ] = ( byte ) ( ( len > > 24 ) & 0xFF ) ;
}
entry = rentry ;
}
2023-10-28 03:31:09 +08:00
if ( ( hdr = = 0x52504637 ) & & name . EndsWith ( ".rpf" , StringComparison . OrdinalIgnoreCase ) ) //'RPF7'
2018-01-11 09:10:03 +08:00
{
isrpf = true ;
}
2023-10-28 03:31:09 +08:00
if ( name . EndsWith ( ".awc" , StringComparison . OrdinalIgnoreCase ) )
2020-02-10 19:24:51 +08:00
{
isawc = true ;
}
2018-01-10 11:17:30 +08:00
if ( entry = = null )
{
//no RSC7 header present, import as a binary file.
2020-02-10 19:24:51 +08:00
var compressed = ( isrpf | | isawc ) ? data : CompressBytes ( data ) ;
2018-01-10 11:17:30 +08:00
var bentry = new RpfBinaryFileEntry ( ) ;
bentry . EncryptionType = 0 ; //TODO: binary encryption
bentry . IsEncrypted = false ;
bentry . FileUncompressedSize = ( uint ) data . Length ;
2020-02-10 19:24:51 +08:00
bentry . FileSize = ( isrpf | | isawc ) ? 0 : ( uint ) compressed . Length ;
2018-01-10 11:17:30 +08:00
if ( bentry . FileSize > 0xFFFFFF )
{
bentry . FileSize = 0 ;
compressed = data ;
//can't compress?? since apparently FileSize>0 means compressed...
}
data = compressed ;
entry = bentry ;
}
entry . Parent = dir ;
entry . File = parent ;
entry . Path = rpath ;
entry . Name = name ;
2023-10-28 03:31:09 +08:00
entry . NameLower = namel ;
2018-01-10 11:17:30 +08:00
foreach ( var exfile in dir . Files )
{
2023-10-28 03:31:09 +08:00
if ( exfile . Name . Equals ( entry . Name , StringComparison . OrdinalIgnoreCase ) )
2018-01-10 11:17:30 +08:00
{
throw new Exception ( "File \"" + entry . Name + "\" already exists!" ) ;
}
}
dir . Files . Add ( entry ) ;
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
2023-10-28 03:31:09 +08:00
using var bw = new BinaryWriter ( fstream ) ;
parent . InsertFileSpace ( bw , entry ) ;
long bbeg = parent . StartPos + ( entry . FileOffset * 512 ) ;
long bend = bbeg + ( GetBlockCount ( entry . GetFileSize ( ) ) * 512 ) ;
fstream . Position = bbeg ;
fstream . Write ( data , 0 , data . Length ) ;
WritePadding ( fstream , bend ) ; //write 0's until the end of the block.
2018-01-10 11:17:30 +08:00
}
2018-01-11 09:10:03 +08:00
if ( isrpf )
{
//importing a raw RPF archive. create the new RpfFile object, and read its headers etc.
RpfFile file = new RpfFile ( name , rpath , data . LongLength ) ;
file . Parent = parent ;
file . ParentFileEntry = entry as RpfBinaryFileEntry ;
file . StartPos = parent . StartPos + ( entry . FileOffset * 512 ) ;
parent . Children . Add ( file ) ;
2023-10-28 03:31:09 +08:00
using var fstream = File . OpenRead ( fpath ) ;
using var br = new BinaryReader ( fstream ) ;
fstream . Position = file . StartPos ;
file . ScanStructure ( br , null , null ) ;
2018-01-11 09:10:03 +08:00
}
2018-01-10 11:17:30 +08:00
return entry ;
}
public static void RenameArchive ( RpfFile file , string newname )
{
//updates all items in the RPF with the new path - no actual file changes made here
//(since all the paths are generated at runtime and not stored)
file . Name = newname ;
file . Path = GetParentPath ( file . Path ) + newname ;
file . FilePath = GetParentPath ( file . FilePath ) + newname ;
file . UpdatePaths ( ) ;
}
public static void RenameEntry ( RpfEntry entry , string newname )
{
//rename the entry in the RPF header...
//also make sure any relevant child paths are updated...
string dirpath = GetParentPath ( entry . Path ) ;
entry . Name = newname ;
entry . Path = dirpath + newname ;
2023-10-28 03:31:09 +08:00
JenkIndex . EnsureLower ( entry . ShortName ) ; //could be anything... but it needs to be there
2018-01-10 11:17:30 +08:00
RpfFile parent = entry . File ;
string fpath = parent . GetPhysicalFilePath ( ) ;
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
using ( var bw = new BinaryWriter ( fstream ) )
{
parent . EnsureAllEntries ( ) ;
parent . WriteHeader ( bw ) ;
}
}
if ( entry is RpfDirectoryEntry )
{
//a folder was renamed, make sure all its children's paths get updated
parent . UpdatePaths ( entry as RpfDirectoryEntry ) ;
}
}
public static void DeleteEntry ( RpfEntry entry )
{
//delete this entry from the RPF header.
//also remove any references to this item in its parent directory...
2018-02-28 00:46:06 +08:00
//if this is a directory entry, this will delete the contents first
2018-01-10 11:17:30 +08:00
RpfFile parent = entry . File ;
string fpath = parent . GetPhysicalFilePath ( ) ;
if ( ! File . Exists ( fpath ) )
{
throw new Exception ( "Root RPF file " + fpath + " does not exist!" ) ;
}
RpfDirectoryEntry entryasdir = entry as RpfDirectoryEntry ;
RpfFileEntry entryasfile = entry as RpfFileEntry ; //it has to be one or the other...
if ( entryasdir ! = null )
{
2018-02-28 00:46:06 +08:00
var deldirs = entryasdir . Directories . ToArray ( ) ;
var delfiles = entryasdir . Files . ToArray ( ) ;
foreach ( var deldir in deldirs )
2018-01-10 11:17:30 +08:00
{
2018-02-28 00:46:06 +08:00
DeleteEntry ( deldir ) ;
}
foreach ( var delfile in delfiles )
{
DeleteEntry ( delfile ) ;
2018-01-10 11:17:30 +08:00
}
}
if ( entry . Parent = = null )
{
throw new Exception ( "Parent directory is null! This shouldn't happen - please refresh the folder!" ) ;
}
if ( entryasdir ! = null )
{
entry . Parent . Directories . Remove ( entryasdir ) ;
}
if ( entryasfile ! = null )
{
entry . Parent . Files . Remove ( entryasfile ) ;
var child = parent . FindChildArchive ( entryasfile ) ;
if ( child ! = null )
{
parent . Children . Remove ( child ) ; //RPF file being deleted...
}
}
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
using ( var bw = new BinaryWriter ( fstream ) )
{
parent . EnsureAllEntries ( ) ;
parent . WriteHeader ( bw ) ;
}
}
}
2018-02-26 20:49:06 +08:00
public static bool EnsureValidEncryption ( RpfFile file , Func < RpfFile , bool > confirm )
{
if ( file = = null ) return false ;
//currently assumes OPEN is the valid encryption type.
//TODO: support other encryption types!
bool needsupd = false ;
var f = file ;
List < RpfFile > files = new List < RpfFile > ( ) ;
while ( f ! = null )
{
if ( f . Encryption ! = RpfEncryption . OPEN )
{
if ( ! confirm ( f ) )
{
return false ;
}
needsupd = true ;
}
if ( needsupd )
{
files . Add ( f ) ;
}
f = f . Parent ;
}
//change encryption types, starting from the root rpf.
files . Reverse ( ) ;
foreach ( var cfile in files )
{
SetEncryptionType ( cfile , RpfEncryption . OPEN ) ;
}
return true ;
}
2018-01-11 09:10:03 +08:00
public static void SetEncryptionType ( RpfFile file , RpfEncryption encryption )
{
file . Encryption = encryption ;
string fpath = file . GetPhysicalFilePath ( ) ;
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
using ( var bw = new BinaryWriter ( fstream ) )
{
file . WriteHeader ( bw ) ;
}
}
}
2018-01-10 11:17:30 +08:00
2020-01-27 22:21:53 +08:00
public static void Defragment ( RpfFile file , Action < string , float > progress = null )
2018-01-12 12:19:32 +08:00
{
if ( file ? . AllEntries = = null ) return ;
string fpath = file . GetPhysicalFilePath ( ) ;
using ( var fstream = File . Open ( fpath , FileMode . Open , FileAccess . ReadWrite ) )
{
using ( var bw = new BinaryWriter ( fstream ) )
{
uint destblock = file . GetHeaderBlockCount ( ) ;
const int BUFFER_SIZE = 16384 ; //what buffer size is best for HDD copy?
var buffer = new byte [ BUFFER_SIZE ] ;
var allfiles = new List < RpfFileEntry > ( ) ;
for ( int i = 0 ; i < file . AllEntries . Count ; i + + )
{
var entry = file . AllEntries [ i ] as RpfFileEntry ;
if ( entry ! = null ) allfiles . Add ( entry ) ;
}
//make sure we process everything in the current order that they are in the archive
allfiles . Sort ( ( a , b ) = > { return a . FileOffset . CompareTo ( b . FileOffset ) ; } ) ;
for ( int i = 0 ; i < allfiles . Count ; i + + )
{
var entry = allfiles [ i ] ;
float prog = ( float ) i / allfiles . Count ;
string txt = "Relocating " + entry . Name + "..." ;
2020-01-27 22:21:53 +08:00
progress ? . Invoke ( txt , prog ) ;
2018-01-12 12:19:32 +08:00
var sourceblock = entry . FileOffset ;
var blockcount = GetBlockCount ( entry . GetFileSize ( ) ) ;
if ( sourceblock > destblock ) //should only be moving things toward the start
{
var source = file . StartPos + ( long ) sourceblock * 512 ;
var dest = file . StartPos + ( long ) destblock * 512 ;
var remlength = ( long ) blockcount * 512 ;
while ( remlength > 0 )
{
fstream . Position = source ;
int n = fstream . Read ( buffer , 0 , ( int ) Math . Min ( remlength , BUFFER_SIZE ) ) ;
fstream . Position = dest ;
fstream . Write ( buffer , 0 , n ) ;
source + = n ;
dest + = n ;
remlength - = n ;
}
entry . FileOffset = destblock ;
var entryrpf = file . FindChildArchive ( entry ) ;
if ( entryrpf ! = null )
{
entryrpf . UpdateStartPos ( file . StartPos + ( long ) entry . FileOffset * 512 ) ;
}
}
else if ( sourceblock ! = destblock )
{ } //shouldn't get here...
destblock + = blockcount ;
}
file . FileSize = ( long ) destblock * 512 ;
file . WriteHeader ( bw ) ;
if ( file . ParentFileEntry ! = null )
{
//make sure to also update the parent archive file entry, if there is one
file . ParentFileEntry . FileUncompressedSize = ( uint ) file . FileSize ;
file . ParentFileEntry . FileSize = 0 ;
if ( file . Parent ! = null )
{
file . Parent . WriteHeader ( bw ) ;
}
}
if ( file . Parent = = null )
{
//this is a root archive, so update the file's length to the new size.
fstream . SetLength ( file . FileSize ) ;
}
}
}
}
2018-01-10 11:17:30 +08:00
private static string GetParentPath ( string path )
{
string dirpath = path . Replace ( '/' , '\\' ) ; //just to make sure..
int lidx = dirpath . LastIndexOf ( '\\' ) ;
if ( lidx > 0 )
{
dirpath = dirpath . Substring ( 0 , lidx + 1 ) ;
}
if ( ! dirpath . EndsWith ( "\\" ) )
{
2023-10-28 03:31:09 +08:00
dirpath + = "\\" ;
2018-01-10 11:17:30 +08:00
}
return dirpath ;
}
2017-09-21 18:33:05 +08:00
public override string ToString ( )
{
return Path ;
}
}
public enum RpfEncryption : uint
{
2018-01-10 11:17:30 +08:00
NONE = 0 , //some modded RPF's may use this
OPEN = 0x4E45504F , //1313165391 "OPEN", ie. "no encryption"
2017-09-21 18:33:05 +08:00
AES = 0x0FFFFFF9 , //268435449
NG = 0x0FEFFFFF , //267386879
}
[TypeConverter(typeof(ExpandableObjectConverter))] public abstract class RpfEntry
{
public RpfFile File { get ; set ; }
2018-01-10 11:17:30 +08:00
public RpfDirectoryEntry Parent { get ; set ; }
2017-09-21 18:33:05 +08:00
2023-10-28 03:31:09 +08:00
public uint NameHash { get
{
if ( nameHash = = 0 & & ! string . IsNullOrEmpty ( Name ) )
{
nameHash = JenkHash . GenHashLower ( Name ) ;
}
return nameHash ;
}
set
{
nameHash = value ;
}
}
public uint ShortNameHash { get
{
if ( shortNameHash = = 0 & & ! string . IsNullOrEmpty ( ShortName ) )
{
shortNameHash = JenkHash . GenHashLower ( ShortName ) ;
}
return shortNameHash ;
}
set
{
shortNameHash = value ;
}
}
2017-09-21 18:33:05 +08:00
public uint NameOffset { get ; set ; }
2023-10-28 03:31:09 +08:00
public string Name { get = > name ; set
{
if ( name = = value )
{
return ;
}
name = value ;
nameLower = null ;
nameHash = 0 ;
shortNameHash = 0 ;
shortName = null ;
}
}
public string NameLower
{
get
{
return nameLower ? ? = Name ? . ToLowerInvariant ( ) ;
}
set { nameLower = value ; }
}
public string ShortName {
get
{
if ( string . IsNullOrEmpty ( shortName ) & & ! string . IsNullOrEmpty ( Name ) )
{
int ind = Name . LastIndexOf ( '.' ) ;
if ( ind > 0 )
{
shortName = Name . Substring ( 0 , ind ) ;
}
else
{
shortName = Name ;
}
}
return shortName ;
}
set
{
shortName = value ;
}
}
2017-09-21 18:33:05 +08:00
public string Path { get ; set ; }
public uint H1 ; //first 2 header values from RPF table...
public uint H2 ;
2023-10-28 03:31:09 +08:00
private string name ;
private string nameLower ;
private uint shortNameHash ;
private uint nameHash ;
private string shortName ;
2017-09-21 18:33:05 +08:00
public abstract void Read ( DataReader reader ) ;
public abstract void Write ( DataWriter writer ) ;
public override string ToString ( )
{
return Path ;
}
2018-01-10 11:17:30 +08:00
public string GetShortName ( )
{
2023-10-28 03:31:09 +08:00
return ShortName ;
2018-01-10 11:17:30 +08:00
}
2017-09-21 18:33:05 +08:00
}
2023-10-28 03:31:09 +08:00
[TypeConverter(typeof(ExpandableObjectConverter))]
public class RpfDirectoryEntry : RpfEntry
2017-09-21 18:33:05 +08:00
{
public uint EntriesIndex { get ; set ; }
public uint EntriesCount { get ; set ; }
public List < RpfDirectoryEntry > Directories = new List < RpfDirectoryEntry > ( ) ;
public List < RpfFileEntry > Files = new List < RpfFileEntry > ( ) ;
public override void Read ( DataReader reader )
{
NameOffset = reader . ReadUInt32 ( ) ;
uint ident = reader . ReadUInt32 ( ) ;
if ( ident ! = 0x7FFFFF00 u )
{
throw new Exception ( "Error in RPF7 directory entry." ) ;
}
EntriesIndex = reader . ReadUInt32 ( ) ;
EntriesCount = reader . ReadUInt32 ( ) ;
}
public override void Write ( DataWriter writer )
{
writer . Write ( NameOffset ) ;
writer . Write ( 0x7FFFFF00 u ) ;
writer . Write ( EntriesIndex ) ;
writer . Write ( EntriesCount ) ;
}
public override string ToString ( )
{
return "Directory: " + Path ;
}
}
2023-10-28 03:31:09 +08:00
[TypeConverter(typeof(ExpandableObjectConverter))]
public abstract class RpfFileEntry : RpfEntry
2017-09-21 18:33:05 +08:00
{
public uint FileOffset { get ; set ; }
public uint FileSize { get ; set ; }
public bool IsEncrypted { get ; set ; }
2018-01-10 11:17:30 +08:00
public abstract long GetFileSize ( ) ;
public abstract void SetFileSize ( uint s ) ;
2017-09-21 18:33:05 +08:00
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class RpfBinaryFileEntry : RpfFileEntry
{
public uint FileUncompressedSize { get ; set ; }
2017-12-20 07:52:50 +08:00
public uint EncryptionType { get ; set ; }
2017-09-21 18:33:05 +08:00
public override void Read ( DataReader reader )
{
2017-12-20 07:52:50 +08:00
ulong buf = reader . ReadUInt64 ( ) ;
NameOffset = ( uint ) buf & 0xFFFF ;
FileSize = ( uint ) ( buf > > 16 ) & 0xFFFFFF ;
FileOffset = ( uint ) ( buf > > 40 ) & 0xFFFFFF ;
2017-09-21 18:33:05 +08:00
FileUncompressedSize = reader . ReadUInt32 ( ) ;
2017-12-20 07:52:50 +08:00
EncryptionType = reader . ReadUInt32 ( ) ;
switch ( EncryptionType )
2017-09-21 18:33:05 +08:00
{
case 0 : IsEncrypted = false ; break ;
case 1 : IsEncrypted = true ; break ;
default :
2023-10-28 03:31:09 +08:00
throw new Exception ( $"Error in RPF7 file entry. {EncryptionType}" ) ;
2017-09-21 18:33:05 +08:00
}
2018-01-10 11:17:30 +08:00
2017-09-21 18:33:05 +08:00
}
public override void Write ( DataWriter writer )
{
writer . Write ( ( ushort ) NameOffset ) ;
var buf1 = new byte [ ] {
( byte ) ( ( FileSize > > 0 ) & 0xFF ) ,
( byte ) ( ( FileSize > > 8 ) & 0xFF ) ,
( byte ) ( ( FileSize > > 16 ) & 0xFF )
} ;
writer . Write ( buf1 ) ;
var buf2 = new byte [ ] {
( byte ) ( ( FileOffset > > 0 ) & 0xFF ) ,
( byte ) ( ( FileOffset > > 8 ) & 0xFF ) ,
( byte ) ( ( FileOffset > > 16 ) & 0xFF )
} ;
writer . Write ( buf2 ) ;
writer . Write ( FileUncompressedSize ) ;
if ( IsEncrypted )
writer . Write ( ( uint ) 1 ) ;
else
writer . Write ( ( uint ) 0 ) ;
}
public override string ToString ( )
{
return "Binary file: " + Path ;
}
public override long GetFileSize ( )
{
2018-01-10 11:17:30 +08:00
return ( FileSize = = 0 ) ? FileUncompressedSize : FileSize ;
}
public override void SetFileSize ( uint s )
{
//FileUncompressedSize = s;
FileSize = s ;
2017-09-21 18:33:05 +08:00
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class RpfResourceFileEntry : RpfFileEntry
{
2020-03-18 02:31:11 +08:00
public RpfResourcePageFlags SystemFlags { get ; set ; }
public RpfResourcePageFlags GraphicsFlags { get ; set ; }
2017-09-21 18:33:05 +08:00
public static int GetSizeFromFlags ( uint flags )
{
//dexfx simplified version
var s0 = ( ( flags > > 27 ) & 0x1 ) < < 0 ; // 1 bit - 27 (*1)
var s1 = ( ( flags > > 26 ) & 0x1 ) < < 1 ; // 1 bit - 26 (*2)
var s2 = ( ( flags > > 25 ) & 0x1 ) < < 2 ; // 1 bit - 25 (*4)
var s3 = ( ( flags > > 24 ) & 0x1 ) < < 3 ; // 1 bit - 24 (*8)
var s4 = ( ( flags > > 17 ) & 0x7F ) < < 4 ; // 7 bits - 17 - 23 (*16) (max 127 * 16)
var s5 = ( ( flags > > 11 ) & 0x3F ) < < 5 ; // 6 bits - 11 - 16 (*32) (max 63 * 32)
var s6 = ( ( flags > > 7 ) & 0xF ) < < 6 ; // 4 bits - 7 - 10 (*64) (max 15 * 64)
var s7 = ( ( flags > > 5 ) & 0x3 ) < < 7 ; // 2 bits - 5 - 6 (*128) (max 3 * 128)
var s8 = ( ( flags > > 4 ) & 0x1 ) < < 8 ; // 1 bit - 4 (*256)
var ss = ( ( flags > > 0 ) & 0xF ) ; // 4 bits - 0 - 3
var baseSize = 0x200 < < ( int ) ss ;
var size = baseSize * ( s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 ) ;
return ( int ) size ;
2020-03-18 02:31:11 +08:00
#region dexyfex testing
2017-09-21 18:33:05 +08:00
//var type = flags >> 28;
//var test = GetFlagsFromSize((int)size, type);
//s0 = ((test >> 27) & 0x1) << 0; // 1 bit - 27 (*1)
//s1 = ((test >> 26) & 0x1) << 1; // 1 bit - 26 (*2)
//s2 = ((test >> 25) & 0x1) << 2; // 1 bit - 25 (*4)
//s3 = ((test >> 24) & 0x1) << 3; // 1 bit - 24 (*8)
//s4 = ((test >> 17) & 0x7F) << 4; // 7 bits - 17 - 23 (*16) (max 127 * 16)
//s5 = ((test >> 11) & 0x3F) << 5; // 6 bits - 11 - 16 (*32) (max 63 * 32)
//s6 = ((test >> 7) & 0xF) << 6; // 4 bits - 7 - 10 (*64) (max 15 * 64)
//s7 = ((test >> 5) & 0x3) << 7; // 2 bits - 5 - 6 (*128) (max 3 * 128)
//s8 = ((test >> 4) & 0x1) << 8; // 1 bit - 4 (*256)
//ss = ((test >> 0) & 0xF); // 4 bits - 0 - 3
//baseSize = 0x200 << (int)ss;
//var tsize = baseSize * (s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8);
//if (tsize != size)
//{ }
//if (s8 == 256)
//{ }
//if ((s0 != 0) || (s1 != 0) || (s2 != 0) || (s3 != 0))
//{ }
//return (int)size;
//examples:
//size:8192, ss:0, s4:1 (ytd)
//size:16384, ss:0, s5:1 (ytyp)
//size:24576, ss:0, s4:1, s5:1 (ytyp)
//size:40960, ss:0, s4:1, s5:2 (ytyp)
//size:49152, ss:0, s4:2, s5:2 (ytyp)
//size:237568, ss:0, s4:5, s7:1, s8:1 (yft)
//size:262144, ss:1, s8:1 (yft)
//size:589824, ss:1, s6:9 (ytd)
//size:663552, ss:1, s3:1, s4:12, s6:1, s7:3 (ydd)
//size:606208, ss:2, s3:1, s4:2, s8:1 (ydr)
//size:958464, ss:2, s2:1, s4:1, s6:3, s8:1 (ydr)
//size:966656, ss:2, s3:1, s4:1, s6:3, s8:1 (ydr)
//size:1695744, ss:2, s2:1, s3:1, s4:5, s5:3, s7:3, s8:1 (ydr)
//size:2768896, ss:3, s2:1, s4:24, s5:1, s6:4 (ydd)
//size:4063232, ss:4, s4:15, s7:2 (ytd)
//size:8650752, ss:5, s4:13, s6:5 (ytd)
2020-03-18 02:31:11 +08:00
#endregion
2017-09-21 18:33:05 +08:00
#region original neo version ( system )
//const int RESOURCE_IDENT = 0x37435352;
//const int BASE_SIZE = 0x2000;
//var SystemPagesDiv16 = (int)(SystemFlags >> 27) & 0x1;
//var SystemPagesDiv8 = (int)(SystemFlags >> 26) & 0x1;
//var SystemPagesDiv4 = (int)(SystemFlags >> 25) & 0x1;
//var SystemPagesDiv2 = (int)(SystemFlags >> 24) & 0x1;
//var SystemPagesMul1 = (int)(SystemFlags >> 17) & 0x7F;
//var SystemPagesMul2 = (int)(SystemFlags >> 11) & 0x3F;
//var SystemPagesMul4 = (int)(SystemFlags >> 7) & 0xF;
//var SystemPagesMul8 = (int)(SystemFlags >> 5) & 0x3;
//var SystemPagesMul16 = (int)(SystemFlags >> 4) & 0x1;
//var SystemPagesSizeShift = (int)(SystemFlags >> 0) & 0xF;
//var systemBaseSize = BASE_SIZE << SystemPagesSizeShift;
//return
// (systemBaseSize * SystemPagesDiv16) / 16 +
// (systemBaseSize * SystemPagesDiv8) / 8 +
// (systemBaseSize * SystemPagesDiv4) / 4 +
// (systemBaseSize * SystemPagesDiv2) / 2 +
// (systemBaseSize * SystemPagesMul1) * 1 +
// (systemBaseSize * SystemPagesMul2) * 2 +
// (systemBaseSize * SystemPagesMul4) * 4 +
// (systemBaseSize * SystemPagesMul8) * 8 +
// (systemBaseSize * SystemPagesMul16) * 16;
#endregion
#region original neo version ( graphics )
//const int RESOURCE_IDENT = 0x37435352;
//const int BASE_SIZE = 0x2000;
//var GraphicsPagesDiv16 = (int)(GraphicsFlags >> 27) & 0x1;
//var GraphicsPagesDiv8 = (int)(GraphicsFlags >> 26) & 0x1;
//var GraphicsPagesDiv4 = (int)(GraphicsFlags >> 25) & 0x1;
//var GraphicsPagesDiv2 = (int)(GraphicsFlags >> 24) & 0x1;
//var GraphicsPagesMul1 = (int)(GraphicsFlags >> 17) & 0x7F;
//var GraphicsPagesMul2 = (int)(GraphicsFlags >> 11) & 0x3F;
//var GraphicsPagesMul4 = (int)(GraphicsFlags >> 7) & 0xF;
//var GraphicsPagesMul8 = (int)(GraphicsFlags >> 5) & 0x3;
//var GraphicsPagesMul16 = (int)(GraphicsFlags >> 4) & 0x1;
//var GraphicsPagesSizeShift = (int)(GraphicsFlags >> 0) & 0xF;
//var graphicsBaseSize = BASE_SIZE << GraphicsPagesSizeShift;
//return
// graphicsBaseSize * GraphicsPagesDiv16 / 16 +
// graphicsBaseSize * GraphicsPagesDiv8 / 8 +
// graphicsBaseSize * GraphicsPagesDiv4 / 4 +
// graphicsBaseSize * GraphicsPagesDiv2 / 2 +
// graphicsBaseSize * GraphicsPagesMul1 * 1 +
// graphicsBaseSize * GraphicsPagesMul2 * 2 +
// graphicsBaseSize * GraphicsPagesMul4 * 4 +
// graphicsBaseSize * GraphicsPagesMul8 * 8 +
// graphicsBaseSize * GraphicsPagesMul16 * 16;
#endregion
}
public static uint GetFlagsFromSize ( int size , uint version )
{
//WIP - may make crashes :(
//type: see SystemSize and GraphicsSize below
//aim for s4: blocksize (0 remainder for 0x2000 block)
int origsize = size ;
int remainder = size & 0x1FF ;
int blocksize = 0x200 ;
if ( remainder ! = 0 )
{
size = ( size - remainder ) + blocksize ; //round up to the minimum blocksize
}
uint blockcount = ( uint ) size > > 9 ; //how many blocks of the minimum size (0x200)
uint ss = 0 ;
while ( blockcount > 1024 )
{
ss + + ;
blockcount = blockcount > > 1 ;
}
if ( ss > 0 )
{
size = origsize ;
blocksize = blocksize < < ( int ) ss ; //adjust the block size to reduce the block count.
remainder = size & blocksize ;
if ( remainder ! = 0 )
{
size = ( size - remainder ) + blocksize ; //readjust size with round-up
}
}
var s0 = ( blockcount > > 0 ) & 0x1 ; //*1 X
var s1 = ( blockcount > > 1 ) & 0x1 ; //*2 X
var s2 = ( blockcount > > 2 ) & 0x1 ; //*4 X
var s3 = ( blockcount > > 3 ) & 0x1 ; //*8 X
var s4 = ( blockcount > > 4 ) & 0x7F ; //*16 7 bits XXXXXXX
var s5 = ( blockcount > > 5 ) & 0x3F ; //*32 6 bits XXXXXX
var s6 = ( blockcount > > 6 ) & 0xF ; //*64 4 bits XXXX
var s7 = ( blockcount > > 7 ) & 0x3 ; //*128 2 bits XX
var s8 = ( blockcount > > 8 ) & 0x1 ; //*256 X
if ( ss > 4 )
{ }
if ( s4 > 0x7F )
{ } //too big...
//needs more work to include higher bits..
uint f = 0 ;
f | = ( version & 0xF ) < < 28 ;
f | = ( s0 & 0x1 ) < < 27 ;
f | = ( s1 & 0x1 ) < < 26 ;
f | = ( s2 & 0x1 ) < < 25 ;
f | = ( s3 & 0x1 ) < < 24 ;
f | = ( s4 & 0x7F ) < < 17 ;
f | = ( ss & 0xF ) ;
return f ;
//var s0 = ((flags >> 27) & 0x1) << 0; // 1 bit - 27 (*1)
//var s1 = ((flags >> 26) & 0x1) << 1; // 1 bit - 26 (*2)
//var s2 = ((flags >> 25) & 0x1) << 2; // 1 bit - 25 (*4)
//var s3 = ((flags >> 24) & 0x1) << 3; // 1 bit - 24 (*8)
//var s4 = ((flags >> 17) & 0x7F) << 4; // 7 bits - 17 - 23 (*16) (max 127 * 16)
//var s5 = ((flags >> 11) & 0x3F) << 5; // 6 bits - 11 - 16 (*32) (max 63 * 32)
//var s6 = ((flags >> 7) & 0xF) << 6; // 4 bits - 7 - 10 (*64) (max 15 * 64)
//var s7 = ((flags >> 5) & 0x3) << 7; // 2 bits - 5 - 6 (*128) (max 3 * 128)
//var s8 = ((flags >> 4) & 0x1) << 8; // 1 bit - 4 (*256)
//var ss = ((flags >> 0) & 0xF); // 4 bits - 0 - 3
//var baseSize = 0x200 << (int)ss;
//var size = baseSize * (s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8);
}
public static uint GetFlagsFromBlocks ( uint blockCount , uint blockSize , uint version )
{
//dexfx test version - seems to work mostly...
uint s0 = 0 ;
uint s1 = 0 ;
uint s2 = 0 ;
uint s3 = 0 ;
uint s4 = 0 ;
uint s5 = 0 ;
uint s6 = 0 ;
uint s7 = 0 ;
uint s8 = 0 ;
uint ss = 0 ;
uint bst = blockSize ;
if ( blockCount > 0 )
{
while ( bst > 0x200 ) //ss is number of bits to shift 0x200 to get blocksize...
{
ss + + ;
bst = bst > > 1 ;
}
}
s0 = ( blockCount > > 0 ) & 0x1 ; //*1 X
s1 = ( blockCount > > 1 ) & 0x1 ; //*2 X
s2 = ( blockCount > > 2 ) & 0x1 ; //*4 X
s3 = ( blockCount > > 3 ) & 0x1 ; //*8 X
s4 = ( blockCount > > 4 ) & 0x7F ; //*16 7 bits XXXXXXX
//s5 = (blockCount >> 5) & 0x3F; //*32 6 bits XXXXXX
//s6 = (blockCount >> 6) & 0xF; //*64 4 bits XXXX
//s7 = (blockCount >> 7) & 0x3; //*128 2 bits XX
//s8 = (blockCount >> 8) & 0x1; //*256 X
//if (blockCount > 0)
//{
// var curblocksize = 0x2000u;
// var totsize = blockCount * blockSize;
// var totcount = totsize / curblocksize;
// if ((totsize % curblocksize) > 0) totcount++;
// ss = 4;
// while (totcount > 0x7f)
// {
// ss++;
// curblocksize = curblocksize << 1;
// totcount = totsize / curblocksize;
// if ((totsize % curblocksize) > 0) totcount++;
// if (ss >= 16)
// { break; }
// }
// s4 = totcount >> 4;
// s3 = (totcount >> 3) & 1;
// s2 = (totcount >> 2) & 1;
// s1 = (totcount >> 1) & 1;
// s0 = (totcount >> 0) & 1;
//}
if ( ss > 0xF )
{ } //too big...
if ( s4 > 0x7F )
{ } //too big...
//needs more work to include higher bits..
uint f = 0 ;
f | = ( version & 0xF ) < < 28 ;
f | = ( s0 & 0x1 ) < < 27 ;
f | = ( s1 & 0x1 ) < < 26 ;
f | = ( s2 & 0x1 ) < < 25 ;
f | = ( s3 & 0x1 ) < < 24 ;
f | = ( s4 & 0x7F ) < < 17 ;
f | = ( s5 & 0x3F ) < < 11 ;
f | = ( s6 & 0xF ) < < 7 ;
f | = ( s7 & 0x3 ) < < 5 ;
f | = ( s8 & 0x1 ) < < 4 ;
f | = ( ss & 0xF ) ;
return f ;
}
public static int GetVersionFromFlags ( uint sysFlags , uint gfxFlags )
{
var sv = ( sysFlags > > 28 ) & 0xF ;
var gv = ( gfxFlags > > 28 ) & 0xF ;
return ( int ) ( ( sv < < 4 ) + gv ) ;
}
public int Version
{
get
{
return GetVersionFromFlags ( SystemFlags , GraphicsFlags ) ;
}
}
public int SystemSize
{
get
{
2020-03-18 02:31:11 +08:00
return ( int ) SystemFlags . Size ;
2017-09-21 18:33:05 +08:00
}
}
public int GraphicsSize
{
get
{
2020-03-18 02:31:11 +08:00
return ( int ) GraphicsFlags . Size ;
2017-09-21 18:33:05 +08:00
}
}
public override void Read ( DataReader reader )
{
2023-10-28 03:31:09 +08:00
var buffer = ArrayPool < byte > . Shared . Rent ( 3 ) ;
2017-09-21 18:33:05 +08:00
NameOffset = reader . ReadUInt16 ( ) ;
2023-10-28 03:31:09 +08:00
reader . ReadBytes ( 3 , buffer ) ;
FileSize = ( uint ) buffer [ 0 ] + ( uint ) ( buffer [ 1 ] < < 8 ) + ( uint ) ( buffer [ 2 ] < < 16 ) ;
2017-09-21 18:33:05 +08:00
2023-10-28 03:31:09 +08:00
reader . ReadBytes ( 3 , buffer ) ;
FileOffset = ( ( uint ) buffer [ 0 ] + ( uint ) ( buffer [ 1 ] < < 8 ) + ( uint ) ( buffer [ 2 ] < < 16 ) ) & 0x7FFFFF ;
ArrayPool < byte > . Shared . Return ( buffer ) ;
2017-09-21 18:33:05 +08:00
SystemFlags = reader . ReadUInt32 ( ) ;
GraphicsFlags = reader . ReadUInt32 ( ) ;
// there are sometimes resources with length=0xffffff which actually
// means length>=0xffffff
if ( FileSize = = 0xFFFFFF )
{
BinaryReader cfr = File . CurrentFileReader ;
long opos = cfr . BaseStream . Position ;
cfr . BaseStream . Position = File . StartPos + ( ( long ) FileOffset * 512 ) ; //need to use the base offset!!
var buf = cfr . ReadBytes ( 16 ) ;
FileSize = ( ( uint ) buf [ 7 ] < < 0 ) | ( ( uint ) buf [ 14 ] < < 8 ) | ( ( uint ) buf [ 5 ] < < 16 ) | ( ( uint ) buf [ 2 ] < < 24 ) ;
cfr . BaseStream . Position = opos ;
}
}
public override void Write ( DataWriter writer )
{
writer . Write ( ( ushort ) NameOffset ) ;
2018-01-10 11:17:30 +08:00
var fs = FileSize ;
if ( fs > 0xFFFFFF ) fs = 0xFFFFFF ; //will also need to make sure the RSC header is updated...
2017-09-21 18:33:05 +08:00
var buf1 = new byte [ ] {
2018-01-10 11:17:30 +08:00
( byte ) ( ( fs > > 0 ) & 0xFF ) ,
( byte ) ( ( fs > > 8 ) & 0xFF ) ,
( byte ) ( ( fs > > 16 ) & 0xFF )
2017-09-21 18:33:05 +08:00
} ;
writer . Write ( buf1 ) ;
var buf2 = new byte [ ] {
( byte ) ( ( FileOffset > > 0 ) & 0xFF ) ,
( byte ) ( ( FileOffset > > 8 ) & 0xFF ) ,
( byte ) ( ( ( FileOffset > > 16 ) & 0xFF ) | 0x80 )
} ;
writer . Write ( buf2 ) ;
writer . Write ( SystemFlags ) ;
writer . Write ( GraphicsFlags ) ;
}
public override string ToString ( )
{
return "Resource file: " + Path ;
}
public override long GetFileSize ( )
{
return ( FileSize = = 0 ) ? ( long ) ( SystemSize + GraphicsSize ) : FileSize ;
}
2018-01-10 11:17:30 +08:00
public override void SetFileSize ( uint s )
{
FileSize = s ;
}
2017-09-21 18:33:05 +08:00
}
2020-03-18 02:31:11 +08:00
[TypeConverter(typeof(ExpandableObjectConverter))] public struct RpfResourcePageFlags
{
public uint Value { get ; set ; }
public RpfResourcePage [ ] Pages
{
get
{
var count = Count ;
if ( count = = 0 ) return null ;
var pages = new RpfResourcePage [ count ] ;
var counts = PageCounts ;
var sizes = BaseSizes ;
int n = 0 ;
uint o = 0 ;
for ( int i = 0 ; i < counts . Length ; i + + )
{
var c = counts [ i ] ;
var s = sizes [ i ] ;
for ( int p = 0 ; p < c ; p + + )
{
pages [ n ] = new RpfResourcePage ( ) { Size = s , Offset = o } ;
o + = s ;
n + + ;
}
}
return pages ;
}
}
2017-09-21 18:33:05 +08:00
2020-03-18 02:31:11 +08:00
public uint TypeVal { get { return ( Value > > 28 ) & 0xF ; } }
public uint BaseShift { get { return ( Value & 0xF ) ; } }
public uint BaseSize { get { return ( 0x200 u < < ( int ) BaseShift ) ; } }
public uint [ ] BaseSizes
{
get
{
var baseSize = BaseSize ;
return new uint [ ]
{
baseSize < < 8 ,
baseSize < < 7 ,
baseSize < < 6 ,
baseSize < < 5 ,
baseSize < < 4 ,
baseSize < < 3 ,
baseSize < < 2 ,
baseSize < < 1 ,
baseSize < < 0 ,
} ;
}
}
public uint [ ] PageCounts
{
get
{
return new uint [ ]
{
( ( Value > > 4 ) & 0x1 ) ,
( ( Value > > 5 ) & 0x3 ) ,
( ( Value > > 7 ) & 0xF ) ,
( ( Value > > 11 ) & 0x3F ) ,
( ( Value > > 17 ) & 0x7F ) ,
( ( Value > > 24 ) & 0x1 ) ,
( ( Value > > 25 ) & 0x1 ) ,
( ( Value > > 26 ) & 0x1 ) ,
( ( Value > > 27 ) & 0x1 ) ,
} ;
}
}
public uint [ ] PageSizes
{
get
{
var counts = PageCounts ;
var baseSizes = BaseSizes ;
return new uint [ ]
{
baseSizes [ 0 ] * counts [ 0 ] ,
baseSizes [ 1 ] * counts [ 1 ] ,
baseSizes [ 2 ] * counts [ 2 ] ,
baseSizes [ 3 ] * counts [ 3 ] ,
baseSizes [ 4 ] * counts [ 4 ] ,
baseSizes [ 5 ] * counts [ 5 ] ,
baseSizes [ 6 ] * counts [ 6 ] ,
baseSizes [ 7 ] * counts [ 7 ] ,
baseSizes [ 8 ] * counts [ 8 ] ,
} ;
}
}
public uint Count
{
get
{
var c = PageCounts ;
return c [ 0 ] + c [ 1 ] + c [ 2 ] + c [ 3 ] + c [ 4 ] + c [ 5 ] + c [ 6 ] + c [ 7 ] + c [ 8 ] ;
}
}
public uint Size
{
get
{
var flags = Value ;
var s0 = ( ( flags > > 27 ) & 0x1 ) < < 0 ;
var s1 = ( ( flags > > 26 ) & 0x1 ) < < 1 ;
var s2 = ( ( flags > > 25 ) & 0x1 ) < < 2 ;
var s3 = ( ( flags > > 24 ) & 0x1 ) < < 3 ;
var s4 = ( ( flags > > 17 ) & 0x7F ) < < 4 ;
var s5 = ( ( flags > > 11 ) & 0x3F ) < < 5 ;
var s6 = ( ( flags > > 7 ) & 0xF ) < < 6 ;
var s7 = ( ( flags > > 5 ) & 0x3 ) < < 7 ;
var s8 = ( ( flags > > 4 ) & 0x1 ) < < 8 ;
var ss = ( ( flags > > 0 ) & 0xF ) ;
var baseSize = 0x200 u < < ( int ) ss ;
return baseSize * ( s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 ) ;
}
}
public RpfResourcePageFlags ( uint v )
{
Value = v ;
}
public RpfResourcePageFlags ( uint [ ] pageCounts , uint baseShift )
{
var v = baseShift & 0xF ;
v + = ( pageCounts [ 0 ] & 0x1 ) < < 4 ;
v + = ( pageCounts [ 1 ] & 0x3 ) < < 5 ;
v + = ( pageCounts [ 2 ] & 0xF ) < < 7 ;
v + = ( pageCounts [ 3 ] & 0x3F ) < < 11 ;
v + = ( pageCounts [ 4 ] & 0x7F ) < < 17 ;
v + = ( pageCounts [ 5 ] & 0x1 ) < < 24 ;
v + = ( pageCounts [ 6 ] & 0x1 ) < < 25 ;
v + = ( pageCounts [ 7 ] & 0x1 ) < < 26 ;
v + = ( pageCounts [ 8 ] & 0x1 ) < < 27 ;
Value = v ;
}
public static implicit operator uint ( RpfResourcePageFlags f )
{
return f . Value ; //implicit conversion
}
public static implicit operator RpfResourcePageFlags ( uint v )
{
return new RpfResourcePageFlags ( v ) ;
}
public override string ToString ( )
{
return "Size: " + Size . ToString ( ) + ", Pages: " + Count . ToString ( ) ;
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public struct RpfResourcePage
{
public uint Size { get ; set ; }
public uint Offset { get ; set ; }
public override string ToString ( )
{
return Size . ToString ( ) + ": " + Offset . ToString ( ) ;
}
}
2017-09-21 18:33:05 +08:00
public interface PackedFile //interface for the different file types to use
{
void Load ( byte [ ] data , RpfFileEntry entry ) ;
}
2023-10-28 03:31:09 +08:00
public interface PackedFileStream : PackedFile
{
void Load ( Stream stream , RpfFileEntry entry ) ;
}
2017-09-21 18:33:05 +08:00
}