Browse Source

SCRIPT - add functions to extract/insert card.dat and .evt files

master
BahaBulle 7 years ago
parent
commit
a8dd4bb837
10 changed files with 2583 additions and 31 deletions
  1. +6
    -0
      Hack.Xenosaga/App.config
  2. +44
    -0
      Hack.Xenosaga/Common/Extension.cs
  3. +47
    -5
      Hack.Xenosaga/Common/Functions.cs
  4. +5
    -2
      Hack.Xenosaga/Common/Variables.cs
  5. +5
    -8
      Hack.Xenosaga/Hack.Xenosaga.csproj
  6. +1977
    -0
      Hack.Xenosaga/Process/JavaClass.cs
  7. +472
    -0
      Hack.Xenosaga/Process/Scripts.cs
  8. +14
    -12
      Hack.Xenosaga/Process/Unpack.cs
  9. +11
    -3
      Hack.Xenosaga/Xenosaga.cs
  10. +2
    -1
      README.md

+ 6
- 0
Hack.Xenosaga/App.config View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
</startup>
</configuration>

+ 44
- 0
Hack.Xenosaga/Common/Extension.cs View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Hack.Xenosaga.Common
{
public static class Extension
{
/// <summary>
/// Ecrit un bloc d'octet dans le flux actuel en utilisant des données lues dans une mémoire tampon (la position du flux actuel reste inchangée).
/// </summary>
/// <param name="buffer">Mémoire tamon dans laquelle lire les données</param>
/// <param name="offset">Dans buffer, offset d'octet de base zéro d'où commencer la copie des octets dans le flux actuel</param>
/// <param name="count">Nombre maximal d'octets à écrire</param>
/// <param name="position">Position dans le flux actuel où écrire les données</param>
public static void Write(this MemoryStream ms, byte[] buffer, int offset, int count, long position)
{
long pos = ms.Position;
ms.Position = position;
ms.Write(buffer, offset, count);
ms.Position = pos;
}
/// <summary>
/// Lit une séquence de 4 octets à partir du flux actuel et avance la position dans le flux du nombre d'octets lus.
/// </summary>
/// <returns>Valeur lue</returns>
public static UInt32 Read(this MemoryStream ms)
{
UInt32 value = 0;
for (int i = 0; i < 4; i++)
value += (UInt32)(ms.ReadByte() << (i * 8) );
return value;
}
}
}

+ 47
- 5
Hack.Xenosaga/Common/Functions.cs View File

