Last active
December 5, 2020 04:26
-
-
Save GMMan/83ce810dfb925d31fb2527a13cd11542 to your computer and use it in GitHub Desktop.
Game & Watch Mario Drawing Song animation converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using SixLabors.ImageSharp; | |
using SixLabors.ImageSharp.Formats.Gif; | |
using SixLabors.ImageSharp.IO; | |
using SixLabors.ImageSharp.Memory; | |
namespace DrawingSongConv | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
if (args.Length < 2) | |
{ | |
Console.WriteLine("Usage: DrawingSongConv <decrypted_flash_path> <output_path>"); | |
return; | |
} | |
int dataOffset = 0x12d44; | |
using BufferedReadStream ifs = new BufferedReadStream(new Configuration(), File.OpenRead(args[0])); | |
using FileStream ofs = File.Create(args[1]); | |
BinaryReader br = new BinaryReader(ifs); | |
BinaryWriter bw = new BinaryWriter(ofs); | |
ifs.Seek(dataOffset, SeekOrigin.Begin); | |
// Header | |
string signature = new string(br.ReadChars(3)); | |
if (signature != "GIF") throw new InvalidDataException("Not a GIF"); | |
bw.Write(signature.ToCharArray()); | |
string version = new string(br.ReadChars(3)); | |
if (version != "89a") throw new InvalidDataException("Not GIF 89a"); | |
bw.Write(version.ToCharArray()); | |
// Logical screen descriptor | |
ushort width = br.ReadUInt16(); | |
ushort height = br.ReadUInt16(); | |
byte packed = br.ReadByte(); | |
byte bgIndex = br.ReadByte(); | |
byte pixAR = br.ReadByte(); | |
bw.Write(width); | |
bw.Write(height); | |
bw.Write(packed); | |
bw.Write(bgIndex); | |
bw.Write(pixAR); | |
// Global color table | |
if ((packed & 0x80) == 0) throw new InvalidDataException("No GCT present"); | |
int colorDepth = (packed & 3) + 1; | |
int gctSize = 1 << ((packed & 3) + 1); | |
byte[] gct = br.ReadBytes(gctSize * 3); | |
bw.Write(gct); | |
// Frames | |
byte minCodeSize = br.ReadByte(); | |
int frame = 0; | |
var memoryAllocator = new ArrayPoolMemoryAllocator(); | |
using Buffer2D<byte> buffer = memoryAllocator.Allocate2D<byte>(new Size(width, height)); | |
int peek; | |
while ((peek = br.PeekChar()) >= 0 && peek != 0x3b) | |
{ | |
// Decode frame | |
// We want to also find a color to use for transparency | |
bool[] isColorUsed = new bool[gctSize]; | |
using Buffer2D<byte> currFrame = memoryAllocator.Allocate2D<byte>(new Size(width, height)); | |
using (LzwDecoder decoder = new LzwDecoder(memoryAllocator, ifs)) | |
decoder.DecodePixels(minCodeSize, currFrame); | |
bool isIncremental = frame > 0; | |
// Update current drawing buffer and flip color | |
for (int y = 0; y < buffer.Height; ++y) | |
for (int x = 0; x < buffer.Width; ++x) | |
{ | |
var pix = currFrame[x, y]; | |
if (pix != 0) | |
{ | |
pix ^= buffer[x, y]; | |
currFrame[x, y] = pix; | |
buffer[x, y] = pix; | |
isColorUsed[pix] = true; | |
} | |
else | |
{ | |
if (isIncremental) currFrame[x, y] = 0xff; | |
} | |
} | |
// Find first transparent color | |
byte transparentColor = 0xff; | |
if (isIncremental) | |
{ | |
for (int i = 0; i < isColorUsed.Length; ++i) | |
{ | |
if (!isColorUsed[i]) | |
{ | |
transparentColor = (byte)i; | |
break; | |
} | |
} | |
if (transparentColor == 0xff) | |
throw new Exception($"Cannot find unused color for transparency on frame {frame}"); | |
// Set transparency color | |
for (int y = 0; y < buffer.Height; ++y) | |
for (int x = 0; x < buffer.Width; ++x) | |
{ | |
if (currFrame[x, y] == 0xff) | |
currFrame[x, y] = transparentColor; | |
} | |
} | |
// Graphic control extension | |
bw.Write((byte)0x21); | |
bw.Write((byte)0xf9); | |
bw.Write((byte)4); | |
bw.Write((byte)((1 << 2) | (isIncremental ? 1 : 0))); // do not dispose, transparent index specified (if not first frame) | |
bw.Write((ushort)(1f / 60 * 5 * 100)); // 60fps, flip every 5 frames | |
bw.Write(transparentColor); | |
bw.Write((byte)0); | |
// Image descriptor | |
bw.Write((byte)0x2c); | |
bw.Write((ushort)0); // x | |
bw.Write((ushort)0); // y | |
bw.Write(width); | |
bw.Write(height); | |
bw.Write((byte)0); // no flags | |
// Image data | |
using LzwEncoder encoder = new LzwEncoder(memoryAllocator, colorDepth); | |
encoder.Encode(currFrame, ofs); | |
byte terminator = br.ReadByte(); | |
if (terminator != 0) throw new InvalidDataException($"Frame terminator has unexpected value, frame {frame}"); | |
++frame; | |
} | |
byte trailer = br.ReadByte(); | |
if (trailer != 0x3b) throw new InvalidDataException("Invalid GIF trailer"); | |
bw.Write(trailer); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs | |
index 9eaa55566..a0e397c92 100644 | |
--- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs | |
+++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs | |
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Gif | |
/// <summary> | |
/// Decompresses and decodes data using the dynamic LZW algorithms. | |
/// </summary> | |
- internal sealed class LzwDecoder : IDisposable | |
+ public sealed class LzwDecoder : IDisposable | |
{ | |
/// <summary> | |
/// The max decoder pixel stack size. | |
diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs | |
index 195a84a1d..c20d0a7c7 100644 | |
--- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs | |
+++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs | |
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Gif | |
/// Joe Orost (decvax!vax135!petsd!joe) | |
/// </para> | |
/// </remarks> | |
- internal sealed class LzwEncoder : IDisposable | |
+ public sealed class LzwEncoder : IDisposable | |
{ | |
/// <summary> | |
/// 80% occupancy | |
diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs | |
index acba3eff0..0c0b2ad91 100644 | |
--- a/src/ImageSharp/IO/BufferedReadStream.cs | |
+++ b/src/ImageSharp/IO/BufferedReadStream.cs | |
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.IO | |
/// A readonly stream that add a secondary level buffer in addition to native stream | |
/// buffered reading to reduce the overhead of small incremental reads. | |
/// </summary> | |
- internal sealed class BufferedReadStream : Stream | |
+ public sealed class BufferedReadStream : Stream | |
{ | |
private readonly int maxBufferIndex; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment