YpfScheme QueryEncryptionScheme(string arc_name, uint version) { var title = FormatCatalog.Instance.LookupGame(arc_name); if (string.IsNullOrEmpty(title)) { title = FormatCatalog.Instance.LookupGame(arc_name, @"..\*.exe"); } YpfScheme scheme; if (!string.IsNullOrEmpty(title) && KnownSchemes.TryGetValue(title, out scheme)) { return(scheme); } var options = Query <YpfOptions> (arcStrings.YPFNotice); if (!KnownSchemes.TryGetValue(options.Scheme, out scheme) || null == scheme) { scheme = new YpfScheme { SwapTable = GuessSwapTable(version), GuessKey = true, ExtraHeaderSize = version >= 0x1D9 ? 4u : 0u, } } ; return(scheme); } delegate uint ChecksumFunc(byte[] data);
YpfScheme QueryEncryptionScheme(uint version) { var options = Query <YpfOptions> (arcStrings.YPFNotice); YpfScheme scheme; if (!KnownSchemes.TryGetValue(options.Scheme, out scheme) || null == scheme) { scheme = new YpfScheme { SwapTable = GuessSwapTable(version), GuessKey = true, ExtraHeaderSize = 0x1F4 == version ? 4u : 0u, } } ; return(scheme); } delegate uint ChecksumFunc(byte[] data);
// int32-name_checksum, byte-name_count, *-name, byte-file_type // byte-pack_flag, int32-size, int32-packed_size, int32-offset, int32-file_checksum public List <Entry> ScanDir(YpfScheme scheme) { uint dir_offset = 0x20; uint dir_remaining = m_dir_size; var dir = new List <Entry> ((int)m_count); byte key = scheme.Key; bool guess_key = scheme.GuessKey; uint extra_size = 0x12 + scheme.ExtraHeaderSize; for (uint num = 0; num < m_count; ++num) { if (dir_remaining < 5 + extra_size) { return(null); } dir_remaining -= 5 + extra_size; uint name_size = DecryptLength(scheme.SwapTable, (byte)(m_file.View.ReadByte(dir_offset + 4) ^ 0xff)); if (name_size > dir_remaining) { return(null); } dir_remaining -= name_size; dir_offset += 5; if (0 == name_size) { return(null); } byte[] raw_name = m_file.View.ReadBytes(dir_offset, name_size); dir_offset += name_size; if (guess_key) { if (name_size < 4) { return(null); } // assume filename contains '.' and 3-characters extension. key = (byte)(raw_name[name_size - 4] ^ '.'); guess_key = false; } for (uint i = 0; i < name_size; ++i) { raw_name[i] ^= key; } string name = Encodings.cp932.GetString(raw_name); // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd // 0x122, 0x12C, 0x196: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd // 0x1F4: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd, 8-ycg 9-psb int type_id = m_file.View.ReadByte(dir_offset); var entry = FormatCatalog.Instance.Create <PackedEntry> (name); if (string.IsNullOrEmpty(entry.Type)) { switch (type_id) { case 0: entry.Type = "script"; break; case 1: case 2: case 3: case 4: case 8: entry.Type = "image"; break; case 5: entry.Type = 0xf7 == m_version ? "video" : "audio"; break; case 6: entry.Type = "audio"; break; case 7: entry.Type = 0xf7 == m_version ? "audio" : "image"; break; } } entry.IsPacked = 0 != m_file.View.ReadByte(dir_offset + 1); entry.UnpackedSize = m_file.View.ReadUInt32(dir_offset + 2); entry.Size = m_file.View.ReadUInt32(dir_offset + 6); entry.Offset = m_file.View.ReadUInt32(dir_offset + 10); if (entry.CheckPlacement(m_file.MaxOffset)) { dir.Add(entry); } dir_offset += extra_size; } return(dir); }
public override void Create(Stream output, IEnumerable <Entry> list, ResourceOptions options, EntryCallback callback) { var ypf_options = GetOptions <YpfOptions> (options); if (null == ypf_options) { throw new ArgumentException("Invalid archive creation options", "options"); } if (ypf_options.Key > 0xff) { throw new InvalidEncryptionScheme(arcStrings.MsgCreationKeyRequired); } if (0 == ypf_options.Version) { throw new InvalidFormatException(arcStrings.MsgInvalidVersion); } var scheme = new YpfScheme { SwapTable = GuessSwapTable(ypf_options.Version), Key = (byte)ypf_options.Key }; int callback_count = 0; var encoding = Encodings.cp932.WithFatalFallback(); ChecksumFunc Checksum = data => Crc32.Compute(data, 0, data.Length); uint data_offset = 0x20; var file_table = new List <YpfEntry>(); foreach (var entry in list) { try { string file_name = entry.Name; byte[] name_buf = encoding.GetBytes(file_name); if (name_buf.Length > 0xff) { throw new InvalidFileName(entry.Name, arcStrings.MsgFileNameTooLong); } uint hash = Checksum(name_buf); byte file_type = GetFileType(ypf_options.Version, file_name); for (int i = 0; i < name_buf.Length; ++i) { name_buf[i] = (byte)(name_buf[i] ^ ypf_options.Key); } file_table.Add(new YpfEntry { Name = file_name, IndexName = name_buf, NameHash = hash, FileType = file_type, IsPacked = 0 == file_type, }); data_offset += (uint)(0x17 + name_buf.Length); } catch (EncoderFallbackException X) { throw new InvalidFileName(entry.Name, arcStrings.MsgIllegalCharacters, X); } } file_table.Sort((a, b) => a.NameHash.CompareTo(b.NameHash)); output.Position = data_offset; uint current_offset = data_offset; foreach (var entry in file_table) { if (null != callback) { callback(callback_count++, entry, arcStrings.MsgAddingFile); } entry.Offset = current_offset; using (var input = File.OpenRead(entry.Name)) { var file_size = input.Length; if (file_size > uint.MaxValue || current_offset + file_size > uint.MaxValue) { throw new FileSizeException(); } entry.UnpackedSize = (uint)file_size; using (var checked_stream = new CheckedStream(output, new Adler32())) { if (entry.IsPacked) { var start = output.Position; using (var zstream = new ZLibStream(checked_stream, CompressionMode.Compress, CompressionLevel.Level9, true)) { input.CopyTo(zstream); } entry.Size = (uint)(output.Position - start); } else { input.CopyTo(checked_stream); entry.Size = entry.UnpackedSize; } checked_stream.Flush(); entry.CheckSum = checked_stream.CheckSumValue; current_offset += entry.Size; } } } if (null != callback) { callback(callback_count++, null, arcStrings.MsgWritingIndex); } output.Position = 0; using (var writer = new BinaryWriter(output, encoding, true)) { writer.Write(Signature); writer.Write(ypf_options.Version); writer.Write(file_table.Count); writer.Write(data_offset); writer.BaseStream.Seek(0x20, SeekOrigin.Begin); foreach (var entry in file_table) { writer.Write(entry.NameHash); byte name_len = (byte)~Parser.DecryptLength(scheme.SwapTable, (byte)entry.IndexName.Length); writer.Write(name_len); writer.Write(entry.IndexName); writer.Write(entry.FileType); writer.Write(entry.IsPacked); writer.Write(entry.UnpackedSize); writer.Write(entry.Size); writer.Write((uint)entry.Offset); writer.Write(entry.CheckSum); } } }
// int32-name_checksum, byte-name_count, *-name, byte-file_type // byte-pack_flag, int32-size, int32-packed_size, int32-offset, int32-file_checksum public List<Entry> ScanDir(YpfScheme scheme) { uint dir_offset = 0x20; uint dir_remaining = m_dir_size; var dir = new List<Entry> ((int)m_count); byte key = scheme.Key; bool guess_key = scheme.GuessKey; uint extra_size = 0x12 + scheme.ExtraHeaderSize; for (uint num = 0; num < m_count; ++num) { if (dir_remaining < 5+extra_size) return null; dir_remaining -= 5+extra_size; uint name_size = DecryptLength (scheme.SwapTable, (byte)(m_file.View.ReadByte (dir_offset+4) ^ 0xff)); if (name_size > dir_remaining) return null; dir_remaining -= name_size; dir_offset += 5; if (0 == name_size) return null; byte[] raw_name = m_file.View.ReadBytes (dir_offset, name_size); dir_offset += name_size; if (guess_key) { if (name_size < 4) return null; // assume filename contains '.' and 3-characters extension. key = (byte)(raw_name[name_size-4] ^ '.'); guess_key = false; } for (uint i = 0; i < name_size; ++i) { raw_name[i] ^= key; } string name = Encodings.cp932.GetString (raw_name); // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd // 0x122, 0x12C, 0x196: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd // 0x1F4: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd, 8-ycg 9-psb int type_id = m_file.View.ReadByte (dir_offset); var entry = FormatCatalog.Instance.Create<PackedEntry> (name); if (string.IsNullOrEmpty (entry.Type)) { switch (type_id) { case 0: entry.Type = "script"; break; case 1: case 2: case 3: case 4: case 8: entry.Type = "image"; break; case 5: entry.Type = 0xf7 == m_version ? "video" : "audio"; break; case 6: entry.Type = "audio"; break; case 7: entry.Type = 0xf7 == m_version ? "audio" : "image"; break; } } entry.IsPacked = 0 != m_file.View.ReadByte (dir_offset+1); entry.UnpackedSize = m_file.View.ReadUInt32 (dir_offset+2); entry.Size = m_file.View.ReadUInt32 (dir_offset+6); entry.Offset = m_file.View.ReadUInt32 (dir_offset+10); if (entry.CheckPlacement (m_file.MaxOffset)) dir.Add (entry); dir_offset += extra_size; } return dir; }
YpfScheme QueryEncryptionScheme(uint version) { var options = Query<YpfOptions> (arcStrings.YPFNotice); YpfScheme scheme; if (!KnownSchemes.TryGetValue (options.Scheme, out scheme) || null == scheme) scheme = new YpfScheme { SwapTable = GuessSwapTable (version), GuessKey = true, ExtraHeaderSize = 0x1F4 == version || 0x1E1 == version ? 4u : 0u, }; return scheme; }
public override void Create(Stream output, IEnumerable<Entry> list, ResourceOptions options, EntryCallback callback) { var ypf_options = GetOptions<YpfOptions> (options); if (null == ypf_options) throw new ArgumentException ("Invalid archive creation options", "options"); if (ypf_options.Key > 0xff) throw new InvalidEncryptionScheme (arcStrings.MsgCreationKeyRequired); if (0 == ypf_options.Version) throw new InvalidFormatException (arcStrings.MsgInvalidVersion); var scheme = new YpfScheme { SwapTable = GuessSwapTable (ypf_options.Version), Key = (byte)ypf_options.Key }; int callback_count = 0; var encoding = Encodings.cp932.WithFatalFallback(); ChecksumFunc Checksum = data => Crc32.Compute (data, 0, data.Length); uint data_offset = 0x20; var file_table = new List<YpfEntry>(); foreach (var entry in list) { try { string file_name = entry.Name; byte[] name_buf = encoding.GetBytes (file_name); if (name_buf.Length > 0xff) throw new InvalidFileName (entry.Name, arcStrings.MsgFileNameTooLong); uint hash = Checksum (name_buf); byte file_type = GetFileType (ypf_options.Version, file_name); for (int i = 0; i < name_buf.Length; ++i) name_buf[i] = (byte)(name_buf[i] ^ ypf_options.Key); file_table.Add (new YpfEntry { Name = file_name, IndexName = name_buf, NameHash = hash, FileType = file_type, IsPacked = 0 == file_type, }); data_offset += (uint)(0x17 + name_buf.Length); } catch (EncoderFallbackException X) { throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X); } } file_table.Sort ((a, b) => a.NameHash.CompareTo (b.NameHash)); output.Position = data_offset; uint current_offset = data_offset; foreach (var entry in file_table) { if (null != callback) callback (callback_count++, entry, arcStrings.MsgAddingFile); entry.Offset = current_offset; using (var input = File.OpenRead (entry.Name)) { var file_size = input.Length; if (file_size > uint.MaxValue || current_offset + file_size > uint.MaxValue) throw new FileSizeException(); entry.UnpackedSize = (uint)file_size; using (var checked_stream = new CheckedStream (output, new Adler32())) { if (entry.IsPacked) { var start = output.Position; using (var zstream = new ZLibStream (checked_stream, CompressionMode.Compress, CompressionLevel.Level9, true)) { input.CopyTo (zstream); } entry.Size = (uint)(output.Position - start); } else { input.CopyTo (checked_stream); entry.Size = entry.UnpackedSize; } checked_stream.Flush(); entry.CheckSum = checked_stream.CheckSumValue; current_offset += entry.Size; } } } if (null != callback) callback (callback_count++, null, arcStrings.MsgWritingIndex); output.Position = 0; using (var writer = new BinaryWriter (output, encoding, true)) { writer.Write (Signature); writer.Write (ypf_options.Version); writer.Write (file_table.Count); writer.Write (data_offset); writer.BaseStream.Seek (0x20, SeekOrigin.Begin); foreach (var entry in file_table) { writer.Write (entry.NameHash); byte name_len = (byte)~Parser.DecryptLength (scheme.SwapTable, (byte)entry.IndexName.Length); writer.Write (name_len); writer.Write (entry.IndexName); writer.Write (entry.FileType); writer.Write (entry.IsPacked); writer.Write (entry.UnpackedSize); writer.Write (entry.Size); writer.Write ((uint)entry.Offset); writer.Write (entry.CheckSum); } } }