@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
@ -9,17 +10,20 @@ namespace Hack.Xenosaga.Common
public static void usage()
{
Trace.WriteLine("");
Trace.WriteLine("xenosaga <option> <file> [regroup]");
Trace.WriteLine("xenosaga <option> <file> [encode/regroup]");
Trace.WriteLine(" option : -l = List files from index");
Trace.WriteLine(" -u = Unpack files from index");
Trace.WriteLine(" -p = Pack files from index");
Trace.WriteLine(" -e = Extract text from file");
Trace.WriteLine(" file : Index file (the first one of each decade : xenosaga.00, xenosaga.10, xenosaga.20...)");
Trace.WriteLine(" regroup : true to regroup all files in only one (ex: 11, 12, 13 in 11) ; false or empty (default) to keep the same system of increment files");
Trace.WriteLine(" encode : (-e) Encoding of the output/input file (ANSI( default), ASCII, UTF8, UNICODE)");
Trace.WriteLine(" regroup : (-p) true to pack all files in only one (ex: 11, 12, 13 in 11) ; false or empty (default) to keep the same system of increment files");
}
public static bool CheckArgs(string[] args, out Variables.stArgs listArgs)
{
listArgs = new Variables.stArgs();
List<string> listActions = new List<string>() { "-l", "-p", "-u", "-e", "-i" };
Trace.Write("Checking arguments : ");
@ -29,13 +33,13 @@ namespace Hack.Xenosaga.Common
return false;
}
if (args[0] != "-l" && args[0] != "-p" && args[0] != "-u" && args[0] != "-e")
if (!listActions.Contains(args[0]))
{
Trace.WriteLine("Incorrect parameter - unknown <option>");
return false;
}
if (!File.Exists(args[1]))
if (!File.Exists(args[1]) && !Directory.Exists(args[1]))
{
Trace.WriteLine("Incorrect parameter - unknown <file>");
return false;
@ -46,11 +50,39 @@ namespace Hack.Xenosaga.Common
Trace.WriteLine("Incorrect parameter - unknown <regroup>");
return false;
}
if ((args.Length == 3 && (args[0] == "-e" || args[0] == "-i")) && (args[2] != "ANSI" && args[2] != "ASCII" && args[2] != "UTF8" && args[2] != "UNICODE"))
{
Trace.WriteLine("Incorrect parameter - unknown <encode>");
return false;
}
listArgs.option = args[0];
listArgs.filename = args[1];
if (args.Length == 3 && listArgs.option == "-p")
listArgs.regroup = args[2] == "true" ? true : false;
listArgs.encode = args.Length == 2 ? Encoding.Default : null;
if (args.Length == 3 && (listArgs.option == "-e" || listArgs.option == "-i"))
{
switch (args[2])
{
case "ASCII":
listArgs.encode = Encoding.ASCII;
break;
case "UTF8":
listArgs.encode = Encoding.UTF8;
break;
case "UNICODE":
listArgs.encode = Encoding.Unicode;
break;
case "ANSI":
default:
listArgs.encode = Encoding.Default;
break;
}
}
Trace.WriteLine("OK");
@ -88,5 +120,15 @@ namespace Hack.Xenosaga.Common
}
}
public static void Padding(MemoryStream ms)
{
long size = ms.Length;
while (size % 4 != 0)
{
ms.WriteByte(0);
size++;
}
}
}
}

+ 5
- 2
Hack.Xenosaga/Common/Variables.cs View File

@ -1,4 +1,5 @@

using System.Text;
namespace Hack.Xenosaga.Common
{
public class Variables
@ -7,6 +8,7 @@ namespace Hack.Xenosaga.Common
{
public string option;
public string filename;
public Encoding encode;
public bool regroup;
}
@ -14,8 +16,9 @@ namespace Hack.Xenosaga.Common
public const string dirExtract = "02-EXTRACT/";
public const string dirInsert = "03-INSERT/";
public const string dirPack = "04-PACK/";
public const string dirFinal = "05-FINAL/";
public const string tblCard = "TABLES/card_ANSI.tbl";
public const string tblEvt = "TABLES/evt_ANSI.tbl";
}
}

+ 5
- 8
Hack.Xenosaga/Hack.Xenosaga.csproj View File

@ -39,14 +39,9 @@
<StartupObject>Hack.Xenosaga.Xenosaga</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="cCalculation">
<HintPath>Include\cCalculation.dll</HintPath>
</Reference>
<Reference Include="cPointers">
<HintPath>Include\cPointers.dll</HintPath>
</Reference>
<Reference Include="cTable">
<HintPath>Include\cTable.dll</HintPath>
<Reference Include="Hack.Tools, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Hack.Tools\Build\bin\Debug\Hack.Tools.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@ -57,9 +52,11 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common\Extension.cs" />
<Compile Include="Common\Functions.cs" />
<Compile Include="Common\PathElement.cs" />
<Compile Include="Common\Variables.cs" />
<Compile Include="Process\JavaClass.cs" />
<Compile Include="Process\Scripts.cs" />
<Compile Include="Process\Unpack.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />


+ 1977
- 0
Hack.Xenosaga/Process/JavaClass.cs
File diff suppressed because it is too large
View File


+ 472
- 0
Hack.Xenosaga/Process/Scripts.cs View File

