Skip to content

Instantly share code, notes, and snippets.

@pleonex
Last active April 24, 2022 20:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pleonex/6265017 to your computer and use it in GitHub Desktop.
Save pleonex/6265017 to your computer and use it in GitHub Desktop.
GBA and DS copyright logo decrypt and encrypt classes
// ----------------------------------------------------------------------
// <copyright file="LogoGBA.cs" company="none">
// Copyright (C) 2013
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// </copyright>
// <author>pleoNeX</author>
// <email>benito356@gmail.com</email>
// <date>19/08/2013</date>
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace NinLogo
{
/// <summary>
/// Logo from GBA/NDS encrypted header data
/// </summary>
public class LogoGBA
{
// Image constants
private const int TileWidth = 8;
private const int TileHeight = 8;
private static Color[] Palette = {
Color.FromArgb(255, 255, 255, 255), // White (background)
Color.FromArgb(255, 248, 0, 248) // Magenta (chars)
};
private const int LogoLength = 0x9C; // Length of the original logo encrypted.
// Huffman header to decoded the logo, it's in the BIOS
private static byte[] BiosHeader = {
0x24, 0xD4, 0x00, 0x00, 0x0F, 0x40, 0x00, 0x00, 0x00, 0x01, 0x81, 0x82,
0x82, 0x83, 0x0F, 0x83, 0x0C, 0xC3, 0x03, 0x83, 0x01, 0x83, 0x04, 0xC3,
0x08, 0x0E, 0x02, 0xC2, 0x0D, 0xC2, 0x07, 0x0B, 0x06, 0x0A, 0x05, 0x09
};
// Codewords of the BIOS Huffman header
private static string[] Codewords = {
"1", "0110", "01010", "0100", "00010", "011110",
"010110", "000110", "00110", "011111", "010111", "000111",
"0010", "01110", "00111", "0000"
};
private byte[] logo;
private LogoGBA()
{
this.logo = new byte[0];
}
/// <summary>
/// Initializes a new instance of the <see cref="NinLogo.LogoGBA"/> class.
/// </summary>
/// <param name="file">File with the encrypted logo data.</param>
/// <param name="offset">Offset to the logo data.</param>
public LogoGBA(string file, int offset)
{
this.Read(File.ReadAllBytes(file), offset);
}
/// <summary>
/// Create a new GBA logo from an image.
/// </summary>
/// <returns>The new GBA logo.</returns>
/// <param name="imgFile">Image file.</param>
public static LogoGBA FromImage(string imgFile)
{
LogoGBA lgba = new LogoGBA();
lgba.SetImage((Bitmap)Image.FromFile(imgFile));
return lgba;
}
/// <summary>
/// Gets the width of the logo.
/// </summary>
/// <value>The width.</value>
public int Width {
get { return 104; }
}
/// <summary>
/// Gets the height of the logo.
/// </summary>
/// <value>The height.</value>
public int Height {
get { return 16; }
}
/// <summary>
/// Gets the bits per pixel of the logo.
/// </summary>
/// <value>The bpp.</value>
public int Bpp {
get { return 1; }
}
/// <summary>
/// Writes the encrypted logo to a file.
/// </summary>
/// <param name="fileOut">File out.</param>
public void Write(string fileOut, int offset)
{
// Encrypt using Addition algorithm
byte[] encrypted = Addition.Encrypt(this.logo);
// Encode data with huffman
byte[] encoded = Huffman.Encrypt(encrypted, LogoLength, Codewords);
// Add the debug bits
encoded[0x98] = 0x21;
encoded[0x99] = 0xD4;
// Write to a file at the given offset
FileStream fs = new FileStream(fileOut, FileMode.Create);
fs.Position = offset;
fs.Write(encoded, 0, encoded.Length);
fs.Flush();
fs.Close();
}
/// <summary>
/// Get the final image from the decoded bytes.
/// </summary>
/// <returns>Logo</returns>
public Bitmap GetImage()
{
Bitmap bmp = new Bitmap(this.Width, this.Height);
for (int y = 0; y < this.Height; y++) {
for (int x = 0; x < this.Width; x++) {
// Get index of pixel of a tiled structure from (x, y) coordinates
int index = this.GetTiledPixel(x, y);
// Get the bit related to that pixel
int bit = (this.logo[index / 8] >> (index % 8)) & 1;
// Get color from that bit using the palette
bmp.SetPixel(x, y, Palette[bit]);
}
}
return bmp;
}
/// <summary>
/// Set the logo from an image
/// </summary>
/// <param name="image">New logo image</param>
private void SetImage(Bitmap image)
{
if (image.Width != this.Width)
throw new ArgumentException("The width of the image is not valid.");
if (image.Height != this.Height)
throw new ArgumentException("The height of the image is not valid.");
int numPixels = this.Width * this.Height;
this.logo = new byte[numPixels / 8];
for (int y = 0; y < this.Height; y++) {
for (int x = 0; x < this.Width; x++) {
// Get color
Color color = image.GetPixel(x, y);
// Get color index in palette
int colorIndex = Array.FindIndex<Color>(Palette, c => c == color);
if (colorIndex == -1)
throw new ArgumentException(string.Format("Invalid color found at ({0},{1})", x, y));
// Get index of pixel of a tiled structure
int index = this.GetTiledPixel(x, y);
// Set the colorIndex
int value = this.logo[index / 8];
value |= colorIndex << (index % 8);
this.logo[index / 8] = (byte)value;
}
}
}
/// <summary>
/// Read the specified data at the offset to get the decrypted logo data.
/// </summary>
/// <param name="data">Encrypted data.</param>
/// <param name="offset">Offset to the logo data.</param>
private void Read(byte[] data, int offset)
{
// Add to the encoded bytes the huffman header
byte[] encoded = new byte[LogoLength + BiosHeader.Length];
BiosHeader.CopyTo(encoded, 0);
Array.Copy(data, offset, encoded, BiosHeader.Length, LogoLength);
// Decode the data with Huffman
byte[] encrypted = Huffman.Decrypt(encoded);
// Finally, decrypt it
this.logo = Addition.Decrypt(encrypted);
}
/// <summary>
/// Gets the pixel index of a tiled structure.
/// </summary>
/// <returns>The tiled pixel index.</returns>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
private int GetTiledPixel(int x, int y)
{
int numTilesX = this.Width / TileWidth;
int tileLength = TileWidth * TileHeight;
Point tilePos = new Point(x / TileWidth, y / TileHeight);
Point pixelPos = new Point(x % TileWidth, y % TileHeight);
int index = tilePos.Y * numTilesX * tileLength + tilePos.X * tileLength; // Start tile index
index += pixelPos.Y * TileWidth + pixelPos.X; // Pixel index
return index;
}
}
/// <summary>
/// Encryption used in the Logo.
/// </summary>
public static class Addition
{
/// <summary>
/// Decrypt data.
/// </summary>
/// <param name="encrypted">Encrypted data.</param>
public static byte[] Decrypt(byte[] encrypted)
{
uint header = BitConverter.ToUInt32(encrypted, 0);
uint size = header >> 8; // Number of bytes to decrypt
byte[] buffer = new byte[size];
ushort sum = 0;
for (int i = 0, j = 4; i < size; i += 2, j += 2) {
sum += BitConverter.ToUInt16(encrypted, j); // Add the current 16bits value
BitConverter.GetBytes(sum).CopyTo(buffer, i); // Store the addition
}
return buffer;
}
/// <summary>
/// Encrypt data.
/// </summary>
/// <param name="decoded">Data to encrypt</param>
public static byte[] Encrypt(byte[] decoded)
{
byte[] encoded = new byte[decoded.Length + 4];
// Create the header
uint header = (uint)(decoded.Length << 8) | 0x82;
BitConverter.GetBytes(header).CopyTo(encoded, 0);
for (int i = 2, j = 6; i < decoded.Length - 1; i += 2, j += 2) {
ushort valuePrev = BitConverter.ToUInt16(decoded, i - 2);
ushort valueNext = BitConverter.ToUInt16(decoded, i);
ushort encrypted = (ushort)(valueNext - valuePrev);
BitConverter.GetBytes(encrypted).CopyTo(encoded, j);
}
return encoded;
}
}
/// <summary>
/// Huffman encoding
/// </summary>
public static class Huffman
{
// Huffman constant
private const byte IncrMask = 0x3F; // (0xFF & ~(0x80 | 0x40)) used in huffman
private const int MaxEncBits = 0x4CE;
/// <summary>
/// Decrypt encoded bytes using Huffman algorithm.
/// </summary>
public static byte[] Decrypt(byte[] encoded)
{
int pos_code = 4; // Position to read the codeworks
int pos_tree = 5; // Position to read the tree
int pos_buffer = 0; // Position in the output buffer
uint code = 0; // Current codeworks
byte node = 0; // Current node byte
byte code_shift = 0; // Shift value for the codework
byte bit_code = 0; // Current bit code
int incr = 0; // Jump to the next node / child
byte dec_bits = 0; // Decoded bits
// Read header
uint header = BitConverter.ToUInt32(encoded, 0);
byte type = (byte)(header & 0xF);
uint decompressed_size = header >> 8;
byte[] decoded = new byte[decompressed_size];
// Get the pos_code initial value skipping the tree
int num_nodes = encoded[pos_code] + 1;
pos_code = pos_code + (num_nodes << 1);
while (pos_buffer < decompressed_size)
{
if (code_shift == 0x00) // If we've already read all the uint, get another
{
code = BitConverter.ToUInt32(encoded, pos_code); pos_code += 4;
code_shift = 0x20; // 32 bits
}
code_shift--; // Update the mask
bit_code = (byte)((code >> code_shift) & 1); // Get the bit code that indicates the branch, 0-> right, 1-> left
node = encoded[pos_tree]; // Get node byte
incr = (node & IncrMask) + 1; // Get the increment to the next node
incr <<= 1;
node <<= bit_code; // Activate the child flag
if ((pos_tree & 1) != 0) incr--; // If we're in the right branch, go back to increment it
pos_tree += (int)(incr + bit_code); // Increment the position in the tree to the next node
if ((node & 0x80) != 0) // If it's a child, get the decoded byte
{
decoded[pos_buffer] |= (byte)(encoded[pos_tree] << dec_bits); // Store the child
dec_bits += type; // Increment the decoded bits
if (dec_bits == 0x8) // If it's a byte, reset it and update the position
{
pos_buffer++;
dec_bits = 0;
}
pos_tree = 5; // Reset the position in the tree
}
}
return decoded;
}
/// <summary>
/// Encrypt the data with huffman compression
/// </summary>
/// <param name="data">Data to encrypt</param>
public static byte[] Encrypt(byte[] data, int outputSize, string[] codewords)
{
byte[] encoded = new byte[outputSize];
uint enc_value = 0;
int pos_enc = 0;
for (int i = 0; i < data.Length; i++)
{
byte value = data[i];
string[] codeword = { codewords[value & 0xF], codewords[value >> 4] };
for (int c = 0; c < codeword.Length; c++)
{
for (int b = 0; b < codeword[c].Length; b++)
{
if (pos_enc == MaxEncBits)
{
Console.WriteLine("ERROR encoded file is bigger than permitted!");
return null;
}
int bit = (codeword[c][b] == '0' ? 0 : 1);
enc_value |= (uint)(bit << (31 - (pos_enc % 32)));
pos_enc++;
if (pos_enc % 32 == 0)
{
BitConverter.GetBytes(enc_value).CopyTo(encoded, (pos_enc / 8) - 4);
enc_value = 0;
}
}
}
}
if (pos_enc % 32 != 0)
BitConverter.GetBytes(enc_value).CopyTo(encoded, (pos_enc / 8) - (pos_enc % 32 / 8));
return encoded;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment