public byte[] ExportCSV(FileManager fileManager, IEnumerable<String> columnNames = null) { if (Attributes.IsEmpty) return new byte[0]; //// init stuffs byte[] csvBuffer = new byte[1024]; int csvOffset = 0; bool isProperties = (StringId == "PROPERTIES" || StringId == "_TCv4_PROPERTIES"); ObjectDelegator objectDelegator; if (fileManager == null || !fileManager.DataFileDelegators.ContainsKey(StringId)) { objectDelegator = new ObjectDelegator(Attributes.RowType.GetFields()); FieldInfo headerField = DataType.GetField("header", BindingFlags.Instance | BindingFlags.NonPublic); objectDelegator.AddField(headerField); } else { objectDelegator = fileManager.DataFileDelegators[StringId]; } //// header row // tables can have different header values String tableHeaderStr = String.Format("{0}({1})", StringId, FileTools.ObjectToStringGeneric(_excelFileHeader, ",")); // rest of columns List<String> columnsList = new List<String> { tableHeaderStr }; foreach (ObjectDelegator.FieldDelegate fieldDelegate in objectDelegator) { if (columnNames == null) { if (!fieldDelegate.IsPublic) continue; } else if (!columnNames.Contains(fieldDelegate.Name)) { continue; } columnsList.Add(fieldDelegate.Name); } // excel table type-specific columns if (Attributes.HasStats) columnsList.Add("Stats"); if (isProperties) columnsList.Add("Script"); // column header row String[] columns = columnsList.ToArray(); int colCount = columns.Length; int rowCount = Count + 1; // +1 for column headers //// csv generation String[][] strings = new string[rowCount][]; strings[0] = columns; int col = -1; foreach (ObjectDelegator.FieldDelegate fieldDelegate in objectDelegator) { if (!columns.Contains(fieldDelegate.Name) && fieldDelegate.Name != "header") continue; col++; OutputAttribute excelAttribute = GetExcelAttribute(fieldDelegate.Info); //if (fieldDelegate.Name == "spawnFromMonsterUnitType") //{ // int bp = 0; //} int row = 0; foreach (Object rowObject in Rows) { String[] rowStr = strings[++row]; if (rowStr == null) { rowStr = new String[colCount]; strings[row] = rowStr; } if (fieldDelegate.Name == "header") { RowHeader rowHeader = (RowHeader)fieldDelegate.GetValue(rowObject); rowStr[col] = FileTools.ObjectToStringGeneric(rowHeader, ","); continue; } if (fieldDelegate.Name == "code") { int code; if (fieldDelegate.FieldType == typeof(short)) { code = (int)(short)fieldDelegate.GetValue(rowObject); // yes, that extra (int) is *needed* to cast the short correctly } else { code = (int)fieldDelegate.GetValue(rowObject); } if (StringId == "REGION") // can't export region code values as chars due to weird chars { rowStr[col] = "\"" + code + "\""; } else { rowStr[col] = "\"" + _CodeToString(code) + "\""; } continue; } bool isArray = (fieldDelegate.FieldType.BaseType == typeof(Array)); if (excelAttribute != null) { if (excelAttribute.IsTableIndex && fileManager != null) { int[] indexValues; Object indexObj = fieldDelegate.GetValue(rowObject); if (isArray) { indexValues = (int[])indexObj; } else { indexValues = new[] { (int)indexObj }; } String[] indexStrs = new String[indexValues.Length]; for (int i = 0; i < indexStrs.Length; i++) { if (indexValues[i] == -1) // empty string/no code { indexStrs[i] = "-1"; continue; } String tableStringId = excelAttribute.TableStringId; String negative = String.Empty; if (indexValues[i] < 0) { indexValues[i] *= -1; negative = "-"; } String indexStr = null; if (fileManager.DataTableHasColumn(tableStringId, "code")) { int code = fileManager.GetExcelIntFromStringId(tableStringId, indexValues[i], "code"); if (code != 0) indexStr = _CodeToString(code); } else if (fileManager.DataTableHasColumn(tableStringId, "name")) { indexStr = fileManager.GetExcelStringFromStringId(tableStringId, indexValues[i], "name"); } if (indexStr == null) { indexStr = fileManager.GetExcelStringFromStringId(tableStringId, indexValues[i]); } indexStrs[i] = negative + indexStr; } rowStr[col] = "\"" + String.Join(",", indexStrs) + "\""; continue; } if (excelAttribute.IsStringOffset) { int offset = (int)fieldDelegate.GetValue(rowObject); if (offset != -1) { rowStr[col] = ReadStringTable(offset); } continue; } if (excelAttribute.IsScript) { int offset = (int)fieldDelegate.GetValue(rowObject); if ((offset == 0)) { FileTools.WriteToBuffer(ref csvBuffer, ref csvOffset, FileTools.StringToASCIIByteArray("0")); continue; } int[] buffer = ReadScriptTable(offset); if (buffer == null) throw new Exceptions.ScriptFormatException("The script bytes were unable to be read using ReadScriptTable.", offset); String scriptString = FileTools.ArrayToStringGeneric(buffer, ","); if (fileManager != null) { if (offset == 9325 /*from DataTable export*/ || offset == 9649 /*from Object export*/ && StringId == "SKILLS") // todo: not sure what's with this script... { /* Compiled Bytes: * 26,30,700,6,26,1,399,358,669,616562688,711,26,62,3,17,669,641728512,26,8,711,26,62,3,17,358,669,322961408,26,5,700,6,26,1,399,358,388,0 * * Ending Stack (FIFO): * SetStat669('sfx_attack_pct', 'all', 30 * ($sklvl - 1)) * SetStat669('sfx_duration_pct', 'all', get_skill_level(@unit, 'Shield_Mastery')) * SetStat669('damage_percent_skill', 8 * get_skill_level(@unit, 'Shield_Mastery')) * SetStat669('damage_percent_skill', 8 * get_skill_level(@unit, 'Shield_Mastery')) + 5 * ($sklvl - 1) * * The last SetStat has strange overhang - decompiling wrong? * Or is it "supposed" to be there? * i.e. It's actually decompiling correctly, but because I've assumed such scripts to be wrong (as the end +5... segment is useless) we get the Stack exception */ scriptString = "ScriptError(" + scriptString + ")"; } else { try { ExcelScript excelScript = new ExcelScript(fileManager); scriptString = "\"" + excelScript.Decompile(_scriptBuffer, offset, scriptString, StringId, row, col, fieldDelegate.Name) + "\""; } catch (Exception e) { Debug.WriteLine(e.ToString()); scriptString = "ScriptError(" + scriptString + ")"; } } } rowStr[col] = scriptString; continue; } if (excelAttribute.IsSecondaryString) { int index = (int)fieldDelegate.GetValue(rowObject); if (index != -1) { rowStr[col] = SecondaryStrings[index]; } continue; } if (excelAttribute.IsBitmask) { rowStr[col] = "\"" + fieldDelegate.GetValue(rowObject) + "\""; continue; } } Object outValue = fieldDelegate.GetValue(rowObject); if (isArray) { rowStr[col] = ((Array)outValue).ToString(","); } else if (fieldDelegate.FieldType == typeof(float)) { rowStr[col] = ((float)outValue).ToString("r"); } //else if (fieldDelegate.FieldType.BaseType == typeof(Enum)) // might as well export enums as their string representations //{ // rowStr[col] = ((UInt32)outValue).ToString(); //} else { rowStr[col] = outValue.ToString(); } } } // stats if (Attributes.HasStats) { col++; int row = -1; foreach (String[] rowStr in strings) { if (row == -1) // columns header row { row++; continue; } rowStr[col] = StatsBuffer[row++].ToString(","); } } // properties scripts if (isProperties) { // not point in doing this //if (tableHeader.Unknown1 != 2 || scriptRow == ExcelFunctions.Count - 1) // need 1 extra row for some reason col++; int row = -1; foreach (String[] rowStr in strings) { if (row == -1) { row++; continue; } if (row >= ExcelFunctions.Count) break; ExcelFunction excelScript = ExcelFunctions[row++]; String excelScriptFunction = String.Empty; foreach (ExcelFunction.Parameter paramater in excelScript.Parameters) { excelScriptFunction += String.Format("\n{0},{1},{2},{3}", paramater.Name, paramater.Unknown, paramater.TypeId, paramater.TypeValues.ToString(",")); } if (excelScript.ScriptByteCode != null) { int offset = 0; excelScriptFunction += "\n" + FileTools.ByteArrayToInt32Array(excelScript.ScriptByteCode, ref offset, excelScript.ScriptByteCode.Length / 4).ToString(",") + "\n"; } rowStr[col] = excelScriptFunction; } } //// join string arrays and create byte array String[] rows = new String[rowCount]; col = 0; foreach (String[] rowStr in strings) { rows[col] = String.Join("\t", rowStr); col++; } String csvString = String.Join(Environment.NewLine, rows); return csvString.ToASCIIByteArray(); }
private bool _ParseCSV(byte[] csvBytes, FileManager fileManager, Dictionary<String, ExcelFile> csvExcelFiles) { // function setup int stringBufferOffset = 0; int integerBufferOffset = 1; bool isProperties = (StringId == "PROPERTIES" || StringId == "_TCv4_PROPERTIES"); ObjectDelegator objectDelegator; OutputAttribute[] excelAttributes; bool needOutputAttributes = true; if (fileManager == null || !fileManager.DataFileDelegators.ContainsKey(StringId)) { FieldInfo[] fieldInfos = DataType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); fieldInfos = fieldInfos.OrderBy(f => f.MetadataToken).ToArray(); // order by defined order - GetFields does not guarantee ordering objectDelegator = new ObjectDelegator(fieldInfos); excelAttributes = new OutputAttribute[fieldInfos.Length]; } else { objectDelegator = fileManager.DataFileDelegators[StringId]; excelAttributes = new OutputAttribute[objectDelegator.FieldCount]; } String[][] tableRows; if (csvBytes == null) { tableRows = _csvTable; } else { // get columns int offset = 0; int colCount = 1; while (csvBytes[offset++] != '\n') if (csvBytes[offset] == CSVDelimiter) colCount++; tableRows = FileTools.CSVToStringArray(csvBytes, colCount, CSVDelimiter); } int rowCount = tableRows.Length; String[] columns = tableRows[0]; if (isProperties) { ExcelFunctions = new List<ExcelFunction>(); _scriptBuffer = new byte[1]; // properties is weird - do this just to ensure 100% byte-for-byte accuracy } // parse file header - could use Regex here - but don't think that'd but faster, in fact, probably a lot slower(?) - meh // format: %s(%s) // first %s = StringId, second %s = FileHeader as String String fileHeader = columns[0]; int fileHeaderStart = fileHeader.IndexOf("(") + 1; int fileHeaderEnd = fileHeader.IndexOf(")"); String fileHeaderStr = fileHeader.Substring(fileHeaderStart, fileHeaderEnd - fileHeaderStart); _excelFileHeader = FileTools.StringToObject<ExcelHeader>(fileHeaderStr, ",", FileHeaderFields); String strId = fileHeader.Substring(0, fileHeader.IndexOf("(")).Trim(); // asking for exception lol Debug.Assert(strId == StringId); // Parse the tableRows bool failedParsing = false; Rows = new List<Object>(); for (int row = 1; row < rowCount; row++) { int col = -1; int csvCol = 0; Object rowInstance = Activator.CreateInstance(DataType); foreach (ObjectDelegator.FieldDelegate fieldDelegate in objectDelegator) { col++; if (needOutputAttributes) excelAttributes[col] = GetExcelAttribute(fieldDelegate.Info); OutputAttribute excelAttribute = excelAttributes[col]; // columns not present if (!columns.Contains(fieldDelegate.Name)) { // create row header object if (fieldDelegate.FieldType == typeof(RowHeader)) { String headerString = tableRows[row][csvCol++]; RowHeader rowHeader = FileTools.StringToObject<RowHeader>(headerString, ",", RowHeaderFields); objectDelegator[fieldDelegate.Name, rowInstance] = rowHeader; continue; } // assign default values MarshalAsAttribute arrayMarshal = null; Array arrayInstance = null; if (fieldDelegate.FieldType.BaseType == typeof(Array)) { arrayMarshal = (MarshalAsAttribute)fieldDelegate.Info.GetCustomAttributes(typeof(MarshalAsAttribute), false).First(); arrayInstance = (Array)Activator.CreateInstance(fieldDelegate.FieldType, arrayMarshal.SizeConst); objectDelegator[fieldDelegate.Name, rowInstance] = arrayInstance; } else if (fieldDelegate.FieldType == typeof(String)) { objectDelegator[fieldDelegate.Name, rowInstance] = String.Empty; } // assign constant non-zero values if (excelAttribute == null || excelAttribute.ConstantValue == null) continue; if (fieldDelegate.FieldType.BaseType == typeof(Array)) { Debug.Assert(arrayInstance != null, "arrayInstance == null"); Debug.Assert(arrayMarshal != null, "arrayMarshal == null"); for (int i = 0; i < arrayMarshal.SizeConst; i++) { arrayInstance.SetValue(excelAttribute.ConstantValue, i); } } else { objectDelegator[fieldDelegate.Name, rowInstance] = excelAttribute.ConstantValue; } continue; } // columns present String value = tableRows[row][csvCol++]; if (fieldDelegate.Name == "code") { int code = (StringId == "REGION") ? int.Parse(value) : StringToCode(value); if (fieldDelegate.FieldType == typeof(short)) { objectDelegator[fieldDelegate.Name, rowInstance] = (short)code; } else { objectDelegator[fieldDelegate.Name, rowInstance] = code; } continue; } bool isArray = (fieldDelegate.FieldType.BaseType == typeof(Array)); bool isEnum = (fieldDelegate.FieldType.BaseType == typeof(Enum)); if (excelAttribute != null) { if (excelAttribute.IsTableIndex && fileManager != null) { int arraySize = 1; if (isArray) { MarshalAsAttribute arrayMarshal = (MarshalAsAttribute)fieldDelegate.Info.GetCustomAttributes(typeof(MarshalAsAttribute), false).First(); arraySize = arrayMarshal.SizeConst; Debug.Assert(arraySize > 0); } String[] indexStrs = value.Split(new[] { ',' }); Int32[] rowIndexValues = new int[arraySize]; for (int i = 0; i < arraySize; i++) rowIndexValues[i] = -1; int maxElements = indexStrs.Length; if (maxElements > arraySize) { Debug.WriteLine(String.Format("{0}: Loss of array elements detected. row = {1}, col = {2}.", StringId, row, col)); maxElements = arraySize; } for (int i = 0; i < maxElements; i++) { value = indexStrs[i]; if (value == "-1") continue; String tableStringId = excelAttribute.TableStringId; bool hasCodeColumn = fileManager.DataTableHasColumn(tableStringId, "code"); if (value.Length == 0 && hasCodeColumn) continue; //LEVEL references multiple blank TREASURE row index values - all appear to be empty rows though, so meh... //Debug.Assert(!String.IsNullOrEmpty(value)); int isNegative = 1; if (value.Length > 0 && value[0] == '-') { isNegative = -1; value = value.Substring(1, value.Length - 1); } int rowIndex = -1; ExcelFile relatedExcel = null; if (csvExcelFiles != null && csvExcelFiles.TryGetValue(tableStringId, out relatedExcel)) { rowIndex = relatedExcel._GetRowIndexFromValue(value, hasCodeColumn ? "code" : null); } if (relatedExcel == null) { if (hasCodeColumn && value.Length <= 4) { int code = StringToCode(value); rowIndex = fileManager.GetExcelRowIndexFromStringId(tableStringId, code, "code"); } else if (fileManager.DataTableHasColumn(tableStringId, "name")) { rowIndex = fileManager.GetExcelRowIndexFromStringId(tableStringId, value, "name"); } else { rowIndex = fileManager.GetExcelRowIndex(tableStringId, value); } } rowIndexValues[i] = rowIndex * isNegative; } if (isArray) { objectDelegator[fieldDelegate.Name, rowInstance] = rowIndexValues; } else { objectDelegator[fieldDelegate.Name, rowInstance] = rowIndexValues[0]; } continue; } if (excelAttribute.IsStringOffset) { if (_stringBuffer == null) _stringBuffer = new byte[1024]; if (String.IsNullOrEmpty(value)) { objectDelegator[fieldDelegate.Name, rowInstance] = -1; continue; } objectDelegator[fieldDelegate.Name, rowInstance] = stringBufferOffset; FileTools.WriteToBuffer(ref _stringBuffer, ref stringBufferOffset, FileTools.StringToASCIIByteArray(value)); stringBufferOffset++; // \0 continue; } if (excelAttribute.IsScript) { if ((fileManager == null && value == "0") || value == "") { objectDelegator[fieldDelegate.Name, rowInstance] = 0; continue; } if (_scriptBuffer == null) { _scriptBuffer = new byte[1024]; _scriptBuffer[0] = 0x00; } int[] scriptByteCode; if (fileManager != null) { ExcelScript excelScript = new ExcelScript(fileManager); scriptByteCode = excelScript.Compile(value, null, StringId, row, col, fieldDelegate.Name); } else { string[] splitValue = value.Split(','); int count = splitValue.Length; scriptByteCode = new int[count]; for (int i = 0; i < count; i++) { scriptByteCode[i] = int.Parse(splitValue[i]); } } objectDelegator[fieldDelegate.Name, rowInstance] = integerBufferOffset; FileTools.WriteToBuffer(ref _scriptBuffer, ref integerBufferOffset, scriptByteCode.ToByteArray()); continue; } if (excelAttribute.IsSecondaryString) { if (SecondaryStrings == null) SecondaryStrings = new List<String>(); if (value == "") { objectDelegator[fieldDelegate.Name, rowInstance] = -1; continue; } if (!SecondaryStrings.Contains(value)) { SecondaryStrings.Add(value); } objectDelegator[fieldDelegate.Name, rowInstance] = SecondaryStrings.IndexOf(value); continue; } if (excelAttribute.IsBitmask) { if (fieldDelegate.FieldType == typeof(UInt32)) { objectDelegator[fieldDelegate.Name, rowInstance] = UInt32.Parse(value); continue; } Object enumVal; try { enumVal = Enum.Parse(fieldDelegate.FieldType, value); } catch (Exception) { String[] enumStrings = value.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); String[] enumNames = Enum.GetNames(fieldDelegate.FieldType); String enumString = String.Empty; String enumSeperator = String.Empty; foreach (String enumStr in enumStrings) { if (!enumNames.Contains(enumStr)) { Debug.WriteLine(String.Format("{0}: bitfield name '{1}' not found.", StringId, enumStr)); continue; } enumString += enumSeperator + enumStr; enumSeperator = ","; } enumVal = enumString == "" ? 0 : Enum.Parse(fieldDelegate.FieldType, enumString); } objectDelegator[fieldDelegate.Name, rowInstance] = enumVal; continue; } } try { Object objValue; if (isArray) { if (fieldDelegate.FieldType == typeof(Int32[])) { objValue = FileTools.StringToArray<Int32>(value, ","); } else { Type elementType = fieldDelegate.FieldType.GetElementType(); if (elementType.BaseType == typeof(Enum)) { String[] enumStrs = value.Split(new[] { ',' }, StringSplitOptions.None); Array enumsArray = Array.CreateInstance(elementType, enumStrs.Length); int i = 0; foreach (String enumStr in enumStrs) { enumsArray.SetValue(Enum.Parse(elementType, enumStr), i++); } objValue = enumsArray; } else { throw new NotImplementedException("if (fieldInfo.FieldType.BaseType == typeof(Array)) :: Type = " + elementType.BaseType); } } objectDelegator[fieldDelegate.Name, rowInstance] = objValue; } else if (isEnum) { object enumVal = Enum.Parse(fieldDelegate.FieldType, value); objectDelegator[fieldDelegate.Name, rowInstance] = enumVal; } else { objValue = FileTools.StringToObject(value, fieldDelegate.FieldType); objectDelegator[fieldDelegate.Name, rowInstance] = objValue; } } catch (Exception e) { Console.WriteLine("Critical Parsing Error: " + e); failedParsing = true; break; } } if (failedParsing) break; needOutputAttributes = false; // applicable only for Unit type; items, missiles, monsters, objects, players if (Attributes.HasStats) { if (StatsBuffer == null) StatsBuffer = new byte[rowCount - 1][]; String value = tableRows[row][csvCol]; String[] stringArray = value.Split(','); byte[] byteArray = new byte[stringArray.Length]; for (int i = 0; i < byteArray.Length; i++) { byteArray[i] = Byte.Parse(stringArray[i]); } StatsBuffer[row - 1] = byteArray; } // properties has extra Scripts stuffs // yea, this is a bit messy, but it's a single table only and mostly done out of curiosity if (isProperties) { String value = tableRows[row][csvCol]; String[] scripts = value.Split('\n'); ExcelFunction excelScript = new ExcelFunction(); if (scripts.Length > 1) { ExcelFunctions.Add(excelScript); } int i = 0; do { if (scripts.Length == 1) continue; i++; String[] values = scripts[i].Split(','); if (values.Length < 4) continue; // script parameters int typeValuesCount = values.Length - 3; ExcelFunction.Parameter parameter = new ExcelFunction.Parameter { Name = values[0], Unknown = UInt32.Parse(values[1]), TypeId = UInt32.Parse(values[2]), TypeValues = new int[typeValuesCount] }; for (int j = 0; j < typeValuesCount; j++) { parameter.TypeValues[j] = Int32.Parse(values[3 + j]); } excelScript.Parameters.Add(parameter); } while (i < scripts.Length - 1 - 2); // -2 for: last line is blank, and line before *might* be script values // last line will be script values if it exists if (i < scripts.Length - 2) { String[] values = scripts[++i].Split(','); int[] scriptValues = new int[values.Length]; for (int j = 0; j < values.Length; j++) { scriptValues[j] = Int32.Parse(values[j]); } excelScript.ScriptByteCode = scriptValues.ToByteArray(); } } Rows.Add(rowInstance); } // resize the integer and string buffers if they were used if (_stringBuffer != null) Array.Resize(ref _stringBuffer, stringBufferOffset); if (_scriptBuffer != null) Array.Resize(ref _scriptBuffer, integerBufferOffset); return HasIntegrity = true; }