@ -0,0 +1,472 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Hack.Xenosaga.Common;
using Hack.Tools.Common;
using Hack.Tools.Pointers;
using Hack.Tools.Table;
namespace Hack.Xenosaga.Process
{
public class Scripts
{
#region Private methods
private static Table loadTable(string tblName, Encoding encode)
{
Table tbl = new Table(encode);
tbl.Load(tblName);
if (tblName == Variables.tblCard)
{
tbl.Remove("2C");
tbl.Remove("27");
}
return tbl;
}
#region card.dat
private static bool extractCard(string filename, Encoding encode)
{
int nbBlocs = 0;
try
{
Trace.Write("Extract card.dat : ");
Directory.CreateDirectory(Variables.dirExtract);
Directory.CreateDirectory(Variables.dirInsert);
using (BinaryReader br = new BinaryReader(File.Open(filename, FileMode.Open)))
{
cTblPointers tblPt = new cTblPointers();
Table tbl = new Table(encode);
tbl.Load(Variables.tblCard);
nbBlocs = br.ReadInt32();
br.BaseStream.Seek(0x10, SeekOrigin.Begin);
// Get all pointers
for (int i = 0; i < nbBlocs; i++)
{
for (int j = 0; j < 6; j++)
br.ReadUInt32();
tblPt.add(br.BaseStream.Position, br.ReadUInt32());
tblPt.add(br.BaseStream.Position, br.ReadUInt32());
br.ReadUInt32();
}
using (StreamWriter sw = new StreamWriter(Variables.dirExtract + filename + ".txt", false, encode))
{
// Extracting text from pointers
foreach (Pointers pt in tblPt.ListPointers)
{
br.BaseStream.Seek((long)pt.Value, SeekOrigin.Begin);
sw.Write(string.Format(pt.Format, pt.Adress, pt.Num));
sw.Write(br.BytesToStringWithTable(tbl));
}
}
}
Trace.WriteLine("OK");
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("ERROR : {0}", ex.Message));
return false;
}
return true;
}
private static bool insertCard(string filename, Encoding encode)
{
int nbBlocs = 0;
try
{
Trace.Write("Insert card.dat : ");
using (BinaryReader brIn = new BinaryReader(File.Open(filename, FileMode.Open)))
using (MemoryStream ms = new MemoryStream())
{
cTblPointers tblPt = new cTblPointers();
Table tbl = loadTable(Variables.tblCard, encode);
nbBlocs = brIn.ReadInt32();
ms.Write(BitConverter.GetBytes(nbBlocs), 0, 4);
for (int i = 0; i < 12; i++)
ms.WriteByte(brIn.ReadByte());
for (int i = 0; i < nbBlocs; i++)
{
for (int j = 0; j < 9; j++)
ms.Write(BitConverter.GetBytes(brIn.ReadUInt32()), 0, 4);
}
using (BinaryReader brTxt = new BinaryReader(File.Open(Variables.dirInsert + filename + ".txt", FileMode.Open), encode))
{
string line = "";
int sizeMax = tbl.ValueMaxSize;
if (tblPt.SizeFormat > tbl.ValueMaxSize)
sizeMax = tblPt.SizeFormat;
long pos = 0;
while (pos < brTxt.BaseStream.Length)
{
brTxt.BaseStream.Seek(pos, SeekOrigin.Begin);
if (brTxt.BaseStream.Length - pos < sizeMax)
sizeMax = (int)(brTxt.BaseStream.Length - pos);
line = brTxt.BytestoString(encode, sizeMax);
int adress, num;
if (tblPt.textIsPointerInfo(line))
{
tblPt.getInfoPt(line, out adress, out num);
tblPt.add(adress, (ulong)ms.Position, 2, (int)typeEndian.LITTLE, num);
pos += tblPt.SizeFormat;
}
else
{
stElement stResult = tbl.FindKeyFromValue(line.ToCharArray());
switch (stResult.type)
{
case typeEntries.NORMAL:
case typeEntries.ENDBLOCK:
ms.Write(stResult.keyBytes, 0, stResult.keySize);
break;
case typeEntries.PARAM:
break;
case typeEntries.NOT_FOUND:
Trace.WriteLine(string.Format("ERROR : Unknown character {0} in {1}", line.Substring(0, 1), line));
return false;
}
pos += stResult.valueSize;
}
}
}
// Write pointer in file
tblPt.insertPointersToFile(ms);
Directory.CreateDirectory(Variables.dirPack);
using (FileStream fs = new FileStream(Variables.dirPack + filename, FileMode.Create))
{
ms.Position = 0;
ms.CopyTo(fs);
}
}
Trace.WriteLine("OK");
return true;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("ERROR : {0}", ex.Message));
return false;
}
}
#endregion
#region *.evt
private static bool decompilEvt(string nameFile, MemoryStream ms)
{
Encoding encode = new UTF8Encoding(false);
using (BinaryReader br = new BinaryReader(ms))
{
JavaClass jc = new JavaClass();
jc.load(br);
//using (StreamWriter sw = new StreamWriter("ST0210_constantes.txt"))
//{
// jc.writeConstants(sw);
//}
Dictionary<int, byte[]> dictBytes = jc.getConstantsBytes();
if (dictBytes.Count > 0)
{
using (StreamWriter sw = new StreamWriter(Variables.dirExtract + nameFile + ".txt", false, encode))
{
Table tbl = loadTable(Variables.tblEvt, Encoding.Default);
foreach (KeyValuePair<int, byte[]> bytes in dictBytes)
{
sw.Write(string.Format("[{0:X4}]\n", bytes.Key));
if (bytes.Value != null)
sw.Write(tbl.BytesToString(bytes.Value));
}
}
}
}
return true;
}
private static MemoryStream compilEvt(evtClass.stFile file)
{
MemoryStream ms = new MemoryStream();
using (BinaryReader br = new BinaryReader(ms))
{
JavaClass jc = new JavaClass();
jc.load(br);
}
return ms;
}
private static bool extractEvt(string pathName, Encoding encode)
{
try
{
Trace.Write(string.Format("Extract {0} : ", pathName));
evtClass evtFile = new evtClass(pathName);
Directory.CreateDirectory(Variables.dirExtract);
Directory.CreateDirectory(Variables.dirInsert);
foreach (evtClass.stFile file in evtFile.listFiles)
{
using (MemoryStream ms = new MemoryStream())
{
ms.Write(file.file, 0, (int)file.fileLength);
ms.Position = 0;
decompilEvt(file.name, ms);
}
}
Trace.WriteLine("OK");
return true;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("ERROR : {0}", ex.Message));
return false;
}
}
private static bool insertEvt(string pathName, Encoding encode)
{
try
{
Trace.WriteLine(string.Format("Insert {0} : ", pathName));
using (BinaryReader br = new BinaryReader(File.Open(pathName, FileMode.Open)))
{
evtClass origEvt = new evtClass(br);
Table tbl = loadTable(Variables.tblEvt, Encoding.Default);
using (MemoryStream destEvt = new MemoryStream())
{
destEvt.Write(BitConverter.GetBytes(origEvt.id), 0, 4);
destEvt.Write(BitConverter.GetBytes(origEvt.unkAdr4), 0, 2);
destEvt.Write(BitConverter.GetBytes(origEvt.unkAdr6), 0, 2);
destEvt.Position = 16;
destEvt.Write(BitConverter.GetBytes(origEvt.unkAdr16), 0, 2);
destEvt.Write(BitConverter.GetBytes(origEvt.nbFiles), 0, 2);
destEvt.Position = 16 * origEvt.nbFiles + 20;
using (MemoryStream msIndex = new MemoryStream())
using (MemoryStream msName = new MemoryStream())
{
msName.WriteByte(0);
msName.WriteByte(0);
foreach (evtClass.stFile classFile in origEvt.listFiles)
{
msIndex.Write(BitConverter.GetBytes(msName.Position), 0, 4);
msIndex.Write(BitConverter.GetBytes(classFile.nameLength), 0, 4);
msIndex.Write(BitConverter.GetBytes(destEvt.Position), 0, 4);
msName.Write(encode.GetBytes(classFile.name), 0, (int)classFile.nameLength);
msName.WriteByte(0);
if (File.Exists(Variables.dirInsert + classFile.name + ".txt"))
{
Dictionary<string, byte[]> list = tbl.StringToBytesArray(Variables.dirInsert + classFile.name + ".txt", new Regex(@"\[(.{4})\]\n"));
JavaClass jc = new JavaClass();
using (MemoryStream msFile = new MemoryStream())
{
msFile.Write(classFile.file, 0, (int)classFile.fileLength);
msFile.Position = 0;
using (BinaryReader brFile = new BinaryReader(msFile))
{
br.BaseStream.Position = 0;
jc.load(brFile);
}
}
Dictionary<int, byte[]> dictBytes = jc.getConstantsBytes();
if (dictBytes.Count > 0)
{
string pt = "";
foreach (KeyValuePair<int, byte[]> bytes in dictBytes)
{
pt = string.Format("{0:X4}", bytes.Key);
if (dictBytes.ContainsKey(Convert.ToInt32(pt, 16)) && list[pt] != null)
jc.setConstantsBytes(bytes.Key, list[pt]);
}
}
MemoryStream newFile = jc.compilClass(classFile);
msIndex.Write(BitConverter.GetBytes(newFile.Length), 0, 4);
destEvt.Write(newFile.ToArray(), 0, (int)newFile.Length);
}
else
{
msIndex.Write(BitConverter.GetBytes(classFile.fileLength), 0, 4);
destEvt.Write(classFile.file, 0, (int)classFile.fileLength);
}
Functions.Padding(destEvt);
}
long posName = destEvt.Position;
// Write filename table
msName.Write(BitConverter.GetBytes(msName.Length-2), 0, 2, 0);
msName.Position = 0;
destEvt.Write(BitConverter.GetBytes(posName), 0, 4, 12);
destEvt.Write(msName.ToArray(), 0, (int)msName.Length);
destEvt.Write(BitConverter.GetBytes(destEvt.Length), 0, 2, 8);
// Write index of files
destEvt.Position = 20;
msIndex.Position = 0;
for (int i = 0; i < origEvt.nbFiles; i++)
{
destEvt.Write(BitConverter.GetBytes(msIndex.Read() + posName), 0, 4);
destEvt.Write(BitConverter.GetBytes(msIndex.Read()), 0, 4);
destEvt.Write(BitConverter.GetBytes(msIndex.Read()), 0, 4);
destEvt.Write(BitConverter.GetBytes(msIndex.Read()), 0, 4);
}
}
using (FileStream fs = new FileStream(Variables.dirPack + Path.GetFileName(pathName), FileMode.Create))
{
destEvt.Position = 0;
fs.Write(destEvt.ToArray(), 0, (int)destEvt.Length);
}
}
}
Trace.WriteLine("OK");
return true;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("ERROR : {0}", ex.Message));
return false;
}
}
#endregion
#endregion
#region Public methods
public static void extract(string pathName, Encoding encode)
{
bool result;
if (File.Exists(pathName))
{
if (Path.GetFileName(pathName) == "card.dat")
result = extractCard(pathName, encode);
if (Path.GetExtension(pathName) == ".evt")
result = extractEvt(pathName, encode);
}
else if (Directory.Exists(pathName))
{
var listFiles = Directory
.EnumerateFiles(pathName)
.Where(file => file.ToLower().EndsWith("dat") || file.ToLower().EndsWith("evt"))
.ToList();
foreach (string file in listFiles)
{
if (Path.GetFileName(file) == "card.dat")
result = extractCard(file.ToLower(), encode);
else if (Path.GetExtension(file) == ".evt")
result = extractEvt(file.ToLower(), encode);
}
}
else
Trace.WriteLine("Unknown file or directory");
}
public static void insert(string pathName, Encoding encode)
{
bool result;
if (File.Exists(pathName))
{
if (Path.GetFileName(pathName) == "card.dat")
result = insertCard(pathName, encode);
if (Path.GetExtension(pathName) == ".evt")
result = insertEvt(pathName, encode);
}
else if (Directory.Exists(pathName))
{
var listFiles = Directory
.EnumerateFiles(pathName)
.Where(file => file.ToLower().EndsWith("dat") || file.ToLower().EndsWith("evt"))
.ToList();
foreach (string file in listFiles)
{
if (Path.GetFileName(file) == "card.dat")
result = insertCard(file.ToLower(), encode);
else if (Path.GetExtension(file) == ".evt")
result = insertEvt(file.ToLower(), encode);
}
}
else
Trace.WriteLine("Unknown file of directory");
}
#endregion
}
}

