PAK.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text.RegularExpressions;
  7. namespace OpenVIII.PAK_Extractor
  8. {
  9. /// <summary>
  10. /// PAK reading class, Gathers all the sections
  11. /// </summary>
  12. /// <remarks>Original class wrote by Maki for the ToolKit</remarks>
  13. /// <see cref="https://github.com/MaKiPL/FF8-Rinoa-s-Toolset/tree/master/SerahToolkit_SharpGL/FF8_Core"/>
  14. public class PAK : IEnumerable
  15. {
  16. #region Fields
  17. /// <summary>
  18. /// Each frame section of cam file is this many bytes
  19. /// </summary>
  20. private const byte CamSectionSize = 0x2C;
  21. /// <summary>
  22. /// If false don't extract low res videos!
  23. /// </summary>
  24. private const bool enableExtractLowRes = false;
  25. /// <summary>
  26. /// Known valid Bink video formats
  27. /// </summary>
  28. private readonly byte[] bik1 = new byte[] { 0x61, 0x64, 0x66, 0x67, 0x68, 0x69 };
  29. /// <summary>
  30. /// Known valid Bink 2 video formats
  31. /// </summary>
  32. private readonly byte[] bik2 = new byte[] { 0x62, 0x64, 0x66, 0x67, 0x68, 0x69 };
  33. /// <summary>
  34. /// Each Movie has 1 cam and 2 versions of the video.
  35. /// </summary>
  36. private List<MovieClip> _movies;
  37. /// <summary>
  38. /// Remembers detected disc number.
  39. /// </summary>
  40. private int discCache = -1;
  41. /// <summary>
  42. /// Current working tmp movie clip
  43. /// </summary>
  44. private MovieClip movie;
  45. /// <summary>
  46. /// Depending on type you read it differently.
  47. /// </summary>
  48. private Dictionary<_Type, Func<BinaryReader, _Type, FileSection>> ReadFunctions;
  49. #endregion Fields
  50. #region Constructors
  51. public PAK(FileInfo info)
  52. {
  53. _movies = new List<MovieClip>(7);
  54. ReadFunctions = new Dictionary<_Type, Func<BinaryReader, _Type, FileSection>>()
  55. {
  56. {_Type.CAM, ReadCAM },
  57. {_Type.BIK, ReadBIK },
  58. {_Type.KB2, ReadKB2 },
  59. };
  60. Read(info);
  61. }
  62. #endregion Constructors
  63. #region Enums
  64. public enum _Type : uint
  65. {
  66. /// <summary>
  67. /// Cam file
  68. /// </summary>
  69. /// <remarks>F8P</remarks>
  70. CAM = 0x503846,
  71. /// <summary>
  72. /// Bink Video
  73. /// </summary>
  74. BIK = 0x4B4942,
  75. /// <summary>
  76. /// Bink Video Version 2.
  77. /// </summary>
  78. KB2 = 0X32424B,
  79. /// <summary>
  80. /// 3 Byte Mask to take uint and extract only the part we need.
  81. /// </summary>
  82. _3B_MASK = 0xFFFFFF,
  83. }
  84. #endregion Enums
  85. #region Properties
  86. /// <summary>
  87. /// Current path
  88. /// </summary>
  89. public FileInfo FilePath { get; private set; }
  90. /// <summary>
  91. /// Each Movie has 1 cam and 2 versions of the video.
  92. /// </summary>
  93. public IReadOnlyList<MovieClip> Movies => _movies;
  94. /// <summary>
  95. /// Total number of movies detected
  96. /// </summary>
  97. public int Count => _movies.Count;
  98. #endregion Properties
  99. #region Indexers
  100. /// <summary>
  101. /// Gets Movie Clip at index
  102. /// </summary>
  103. /// <param name="i">Index</param>
  104. public MovieClip this[int i] => _movies[i];
  105. #endregion Indexers
  106. #region Methods
  107. public void Extract(string destPath)
  108. {
  109. Console.WriteLine($"Extracting {FilePath.FullName}");
  110. foreach (PAK.MovieClip item in this)
  111. {
  112. using (BinaryReader br = new BinaryReader(File.OpenRead(FilePath.FullName)))
  113. {
  114. Extract(br, item.CAM);
  115. Extract(br, item.BINK_HIGH);
  116. if (enableExtractLowRes)
  117. #pragma warning disable CS0162 // Unreachable code detected
  118. Extract(br, item.BINK_LOW);
  119. #pragma warning restore CS0162 // Unreachable code detected
  120. }
  121. }
  122. void Extract(BinaryReader br, PAK.FileSection fs)
  123. {
  124. string outpath = Path.Combine(destPath, fs.FileName);
  125. bool overwrite = true;
  126. if (File.Exists(outpath))
  127. {
  128. using (FileStream s = File.OpenRead(outpath))
  129. {
  130. overwrite = !(s.Length == fs.Size);
  131. }
  132. if (overwrite) File.Delete(outpath);
  133. else
  134. {
  135. Console.WriteLine($"File Exists {fs.FileName}");
  136. return;
  137. }
  138. }
  139. using (BinaryWriter bw = new BinaryWriter(File.Create(outpath)))
  140. {
  141. Console.WriteLine($"Extracting {fs.FileName}");
  142. br.BaseStream.Seek(fs.Offset, SeekOrigin.Begin);
  143. bw.Write(br.ReadBytes((int)fs.Size));
  144. }
  145. }
  146. }
  147. public IEnumerator GetEnumerator() => ((IEnumerable)_movies).GetEnumerator();
  148. private string GenerateFileName(string extension, string suffux = "") =>
  149. string.Format("disc{0:00}_{1:00}{2}.{3}", GetDiscNumber() - 1, Count, suffux, extension.Trim('.').ToLower());
  150. private int GetDiscNumber()
  151. {
  152. if (discCache == -1)
  153. {
  154. Regex re = new Regex(@"\d+");
  155. Match m = re.Match(Path.GetFileNameWithoutExtension(FilePath.FullName));
  156. if (m.Success)
  157. {
  158. if (int.TryParse(m.Value, out discCache))
  159. {
  160. return discCache;
  161. }
  162. else
  163. throw new Exception($"{this} :: Could not parse number from: {m.Value}");
  164. }
  165. else
  166. {
  167. throw new Exception($"{this} :: No number in filename: {FilePath}");
  168. }
  169. }
  170. return discCache;
  171. }
  172. /// <summary>
  173. /// Read complete pak file for offsets and sizes of each section.
  174. /// </summary>
  175. /// <param name="info">File path info</param>
  176. private void Read(FileInfo info)
  177. {
  178. FilePath = info;
  179. using (BinaryReader br = new BinaryReader(File.OpenRead(info.FullName)))
  180. {
  181. movie = new MovieClip();
  182. while (br.BaseStream.Position < br.BaseStream.Length)
  183. {
  184. _Type header = (_Type)(br.ReadUInt32() & (uint)_Type._3B_MASK);
  185. if (ReadFunctions.ContainsKey(header))
  186. {
  187. ReadFunctions[header](br, header);
  188. }
  189. else throw new Exception($"{header} is invalid, reading from: {info}, offset {br.BaseStream.Position}");
  190. }
  191. }
  192. }
  193. /// <summary>
  194. /// Read Bink video offset and size
  195. /// </summary>
  196. /// <param name="br">Binary reader</param>
  197. /// <param name="type">Header of file type</param>
  198. private FileSection ReadBIK(BinaryReader br, _Type type)
  199. {
  200. br.BaseStream.Seek(-1, SeekOrigin.Current);
  201. byte version = br.ReadByte();
  202. if ((type == _Type.BIK && bik1.Contains(version)) || (type == _Type.KB2 && bik2.Contains(version)))
  203. {
  204. }
  205. else
  206. throw new Exception($"_Type {type}, version {version}, is invalid");
  207. FileSection fs = new FileSection()
  208. {
  209. _Type = type,
  210. Offset = br.BaseStream.Position - 4,
  211. Size = br.ReadUInt32() + 8,
  212. Frames = br.ReadUInt32()
  213. };
  214. br.BaseStream.Seek(fs.Offset + fs.Size, SeekOrigin.Begin);
  215. if (movie.BINK_HIGH == null)
  216. {
  217. movie.BINK_HIGH = fs;
  218. movie.BINK_HIGH.FileName = GenerateFileName(movie.BINK_HIGH._Type == _Type.BIK ? "bik" : "bk2", "h");
  219. }
  220. else
  221. {
  222. if (fs.Size > movie.BINK_HIGH.Size)
  223. {
  224. movie.BINK_LOW = movie.BINK_HIGH;
  225. movie.BINK_HIGH = fs;
  226. }
  227. else
  228. {
  229. movie.BINK_LOW = fs;
  230. }
  231. movie.BINK_HIGH.FileName = GenerateFileName(movie.BINK_LOW._Type == _Type.BIK ? "bik" : "bk2", "h");
  232. movie.BINK_LOW.FileName = GenerateFileName(movie.BINK_LOW._Type == _Type.BIK ? "bik" : "bk2", "l");
  233. _movies.Add(movie);
  234. movie = new MovieClip();
  235. }
  236. return fs;
  237. }
  238. /// <summary>
  239. /// Read cam file offset and size
  240. /// </summary>
  241. /// <param name="br">Binary reader</param>
  242. /// <param name="type">Header of file type</param>
  243. private FileSection ReadCAM(BinaryReader br, _Type type)
  244. {
  245. long offset = br.BaseStream.Position - 4; // start of section
  246. br.BaseStream.Seek(2, SeekOrigin.Current); // skip 2 bytes
  247. ushort frames = br.ReadUInt16(); // get approx number of frames
  248. br.BaseStream.Seek((frames) * CamSectionSize, SeekOrigin.Current);
  249. uint b = 0;
  250. // there seems to be 1 or more extra frames. Check for those.
  251. while ((b = br.ReadUInt32()) > 0 && !(((_Type)(b & (uint)_Type._3B_MASK)) == _Type.BIK || ((_Type)(b & (uint)_Type._3B_MASK)) == _Type.KB2))
  252. {
  253. br.BaseStream.Seek(CamSectionSize - sizeof(uint), SeekOrigin.Current);
  254. frames++;
  255. }
  256. // Found the end go back to it.
  257. br.BaseStream.Seek(-sizeof(uint), SeekOrigin.Current);
  258. // There is only one cam per movie. Checking for possibility of only one video instead of the normal 2 per movie.
  259. if (movie.CAM != null)
  260. {
  261. if (!_movies.Contains(movie))
  262. //add existing movie to movies list.
  263. _movies.Add(movie);
  264. //start from scratch
  265. movie = new MovieClip();
  266. }
  267. FileSection fs = new FileSection() { _Type = type, Offset = offset, Size = br.BaseStream.Position - offset, Frames = frames, FileName = GenerateFileName("cam") };
  268. movie.CAM = fs;
  269. return fs;
  270. }
  271. private FileSection ReadKB2(BinaryReader br, _Type header) => ReadBIK(br, header);
  272. #endregion Methods
  273. #region Structs
  274. public struct MovieClip
  275. {
  276. #region Fields
  277. /// <summary>
  278. /// High res bink video
  279. /// </summary>
  280. public FileSection BINK_HIGH;
  281. /// <summary>
  282. /// Low res bink video
  283. /// </summary>
  284. public FileSection BINK_LOW;
  285. /// <summary>
  286. /// Cam file
  287. /// </summary>
  288. public FileSection CAM;
  289. #endregion Fields
  290. }
  291. #endregion Structs
  292. #region Classes
  293. public class FileSection
  294. {
  295. #region Fields
  296. /// <summary>
  297. /// Type of file in Section
  298. /// </summary>
  299. public _Type _Type;
  300. /// <summary>
  301. /// Output FileName;
  302. /// </summary>
  303. public string FileName;
  304. /// <summary>
  305. /// Frame count
  306. /// </summary>
  307. public uint Frames;
  308. /// <summary>
  309. /// Location of Data
  310. /// </summary>
  311. public long Offset;
  312. /// <summary>
  313. /// Size of Data
  314. /// </summary>
  315. public long Size;
  316. #endregion Fields
  317. }
  318. #endregion Classes
  319. }
  320. }