1.
2. Java
3. C#
is used to get the source codes of latest version.
java.lang.NullPointerException: Cannot read the array length because "array" is null
2.7 讀取CSV讀取CSV的功能我使用了
Univocity Parser,請記得在ReplaceEXDF.java加上import。
ConfigApplicationPanel.java
```java=152
// added
// 因為新增了這一段,以下各物件的setBounds的第二項都要增加30(往下移)。
// 為了因應CSV,新增CSV選項。
this.fLangLable.setBounds(30, 70, 100, 25);
this.fLangLable.setFont(new Font("Microsoft Yahei", 1, 13));
this.fLangLable.setForeground(new Color(110, 110, 110));
add(this.fLangLable, 0);
this.fLangLableVal = new JComboBox<>();
this.fLangLableVal.addItem("CSV");
this.fLangLableVal.addItem("日文");
this.fLangLableVal.addItem("英文");
this.fLangLableVal.addItem("德文");
this.fLangLableVal.addItem("法文");
this.fLangLableVal.addItem("簡體中文");
this.fLangLableVal.setBounds(100, 70, 160, 23);
this.fLangLableVal.setFont(new Font("Microsoft Yahei", 1, 13));
this.fLangLableVal.setForeground(new Color(110, 110, 110));
this.fLangLableVal.setOpaque(false);
this.fLangLableVal.setFocusable(false);
add(this.fLangLableVal, 0);
```
Language.java
```java=3
public enum Language {
CHS("簡體中文", "CHS", "chs", "5"),
CHT("正體中文", "CHT", "cht", "5"),
CSV("CSV", "CSV", "csv", "6"),
JA("日文", "JA", "ja", "0"),
EN("英文", "EN", "en-gb", "1"),
DE("德文", "DE", "de", "2"),
FR("法文", "FR", "fr", "3");
```
ReplaceEXDF.java 在以下位置新增CSV用的內容
```java=162
if ((exhSE.getLangs()).length > 0) {
// added for CSV
HashMap<Integer, Integer> offsetMap = new HashMap<>();
// ArrayList<List<String>> dataList = new ArrayList<List<String>>();
HashMap<Integer, String[]> csvDataMap = new HashMap<>();
if (this.csv) {
try {
CsvParserSettings csvSettings = new CsvParserSettings();
csvSettings.setMaxCharsPerColumn(-1);
csvSettings.setMaxColumns(4096);
CsvParser csvParser = new CsvParser(csvSettings);
String csvPath = "resource" + File.separator + "rawexd" + File.separator + replaceFile.substring(4, replaceFile.indexOf(".")) + ".csv";
if (new File(csvPath).exists()) {
List<String[]> allRows = csvParser.parseAll(new FileReader(csvPath, StandardCharsets.UTF_8));
for (int i = 1; i < allRows.get(1).length; i++) {
offsetMap.put(Integer.valueOf((allRows.get(1))), i - 1);
}
int rowNumber = allRows.size();
for (int i = 3; i < rowNumber; i++) {
csvDataMap.put(Integer.valueOf((allRows.get(i))[0]), Arrays.copyOfRange(allRows.get(i), 1, allRows.get(i).length));
}
} else {
System.out.println("\t\tCSV file not exists! " + csvPath);
continue;
}
} catch (Exception csvFileIndexValueException) {
System.out.println("\t\tCSV Exception. " + csvFileIndexValueException.getMessage());
continue;
}
}
```
```java=269
// 更新文本內容
// EXD/warp/WarpInnUldah.EXH -> exd/warp/warpinnuldah_xxxxx_xxxxx
String transKey = replaceFile.substring(0, replaceFile.lastIndexOf(".")).toLowerCase() + "_" + String.valueOf(listEntryIndex) + "_" + String.valueOf(stringCount);
if (this.csv) {
// added CSV mode
// need a name like quest or quest/000/ClsHrv001_00003 (replaceFile)
// need an offset (exdfDatasetSE.offset)
Integer offsetInteger = offsetMap.get(Integer.valueOf(exdfDatasetSE.offset));
String[] rowStrings = csvDataMap.get(listEntryIndex);
if (rowStrings != null) {
String readString = rowStrings[offsetInteger];
String newString = new String();
boolean isHexString = false;
if (readString != null) {
for (int i = 0; i < readString.length(); i++) {
char currentChar = readString.charAt(i);
switch (currentChar) {
case '<': {
if ((readString.charAt(i+1) == 'h') && (readString.charAt(i+2) == 'e') && (readString.charAt(i+3) == 'x')) {
if (isHexString) {
throw new Exception("TagInTagException!" + readString);
} else {
isHexString = true;
}
}
if (newString.length() > 0) {
newFFXIVString = ArrayUtil.append(newFFXIVString, newString.getBytes("UTF-8"));
newString = "";
}
newString += currentChar;
break;
}
case '>': {
newString += currentChar;
if (isHexString) {
newFFXIVString = ArrayUtil.append(newFFXIVString, HexUtils.hexStringToBytes(newString.substring(5, newString.length() - 1)));
newString = "";
isHexString = false;
}
break;
}
default: {
newString += currentChar;
}
}
}
if (newString.length() > 0) {
newFFXIVString = ArrayUtil.append(newFFXIVString, newString.getBytes("UTF-8"));
newString = "";
}
} else {
System.out.println("\t\tCannot find listEntryIndex " + String.valueOf(listEntryIndex));
newFFXIVString = ArrayUtil.append(newFFXIVString, jaBytes);
}
}
// added end ^
} else if (Config.getConfigResource("transtable") != null && Config.getProperty("transtable", transKey) != null && Config.getProperty("transtable", transKey).length() > 0) {
```
如果想要,前面也可以作相對應修改,讓我們可以不用在`common/text`資料夾下放陸版檔案。
```java=153
boolean cnEXHFileAvailable = true;
if (!this.csv) {
try {
SqPackIndexFile exhIndexFileCN = (SqPackIndexFile)((SqPackIndexFolder)indexCN.get(filePatchCRC)).getFiles().get(exhFileCRC);
byte[] exhFileCN = extractFile(this.pathToIndexCN, exhIndexFileCN.getOffset());
exhCN = new EXHFFile(exhFileCN);
// 添加對照的StringDataset
int cnDatasetPossition = 0;
if (datasetStringCount(exhSE.getDatasets()) > 0 && datasetStringCount(exhSE.getDatasets()) == datasetStringCount(exhCN.getDatasets()))
for (EXDFDataset datasetSE : exhSE.getDatasets()) {
if (datasetSE.type == 0) {
while ((exhCN.getDatasets()[cnDatasetPossition]).type != 0)
cnDatasetPossition++;
datasetMap.put(datasetSE, exhCN.getDatasets()[cnDatasetPossition++]);
}
}
} catch (Exception cnEXHFileException) {
cnEXHFileAvailable = false;
}
} else {
cnEXHFileAvailable = false;
}
if ((exhSE.getLangs()).length > 0) {
// added for CSV
HashMap<Integer, Integer> offsetMap = new HashMap<>();
// ArrayList<List<String>> dataList = new ArrayList<List<String>>();
HashMap<Integer, String[]> csvDataMap = new HashMap<>();
if (this.csv) {
try {
CsvParserSettings csvSettings = new CsvParserSettings();
csvSettings.setMaxCharsPerColumn(-1);
csvSettings.setMaxColumns(4096);
CsvParser csvParser = new CsvParser(csvSettings);
String csvPath = "resource" + File.separator + "rawexd" + File.separator + replaceFile.substring(4, replaceFile.indexOf(".")) + ".csv";
if (new File(csvPath).exists()) {
List<String[]> allRows = csvParser.parseAll(new FileReader(csvPath));
for (int i = 1; i < allRows.get(1).length; i++) {
offsetMap.put(Integer.valueOf((allRows.get(1))), i - 1);
}
int rowNumber = allRows.size();
for (int i = 3; i < rowNumber; i++) {
csvDataMap.put(Integer.valueOf((allRows.get(i))[0]), Arrays.copyOfRange(allRows.get(i), 1, allRows.get(i).length));
}
} else {
System.out.println("\t\tCSV file not exists! " + csvPath);
continue;
}
} catch (Exception csvFileIndexValueException) {
System.out.println("\t\tCSV Exception. " + csvFileIndexValueException.getMessage());
continue;
}
}
```
然後是ReplaceThread.java
```java=39
if ((new File("resource" + File.separator + "text" + File.separator + "0a0000.win32.index")).exists()) {
(new ReplaceEXDF(this.resourceFolder + File.separator + "0a0000.win32.index", "resource" + File.separator + "text" + File.separator + "0a0000.win32.index", percentPanel)).replace();
} else if ((new File("resource" + File.separator + "rawexd" + File.separator + "Achievement.csv")).exists()) {
(new ReplaceEXDF(this.resourceFolder + File.separator + "0a0000.win32.index", "resource" + File.separator + "rawexd" + File.separator + "Achievement.csv", percentPanel)).replace();
} else {
System.out.println("No resource files detected!");
}
```
2.8 字型問題在我們可以選擇用已漢化的包作為放進`resource/text/`的檔案後,發現這樣做的時候字體包會不太一樣,導致某些圖像類的英數字區塊會出現亂碼。
![]()
因為ReplaceFont和ReplaceEXDF是處理不同的檔案,目前先用手上的漢化覆蓋檔的`000000.win32....`來直接覆蓋遊戲檔案,可以解決。如果手上有覆蓋漢化檔,可以用以下方式解決。
2.8.1 拆包替換使用
FFXIV Explorer查看漢化覆蓋檔的000000.win32.dat0裡面的檔案,然後和提摩院的漢化`resource/font`資料夾內的檔案做比較。
除了完全一樣的檔案外,部分檔案在FFXIV Explorer無法正確顯示。透過比較binary內容,確定以下檔案對應:
漢化包檔案 |
覆蓋檔FFXIV Explorer拆包檔案 |
axis_12.fdt |
~a9b7b1a2 |
axis_14.fdt |
~26f74402 |
axis_18.fdt |
~e307a903 |
axis_36.fdt |
~11ffb669 |
axis_96.fdt |
~b064950f |
miedingermid_10.fdt |
~7b3aa512 |
miedingermid_12.fdt |
~1faf672 |
miedingermid_14.fdt |
~8eba03d2 |
miedingermid_18.fdt |
~4b4aeed3 |
trumpgothic_184.fdt |
~ffa087be |
除此之外,以下檔案與原版檔案不同,且漢化包中沒有。我們猜測這些檔案和其他檔案一樣,都是font + number + .fdt的格式,所以手猜猜出了四個,最後一個是用程式窮舉直到找出結果為止。將這些檔案改名後放到`resource/font`裡面,就可以解決字體問題。
覆蓋檔FFXIV Explorer拆包檔案 |
嘗試結果 |
~73CDF66F |
meidinger_40.fdt |
~B9B3F2B9 |
miedingermid_36.fdt |
~D2024114 |
jupiter_90.fdt |
~DC44E770 |
trumpgothic_68.fdt |
~E1DCA76A |
jupiter_46.fdt |
2.9 指令碼處理可參考
這篇。例如最基礎的換行指令碼(`0x02100103`)基本的結構是:
02 |
10 |
01 |
03 |
指令碼開頭 |
指令碼類型 |
指令碼長度 |
指令碼結尾 |
- 指令碼類型可以參考SaintCoinach的`TagType.cs`
- 指令碼長度$n$是指接下來的$n - 1$的byte屬於指令內,不含指令碼結尾,例如`0x01`代表$1-1=0$
- 特殊指令碼長度可以參考SaintCoinach的`XIVStringDecoder.cs`的`protected static int GetInteger(BinaryReader input, List<byte> lenByte)`等函式
- 指令碼中的特殊指令碼(例如If判斷句)會以`0xFF`開頭,後面接一個長度,格式與上類似。
- `XIVStringDecoder.cs`的`protected INode DecodeExpression(BinaryReader input)`
- `DecodeExpressionType.cs`
- 其他關於SaintCoinach的實作和修改列於後面章節。
另一個例子:
更複雜的例子:
```
020851E4E80201FF25024804F2021E03024904F2021F03e78db5e99abce999a3e7879f02490201030248020103FF25024804F2022003024904F2022103e6b8a1e9b489e999a3e7879f0249020103024802010303
<hex:020851E4E80201FF25><hex:024804F2021E03><hex:024904F2021F03>獵隼陣營<hex:0249020103><hex:0248020103><hex:FF25><hex:024804F2022003><hex:024904F2022103>渡鴉陣營<hex:0249020103><hex:0248020103><hex:03>
```
switch case:
`<hex:020957E802FF10E38199E381B9E381A6E8A1A8E7A4BAFF1FE382B3E383B3E38397E383AAE383BCE38388E381AEE381BFE8A1A8E7A4BAFF22E69CAAE382B3E383B3E38397E383AAE383BCE38388E381AEE381BFE8A1A8E7A4BA03>`
`<Switch(IntegerParameter(1))><Case(1)>顯示全部</Case><Case(2)>只顯示已完成</Case><Case(3)>只顯示未完成</Case></Switch>`
- `02` 指令開頭
- `09` 指令類型:Switch
- `57` 指令長度
- `FF` 指令段落點,下一個byte`10`是段落長度
- `E38199E381B9E381A6E8A1A8E7A4BA` 內文,UTF-8為`すべて表示`
- `FF1F` 前byte為指令段落點,後byte為段落長度
- `E382B3E383B3E38397E383AAE383BCE38388E381AEE381BFE8A1A8E7A4BA` 內文,UTF-8為`コンプリートのみ表示`
- `FF22`
- `E69CAAE382B3E383B3E38397E383AAE383BCE38388E381AEE381BFE8A1A8E7A4BA` 內文,UTF-8為`未コンプリートのみ表示`
- `03` 指令結尾
3 SaintCoinach
- 安裝Visual Studio
- 匯入專案
- 工具 > Nuget > 套件管理器主控臺
- 輸入 `Update-Package -reinstall`
參考資料來源:https://docs.microsoft.com/zh-tw/nuget/consume-packages/reinstalling-and-updating-packages
3.1 讓SaintCoinach可以輸出Offset為了讓我們可以在csv直接讀到各個column的offset,我們需要讓其輸出offset資訊。我們修改`SaintCoinach.Cmd\ExdHelper.cs`:
```csharp=16
public static void SaveAsCsv(Ex.Relational.IRelationalSheet sheet, Language language, string path, bool writeRaw) {
using (var s = new StreamWriter(path, false, Encoding.UTF8)) {
var indexLine = new StringBuilder("key");
var nameLine = new StringBuilder("#");
var offsetLine = new StringBuilder("offset"); // added
var typeLine = new StringBuilder("int32");
var colIndices = new List<int>();
foreach (var col in sheet.Header.Columns) {
indexLine.AppendFormat(",{0}", col.Index);
nameLine.AppendFormat(",{0}", col.Name);
offsetLine.AppendFormat(",{0}", col.Offset); // added
typeLine.AppendFormat(",{0}", col.ValueType);
colIndices.Add(col.Index);
}
s.WriteLine(indexLine);
s.WriteLine(nameLine);
s.WriteLine(offsetLine); // added
s.WriteLine(typeLine);
ExdHelper.WriteRows(s, sheet, language, colIndices, writeRaw);
}
}
```
3.2 修改輸出格式雖然我們也可以像FFXIVChnTextPatch一樣將包在指令碼裡面的一般文字全部以hex呈現,但既然SaintCoinach做了更精細的分類,那麼沒道理不用。
首先從`XivStringDecoder.cs`開始。
在SC做decode的過程中,會將代表指令長度的bytes捨去或轉換成int做處理。我們為所有Decoder增加第四個引數lengthByteStr,讓他能以hex的形式輸出這個長度。
```csharp=11
public class XivStringDecoder {
public delegate INode TagDecoder(BinaryReader input, TagType tag, int length, String lengthByteStr);
```
```csharp=53
#region Constructor
public XivStringDecoder() {
this.DefaultTagDecoder = DecodeTagDefault;
SetDecoder(TagType.Clickable, (i, t, l, h) => DecodeGenericElementWithVariableArguments(i, t, l, h, 1, int.MaxValue)); // I have no idea.
SetDecoder(TagType.Color, DecodeColor);
SetDecoder(TagType.CommandIcon, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 1, false));
SetDecoder(TagType.Dash, (i, t, l, h) => new Nodes.StaticString(this.Dash));
SetDecoder(TagType.Emphasis, DecodeGenericSurroundingTag);
SetDecoder(TagType.Emphasis2, DecodeGenericSurroundingTag);
// TODO: Fixed
SetDecoder(TagType.Format, DecodeFormat);
SetDecoder(TagType.Gui, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 1, false));
SetDecoder(TagType.Highlight, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 0, true));
SetDecoder(TagType.If, DecodeIf);
SetDecoder(TagType.IfEquals, DecodeIfEquals);
// Indent
SetDecoder(TagType.InstanceContent, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 0, true));
SetDecoder(TagType.LineBreak, (i, t, l, h) => new Nodes.StaticString("<hex:02100103>"));
SetDecoder(TagType.Sheet, (i, t, l, h) => DecodeGenericElementWithVariableArguments(i, t, l, h, 2, int.MaxValue)); // Sheet name, Row[, Column[, Parameters]+]
SetDecoder(TagType.SheetDe, (i, t, l, h) => DecodeGenericElementWithVariableArguments(i, t, l, h, 3, int.MaxValue)); // Sheet name, Attributive row, Sheet row[, Sheet column[, Attributive index[, Parameters]+]
SetDecoder(TagType.SheetEn, (i, t, l, h) => DecodeGenericElementWithVariableArguments(i, t, l, h, 3, int.MaxValue)); // Sheet name, Attributive row, Sheet row[, Sheet column[, Attributive index[, Parameters]+]
SetDecoder(TagType.SheetFr, (i, t, l, h) => DecodeGenericElementWithVariableArguments(i, t, l, h, 3, int.MaxValue)); // Sheet name, Attributive row, Sheet row[, Sheet column[, Attributive index[, Parameters]+]
SetDecoder(TagType.SheetJa, (i, t, l, h) => DecodeGenericElementWithVariableArguments(i, t, l, h, 3, int.MaxValue)); // Sheet name, Attributive row, Sheet row[, Sheet column[, Attributive index[, Parameters]+]
SetDecoder(TagType.Split, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 3, false)); // Input expression, Seperator, Index to use
SetDecoder(TagType.Switch, DecodeSwitch);
SetDecoder(TagType.Time, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 1, false));
SetDecoder(TagType.TwoDigitValue, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 0, true));
// Unknowns
SetDecoder(TagType.Value, (i, t, l, h) => DecodeGenericElement(i, t, l, h, 0, true));
SetDecoder(TagType.ZeroPaddedValue, DecodeZeroPaddedValue);
}
```
修改主要Decode函式的規則如下。注意第二層Decode()也會被指令碼出現`0xFF`時呼叫,所以同樣新增`lenByte`──而且在這種情況下,`lenByte`會含有`0xFF`以及代表長度的byte(s)。
```csharp=97
#region Decode
public XivString Decode(byte[] buffer) {
using (var ms = new MemoryStream(buffer)) {
using (var r = new BinaryReader(ms, this.Encoding))
return Decode(r, buffer.Length, new List<byte> { } );
}
}
public XivString Decode(BinaryReader input, int length, List<byte> lenByte) {
// check input size
if (length < 0)
throw new ArgumentOutOfRangeException("length");
// set the end of the input
var end = input.BaseStream.Position + length;
if (end > input.BaseStream.Length)
throw new ArgumentOutOfRangeException("length");
var parts = new List<INode>();
var pendingStatic = new List<byte>();
// Add the bytes representing decode tag (if exist) and length to pendingStatic
if (lenByte.Count > 0) {
String forText = BitConverter.ToString(lenByte.ToArray()).Replace("-", String.Empty) + ">";
parts.Add(new Nodes.StaticString(forText));
}
// while loop until reaching the end
while (input.BaseStream.Position < end) {
var v = input.ReadByte();
if (v == TagStartMarker) {
// What this function does:
// If no item in "pending", just return
// A list of interface can take any instance of that interface
// TargetParts adds an element, which is the string version of "pending"
// Finally, remove everything in "pending"
// P.S. it modifies the references directly. (list = reference type)
AddStatic(pendingStatic, parts);
// DecodeTag: byte -> string; added into "parts"
parts.Add(DecodeTag(input));
if (input.BaseStream.Position > end)
throw new InvalidOperationException();
} else
pendingStatic.Add(v);
}
AddStatic(pendingStatic, parts);
// Add <hex: if needed
if (lenByte.Count > 0) {
parts.Add(new Nodes.StaticString("<hex:"));
}
return new XivString(parts);
}
```
如果讀入的bytes出現代表指令碼開頭的`0x02`,就會呼叫以下函式。這個函式會將輸入的binary code轉換成自定義的class `INode`。
```csharp=159
private INode DecodeTag(BinaryReader input) {
// the first byte means the tag type
var tag = (TagType)input.ReadByte();
// edited
// the second byte(s) means the length of commnad
List<byte> lengthByte = new List<byte> { };
var length = GetInteger(input, lengthByte);
String lengthByteStr = BitConverter.ToString(lengthByte.ToArray()).Replace("-", String.Empty);
var end = input.BaseStream.Position + length;
// System.Diagnostics.Trace.WriteLine(string.Format("{0} @ {1:X}h+{2:X}h", tag, input.BaseStream.Position, length));
TagDecoder decoder = null;
// ref and out:
// both means to modify the reference;
// "out" means it may not be initialized yet, so has to be done in the function.
// 這個方法傳回時,如果找到索引鍵,則包含與指定索引鍵相關聯的值,否則為 value 參數類型的預設值。這個參數會以未初始化的狀態傳遞。
_TagDecoders.TryGetValue(tag, out decoder);
// "??" operator: return the left side if it is not null; otherwise, return the right side.
// If tag in _TagDecoders, decoder will be that decoder; otherwise, it will be DefaultTagDecoder
var result = (decoder ?? DefaultTagDecoder)(input, tag, length, lengthByteStr);
if (input.BaseStream.Position != end)
{
// Triggered by two entries in LogMessage as of 3.15.
// Looks like a tag has some extra bits, as the end length is a proper TagEndMarker.
System.Diagnostics.Debug.WriteLine(string.Format("Position mismatch in XivStringDecoder.DecodeTag. Position {0} != predicted {1}.", input.BaseStream.Position, end));
input.BaseStream.Position = end;
}
if (input.ReadByte() != TagEndMarker)
throw new InvalidDataException();
return result;
}
```
修改各個Tag Decoder的規則:
```csharp=192
#region Generic
protected INode DecodeTagDefault(BinaryReader input, TagType tag, int length, String lenByte) {
return new Nodes.DefaultElement(tag, input.ReadBytes(length), lenByte);
}
protected INode DecodeExpression(BinaryReader input) {
var t = input.ReadByte();
// expressionTypeByte = t;
return DecodeExpression(input, (DecodeExpressionType)t);
}
protected INode DecodeExpression(BinaryReader input, DecodeExpressionType exprType) {
var t = (byte)exprType;
if (t < 0xD0) {
return new Nodes.StaticInteger(t - 1, ((byte)t).ToString("X2"));
}
if (t < 0xE0) {
return new Nodes.TopLevelParameter(t - 1, ((byte)t).ToString("X2"));
}
List<byte> addByte = new List<byte> { };
addByte.Add((Byte)exprType);
switch (exprType) {
case DecodeExpressionType.Decode: {
var len = GetInteger(input, addByte);
// XIVString is also an INode
return Decode(input, len, addByte);
}
case DecodeExpressionType.Byte: {
var expr = GetInteger(input, IntegerType.Byte, addByte);
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
return new Nodes.StaticInteger(expr, lenByte);
}
case DecodeExpressionType.Int16_MinusOne: {
var expr = GetInteger(input, IntegerType.Int16, addByte) - 1;
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
return new Nodes.StaticInteger(expr, lenByte);
}
case DecodeExpressionType.Int16_1:
case DecodeExpressionType.Int16_2: {
var expr = GetInteger(input, IntegerType.Int16, addByte);
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
return new Nodes.StaticInteger(expr, lenByte);
}
case DecodeExpressionType.Int24_MinusOne: {
var expr = GetInteger(input, IntegerType.Int24, addByte) - 1;
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
return new Nodes.StaticInteger(expr, lenByte);
}
case DecodeExpressionType.Int24: {
var expr = GetInteger(input, IntegerType.Int24, addByte);
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
return new Nodes.StaticInteger(expr, lenByte);
}
case DecodeExpressionType.Int24_Lsh8: {
var expr = GetInteger(input, IntegerType.Int24, addByte) << 8;
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
return new Nodes.StaticInteger(expr, lenByte);
}
case DecodeExpressionType.Int24_SafeZero: {
var v16 = input.ReadByte();
var v8 = input.ReadByte();
var v0 = input.ReadByte();
addByte.Add(v16);
addByte.Add(v8);
addByte.Add(v0);
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
int v = 0;
if (v16 != byte.MaxValue)
v |= v16 << 16;
if (v8 != byte.MaxValue)
v |= v8 << 8;
if (v0 != byte.MaxValue)
v |= v0;
return new Nodes.StaticInteger(v, lenByte);
}
case DecodeExpressionType.Int32: {
var expr = GetInteger(input, IntegerType.Int32, addByte);
var lenByte = BitConverter.ToString(addByte.ToArray()).Replace("-", String.Empty);
return new Nodes.StaticInteger(expr, lenByte);
}
case DecodeExpressionType.GreaterThanOrEqualTo:
case DecodeExpressionType.GreaterThan:
case DecodeExpressionType.LessThanOrEqualTo:
case DecodeExpressionType.LessThan:
case DecodeExpressionType.NotEqual:
case DecodeExpressionType.Equal: {
var left = DecodeExpression(input);
var right = DecodeExpression(input);
return new Nodes.Comparison(exprType, left, right);
}
case DecodeExpressionType.IntegerParameter:
case DecodeExpressionType.PlayerParameter:
case DecodeExpressionType.StringParameter:
case DecodeExpressionType.ObjectParameter: {
var parameter = DecodeExpression(input);
return new Nodes.Parameter(exprType, parameter);
}
default:
throw new NotSupportedException();
}
}
protected INode DecodeGenericElement(BinaryReader input, TagType tag, int length, String lenByte, int argCount, bool hasContent) {
if (length == 0) {
return new Nodes.EmptyElement(tag, lenByte);
}
var arguments = new INode[argCount];
for (var i = 0; i < argCount; ++i) {
arguments = DecodeExpression(input);
}
INode content = null;
if (hasContent) {
content = DecodeExpression(input);
}
return new Nodes.GenericElement(tag, content, lenByte, arguments);
}
protected INode DecodeGenericElementWithVariableArguments(BinaryReader input, TagType tag, int length, String lenByte, int minCount, int maxCount) {
var end = input.BaseStream.Position + length;
var args = new List<INode>();
for (var i = 0; i < maxCount && input.BaseStream.Position < end; ++i) {
args.Add(DecodeExpression(input));
}
return new Nodes.GenericElement(tag, null, lenByte, args);
}
protected INode DecodeGenericSurroundingTag(BinaryReader input, TagType tag, int length, String lenByte) {
if (length != 1)
throw new ArgumentOutOfRangeException("length");
List<byte> insideLenByte = new List<byte> { };
var status = GetInteger(input, insideLenByte);
lenByte += BitConverter.ToString(insideLenByte.ToArray()).Replace("-", String.Empty);
if (status == 0)
return new Nodes.CloseTag(tag, lenByte);
if (status == 1)
return new Nodes.OpenTag(tag, lenByte, null); /* should be lenByte or insideLenByte? */
throw new InvalidDataException();
}
#endregion
}
```
```csharp=+
#region Specific
protected INode DecodeZeroPaddedValue(BinaryReader input, TagType tag, int length, String lenByte) {
var val = DecodeExpression(input);
var arg = DecodeExpression(input);
return new GenericElement(tag, val, lenByte, arg);
}
protected INode DecodeColor(BinaryReader input, TagType tag, int length, String lenByte) {
var t = input.ReadByte();
// I think the byte should be added
if (length == 1 && t == 0xEC)
return new Nodes.CloseTag(tag, lenByte + t.ToString("X2"));
var color = DecodeExpression(input, (DecodeExpressionType)t);
return new Nodes.OpenTag(tag, lenByte, color);
}
protected INode DecodeFormat(BinaryReader input, TagType tag, int length, String lenByte) {
var end = input.BaseStream.Position + length;
var arg1 = DecodeExpression(input);
var arg2 = new Nodes.StaticByteArray(input.ReadBytes((int)(end - input.BaseStream.Position)));
return new Nodes.GenericElement(tag, null, lenByte, arg1, arg2);
}
protected INode DecodeIf(BinaryReader input, TagType tag, int length, String lenByte) {
var end = input.BaseStream.Position + length;
var condition = DecodeExpression(input);
INode trueValue, falseValue;
DecodeConditionalOutputs(input, (int)end, out trueValue, out falseValue);
return new Nodes.IfElement(tag, condition, trueValue, falseValue, lenByte);
}
protected INode DecodeIfEquals(BinaryReader input, TagType tag, int length, String lenByte) {
var end = input.BaseStream.Position + length;
var left = DecodeExpression(input);
var right = DecodeExpression(input);
/*
var trueValue = DecodeExpression(input);
INode falseValue = null;
if (input.BaseStream.Position != end)
falseValue = DecodeExpression(input);*/
INode trueValue, falseValue;
DecodeConditionalOutputs(input, (int)end, out trueValue, out falseValue);
return new Nodes.IfEqualsElement(tag, left, right, trueValue, falseValue, lenByte);
}
protected void DecodeConditionalOutputs(BinaryReader input, int end, out INode trueValue, out INode falseValue) {
var exprs = new List<INode>();
while (input.BaseStream.Position != end) {
var expr = DecodeExpression(input);
exprs.Add(expr);
}
// Only one instance with more than two expressions (LogMessage.en[1115][4])
// TODO: Not sure how it should be handled, discarding all but first and second for now.
if (exprs.Count > 0)
trueValue = exprs[0];
else
trueValue = null;
if (exprs.Count > 1)
falseValue = exprs[1];
else
falseValue = null;
}
protected INode DecodeSwitch(BinaryReader input, TagType tag, int length, String lenByte) {
var end = input.BaseStream.Position + length;
var caseSwitch = DecodeExpression(input);
var cases = new Dictionary<int, INode>();
var i = 1;
while (input.BaseStream.Position < end)
cases.Add(i++, DecodeExpression(input));
return new Nodes.SwitchElement(tag, caseSwitch, cases, lenByte);
}
#endregion
```
下面是處理指令長度的函式。在一般情況下,指令長度只會有一個byte,但若數字較大也會需要有兩個bytes的時候。
用舉例來看看下面的程式碼。當指令只有一個byte`0xC8`,這個byte會被放進lenByte裡面回傳(因為List是reference type,在函式裡加東西進去在函式外也有效)。因為唯一的byte被讀完了,這時候的`GetInteger(input, type, lenByte)`可以想成`GetInteger(空的input, 0xC8, lenByte{0xC8})`。
```csharp=+
#region Shared
protected static int GetInteger(BinaryReader input, List<byte> lenByte) {
// added new function
var t = input.ReadByte();
var type = (IntegerType)t;
lenByte.Add(t);
return GetInteger(input, type, lenByte);
}
protected static int GetInteger(BinaryReader input, IntegerType type, List<byte> lenByte) {
const byte ByteLengthCutoff = 0xF0;
var t = (byte)type;
if (t < ByteLengthCutoff)
return t - 1;
switch (type) {
case IntegerType.Byte: {
byte res = input.ReadByte();
lenByte.Add(res);
return (res);
}
case IntegerType.ByteTimes256: {
byte res = input.ReadByte();
lenByte.Add(res);
return (res * 256);
}
case IntegerType.Int16: {
int v = 0;
byte res = input.ReadByte();
lenByte.Add(res);
v |= res << 8;
res = input.ReadByte();
lenByte.Add(res);
v |= res;
return (v);
}
case IntegerType.Int24: {
int v = 0;
byte res = input.ReadByte();
lenByte.Add(res);
v |= res << 16;
res = input.ReadByte();
lenByte.Add(res);
v |= res << 8;
res = input.ReadByte();
lenByte.Add(res);
v |= res;
return (v);
}
case IntegerType.Int32: {
int v = 0;
byte res = input.ReadByte();
lenByte.Add(res);
v |= res << 24;
res = input.ReadByte();
lenByte.Add(res);
v |= res << 16;
res = input.ReadByte();
lenByte.Add(res);
v |= res << 8;
res = input.ReadByte();
lenByte.Add(res);
v |= res;
return (v);
}
default:
throw new NotSupportedException();
}
}
#endregion
```
接著修改以下相應檔案(附例子):
- `DefaultElement.cs`
- `EmptyElement.cs`
- `GenericElement.cs`
- `StaticInteger.cs`
- `CloseTag.cs`、`OpenTag.cs`
- 兩者皆會被`<Emphasis></Emphasis>`和`<Color(數字)></color>`兩種狀況呼叫。
- `<Emphasis><Value>IntegerParameter(1)</Value></Emphasis>` → `<hex:021A020203>` `<Value>IntegerParameter(1)</Value>` `<hex:021A020103>`
- `<Color(-15523537)><Unknown14>FEFFB1BACD</Unknown14>選擇大國防聯軍<Unknown14>EC</Unknown14></Color>` → `<hex:021306FEFF13212F03>` `<Unknown14>FEFFB1BACD</Unknown14>選擇大國防聯軍<Unknown14>EC</Unknown14>` `<hex:021302EC03>`
- 其餘字串交由其他檔案處理
- `IfElement.cs`、`IfEqualsElement.cs`
- `<If(GreaterThan(IntegerParameter(1),9999))>9,999+<Else/><Format(IntegerParameter(1),FF022C)/></If>`
- → `<hex:02081A` `GreaterThan(IntegerParameter(1),9999)` `FF07>9,999+<hex:FF0A>` `<Format(IntegerParameter(1),FF022C)/>` `<hex:03>`
- 其餘字串交由其他檔案處理
- `SwitchElement.cs`
- `<Switch(IntegerParameter(2))><Case(1)>格里達尼亞新街</Case><Case(2)>彎枝牧場</Case><Case(3)>霍桑山寨</Case><Case(4)>石場水車</Case><Case(5)>恬靜路營地</Case><Case(6)>秋瓜浮村</Case></Switch>` → `<hex:020963E803FF16>格里達尼亞新街<hex:FF0D>彎枝牧場<hex:FF0D>霍桑山寨<hex:FF0D>石場水車<hex:FF10>恬靜路營地<hex:FF0D>秋瓜浮村<hex:03>`
- `TopLevelParameter.cs`
- 觀察程式碼,這個建構子會在`DecodeExpression`裡面,當代表表示式類型的byte在`0xD0`到`0xDF`之間(含)時呼叫。
- `TopLevelParameter(222)` → `DF`
- `Comparison.cs`
- `Equal(IntegerParameter(1),1)` → `E4` `IntegerParameter(1),1`
- `ArgumentCollection.cs`
- `(IntegerParameter(1),1)` → `IntegerParameter(1)` `02`
- `Parameter.cs`
- `IntegerParameter(1)` → `E802`
通常是將各個INode新增代表長度的byte(原本程式會將這些bytes轉成人類可讀的數字,但現在我們要繼續保留HEX的形式),方便我們output時使用。
需要修改的地方包括properties、get functions、`ToString()`。以`DefaultElement.cs`為例。我們增加了作為String傳入的`lenByte`,接著修改`ToString()`函數。
```csharp=7
namespace SaintCoinach.Text.Nodes {
public class DefaultElement : INode {
private readonly TagType _Tag;
private readonly StaticByteArray _Data;
private readonly String _LenByte;
public TagType Tag { get { return _Tag; } }
public INode Data { get { return _Data; } }
public String LenByte { get { return _LenByte; } }
NodeFlags INode.Flags { get { return NodeFlags.IsStatic; } }
public DefaultElement(TagType tag, byte[] innerBuffer, String lenByte) {
_Tag = tag;
_Data = new StaticByteArray(innerBuffer);
_LenByte = lenByte;
}
public override string ToString() {
var sb = new StringBuilder();
ToString(sb);
return sb.ToString();
}
public void ToString(StringBuilder builder) {
// edit here!!!!
builder.Append(StringTokens.TagOpen);
builder.Append("hex:02");
builder.Append(((byte)Tag).ToString("X2")); /* X means hex, 2 means 2-digit */
builder.Append(LenByte);
if (_Data.Value.Length == 0) {
builder.Append("03");
builder.Append(StringTokens.TagClose);
}
else {
_Data.ToString(builder);
builder.Append("03");
builder.Append(StringTokens.TagClose);
}
/*
builder.Append("DefaultElement");
builder.Append(StringTokens.TagOpen);
builder.Append(Tag);
if (_Data.Value.Length == 0) {
builder.Append(StringTokens.ElementClose);
builder.Append(StringTokens.TagClose);
} else {
builder.Append(StringTokens.TagClose);
_Data.ToString(builder);
builder.Append(StringTokens.TagOpen);
builder.Append(StringTokens.ElementClose);
builder.Append(Tag);
builder.Append(StringTokens.TagClose);
}
*/
}
public T Accept<T>(SaintCoinach.Text.Nodes.INodeVisitor<T> visitor) {
return visitor.Visit(this);
}
}
}
```
4 結語