public static DuplicatableStream ReadDuplicatableSubstreamFromLocationAndReset(this Stream stream, long position, long bytecount) { long p = stream.Position; DuplicatableStream?ds = stream as DuplicatableStream; if (ds != null) { return(new PartialStream(ds, p, bytecount)); } else { stream.Position = position; DuplicatableStream nds = new DuplicatableByteArrayStream(ReadBytes(stream, bytecount)); stream.Position = p; return(nds); } }
private static void ReplaceFile(HyoutaTools.Tales.CPK.CpkFile file, DuplicatableByteArrayStream stream, bool tryCompress = false) { if (tryCompress && stream.Length > 0x100) { MemoryStream ms = new MemoryStream(); long len = utf_tab_sharp.CpkCompress.compress(stream, 0, stream.Length, ms); if (len < stream.Length) { file.FileStream = ms.CopyToByteArrayStreamAndDispose(); file.DecompressedSize = (uint)stream.Length; return; } } file.FileStream = stream; file.DecompressedSize = (uint)stream.Length; }
public t_voice_tbl(Stream stream) { Stream s = new DuplicatableByteArrayStream(stream.CopyToByteArray()); EndianUtils.Endianness e = EndianUtils.Endianness.LittleEndian; ushort count = s.ReadUInt16(e); if (s.PeekUInt32(e) == 1) { // CS2 has something odd I don't fully understand at the start of the file, try to skip past that s.Position += 0xe; } Entries = new List <t_voice_tbl_entry>(); for (int i = 0; i < count; ++i) { Entries.Add(new t_voice_tbl_entry(s, e)); } return; }
private static ulong PackDataBlock(Stream target, List <HyoutaArchiveFileInfo> files, byte packedAlignment, EndianUtils.Endianness endian) { byte smallPackedAlignment = ToSmallPackedAlignment(packedAlignment); long startPosition = target.Position; target.WriteUInt16(0); // offsetToFirstFileInfo, fill in later bool hasDummyContent = files.Any(x => x.DummyContent != null); uint dummyContentLength = hasDummyContent ? ((uint)files.Max(x => x.DummyContent?.Length ?? 0)).Align(1 << smallPackedAlignment) : 0; bool hasFilename = files.Any(x => x.Filename != null); uint filenameLength = 0; //bool embedFilenamesInFileInfo = false; List <byte[]?>?encodedFilenames = null; if (hasFilename) { // figure out whether we want the strings to embed into the fileinfo directly // or whether to use an offset and write the string data at the end of the fileinfo // note that if a string is <= 8 bytes we can always embed it as we'd need 8 bytes for the offset anyway // so... encodedFilenames = new List <byte[]?>(files.Count); long longestBytecount = 0; long totalBytecount = 0; long filenameCountOver8Bytes = 0; for (int i = 0; i < files.Count; ++i) { var currentFilename = files[i].Filename; if (currentFilename == null) { encodedFilenames.Add(null); } else { byte[] stringbytes = EncodeString(currentFilename); encodedFilenames.Add(stringbytes); if (stringbytes.LongLength > 8) { longestBytecount = Math.Max(longestBytecount, stringbytes.LongLength); totalBytecount += stringbytes.LongLength; ++filenameCountOver8Bytes; } } } // alright so we have, in practice, two options here // - make filenameLength == 16, store strings that are longer than that offsetted long nonEmbedSize = files.Count * 16 + totalBytecount.Align(1 << smallPackedAlignment); // - make filenameLength long enough so all strings can be embedded long embedSize = files.Count * (8 + longestBytecount).Align(1 << smallPackedAlignment); // pick whatever results in a smaller file; on a tie embed if (nonEmbedSize < embedSize) { //embedFilenamesInFileInfo = false; filenameLength = 16; } else { //embedFilenamesInFileInfo = true; filenameLength = (uint)(8 + longestBytecount).Align(1 << smallPackedAlignment); } } bool hasCompression = files.Any(x => x.CompressionInfo != null); uint compressionInfoLength = hasCompression ? files.Max(x => x.CompressionInfo?.MaximumCompressionInfoLength() ?? 0).Align(1 << smallPackedAlignment) : 0; bool hasBpsPatch = files.Any(x => x.BpsPatchInfo != null); uint bpsPatchInfoLength = hasBpsPatch ? 16u.Align(1 << smallPackedAlignment) : 0; bool hasCrc32 = files.Any(x => x.crc32 != null); uint crc32ContentLength = hasCrc32 ? 4u.Align(1 << smallPackedAlignment) : 0u; bool hasMd5 = files.Any(x => x.md5 != null); uint md5ContentLength = hasMd5 ? 16u.Align(1 << smallPackedAlignment) : 0u; bool hasSha1 = files.Any(x => x.sha1 != null); uint sha1ContentLength = hasSha1 ? 20u.Align(1 << smallPackedAlignment) : 0u; ushort contentBitfield1 = 0; contentBitfield1 |= (ushort)(hasDummyContent ? 0x0001u : 0); contentBitfield1 |= (ushort)(hasFilename ? 0x0002u : 0); contentBitfield1 |= (ushort)(hasCompression ? 0x0004u : 0); contentBitfield1 |= (ushort)(hasBpsPatch ? 0x0008u : 0); contentBitfield1 |= (ushort)(hasCrc32 ? 0x0010u : 0); contentBitfield1 |= (ushort)(hasMd5 ? 0x0020u : 0); contentBitfield1 |= (ushort)(hasSha1 ? 0x0040u : 0); target.WriteUInt16(contentBitfield1, endian); if (hasDummyContent) { WriteContentLength(dummyContentLength, target, endian); } if (hasFilename) { WriteContentLength(filenameLength, target, endian); } if (hasCompression) { WriteContentLength(compressionInfoLength, target, endian); } if (hasBpsPatch) { WriteContentLength(bpsPatchInfoLength, target, endian); } if (hasCrc32) { WriteContentLength(crc32ContentLength, target, endian); } if (hasMd5) { WriteContentLength(md5ContentLength, target, endian); } if (hasSha1) { WriteContentLength(sha1ContentLength, target, endian); } long offsetToFirstFileInfo = (target.Position - startPosition).Align(1 << smallPackedAlignment); StreamUtils.WriteZeros(target, offsetToFirstFileInfo - (target.Position - startPosition)); target.Position = startPosition; WriteContentLength((uint)offsetToFirstFileInfo, target, endian); target.Position = startPosition + offsetToFirstFileInfo; long singleFileInfoLength = 16 + dummyContentLength + filenameLength + compressionInfoLength + bpsPatchInfoLength + crc32ContentLength + md5ContentLength + sha1ContentLength; long totalFileInfoLength = singleFileInfoLength * files.Count; long offsetToEndOfFileInfo = (offsetToFirstFileInfo + totalFileInfoLength).Align(1 << smallPackedAlignment); StreamUtils.WriteZeros(target, offsetToEndOfFileInfo - offsetToFirstFileInfo); var filedata = new List <(long position, DuplicatableStream data)>(files.Count); long positionOfFreeSpace = offsetToEndOfFileInfo; for (int i = 0; i < files.Count; ++i) { HyoutaArchiveFileInfo fi = files[i]; var fiData = fi.Data; if (fiData == null) { throw new Exception("Data of file " + i + " is null."); } using (DuplicatableStream fs = fiData.Duplicate()) { DuplicatableStream streamToWrite = fs; bool streamIsInternallyCompressed = fi.StreamIsCompressed; if (fi.BpsPatchInfo != null && fi.CompressionInfo != null && streamIsInternallyCompressed && !fi.StreamIsBpsPatch) { // this is a weird case; the stream wants both bps patch and compression // and is already compressed but not already bps patched, which breaks the defined order // we can handle this by decompressing, creating patch, recompressing streamToWrite = fi.DataStream.Duplicate(); // this decompresses the stream streamIsInternallyCompressed = false; // and fake-set the stream as uncompressed for packing logic } byte[]? bpsPatchInfoBytes = null; byte[]? compressionInfoBytes = null; if (hasBpsPatch) { if (fi.BpsPatchInfo == null) { // chunk has patches but this file is unpatched; we store this by pointing the file to itself bpsPatchInfoBytes = new HyoutaArchiveBpsPatchInfo((ulong)i, (ulong)streamToWrite.Length, null).Serialize(endian); } else if (fi.StreamIsBpsPatch) { bpsPatchInfoBytes = fi.BpsPatchInfo.Serialize(endian); } else { var p = HyoutaArchiveBps.CreatePatch(fi.BpsPatchInfo, streamToWrite, endian); bpsPatchInfoBytes = p.patchInfo; streamToWrite = new DuplicatableByteArrayStream(p.patchData); } } if (hasCompression && fi.CompressionInfo != null) { if (streamIsInternallyCompressed) { compressionInfoBytes = fi.CompressionInfo.Serialize(endian); } else { var p = fi.CompressionInfo.Compress(streamToWrite, endian); compressionInfoBytes = p.compressionInfo; streamToWrite = new DuplicatableByteArrayStream(p.compressedData); } } // write file info target.Position = (singleFileInfoLength * i) + offsetToFirstFileInfo + startPosition; long positionPosition = target.Position; target.WriteUInt64(0); // position of file, will be filled later target.WriteUInt64((ulong)streamToWrite.Length, endian); if (hasDummyContent) { if (fi.DummyContent != null) { target.Write(fi.DummyContent); target.WriteZeros(dummyContentLength - fi.DummyContent.Length); } else { target.WriteZeros(dummyContentLength); } } if (hasFilename) { if (fi.Filename != null) { var efn = encodedFilenames ![i];
public static List <MemChunk> FindFreeMemoryInFontTexture(MemoryStream fontStream) { DuplicatableStream textureWiiStream = new DuplicatableByteArrayStream(fontStream.CopyToByteArray()); HyoutaTools.Tales.Vesperia.FPS4.FPS4 textureWiiFps4 = new HyoutaTools.Tales.Vesperia.FPS4.FPS4(textureWiiStream); HyoutaTools.Tales.Vesperia.Texture.TXM textureWiiTxm = new HyoutaTools.Tales.Vesperia.Texture.TXM(textureWiiFps4.GetChildByIndex(0).AsFile.DataStream); HyoutaTools.Tales.Vesperia.Texture.TXV textureWiiTxv = new HyoutaTools.Tales.Vesperia.Texture.TXV(textureWiiTxm, textureWiiFps4.GetChildByIndex(1).AsFile.DataStream, false); Bitmap bitmapWii = textureWiiTxv.textures[2].GetBitmaps()[0]; { for (int y = 0; y < bitmapWii.Height; ++y) { for (int x = 0; x < bitmapWii.Width; ++x) { Color color; switch (IdentifyPixel(x, y)) { case TileIdentification.UsedTile: color = Color.FromArgb(0, 255, 0); break; case TileIdentification.UnusedTile: color = Color.FromArgb(0, 0, 255); break; default: throw new Exception("???"); } bitmapWii.SetPixel(x, y, color); } } } var pxit = new HyoutaTools.Textures.PixelOrderIterators.TiledPixelOrderIterator(bitmapWii.Width, bitmapWii.Height, 8, 8); MemoryStream stream = new MemoryStream(); byte storage = 0; bool even = false; foreach (var px in pxit) { if (px.X < bitmapWii.Width && px.Y < bitmapWii.Height) { Color col = bitmapWii.GetPixel(px.X, px.Y); bool pixelUnused = col.B > 0; var colidx = pixelUnused ? 0xF : 0x0; if (!even) { storage = (byte)colidx; } else { storage = (byte)(storage << 4 | (byte)colidx); stream.WriteByte(storage == 0xFF ? (byte)1 : (byte)0); } even = !even; } } List <MemChunk> chunks = new List <MemChunk>(); uint offset = textureWiiFps4.Files[1].Location.Value + textureWiiTxv.textures[2].TXM.TxvLocation; long len = stream.Length; long startOfLastSafeBlock = -1; var fontMapper = new FontMapper(0xE0000000 - textureWiiFps4.Files[1].Location.Value); stream.Position = 0; for (long i = 0; i <= len; ++i) { bool safeToWriteTo = i == len ? false : stream.ReadUInt8() == 1; if (safeToWriteTo && startOfLastSafeBlock == -1) { // start of block startOfLastSafeBlock = i; } else if (!safeToWriteTo && startOfLastSafeBlock != -1) { // end of block long start = startOfLastSafeBlock; long length = i - startOfLastSafeBlock; MemChunk mc = new MemChunk(); mc.Address = (uint)(start + offset); mc.FreeBytes = (uint)length; mc.File = fontStream; mc.Mapper = fontMapper; mc.IsInternal = false; chunks.Add(mc); startOfLastSafeBlock = -1; } } return(chunks); }
internal static Stream InjectEnglishContainedVoice(Config config, FileFetcher _fc, string name, DuplicatableStream wstream, DuplicatableStream jstream, DuplicatableStream ustream, ContainedVoiceInfo cvi, SkitTexCache skitTexCache) { var fps4 = new HyoutaTools.Tales.Vesperia.FPS4.FPS4(wstream.Duplicate()); var se3stream = fps4.GetChildByIndex(cvi.SE3Index).AsFile.DataStream; var se3 = new HyoutaTools.Tales.Vesperia.SE3.SE3(se3stream.Duplicate(), EndianUtils.Endianness.BigEndian, TextUtils.GameTextEncoding.ASCII); var se3ms = se3.ExtractSe3HeaderStream(); var nubms = se3.ExtractNubStream(); var nubstream = new DuplicatableByteArrayStream(nubms.CopyToByteArrayAndDispose()); var newnubstream = RebuildNubStream(nubstream, Path.Combine(config.EnglishVoiceProcessingDir, "other"), cvi.WiiType, x => Path.GetFileNameWithoutExtension(name)); var newse3stream = new MemoryStream(); se3ms.Position = 0; newnubstream.Position = 0; StreamUtils.CopyStream(se3ms, newse3stream); StreamUtils.CopyStream(newnubstream, newse3stream); if (cvi.IsSkit) { using (var texIdStream = fps4.GetChildByIndex(3).AsFile.DataStream.Duplicate().CopyToByteArrayStreamAndDispose()) { int idx = 4; uint[] texIds = texIdStream.ReadUInt32Array(texIdStream.Length / 4, EndianUtils.Endianness.BigEndian); foreach (uint texId in texIds) { skitTexCache.AddTextureIfNotExists(texId, fps4.GetChildByIndex(idx).AsFile.DataStream.Duplicate().CopyToByteArrayStreamAndDispose()); ++idx; } } } newse3stream.Position = 0; MemoryStream newfps4stream = new MemoryStream(); using (var ufps4 = new HyoutaTools.Tales.Vesperia.FPS4.FPS4(ustream.Duplicate())) { uint[] utexIds = null; if (cvi.IsSkit) { using (var texIdStream = ufps4.GetChildByIndex(3).AsFile.DataStream.Duplicate().CopyToByteArrayStreamAndDispose()) { utexIds = texIdStream.ReadUInt32Array(texIdStream.Length / 4, EndianUtils.Endianness.BigEndian); } } List <HyoutaTools.Tales.Vesperia.FPS4.PackFileInfo> packFileInfos = new List <HyoutaTools.Tales.Vesperia.FPS4.PackFileInfo>(fps4.Files.Count); for (int i = 0; i < (cvi.IsSkit ? ufps4 : fps4).Files.Count - 1; ++i) { var pf = new HyoutaTools.Tales.Vesperia.FPS4.PackFileInfo(); pf.Name = (cvi.IsSkit ? ufps4 : fps4).Files[i].FileName; if (i == cvi.SE3Index) { pf.DataStream = new DuplicatableByteArrayStream(newse3stream.CopyToByteArrayAndDispose()); } else if (cvi.IsSkit && (i == 0 || i == 2 || i == 3)) { // copy over the actual skit script/timing from the EN version so the voice timing and lipsync matches with the skit pf.DataStream = ufps4.GetChildByIndex(i).AsFile.DataStream.Duplicate(); } else if (cvi.IsSkit && i >= 4) { uint texId = NormalizePs3SkitTextureIdForWii(utexIds[i - 4]); try { pf.DataStream = skitTexCache.GetTextureStream(texId); } catch (Exception ex) { Console.WriteLine("ERROR: Failed to get skit texture with ID 0x" + texId.ToString("x4")); Console.WriteLine(" tex name: " + new SkitTexCache.SkitTex() { Stream = ufps4.GetChildByIndex(i).AsFile.DataStream.Duplicate() }.ToString()); throw ex; } } else { pf.DataStream = fps4.GetChildByIndex(i).AsFile.DataStream.Duplicate(); } pf.Length = pf.DataStream.Length; packFileInfos.Add(pf); } packFileInfos = HyoutaTools.Tales.Vesperia.FPS4.FPS4.DetectDuplicates(packFileInfos); HyoutaTools.Tales.Vesperia.FPS4.FPS4.Pack(packFileInfos, newfps4stream, fps4.ContentBitmask, EndianUtils.Endianness.BigEndian, fps4.Unknown2, cvi.IsSkit ? null : wstream.Duplicate(), fps4.ArchiveName, fps4.FirstFileStart, 0x20); } //using (var fs = new FileStream(Path.Combine(@"c:\__graces\______fps4repacktest\", name.Replace("/", "_") + "_old.fps4"), FileMode.Create)) { // using (var wcpy = wstream.Duplicate()) { // wcpy.Position = 0; // StreamUtils.CopyStream(wcpy, fs); // } //} //using (var fs = new FileStream(Path.Combine(@"c:\__graces\______fps4repacktest\", name.Replace("/", "_") + "_new.fps4"), FileMode.Create)) { // newfps4stream.Position = 0; // StreamUtils.CopyStream(newfps4stream, fs); //} newfps4stream.Position = 0; return(newfps4stream); }