private Afs2Archive GetExternalAwbArchive() { var acbFileName = AcbFileName; var awbDirPath = Path.GetDirectoryName(acbFileName); if (awbDirPath == null) { awbDirPath = string.Empty; } var awbBaseFileName = Path.GetFileNameWithoutExtension(acbFileName); string[] awbFiles = null; string awbMask1 = null, awbMask2 = null, awbMask3 = null; if (awbFiles == null || awbFiles.Length < 1) { awbMask1 = string.Format(AwbFileNameFormats.Format1, awbBaseFileName); awbFiles = Directory.GetFiles(awbDirPath, awbMask1, SearchOption.TopDirectoryOnly); } if (awbFiles == null || awbFiles.Length < 1) { awbMask2 = string.Format(AwbFileNameFormats.Format2, awbBaseFileName); awbFiles = Directory.GetFiles(awbDirPath, awbMask2, SearchOption.TopDirectoryOnly); } if (awbFiles == null || awbFiles.Length < 1) { awbMask3 = string.Format(AwbFileNameFormats.Format3, awbBaseFileName); awbFiles = Directory.GetFiles(awbDirPath, awbMask3, SearchOption.TopDirectoryOnly); } if (awbFiles.Length < 1) { throw new FileNotFoundException($"Cannot find AWB file. Please verify corresponding AWB file is named '{awbMask1}', '{awbMask2}', or '{awbMask3}'."); } if (awbFiles.Length > 1) { throw new FileNotFoundException($"More than one matching AWB file for this ACB. Please verify only one AWB file is named '{awbMask1}', '{awbMask2}' or '{awbMask3}'."); } var externalAwbHash = GetFieldValueAsData(0, "StreamAwbHash"); var fs = File.Open(awbFiles[0], FileMode.Open, FileAccess.Read, FileShare.Read); var awbHash = AcbHelper.GetMd5Checksum(fs); if (!AcbHelper.AreDataIdentical(awbHash, externalAwbHash)) { Trace.WriteLine($"Checksum of AWB file '{fs.Name}' ({BitConverter.ToString(awbHash)}) does not match MD5 checksum inside ACB file '{acbFileName}' ({BitConverter.ToString(externalAwbHash)})."); } var archive = new Afs2Archive(fs, 0, fs.Name, true); archive.Initialize(); return(archive); }
private Stream GetDataStreamFromCueInfo(AcbCueRecord cue, string fileNameForErrorInfo) { if (!cue.IsWaveformIdentified) { throw new InvalidOperationException($"File '{fileNameForErrorInfo}' is not identified."); } Stream result; if (cue.IsStreaming) { var externalAwb = ExternalAwb; if (externalAwb == null) { throw new InvalidOperationException($"External AWB does not exist for streaming file '{fileNameForErrorInfo}'."); } if (!externalAwb.Files.ContainsKey(cue.WaveformId)) { throw new InvalidOperationException($"Waveform ID {cue.WaveformId} is not found in AWB file {externalAwb.FileName}."); } var targetExternalFile = externalAwb.Files[cue.WaveformId]; using (var fs = File.Open(externalAwb.FileName, FileMode.Open, FileAccess.Read, FileShare.Read)) { result = AcbHelper.ExtractToNewStream(fs, targetExternalFile.FileOffsetAligned, (int)targetExternalFile.FileLength); } } else { var internalAwb = InternalAwb; if (internalAwb == null) { throw new InvalidOperationException($"Internal AWB is not found for memory file '{fileNameForErrorInfo}' in '{AcbFileName}'."); } if (!internalAwb.Files.ContainsKey(cue.WaveformId)) { throw new InvalidOperationException($"Waveform ID {cue.WaveformId} is not found in internal AWB in {AcbFileName}."); } var targetInternalFile = internalAwb.Files[cue.WaveformId]; result = AcbHelper.ExtractToNewStream(Stream, targetInternalFile.FileOffsetAligned, (int)targetInternalFile.FileLength); } return(result); }
public Stream OpenDataStream(string fileName) { AcbCueRecord cue; try { cue = Cues.Single(c => c.CueName == fileName); } catch (InvalidOperationException ex) { throw new InvalidOperationException($"File '{fileName}' is not found or it has multiple entries.", ex); } if (!cue.IsWaveformIdentified) { throw new InvalidOperationException($"File '{fileName}' is not identified."); } if (cue.IsStreaming) { var externalAwb = ExternalAwb; if (externalAwb == null) { throw new InvalidOperationException($"External AWB does not exist for streaming file '{fileName}'."); } if (!externalAwb.Files.ContainsKey(cue.WaveformId)) { throw new InvalidOperationException($"Waveform ID {cue.WaveformId} is not found in AWB file {externalAwb.FileName}."); } var targetExternalFile = externalAwb.Files[cue.WaveformId]; using (var fs = File.Open(externalAwb.FileName, FileMode.Open, FileAccess.Read)) { return(AcbHelper.ExtractToNewStream(fs, targetExternalFile.FileOffsetAligned, (int)targetExternalFile.FileLength)); } } else { var internalAwb = InternalAwb; if (internalAwb == null) { throw new InvalidOperationException($"Internal AWB is not found for memory file '{fileName}' in '{AcbFileName}'."); } if (!internalAwb.Files.ContainsKey(cue.WaveformId)) { throw new InvalidOperationException($"Waveform ID {cue.WaveformId} is not found in internal AWB in {AcbFileName}."); } var targetInternalFile = internalAwb.Files[cue.WaveformId]; return(AcbHelper.ExtractToNewStream(Stream, targetInternalFile.FileOffsetAligned, (int)targetInternalFile.FileLength)); } }
internal virtual void Initialize() { var stream = _stream; var offset = _offset; var magic = stream.PeekBytes(offset, 4); magic = CheckEncryption(magic); if (!AcbHelper.AreDataIdentical(magic, UtfSignature)) { throw new FormatException($"'@UTF' signature (or its encrypted equivalent) is not found in '{_acbFileName}' at offset 0x{offset:x8}."); } using (var tableDataStream = GetTableDataStream()) { var header = GetUtfHeader(tableDataStream); _utfHeader = header; _rows = new Dictionary <string, UtfField> [header.RowCount]; if (header.TableSize > 0) { InitializeUtfSchema(stream, tableDataStream, 0x20); } } }
private byte[] CheckEncryption(byte[] magicBytes) { if (AcbHelper.AreDataIdentical(magicBytes, UtfSignature)) { _isEncrypted = false; _utfReader = new UtfReader(); return(magicBytes); } else { _isEncrypted = true; byte seed, increment; var keysFound = GetKeysForEncryptedUtfTable(magicBytes, out seed, out increment); if (!keysFound) { throw new FormatException($"Unable to decrypt UTF table at offset 0x{_offset:x8}"); } else { _utfReader = new UtfReader(seed, increment); } return(UtfSignature); } }
public static bool IsAfs2Archive(Stream stream, long offset) { var fileSignature = stream.PeekBytes(offset, 4); return(AcbHelper.AreDataIdentical(fileSignature, Afs2Signature)); }
public void Initialize() { var stream = _stream; var offset = _streamOffset; var acbFileName = _fileName; if (!IsAfs2Archive(stream, offset)) { throw new FormatException($"File '{acbFileName}' does not contain a valid AFS2 archive at offset {offset}."); } var fileCount = (int)stream.PeekUInt32LE(offset + 8); if (fileCount > ushort.MaxValue) { throw new IndexOutOfRangeException($"File count {fileCount} exceeds maximum possible value (65535)."); } var files = new Dictionary <int, Afs2FileRecord>(fileCount); _files = files; var byteAlignment = stream.PeekUInt32LE(offset + 12); _byteAlignment = byteAlignment & 0xffff; // Is the mask a constant? _hcaKeyModifier = (ushort)(byteAlignment >> 16); var version = stream.PeekUInt32LE(offset + 4); _version = version; var offsetFieldSize = (int)(version >> 8) & 0xff; // versionBytes[1], always 4? uint offsetMask = 0; for (var j = 0; j < offsetFieldSize; j++) { offsetMask |= (uint)(0xff << (j * 8)); } const int invalidCueId = -1; var previousCueId = invalidCueId; var fileOffsetFieldBase = 0x10 + fileCount * 2; for (ushort i = 0; i < fileCount; ++i) { var currentFileOffsetBase = fileOffsetFieldBase + offsetFieldSize * i; var record = new Afs2FileRecord { CueId = stream.PeekUInt16LE(offset + (0x10 + 2 * i)), // TODO: Dynamically judge if the field is U32/U16 or else (see offsetFieldSize). FileOffsetRaw = stream.PeekUInt32LE(offset + currentFileOffsetBase) }; record.FileOffsetRaw &= offsetMask; record.FileOffsetRaw += offset; record.FileOffsetAligned = AcbHelper.RoundUpToAlignment(record.FileOffsetRaw, ByteAlignment); if (i == fileCount - 1) { record.FileLength = stream.PeekUInt32LE(offset + currentFileOffsetBase + offsetFieldSize) + offset - record.FileOffsetAligned; } if (previousCueId != invalidCueId) { files[previousCueId].FileLength = record.FileOffsetRaw - files[previousCueId].FileOffsetAligned; } files.Add(record.CueId, record); previousCueId = record.CueId; } }