cpk-tools/CriPakTools/Program.cs
2024-12-19 15:05:55 +08:00

487 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}
}
}