public TextEntry(int offset, byte[] data) { Offset = offset; Text = TextUtility.Decode(data); Length = data.Length; Data = data; }
public static async Task <BMG> Decode(string path, List <System.Drawing.Color> colors, Delegate reportProgressFunc = null) { using (var fileStream = new FileStream(path, FileMode.Open)) { Stream stream = fileStream; // TODO: Remove the MainWindow static variable reference. if (MainWindow.SelectedCharacterSet == CharacterSet.WildWorld && LZ77.IsLz77Compressed(fileStream)) { Debug.WriteLine($"In file size: {fileStream.Length:X}"); stream = new LZ77().Decompress(fileStream); stream.Seek(0, SeekOrigin.Begin); // TEST var recompress = new LZ77().Compress(stream); Debug.WriteLine($"Recompressed file size: {recompress.Length:X}"); stream.Seek(0, SeekOrigin.Begin); // Test write /*using (var fStream = new FileStream(Path.Combine(Path.GetDirectoryName(path), "decompressTest.bmg"), * FileMode.Create)) * { * var data = ((MemoryStream)recompress).ToArray(); * fStream.Write(data, 0, data.Length); * recompress.Close(); * }*/ } using (var reader = new BinaryReader(stream)) { #if DEBUG var watch = Stopwatch.StartNew(); watch.Start(); #endif var bmg = new BMG { FileType = new string(reader.ReadChars(8)), Size = reader.ReadUInt32().Reverse() }; // Use size to determine endianness if (bmg.Size != 0 && bmg.Size > reader.BaseStream.Length) { bmg.IsLittleEndian = true; } // Confirm Size isn't 0 if (bmg.Size == 0) { //Console.WriteLine("BMG Size was zero. Setting it to the filesize."); //bmg.Size = (ulong)Reader.BaseStream.Length; } bmg.SectionCount = reader.ReadUInt32(); bmg.Encoding = reader.ReadUInt32(); if (!bmg.IsLittleEndian) { bmg.SectionCount = bmg.SectionCount.Reverse(); bmg.Encoding = bmg.Encoding.Reverse(); } // Debug Lines Console.WriteLine("BMG Section Count: " + bmg.SectionCount); Console.WriteLine("UTF16: " + (bmg.Encoding == 0x02000000)); // Create our Text Info Section (INF) reader.BaseStream.Position = 0x20; bmg.INF_Section.SectionType = new string(reader.ReadChars(4)); bmg.INF_Section.Size = reader.ReadUInt32(); bmg.INF_Section.MessageCount = reader.ReadUInt16(); bmg.INF_Section.INF_Size = reader.ReadUInt16(); bmg.INF_Section.Unknown = reader.ReadUInt32(); if (!bmg.IsLittleEndian) { bmg.INF_Section.Size = bmg.INF_Section.Size.Reverse(); bmg.INF_Section.MessageCount = bmg.INF_Section.MessageCount.Reverse(); bmg.INF_Section.INF_Size = bmg.INF_Section.INF_Size.Reverse(); bmg.INF_Section.Unknown = bmg.INF_Section.Unknown.Reverse(); } bmg.INF_Section.Items = new BMG_INF_Item[bmg.INF_Section.MessageCount]; //Debug Lines Console.WriteLine("INF Size: 0x" + bmg.INF_Section.Size.ToString("X")); Console.WriteLine("INF Message Count: " + bmg.INF_Section.MessageCount); Console.WriteLine("INF CharSize: 0x" + bmg.INF_Section.INF_Size.ToString("X")); // Load our Text Info Items reader.BaseStream.Position = 0x30; for (var i = 0; i < bmg.INF_Section.MessageCount; i++) { var item = new BMG_INF_Item { Text_Offset = bmg.IsLittleEndian ? reader.ReadUInt32() : reader.ReadUInt32().Reverse() }; bmg.INF_Section.Items[i] = item; if (bmg.INF_Section.INF_Size > 4) { // TODO: This is a hack. We should figure out what the additional size means reader.BaseStream.Seek(bmg.INF_Section.INF_Size - 4, SeekOrigin.Current); } } // Create our Text Data Section (DAT) reader.BaseStream.Position = bmg.Size == 0 ? bmg.INF_Section.Size : bmg.INF_Section.Size + 0x20; // + 0x20 for the bgm header if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "DAT1") { var dat1Found = false; while (!dat1Found) { if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "DAT1") { continue; } reader.BaseStream.Position -= 4; Debug.WriteLine("Found DAT1: 0x" + reader.BaseStream.Position.ToString("X")); dat1Found = true; } } else { reader.BaseStream.Position -= 4; } bmg.DAT_Section.Offset = (int)reader.BaseStream.Position; bmg.DAT_Section.SectionType = Encoding.ASCII.GetString(reader.ReadBytes(4)); bmg.DAT_Section.Size = reader.ReadUInt32(); if (!bmg.IsLittleEndian) { bmg.DAT_Section.Size = bmg.DAT_Section.Size.Reverse(); } bmg.DAT_Section.Strings = new string[bmg.INF_Section.MessageCount]; long stringStartOffset = bmg.DAT_Section.Offset + 0x8; await Task.Run(() => { // TODO: Move this static reference out of here. var parser = Parser.GetParser(MainWindow.SelectedCharacterSet); for (var i = 0; i < bmg.INF_Section.MessageCount; i++) { reader.BaseStream.Position = stringStartOffset + bmg.INF_Section.Items[i].Text_Offset; long endingOffset; if (i == bmg.INF_Section.MessageCount - 1) { endingOffset = bmg.DAT_Section.Size > 0 ? bmg.DAT_Section.Offset + bmg.DAT_Section.Size : reader.BaseStream.Length; } else { endingOffset = stringStartOffset + bmg.INF_Section.Items[i + 1].Text_Offset; } var startingOffset = reader.BaseStream.Position; // TODO: Wild World has a case where if the next INF entry is 0, the message id? or something is set to the next value after that // This means that each entry is 0xC in size max. var readSize = (int)(endingOffset - startingOffset); if (readSize < 0) { Console.WriteLine($"Read size is less than 0 for entry {i:X4}"); } bmg.INF_Section.Items[i].Data = reader.ReadBytes(readSize); bmg.INF_Section.Items[i].Text = MainWindow.SelectedCharacterSet == CharacterSet.DoubutsuNoMoriPlus ? TextUtility.Decode(bmg.INF_Section.Items[i].Data, colors) : parser.Decode(bmg.INF_Section.Items[i].Data); bmg.INF_Section.Items[i].Length = (uint)(endingOffset - startingOffset); if (reportProgressFunc != null && i % 50 == 0) { Application.Current.Dispatcher.Invoke(new Action(() => reportProgressFunc.DynamicInvoke(i, bmg.INF_Section.MessageCount))); } } return(bmg); }); if (reportProgressFunc != null) { Application.Current.Dispatcher.Invoke(new Action(() => reportProgressFunc.DynamicInvoke(bmg.INF_Section.MessageCount, bmg.INF_Section.MessageCount))); } #if DEBUG watch.Stop(); Debug.WriteLine($"Decode time elapsed: {watch.ElapsedMilliseconds} ms"); #endif return(bmg); } } }