487 lines
23 KiB
C#
487 lines
23 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.IO;
|
||
using System.Runtime.InteropServices;
|
||
using LibCPK;
|
||
namespace CriPakTools
|
||
{
|
||
public class SystemEncoding
|
||
{
|
||
public static Encoding Codecs = Encoding.UTF8;
|
||
}
|
||
class Program
|
||
{
|
||
[DllImport("user32.dll", EntryPoint = "MessageBox")]
|
||
public static extern int MsgBox(IntPtr hwnd, string text, string caption, uint type);
|
||
public static void ShowMsgBox(string msg)
|
||
{
|
||
MsgBox(IntPtr.Zero, msg, "CriPakTools", 1);
|
||
}
|
||
|
||
|
||
static void Main(string[] args)
|
||
{
|
||
if (args.Length == 0)
|
||
{
|
||
|
||
Console.WriteLine("error: no args\n");
|
||
Console.WriteLine("====================");
|
||
Console.WriteLine("This tool has been redesigned, please use CriPakGUI.exe ");
|
||
Console.WriteLine("====================");
|
||
Console.WriteLine("This tool is based on the codes by Falo , Nanashi3 ,esperknight and uyjulian");
|
||
Console.WriteLine("I forked and added batch reimport and compress code .");
|
||
Console.WriteLine("Thanks for KenTse 's CRILAYLA compression method");
|
||
Console.WriteLine(" by: wmltogether@gmail.com");
|
||
Console.WriteLine("CriPakTool Usage:");
|
||
Console.WriteLine(" -e - set encodings (utf8, utf16, cp932)");
|
||
Console.WriteLine(" -l - Displays all contained chunks.");
|
||
Console.WriteLine(" -x - Extracts all files.");
|
||
Console.WriteLine(" -r REPLACE_ME REPLACE_WITH - Replaces REPLACE_ME with REPLACE_WITH.");
|
||
Console.WriteLine(" -o OUT_FILE - Set output file.");
|
||
Console.WriteLine(" -d OUT_DIR - Set output directory.");
|
||
Console.WriteLine(" -i IN_FILE - Set input file.");
|
||
Console.WriteLine(" -c - use CRILAYLA compression");
|
||
Console.WriteLine(" -y - use legacy (c)CRI decompression");
|
||
Console.WriteLine(" -b BATCH_REPLACE_LIST_TXT - Batch Replace file recorded in filelist.txt .");
|
||
Console.WriteLine(" -h HELP");
|
||
Program.ShowMsgBox("Error: \n Please run this program from console!");
|
||
|
||
return;
|
||
}
|
||
|
||
bool doExtract = false;
|
||
bool doReplace = false;
|
||
bool doDisplay = false;
|
||
bool bUseCompress = false;
|
||
bool doBatchReplace = false; //添加批量替换功能
|
||
bool bUseLegacyCompress = false; //添加旧式解压参数
|
||
string outDir = ".";
|
||
string inFile = "";
|
||
string outFile = "";
|
||
string replaceMe = "";
|
||
string replaceWith = "";
|
||
string batch_text_name = "";
|
||
|
||
for (int i = 0; i < args.Length; i++)
|
||
{
|
||
string option = args[i];
|
||
if (option[0] == '-')
|
||
{
|
||
switch (option[1])
|
||
{
|
||
case 'e':
|
||
{
|
||
var encodingdata = args[i + 1];
|
||
if (encodingdata.ToLower().Equals("utf8") || encodingdata.ToLower().Equals("utf-8"))
|
||
{
|
||
SystemEncoding.Codecs = Encoding.UTF8;
|
||
}
|
||
|
||
else if (encodingdata.ToLower().Equals("cp932") || encodingdata.ToLower().Equals("sjis") || encodingdata.ToLower().Equals("shift-jis"))
|
||
{
|
||
SystemEncoding.Codecs = Encoding.GetEncoding(932);
|
||
}
|
||
else if (encodingdata.ToLower().Equals("utf-16") || encodingdata.ToLower().Equals("unicode") || encodingdata.ToLower().Equals("utf16"))
|
||
{
|
||
SystemEncoding.Codecs = Encoding.Unicode;
|
||
}
|
||
break;
|
||
}
|
||
case 'x': doExtract = true; break;
|
||
case 'c': bUseCompress = true; break;
|
||
case 'r': doReplace = true; replaceMe = args[i + 1]; replaceWith = args[i + 2]; break;
|
||
case 'l': doDisplay = true; break;
|
||
case 'd': outDir = args[i + 1]; break;
|
||
case 'i': inFile = args[i + 1]; break;
|
||
case 'o': outFile = args[i + 1]; break;
|
||
case 'b': doBatchReplace = true; batch_text_name = args[i + 1]; break;
|
||
case 'y': bUseLegacyCompress = true; break;
|
||
case 'h':
|
||
Console.WriteLine("CriPakTool Usage:");
|
||
Console.WriteLine(" -l - Displays all contained chunks.");
|
||
Console.WriteLine(" -x - Extracts all files.");
|
||
Console.WriteLine(" -c - use CRILAYLA compression");
|
||
Console.WriteLine(" -r REPLACE_ME REPLACE_WITH - Replaces REPLACE_ME with REPLACE_WITH.");
|
||
Console.WriteLine(" -o OUT_FILE - Set output file.");
|
||
Console.WriteLine(" -d OUT_DIR - Set output directory.");
|
||
Console.WriteLine(" -i IN_FILE - Set input file.");
|
||
Console.WriteLine(" -b BATCH_REPLACE_LIST_TXT - Batch Replace file recorded in filelist.txt .");
|
||
Console.WriteLine(" -h HELP");
|
||
|
||
Console.WriteLine(" [Extract files from cpk]");
|
||
Console.WriteLine(" CriPakTool.exe -x -i xxx.cpk -d xxx.cpk_unpacked");
|
||
Console.WriteLine(" [Display files from cpk]");
|
||
Console.WriteLine(" CriPakTool.exe -l -i xxx.cpk");
|
||
Console.WriteLine(" [Batch Replace files into cpk]");
|
||
Console.WriteLine(" CriPakTool.exe -b filelist.txt -c -i xxx.cpk -o xxx_patched.cpk");
|
||
Console.WriteLine(" //e.g. FILELIST.TXT");
|
||
Console.WriteLine(" original_file_name(in cpk),patch_file_name(in folder)");
|
||
Console.WriteLine(" /HD_font_a.ftx,patch/BOOT.cpk_unpacked/HD_font_a.ftx");
|
||
Console.WriteLine(" OTHER/ICON0.PNG,patch/BOOT.cpk_unpacked/OTHER/ICON0.PNG");
|
||
Console.WriteLine("...");
|
||
return;
|
||
default:
|
||
Console.WriteLine("CriPakTool Usage:");
|
||
Console.WriteLine(" -l - Displays all contained chunks.");
|
||
Console.WriteLine(" -x - Extracts all files.");
|
||
Console.WriteLine(" -c - use CRILAYLA compression");
|
||
Console.WriteLine(" -r REPLACE_ME REPLACE_WITH - Replaces REPLACE_ME with REPLACE_WITH.");
|
||
Console.WriteLine(" -o OUT_FILE - Set output file.");
|
||
Console.WriteLine(" -d OUT_DIR - Set output directory.");
|
||
Console.WriteLine(" -i IN_FILE - Set input file.");
|
||
Console.WriteLine(" -b BATCH_REPLACE_LIST_TXT - Batch Replace file recorded in filelist.txt .");
|
||
break;
|
||
|
||
}
|
||
}
|
||
}
|
||
if (inFile == "")
|
||
{
|
||
Console.WriteLine("ERROR :You must give -i argv");
|
||
return;
|
||
}
|
||
|
||
if (!File.Exists(inFile))
|
||
{
|
||
Console.WriteLine("ERROR :INPUT FILE NOT EXISTS");
|
||
return;
|
||
}
|
||
|
||
if (!(doExtract || doReplace || doDisplay || doBatchReplace))
|
||
{ //Lazy sanity checking for now
|
||
Console.WriteLine("no? \n");
|
||
return;
|
||
}
|
||
|
||
string cpk_name = inFile;
|
||
|
||
CPK cpk = new CPK(new Tools());
|
||
cpk.ReadCPK(cpk_name, SystemEncoding.Codecs);
|
||
|
||
BinaryReader oldFile = new BinaryReader(File.OpenRead(cpk_name));
|
||
|
||
if (doDisplay)
|
||
{
|
||
List<FileEntry> entries = cpk.FileTable.OrderBy(x => x.FileOffset).ToList();
|
||
for (int i = 0; i < entries.Count; i++)
|
||
{
|
||
Console.WriteLine("FILE ID:{0},File Name:{1},File Type:{5},FileOffset:{2:x8},Extract Size:{3:x8},Chunk Size:{4:x8}", entries[i].ID,
|
||
(((entries[i].DirName != null) ? entries[i].DirName + "/" : "") + entries[i].FileName) ,
|
||
entries[i].FileOffset,
|
||
entries[i].ExtractSize,
|
||
entries[i].FileSize,
|
||
entries[i].FileType);
|
||
}
|
||
}
|
||
else if (doExtract)
|
||
{
|
||
if (!Directory.Exists(outDir))
|
||
{
|
||
Directory.CreateDirectory(outDir);
|
||
}
|
||
|
||
List<FileEntry> entries = null;
|
||
|
||
entries = cpk.FileTable.Where(x => x.FileType == "FILE").ToList();
|
||
|
||
if (entries.Count == 0)
|
||
{
|
||
Console.WriteLine("err while extracting.");
|
||
}
|
||
|
||
for (int i = 0; i < entries.Count; i++)
|
||
{
|
||
if (!String.IsNullOrEmpty((string)entries[i].DirName))
|
||
{
|
||
Directory.CreateDirectory(outDir + "/" + entries[i].DirName.ToString());
|
||
}
|
||
|
||
oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);
|
||
|
||
string isComp = Encoding.ASCII.GetString(oldFile.ReadBytes(8));
|
||
oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);
|
||
|
||
byte[] chunk = oldFile.ReadBytes(Int32.Parse(entries[i].FileSize.ToString()));
|
||
Console.WriteLine("FileName :{0}\n FileOffset:{1:x8} ExtractSize:{2:x8} ChunkSize:{3:x8}",
|
||
entries[i].FileName.ToString(),
|
||
(long)entries[i].FileOffset,
|
||
entries[i].ExtractSize,
|
||
entries[i].FileSize);
|
||
if (isComp == "CRILAYLA")
|
||
{
|
||
Console.WriteLine("Got CRILAYLA !");
|
||
int size = Int32.Parse((entries[i].ExtractSize ?? entries[i].FileSize).ToString());
|
||
|
||
if (size != 0)
|
||
if (bUseLegacyCompress == false)
|
||
{
|
||
chunk = cpk.DecompressCRILAYLA(chunk, size);
|
||
}
|
||
else
|
||
{
|
||
chunk = cpk.DecompressLegacyCRI(chunk, size);
|
||
}
|
||
}
|
||
|
||
string dstpath = outDir + "/" + ((entries[i].DirName != null) ? entries[i].DirName + "/" : "") + entries[i].FileName.ToString();
|
||
dstpath = Tools.GetSafePath(dstpath);
|
||
string dstdir = Path.GetDirectoryName(dstpath);
|
||
if (!Directory.Exists(dstdir))
|
||
{
|
||
Directory.CreateDirectory(dstdir);
|
||
}
|
||
File.WriteAllBytes(dstpath, chunk);
|
||
|
||
}
|
||
}
|
||
else if (doBatchReplace)
|
||
{
|
||
//批量处理功能 ,读取filelist.txt内文件,将相应文件批量导入到cpk
|
||
|
||
FileInfo fi = new FileInfo(cpk_name);
|
||
|
||
string outputName = outFile;
|
||
|
||
BinaryWriter newCPK = new BinaryWriter(File.OpenWrite(outputName));
|
||
|
||
List<FileEntry> entries = cpk.FileTable.OrderBy(x => x.FileOffset).ToList();
|
||
|
||
Tools tool = new Tools();
|
||
Dictionary<string, string> batch_file_list = tool.ReadBatchScript(batch_text_name);
|
||
for (int i = 0; i < entries.Count; i++)
|
||
{
|
||
if (entries[i].FileType != "CONTENT")
|
||
{
|
||
|
||
if (entries[i].FileType == "FILE")
|
||
{
|
||
// I'm too lazy to figure out how to update the ContextOffset position so this works :)
|
||
if ((ulong)newCPK.BaseStream.Position < cpk.ContentOffset)
|
||
{
|
||
ulong padLength = cpk.ContentOffset - (ulong)newCPK.BaseStream.Position;
|
||
for (ulong z = 0; z < padLength; z++)
|
||
{
|
||
newCPK.Write((byte)0);
|
||
}
|
||
}
|
||
}
|
||
|
||
string currentName = ((entries[i].DirName != null) ? entries[i].DirName + "/" : "") + entries[i].FileName;
|
||
|
||
if (!currentName.Contains("/"))
|
||
{
|
||
currentName = "/" + currentName;
|
||
}
|
||
|
||
if (!batch_file_list.Keys.Contains(currentName.ToString()))
|
||
//如果不在表中,复制原始数据
|
||
{
|
||
oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);
|
||
|
||
entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;
|
||
|
||
if (entries[i].FileName.ToString() == "ETOC_HDR")
|
||
{
|
||
|
||
cpk.EtocOffset = entries[i].FileOffset;
|
||
Console.WriteLine("Fix ETOC_OFFSET to {0:x8}", cpk.EtocOffset);
|
||
|
||
}
|
||
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
|
||
byte[] chunk = oldFile.ReadBytes(Int32.Parse(entries[i].FileSize.ToString()));
|
||
newCPK.Write(chunk);
|
||
|
||
if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
|
||
{
|
||
long cur_pos = newCPK.BaseStream.Position;
|
||
for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
|
||
{
|
||
newCPK.Write((byte)0);
|
||
}
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
string replace_with = batch_file_list[currentName.ToString()];
|
||
//Got patch file name
|
||
Console.WriteLine("Patching: {0}", currentName.ToString());
|
||
|
||
byte[] newbie = File.ReadAllBytes(replace_with);
|
||
entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;
|
||
int o_ext_size = Int32.Parse((entries[i].ExtractSize).ToString());
|
||
int o_com_size = Int32.Parse((entries[i].FileSize).ToString());
|
||
if ((o_com_size < o_ext_size) && entries[i].FileType == "FILE" && bUseCompress == true)
|
||
{
|
||
// is compressed
|
||
Console.Write("Compressing data:{0:x8}", newbie.Length);
|
||
|
||
byte[] dest_comp = cpk.CompressCRILAYLA(newbie);
|
||
|
||
entries[i].FileSize = Convert.ChangeType(dest_comp.Length, entries[i].FileSizeType);
|
||
entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
newCPK.Write(dest_comp);
|
||
Console.Write(">> {0:x8}\r\n", dest_comp.Length);
|
||
}
|
||
|
||
else
|
||
{
|
||
Console.Write("Storing data:{0:x8}\r\n", newbie.Length);
|
||
entries[i].FileSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
|
||
entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
newCPK.Write(newbie);
|
||
}
|
||
|
||
|
||
if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
|
||
{
|
||
long cur_pos = newCPK.BaseStream.Position;
|
||
for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
|
||
{
|
||
newCPK.Write((byte)0);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
else
|
||
{
|
||
// Content is special.... just update the position
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
}
|
||
}
|
||
|
||
cpk.WriteCPK(newCPK);
|
||
cpk.WriteITOC(newCPK);
|
||
cpk.WriteTOC(newCPK);
|
||
cpk.WriteETOC(newCPK, cpk.EtocOffset);
|
||
cpk.WriteGTOC(newCPK);
|
||
|
||
newCPK.Close();
|
||
oldFile.Close();
|
||
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
string ins_name = replaceMe;
|
||
string replace_with = replaceWith;
|
||
|
||
FileInfo fi = new FileInfo(cpk_name);
|
||
|
||
string outputName = outFile;
|
||
|
||
BinaryWriter newCPK = new BinaryWriter(File.OpenWrite(outputName));
|
||
|
||
List<FileEntry> entries = cpk.FileTable.OrderBy(x => x.FileOffset).ToList();
|
||
|
||
for (int i = 0; i < entries.Count; i++)
|
||
{
|
||
if (entries[i].FileType != "CONTENT")
|
||
{
|
||
|
||
if (entries[i].FileType == "FILE")
|
||
{
|
||
// I'm too lazy to figure out how to update the ContextOffset position so this works :)
|
||
if ((ulong)newCPK.BaseStream.Position < cpk.ContentOffset)
|
||
{
|
||
ulong padLength = cpk.ContentOffset - (ulong)newCPK.BaseStream.Position;
|
||
for (ulong z = 0; z < padLength; z++)
|
||
{
|
||
newCPK.Write((byte)0);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if (entries[i].FileName.ToString() != ins_name)
|
||
{
|
||
oldFile.BaseStream.Seek((long)entries[i].FileOffset, SeekOrigin.Begin);
|
||
Console.WriteLine("{0},{1}", entries[i].FileName, entries[i].FileType);
|
||
entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
|
||
byte[] chunk = oldFile.ReadBytes(Int32.Parse(entries[i].FileSize.ToString()));
|
||
newCPK.Write(chunk);
|
||
if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
|
||
{
|
||
long cur_pos = newCPK.BaseStream.Position;
|
||
for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
|
||
{
|
||
newCPK.Write((byte)0);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//Got patch file name
|
||
Console.WriteLine("{0} Patched.", entries[i].FileName.ToString());
|
||
byte[] newbie = File.ReadAllBytes(replace_with);
|
||
entries[i].FileOffset = (ulong)newCPK.BaseStream.Position;
|
||
int o_ext_size = Int32.Parse((entries[i].ExtractSize).ToString());
|
||
int o_com_size = Int32.Parse((entries[i].FileSize).ToString());
|
||
if ((o_com_size < o_ext_size) && entries[i].FileType == "FILE" && bUseCompress == true)
|
||
{
|
||
// is compressed
|
||
|
||
byte[] dest_comp = cpk.CompressCRILAYLA(newbie);
|
||
|
||
entries[i].FileSize = Convert.ChangeType(dest_comp.Length, entries[i].FileSizeType);
|
||
entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
newCPK.Write(dest_comp);
|
||
Console.WriteLine("Compressing {0:x8} >> {1:x8}", newbie.Length, dest_comp.Length);
|
||
}
|
||
|
||
else
|
||
{
|
||
|
||
entries[i].FileSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
|
||
entries[i].ExtractSize = Convert.ChangeType(newbie.Length, entries[i].FileSizeType);
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
newCPK.Write(newbie);
|
||
}
|
||
|
||
|
||
if ((newCPK.BaseStream.Position % 0x800) > 0 && i < entries.Count - 1)
|
||
{
|
||
long cur_pos = newCPK.BaseStream.Position;
|
||
for (int j = 0; j < (0x800 - (cur_pos % 0x800)); j++)
|
||
{
|
||
newCPK.Write((byte)0);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine("{0},{1}", entries[i].FileName, entries[i].FileType);
|
||
// Content is special.... just update the position
|
||
cpk.UpdateFileEntry(entries[i]);
|
||
}
|
||
}
|
||
|
||
cpk.WriteCPK(newCPK);
|
||
cpk.WriteITOC(newCPK);
|
||
cpk.WriteTOC(newCPK);
|
||
cpk.WriteETOC(newCPK , cpk.EtocOffset);
|
||
cpk.WriteGTOC(newCPK);
|
||
|
||
newCPK.Close();
|
||
oldFile.Close();
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|