diff --git a/LibForge/ForgeTool/Program.cs b/LibForge/ForgeTool/Program.cs index 17d6f21..c739b33 100644 --- a/LibForge/ForgeTool/Program.cs +++ b/LibForge/ForgeTool/Program.cs @@ -68,8 +68,13 @@ void WithIO(Action action) case "tex2png": WithIO((fi, fo) => { + var compressionType = "default"; + if(args.Length > 3) + { + compressionType = args[3]; + } var tex = TextureReader.ReadStream(fi); - var bitmap = TextureConverter.ToBitmap(tex, 0); + var bitmap = TextureConverter.ToBitmap(tex, 0, compressionType); bitmap.Save(fo, System.Drawing.Imaging.ImageFormat.Png); }); break; diff --git a/LibForge/ForgeToolGUI/ForgeToolGUI.csproj b/LibForge/ForgeToolGUI/ForgeToolGUI.csproj index 8f17047..ffad84d 100644 --- a/LibForge/ForgeToolGUI/ForgeToolGUI.csproj +++ b/LibForge/ForgeToolGUI/ForgeToolGUI.csproj @@ -5,7 +5,7 @@ Debug AnyCPU {233099F7-0471-4064-B000-F6DDE21E64E6} - WinExe + Exe ForgeToolGUI ForgeToolGUI v4.7.1 @@ -36,6 +36,9 @@ anvil.ico + + + ..\..\Dependencies\MidiCS.dll diff --git a/LibForge/ForgeToolGUI/Inspectors/ConversionInspector.cs b/LibForge/ForgeToolGUI/Inspectors/ConversionInspector.cs index 692eb2a..1d80538 100644 --- a/LibForge/ForgeToolGUI/Inspectors/ConversionInspector.cs +++ b/LibForge/ForgeToolGUI/Inspectors/ConversionInspector.cs @@ -81,7 +81,7 @@ private void LoadCons(string[] filenames) } catch (Exception e) { - logBox.AppendText($"Error loading {filename}: {e.Message}" + Environment.NewLine); + logBox.AppendText($"Error loading {filename}: {e.Message}" + Environment.NewLine + "Error Stack:" + Environment.NewLine + e.StackTrace); } } UpdateState(); diff --git a/LibForge/ForgeToolGUI/Inspectors/Inspector.cs b/LibForge/ForgeToolGUI/Inspectors/Inspector.cs index dde2371..e0d3c72 100644 --- a/LibForge/ForgeToolGUI/Inspectors/Inspector.cs +++ b/LibForge/ForgeToolGUI/Inspectors/Inspector.cs @@ -26,7 +26,7 @@ public static Inspector GetInspector(object obj) switch (obj) { case Texture i: - return new ImageInspector(TextureConverter.ToBitmap(i, 0)); + return new ImageInspector(i); case string s: return new StringInspector(s); case SongData d: @@ -54,7 +54,7 @@ public static object LoadObject(GameArchives.IFile i) { try { - return TextureReader.ReadStream(s); + return TextureReader.ReadStream(s, i.Name); } catch (Exception ex) { diff --git a/LibForge/ForgeToolGUI/Inspectors/TextureInspector.Designer.cs b/LibForge/ForgeToolGUI/Inspectors/TextureInspector.Designer.cs index efec771..311c649 100644 --- a/LibForge/ForgeToolGUI/Inspectors/TextureInspector.Designer.cs +++ b/LibForge/ForgeToolGUI/Inspectors/TextureInspector.Designer.cs @@ -29,31 +29,77 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.DecompressList = new System.Windows.Forms.ComboBox(); + this.Label = new System.Windows.Forms.Label(); + this.Save = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); this.SuspendLayout(); // // pictureBox1 // - this.pictureBox1.Location = new System.Drawing.Point(0, 0); + this.pictureBox1.Location = new System.Drawing.Point(0, 26); this.pictureBox1.Name = "pictureBox1"; this.pictureBox1.Size = new System.Drawing.Size(91, 70); this.pictureBox1.TabIndex = 3; this.pictureBox1.TabStop = false; // - // TextureInspector + // DecompressList + // + this.DecompressList.FormattingEnabled = true; + this.DecompressList.Items.AddRange(new object[] { + "default", + "BC3", + "BC4", + "BC5", + "R8G8"}); + this.DecompressList.Location = new System.Drawing.Point(97, 3); + this.DecompressList.Name = "DecompressList"; + this.DecompressList.Size = new System.Drawing.Size(121, 21); + this.DecompressList.TabIndex = 5; + this.DecompressList.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); + // + // Label + // + this.Label.AutoSize = true; + this.Label.Location = new System.Drawing.Point(3, 6); + this.Label.Name = "Label"; + this.Label.Size = new System.Drawing.Size(88, 13); + this.Label.TabIndex = 6; + this.Label.Text = "Decompress with"; + this.Label.Click += new System.EventHandler(this.label1_Click); + // + // Save + // + this.Save.Location = new System.Drawing.Point(237, 3); + this.Save.Name = "Save"; + this.Save.Size = new System.Drawing.Size(75, 23); + this.Save.TabIndex = 7; + this.Save.Text = "Save"; + this.Save.UseVisualStyleBackColor = true; + this.Save.Click += new System.EventHandler(this.button1_Click); + // + // ImageInspector // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScroll = true; + this.Controls.Add(this.Save); + this.Controls.Add(this.Label); + this.Controls.Add(this.DecompressList); this.Controls.Add(this.pictureBox1); - this.Name = "TextureInspector"; + this.Name = "ImageInspector"; + this.Size = new System.Drawing.Size(409, 288); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); this.ResumeLayout(false); + this.PerformLayout(); } #endregion private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.ComboBox DecompressList; + private System.Windows.Forms.Label Label; + private System.Windows.Forms.Button Save; } } diff --git a/LibForge/ForgeToolGUI/Inspectors/TextureInspector.cs b/LibForge/ForgeToolGUI/Inspectors/TextureInspector.cs index 364f312..99add4a 100644 --- a/LibForge/ForgeToolGUI/Inspectors/TextureInspector.cs +++ b/LibForge/ForgeToolGUI/Inspectors/TextureInspector.cs @@ -6,17 +6,54 @@ using System.Linq; using System.Text; using System.Windows.Forms; +using LibForge; +using System.IO; namespace ForgeToolGUI { public partial class ImageInspector : Inspector { - public ImageInspector(Image i) + LibForge.Texture.Texture uncompressedTexture; + public ImageInspector(LibForge.Texture.Texture i) { InitializeComponent(); - pictureBox1.Width = i.Width; - pictureBox1.Height = i.Height; - pictureBox1.Image = i; + uncompressedTexture = i; + Image decompressedImage = LibForge.Texture.TextureConverter.ToBitmap(i, 0, "default"); + pictureBox1.Width = decompressedImage.Width; + pictureBox1.Height = decompressedImage.Height; + pictureBox1.Image = decompressedImage; + } + + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + changeImageDecompression(DecompressList.SelectedItem.ToString()); + } + + private void changeImageDecompression(String decompression) + { + Image newDecompressedImage = LibForge.Texture.TextureConverter.ToBitmap(uncompressedTexture, 0, decompression); + pictureBox1.Width = newDecompressedImage.Width; + pictureBox1.Height = newDecompressedImage.Height; + pictureBox1.Image = newDecompressedImage; + } + + private void label1_Click(object sender, EventArgs e) + { + + } + + private void button1_Click(object sender, EventArgs e) + { + SaveFileDialog dialog = new SaveFileDialog(); + dialog.InitialDirectory = @"./"; + dialog.RestoreDirectory = true; + dialog.FileName = uncompressedTexture.FileName.Replace("bmp_ps4", "").Replace("png_ps4", "") + ".png"; + dialog.DefaultExt = "png"; + + if(dialog.ShowDialog() == DialogResult.OK) + { + pictureBox1.Image.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png); + } } } } diff --git a/LibForge/LibForge.sln b/LibForge/LibForge.sln index 9f1810b..29b5a1d 100644 --- a/LibForge/LibForge.sln +++ b/LibForge/LibForge.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32014.148 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32602.291 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibForge", "LibForge\LibForge.csproj", "{3684B7E6-0978-487A-895C-D0ED8F6B7B9A}" EndProject diff --git a/LibForge/LibForge/LibForge.csproj b/LibForge/LibForge/LibForge.csproj index 3281484..656526d 100644 --- a/LibForge/LibForge/LibForge.csproj +++ b/LibForge/LibForge/LibForge.csproj @@ -92,6 +92,7 @@ + @@ -133,5 +134,8 @@ LibOrbisPkg + + + \ No newline at end of file diff --git a/LibForge/LibForge/Midi/RBMidConverter.cs b/LibForge/LibForge/Midi/RBMidConverter.cs index c9d97d5..ff02083 100644 --- a/LibForge/LibForge/Midi/RBMidConverter.cs +++ b/LibForge/LibForge/Midi/RBMidConverter.cs @@ -182,7 +182,7 @@ public MidiConverter(MidiFile mf, int hopoThreshold = 170, Action warnAc public RBMid ToRBMid() { rb = new RBMid(); - var processedMidiTracks = ConvertVenueTrack(mf.Tracks); + var processedMidiTracks = ConvertVenueTrack(mf.Tracks, warnAction); ReadMidiFileResourceFromMidiFile(rb, mf, processedMidiTracks, MeasureTicks); rb.Format = 0x10; @@ -1150,16 +1150,22 @@ private void HandleVocalsTrk(MidiTrackProcessed track) // Event handlers bool AddLyric(MidiText e) { + // TODO: investigate further what is read from midi + try { if (e.Text[0] != '[') { - lyrics.Add(new RBMid.TICKTEXT - { - Text = e.Text.Trim(' '), - Tick = e.StartTicks, - }); + lyrics.Add(new RBMid.TICKTEXT + { + Text = e.Text.Trim(' '), + Tick = e.StartTicks, + }); return true; } return false; + } catch + { + return false; + } } bool AddVocalNote(MidiNote e) { @@ -1608,7 +1614,7 @@ private void HandleVenueTrk(MidiTrackProcessed track) } // Converts the venue track from RBN2 to RBN1 so that RB4 can autogen animations - private static List ConvertVenueTrack(List tracks) + private static List ConvertVenueTrack(List tracks, Action warnAction) { const int tpqn = 480; const int note_length = tpqn / 4; //16th note @@ -1620,12 +1626,19 @@ private static List ConvertVenueTrack(List tracks) var venueTrack = tracks.Where(x => x.Name == "VENUE").FirstOrDefault(); if (venueTrack == null) { + MidiTrack eventTrack = tracks.Where(x => x.Name == "EVENTS").FirstOrDefault(); + foreach(var message in eventTrack.Messages) + { + warnAction(message.ToString()); + } + warnAction?.Invoke("no venueTrack"); return tracks; } if(!venueTrack.Messages.Any(m => m is TextEvent t && (t.Text.Contains(".pp]") || t.Text.Contains("[coop") || t.Text.Contains("[directed")))) { // If this is already a RBN1 VENUE, skip it. + warnAction("Skipping, already a RBN1 Venue"); return tracks; } long last_first = 0; @@ -1642,6 +1655,7 @@ private static List ConvertVenueTrack(List tracks) { var index = mt.Text.IndexOf("[", StringComparison.Ordinal); var new_event = mt.Text.Substring(index, mt.Text.Length - index).Trim(); + warnAction(new_event); if (new_event.Contains("[directed")) { diff --git a/LibForge/LibForge/Milo/BlockStructure.cs b/LibForge/LibForge/Milo/BlockStructure.cs index 4281618..098bc73 100644 --- a/LibForge/LibForge/Milo/BlockStructure.cs +++ b/LibForge/LibForge/Milo/BlockStructure.cs @@ -2,26 +2,61 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; namespace LibForge.Milo { public enum BlockStructure : uint { /// - /// Structured as milo (No compression) + /// Raw data. Uncompressed. + /// + NONE = 0, + /// + /// Raw data. Compressed with GZip. + /// *FreQ + /// + GZIP = 1, + /// + /// Structured as milo. No compression. + /// *RBN /// MILO_A = 0xCABEDEAF, /// - /// Structured as milo (Compressed with ZLib) + /// Structured as milo. Compressed with ZLib. + /// *GH1 + /// *GH2 + /// *GH80's + /// *RB1 + /// *RBTP Vol. 1 + /// *RB2 + /// *ACDC RB + /// *RBTB Vol. 2 + /// *RBTP Classic Rock + /// *RBTP Country + /// *TBRB + /// *RBTP Metal + /// *LRB + /// *GDRB + /// *RBTP Country 2 /// MILO_B = 0xCBBEDEAF, /// - /// Structured as milo (Compressed with GZip) + /// Structured as milo. Compressed with GZip. + /// *Amp + /// *KR1 + /// *KR2 + /// *KR3 /// MILO_C = 0xCCBEDEAF, /// - /// Structured as milo (Compressed with ZLib) + /// Structured as milo. Compressed with ZLib. + /// *RB3 + /// *DC1 + /// *DC2 + /// *RBB + /// *DC3 /// MILO_D = 0xCDBEDEAF } -} +} \ No newline at end of file diff --git a/LibForge/LibForge/Milo/Compression.cs b/LibForge/LibForge/Milo/Compression.cs new file mode 100644 index 0000000..89cedcf --- /dev/null +++ b/LibForge/LibForge/Milo/Compression.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using System.IO.Compression; // For GZip/ZLib compression +namespace LibForge.Milo + + +// Credits: PikminGuts92 +// Code from: https://github.com/PikminGuts92/Mackiloha/blob/master/Src/Core/Mackiloha/Compression.cs +{ + public enum CompressionType + { + ZLIB, + GZIP + } + public static class Compression + { + private static readonly byte[] ZLIB_MAGIC = { 0x78, 0x9C }; // Default compression + + public static byte[] InflateBlock(byte[] inBlock, CompressionType type, int offset = 0) + { + if (offset < 0) offset = 0; + byte[] outBlock; + const int MAX_READ_SIZE = 0x8000; + + switch (type) + { + case CompressionType.GZIP: + using (MemoryStream ms = new MemoryStream()) + { + // Decompresses gzip stream + GZipStream gzip = new GZipStream(new MemoryStream(inBlock.Skip(offset).ToArray()), CompressionMode.Decompress); + + gzip.CopyTo(ms); + outBlock = ms.ToArray(); + gzip.Flush(); + } + break; + case CompressionType.ZLIB: + using (MemoryStream ms = new MemoryStream()) + { + // Decompresses zlib stream + using (var outZStream = new DeflateStream(new MemoryStream(inBlock.Skip(offset).ToArray()), CompressionMode.Decompress, true)) + { + outZStream.CopyTo(ms); + } + + outBlock = ms.ToArray(); + } + break; + default: + outBlock = new byte[inBlock.Length]; + Array.Copy(inBlock, outBlock, inBlock.Length); + break; + } + + return outBlock; + } + + public static byte[] DeflateBlock(byte[] inBlock, CompressionType type, int offset = 0) + { + if (offset < 0) offset = 0; + byte[] outBlock; + + switch (type) + { + case CompressionType.GZIP: + using (MemoryStream ms = new MemoryStream()) + { + // Compresses gzip stream + GZipStream gzip = new GZipStream(new MemoryStream(inBlock.Skip(offset).ToArray()), CompressionMode.Compress); + + gzip.CopyTo(ms); + outBlock = ms.ToArray(); + gzip.Flush(); + } + break; + case CompressionType.ZLIB: + using (MemoryStream ms = new MemoryStream()) + { + // Compresses zlib stream + using (var outZStream = new DeflateStream(ms, CompressionLevel.Optimal, true)) + { + outZStream.Write(inBlock, offset, inBlock.Length - offset); + } + + outBlock = ms.ToArray(); + } + return outBlock; // Returns without magic + default: + outBlock = new byte[inBlock.Length]; + Array.Copy(inBlock, outBlock, inBlock.Length); + break; + } + + return outBlock; + } + } +} diff --git a/LibForge/LibForge/Milo/MiloFile.cs b/LibForge/LibForge/Milo/MiloFile.cs index 49db3ad..990a50d 100644 --- a/LibForge/LibForge/Milo/MiloFile.cs +++ b/LibForge/LibForge/Milo/MiloFile.cs @@ -19,50 +19,64 @@ private MiloFile(string name, string type) Entries = new List(); } + public static int ReadInt32(Stream stream, Boolean big) + { + byte[] data = stream.ReadBytes(4); + if (big) Array.Reverse(data); + return BitConverter.ToInt32(data, 0); + } + + public static uint ReadUInt32(Stream stream, Boolean big) + { + byte[] data = stream.ReadBytes(4); + if (big) Array.Reverse(data); + return BitConverter.ToUInt32(data, 0); + } + public static MiloFile ReadFromStream(Stream stream) { long startingOffset = stream.Position; // You might not be starting at 0x0 var structureType = (BlockStructure)stream.ReadUInt32LE(); - if (!(structureType == BlockStructure.MILO_A || structureType == BlockStructure.MILO_D)) - throw new Exception("Unsupported milo compression"); - - int offset = stream.ReadInt32LE(); // Start of blocks + uint offset = stream.ReadUInt32LE(); int blockCount = stream.ReadInt32LE(); - stream.Seek(4, SeekOrigin.Current); // Skips max uncompressed size + int maxBlockSize = stream.ReadInt32LE(); // Skips max uncompressed size // Reads block sizes - int totalSize; - if (structureType == BlockStructure.MILO_A) - { - totalSize = Enumerable.Range(0, blockCount) - .Select(x => stream.ReadInt32LE()) - .Sum(); - } - else // Milo_D - { - // TODO: Implement zlib block inflation - totalSize = Enumerable.Range(0, blockCount) - .Select(x => - { - var blockSize = stream.ReadInt32LE(); - var compressed = (blockSize & 0x01_00_00_00) == 0; - - if (compressed) - throw new NotImplementedException("Zlib block inflation not implemented yet"); - - return blockSize & 0xFF_FF_FF; - }) - .Sum(); - } + var sizes = Enumerable.Range(0, blockCount).Select(x => stream.ReadUInt32LE()).ToArray(); // Jumps to first block offset stream.Position = startingOffset + offset; - using (var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { - // Copies raw milo data - stream.CopyTo(ms, totalSize); + foreach(var size in sizes) + { + bool compressed = (structureType == BlockStructure.MILO_D && (size & 0xFF000000) == 0) + || structureType == BlockStructure.MILO_B + || structureType == BlockStructure.MILO_C; + + uint blockSize = size & 0x00FFFFFF; + byte[] block = stream.ReadBytes((int)blockSize); + + if (block.Length > 0 && compressed) + { + switch (structureType) + { + case BlockStructure.MILO_B: + block = Compression.InflateBlock(block, CompressionType.ZLIB); + break; + case BlockStructure.MILO_C: + block = Compression.InflateBlock(block, CompressionType.GZIP); + break; + default: // MILO_D + block = Compression.InflateBlock(block, CompressionType.ZLIB, 4); + break; + } + } + ms.Write(block, 0, block.Length); + } + ms.Seek(0, SeekOrigin.Begin); return ParseDirectory(ms); } diff --git a/LibForge/LibForge/SongData/SongDataConverter.cs b/LibForge/LibForge/SongData/SongDataConverter.cs index 565a2ea..a68f230 100644 --- a/LibForge/LibForge/SongData/SongDataConverter.cs +++ b/LibForge/LibForge/SongData/SongDataConverter.cs @@ -6,47 +6,102 @@ namespace LibForge.SongData { + public enum Decade + { + the30s = 1930, + the40s = 1940, + the50s = 1950, + the60s = 1960, + the70s = 1970, + the80s = 1980, + the90s = 1990, + the00s = 2000, + the10s = 2010, + the20s = 2020 + } public class SongDataConverter { - public static SongData ToSongData(DataArray songDta) + + public static SongData ToSongData(DataArray songDta, GameArchives.IDirectory dlcRoot, Boolean isSongRoot = false) { var songId = songDta.Array("song_id"); - var art = songDta.Array("album_art").Any(1); - var shortName = songDta.Array("song").Array("name").String(1).Split('/').Last(); + var art = songDta.Array("album_art")?.Any(1) ?? "FALSE"; + var songName = songDta.Array("song").Array("name")?.String(1) ?? songDta.Array("song").Array("name")?.Symbol(1).ToString(); + var shortName = songName.Split('/').Last(); var songIdNum = (shortName.GetHashCode() & 0xFFFFFF) + 90000000; + // Get MIDI duration dta song_length is not defined, round just to make sure... + string subPath = ""; + if(!isSongRoot) + { + subPath = shortName + "/"; + } + var midi = MidiCS.MidiFileReader.FromBytes(dlcRoot.GetFileAtPath(subPath + shortName + ".mid").GetBytes()); + var midiDuration = (int)(Math.Ceiling(midi.Duration)* 1000); + + + var songLength = songDta.Array("song_length")?.Int(1) ?? midiDuration; + + + string decade = songDta.Array("decade")?.Any(1).ToString() ?? "the00s"; + var decadeInt = (int)Enum.Parse(typeof(Decade), decade); + + + var albumArt = art == "1" || art == "TRUE"; + var albumName = songDta.Array("album_name")?.String(1) ?? ""; + var albumTrackNumber = (short)(songDta.Array("album_track_number")?.Int(1) ?? 0); + var albumYear = songDta.Array("year_released")?.Int(1) ?? 0; + var artist = songDta.Array("artist").String(1); + var bandRank = songDta.Array("rank").Array("band").Int(1); + var bassRank = songDta.Array("rank").Array("bass").Int(1); + var drumRank = songDta.Array("rank").Array("drum").Int(1); + var guitarRank = songDta.Array("rank").Array("guitar").Int(1); + var keysRank = songDta.Array("rank").Array("keys")?.Int(1) ?? 0; + var realKeysRank = songDta.Array("rank").Array("real_keys")?.Int(1) ?? 0; + var vocalsRank = songDta.Array("rank").Array("vocals").Int(1); + var gameOrigin = songDta.Array("game_origin")?.Any(1) ?? "ugc_plus"; + var genre = songDta.Array("genre").Symbol(1).ToString(); + var name = songDta.Array("name").String(1); + var originalYear = songDta.Array("year_released")?.Int(1) ?? decadeInt; + var vocalGender = (byte)((songDta.Array("vocal_gender")?.Any(1) ?? "male") == "male" ? 1 : 2); + var vocalParts = songDta.Array("song").Array("vocal_parts")?.Int(1) ?? 1; + var shortname = shortName.ToLowerInvariant(); + var previewStart = songDta.Array("preview").Int(1); + var previewEnd = songDta.Array("preview").Int(2); + + return new SongData { - AlbumArt = art == "1" || art == "TRUE", - AlbumName = songDta.Array("album_name")?.String(1) ?? "", - AlbumTrackNumber = (short)(songDta.Array("album_track_number")?.Int(1) ?? 0), - AlbumYear = songDta.Array("year_released")?.Int(1) ?? 0, - Artist = songDta.Array("artist").String(1), - BandRank = songDta.Array("rank").Array("band").Int(1), - BassRank = songDta.Array("rank").Array("bass").Int(1), - DrumRank = songDta.Array("rank").Array("drum").Int(1), - GuitarRank = songDta.Array("rank").Array("guitar").Int(1), - KeysRank = songDta.Array("rank").Array("keys")?.Int(1) ?? 0, - RealKeysRank = songDta.Array("rank").Array("real_keys")?.Int(1) ?? 0, - VocalsRank = songDta.Array("rank").Array("vocals").Int(1), + AlbumArt = albumArt, + AlbumName = albumName, + AlbumTrackNumber = albumTrackNumber, + AlbumYear = albumYear, + Artist = artist, + BandRank = bandRank, + BassRank = bassRank, + DrumRank = drumRank, + GuitarRank = guitarRank, + KeysRank = keysRank, + RealKeysRank = realKeysRank, + VocalsRank = vocalsRank, Cover = false, Fake = false, Flags = 0, - GameOrigin = songDta.Array("game_origin")?.Any(1) ?? "ugc_plus", - Genre = songDta.Array("genre").Symbol(1).ToString(), + GameOrigin = gameOrigin, + Genre = genre, HasFreestyleVocals = false, Medium = "", - Name = songDta.Array("name").String(1), - OriginalYear = songDta.Array("year_released").Int(1), + Name = name, + OriginalYear = originalYear, Tutorial = false, Type = 11, Version = -1, - VocalGender = (byte)((songDta.Array("vocal_gender")?.Any(1) ?? "male") == "male" ? 1 : 2), - VocalParts = songDta.Array("song").Array("vocal_parts")?.Int(1) ?? 1, - Shortname = shortName.ToLowerInvariant(), + VocalGender = vocalGender, + VocalParts = vocalParts, + Shortname = shortname, SongId = (uint)songIdNum, - SongLength = songDta.Array("song_length").Int(1), - PreviewStart = songDta.Array("preview").Int(1), - PreviewEnd = songDta.Array("preview").Int(2), + SongLength = songLength, + PreviewStart = previewStart, + PreviewEnd = previewEnd, }; } } diff --git a/LibForge/LibForge/Texture/Texture.cs b/LibForge/LibForge/Texture/Texture.cs index 3569346..8be8e07 100644 --- a/LibForge/LibForge/Texture/Texture.cs +++ b/LibForge/LibForge/Texture/Texture.cs @@ -18,5 +18,6 @@ public class Mipmap public Mipmap[] Mipmaps; public byte[] HeaderData; public byte[] FooterData; + public String FileName; } } diff --git a/LibForge/LibForge/Texture/TextureConverter.cs b/LibForge/LibForge/Texture/TextureConverter.cs index 85a4cd3..ec957e5 100644 --- a/LibForge/LibForge/Texture/TextureConverter.cs +++ b/LibForge/LibForge/Texture/TextureConverter.cs @@ -1,10 +1,12 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text; +using System.Diagnostics; using LibForge.Extensions; namespace LibForge.Texture @@ -25,27 +27,198 @@ static ushort ARGBToRGB565(Color input) (((input.G * 0x3F / 0xFF) & 0x3F) << 5) | (((input.B * 0x1F / 0xFF) & 0x1F))); } - // TODO: Decode DXT5 alpha channel - public static Bitmap ToBitmap(Texture t, int mipmap) + + // use texconv.exe to convert images + + static byte[] DDSHeader = new byte[] +{ + // 0 1 2 3 4 5 6 7 8 9 0A 0B 0C 0D 0E 0F + 0x44, 0x44, 0x53, 0x20, 0x7C, 0x00, 0x00, 0x00, 0x07, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + + static readonly short heightHeaderOffset = 0xC; + static readonly short widthHeaderOffset = 0x10; + static readonly short pitchHeaderOffset = 0x14; + static readonly short compressionTypeOffset = 0x54; + + public static byte[] getHeaderCompressionType (string compressionType) + { + String translatedCompressionType = ""; + if(compressionType.ToUpper().Contains("BC1")) + { + translatedCompressionType = "DXT1"; + } else if (compressionType.ToUpper().Contains("BC2")) + { + translatedCompressionType = "DXT3"; + } else if (compressionType.ToUpper().Contains("BC3")) + { + translatedCompressionType = "DXT5"; + } else if (compressionType.ToUpper().Contains("BC4")) + { + translatedCompressionType = "ATI1"; + } else if (compressionType.ToUpper().Contains("BC5") && !compressionType.ToUpper().Contains("BC5_SNORM")) + { + translatedCompressionType = "ATI2"; + } else + { + translatedCompressionType = "DX10"; + } + + return new UTF8Encoding().GetBytes(translatedCompressionType); + } + + public static byte[] convertIntToByteArray(int number) + { + byte[] bytes = BitConverter.GetBytes(number); + return bytes; + } + + public static int pitchCalculation(int width, string compressionType = "BC1") + { + var blockSize = 16; + if(compressionType.ToUpper().Contains("DXT1") || compressionType.ToUpper().Contains("BC1") || compressionType.ToUpper().Contains("BC4")) + { + blockSize = 8; + } + + if(compressionType.ToUpper().Contains("BC") ||compressionType.ToUpper().Contains("BC")) + { + return Math.Max(1, ((width + 3) / 4)) * blockSize; + } else if (compressionType.ToUpper().Contains("R8G8_B8G8") || compressionType.ToUpper().Contains("G8R8_G8B8") || compressionType.ToUpper().Contains("UYVY") || compressionType.ToUpper().Contains("YUY2")) + { + return ((width + 1) >> 1) * 4; + } else + { + // dont know about bpp right now + var bitsPerPixel = 8; + return (width * bitsPerPixel + 7) / 8; + } + } +/* + public static Bitmap ToBitmap(Texture t, int mipmap, string compressionType) { + compressionType = compressionType.ToLower() == "default" ? predictCompressionType(t.HeaderData[0x70]) : compressionType; var m = t.Mipmaps[mipmap]; var output = new Bitmap(m.Width, m.Height, PixelFormat.Format32bppArgb); int[] imageData = new int[m.Width * m.Height]; + byte[] ddfHeader = DDSHeader; + byte[] heightInBytes = convertIntToByteArray(m.Height); + byte[] widthInBytes = convertIntToByteArray(m.Width); + byte[] pitchInBytes = convertIntToByteArray(pitchCalculation(m.Width, compressionType)); + byte[] compressionTypeInBytes = getHeaderCompressionType(compressionType); + + Console.WriteLine(t.FileName); + + for (var i = 0; i < 4; i++) + { + ddfHeader[heightHeaderOffset + i] = heightInBytes[i]; + ddfHeader[widthHeaderOffset + i] = widthInBytes[i]; + ddfHeader[pitchHeaderOffset + i] = pitchInBytes[i]; + ddfHeader[compressionTypeOffset + i] = compressionTypeInBytes[i]; + } + + byte[] rawDataWithDDfHeader = new byte[ddfHeader.Length + m.Data.Length]; + Buffer.BlockCopy(ddfHeader, 0, rawDataWithDDfHeader, 0, ddfHeader.Length); + Buffer.BlockCopy(m.Data, 0, rawDataWithDDfHeader, ddfHeader.Length, m.Data.Length); + if (m.Data.Length == (imageData.Length * 4)) { + // non compressed Buffer.BlockCopy(m.Data, 0, imageData, 0, m.Data.Length); } - else if (m.Data.Length == imageData.Length) + else { - DecodeDXT(m, imageData, true); + // get current file path + string currentPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + string tempPath = Path.Combine(currentPath, "temp"); + string tempMipmapFileName = t.FileName.Length > 0 ? t.FileName : "image.raw"; + + bool exists = System.IO.Directory.Exists(tempPath); + if(!exists) + { + System.IO.Directory.CreateDirectory(Path.Combine(tempPath)); + } + + // create a temp file of the biggest mipmap + using (BinaryWriter writer = new BinaryWriter(File.Create(Path.Combine(tempPath, tempMipmapFileName)))) + { + writer.Write(rawDataWithDDfHeader); + } + + // execute texconv.exe with the temp file + Console.WriteLine("-f " + compressionType + " -y -ft png -o temp " + Path.Combine("./temp", tempMipmapFileName)); + Process process = Process.Start(Path.Combine(currentPath,"texconv.exe"), "-f " + compressionType + " -y -ft png -o temp " + Path.Combine("./temp", tempMipmapFileName)); + int id = process.Id; + Process tempProc = Process.GetProcessById(id); + tempProc.WaitForExit(); + + // read converted texture + Bitmap convertedTexture = new Bitmap(Path.Combine(tempPath, tempMipmapFileName.Replace(".bmp_ps4", "").Replace(".png_ps4", "") + ".png")); + + return convertedTexture; } - else if (m.Data.Length == (imageData.Length / 2)) + + return output; + + } + + */ + + // TODO: Decode DXT5 alpha channel + + + public static Bitmap ToBitmap(Texture t, int mipmap, string compressionType) + { + var m = t.Mipmaps[mipmap]; + var output = new Bitmap(m.Width, m.Height, PixelFormat.Format32bppArgb); + int[] imageData = new int[m.Width * m.Height]; + + compressionType = compressionType.ToLower() == "default" ? predictCompressionType(t.HeaderData[0x70]) : compressionType; + if (m.Data.Length == (imageData.Length * 4)) { - DecodeDXT(m, imageData, false); + // non compressed + Buffer.BlockCopy(m.Data, 0, imageData, 0, m.Data.Length); + } else if (compressionType.ToUpper() == "R8G8") + { + DecodeR8G8(m, imageData); + } else if (compressionType.ToUpper() == "BC4") + { + DecodeBC4(m, imageData); + } else if (compressionType.ToUpper() == "BC5") + { + DecodeBC5(m, imageData); + } else if (compressionType.ToUpper() == "BC7") + { + DecodeBC7(m, imageData); + } else if (compressionType.ToUpper() == "DXT" || compressionType.ToUpper() == "BC3") + { + if (m.Data.Length == imageData.Length) + { + DecodeDXT(m, imageData, true); + } + else if (m.Data.Length == (imageData.Length / 2)) + { + DecodeDXT(m, imageData, false); + } else if (m.Data.Length == imageData.Length * 2) + { + DecodeR8G8(m, imageData); + } + else + { + Console.WriteLine($"Don't know what to do with this texture (version={t.Version})... Hint: Try R8G8 compressionType"); + // throw new Exception($"Don't know what to do with this texture (version={t.Version})... Hint: Try R8G8 compressionType"); + } } else { - throw new Exception($"Don't know what to do with this texture (version={t.Version})"); + throw new Exception("Argument compressionType missing!"); } // Copy data to bitmap { @@ -56,53 +229,349 @@ public static Bitmap ToBitmap(Texture t, int mipmap) return output; } - private static void DecodeDXT(Texture.Mipmap m, int[] imageData, bool DXT5) + private static String predictCompressionType(int value) + { + if(value <= 34) + { + return "BC3"; + } else if(value == 35) + { + return "BC4"; + } else if (value == 37) + { + return "BC5"; + } + + return "BC3"; + } + + // (BC4 Block ATI1/3Dc+) + private static void DecodeBC4(Texture.Mipmap m, int[] imageData) { - int[] colors = new int[4]; using (var s = new MemoryStream(m.Data)) { - ushort[] c = new ushort[4]; - byte[] iData = new byte[4]; for (var y = 0; y < m.Height; y += 4) + { for (var x = 0; x < m.Width; x += 4) { - if (DXT5) s.Seek(8, SeekOrigin.Current); - ushort c0 = s.ReadUInt16LE(); - ushort c1 = s.ReadUInt16LE(); - colors[0] = RGB565ToARGB(c0); - colors[1] = RGB565ToARGB(c1); - var color0 = Color.FromArgb(colors[0]); - var color1 = Color.FromArgb(colors[1]); - s.Read(iData, 0, 4); - if (c0 > c1) + float[] red = new float[16]; + + byte[] redColorData = s.ReadBytes(8); + ushort red0 = redColorData[0]; + ushort red1 = redColorData[1]; + red[0] = red0; + red[1] = red1; + ulong redMask = redColorData[2] | ((ulong)redColorData[3] << 8) | ((ulong)redColorData[4] << 16) | ((ulong)redColorData[5] << 24) | ((ulong)redColorData[6] << 32) | ((ulong)redColorData[7] << 40); + + if (red0 > red1) { - colors[2] = Color.FromArgb(0xFF, - (color0.R * 2 + color1.R) / 3, - (color0.G * 2 + color1.G) / 3, - (color0.B * 2 + color1.B) / 3).ToArgb(); - colors[3] = Color.FromArgb(0xFF, - (color0.R + (color1.R * 2)) / 3, - (color0.G + (color1.G * 2)) / 3, - (color0.B + (color1.B * 2)) / 3).ToArgb(); + // 6 interpolated color values + red[2] = (6 * red0 + 1 * red1) / 7.0f; // bit code 010 + red[3] = (5 * red0 + 2 * red1) / 7.0f; // bit code 011 + red[4] = (4 * red0 + 3 * red1) / 7.0f; // bit code 100 + red[5] = (3 * red0 + 4 * red1) / 7.0f; // bit code 101 + red[6] = (2 * red0 + 5 * red1) / 7.0f; // bit code 110 + red[7] = (1 * red0 + 6 * red1) / 7.0f; // bit code 111 } else { - colors[2] = Color.FromArgb(0xFF, - (color0.R + color1.R) / 2, - (color0.G + color1.G) / 2, - (color0.B + color1.B) / 2).ToArgb(); - colors[3] = Color.Black.ToArgb(); + // 4 interpolated color values + red[2] = (4 * red0 + 1 * red1) / 5.0f; // bit code 010 + red[3] = (3 * red0 + 2 * red1) / 5.0f; // bit code 011 + red[4] = (2 * red0 + 3 * red1) / 5.0f; // bit code 100 + red[5] = (1 * red0 + 4 * red1) / 5.0f; // bit code 101 + red[6] = 0.0f; // bit code 110 + red[7] = 1.0f; // bit code 111 } + var offset = y * m.Width + x; + int currentIndex = 0; for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { - var idx = (iData[i] >> (2 * j)) & 0x3; - imageData[offset + i * m.Width + j] = colors[idx]; + int redIndex = (int)((redMask >> (3 * currentIndex)) & 0x7); + var selectedRed = red[redIndex]; + + + var color = Color.FromArgb(0XFF, (int)selectedRed, (int)selectedRed, (int)selectedRed).ToArgb(); + imageData[offset + i * m.Width + j] = color; + currentIndex++; } } } + } + } + } + + // usually used for normal maps (BC5 Block ATI2/3Dc) + private static void DecodeBC5(Texture.Mipmap m, int[] imageData) { + + using (var s = new MemoryStream(m.Data)) + { + for (var y = 0; y < m.Height; y += 4) + { + for (var x = 0; x < m.Width; x += 4) + { + float[] red = new float[16]; + float[] green = new float[16]; + + byte[] redColorData = s.ReadBytes(8); + ushort red0 = redColorData[0]; + ushort red1 = redColorData[1]; + red[0] = red0; + red[1] = red1; + ulong redMask = redColorData[2] | ((ulong)redColorData[3] << 8) | ((ulong)redColorData[4] << 16) | ((ulong)redColorData[5] << 24) | ((ulong)redColorData[6] << 32) | ((ulong)redColorData[7] << 40); + + byte[] greenColorData = s.ReadBytes(8); + ushort green0 = greenColorData[0]; + ushort green1 = greenColorData[1]; + green[0] = green0; + green[1] = green1; + ulong greenMask = greenColorData[2] | ((ulong)greenColorData[3] << 8) | ((ulong)greenColorData[4] << 16) | ((ulong)greenColorData[5] << 24) | ((ulong)greenColorData[6] << 32) | ((ulong)greenColorData[7] << 40); + + if (red0 > red1) + { + // 6 interpolated color values + red[2] = (6 * red0 + 1 * red1) / 7.0f; // bit code 010 + red[3] = (5 * red0 + 2 * red1) / 7.0f; // bit code 011 + red[4] = (4 * red0 + 3 * red1) / 7.0f; // bit code 100 + red[5] = (3 * red0 + 4 * red1) / 7.0f; // bit code 101 + red[6] = (2 * red0 + 5 * red1) / 7.0f; // bit code 110 + red[7] = (1 * red0 + 6 * red1) / 7.0f; // bit code 111 + } + else + { + // 4 interpolated color values + red[2] = (4 * red0 + 1 * red1) / 5.0f; // bit code 010 + red[3] = (3 * red0 + 2 * red1) / 5.0f; // bit code 011 + red[4] = (2 * red0 + 3 * red1) / 5.0f; // bit code 100 + red[5] = (1 * red0 + 4 * red1) / 5.0f; // bit code 101 + red[6] = 0.0f; // bit code 110 + red[7] = 1.0f; // bit code 111 + } + + if (green0 > green1) + { + // 6 interpolated color values + green[2] = (6 * green0 + 1 * green1) / 7.0f; // bit code 010 + green[3] = (5 * green0 + 2 * green1) / 7.0f; // bit code 011 + green[4] = (4 * green0 + 3 * green1) / 7.0f; // bit code 100 + green[5] = (3 * green0 + 4 * green1) / 7.0f; // bit code 101 + green[6] = (2 * green0 + 5 * green1) / 7.0f; // bit code 110 + green[7] = (1 * green0 + 6 * green1) / 7.0f; // bit code 111 + } + else + { + // 4 interpolated color values + green[2] = (4 * green0 + 1 * green1) / 5.0f; // bit code 010 + green[3] = (3 * green0 + 2 * green1) / 5.0f; // bit code 011 + green[4] = (2 * green0 + 3 * green1) / 5.0f; // bit code 100 + green[5] = (1 * green0 + 4 * green1) / 5.0f; // bit code 101 + green[6] = 0.0f; // bit code 110 + green[7] = 1.0f; // bit code 111 + } + + + var offset = y * m.Width + x; + int currentIndex = 0; + for (var i = 0; i < 4; i++) + { + for (var j = 0; j < 4; j++) + { + int redIndex = (int)((redMask >> (3 * currentIndex)) & 0x7); + var selectedRed = red[redIndex]; + int greenIndex = (int)((greenMask >> (3 * currentIndex)) & 0x7); + var selectedGreen = green[greenIndex]; + + // not sure about calculating channel blue + // var computedBlue = Math.Sqrt(1.0 - (Math.Pow(selectedRed / 1000, 2)) - (Math.Pow(selectedGreen / 1000, 2))) * 255; + + var color = Color.FromArgb(0XFF, (int)selectedRed, (int)selectedGreen, 0xFF).ToArgb(); + imageData[offset + i * m.Width + j] = color; + currentIndex++; + } + } + } + } + } + } + + +/* + CMP_BTI bti_cpu[NUM_BLOCK_TYPES] = { + {NO_ALPHA, 4, 0, 0, 0, 12, TWO_PBIT, 3, {3, 0}}, // Format Mode 0 + {NO_ALPHA, 6, 0, 0, 0, 18, ONE_PBIT, 2, {3, 0}}, // Format Mode 1 + {NO_ALPHA, 6, 0, 0, 0, 15, NO_PBIT, 3, {2, 0}}, // Format Mode 2 + {NO_ALPHA, 6, 0, 0, 0, 21, TWO_PBIT, 2, {2, 0}}, // Format Mode 3 + {SEPARATE_ALPHA, 0, 2, 1, 6, 15, NO_PBIT, 1, {2, 3}}, // Format Mode 4 + {SEPARATE_ALPHA, 0, 2, 0, 8, 21, NO_PBIT, 1, {2, 2}}, // Format Mode 5 + {COMBINED_ALPHA, 0, 0, 0, 0, 28, TWO_PBIT, 1, {4, 0}}, // Format Mode 6 + {COMBINED_ALPHA, 6, 0, 0, 0, 20, TWO_PBIT, 2, {2, 0}} // Format Mode 7 +}; +*/ + + private static void DecodeBC7(Texture.Mipmap m, int[] imageData) + { + using (var s = new MemoryStream(m.Data)) + { + for (var y = 0; y < m.Height; y += 4) + { + for (var x = 0; x < m.Width; x += 4) + { + int blockMode = 0; + byte readMode = s.ReadUInt8(); + + while(!IsBitSet(readMode, blockMode) && blockMode < 8) + { + blockMode++; + } + + if(blockMode > 7) + { + throw new Exception("Something bad happened"); + } + + } + } + } + } + + private static bool IsBitSet(byte b, int pos) + { + return ((b >> pos) & 1) != 0; + } + + public static int GetLSB(int intValue) + { + return (intValue & 0x0000FFFF); + } + + // seems like this is used for uncompressed normal maps + // not sure about calculating blue + private static void DecodeR8G8(Texture.Mipmap m, int[] imageData) + { + + using (var s = new MemoryStream(m.Data)) + { + for (var i = 0; i < imageData.Length; i++) + { + ushort red = s.ReadUInt8(); + ushort green = s.ReadUInt8(); + //var computedBlue = Math.Sqrt(1.0 - (Math.Pow(red / 1000, 2)) - (Math.Pow(green / 1000, 2))) * 255; + + var color = Color.FromArgb(0xFF, red, green, 0xFF).ToArgb(); + imageData[i] = color; + } + } + } + + + private static void DecodeDXT(Texture.Mipmap m, int[] imageData, bool DXT5) + { + int[] alpha = new int[16]; + int[] colors = new int[4]; + using (var s = new MemoryStream(m.Data)) + { + byte[] iData = new byte[4]; + + for (var y = 0; y < m.Height; y += 4) + for (var x = 0; x < m.Width; x += 4) + { + byte[] alphaData; + ushort alpha0; + ushort alpha1; + ulong alphaMask = 0; + if (DXT5) + { + alphaData = s.ReadBytes(8); + alpha0 = alphaData[0]; + alpha1 = alphaData[1]; + alphaMask = alphaData[2] | ((ulong)alphaData[3] << 8) | + ((ulong)alphaData[4] << 16) | ((ulong)alphaData[5] << 24) | + ((ulong)alphaData[6] << 32) | ((ulong)alphaData[7] << 40); + + + alpha[0] = alpha0; + alpha[1] = alpha1; + + if (alpha0 > alpha1) + { + alpha[2] = (byte)((6 * alpha0 + alpha1) / 7); + alpha[3] = (byte)((5 * alpha0 + 2 * alpha1) / 7); + alpha[4] = (byte)((4 * alpha0 + 3 * alpha1) / 7); + alpha[5] = (byte)((3 * alpha0 + 4 * alpha1) / 7); + alpha[6] = (byte)((2 * alpha0 + 5 * alpha1) / 7); + alpha[7] = (byte)((alpha0 + 6 * alpha1) / 7); + } + + else + { + alpha[2] = (byte)((4 * alpha0 + alpha1) / 5); + alpha[3] = (byte)((3 * alpha0 + 2 * alpha1) / 5); + alpha[4] = (byte)((2 * alpha0 + 3 * alpha1) / 5); + alpha[5] = (byte)((alpha0 + 4 * alpha1) / 5); + alpha[6] = 0; + alpha[7] = 255; + } + + } + + ushort c0 = s.ReadUInt16LE(); + ushort c1 = s.ReadUInt16LE(); + colors[0] = RGB565ToARGB(c0); + colors[1] = RGB565ToARGB(c1); + var color0 = Color.FromArgb(colors[0]); + var color1 = Color.FromArgb(colors[1]); + s.Read(iData, 0, 4); + + ulong colorMask = ((ulong)iData[0]) | ((ulong)iData[1] << 8) | ((ulong)iData[2] << 16) | ((ulong)iData[3] << 24); + + if (c0 > c1) + { + colors[2] = Color.FromArgb(0xFF, + (color0.R * 2 + color1.R) / 3, + (color0.G * 2 + color1.G) / 3, + (color0.B * 2 + color1.B) / 3).ToArgb(); + colors[3] = Color.FromArgb(0xFF, + (color0.R + (color1.R * 2)) / 3, + (color0.G + (color1.G * 2)) / 3, + (color0.B + (color1.B * 2)) / 3).ToArgb(); + } + else + { + colors[2] = Color.FromArgb(0xFF, + (color0.R + color1.R) / 2, + (color0.G + color1.G) / 2, + (color0.B + color1.B) / 2).ToArgb(); + colors[3] = Color.Black.ToArgb(); + } + var offset = y * m.Width + x; + int currentIndex = 0; + for (var i = 0; i < 4; i++) + { + for (var j = 0; j < 4; j++) + { + if (DXT5) + { + var idx = (colorMask >> (2 * currentIndex)) & 0x3; + // var idx = (iData[i] >> (2 * j)) & 0x3; + var selectedColor = colors[idx]; + int alphaIndex = (int)((alphaMask >> (3 * currentIndex)) & 0x7); + var selectedAlpha = alpha[alphaIndex]; + var colorWithoutAlpha = Color.FromArgb(selectedColor); + var colorWithAlpha = Color.FromArgb(selectedAlpha, colorWithoutAlpha).ToArgb(); + imageData[offset + i * m.Width + j] = colorWithAlpha; + } + else + { + var idx = (iData[i] >> (2 * j)) & 0x3; + imageData[offset + i * m.Width + j] = colors[idx]; + } + currentIndex++; + } + } + } } } diff --git a/LibForge/LibForge/Texture/TextureReader.cs b/LibForge/LibForge/Texture/TextureReader.cs index a15d4ec..46ae199 100644 --- a/LibForge/LibForge/Texture/TextureReader.cs +++ b/LibForge/LibForge/Texture/TextureReader.cs @@ -9,9 +9,11 @@ namespace LibForge.Texture { public class TextureReader : ReaderBase { - public static Texture ReadStream(Stream s) + public static Texture ReadStream(Stream s, string fileName = "") { - return new TextureReader(s).Read(); + var tr = new TextureReader(s).Read(); + tr.FileName = fileName; + return tr; } public TextureReader(Stream s) : base(s) { } @@ -23,7 +25,11 @@ public override Texture Read() throw new Exception($"Unknown texture magic {magic}"); } var version = Int(); - var hdrData = magic == 6 ? FixedArr(Byte, version == 0xC ? 0x7Cu : 0xACu) : FixedArr(Byte, 0xA8); + long position = s.Position; + //Console.WriteLine(s.Position.ToString()); + var hdrData = magic == 6 ? FixedArr(Byte, version == 0xC ? 0x7Cu : 0xACu) : FixedArr(Byte, 0xA4); + /* Console.WriteLine(s.Position.ToString()); + Console.WriteLine("[{0}]", string.Join(", ", hdrData)); */ var MipmapLevels = UInt(); var Mipmaps = FixedArr(() => new Texture.Mipmap { @@ -36,13 +42,20 @@ public override Texture Read() { Mipmaps[i].Data = Arr(Byte); } + var numberOfPixels = Mipmaps[0].Width * Mipmaps[0].Height; + var dataLength = Mipmaps[0].Data.Length; + Console.WriteLine("[{0}]", string.Join(", ", hdrData)); + float result = (float)numberOfPixels / (float)dataLength; + /* Console.WriteLine(numberOfPixels.ToString() + "px" + " - " + dataLength.ToString() + "byte " + "result: " + result); + Console.WriteLine(version); + Console.WriteLine("---------------"); */ var footerData = FixedArr(Byte, 0x1C); return new Texture { Version = magic, Mipmaps = Mipmaps, HeaderData = hdrData, - FooterData = footerData + FooterData = footerData, }; } } diff --git a/LibForge/LibForge/Util/PkgCreator.cs b/LibForge/LibForge/Util/PkgCreator.cs index 73f53a9..bfb719a 100644 --- a/LibForge/LibForge/Util/PkgCreator.cs +++ b/LibForge/LibForge/Util/PkgCreator.cs @@ -139,14 +139,32 @@ public static DataArray MakeMoggDta(DataArray array, float volumeAdjustment) var crowdData = array.Array("song").Array("crowd_channels"); if (crowdData != null) { + // I guess "i" should start with 1 instead of 0 int i = 0; while (true) { - int? maybeChannel = crowdData.Int(i); + int? maybeChannel; + + try { + maybeChannel = crowdData.Int(i); + } + + // because iam not sure why i starts with 0 try once more if its the first iteration + catch { + if(i == 0) { + i++; + continue; + } else { + break; + } + } + if (maybeChannel == null) + { break; - else + } else { crowdChannels.Add(maybeChannel.Value); + } i++; } } @@ -206,14 +224,13 @@ public static DLCSong ConvertDLCSong(DataArray songDta, GameArchives.IDirectory var midPath = shortname + ".mid"; var artPath = $"gen/{shortname}_keep.png_xbox"; var miloPath = $"gen/{shortname}.milo_xbox"; - var songId = songDta.Array("song_id").Node(1); var name = songDta.Array("name").String(1); var artist = songDta.Array("artist").String(1); var mid = MidiCS.MidiFileReader.FromBytes(songRoot.GetFileAtPath(midPath).GetBytes()); // TODO: Catch possible conversion exceptions? i.e. Unsupported milo version var milo = MiloFile.ReadFromStream(songRoot.GetFileAtPath(miloPath).GetStream()); - var songData = SongDataConverter.ToSongData(songDta); + var songData = SongDataConverter.ToSongData(songDta, songRoot, true); Texture.Texture artwork = null; if (songData.AlbumArt) @@ -247,7 +264,7 @@ public static DLCSong ConvertDLCSong(DataArray songDta, GameArchives.IDirectory var dta = DTX.FromPlainTextBytes(dlcRoot.GetFile("songs.dta").GetBytes()); for (int i = 0; i < dta.Count; i++) { - metas.Add(SongDataConverter.ToSongData(dta.Array(i))); + metas.Add(SongDataConverter.ToSongData(dta.Array(i), dlcRoot)); } return metas; }