+ 14
- 12
Hack.Xenosaga/Process/Unpack.cs View File

@ -8,6 +8,8 @@ namespace Hack.Xenosaga.Process
{
class Unpack
{
public enum EnumSource { ISO_FILE, PACK_DIR, UNPACK_DIR };
private static BinaryWriter _bw;
private const string _listExtension = ".lst";
private const string _copyright = "Hacked by BahaBulle (c)2016\0";
@ -193,18 +195,18 @@ namespace Hack.Xenosaga.Process
return size;
}
private static int findFile(pathElement entry, string numberFile)
private static EnumSource findFile(pathElement entry, string numberFile)
{
// Check if the file is in the INSERT directory
if (File.Exists(Variables.dirInsert + entry.Name))
return 1;
if (File.Exists(Variables.dirPack + entry.Name))
return EnumSource.PACK_DIR;
// Check if the file is in the UNPACK directory
if (File.Exists(Variables.dirUnpack + numberFile + entry.FullPath))
return 2;
return EnumSource.UNPACK_DIR;
// Otherwise, get the file from iso files
return -1;
return EnumSource.ISO_FILE;
}
#endregion
@ -359,8 +361,8 @@ namespace Hack.Xenosaga.Process
Trace.WriteLine("Inserting files");
Trace.Indent();
string s_pathname = string.Format("{0}{1}.", Variables.dirPack, fileNameBase);
Directory.CreateDirectory(Variables.dirPack);
string s_pathname = string.Format("{0}{1}.", Variables.dirFinal, fileNameBase);
Directory.CreateDirectory(Variables.dirFinal);
foreach (pathElement entryPath in index.getEntries())
{
@ -368,15 +370,15 @@ namespace Hack.Xenosaga.Process
{
long size = 0;
int idFile = findFile(entryPath, string.Format("{0:D2}", numFileIndex));
EnumSource idFile = findFile(entryPath, string.Format("{0:D2}", numFileIndex));
if (idFile == 1 || idFile == 2)
if (idFile == EnumSource.PACK_DIR || idFile == EnumSource.UNPACK_DIR)
{
if (idFile == 1)
if (idFile == EnumSource.PACK_DIR)
{
Trace.WriteLine(string.Format("From {0} : file {1}", Variables.dirInsert, entryPath.FullPath));
Trace.WriteLine(string.Format("From {0} : file {1}", Variables.dirPack, entryPath.FullPath));
filename = Variables.dirInsert + entryPath.Name;
filename = Variables.dirPack + entryPath.Name;
}
else
{


+ 11
- 3
Hack.Xenosaga/Xenosaga.cs View File

@ -22,15 +22,23 @@ namespace Hack.Xenosaga
switch (listArgs.option)
{
case "-l":
Unpack.listFiles(listArgs.filename);
Unpack.listFiles(listArgs.filename.ToLower());
break;
case "-p":
Unpack.packIsoFiles(listArgs.filename, listArgs.regroup);
Unpack.packIsoFiles(listArgs.filename.ToLower(), listArgs.regroup);
break;
case "-u":
Unpack.unpackIsoFiles(listArgs.filename);
Unpack.unpackIsoFiles(listArgs.filename.ToLower());
break;
case "-e":
Scripts.extract(listArgs.filename.ToLower(), listArgs.encode);
break;
case "-i":
Scripts.insert(listArgs.filename.ToLower(), listArgs.encode);
break;
}
}


+ 2
- 1
README.md View File

@ -6,6 +6,7 @@ xenosaga &lt;option&gt; &lt;file&gt; [regroup]
option : -l = List files from index
-u = Unpack files from index
-p = Pack files from index
-e = Extract text from file
file : Index file (the first one of each decade : xenosaga.00, xenosaga.10, xenosaga.20...)
regroup : true to regroup all files in only one (ex: 11, 12, 13 in 11) ; false or empty (default) to keep the same system of increment files
regroup : true to pack all files in only one (ex: 11, 12, 13 in 11) ; false or empty (default) to keep the same system of increment files
</pre>

Loading…
Cancel
Save