From 9db885801e50ea1ce4af2b471fcc7b5e58aa93cc Mon Sep 17 00:00:00 2001 From: Daribon Date: Fri, 25 Jun 2021 06:11:41 +0200 Subject: [PATCH] Updated extractor to support WoTLK and Cata. Credit to Brotalnia. --- Source/Tools/Mangos.Extractor/Functions.cs | 455 +++++++++++---------- 1 file changed, 250 insertions(+), 205 deletions(-) diff --git a/Source/Tools/Mangos.Extractor/Functions.cs b/Source/Tools/Mangos.Extractor/Functions.cs index 545c6034..15ce3713 100644 --- a/Source/Tools/Mangos.Extractor/Functions.cs +++ b/Source/Tools/Mangos.Extractor/Functions.cs @@ -189,67 +189,67 @@ public static string ToType(int iType) private static void AddFlag(ref string sFlags, string sFlag) { - if (!string.IsNullOrEmpty(sFlags)) - { - sFlags += " + "; - } - + if (!string.IsNullOrEmpty(sFlags)) + { + sFlags += " + "; + } + sFlags += sFlag; } public static string ToFlags(int iFlags) { string tmp = ""; - if (iFlags == 0) - { - tmp = "NONE"; - } - - if (Conversions.ToBoolean(iFlags & 1)) - { - AddFlag(ref tmp, "PUBLIC"); - } - - if (Conversions.ToBoolean(iFlags & 2)) - { - AddFlag(ref tmp, "PRIVATE"); - } - - if (Conversions.ToBoolean(iFlags & 4)) - { - AddFlag(ref tmp, "OWNER_ONLY"); - } - - if (Conversions.ToBoolean(iFlags & 8)) - { - AddFlag(ref tmp, "UNK1"); - } - - if (Conversions.ToBoolean(iFlags & 16)) - { - AddFlag(ref tmp, "UNK2"); - } - - if (Conversions.ToBoolean(iFlags & 32)) - { - AddFlag(ref tmp, "UNK3"); - } - - if (Conversions.ToBoolean(iFlags & 64)) - { - AddFlag(ref tmp, "GROUP_ONLY"); - } - - if (Conversions.ToBoolean(iFlags & 128)) - { - AddFlag(ref tmp, "UNK5"); - } - - if (Conversions.ToBoolean(iFlags & 256)) - { - AddFlag(ref tmp, "DYNAMIC"); - } - + if (iFlags == 0) + { + tmp = "NONE"; + } + + if (Conversions.ToBoolean(iFlags & 1)) + { + AddFlag(ref tmp, "PUBLIC"); + } + + if (Conversions.ToBoolean(iFlags & 2)) + { + AddFlag(ref tmp, "PRIVATE"); + } + + if (Conversions.ToBoolean(iFlags & 4)) + { + AddFlag(ref tmp, "OWNER_ONLY"); + } + + if (Conversions.ToBoolean(iFlags & 8)) + { + AddFlag(ref tmp, "UNK1"); + } + + if (Conversions.ToBoolean(iFlags & 16)) + { + AddFlag(ref tmp, "UNK2"); + } + + if (Conversions.ToBoolean(iFlags & 32)) + { + AddFlag(ref tmp, "SPECIAL_INFO"); + } + + if (Conversions.ToBoolean(iFlags & 64)) + { + AddFlag(ref tmp, "GROUP_ONLY"); + } + + if (Conversions.ToBoolean(iFlags & 128)) + { + AddFlag(ref tmp, "UNK5"); + } + + if (Conversions.ToBoolean(iFlags & 256)) + { + AddFlag(ref tmp, "DYNAMIC"); + } + return tmp; } @@ -276,44 +276,59 @@ public enum Types public static void ExtractUpdateFields() { - int TBC = 0; - int alpha = 0; FileVersionInfo versInfo = FileVersionInfo.GetVersionInfo("Wow.exe"); FileStream f = new("wow.exe", FileMode.Open, FileAccess.Read, FileShare.Read, 10000000); - BinaryReader r1 = new(f); - StreamReader r2 = new(f); - FileStream o = new(versInfo.FileMajorPart + "." + versInfo.FileMinorPart + "." + versInfo.FileBuildPart + "." + versInfo.FilePrivatePart + "_Global.UpdateFields.cs", FileMode.Create, FileAccess.Write, FileShare.None, 1024); + FileStream o = new("Global.UpdateFields.h", FileMode.Create, FileAccess.Write, FileShare.None, 1024); StreamWriter w = new(o); - int FIELD_NAME_OFFSET = SearchInFile(f, "CORPSE_FIELD_PAD"); - int OBJECT_FIELD_GUID = SearchInFile(f, "OBJECT_FIELD_GUID") + 0x400000; - int FIELD_TYPE_OFFSET = SearchInFile(f, OBJECT_FIELD_GUID); -#if DEBUG - MessageBox.Show("FIELD_NAME_OFFSET " + FIELD_NAME_OFFSET + " OBJECT_FIELD_GUID " + OBJECT_FIELD_GUID + " FIELD_TYPE_OFFSET " + FIELD_TYPE_OFFSET); -#endif - if (FIELD_NAME_OFFSET == -1) // pre 1.5 vanilla support - { - FIELD_NAME_OFFSET = SearchInFile(f, "CORPSE_FIELD_FLAGS"); - } - if (FIELD_NAME_OFFSET == -1) // alpha support + + // this is right after the data for size, type and flags of update fields begins + int OBJECT_FIELD_GUID_PointerAddress = SearchInFile(f, "\u0000\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0004\u0000\u0000\u0000\u0001\u0000\u0000\u0000"); + if (OBJECT_FIELD_GUID_PointerAddress == -1) { - FIELD_NAME_OFFSET = SearchInFile(f, "CORPSE_FIELD_LEVEL"); - alpha = 1; + MessageBox.Show("Cannot find where data for update field types begins!"); + return; } - if (FIELD_TYPE_OFFSET == -1) // TBC support + + // read the uint32 before that address + OBJECT_FIELD_GUID_PointerAddress -= 4; + f.Seek(OBJECT_FIELD_GUID_PointerAddress, SeekOrigin.Begin); + byte[] Buffer = new byte[4]; + f.Read(Buffer, 0, 4); + uint OBJECT_FIELD_GUID_Pointer = BitConverter.ToUInt32(Buffer, 0); + + // find the address of the first update field name + int OBJECT_FIELD_GUID_NameAddress = SearchInFile(f, "OBJECT_FIELD_GUID"); + if (OBJECT_FIELD_GUID_NameAddress == -1) { - OBJECT_FIELD_GUID = SearchInFile(f, "OBJECT_FIELD_GUID") + 0x1A00 + 0x400000; - FIELD_TYPE_OFFSET = SearchInFile(f, OBJECT_FIELD_GUID); - TBC = 1; + MessageBox.Show("Cannot find OBJECT_FIELD_GUID string!"); + return; } - if (FIELD_NAME_OFFSET == -1 || FIELD_TYPE_OFFSET == -1) + // substract the address of the name to get the offset + uint AddressOffset = OBJECT_FIELD_GUID_Pointer - (uint)OBJECT_FIELD_GUID_NameAddress; + + int FieldTypesBegin = OBJECT_FIELD_GUID_PointerAddress; + int FieldNamesBegin = SearchInFile(f, "AREATRIGGER_FINAL_POS"); + if (FieldNamesBegin == -1) { - MessageBox.Show("Wrong offsets! " + FIELD_NAME_OFFSET + " " + FIELD_TYPE_OFFSET); + FieldNamesBegin = SearchInFile(f, "CORPSE_FIELD_PAD"); + } + if (FieldNamesBegin == -1) + { + FieldNamesBegin = SearchInFile(f, "CORPSE_FIELD_FLAGS"); + } + if (FieldNamesBegin == -1) + { + FieldNamesBegin = SearchInFile(f, "CORPSE_FIELD_LEVEL"); + } + if (FieldNamesBegin == -1) + { + MessageBox.Show("Cannot find last update field name!"); } else { List Names = new(); string Last = ""; - int Offset = FIELD_NAME_OFFSET; + int Offset = FieldNamesBegin; f.Seek(Offset, SeekOrigin.Begin); while (Last != "OBJECT_FIELD_GUID") { @@ -323,12 +338,11 @@ public static void ExtractUpdateFields() List Info = new(); int Temp; - byte[] Buffer = new byte[4]; Offset = 0; - f.Seek(FIELD_TYPE_OFFSET, SeekOrigin.Begin); + f.Seek(FieldTypesBegin, SeekOrigin.Begin); for (int i = 0, loopTo = Names.Count - 1; i <= loopTo; i++) { - f.Seek(FIELD_TYPE_OFFSET + (i * 5 * 4) + Offset, SeekOrigin.Begin); + f.Seek(FieldTypesBegin + (i * 5 * 4) + Offset, SeekOrigin.Begin); f.Read(Buffer, 0, 4); Temp = BitConverter.ToInt32(Buffer, 0); if (Temp < 0xFFFF) @@ -359,7 +373,6 @@ public static void ExtractUpdateFields() MessageBox.Show(string.Format("{0} fields extracted.", Names.Count)); w.WriteLine("// Auto generated file"); - w.WriteLine("// {0}", DateAndTime.Now); w.WriteLine("// Patch: " + versInfo.FileMajorPart + "." + versInfo.FileMinorPart + "." + versInfo.FileBuildPart); w.WriteLine("// Build: " + versInfo.FilePrivatePart); w.WriteLine(); @@ -369,111 +382,124 @@ public static void ExtractUpdateFields() int BasedOn = 0; string BasedOnName = ""; Dictionary EndNum = new(); + + // on first iteration just add the enum names for (int j = 0, loopTo1 = Info.Count - 1; j <= loopTo1; j++) { - sName = ReadString(f, Info[j].Name - 0x400000); - if (TBC == 1) // TBC support + long nextAddress = Info[j].Name - AddressOffset; + if (nextAddress < 0 || nextAddress > f.Length) { - sName = ReadString(f, Info[j].Name - (0x1A00 + 0x400000)); + MessageBox.Show("Wrong address for update field name! " + nextAddress); + continue; } + sName = ReadString(f, nextAddress); if (!string.IsNullOrEmpty(sName)) { sField = ToField(sName.Substring(0, sName.IndexOf("_"))); - if (sName == "OBJECT_FIELD_CREATED_BY" && alpha == 0) - { - sField = "GameObject"; - } - - if (sName is "UINT_FIELD_BASESTAT0" or // alpha support - "UINT_FIELD_BASESTAT1" or - "UINT_FIELD_BASESTAT2" or - "UINT_FIELD_BASESTAT3" or - "UINT_FIELD_BASESTAT4" or - "UINT_FIELD_BYTES_1") - { - sField = "Unit"; - } - + if (sName is "UINT_FIELD_BASESTAT0" or + "UINT_FIELD_BASESTAT1" or + "UINT_FIELD_BASESTAT2" or + "UINT_FIELD_BASESTAT3" or + "UINT_FIELD_BASESTAT4" or + "UINT_FIELD_BYTES_1") + { + sField = "Unit"; + } + if (sName == "OBJECT_FIELD_CREATED_BY") + sField = "GameObject"; if ((LastFieldType ?? "") != (sField ?? "")) { if (!string.IsNullOrEmpty(LastFieldType)) { EndNum.Add(LastFieldType, Info[j - 1].Offset + 1); + } + LastFieldType = sField; + } + } + } + + sField = ""; + LastFieldType = ""; + + for (int j = 0, loopTo1 = Info.Count - 1; j <= loopTo1; j++) + { + long nextAddress = Info[j].Name - AddressOffset; + if (nextAddress < 0 || nextAddress > f.Length) + { + MessageBox.Show("Wrong address for update field name! " + nextAddress); + w.WriteLine("// An error occurred while reading field at this spot"); + continue; + } + sName = ReadString(f, nextAddress); + if (!string.IsNullOrEmpty(sName)) + { + sField = ToField(sName.Substring(0, sName.IndexOf("_"))); + if (sName is "UINT_FIELD_BASESTAT0" or + "UINT_FIELD_BASESTAT1" or + "UINT_FIELD_BASESTAT2" or + "UINT_FIELD_BASESTAT3" or + "UINT_FIELD_BASESTAT4" or + "UINT_FIELD_BYTES_1") + { + sField = "Unit"; + } + if (sName == "OBJECT_FIELD_CREATED_BY") + sField = "GameObject"; + if ((LastFieldType ?? "") != (sField ?? "")) + { + if (!string.IsNullOrEmpty(LastFieldType)) + {; if (LastFieldType.ToLower() == "object") { - w.WriteLine(" {0,-78}", LastFieldType.ToUpper() + "_END = 0x" + Conversion.Hex(Info[j - 1].Offset + Info[j - 1].Size)); + w.WriteLine(" {0,-48} = {1,-20}", LastFieldType.ToUpper() + "_END", "0x" + Conversion.Hex(Info[j - 1].Offset + Info[j - 1].Size)); } else { - w.WriteLine(" {0,-78}// 0x{1:X3}", LastFieldType.ToUpper() + "_END = " + BasedOnName + " + 0x" + Conversion.Hex(Info[j - 1].Offset + Info[j - 1].Size), BasedOn + Info[j - 1].Offset + Info[j - 1].Size); + w.WriteLine(" {0,-48} = {1,-20}// 0x{2:X3}", LastFieldType.ToUpper() + "_END", BasedOnName.Substring(BasedOnName.IndexOf('.') + 1) + " + 0x" + Conversion.Hex(Info[j - 1].Offset + Info[j - 1].Size), BasedOn + Info[j - 1].Offset + Info[j - 1].Size); } - w.WriteLine("}"); + w.WriteLine("};"); + } + + w.WriteLine("enum E" + sField + "Fields"); + w.WriteLine("{"); + + if (sField.ToLower() == "container") + { + BasedOn = EndNum["Item"]; + BasedOnName = "EItemFields.ITEM_END"; + } + else if (sField.ToLower() == "player") + { + BasedOn = EndNum["Unit"]; + BasedOnName = "EUnitFields.UNIT_END"; + } + else if (sField.ToLower() != "object") + { + BasedOn = EndNum["Object"]; + BasedOnName = "EObjectFields.OBJECT_END"; } - w.WriteLine("Public Enum E" + sField + "Fields"); - w.WriteLine("{"); -#if DEBUG - MessageBox.Show("sField: " + sField + "\nsName: " + sName); -#endif - if (TBC == 1) // TBC support - { - if (sField.ToLower() == "item") - { - BasedOn = EndNum["Container"]; - BasedOnName = "EContainerFields.CONTAINER_END"; - } - else if (sField.ToLower() == "player") - { - BasedOn = EndNum["Unit"]; - BasedOnName = "EUnitFields.UNIT_END"; - } - else if (sField.ToLower() != "object") - { - BasedOn = EndNum["Object"]; - BasedOnName = "EObjectFields.OBJECT_END"; - } - } - - if (TBC == 0) - { - if (sField.ToLower() == "container") - { - BasedOn = EndNum["Item"]; - BasedOnName = "EItemFields.ITEM_END"; - } - else if (sField.ToLower() == "player") - { - BasedOn = EndNum["Unit"]; - BasedOnName = "EUnitFields.UNIT_END"; - } - else if (sField.ToLower() != "object") - { - BasedOn = EndNum["Object"]; - BasedOnName = "EObjectFields.OBJECT_END"; - } - } - LastFieldType = sField; } if (BasedOn > 0) { - w.WriteLine(" {0,-78}// 0x{1:X3} - Size: {2} - Type: {3} - Flags: {4}", sName + " = " + BasedOnName + " + 0x" + Conversion.Hex(Info[j].Offset) + ",", BasedOn + Info[j].Offset, Info[j].Size, ToType(Info[j].Type), ToFlags(Info[j].Flags)); + w.WriteLine(" {0,-48} = {1,-20}// 0x{2:X3} - Size: {3} - Type: {4} - Flags: {5}", sName, BasedOnName.Substring(BasedOnName.IndexOf('.') + 1) + " + 0x" + Conversion.Hex(Info[j].Offset) + ",", BasedOn + Info[j].Offset, Info[j].Size, ToType(Info[j].Type), ToFlags(Info[j].Flags)); } else { - w.WriteLine(" {0,-78}// 0x{1:X3} - Size: {2} - Type: {3} - Flags: {4}", sName + " = 0x" + Conversion.Hex(Info[j].Offset) + ",", Info[j].Offset, Info[j].Size, ToType(Info[j].Type), ToFlags(Info[j].Flags)); + w.WriteLine(" {0,-48} = {1,-20}// 0x{2:X3} - Size: {3} - Type: {4} - Flags: {5}", sName, "0x" + Conversion.Hex(Info[j].Offset) + ",", Info[j].Offset, Info[j].Size, ToType(Info[j].Type), ToFlags(Info[j].Flags)); } } } - if (!string.IsNullOrEmpty(LastFieldType)) - { - w.WriteLine(" {0,-78}// 0x{1:X3}", LastFieldType.ToUpper() + "_END = " + BasedOnName + " + 0x" + Conversion.Hex(Info[^1].Offset + Info[^1].Size), BasedOn + Info[^1].Offset + Info[^1].Size); - } - - w.WriteLine("}"); + if (!string.IsNullOrEmpty(LastFieldType)) + { + w.WriteLine(" {0,-48} = {1,-20}// 0x{2:X3}", LastFieldType.ToUpper() + "_END", BasedOnName.Substring(BasedOnName.IndexOf('.') + 1) + " + 0x" + Conversion.Hex(Info[^1].Offset + Info[^1].Size), BasedOn + Info[^1].Offset + Info[^1].Size); + } + + w.WriteLine("};"); w.Flush(); } @@ -483,16 +509,16 @@ public static void ExtractUpdateFields() public static void ExtractOpcodes() { + FileVersionInfo versInfo = FileVersionInfo.GetVersionInfo("Wow.exe"); FileStream f = new("wow.exe", FileMode.Open, FileAccess.Read, FileShare.Read, 10000000); - BinaryReader r1 = new(f); - StreamReader r2 = new(f); FileStream o = new("Global.Opcodes.cs", FileMode.Create, FileAccess.Write, FileShare.None, 1024); StreamWriter w = new(o); - MessageBox.Show(ReadString(f, SearchInFile(f, "CMSG_REQUEST_PARTY_MEMBER_STATS"))); + int START = SearchInFile(f, "NUM_MSG_TYPES"); - if (START == -1) + int END = SearchInFile(f, "CMSG_BOOTME"); + if (START == -1 || END == -1) { - MessageBox.Show("Wrong offsets!"); + MessageBox.Show("Client does not contain opcodes enum!"); } else { @@ -507,14 +533,15 @@ public static void ExtractOpcodes() MessageBox.Show(string.Format("{0} opcodes extracted.", Names.Count)); w.WriteLine("// Auto generated file"); - w.WriteLine("// {0}", DateAndTime.Now); + w.WriteLine("// Patch: " + versInfo.FileMajorPart + "." + versInfo.FileMinorPart + "." + versInfo.FileBuildPart); + w.WriteLine("// Build: " + versInfo.FilePrivatePart); w.WriteLine(); w.WriteLine("Public Enum OPCODES"); w.WriteLine("{"); int i = 0; while (Names.Count > 0) { - w.WriteLine(" {0,-64}// 0x{1:X3}", Names.Pop() + "=" + i, i); + w.WriteLine(" {0,-64}// 0x{1:X3}", Names.Pop() + "=" + i + ",", i); i += 1; } @@ -528,34 +555,35 @@ public static void ExtractOpcodes() public static void ExtractSpellFailedReason() { + FileVersionInfo versInfo = FileVersionInfo.GetVersionInfo("Wow.exe"); FileStream f = new("wow.exe", FileMode.Open, FileAccess.Read, FileShare.Read, 10000000); - BinaryReader r1 = new(f); - StreamReader r2 = new(f); FileStream o = new("Global.SpellFailedReasons.cs", FileMode.Create, FileAccess.Write, FileShare.None, 1024); StreamWriter w = new(o); - int REASON_NAME_OFFSET = SearchInFile(f, "SPELL_FAILED_UNKNOWN"); - if (REASON_NAME_OFFSET == -1) + + int LastCastReasonAddress = SearchInFile(f, "SPELL_FAILED_UNKNOWN"); + if (LastCastReasonAddress == -1) { - MessageBox.Show("Wrong offsets!"); + MessageBox.Show("Client does not contain cast result enum!"); } else { Stack Names = new(); string Last = ""; - int Offset = REASON_NAME_OFFSET; + int Offset = LastCastReasonAddress; f.Seek(Offset, SeekOrigin.Begin); - while (Last.Length == 0 || Last.Substring(0, 13) == "SPELL_FAILED_") + while (Last.Length == 0 || (Last.Length > 13 && Last.Substring(0, 13) == "SPELL_FAILED_")) { Last = ReadString(f); - if (Last.Length > 13 && Last.Substring(0, 13) == "SPELL_FAILED_") - { - Names.Push(Last); - } + if (Last.Length > 13 && Last.Substring(0, 13) == "SPELL_FAILED_") + { + Names.Push(Last); + } } MessageBox.Show(string.Format("{0} spell failed reasons extracted.", Names.Count)); w.WriteLine("// Auto generated file"); - w.WriteLine("// {0}", DateAndTime.Now); + w.WriteLine("// Patch: " + versInfo.FileMajorPart + "." + versInfo.FileMinorPart + "." + versInfo.FileBuildPart); + w.WriteLine("// Build: " + versInfo.FilePrivatePart); w.WriteLine(); w.WriteLine("Public Enum SpellFailedReason"); w.WriteLine("{"); @@ -577,48 +605,65 @@ public static void ExtractSpellFailedReason() public static void ExtractChatTypes() { + FileVersionInfo versInfo = FileVersionInfo.GetVersionInfo("Wow.exe"); FileStream f = new("wow.exe", FileMode.Open, FileAccess.Read, FileShare.Read, 10000000); - BinaryReader r1 = new(f); - StreamReader r2 = new(f); FileStream o = new("Global.ChatTypes.cs", FileMode.Create, FileAccess.Write, FileShare.None, 1024); StreamWriter w = new(o); - int START = SearchInFile(f, "CHAT_MSG_RAID_WARNING"); + + // this string is present before the chat msg enum in all versions + int START = SearchInFile(f, "LANGUAGE_LIST_CHANGED"); if (START == -1) { - MessageBox.Show("Wrong offsets!"); + MessageBox.Show("Cannot determine where chat msg enum starts!"); + return; } - else - { - Stack Names = new(); - string Last = ""; - int Offset = START; - f.Seek(Offset, SeekOrigin.Begin); - while (Last.Length == 0 || Last.Substring(0, 9) == "CHAT_MSG_") - { - Last = ReadString(f); - if (Last.Length > 10 && Last.Substring(0, 9) == "CHAT_MSG_") - { - Names.Push(Last); - } - } + START += ("LANGUAGE_LIST_CHANGED").Length; - MessageBox.Show(string.Format("{0} chat types extracted.", Names.Count)); - w.WriteLine("// Auto generated file"); - w.WriteLine("// {0}", DateAndTime.Now); - w.WriteLine(); - w.WriteLine("Public Enum ChatMsg"); - w.WriteLine("{"); - int i = 0; - while (Names.Count > 0) + // skin any null bytes after it + f.Seek(START, SeekOrigin.Begin); + do + { + int chr = f.ReadByte(); + if (chr == -1) { - w.WriteLine(" {0,-64}// 0x{1:X3}", Names.Pop() + " = 0x" + Conversion.Hex(i) + ",", i); - i += 1; + MessageBox.Show("Cannot determine where chat msg enum starts!"); + return; } + if (chr != 0) + break; + START++; + } while (true); + + Stack Names = new(); + string Last = ""; + int Offset = START; + f.Seek(Offset, SeekOrigin.Begin); + while (Last.Length == 0 || Last.Substring(0, 9) == "CHAT_MSG_" || Last.Substring(0, 9) == "RAID_BOSS") + { + Last = ReadString(f); + if (Last.Length > 10 && Last.Substring(0, 9) == "CHAT_MSG_" || Last.Substring(0, 9) == "RAID_BOSS") + { + Names.Push(Last); + } + } - w.WriteLine("}"); - w.Flush(); + MessageBox.Show(string.Format("{0} chat types extracted.", Names.Count)); + w.WriteLine("// Auto generated file"); + w.WriteLine("// Patch: " + versInfo.FileMajorPart + "." + versInfo.FileMinorPart + "." + versInfo.FileBuildPart); + w.WriteLine("// Build: " + versInfo.FilePrivatePart); + w.WriteLine(); + w.WriteLine("Public Enum ChatMsg"); + w.WriteLine("{"); + int i = 0; + while (Names.Count > 0) + { + w.WriteLine(" {0,-64}// 0x{1:X3}", Names.Pop() + " = 0x" + Conversion.Hex(i) + ",", i); + i += 1; } + w.WriteLine("}"); + w.Flush(); + o.Close(); f.Close(); }