PAK.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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 = { 0x61, 0x64, 0x66, 0x67, 0x68, 0x69 };
  29. /// <summary>
  30. /// Known valid Bink 2 video formats
  31. /// </summary>
  32. private readonly byte[] _bik2 = { 0x62, 0x64, 0x66, 0x67, 0x68, 0x69 };
  33. /// <summary>
  34. /// Each Movie has 1 cam and 2 versions of the video.
  35. /// </summary>
  36. private readonly List<MovieClip> _movies;
  37. /// <summary>
  38. /// Depending on type you read it differently.
  39. /// </summary>
  40. private readonly Dictionary<Type, Func<BinaryReader, Type, FileSection>> _readFunctions;
  41. /// <summary>
  42. /// Remembers detected disc number.
  43. /// </summary>
  44. private int _discCache = -1;
  45. /// <summary>
  46. /// Current working tmp movie clip
  47. /// </summary>
  48. private MovieClip _movie;
  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. /// Total number of movies detected
  88. /// </summary>
  89. public int Count => _movies.Count;
  90. /// <summary>
  91. /// Current path
  92. /// </summary>
  93. public FileInfo FilePath { get; private set; }
  94. /// <summary>
  95. /// Each Movie has 1 cam and 2 versions of the video.
  96. /// </summary>
  97. public IReadOnlyList<MovieClip> Movies => _movies;
  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 (MovieClip item in this)
  111. {
  112. using (var br = new BinaryReader(File.OpenRead(FilePath.FullName)))
  113. {
  114. Extract(br, item.Cam);
  115. Extract(br, item.BinkHigh);
  116. // ReSharper disable once ConditionIsAlwaysTrueOrFalse
  117. if (EnableExtractLowRes)
  118. // ReSharper disable once HeuristicUnreachableCode
  119. #pragma warning disable 162
  120. Extract(br, item.BinkLow);
  121. #pragma warning restore 162
  122. }
  123. }
  124. void Extract(BinaryReader br, FileSection fs)
  125. {
  126. var outPath = Path.Combine(destPath, fs.FileName);
  127. if (File.Exists(outPath))
  128. {
  129. bool overwrite;
  130. using (var s = File.OpenRead(outPath))
  131. {
  132. overwrite = s.Length != fs.Size;
  133. }
  134. if (overwrite) File.Delete(outPath);
  135. else
  136. {
  137. Console.WriteLine($"File Exists {fs.FileName}");
  138. return;
  139. }
  140. }
  141. using (var bw = new BinaryWriter(File.Create(outPath)))
  142. {
  143. Console.WriteLine($"Extracting {fs.FileName}");
  144. br.BaseStream.Seek(fs.Offset, SeekOrigin.Begin);
  145. bw.Write(br.ReadBytes((int)fs.Size));
  146. }
  147. }
  148. }
  149. public IEnumerator GetEnumerator() => ((IEnumerable)_movies).GetEnumerator();
  150. private string GenerateFileName(string extension, string suffix = "") =>
  151. $"disc{GetDiscNumber() - 1:00}_{Count:00}{suffix}.{extension.Trim('.').ToLower()}";
  152. private int GetDiscNumber()
  153. {
  154. if (_discCache == -1)
  155. {
  156. var re = new Regex(@"\d+");
  157. var m = re.Match(Path.GetFileNameWithoutExtension(FilePath.FullName));
  158. if (m.Success)
  159. {
  160. if (int.TryParse(m.Value, out _discCache))
  161. {
  162. return _discCache;
  163. }
  164. else
  165. throw new Exception($"{this} :: Could not parse number from: {m.Value}");
  166. }
  167. else
  168. {
  169. throw new Exception($"{this} :: No number in filename: {FilePath}");
  170. }
  171. }
  172. return _discCache;
  173. }
  174. /// <summary>
  175. /// Read complete pak file for offsets and sizes of each section.
  176. /// </summary>
  177. /// <param name="info">File path info</param>
  178. private void Read(FileInfo info)
  179. {
  180. FilePath = info;
  181. using (var br = new BinaryReader(File.OpenRead(info.FullName)))
  182. {
  183. _movie = new MovieClip();
  184. while (br.BaseStream.Position < br.BaseStream.Length)
  185. {
  186. var header = (Type)(br.ReadUInt32() & (uint)Type._3B_MASK);
  187. if (_readFunctions.ContainsKey(header))
  188. {
  189. _readFunctions[header](br, header);
  190. }
  191. else throw new Exception($"{header} is invalid, reading from: {info}, offset {br.BaseStream.Position}");
  192. }
  193. }
  194. }
  195. /// <summary>
  196. /// Read Bink video offset and size
  197. /// </summary>
  198. /// <param name="br">Binary reader</param>
  199. /// <param name="type">Header of file type</param>
  200. private FileSection ReadBik(BinaryReader br, Type type)
  201. {
  202. br.BaseStream.Seek(-1, SeekOrigin.Current);
  203. var version = br.ReadByte();
  204. if ((type == Type.Bik && _bik1.Contains(version)) || (type == Type.Kb2 && _bik2.Contains(version)))
  205. {
  206. }
  207. else
  208. throw new Exception($"_Type {type}, version {version}, is invalid");
  209. var fs = new FileSection()
  210. {
  211. Type = type,
  212. Offset = br.BaseStream.Position - 4,
  213. Size = br.ReadUInt32() + 8,
  214. Frames = br.ReadUInt32()
  215. };
  216. br.BaseStream.Seek(fs.Offset + fs.Size, SeekOrigin.Begin);
  217. if (_movie.BinkHigh == null)
  218. {
  219. _movie.BinkHigh = fs;
  220. _movie.BinkHigh.FileName = GenerateFileName(_movie.BinkHigh.Type == Type.Bik ? "bik" : "bk2", "h");
  221. }
  222. else
  223. {
  224. if (fs.Size > _movie.BinkHigh.Size)
  225. {
  226. _movie.BinkLow = _movie.BinkHigh;
  227. _movie.BinkHigh = fs;
  228. }
  229. else
  230. {
  231. _movie.BinkLow = fs;
  232. }
  233. _movie.BinkHigh.FileName = GenerateFileName(_movie.BinkLow.Type == Type.Bik ? "bik" : "bk2", "h");
  234. _movie.BinkLow.FileName = GenerateFileName(_movie.BinkLow.Type == Type.Bik ? "bik" : "bk2", "l");
  235. _movies.Add(_movie);
  236. _movie = new MovieClip();
  237. }
  238. return fs;
  239. }
  240. /// <summary>
  241. /// Read cam file offset and size
  242. /// </summary>
  243. /// <param name="br">Binary reader</param>
  244. /// <param name="type">Header of file type</param>
  245. private FileSection ReadCam(BinaryReader br, Type type)
  246. {
  247. var offset = br.BaseStream.Position - 4; // start of section
  248. br.BaseStream.Seek(2, SeekOrigin.Current); // skip 2 bytes
  249. var frames = br.ReadUInt16(); // get approx number of frames
  250. br.BaseStream.Seek((frames) * CamSectionSize, SeekOrigin.Current);
  251. uint b;
  252. // there seems to be 1 or more extra frames. Check for those.
  253. while ((b = br.ReadUInt32()) > 0 && !(((Type)(b & (uint)Type._3B_MASK)) == Type.Bik || ((Type)(b & (uint)Type._3B_MASK)) == Type.Kb2))
  254. {
  255. br.BaseStream.Seek(CamSectionSize - sizeof(uint), SeekOrigin.Current);
  256. frames++;
  257. }
  258. // Found the end go back to it.
  259. br.BaseStream.Seek(-sizeof(uint), SeekOrigin.Current);
  260. // There is only one cam per movie. Checking for possibility of only one video instead of the normal 2 per movie.
  261. if (_movie.Cam != null)
  262. {
  263. if (!_movies.Contains(_movie))
  264. //add existing movie to movies list.
  265. _movies.Add(_movie);
  266. //start from scratch
  267. _movie = new MovieClip();
  268. }
  269. var fs = new FileSection() { Type = type, Offset = offset, Size = br.BaseStream.Position - offset, Frames = frames, FileName = GenerateFileName("cam") };
  270. _movie.Cam = fs;
  271. return fs;
  272. }
  273. private FileSection ReadKb2(BinaryReader br, Type header) => ReadBik(br, header);
  274. #endregion Methods
  275. #region Structs
  276. public struct MovieClip
  277. {
  278. #region Fields
  279. /// <summary>
  280. /// High res bink video
  281. /// </summary>
  282. public FileSection BinkHigh;
  283. /// <summary>
  284. /// Low res bink video
  285. /// </summary>
  286. public FileSection BinkLow;
  287. /// <summary>
  288. /// Cam file
  289. /// </summary>
  290. public FileSection Cam;
  291. #endregion Fields
  292. }
  293. #endregion Structs
  294. #region Classes
  295. public class FileSection
  296. {
  297. #region Fields
  298. /// <summary>
  299. /// Output FileName;
  300. /// </summary>
  301. public string FileName;
  302. /// <summary>
  303. /// Frame Count
  304. /// </summary>
  305. public uint Frames;
  306. /// <summary>
  307. /// Location of Data
  308. /// </summary>
  309. public long Offset;
  310. /// <summary>
  311. /// Size of Data
  312. /// </summary>
  313. public long Size;
  314. /// <summary>
  315. /// Type of file in Section
  316. /// </summary>
  317. public Type Type;
  318. #endregion Fields
  319. }
  320. #endregion Classes
  321. }
  322. }