mirror of
https://github.com/kwsch/pk3DS.git
synced 2026-10-03 06:16:55 -05:00
Refactoring
Also included the new GARC handler which can now process all GARCs.
This commit is contained in:
754
pk3DS/3DS/BCLIM.cs
Normal file
754
pk3DS/3DS/BCLIM.cs
Normal file
@@ -0,0 +1,754 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
namespace CTR
|
||||
{
|
||||
class BCLIM
|
||||
{
|
||||
internal static void openFile(string path, bool autosave = false, bool crop = true, char format = 'X')
|
||||
{
|
||||
// Handle file
|
||||
if (!File.Exists(path)) throw new Exception("Can only accept files, not folders");
|
||||
string ext = Path.GetExtension(path);
|
||||
if (ext == ".png")
|
||||
makeBCLIM(path, format);
|
||||
else if (ext == ".bin" || ext == ".bclim")
|
||||
makeBMP(path, autosave, crop);
|
||||
}
|
||||
|
||||
internal static Image makeBCLIM(string path, char fc)
|
||||
{
|
||||
byte[] byteArray = File.ReadAllBytes(path);
|
||||
using (Stream BitmapStream = new MemoryStream(byteArray)) // Open the file, even if it is in use.
|
||||
{
|
||||
Image img = Image.FromStream(BitmapStream);
|
||||
Bitmap mBitmap = new Bitmap(img);
|
||||
// Pixel format acquired. Now to determine how we want to save the data.
|
||||
MemoryStream ms = new MemoryStream();
|
||||
int bclimformat = 7; // Init to default (for X)
|
||||
|
||||
if (fc == 'X')
|
||||
write16BitColorPalette(mBitmap, ref ms);
|
||||
else
|
||||
{
|
||||
bclimformat = Convert.ToInt16(fc.ToString(), 16);
|
||||
try
|
||||
{
|
||||
writeGeneric(bclimformat, mBitmap, ref ms);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Media.SystemSounds.Beep.Play();
|
||||
System.Diagnostics.Debug.WriteLine(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
long datalength = ms.Length;
|
||||
// Write the CLIM + imag data.
|
||||
using (BinaryWriter bw = new BinaryWriter(ms))
|
||||
{
|
||||
bw.Write((uint)0x4D494C43); // CLIM
|
||||
bw.Write((ushort)0xFEFF); // BOM
|
||||
bw.Write((uint)0x14);
|
||||
bw.Write((ushort)0x0202); // 2 2
|
||||
bw.Write((uint)(datalength+0x28));
|
||||
bw.Write((uint)1);
|
||||
bw.Write((uint)0x67616D69);
|
||||
bw.Write((uint)0x10);
|
||||
bw.Write((ushort)mBitmap.Width);
|
||||
bw.Write((ushort)mBitmap.Height);
|
||||
bw.Write((uint)bclimformat);
|
||||
bw.Write((uint)datalength);
|
||||
}
|
||||
string fp = Path.GetFileNameWithoutExtension(path);
|
||||
fp = "new_" + fp.Substring(fp.IndexOf('_') + 1);
|
||||
string pp = Path.GetDirectoryName(path);
|
||||
string newPath = Path.Combine(pp, fp + ".bclim");
|
||||
File.WriteAllBytes(newPath, ms.ToArray());
|
||||
|
||||
return makeBMP(newPath);
|
||||
}
|
||||
}
|
||||
internal static Image makeBMP(string path, bool autosave = false, bool crop = true)
|
||||
{
|
||||
CLIM bclim = analyze(path);
|
||||
if (bclim.Magic != 0x4D494C43)
|
||||
{
|
||||
System.Media.SystemSounds.Beep.Play();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Interpret data.
|
||||
int f = bclim.FileFormat;
|
||||
if (f > 13)
|
||||
{
|
||||
System.Media.SystemSounds.Exclamation.Play();
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap img;
|
||||
if (f == 7 && BitConverter.ToUInt16(bclim.Data, 0) == 2)
|
||||
// PKM XY Format 7 (Color Palette)
|
||||
img = getIMG_XY7(bclim);
|
||||
else if (f == 10 || f == 11)
|
||||
img = getIMG_ETC(bclim);
|
||||
else
|
||||
img = getIMG(bclim);
|
||||
|
||||
Rectangle cropRect = new Rectangle(0, 0, bclim.Width, bclim.Height);
|
||||
Bitmap CropBMP = new Bitmap(cropRect.Width, cropRect.Height);
|
||||
using (Graphics g = Graphics.FromImage(CropBMP))
|
||||
{
|
||||
g.DrawImage(img,
|
||||
new Rectangle(0, 0, CropBMP.Width, CropBMP.Height),
|
||||
cropRect,
|
||||
GraphicsUnit.Pixel);
|
||||
}
|
||||
if (!autosave) return !crop ? img : CropBMP;
|
||||
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
//error will throw from here
|
||||
CropBMP.Save(ms, ImageFormat.Png);
|
||||
byte[] data = ms.ToArray();
|
||||
File.WriteAllBytes(bclim.FilePath + "\\" + bclim.FileName + ".png", data);
|
||||
}
|
||||
return !crop ? img : CropBMP;
|
||||
}
|
||||
// Bitmap Data Writing
|
||||
internal static Bitmap getIMG(CLIM bclim)
|
||||
{
|
||||
// New Image
|
||||
Bitmap img = new Bitmap(nlpo2(gcm(bclim.Width,8)), nlpo2(gcm(bclim.Height,8)));
|
||||
int f = bclim.FileFormat;
|
||||
int area = img.Width * img.Height;
|
||||
if (f == 9 && area > bclim.Data.Length/4)
|
||||
{
|
||||
img = new Bitmap(gcm(bclim.Width, 8), gcm(bclim.Height, 8));
|
||||
area = img.Width * img.Height;
|
||||
}
|
||||
// Coordinates
|
||||
// Colors
|
||||
// Tiles Per Width
|
||||
int p = gcm(img.Width, 8) / 8;
|
||||
if (p == 0) p = 1;
|
||||
|
||||
// Build Image
|
||||
using (Stream BitmapStream = new MemoryStream(bclim.Data))
|
||||
using (BinaryReader br = new BinaryReader(BitmapStream))
|
||||
for (uint i = 0; i < area; i++) // for every pixel
|
||||
{
|
||||
uint x;
|
||||
uint y;
|
||||
d2xy(i % 64, out x, out y);
|
||||
uint tile = i / 64;
|
||||
|
||||
// Shift Tile Coordinate into Tilemap
|
||||
x += (uint)(tile % p) * 8;
|
||||
y += (uint)(tile / p) * 8;
|
||||
|
||||
// Get Color
|
||||
Color c;
|
||||
switch (f)
|
||||
{
|
||||
case 0x0: // L8 // 8bit/1 byte
|
||||
case 0x1: // A8
|
||||
case 0x2: // LA4
|
||||
c = DecodeColor(br.ReadByte(), f);
|
||||
break;
|
||||
case 0x3: // LA8 // 16bit/2 byte
|
||||
case 0x4: // HILO8
|
||||
case 0x5: // RGB565
|
||||
case 0x8: // RGBA4444
|
||||
case 0x7: // RGBA5551
|
||||
c = DecodeColor(br.ReadUInt16(), f);
|
||||
break;
|
||||
case 0x6: // RGB8: // 24bit
|
||||
byte[] data = br.ReadBytes(3); Array.Resize(ref data, 4);
|
||||
c = DecodeColor(BitConverter.ToUInt32(data, 0), f);
|
||||
break;
|
||||
case 0x9: // RGBA8888
|
||||
c = DecodeColor(br.ReadUInt32(), f);
|
||||
break;
|
||||
case 0xC: // L4
|
||||
case 0xD: // A4 // 4bit - Do 2 pixels at a time.
|
||||
uint val = br.ReadByte();
|
||||
img.SetPixel((int)x, (int)y, DecodeColor(val & 0xF, f)); // lowest bits for the low pixel
|
||||
i++; x++;
|
||||
c = DecodeColor(val >> 4, f); // highest bits for the high pixel
|
||||
break;
|
||||
default: throw new Exception("Invalid FileFormat.");
|
||||
}
|
||||
img.SetPixel((int)x, (int)y, c);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
internal static Bitmap getIMG_XY7(CLIM bclim)
|
||||
{
|
||||
Bitmap img = new Bitmap(bclim.BaseSize, bclim.BaseSize);
|
||||
using (Stream BitmapStream = new MemoryStream(bclim.Data))
|
||||
using (BinaryReader br = new BinaryReader(BitmapStream))
|
||||
{
|
||||
// Fetch Color stuff.
|
||||
if (br.ReadUInt16() != 2) return null;
|
||||
ushort colors = br.ReadUInt16();
|
||||
Color[] ca = new Color[colors];
|
||||
for (int i = 0; i < colors; i++)
|
||||
ca[i] = DecodeColor(br.ReadUInt16(), 7);
|
||||
|
||||
// Coordinates
|
||||
// Colors
|
||||
// Tiles Per Width
|
||||
int p = gcm(img.Width, 8) / 8;
|
||||
if (p == 0) p = 1;
|
||||
|
||||
for (uint i = 0; i < bclim.BaseSize * bclim.BaseSize; i++) // for every pixel
|
||||
{
|
||||
uint x;
|
||||
uint y;
|
||||
d2xy(i % 64, out x, out y);
|
||||
uint tile = i / 64;
|
||||
|
||||
// Shift Tile Coordinate into Tilemap
|
||||
x += (uint)(tile % p) * 8;
|
||||
y += (uint)(tile / p) * 8;
|
||||
|
||||
byte val = br.ReadByte();
|
||||
if (colors <= 0x10) // Handle 2 pixels at a time
|
||||
{
|
||||
img.SetPixel((int)x, (int)y, ca[val >> 4]);
|
||||
x++; i++; val &= 0xF;
|
||||
img.SetPixel((int)x, (int)y, ca[val]);
|
||||
}
|
||||
else //1bpp instead of .5, handle 2 pixels at a time the same way for no reason
|
||||
{
|
||||
img.SetPixel((int)x, (int)y, ca[val]);
|
||||
x++; i++; val = br.ReadByte();
|
||||
img.SetPixel((int)x, (int)y, ca[val]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
internal static Bitmap getIMG_ETC(CLIM bclim)
|
||||
{
|
||||
//int format = bclim.FileFormat;
|
||||
//if (format != 10 && format != 11)
|
||||
// return null;
|
||||
|
||||
//byte[] data = ETC1_Decompress(bclim.Data, bclim.Width, bclim.Height, format + 2);
|
||||
//Bitmap img = new Bitmap(bclim.Width, bclim.Height);
|
||||
|
||||
//for (int i = 0; i < bclim.Width * bclim.Height; i++) // for every pixel
|
||||
//{
|
||||
// Color c = DecodeColor(BitConverter.ToUInt32(data, i * 4), 9);
|
||||
// int x = i % bclim.Width;
|
||||
// int y = i / bclim.Width;
|
||||
// img.SetPixel(x, y, c);
|
||||
//}
|
||||
//return img;
|
||||
return null;
|
||||
}
|
||||
|
||||
// BCLIM Data Writing
|
||||
internal static int write16BitColorPalette(Bitmap img, ref MemoryStream ms)
|
||||
{
|
||||
using (Stream pixelcolors = new MemoryStream())
|
||||
using (BinaryWriter bz = new BinaryWriter(pixelcolors))
|
||||
{
|
||||
// Set up our basis.
|
||||
bool under16colors = false;
|
||||
int colors = getColorCount(img);
|
||||
Color[] pcs = new Color[colors];
|
||||
if (colors < 16) under16colors = true;
|
||||
uint div = 1;
|
||||
if (under16colors)
|
||||
div = 2;
|
||||
|
||||
if (colors > 70) throw new Exception("Too many colors");
|
||||
|
||||
// Set up a new reverse image to build into.
|
||||
int w = gcm(img.Width, 8);
|
||||
int h = gcm(img.Height, 8);
|
||||
w = Math.Max(nlpo2(w), nlpo2(h));
|
||||
h = w;
|
||||
byte[] pixelarray = new Byte[w * h];
|
||||
|
||||
const int colorformat = 2;
|
||||
int ctr = 1;
|
||||
|
||||
pcs[0] = Color.FromArgb(0, 0xFF, 0xFF, 0xFF);
|
||||
|
||||
int p = gcm(w, 8) / 8;
|
||||
if (p == 0) p = 1;
|
||||
int d = 0;
|
||||
for (uint i = 0; i < pixelarray.Length; i++)
|
||||
{
|
||||
d = (int)(i / div);
|
||||
// Get Tile Coordinate
|
||||
uint x;
|
||||
uint y;
|
||||
d2xy(i % 64, out x, out y);
|
||||
|
||||
// Get Shift Tile
|
||||
uint tile = i / 64;
|
||||
|
||||
// Shift Tile Coordinate into Tilemap
|
||||
x += (uint)(tile % p) * 8;
|
||||
y += (uint)(tile / p) * 8;
|
||||
if (x >= img.Width || y >= img.Height) // Don't try to access any pixel data outside of our bounds.
|
||||
{ i++; continue; } // Goto next tile.
|
||||
|
||||
// Get Color of Pixel
|
||||
Color c = img.GetPixel((int)x, (int)y);
|
||||
|
||||
// Color Table Building Logic
|
||||
int index = Array.IndexOf(pcs, c);
|
||||
if (c.A == 0) index = 0;
|
||||
if (index < 0) // If new color
|
||||
{ pcs[ctr] = c; index = ctr; ctr++; } // Add it to color list
|
||||
|
||||
// Add pixel to pixeldata
|
||||
if (under16colors) index = index << 4;
|
||||
pixelarray[i / div] = (byte)index;
|
||||
if (!under16colors) continue;
|
||||
|
||||
c = img.GetPixel((int)x + 1, (int)y);
|
||||
index = Array.IndexOf(pcs, c);
|
||||
if (c.A == 0) index = 0;
|
||||
if (index < 0) // If new color
|
||||
{ pcs[ctr] = c; index = ctr; ctr++; }
|
||||
pixelarray[i / div] |= (byte)(index);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Write Intro
|
||||
bz.Write((ushort)colorformat); bz.Write((ushort)ctr);
|
||||
// Write Colors
|
||||
for (int i = 0; i < ctr; i++)
|
||||
bz.Write(GetRGBA5551(pcs[i])); // Write byte array.
|
||||
// Write Pixel Data
|
||||
for (uint i = 0; i < d; i++)
|
||||
bz.Write(pixelarray[i]);
|
||||
// Write Padding
|
||||
while (pixelcolors.Length < nlpo2((int)pixelcolors.Length))
|
||||
bz.Write((byte)0);
|
||||
// Copy to main CLIM.
|
||||
pixelcolors.Position = 0; pixelcolors.CopyTo(ms);
|
||||
}
|
||||
return 7;
|
||||
}
|
||||
internal static void writeGeneric(int format, Bitmap img, ref MemoryStream ms)
|
||||
{
|
||||
int w = img.Width;
|
||||
int h = img.Height;
|
||||
|
||||
// square format. Yay.
|
||||
w = h = Math.Max(nlpo2(w),nlpo2(h));
|
||||
if (format == 0 && Math.Min(img.Width, img.Height) > 64)
|
||||
{
|
||||
w = nlpo2(img.Width);
|
||||
h = nlpo2(img.Height);
|
||||
}
|
||||
using (MemoryStream mz = new MemoryStream())
|
||||
using (BinaryWriter bz = new BinaryWriter(mz))
|
||||
{
|
||||
int p = gcm(w, 8) / 8;
|
||||
if (p == 0) p = 1;
|
||||
for (uint i = 0; i < w * h; i++)
|
||||
{
|
||||
uint x;
|
||||
uint y;
|
||||
d2xy(i % 64, out x, out y);
|
||||
|
||||
// Get Shift Tile
|
||||
uint tile = i / 64;
|
||||
|
||||
// Shift Tile Coordinate into Tilemap
|
||||
x += (uint)(tile % p) * 8;
|
||||
y += (uint)(tile / p) * 8;
|
||||
|
||||
// Don't write data
|
||||
Color c;
|
||||
if (x >= img.Width || y >= img.Height)
|
||||
{ c = Color.FromArgb(0, 0, 0, 0); }
|
||||
else
|
||||
{ c = img.GetPixel((int)x, (int)y); if (c.A == 0) c = Color.FromArgb(0, 86, 86, 86); }
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case 0: bz.Write(GetL8(c)); break; // L8
|
||||
case 1: bz.Write(GetA8(c)); break; // A8
|
||||
case 2: bz.Write(GetLA4(c)); break; // LA4(4)
|
||||
case 3: bz.Write(GetLA8(c)); break; // LA8(8)
|
||||
case 4: bz.Write(GetHILO8(c)); break; // HILO8
|
||||
case 5: bz.Write(GetRGB565(c)); break; // RGB565
|
||||
case 6:
|
||||
{
|
||||
bz.Write(c.B);
|
||||
bz.Write(c.G);
|
||||
bz.Write(c.R); break;
|
||||
}
|
||||
case 7: bz.Write(GetRGBA5551(c)); break; // RGBA5551
|
||||
case 8: bz.Write(GetRGBA4444(c)); break; // RGBA4444
|
||||
case 9: bz.Write(GetRGBA8888(c)); break; // RGBA8
|
||||
case 10: throw new Exception("ETC1 not supported.");
|
||||
case 11: throw new Exception("ETC1A4 not supported.");
|
||||
case 12:
|
||||
{
|
||||
byte val = (byte)(GetL8(c) / 0x11); // First Pix // L4
|
||||
{ c = img.GetPixel((int)x, (int)y); if (c.A == 0) c = Color.FromArgb(0, 0, 0, 0); }
|
||||
val |= (byte)((GetL8(c) / 0x11) << 4); i++;
|
||||
bz.Write(val); break;
|
||||
}
|
||||
case 13:
|
||||
{
|
||||
byte val = (byte)(GetA8(c) / 0x11); // First Pix // L4
|
||||
{ c = img.GetPixel((int)x, (int)y); }
|
||||
val |= (byte)((GetA8(c) / 0x11) << 4); i++;
|
||||
bz.Write(val); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (mz.Length < nlpo2((int)mz.Length)) // pad
|
||||
bz.Write((byte)0);
|
||||
mz.Position = 0;
|
||||
mz.CopyTo(ms);
|
||||
}
|
||||
}
|
||||
internal static int getColorCount(Bitmap img)
|
||||
{
|
||||
Color[] colors = new Color[img.Width * img.Height];
|
||||
int colorct = 1;
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
Color c = img.GetPixel(i % img.Width, i / img.Width);
|
||||
int index = Array.IndexOf(colors, c);
|
||||
if (c.A == 0) index = 0;
|
||||
if (index >= 0) continue;
|
||||
|
||||
colors[colorct] = c;
|
||||
colorct++;
|
||||
}
|
||||
return colorct;
|
||||
}
|
||||
|
||||
|
||||
internal static int[] Convert5To8 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39,
|
||||
0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B,
|
||||
0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD,
|
||||
0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF };
|
||||
|
||||
private static Color DecodeColor(uint val, int format)
|
||||
{
|
||||
int alpha = 0xFF, red, green, blue;
|
||||
switch (format)
|
||||
{
|
||||
case 0: // L8
|
||||
return Color.FromArgb(alpha, (byte)val, (byte)val, (byte)val);
|
||||
case 1: // A8
|
||||
return Color.FromArgb((byte)val, alpha, alpha, alpha);
|
||||
case 2: // LA4
|
||||
red = (byte)(val >> 4);
|
||||
alpha = (byte)(val & 0x0F);
|
||||
return Color.FromArgb(alpha, red, red, red);
|
||||
case 3: // LA8
|
||||
red = (byte)((val >> 8 & 0xFF));
|
||||
alpha = (byte)(val & 0xFF);
|
||||
return Color.FromArgb(alpha, red, red, red);
|
||||
case 4: // HILO8
|
||||
red = (byte)(val >> 8);
|
||||
green = (byte)(val & 0xFF);
|
||||
return Color.FromArgb(alpha, red, green, 0xFF);
|
||||
case 5: // RGB565
|
||||
red = Convert5To8[(val >> 11) & 0x1F];
|
||||
green = (byte)(((val >> 5) & 0x3F) * 4);
|
||||
blue = Convert5To8[val & 0x1F];
|
||||
return Color.FromArgb(alpha, red, green, blue);
|
||||
case 6: // RGB8
|
||||
red = (byte)((val >> 16) & 0xFF);
|
||||
green = (byte)((val >> 8) & 0xFF);
|
||||
blue = (byte)(val & 0xFF);
|
||||
return Color.FromArgb(alpha, red, green, blue);
|
||||
case 7: // RGBA5551
|
||||
red = Convert5To8[(val >> 11) & 0x1F];
|
||||
green = Convert5To8[(val >> 6) & 0x1F];
|
||||
blue = Convert5To8[(val >> 1) & 0x1F];
|
||||
alpha = (val & 0x0001) == 1 ? 0xFF : 0x00;
|
||||
return Color.FromArgb(alpha, red, green, blue);
|
||||
case 8: // RGBA4444
|
||||
alpha = (byte)(0x11 * (val & 0xf));
|
||||
red = (byte)(0x11 * ((val >> 12) & 0xf));
|
||||
green = (byte)(0x11 * ((val >> 8) & 0xf));
|
||||
blue = (byte)(0x11 * ((val >> 4) & 0xf));
|
||||
return Color.FromArgb(alpha, red, green, blue);
|
||||
case 9: // RGBA8888
|
||||
red = (byte)((val >> 24) & 0xFF);
|
||||
green = (byte)((val >> 16) & 0xFF);
|
||||
blue = (byte)((val >> 8) & 0xFF);
|
||||
alpha = (byte)(val & 0xFF);
|
||||
return Color.FromArgb(alpha, red, green, blue);
|
||||
// case 10:
|
||||
// case 11:
|
||||
case 12: // L4
|
||||
return Color.FromArgb(alpha, (byte)(val * 0x11), (byte)(val * 0x11), (byte)(val * 0x11));
|
||||
case 13: // A4
|
||||
return Color.FromArgb((byte)(val * 0x11), alpha, alpha, alpha);
|
||||
default:
|
||||
return Color.White;
|
||||
}
|
||||
}
|
||||
|
||||
// Color Conversion
|
||||
internal static byte GetL8(Color c)
|
||||
{
|
||||
byte red = c.R;
|
||||
byte green = c.G;
|
||||
byte blue = c.B;
|
||||
// Luma (Y’) = 0.299 R’ + 0.587 G’ + 0.114 B’ from wikipedia
|
||||
return (byte)(((0x4CB2 * red + 0x9691 * green + 0x1D3E * blue) >> 16) & 0xFF);
|
||||
} // L8
|
||||
internal static byte GetA8(Color c)
|
||||
{
|
||||
return c.A;
|
||||
} // A8
|
||||
internal static byte GetLA4(Color c)
|
||||
{
|
||||
return (byte)((c.A / 0x11) + (c.R / 0x11) << 4);
|
||||
} // LA4
|
||||
internal static ushort GetLA8(Color c)
|
||||
{
|
||||
return (ushort)((c.A) + ((c.R) << 8));
|
||||
} // LA8
|
||||
internal static ushort GetHILO8(Color c)
|
||||
{
|
||||
return (ushort)((c.G) + ((c.R) << 8));
|
||||
} // HILO8
|
||||
internal static ushort GetRGB565(Color c)
|
||||
{
|
||||
int val = 0;
|
||||
// val += c.A >> 8; // unused
|
||||
val += convert8to5(c.B) >> 3;
|
||||
val += (c.G >> 2) << 5;
|
||||
val += convert8to5(c.R) << 10;
|
||||
return (ushort)val;
|
||||
} // RGB565
|
||||
// RGB8
|
||||
internal static ushort GetRGBA5551(Color c)
|
||||
{
|
||||
int val = 0;
|
||||
val += (byte)(c.A > 0x80 ? 1 : 0);
|
||||
val += convert8to5(c.R) << 11;
|
||||
val += convert8to5(c.G) << 6;
|
||||
val += convert8to5(c.B) << 1;
|
||||
ushort v = (ushort)val;
|
||||
|
||||
return v;
|
||||
}// RGBA5551
|
||||
internal static ushort GetRGBA4444(Color c)
|
||||
{
|
||||
int val = 0;
|
||||
val += (c.A / 0x11);
|
||||
val += ((c.B / 0x11) << 4);
|
||||
val += ((c.G / 0x11) << 8);
|
||||
val += ((c.R / 0x11) << 12);
|
||||
return (ushort)val;
|
||||
}// RGBA4444
|
||||
internal static uint GetRGBA8888(Color c) // RGBA8888
|
||||
{
|
||||
uint val = 0;
|
||||
val += c.A;
|
||||
val += (uint)(c.B << 8);
|
||||
val += (uint)(c.G << 16);
|
||||
val += (uint)(c.R << 24);
|
||||
return val;
|
||||
}
|
||||
|
||||
// Unit Conversion
|
||||
internal static byte convert8to5(int colorval)
|
||||
{
|
||||
|
||||
byte[] Convert8to5 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39,
|
||||
0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B,
|
||||
0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD,
|
||||
0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF };
|
||||
byte i = 0;
|
||||
while (colorval > Convert8to5[i]) i++;
|
||||
return i;
|
||||
}
|
||||
internal static UInt32 DM2X(UInt32 code)
|
||||
{
|
||||
return C11(code >> 0);
|
||||
}
|
||||
internal static UInt32 DM2Y(UInt32 code)
|
||||
{
|
||||
return C11(code >> 1);
|
||||
}
|
||||
internal static UInt32 C11(UInt32 x)
|
||||
{
|
||||
x &= 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0
|
||||
x = (x ^ (x >> 1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10
|
||||
x = (x ^ (x >> 2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210
|
||||
x = (x ^ (x >> 4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210
|
||||
x = (x ^ (x >> 8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210
|
||||
return x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Greatest common multiple (to round up)
|
||||
/// </summary>
|
||||
/// <param name="n">Number to round-up.</param>
|
||||
/// <param name="m">Multiple to round-up to.</param>
|
||||
/// <returns>Rounded up number.</returns>
|
||||
internal static int gcm(int n, int m)
|
||||
{
|
||||
return ((n + m - 1) / m) * m;
|
||||
}
|
||||
/// <summary>
|
||||
/// Next Largest Power of 2
|
||||
/// </summary>
|
||||
/// <param name="x">Input to round up to next 2^n</param>
|
||||
/// <returns>2^n > x && x > 2^(n-1) </returns>
|
||||
internal static int nlpo2(int x)
|
||||
{
|
||||
x--; // comment out to always take the next biggest power of two, even if x is already a power of two
|
||||
x |= (x >> 1);
|
||||
x |= (x >> 2);
|
||||
x |= (x >> 4);
|
||||
x |= (x >> 8);
|
||||
x |= (x >> 16);
|
||||
return (x+1);
|
||||
}
|
||||
|
||||
// Morton Translation
|
||||
/// <summary>
|
||||
/// Combines X/Y Coordinates to a decimal ordinate.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
internal static uint xy2d(uint x, uint y)
|
||||
{
|
||||
x &= 0x0000ffff;
|
||||
y &= 0x0000ffff;
|
||||
x |= (x << 8);
|
||||
y |= (y << 8);
|
||||
x &= 0x00ff00ff;
|
||||
y &= 0x00ff00ff;
|
||||
x |= (x << 4);
|
||||
y |= (y << 4);
|
||||
x &= 0x0f0f0f0f;
|
||||
y &= 0x0f0f0f0f;
|
||||
x |= (x << 2);
|
||||
y |= (y << 2);
|
||||
x &= 0x33333333;
|
||||
y &= 0x33333333;
|
||||
x |= (x << 1);
|
||||
y |= (y << 1);
|
||||
x &= 0x55555555;
|
||||
y &= 0x55555555;
|
||||
return x | (y << 1);
|
||||
}
|
||||
/// <summary>
|
||||
/// Decimal Ordinate In to X / Y Coordinate Out
|
||||
/// </summary>
|
||||
/// <param name="d">Loop integer which will be decoded to X/Y</param>
|
||||
/// <param name="x">Output X coordinate</param>
|
||||
/// <param name="y">Output Y coordinate</param>
|
||||
internal static void d2xy(uint d, out uint x, out uint y)
|
||||
{
|
||||
x = d;
|
||||
y = (x >> 1);
|
||||
x &= 0x55555555;
|
||||
y &= 0x55555555;
|
||||
x |= (x >> 1);
|
||||
y |= (y >> 1);
|
||||
x &= 0x33333333;
|
||||
y &= 0x33333333;
|
||||
x |= (x >> 2);
|
||||
y |= (y >> 2);
|
||||
x &= 0x0f0f0f0f;
|
||||
y &= 0x0f0f0f0f;
|
||||
x |= (x >> 4);
|
||||
y |= (y >> 4);
|
||||
x &= 0x00ff00ff;
|
||||
y &= 0x00ff00ff;
|
||||
x |= (x >> 8);
|
||||
y |= (y >> 8);
|
||||
x &= 0x0000ffff;
|
||||
y &= 0x0000ffff;
|
||||
}
|
||||
|
||||
public static CLIM analyze(string path)
|
||||
{
|
||||
CLIM bclim = new CLIM
|
||||
{
|
||||
FileName = Path.GetFileNameWithoutExtension(path),
|
||||
FilePath = Path.GetDirectoryName(path),
|
||||
Extension = Path.GetExtension(path)
|
||||
};
|
||||
byte[] byteArray = File.ReadAllBytes(path);
|
||||
using (BinaryReader br = new BinaryReader(new MemoryStream(byteArray)))
|
||||
{
|
||||
br.BaseStream.Seek(br.BaseStream.Length - 0x28, SeekOrigin.Begin);
|
||||
bclim.Magic = br.ReadUInt32();
|
||||
|
||||
bclim.BOM = br.ReadUInt16();
|
||||
bclim.CLIMLength = br.ReadUInt32();
|
||||
bclim.TileWidth = 2 << br.ReadByte();
|
||||
bclim.TileHeight = 2 << br.ReadByte();
|
||||
bclim.totalLength = br.ReadUInt32();
|
||||
bclim.Count = br.ReadUInt32();
|
||||
|
||||
bclim.imag = br.ReadChars(4);
|
||||
bclim.imagLength = br.ReadUInt32();
|
||||
bclim.Width = br.ReadUInt16();
|
||||
bclim.Height = br.ReadUInt16();
|
||||
bclim.FileFormat = br.ReadInt32();
|
||||
bclim.dataLength = br.ReadUInt32();
|
||||
|
||||
bclim.BaseSize = Math.Max(nlpo2(bclim.Width), nlpo2(bclim.Height));
|
||||
|
||||
br.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||
bclim.Data = br.ReadBytes((int)bclim.dataLength);
|
||||
|
||||
return bclim;
|
||||
}
|
||||
}
|
||||
public struct CLIM
|
||||
{
|
||||
public UInt32 Magic; // CLIM = 0x4D494C43
|
||||
public UInt16 BOM; // 0xFFFE
|
||||
public UInt32 CLIMLength; // HeaderLength - 14
|
||||
public int TileWidth; // 1<<[[n]]
|
||||
public int TileHeight; // 1<<[[n]]
|
||||
public UInt32 totalLength; // Total Length of file
|
||||
public UInt32 Count; // "1" , guessing it's just Count.
|
||||
|
||||
public char[] imag; // imag = 0x67616D69
|
||||
public UInt32 imagLength; // HeaderLength - 10
|
||||
public UInt16 Width; // Final Dimensions
|
||||
public UInt16 Height; // Final Dimensions
|
||||
public Int32 FileFormat; // ??
|
||||
public UInt32 dataLength; // Pixel Data Region Length
|
||||
|
||||
public byte[] Data;
|
||||
|
||||
public Int32 BaseSize;
|
||||
|
||||
//// Contained Data
|
||||
//public int ColorFormat;
|
||||
//public int ColorCount;
|
||||
//public Color[] Colors;
|
||||
|
||||
//public int[] Pixels;
|
||||
|
||||
public string FileName;
|
||||
public string FilePath;
|
||||
public string Extension;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BLZ
|
||||
namespace CTR
|
||||
{
|
||||
public class BLZCoder
|
||||
{
|
||||
|
||||
6
pk3DS/3DS/ETC1.cs
Normal file
6
pk3DS/3DS/ETC1.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CTR
|
||||
{
|
||||
class ETC1
|
||||
{
|
||||
}
|
||||
}
|
||||
503
pk3DS/3DS/GARC.cs
Normal file
503
pk3DS/3DS/GARC.cs
Normal file
@@ -0,0 +1,503 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Media;
|
||||
using System.Windows.Forms;
|
||||
using pk3DS;
|
||||
|
||||
namespace CTR
|
||||
{
|
||||
#region GARC Class & Struct
|
||||
public class GARC
|
||||
{
|
||||
internal static bool garcPackMS(string folderPath, string garcPath, ProgressBar pBar1 = null, Label label = null, bool supress = false)
|
||||
{
|
||||
// Check to see if our input folder exists.
|
||||
if (!new DirectoryInfo(folderPath).Exists) { Util.Error("Folder does not exist."); return false; }
|
||||
|
||||
// Okay some basic proofing is done. Proceed.
|
||||
int filectr = 0;
|
||||
// Get the paths of the files to pack up.
|
||||
string[] files = Directory.GetFiles(folderPath);
|
||||
string[] folders = Directory.GetDirectories(folderPath, "*.*", SearchOption.TopDirectoryOnly);
|
||||
|
||||
string[] packOrder = new string[files.Length + folders.Length];
|
||||
#region Reassemble a list of filenames.
|
||||
try
|
||||
{
|
||||
foreach (string f in files)
|
||||
{
|
||||
string fn = Path.GetFileNameWithoutExtension(f);
|
||||
int compressed = fn.IndexOf("dec_", StringComparison.Ordinal);
|
||||
int fileNumber = (compressed < 0)
|
||||
? Int32.Parse(fn)
|
||||
: Int32.Parse(fn.Substring(compressed + 4));
|
||||
|
||||
packOrder[fileNumber] = f;
|
||||
filectr++;
|
||||
}
|
||||
foreach (string f in folders)
|
||||
{
|
||||
packOrder[Int32.Parse(new DirectoryInfo(f).Name)] = f;
|
||||
filectr += Directory.GetFiles(f).Length;
|
||||
}
|
||||
}
|
||||
catch (Exception e) { Util.Error("Invalid packing filenames", e.ToString()); return false; }
|
||||
#endregion
|
||||
|
||||
#region Initialize Progress Update Display
|
||||
if (pBar1 == null) pBar1 = new ProgressBar();
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filectr; });
|
||||
else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filectr; }
|
||||
if (label == null) label = new Label();
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = true; });
|
||||
#endregion
|
||||
|
||||
// Set Up the GARC template.
|
||||
GARCFile garc = new GARCFile
|
||||
{
|
||||
fato =
|
||||
{
|
||||
// Magic = new[] { 'O', 'T', 'A', 'F' },
|
||||
Entries = new FATO_Entry[packOrder.Length],
|
||||
EntryCount = (ushort)packOrder.Length,
|
||||
HeaderSize = 0xC + packOrder.Length * 4,
|
||||
Padding = 0xFFFF
|
||||
},
|
||||
fatb =
|
||||
{
|
||||
// Magic = new[] { 'B', 'T', 'A', 'F' },
|
||||
Entries = new FATB_Entry[packOrder.Length],
|
||||
FileCount = filectr
|
||||
}
|
||||
};
|
||||
|
||||
#region Start Reassembling the FAT* tables.
|
||||
{
|
||||
int op = 0;
|
||||
int od = 0;
|
||||
int v = 0;
|
||||
int index = 0;
|
||||
for (int i = 0; i < garc.fatb.Entries.Length; i++)
|
||||
{
|
||||
garc.fato.Entries[i].Offset = op; // FATO offset
|
||||
garc.fatb.Entries[i].SubEntries = new FATB_SubEntry[32];
|
||||
op += 4; // Vector
|
||||
if (!Directory.Exists(packOrder[i])) // is not folder
|
||||
{
|
||||
garc.fatb.Entries[i].IsFolder = false;
|
||||
garc.fatb.Entries[i].SubEntries[0].Exists = true;
|
||||
|
||||
string fn = Path.GetFileNameWithoutExtension(packOrder[i]);
|
||||
int compressed = fn.IndexOf("dec_", StringComparison.Ordinal);
|
||||
int fileNumber = (compressed < 0)
|
||||
? Int32.Parse(fn)
|
||||
: Int32.Parse(fn.Substring(compressed + 4));
|
||||
|
||||
if (compressed >= 0)
|
||||
{
|
||||
string old = packOrder[i];
|
||||
LZSS.Compress(packOrder[i], packOrder[i] = Path.Combine(Path.GetDirectoryName(packOrder[i]), fileNumber.ToString()));
|
||||
File.Delete(old);
|
||||
}
|
||||
|
||||
// Assemble Vector
|
||||
v = 1;
|
||||
|
||||
// Assemble Entry
|
||||
FileInfo fi = new FileInfo(packOrder[i]);
|
||||
int actualLength = (int)((fi.Length % 4 == 0) ? fi.Length : fi.Length + 4 - (fi.Length % 4));
|
||||
garc.fatb.Entries[i].SubEntries[0].Start = od;
|
||||
garc.fatb.Entries[i].SubEntries[0].End = actualLength + garc.fatb.Entries[i].SubEntries[0].Start;
|
||||
garc.fatb.Entries[i].SubEntries[0].Length = (int)fi.Length;
|
||||
od += actualLength;
|
||||
|
||||
op += 12;
|
||||
#region Step
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)(() => pBar1.PerformStep()));
|
||||
else { pBar1.PerformStep(); }
|
||||
string update = String.Format("{0:P2} - {1}/{2} - {3}", ((float)index) / ((float)filectr), index, filectr, packOrder[i]);
|
||||
index++;
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Text = update; });
|
||||
else { label.Text = update; }
|
||||
#endregion
|
||||
}
|
||||
else
|
||||
{
|
||||
garc.fatb.Entries[i].IsFolder = true;
|
||||
string[] subFiles = Directory.GetFiles(packOrder[i]);
|
||||
foreach (string f in subFiles)
|
||||
{
|
||||
string s = f;
|
||||
string fn = Path.GetFileNameWithoutExtension(f);
|
||||
int compressed = fn.IndexOf("dec_", StringComparison.Ordinal);
|
||||
int fileNumber = (compressed < 0)
|
||||
? Int32.Parse(fn)
|
||||
: Int32.Parse(fn.Substring(compressed + 4));
|
||||
garc.fatb.Entries[i].SubEntries[fileNumber].Exists = true;
|
||||
|
||||
if (compressed >= 0)
|
||||
{
|
||||
LZSS.Compress(f, s = Path.Combine(Path.GetDirectoryName(f), fileNumber.ToString()));
|
||||
File.Delete(f);
|
||||
}
|
||||
|
||||
// Assemble Vector
|
||||
v |= 1 << fileNumber;
|
||||
|
||||
// Assemble Entry
|
||||
FileInfo fi = new FileInfo(s);
|
||||
int actualLength = (int)((fi.Length % 4 == 0) ? fi.Length : fi.Length + 4 - (fi.Length % 4));
|
||||
garc.fatb.Entries[i].SubEntries[fileNumber].Start = od;
|
||||
garc.fatb.Entries[i].SubEntries[fileNumber].End = actualLength + garc.fatb.Entries[i].SubEntries[fileNumber].Start;
|
||||
garc.fatb.Entries[i].SubEntries[fileNumber].Length = (int)fi.Length;
|
||||
od += actualLength;
|
||||
|
||||
op += 12;
|
||||
#region Step
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)(() => pBar1.PerformStep()));
|
||||
else { pBar1.PerformStep(); }
|
||||
string update = String.Format("{0:P2} - {1}/{2} - {3}", ((float)index) / ((float)filectr), index, filectr, f);
|
||||
index++;
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Text = update; });
|
||||
else { label.Text = update; }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
garc.fatb.Entries[i].Vector = (uint)v;
|
||||
}
|
||||
garc.fatb.HeaderSize = (0xC + op);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// Delete the old garc if it exists, then begin writing our new one
|
||||
try { File.Delete(garcPath); }
|
||||
catch { }
|
||||
|
||||
// Set up the Header Info
|
||||
using (MemoryStream newGARC = new MemoryStream())
|
||||
using (MemoryStream GARCdata = new MemoryStream())
|
||||
using (BinaryWriter gw = new BinaryWriter(newGARC))
|
||||
{
|
||||
#region Write GARC Headers
|
||||
// Write GARC
|
||||
gw.Write((uint)0x47415243); // GARC
|
||||
gw.Write((uint)0x0000001C); // Header Length
|
||||
gw.Write((ushort)0xFEFF); // Endianness BOM
|
||||
gw.Write((ushort)0x0400); // Const (4)
|
||||
gw.Write((uint)0x00000004); // Section Count (4)
|
||||
gw.Write((uint)0x00000000); // Data Offset (temp)
|
||||
gw.Write((uint)0x00000000); // File Length (temp)
|
||||
gw.Write((uint)0x00000000); // Largest File Size (temp)
|
||||
|
||||
// Write FATO
|
||||
gw.Write((uint)0x4641544F); // FATO
|
||||
gw.Write(garc.fato.HeaderSize); // Header Size
|
||||
gw.Write(garc.fato.EntryCount); // Entry Count
|
||||
gw.Write(garc.fato.Padding); // Padding
|
||||
for (int i = 0; i < garc.fato.Entries.Length; i++)
|
||||
gw.Write((uint)garc.fato.Entries[i].Offset);
|
||||
|
||||
// Write FATB
|
||||
gw.Write((uint)0x46415442); // FATB
|
||||
gw.Write(garc.fatb.HeaderSize); // Header Size
|
||||
gw.Write(garc.fatb.FileCount); // File Count
|
||||
foreach (var e in garc.fatb.Entries)
|
||||
{
|
||||
gw.Write(e.Vector);
|
||||
foreach (var s in e.SubEntries.Where(s => s.Exists))
|
||||
{ gw.Write((uint)s.Start); gw.Write((uint)s.End); gw.Write((uint)s.Length); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Write Files
|
||||
long largestSize = 0; // Required memory to allocate to handle the largest file
|
||||
foreach (string e in packOrder)
|
||||
{
|
||||
string[] fa = Directory.Exists(e) ? Directory.GetFiles(e) : new[] { e };
|
||||
foreach (string f in fa)
|
||||
{
|
||||
// Update largest file length if necessary
|
||||
long len = new FileInfo(f).Length;
|
||||
if (len > largestSize) largestSize = len;
|
||||
|
||||
// Write to FIMB
|
||||
using (var input = File.OpenRead(f))
|
||||
input.CopyTo(GARCdata);
|
||||
|
||||
// While length is not divisible by 4, pad with FF (unused byte)
|
||||
while (GARCdata.Length % 4 > 0) GARCdata.WriteByte(0xFF);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
gw.Write((uint)0x46494D42); // FIMB
|
||||
gw.Write((uint)0x0000000C); // Header Length
|
||||
gw.Write((uint)GARCdata.Length); // Data Length
|
||||
|
||||
gw.Seek(0x10, SeekOrigin.Begin); // Goto the start of the un-set 0 data we set earlier and set it.
|
||||
gw.Write((uint)newGARC.Length); // Write Data Offset
|
||||
gw.Write((uint)(newGARC.Length + GARCdata.Length)); // Write total GARC Length
|
||||
gw.Write((uint)largestSize); // Write Largest File stat (?)
|
||||
|
||||
newGARC.Seek(0, SeekOrigin.End); // Goto the end so we can copy the filedata after the GARC headers.
|
||||
|
||||
// Write in the data
|
||||
GARCdata.Position = 0;
|
||||
GARCdata.CopyTo(newGARC); // Copy the data.
|
||||
// New File is ready to be saved (memstream newGARC)
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(garcPath, newGARC.ToArray());
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = false; });
|
||||
else { label.Visible = false; }
|
||||
|
||||
// We're done.
|
||||
SystemSounds.Exclamation.Play();
|
||||
if (!supress) Util.Alert("Pack Successful!", filectr + " files packed to the GARC!");
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) { Util.Error("Packing Failed!", e.ToString()); return false; }
|
||||
}
|
||||
}
|
||||
internal static bool garcUnpack(string garcPath, string outPath, bool skipDecompression, ProgressBar pBar1 = null, Label label = null, bool supress = false, bool bypassExt = false)
|
||||
{
|
||||
if (!File.Exists(garcPath) && !supress) { Util.Alert("File does not exist"); return false; }
|
||||
|
||||
// Unpack the GARC
|
||||
GARCFile garc = unpackGARC(garcPath);
|
||||
const string ext = "bin"; // Default Extension Name
|
||||
int fileCount = garc.fatb.FileCount;
|
||||
string format = "D" + Math.Ceiling(Math.Log10(fileCount));
|
||||
|
||||
#region Display
|
||||
// Initialize ProgressBar
|
||||
if (pBar1 == null) pBar1 = new ProgressBar();
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = fileCount; });
|
||||
else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = garc.fatb.FileCount; }
|
||||
|
||||
if (label == null) label = new Label();
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = true; });
|
||||
#endregion
|
||||
|
||||
using (BinaryReader br = new BinaryReader(File.OpenRead(garcPath)))
|
||||
{
|
||||
// Create Extraction folder if it does not exist.
|
||||
if (!Directory.Exists(outPath))
|
||||
Directory.CreateDirectory(outPath);
|
||||
|
||||
int filectr = 0;
|
||||
// Pull out all the files
|
||||
for (int o = 0; o < garc.fato.EntryCount; o++)
|
||||
{
|
||||
|
||||
var Entry = garc.fatb.Entries[o];
|
||||
// Set Entry File Name
|
||||
string fileName = o.ToString(format);
|
||||
|
||||
#region OutDirectory Determination
|
||||
string parentFolder = Entry.IsFolder ? Path.Combine(outPath, fileName) : outPath;
|
||||
if (Entry.IsFolder) // Process Folder
|
||||
Directory.CreateDirectory(parentFolder);
|
||||
#endregion
|
||||
|
||||
uint vector = Entry.Vector;
|
||||
for (int i = 0; i < 32; i++) // For each bit in vector
|
||||
{
|
||||
var SubEntry = Entry.SubEntries[i];
|
||||
if (!SubEntry.Exists) continue;
|
||||
|
||||
// Seek to Offset
|
||||
br.BaseStream.Position = SubEntry.Start + garc.DataOffset;
|
||||
|
||||
// Check if Compressed
|
||||
bool compressed = false;
|
||||
if (!skipDecompression)
|
||||
try { compressed = (byte)br.PeekChar() == 0x11; }
|
||||
catch { }
|
||||
|
||||
// Write File
|
||||
string fileOut = Path.Combine(parentFolder, (Entry.IsFolder ? i.ToString("00") : fileName) + "." + ext);
|
||||
using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(fileOut)))
|
||||
{
|
||||
// Write out the data for the file
|
||||
br.BaseStream.Position = SubEntry.Start + garc.DataOffset;
|
||||
bw.Write(br.ReadBytes(SubEntry.Length));
|
||||
filectr++;
|
||||
}
|
||||
if (compressed)
|
||||
#region Decompression
|
||||
{
|
||||
string decout = Path.Combine(Path.GetDirectoryName(fileOut), "dec_" + Path.GetFileName(fileOut));
|
||||
try
|
||||
{
|
||||
LZSS.Decompress(fileOut, decout);
|
||||
try { File.Delete(fileOut); }
|
||||
catch (Exception e) { Util.Error("A compressed file could not be deleted.", fileOut, e.ToString()); }
|
||||
}
|
||||
catch
|
||||
{
|
||||
// File is really not encrypted.
|
||||
try { File.Delete(decout); }
|
||||
catch (Exception e) { Util.Error("This shouldn't happen", e.ToString()); }
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Step
|
||||
if (pBar1.InvokeRequired) pBar1.Invoke((MethodInvoker)(() => pBar1.PerformStep()));
|
||||
else pBar1.PerformStep();
|
||||
|
||||
string update = String.Format("{0:P2} - {1}/{2}", (filectr) / (fileCount), filectr, fileCount);
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Text = update; });
|
||||
else { label.Text = update; }
|
||||
#endregion
|
||||
|
||||
if ((vector >>= 1) == 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#region Updates
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = false; });
|
||||
else { label.Visible = false; }
|
||||
SystemSounds.Exclamation.Play();
|
||||
if (!supress) Util.Alert("Unpack Successful!", fileCount + " files unpacked from the GARC!");
|
||||
#endregion
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static GARCFile unpackGARC(string path)
|
||||
{
|
||||
GARCFile garc = new GARCFile();
|
||||
using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
|
||||
{
|
||||
// GARC Header
|
||||
garc.Magic = br.ReadChars(4);
|
||||
garc.HeaderSize = br.ReadUInt32();
|
||||
garc.Endianess = br.ReadUInt16();
|
||||
garc.ChunkCount = br.ReadUInt16();
|
||||
garc.FileSize = br.ReadUInt32();
|
||||
|
||||
garc.DataOffset = br.ReadUInt32();
|
||||
garc.FileSize = br.ReadUInt32();
|
||||
garc.LastSize = br.ReadUInt32();
|
||||
|
||||
// FATO (File Allocation Table Offsets)
|
||||
garc.fato.Magic = br.ReadChars(4);
|
||||
garc.fato.HeaderSize = br.ReadInt32();
|
||||
garc.fato.EntryCount = br.ReadUInt16();
|
||||
garc.fato.Padding = br.ReadUInt16();
|
||||
|
||||
garc.fato.Entries = new FATO_Entry[garc.fato.EntryCount];
|
||||
for (int i = 0; i < garc.fato.EntryCount; i++)
|
||||
garc.fato.Entries[i].Offset = br.ReadInt32();
|
||||
|
||||
// FATB (File Allocation Table Bits)
|
||||
garc.fatb.Magic = br.ReadChars(4);
|
||||
garc.fatb.HeaderSize = br.ReadInt32();
|
||||
garc.fatb.FileCount = br.ReadInt32();
|
||||
|
||||
garc.fatb.Entries = new FATB_Entry[garc.fato.EntryCount];
|
||||
for (int i = 0; i < garc.fato.EntryCount; i++) // Loop through all FATO entries
|
||||
{
|
||||
garc.fatb.Entries[i].Vector = br.ReadUInt32();
|
||||
garc.fatb.Entries[i].SubEntries = new FATB_SubEntry[32];
|
||||
uint bitvector = garc.fatb.Entries[i].Vector;
|
||||
int ctr = 0;
|
||||
for (int b = 0; b < 32; b++)
|
||||
{
|
||||
garc.fatb.Entries[i].SubEntries[b].Exists = (bitvector & 1) == 1;
|
||||
bitvector >>= 1;
|
||||
if (!garc.fatb.Entries[i].SubEntries[b].Exists) continue;
|
||||
garc.fatb.Entries[i].SubEntries[b].Start = br.ReadInt32();
|
||||
garc.fatb.Entries[i].SubEntries[b].End = br.ReadInt32();
|
||||
garc.fatb.Entries[i].SubEntries[b].Length = br.ReadInt32();
|
||||
ctr++;
|
||||
}
|
||||
garc.fatb.Entries[i].IsFolder = ctr > 1;
|
||||
}
|
||||
|
||||
// FIMB (File IMage Bytes)
|
||||
garc.fimg.Magic = br.ReadChars(4);
|
||||
garc.fimg.HeaderSize = br.ReadInt32();
|
||||
garc.fimg.DataSize = br.ReadInt32();
|
||||
|
||||
// Files data
|
||||
// Oftentimes too large to toss into a byte array. Fetch as needed with a BinaryReader.
|
||||
}
|
||||
return garc;
|
||||
}
|
||||
|
||||
public struct GARCFile
|
||||
{
|
||||
public Char[] Magic; // Always GARC = 0x4E415243
|
||||
public UInt32 HeaderSize; // Always 0x001C
|
||||
public UInt16 Endianess; // 0xFFFE
|
||||
public UInt16 ChunkCount; // Always 0x0400 chunk count
|
||||
|
||||
public UInt32 DataOffset;
|
||||
public UInt32 FileSize;
|
||||
public UInt32 LastSize;
|
||||
|
||||
public FATO fato;
|
||||
public FATB fatb;
|
||||
public FIMG fimg;
|
||||
}
|
||||
|
||||
public struct FATO
|
||||
{
|
||||
public Char[] Magic;
|
||||
public Int32 HeaderSize;
|
||||
public UInt16 EntryCount;
|
||||
public UInt16 Padding;
|
||||
|
||||
public FATO_Entry[] Entries;
|
||||
}
|
||||
public struct FATO_Entry
|
||||
{
|
||||
public Int32 Offset;
|
||||
}
|
||||
|
||||
public struct FATB
|
||||
{
|
||||
public Char[] Magic;
|
||||
public Int32 HeaderSize;
|
||||
public Int32 FileCount;
|
||||
|
||||
public FATB_Entry[] Entries;
|
||||
}
|
||||
public struct FATB_Entry
|
||||
{
|
||||
public UInt32 Vector;
|
||||
public Boolean IsFolder;
|
||||
public FATB_SubEntry[] SubEntries;
|
||||
}
|
||||
public struct FATB_SubEntry
|
||||
{
|
||||
public Boolean Exists;
|
||||
public Int32 Start;
|
||||
public Int32 End;
|
||||
public Int32 Length;
|
||||
}
|
||||
|
||||
public struct FIMG
|
||||
{
|
||||
public Char[] Magic;
|
||||
public Int32 HeaderSize;
|
||||
public Int32 DataSize;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -1,397 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Media;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace pk3DS
|
||||
namespace CTR
|
||||
{
|
||||
public class GARCTool
|
||||
// LZSS (de)compression, heavily taken from dsdecmp
|
||||
public class LZSS
|
||||
{
|
||||
// Note: This has been customized from the original GARCTool source to not use temporary files (instead uses MemoryStream).
|
||||
public static bool garcPackMS(string folderPath, string garcPath, ProgressBar pBar1 = null, Label label = null, bool supress = false)
|
||||
{
|
||||
// Check to see if our input folder exists.
|
||||
if (!new DirectoryInfo(folderPath).Exists) { Util.Error("Folder does not exist."); return false; }
|
||||
|
||||
// Okay some basic proofing is done. Proceed.
|
||||
|
||||
// Get the paths of the files to pack up.
|
||||
string[] filepaths = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly);
|
||||
|
||||
// Initialize ProgressBar
|
||||
if (pBar1 == null) pBar1 = new ProgressBar();
|
||||
if (label == null) label = new Label();
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filepaths.Length; });
|
||||
else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = filepaths.Length; }
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = true; });
|
||||
else { label.Visible = true; }
|
||||
|
||||
// Copy the files to the working directory so our compression doesn't overwrite anything.
|
||||
|
||||
|
||||
// Scan through to see if we have to compress anything.
|
||||
// Delete the old garc if it exists, then write our new one
|
||||
try { File.Delete(garcPath); } catch { }
|
||||
|
||||
// Set up the Header Info
|
||||
using (var newGARC = new MemoryStream())
|
||||
using (BinaryWriter gw = new BinaryWriter(newGARC))
|
||||
{
|
||||
|
||||
// Write GARC header
|
||||
gw.Write((uint)0x47415243); // Write "CRAG"
|
||||
gw.Write((uint)0x0000001C); // Header Length
|
||||
gw.Write((ushort)0xFEFF); // FEFF BOM
|
||||
gw.Write((ushort)0x0400); //
|
||||
gw.Write((uint)0x00000004); //
|
||||
gw.Write((uint)0x00000000); // Data Offset
|
||||
gw.Write((uint)0x00000000); // File Length
|
||||
gw.Write((uint)0x00000000); // FATB chunk last word
|
||||
|
||||
// Write OTAF
|
||||
gw.Write((uint)0x4641544F); // OTAF
|
||||
gw.Write((uint)(0xC + 4 * filepaths.Length)); // Section Size
|
||||
gw.Write((ushort)filepaths.Length); // Count: n
|
||||
gw.Write((ushort)0xFFFF); // padding? dunno
|
||||
|
||||
// write BTAF jump offsets
|
||||
for (int i = 0; i < filepaths.Length; i++)
|
||||
gw.Write((uint)i * 0x10);
|
||||
|
||||
// Start BTAF
|
||||
gw.Write((uint)0x46415442); // BTAF
|
||||
gw.Write((uint)(0xC + 0x10 * filepaths.Length)); // Chunk Size
|
||||
gw.Write((uint)filepaths.Length);
|
||||
|
||||
uint offset = 0;
|
||||
uint largest = 0;
|
||||
|
||||
// Sort out the files to get the actual order
|
||||
int[] fp = new int[filepaths.Length];
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < fp.Length; i++)
|
||||
{
|
||||
string fn = Path.GetFileNameWithoutExtension(filepaths[i]);
|
||||
int compressed = fn.IndexOf("dec_", StringComparison.Ordinal);
|
||||
if (compressed < 0)
|
||||
fp[Int32.Parse(fn)] = i;
|
||||
else
|
||||
fp[Int32.Parse(fn.Substring(compressed + 4))] = i;
|
||||
}
|
||||
}
|
||||
catch (Exception e) { Util.Error("Invalid packing filenames", e.ToString()); return false; }
|
||||
|
||||
// Assemble the GARCData.
|
||||
using (var GARCdata = new MemoryStream())
|
||||
{
|
||||
for (int i = 0; i < filepaths.Length; i++)
|
||||
{
|
||||
string name = filepaths[fp[i]];
|
||||
try
|
||||
{
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Text = String.Format("{0:P2} - {1}/{2} - {3}", ((float)i) / ((float)filepaths.Length), i, filepaths.Length, Path.GetFileName(name)); });
|
||||
else { label.Text = String.Format("{0:P2} - {1}/{2} - {3}", ((float)i) / ((float)filepaths.Length), i, filepaths.Length, Path.GetFileName(name)); }
|
||||
}
|
||||
catch { }
|
||||
int compressed = Path.GetFileName(name).IndexOf("dec_", StringComparison.Ordinal);
|
||||
if (compressed > -1)
|
||||
{
|
||||
// File needs to be compressed and replaced.
|
||||
string compressedName = Path.Combine(Path.GetDirectoryName(name), Path.GetFileNameWithoutExtension(name).Substring(compressed + 4) + ".bin");
|
||||
|
||||
try { dsdecmp.Compress(name, compressedName); }
|
||||
catch { throw new Exception("Compression failed"); }
|
||||
|
||||
// Replace file name with compressed name so we can delete after packing.
|
||||
name = compressedName;
|
||||
}
|
||||
FileInfo fi = new FileInfo(name);
|
||||
using (var input = File.OpenRead(name))
|
||||
{
|
||||
gw.Write((uint)1); // garc.btaf.entries[i].bits = br.ReadUInt32();
|
||||
gw.Write((uint)offset); // Start/Begin Offset
|
||||
uint round = (uint)Math.Ceiling(((double)fi.Length / 4)) * 4;
|
||||
offset += (uint)(round); // Round up Offset.
|
||||
gw.Write((uint)offset); // End/Stop Offset
|
||||
gw.Write((uint)fi.Length); // Length/Size
|
||||
|
||||
if (fi.Length > largest) largest = (uint)fi.Length;
|
||||
|
||||
// Write the data to the BMIF data section
|
||||
input.CopyTo(GARCdata); // then pad with FF's if not /4
|
||||
while (GARCdata.Length % 4 > 0) GARCdata.WriteByte(0xFF);
|
||||
}
|
||||
|
||||
// Delete file if it is compressed.
|
||||
if (compressed > -1) File.Delete(name);
|
||||
|
||||
// Advance the ProgressBar.
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)delegate { pBar1.PerformStep(); });
|
||||
else { pBar1.PerformStep(); }
|
||||
}
|
||||
|
||||
gw.Write((uint)0x46494D42);
|
||||
gw.Write((uint)0x0000000C);
|
||||
gw.Write((uint)offset);
|
||||
|
||||
gw.Seek(0x10, SeekOrigin.Begin); // Goto the start of the un-set 0 data we set earlier and set it.
|
||||
gw.Write((uint)newGARC.Length); // Write Data Offset
|
||||
gw.Write((uint)(newGARC.Length + GARCdata.Length)); // Write total GARC Length
|
||||
gw.Write((uint)largest); // Write Largest File stat (?)
|
||||
|
||||
newGARC.Seek(0, SeekOrigin.End); // Goto the end so we can copy the filedata after the GARC headers.
|
||||
|
||||
// Write in the data
|
||||
GARCdata.Position = 0;
|
||||
GARCdata.CopyTo(newGARC); // Copy the data.
|
||||
}
|
||||
// New File is ready to be saved (memstream newGARC)
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(garcPath, newGARC.ToArray());
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = false; });
|
||||
else { label.Visible = false; }
|
||||
|
||||
// We're done.
|
||||
SystemSounds.Exclamation.Play();
|
||||
if (!supress) Util.Alert("Pack Successful!", filepaths.Length + " files packed to the GARC!");
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) { Util.Error("Packing Failed!", e.ToString()); return false; }
|
||||
}
|
||||
}
|
||||
public static bool garcUnpack(string garcPath, string outPath, bool skipDecompression, ProgressBar pBar1 = null, Label label = null, bool supress = false, bool bypassExt = false)
|
||||
{
|
||||
if (!File.Exists(garcPath) && !supress) { Util.Alert("File does not exist"); return false; }
|
||||
|
||||
// Unpack the GARC
|
||||
GARC garc = ARC.unpackGARC(garcPath);
|
||||
|
||||
// Initialize ProgressBar
|
||||
if (pBar1 == null) pBar1 = new ProgressBar();
|
||||
if (label == null) label = new Label();
|
||||
|
||||
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)delegate { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = garc.otaf.nFiles; });
|
||||
else { pBar1.Minimum = 0; pBar1.Step = 1; pBar1.Value = 0; pBar1.Maximum = garc.otaf.nFiles; }
|
||||
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = true; });
|
||||
else { label.Visible = true; }
|
||||
|
||||
using (BinaryReader br = new BinaryReader(File.OpenRead(garcPath)))
|
||||
{
|
||||
// Create Extraction folder if it does not exist.
|
||||
if (!Directory.Exists(outPath))
|
||||
Directory.CreateDirectory(outPath);
|
||||
|
||||
// Pull out all the files
|
||||
for (int o = 0; o < garc.otaf.nFiles; o++)
|
||||
{
|
||||
for (int i = 0; i < garc.btaf.nFiles; i++)
|
||||
{
|
||||
string ext = "bin";
|
||||
bool compressed = false;
|
||||
|
||||
br.BaseStream.Position = garc.btaf.entries[i].start_offset + garc.data_offset;
|
||||
byte lzss = 0;
|
||||
if (!skipDecompression)
|
||||
{
|
||||
try
|
||||
{ lzss = (byte)br.PeekChar(); }
|
||||
catch { lzss = 0; }
|
||||
}
|
||||
if (lzss == 0x11)
|
||||
compressed = true;
|
||||
else
|
||||
{
|
||||
ext = "bin";
|
||||
br.BaseStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Set File Name
|
||||
string filename = o.ToString("D" + Math.Ceiling(Math.Log10(garc.otaf.nFiles)));
|
||||
if (garc.btaf.nFiles == garc.otaf.nFiles) filename = i.ToString("D" + Math.Ceiling(Math.Log10(garc.otaf.nFiles)));
|
||||
else if (garc.btaf.nFiles > 1 && garc.btaf.nFiles != garc.otaf.nFiles) filename += "." + i;
|
||||
string fileout = Path.Combine(outPath, filename + "." + ext);
|
||||
using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(fileout)))
|
||||
{
|
||||
// Write out the data for the file
|
||||
br.BaseStream.Position = garc.btaf.entries[i].start_offset + garc.data_offset;
|
||||
for (int x = 0; x < garc.btaf.entries[i].length; x++)
|
||||
bw.Write(br.ReadByte());
|
||||
}
|
||||
// See if decompression should be attempted.
|
||||
#region Decompression
|
||||
if (compressed && !skipDecompression)
|
||||
{
|
||||
string decout = Path.Combine(outPath, "dec_" + filename + ".bin");
|
||||
try
|
||||
{
|
||||
long n = dsdecmp.Decompress(fileout, decout);
|
||||
try { File.Delete(fileout); }
|
||||
catch (Exception e) { Util.Error("A compressed file could not be deleted.", fileout, e.ToString()); }
|
||||
|
||||
// Try to detect for extension now
|
||||
ext = "bin";
|
||||
|
||||
File.Move(decout, Path.Combine(outPath, "dec_" + filename + "." + ext));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// File is really not encrypted.
|
||||
try { File.Delete(decout); }
|
||||
catch (Exception e) { Util.Error("This shouldn't happen", e.ToString()); }
|
||||
}
|
||||
}
|
||||
if (pBar1.InvokeRequired)
|
||||
pBar1.Invoke((MethodInvoker)delegate { pBar1.PerformStep(); });
|
||||
else
|
||||
pBar1.PerformStep();
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Text = String.Format("{0:P2} - {1}/{2}", ((float)i) / garc.otaf.nFiles, i, garc.otaf.nFiles); });
|
||||
else { label.Text = String.Format("{0:P2} - {1}/{2}", ((float)i) / garc.otaf.nFiles, i, garc.otaf.nFiles); }
|
||||
#endregion
|
||||
}
|
||||
if (garc.otaf.nFiles == garc.btaf.nFiles) break;
|
||||
}
|
||||
}
|
||||
if (label.InvokeRequired)
|
||||
label.Invoke((MethodInvoker)delegate { label.Visible = false; });
|
||||
else { label.Visible = false; }
|
||||
SystemSounds.Exclamation.Play();
|
||||
if (!supress) Util.Alert("Unpack Successful!", garc.otaf.nFiles + " files unpacked from the GARC!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-classes
|
||||
#region GARC Class & Struct
|
||||
public partial class ARC
|
||||
{
|
||||
public static GARC unpackGARC(string path)
|
||||
{
|
||||
GARC garc = new GARC();
|
||||
BinaryReader br = new BinaryReader(File.OpenRead(path));
|
||||
|
||||
// GARC Header
|
||||
garc.id = br.ReadChars(4);
|
||||
garc.header_size = br.ReadUInt32();
|
||||
garc.id_endian = br.ReadUInt16();
|
||||
if (garc.id_endian == 0xFEFF)
|
||||
Util.Reverse(garc.id);
|
||||
garc.constant = br.ReadUInt16();
|
||||
garc.file_size = br.ReadUInt32();
|
||||
|
||||
garc.data_offset = br.ReadUInt32();
|
||||
garc.file_length = br.ReadUInt32();
|
||||
garc.lastsize = br.ReadUInt32();
|
||||
|
||||
// OTAF
|
||||
garc.otaf.id = br.ReadChars(4);
|
||||
garc.otaf.section_size = br.ReadUInt32();
|
||||
garc.otaf.nFiles = br.ReadUInt16();
|
||||
garc.otaf.padding = br.ReadUInt16();
|
||||
|
||||
garc.otaf.entries = new OTAF_Entry[garc.otaf.nFiles];
|
||||
// not really needed; plus it's wrong
|
||||
for (int i = 0; i < garc.otaf.nFiles; i++)
|
||||
{
|
||||
uint val = br.ReadUInt32();
|
||||
if (garc.otaf.padding == 0xffff)
|
||||
val = Util.Reverse(val);
|
||||
|
||||
garc.otaf.entries[i].name = val.ToString();
|
||||
}
|
||||
|
||||
// BTAF (File Allocation TaBle)
|
||||
garc.btaf.id = br.ReadChars(4);
|
||||
garc.btaf.section_size = br.ReadUInt32();
|
||||
garc.btaf.nFiles = br.ReadUInt32();
|
||||
|
||||
garc.btaf.entries = new BTAF_Entry[garc.btaf.nFiles];
|
||||
garc.btaf.entries[0].bits = br.ReadUInt32();
|
||||
for (int i = 0; i < garc.btaf.nFiles; i++)
|
||||
{
|
||||
if (i != 0 && garc.btaf.nFiles == garc.otaf.nFiles)
|
||||
garc.btaf.entries[i].bits = br.ReadUInt32();
|
||||
garc.btaf.entries[i].start_offset = br.ReadUInt32();
|
||||
garc.btaf.entries[i].end_offset = br.ReadUInt32();
|
||||
garc.btaf.entries[i].length = br.ReadUInt32();
|
||||
}
|
||||
|
||||
// BMIF
|
||||
garc.gmif.id = br.ReadChars(4);
|
||||
garc.gmif.section_size = br.ReadUInt32();
|
||||
garc.gmif.data_size = br.ReadUInt32();
|
||||
|
||||
// Files data
|
||||
|
||||
br.Close();
|
||||
return garc;
|
||||
}
|
||||
}
|
||||
public struct GARC
|
||||
{
|
||||
public char[] id; // Always GARC = 0x4E415243
|
||||
public UInt32 header_size; // Always 0x001C
|
||||
public UInt16 id_endian; // 0xFFFE
|
||||
public UInt16 constant; // Always 0x0400 chunk count
|
||||
public UInt32 file_size;
|
||||
|
||||
public UInt32 data_offset;
|
||||
public UInt32 file_length;
|
||||
public UInt32 lastsize;
|
||||
|
||||
public OTAF otaf;
|
||||
public BTAF btaf;
|
||||
public GMIF gmif;
|
||||
}
|
||||
public struct OTAF
|
||||
{
|
||||
public char[] id;
|
||||
public UInt32 section_size;
|
||||
public UInt16 nFiles;
|
||||
public UInt16 padding;
|
||||
|
||||
public OTAF_Entry[] entries;
|
||||
}
|
||||
public struct OTAF_Entry
|
||||
{
|
||||
public string name;
|
||||
}
|
||||
public struct BTAF
|
||||
{
|
||||
public char[] id;
|
||||
public UInt32 section_size;
|
||||
public UInt32 nFiles;
|
||||
public BTAF_Entry[] entries;
|
||||
}
|
||||
public struct BTAF_Entry
|
||||
{
|
||||
public UInt32 bits;
|
||||
public UInt32 start_offset;
|
||||
public UInt32 end_offset;
|
||||
public UInt32 length;
|
||||
}
|
||||
public struct GMIF
|
||||
{
|
||||
public char[] id;
|
||||
public UInt32 section_size;
|
||||
public UInt32 data_size;
|
||||
}
|
||||
#endregion
|
||||
#region dsdecmp Classes
|
||||
public class dsdecmp // LZ11 (de)compression
|
||||
{
|
||||
public static long Decompress(string infile, string outfile)
|
||||
internal static long Decompress(string infile, string outfile)
|
||||
{
|
||||
// make sure the output directory exists
|
||||
string outDirectory = Path.GetDirectoryName(outfile);
|
||||
@@ -419,7 +34,7 @@ public static long Decompress(string infile, string outfile)
|
||||
/// <returns>The length of the output data.</returns>
|
||||
/// <exception cref="NotEnoughDataException">When the given length of the input data
|
||||
/// is not enough to properly decompress the input.</exception>
|
||||
public static long Decompress(Stream instream, long inLength, Stream outstream)
|
||||
internal static long Decompress(Stream instream, long inLength, Stream outstream)
|
||||
{
|
||||
#region Format definition in NDSTEK style
|
||||
/* Data header (32bit)
|
||||
@@ -518,7 +133,7 @@ Flag data (8bit)
|
||||
throw new StreamTooShortException();
|
||||
|
||||
int length = byte1 >> 4;
|
||||
int disp = -1;
|
||||
int disp;
|
||||
if (length == 0)
|
||||
{
|
||||
#region case 0; 0(B C)(D EF) + (0x11)(0x1) = (LEN)(DISP)
|
||||
@@ -628,7 +243,7 @@ Flag data (8bit)
|
||||
return decompressedSize;
|
||||
}
|
||||
|
||||
public static int Compress(string infile, string outfile)
|
||||
internal static int Compress(string infile, string outfile)
|
||||
{
|
||||
// make sure the output directory exists
|
||||
string outDirectory = Path.GetDirectoryName(outfile);
|
||||
@@ -647,7 +262,7 @@ public static int Compress(string infile, string outfile)
|
||||
/// This algorithm should yield files that are the same as those found in the games.
|
||||
/// (delegates to the optimized method if LookAhead is set)
|
||||
/// </summary>
|
||||
public unsafe static int Compress(Stream instream, long inLength, Stream outstream, bool original)
|
||||
internal unsafe static int Compress(Stream instream, long inLength, Stream outstream, bool original)
|
||||
{
|
||||
// make sure the decompressed size fits in 3 bytes.
|
||||
// There should be room for four bytes, however I'm not 100% sure if that can be used
|
||||
@@ -774,7 +389,7 @@ public unsafe static int Compress(Stream instream, long inLength, Stream outstre
|
||||
/// and determine the optimal 'length' values for the compressed blocks. Is not 100% optimal,
|
||||
/// as the flag-bytes are not taken into account.
|
||||
/// </summary>
|
||||
private unsafe static int CompressWithLA(Stream instream, long inLength, Stream outstream)
|
||||
internal unsafe static int CompressWithLA(Stream instream, long inLength, Stream outstream)
|
||||
{
|
||||
// save the input data in an array to prevent having to go back and forth in a file
|
||||
byte[] indata = new byte[inLength];
|
||||
@@ -892,7 +507,7 @@ private unsafe static int CompressWithLA(Stream instream, long inLength, Stream
|
||||
/// this value is the optimal 'length' value. If it is 1, the block should not be compressed.</param>
|
||||
/// <param name="disps">The 'disp' values of the compressed blocks. May be 0, in which case the
|
||||
/// corresponding length will never be anything other than 1.</param>
|
||||
private unsafe static void GetOptimalCompressionLengths(byte* indata, int inLength, out int[] lengths, out int[] disps)
|
||||
internal unsafe static void GetOptimalCompressionLengths(byte* indata, int inLength, out int[] lengths, out int[] disps)
|
||||
{
|
||||
lengths = new int[inLength];
|
||||
disps = new int[inLength];
|
||||
@@ -1148,5 +763,4 @@ public static int ToNDSu24(byte[] buffer, int offset)
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
228
pk3DS/3DS/mini.cs
Normal file
228
pk3DS/3DS/mini.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace CTR
|
||||
{
|
||||
// Mini Packing Util
|
||||
class mini
|
||||
{
|
||||
internal static void packMini(string path, string ident, string fileName, string outExt = null, string outFolder = null, bool delete = true)
|
||||
{
|
||||
if (outFolder == null)
|
||||
{
|
||||
delete = false;
|
||||
outFolder = path;
|
||||
}
|
||||
if (outExt == null) outExt = ".bin";
|
||||
// Create new Binary with the relevant header bytes
|
||||
byte[] data = new byte[4];
|
||||
data[0] = (byte)ident[0];
|
||||
data[1] = (byte)ident[1];
|
||||
string[] files = Directory.GetFiles(path);
|
||||
Array.Copy(BitConverter.GetBytes((ushort)files.Length), 0, data, 2, 2);
|
||||
|
||||
int count = files.Length;
|
||||
int dataOffset = 4 + 4 + count * 4;
|
||||
|
||||
// Start the data filling.
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
using (MemoryStream offsetMap = new MemoryStream())
|
||||
using (BinaryWriter bd = new BinaryWriter(dataout))
|
||||
using (BinaryWriter bo = new BinaryWriter(offsetMap))
|
||||
{
|
||||
// For each file...
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// Write File Offset
|
||||
uint fileOffset = (uint)(dataout.Position + dataOffset);
|
||||
bo.Write(fileOffset);
|
||||
|
||||
// Write File to Stream
|
||||
bd.Write(File.ReadAllBytes(files[i]));
|
||||
|
||||
// Pad the Data MemoryStream with Zeroes until len%4=0;
|
||||
while (dataout.Length % 4 != 0)
|
||||
bd.Write((byte)0);
|
||||
// File Offset will be updated as the offset is based off of the Data length.
|
||||
|
||||
// Delete the File
|
||||
File.Delete(files[i]);
|
||||
}
|
||||
// Cap the File
|
||||
bo.Write((uint)(dataout.Position + dataOffset));
|
||||
|
||||
using (var newPack = File.Create(Path.Combine(outFolder, fileName + outExt)))
|
||||
using (var header = new MemoryStream(data))
|
||||
{
|
||||
header.WriteTo(newPack);
|
||||
offsetMap.WriteTo(newPack);
|
||||
dataout.WriteTo(newPack);
|
||||
}
|
||||
}
|
||||
if (delete)
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
internal static byte[] packMini(byte[][] fileData, string ident)
|
||||
{
|
||||
// Create new Binary with the relevant header bytes
|
||||
byte[] data = new byte[4];
|
||||
data[0] = (byte)ident[0];
|
||||
data[1] = (byte)ident[1];
|
||||
Array.Copy(BitConverter.GetBytes((ushort)fileData.Length), 0, data, 2, 2);
|
||||
|
||||
int count = fileData.Length;
|
||||
int dataOffset = 4 + 4 + count * 4;
|
||||
|
||||
// Start the data filling.
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
using (MemoryStream offsetMap = new MemoryStream())
|
||||
using (BinaryWriter bd = new BinaryWriter(dataout))
|
||||
using (BinaryWriter bo = new BinaryWriter(offsetMap))
|
||||
{
|
||||
// For each file...
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// Write File Offset
|
||||
uint fileOffset = (uint)(dataout.Position + dataOffset);
|
||||
bo.Write(fileOffset);
|
||||
|
||||
// Write File to Stream
|
||||
bd.Write(fileData[i]);
|
||||
|
||||
// Pad the Data MemoryStream with Zeroes until len%4=0;
|
||||
while (dataout.Length % 4 != 0)
|
||||
bd.Write((byte)0);
|
||||
// File Offset will be updated as the offset is based off of the Data length.
|
||||
}
|
||||
// Cap the File
|
||||
bo.Write((uint)(dataout.Position + dataOffset));
|
||||
|
||||
using (var newPack = new MemoryStream())
|
||||
using (var header = new MemoryStream(data))
|
||||
{
|
||||
header.WriteTo(newPack);
|
||||
offsetMap.WriteTo(newPack);
|
||||
dataout.WriteTo(newPack);
|
||||
return newPack.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static bool packMini2(string path, string ident, string fileName)
|
||||
{
|
||||
if (!Directory.Exists(path)) return false;
|
||||
try
|
||||
{
|
||||
string[] filesToPack = Directory.GetFiles(path);
|
||||
byte[][] fileData = new byte[filesToPack.Length][];
|
||||
for (int i = 0; i < filesToPack.Length; i++) fileData[i] = File.ReadAllBytes(filesToPack[i]);
|
||||
byte[] miniBytes = packMini(fileData, ident);
|
||||
File.WriteAllBytes(fileName, miniBytes);
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
internal static void unpackMini(string path, string ident, string outFolder = null, bool delete = true)
|
||||
{
|
||||
if (outFolder == null) outFolder = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(outFolder)) Directory.CreateDirectory(outFolder);
|
||||
using (var s = new MemoryStream(File.ReadAllBytes(path)))
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
string fx = new string(br.ReadChars(2));
|
||||
|
||||
if (fx != ident) return;
|
||||
|
||||
ushort count = br.ReadUInt16();
|
||||
string namePad = "D" + Math.Ceiling(Math.Log10(count));
|
||||
uint[] offsets = new uint[count + 1];
|
||||
for (int i = 0; i < count; i++)
|
||||
offsets[i] = br.ReadUInt32();
|
||||
|
||||
uint length = br.ReadUInt32();
|
||||
offsets[offsets.Length - 1] = length;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
br.BaseStream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
{
|
||||
byte[] data = new byte[0];
|
||||
s.CopyTo(dataout, (int)offsets[i]);
|
||||
int len = (int)offsets[i + 1] - (int)offsets[i];
|
||||
|
||||
if (len != 0)
|
||||
{
|
||||
data = dataout.ToArray();
|
||||
Array.Resize(ref data, len);
|
||||
}
|
||||
string newFile = Path.Combine(outFolder, i.ToString(namePad) + ".bin");
|
||||
File.WriteAllBytes(newFile, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (delete)
|
||||
File.Delete(path); // File is unpacked.
|
||||
}
|
||||
internal static byte[][] unpackMini(byte[] fileData, string ident)
|
||||
{
|
||||
using (var s = new MemoryStream(fileData))
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
string fx = new string(br.ReadChars(2));
|
||||
|
||||
if (fx != ident) return null;
|
||||
|
||||
ushort count = br.ReadUInt16();
|
||||
byte[][] returnData = new byte[count][];
|
||||
|
||||
uint[] offsets = new uint[count + 1];
|
||||
for (int i = 0; i < count; i++)
|
||||
offsets[i] = br.ReadUInt32();
|
||||
|
||||
uint length = br.ReadUInt32();
|
||||
offsets[offsets.Length - 1] = length;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
br.BaseStream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
{
|
||||
byte[] data = new byte[0];
|
||||
s.CopyTo(dataout, (int)offsets[i]);
|
||||
int len = (int)offsets[i + 1] - (int)offsets[i];
|
||||
|
||||
if (len != 0)
|
||||
{
|
||||
data = dataout.ToArray();
|
||||
Array.Resize(ref data, len);
|
||||
}
|
||||
returnData[i] = data;
|
||||
}
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
internal static string getIsMini(string path)
|
||||
{
|
||||
byte[] data = File.ReadAllBytes(path);
|
||||
var fi = new FileInfo(path);
|
||||
try
|
||||
{
|
||||
string fx = new string(new[] { (char)data[0], (char)data[1] });
|
||||
ushort count = BitConverter.ToUInt16(data, 2);
|
||||
|
||||
uint[] offsets = new uint[count + 1];
|
||||
uint length = 1338;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
offsets[i] = BitConverter.ToUInt32(data, 4 + i * 4);
|
||||
length = BitConverter.ToUInt32(data, 8 + i * 4);
|
||||
}
|
||||
|
||||
offsets[offsets.Length - 1] = length;
|
||||
return (fi.Length == length) ? fx : null;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
213
pk3DS/ARCUtil.cs
213
pk3DS/ARCUtil.cs
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace pk3DS
|
||||
{
|
||||
public partial class ARC
|
||||
public class ARC
|
||||
{
|
||||
// Multi Type Archive Handling
|
||||
internal static Boolean onefile = true;
|
||||
@@ -468,7 +468,6 @@ internal static string Interpret(string path)
|
||||
{
|
||||
var fs = File.OpenRead(path);
|
||||
uint FileLen = t.FileDataEnd - t.FileDataStart;
|
||||
string FileName = "";
|
||||
fs.Seek(t.FileDataStart + sarc.DataOffset, SeekOrigin.Begin);
|
||||
byte[] fileBuffer = new byte[FileLen];
|
||||
fs.Read(fileBuffer, 0, (int)FileLen);
|
||||
@@ -479,7 +478,7 @@ internal static string Interpret(string path)
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
FileName = sb.ToString().Replace('/', Path.DirectorySeparatorChar);
|
||||
string FileName = sb.ToString().Replace('/', Path.DirectorySeparatorChar);
|
||||
fs.Close();
|
||||
string FileDir = Path.GetDirectoryName(dir + FileName) + Path.DirectorySeparatorChar;
|
||||
if (!Directory.Exists(FileDir))
|
||||
@@ -496,213 +495,7 @@ internal static string Interpret(string path)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Mini Packing Util
|
||||
internal static void packMini(string path, string ident, string fileName, string outExt = null, string outFolder = null, bool delete = true)
|
||||
{
|
||||
if (outFolder == null)
|
||||
{
|
||||
delete = false;
|
||||
outFolder = path;
|
||||
}
|
||||
if (outExt == null) outExt = ".bin";
|
||||
// Create new Binary with the relevant header bytes
|
||||
byte[] data = new byte[4];
|
||||
data[0] = (byte)ident[0];
|
||||
data[1] = (byte)ident[1];
|
||||
string[] files = Directory.GetFiles(path);
|
||||
Array.Copy(BitConverter.GetBytes((ushort)files.Length), 0, data, 2, 2);
|
||||
|
||||
int count = files.Length;
|
||||
int dataOffset = 4 + 4 + count * 4;
|
||||
|
||||
// Start the data filling.
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
using (MemoryStream offsetMap = new MemoryStream())
|
||||
using (BinaryWriter bd = new BinaryWriter(dataout))
|
||||
using (BinaryWriter bo = new BinaryWriter(offsetMap))
|
||||
{
|
||||
// For each file...
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// Write File Offset
|
||||
uint fileOffset = (uint)(dataout.Position + dataOffset);
|
||||
bo.Write(fileOffset);
|
||||
|
||||
// Write File to Stream
|
||||
bd.Write(File.ReadAllBytes(files[i]));
|
||||
|
||||
// Pad the Data MemoryStream with Zeroes until len%4=0;
|
||||
while (dataout.Length % 4 != 0)
|
||||
bd.Write((byte)0);
|
||||
// File Offset will be updated as the offset is based off of the Data length.
|
||||
|
||||
// Delete the File
|
||||
File.Delete(files[i]);
|
||||
}
|
||||
// Cap the File
|
||||
bo.Write((uint)(dataout.Position + dataOffset));
|
||||
|
||||
using (var newPack = File.Create(Path.Combine(outFolder, fileName + outExt)))
|
||||
using (var header = new MemoryStream(data))
|
||||
{
|
||||
header.WriteTo(newPack);
|
||||
offsetMap.WriteTo(newPack);
|
||||
dataout.WriteTo(newPack);
|
||||
}
|
||||
}
|
||||
if (delete)
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
internal static byte[] packMini(byte[][] fileData, string ident)
|
||||
{
|
||||
// Create new Binary with the relevant header bytes
|
||||
byte[] data = new byte[4];
|
||||
data[0] = (byte)ident[0];
|
||||
data[1] = (byte)ident[1];
|
||||
Array.Copy(BitConverter.GetBytes((ushort)fileData.Length), 0, data, 2, 2);
|
||||
|
||||
int count = fileData.Length;
|
||||
int dataOffset = 4 + 4 + count * 4;
|
||||
|
||||
// Start the data filling.
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
using (MemoryStream offsetMap = new MemoryStream())
|
||||
using (BinaryWriter bd = new BinaryWriter(dataout))
|
||||
using (BinaryWriter bo = new BinaryWriter(offsetMap))
|
||||
{
|
||||
// For each file...
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// Write File Offset
|
||||
uint fileOffset = (uint)(dataout.Position + dataOffset);
|
||||
bo.Write(fileOffset);
|
||||
|
||||
// Write File to Stream
|
||||
bd.Write(fileData[i]);
|
||||
|
||||
// Pad the Data MemoryStream with Zeroes until len%4=0;
|
||||
while (dataout.Length % 4 != 0)
|
||||
bd.Write((byte)0);
|
||||
// File Offset will be updated as the offset is based off of the Data length.
|
||||
}
|
||||
// Cap the File
|
||||
bo.Write((uint)(dataout.Position + dataOffset));
|
||||
|
||||
using (var newPack = new MemoryStream())
|
||||
using (var header = new MemoryStream(data))
|
||||
{
|
||||
header.WriteTo(newPack);
|
||||
offsetMap.WriteTo(newPack);
|
||||
dataout.WriteTo(newPack);
|
||||
return newPack.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static void unpackMini(string path, string ident, string outFolder = null, bool delete = true)
|
||||
{
|
||||
if (outFolder == null) outFolder = Path.GetDirectoryName(path);
|
||||
if (!Directory.Exists(outFolder)) Directory.CreateDirectory(outFolder);
|
||||
using (var s = new MemoryStream(File.ReadAllBytes(path)))
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
string fx = new string(br.ReadChars(2));
|
||||
|
||||
if (fx != ident) return;
|
||||
|
||||
ushort count = br.ReadUInt16();
|
||||
string namePad = "D" + Math.Ceiling(Math.Log10(count));
|
||||
uint[] offsets = new uint[count + 1];
|
||||
for (int i = 0; i < count; i++)
|
||||
offsets[i] = br.ReadUInt32();
|
||||
|
||||
uint length = br.ReadUInt32();
|
||||
offsets[offsets.Length - 1] = length;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
br.BaseStream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
{
|
||||
byte[] data = new byte[0];
|
||||
s.CopyTo(dataout, (int)offsets[i]);
|
||||
int len = (int)offsets[i + 1] - (int)offsets[i];
|
||||
|
||||
if (len != 0)
|
||||
{
|
||||
data = dataout.ToArray();
|
||||
Array.Resize(ref data, len);
|
||||
}
|
||||
string newFile = Path.Combine(outFolder, i.ToString(namePad) + ".bin");
|
||||
File.WriteAllBytes(newFile, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (delete)
|
||||
File.Delete(path); // File is unpacked.
|
||||
}
|
||||
internal static byte[][] unpackMini(byte[] fileData, string ident)
|
||||
{
|
||||
using (var s = new MemoryStream(fileData))
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
string fx = new string(br.ReadChars(2));
|
||||
|
||||
if (fx != ident) return null;
|
||||
|
||||
ushort count = br.ReadUInt16();
|
||||
byte[][] returnData = new byte[count][];
|
||||
|
||||
uint[] offsets = new uint[count + 1];
|
||||
for (int i = 0; i < count; i++)
|
||||
offsets[i] = br.ReadUInt32();
|
||||
|
||||
uint length = br.ReadUInt32();
|
||||
offsets[offsets.Length - 1] = length;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
br.BaseStream.Seek(offsets[i], SeekOrigin.Begin);
|
||||
using (MemoryStream dataout = new MemoryStream())
|
||||
{
|
||||
byte[] data = new byte[0];
|
||||
s.CopyTo(dataout, (int)offsets[i]);
|
||||
int len = (int)offsets[i + 1] - (int)offsets[i];
|
||||
|
||||
if (len != 0)
|
||||
{
|
||||
data = dataout.ToArray();
|
||||
Array.Resize(ref data, len);
|
||||
}
|
||||
returnData[i] = data;
|
||||
}
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
internal static string getIsMini(string path)
|
||||
{
|
||||
byte[] data = File.ReadAllBytes(path);
|
||||
var fi = new FileInfo(path);
|
||||
try
|
||||
{
|
||||
string fx = new string(new[]{(char)data[0], (char)data[1]});
|
||||
ushort count = BitConverter.ToUInt16(data, 2);
|
||||
|
||||
uint[] offsets = new uint[count + 1];
|
||||
uint length = 1338;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
offsets[i] = BitConverter.ToUInt32(data, 4 + i*4);
|
||||
length = BitConverter.ToUInt32(data, 8 + i*4);
|
||||
}
|
||||
|
||||
offsets[offsets.Length - 1] = length;
|
||||
return (fi.Length == length) ? fx : null;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
|
||||
// Unpacking
|
||||
internal static string unpackDARC(string path, string outFolder = null, bool delete = true)
|
||||
{
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
using System.Media;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using BLZ;
|
||||
using CTR;
|
||||
|
||||
namespace pk3DS
|
||||
{
|
||||
@@ -165,14 +163,14 @@ private void openQuick(string path)
|
||||
if (fi.Name.Contains("code.bin")) // Compress/Decompress .code.bin
|
||||
{
|
||||
if (fi.Length % 0x200 == 0 && (Util.Prompt(MessageBoxButtons.YesNo, "Detected Decompressed code.bin.", "Compress? File will be replaced.") == DialogResult.Yes))
|
||||
new Thread(() => { threads++; new BLZCoder(new[] { "-en", path }, pBar1); threads--; Util.Alert("Compressed!"); }).Start();
|
||||
new Thread(() => { threads++; new CTR.BLZCoder(new[] { "-en", path }, pBar1); threads--; Util.Alert("Compressed!"); }).Start();
|
||||
else if (Util.Prompt(MessageBoxButtons.YesNo, "Detected Compressed code.bin.", "Decompress? File will be replaced.") == DialogResult.Yes)
|
||||
new Thread(() => { threads++; new BLZCoder(new[] { "-d", path }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start();
|
||||
new Thread(() => { threads++; new CTR.BLZCoder(new[] { "-d", path }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start();
|
||||
}
|
||||
else if (fi.Name.ToLower().Contains("exe")) // Unpack exefs
|
||||
{
|
||||
if (fi.Length % 0x200 == 0 && (Util.Prompt(MessageBoxButtons.YesNo, "Detected ExeFS.bin.", "Unpack?") == DialogResult.Yes))
|
||||
new Thread(() => { threads++; ExeFS.get(path, Path.GetDirectoryName(path)); threads--; Util.Alert("Unpacked!"); }).Start();
|
||||
new Thread(() => { threads++; CTR.ExeFS.get(path, Path.GetDirectoryName(path)); threads--; Util.Alert("Unpacked!"); }).Start();
|
||||
}
|
||||
else if (fi.Name.ToLower().Contains("rom"))
|
||||
{
|
||||
@@ -275,7 +273,7 @@ private bool checkIfExeFS(string path)
|
||||
FileInfo fi = new FileInfo(files[0]);
|
||||
if (!fi.Name.Contains("code")) return false;
|
||||
if (fi.Length % 0x200 != 0 && (Util.Prompt(MessageBoxButtons.YesNo, "Detected Compressed code.bin.", "Decompress? File will be replaced.") == DialogResult.Yes))
|
||||
new Thread(() => { threads++; new BLZCoder(new[] { "-d", files[0] }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start();
|
||||
new Thread(() => { threads++; new CTR.BLZCoder(new[] { "-d", files[0] }, pBar1); threads--; Util.Alert("Decompressed!"); }).Start();
|
||||
|
||||
ExeFSPath = path;
|
||||
return true;
|
||||
@@ -533,7 +531,7 @@ private void rebuildExeFS(object sender, EventArgs e)
|
||||
new Thread(() =>
|
||||
{
|
||||
threads++;
|
||||
new BLZCoder(new[] { "-en", files[file] }, pBar1);
|
||||
new CTR.BLZCoder(new[] { "-en", files[file] }, pBar1);
|
||||
Util.Alert("Compressed!");
|
||||
CTR.ExeFS.set(Directory.GetFiles(ExeFSPath), sfd.FileName);
|
||||
threads--;
|
||||
@@ -647,7 +645,7 @@ public bool getGARC(string infile, string outfolder, bool PB, bool bypassExt = f
|
||||
}
|
||||
try
|
||||
{
|
||||
bool success = GARCTool.garcUnpack(infile, outfolder, bypassExt, (PB) ? pBar1 : null, null, true, bypassExt);
|
||||
bool success = CTR.GARC.garcUnpack(infile, outfolder, bypassExt, (PB) ? pBar1 : null, null, true, bypassExt);
|
||||
updateStatus(String.Format(success ? "Success!" : "Failed!"), false);
|
||||
threads--;
|
||||
return success;
|
||||
@@ -661,7 +659,7 @@ public bool setGARC(string outfile, string infolder, bool PB)
|
||||
|
||||
try
|
||||
{
|
||||
bool success = GARCTool.garcPackMS(infolder, outfile, (PB) ? pBar1 : null, null, true);
|
||||
bool success = CTR.GARC.garcPackMS(infolder, outfile, (PB) ? pBar1 : null, null, true);
|
||||
threads--;
|
||||
updateStatus(String.Format(success ? "Success!" : "Failed!"), false);
|
||||
return success;
|
||||
|
||||
@@ -9,7 +9,7 @@ public partial class Moves : Form
|
||||
public Moves()
|
||||
{
|
||||
if (Main.oras)
|
||||
ARC.unpackMini(Directory.GetFiles("move")[0], "WD");
|
||||
CTR.mini.unpackMini(Directory.GetFiles("move")[0], "WD");
|
||||
|
||||
movelist[0] = "";
|
||||
sortedmoves = (string[])movelist.Clone();
|
||||
@@ -165,7 +165,7 @@ private void formClosing(object sender, FormClosingEventArgs e)
|
||||
setEntry();
|
||||
|
||||
if (Main.oras)
|
||||
ARC.packMini("move", "WD", "0", ".bin");
|
||||
CTR.mini.packMini("move", "WD", "0", ".bin");
|
||||
}
|
||||
|
||||
private void B_RandAll_Click(object sender, EventArgs e)
|
||||
@@ -190,14 +190,14 @@ private void B_RandAll_Click(object sender, EventArgs e)
|
||||
internal static int[] getTypes()
|
||||
{
|
||||
if (Main.oras)
|
||||
ARC.unpackMini(Directory.GetFiles("move")[0], "WD");
|
||||
CTR.mini.unpackMini(Directory.GetFiles("move")[0], "WD");
|
||||
string[] f2 = Directory.GetFiles("move");
|
||||
int[] moveTypes = new int[f2.Length];
|
||||
for (int i = 0; i < f2.Length; i++)
|
||||
moveTypes[i] = File.ReadAllBytes(f2[i])[0];
|
||||
|
||||
if (Main.oras)
|
||||
ARC.packMini("move", "WD", "0", ".bin");
|
||||
CTR.mini.packMini("move", "WD", "0", ".bin");
|
||||
|
||||
return moveTypes;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ private void getEntry()
|
||||
if (entry < 0) return;
|
||||
RTB_F.Text = RTB_O.Text = RTB_W.Text = RTB_T.Text = string.Empty;
|
||||
byte[] raw = File.ReadAllBytes(filepaths[entry]);
|
||||
locationData = ARC.unpackMini(raw, "ZO");
|
||||
locationData = CTR.mini.unpackMini(raw, "ZO");
|
||||
if (locationData == null) return;
|
||||
|
||||
RichTextBox[] rtba = {RTB_MapInfo, RTB_OWSC, RTB_MapSC, RTB_Encounter, RTB_File5};
|
||||
|
||||
1
pk3DS/Tools/ToolsUI.Designer.cs
generated
1
pk3DS/Tools/ToolsUI.Designer.cs
generated
@@ -103,7 +103,6 @@ private void InitializeComponent()
|
||||
this.CB_Repack.Name = "CB_Repack";
|
||||
this.CB_Repack.Size = new System.Drawing.Size(150, 21);
|
||||
this.CB_Repack.TabIndex = 8;
|
||||
this.CB_Repack.SelectedIndexChanged += new System.EventHandler(this.CB_Repack_SelectedIndexChanged);
|
||||
//
|
||||
// PB_BCLIM
|
||||
//
|
||||
|
||||
@@ -20,7 +20,6 @@ public ToolsUI()
|
||||
PB_BCLIM.DragDrop += tabMain_DragDrop;
|
||||
CLIMWindow = PB_BCLIM.Size;
|
||||
CB_Repack.Items.Add("GARC Pack");
|
||||
CB_Repack.Items.Add("GARC Pack (from Existing)");
|
||||
CB_Repack.Items.Add("DARC Pack (use filenames)");
|
||||
CB_Repack.Items.Add("Mini Pack (from Name)");
|
||||
CB_Repack.SelectedIndex = 2;
|
||||
@@ -51,7 +50,7 @@ private void dropLeave(object sender, EventArgs e)
|
||||
}
|
||||
private void openIMG(string path)
|
||||
{
|
||||
var img = png2bclim.makeBMP(path, CHK_PNG.Checked);
|
||||
var img = CTR.BCLIM.makeBMP(path, CHK_PNG.Checked);
|
||||
if (img == null) return;
|
||||
PB_BCLIM.Size = new Size(img.Width + 2, img.Height + 2);
|
||||
PB_BCLIM.BackgroundImage = img;
|
||||
@@ -67,12 +66,12 @@ private void openARC(string path, bool recursing = false)
|
||||
|
||||
// Determine if it is a DARC or a Mini
|
||||
// Check if Mini first
|
||||
string fx = ARC.getIsMini(path);
|
||||
string fx = CTR.mini.getIsMini(path);
|
||||
string newFolder = folderPath + "_" + fx;
|
||||
if (fx != null) // Is Mini Packed File
|
||||
{
|
||||
// Fetch Mini File Contents
|
||||
ARC.unpackMini(path, fx, newFolder, false);
|
||||
CTR.mini.unpackMini(path, fx, newFolder, false);
|
||||
// Recurse throught the extracted contents if they extract successfully
|
||||
if (Directory.Exists(newFolder))
|
||||
foreach (string file in Directory.GetFiles(newFolder))
|
||||
@@ -88,11 +87,13 @@ private void openARC(string path, bool recursing = false)
|
||||
if (pos >= data.Length) return;
|
||||
}
|
||||
var darcData = data.Skip(pos).ToArray();
|
||||
var res = CTR.DARC.darc2files(darcData, folderPath + "_d");
|
||||
bool r = CTR.DARC.darc2files(darcData, folderPath + "_d");
|
||||
if (!r) Util.Alert("Unpacking failed.");
|
||||
}
|
||||
else if (BitConverter.ToUInt32(File.ReadAllBytes(path), 0) == 0x47415243) // GARC
|
||||
{
|
||||
GARCTool.garcUnpack(path, folderPath + "_g", false);
|
||||
bool r = CTR.GARC.garcUnpack(path, folderPath + "_g", false);
|
||||
if (!r) Util.Alert("Unpacking failed.");
|
||||
}
|
||||
else if (!recursing)
|
||||
{ Util.Alert("File is not a darc or a mini packed file:" + Environment.NewLine + path); return ;}
|
||||
@@ -114,10 +115,13 @@ private void saveARC(string path)
|
||||
int type = CB_Repack.SelectedIndex;
|
||||
switch (type)
|
||||
{
|
||||
case -1: return;
|
||||
case 0: // GARC Pack
|
||||
case 1: // GARC Pack Existing
|
||||
case 2: // DARC Pack
|
||||
{
|
||||
bool r = CTR.GARC.garcPackMS(path, folderName + ".garc");
|
||||
if (!r) Util.Alert("Packing failed.");
|
||||
break;
|
||||
}
|
||||
case 1: // DARC Pack (from existing if exists)
|
||||
{
|
||||
string oldFile = path.Replace("_d", "");
|
||||
if (File.Exists(Path.Combine(parentName, oldFile)))
|
||||
@@ -127,10 +131,12 @@ private void saveARC(string path)
|
||||
else if (File.Exists(Path.Combine(parentName, oldFile + ".darc")))
|
||||
oldFile = Path.Combine(parentName, oldFile + ".darc");
|
||||
else oldFile = null;
|
||||
bool result = CTR.DARC.files2darc(path, false, oldFile);
|
||||
|
||||
bool r = CTR.DARC.files2darc(path, false, oldFile);
|
||||
if (!r) Util.Alert("Packing failed.");
|
||||
break;
|
||||
}
|
||||
case 3: // Mini Pack
|
||||
case 2: // Mini Pack
|
||||
{
|
||||
string fileName = folderName.Replace("_", "");
|
||||
if (fileName.Length < 2) { Util.Error("Mini Folder name not valid:", path); return; }
|
||||
@@ -138,15 +144,8 @@ private void saveARC(string path)
|
||||
string fileExt = fileName.Substring(fileName.Length - 2);
|
||||
string fileNum = fileName.Substring(0, fileName.Length - 3); // ignore "."
|
||||
|
||||
string[] filesToPack = Directory.GetFiles(path);
|
||||
byte[][] data = new byte[filesToPack.Length][];
|
||||
for (int i = 0; i < filesToPack.Length; i++) data[i] = File.ReadAllBytes(filesToPack[i]);
|
||||
byte[] miniBytes = ARC.packMini(data, fileExt);
|
||||
|
||||
string newName = fileNum + "." + fileExt;
|
||||
File.WriteAllBytes(parentName + newName, miniBytes);
|
||||
Util.Alert("Mini Folder Repacked!",
|
||||
String.Format("InFolder: {0}{2}OutFile{1}", folderName, newName, Path.DirectorySeparatorChar));
|
||||
bool r = CTR.mini.packMini2(path, fileExt, parentName + fileNum + "." + fileExt);
|
||||
if (!r) Util.Alert("Packing failed.");
|
||||
break;
|
||||
}
|
||||
default: Util.Alert("Repacking not implemented." + Environment.NewLine + path);
|
||||
@@ -171,14 +170,5 @@ private void B_Reset_Click(object sender, EventArgs e)
|
||||
{
|
||||
PB_BCLIM.Size = CLIMWindow;
|
||||
}
|
||||
|
||||
private void CB_Repack_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (CB_Repack.SelectedIndex < 2) // 0,1
|
||||
{
|
||||
Util.Alert("Not implemented:", CB_Repack.Text);
|
||||
CB_Repack.SelectedIndex = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,14 +52,17 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="3DS\AES.cs" />
|
||||
<Compile Include="3DS\DARC.cs" />
|
||||
<Compile Include="3DS\ETC1.cs" />
|
||||
<Compile Include="3DS\Exheader.cs" />
|
||||
<Compile Include="3DS\LZSS.cs" />
|
||||
<Compile Include="3DS\mini.cs" />
|
||||
<Compile Include="3DS\NCCH.cs" />
|
||||
<Compile Include="3DS\NCSD.cs" />
|
||||
<Compile Include="ARCUtil.cs" />
|
||||
<Compile Include="3DS\BLZ.cs" />
|
||||
<Compile Include="3DS\CTR.cs" />
|
||||
<Compile Include="3DS\ExeFS.cs" />
|
||||
<Compile Include="png2bclim.cs" />
|
||||
<Compile Include="3DS\BCLIM.cs" />
|
||||
<Compile Include="Randomizer.cs" />
|
||||
<Compile Include="3DS\RomFS.cs" />
|
||||
<Compile Include="Subforms\TitleScreen.cs">
|
||||
@@ -146,7 +149,7 @@
|
||||
<Compile Include="Subforms\EggMove.Designer.cs">
|
||||
<DependentUpon>EggMove.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="GARCTool.cs" />
|
||||
<Compile Include="3DS\GARC.cs" />
|
||||
<Compile Include="Legal.cs" />
|
||||
<Compile Include="Main.cs">
|
||||
<SubType>Form</SubType>
|
||||
|
||||
1537
pk3DS/png2bclim.cs
1537
pk3DS/png2bclim.cs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user