/// <summary>Saves the mission to <see cref="MissionFile.MissionPath"/></summary> /// <exception cref="UnauthorizedAccessException">Write permissions for <see cref="MissionFile.MissionPath"/> are denied</exception> public void Save() { //[JB] Rewrote the backup logic since it was broken. It should now retain the same protection concept with more robust handling. //First check whether the file exists and is read-only. Copying to a backup will inherit the read-only property and prevent any attempt to delete, silently breaking the backup feature unless the user directly intervenes to manually delete it. if (File.Exists(MissionPath) && (File.GetAttributes(MissionPath) & FileAttributes.ReadOnly) != 0) { throw new UnauthorizedAccessException("Cannot save, existing file is read-only."); } FileStream fs = null; //The backup filename must be normalized, as filenames are case-insensitive, unlike strings. If the replace does not work, the resulting backup name will match the source, and attempting to copy will throw an exception. string backup = MissionPath.ToLower().Replace(".tie", "_tie.bak"); bool backupCreated = false, writerCreated = false; if (File.Exists(MissionPath) && MissionPath.ToLower() != backup) { try { if (File.Exists(backup)) { File.Delete(backup); } File.Copy(MissionPath, backup); backupCreated = true; } catch { } } try { if (File.Exists(MissionPath)) { File.Delete(MissionPath); } fs = File.OpenWrite(MissionPath); BinaryWriter bw = new BinaryWriter(fs, System.Text.Encoding.GetEncoding(437)); //[JB] Changed encoding to IBM437 (OEM United States) to properly handle the DOS ASCII character set. writerCreated = true; bw.Write((short)-1); bw.Write((short)FlightGroups.Count); bw.Write((short)Messages.Count); bw.Write((short)3); fs.Position = 0xA; fs.WriteByte((byte)OfficersPresent); fs.Position = 0xD; bw.Write(CapturedOnEjection); fs.Position = 0x18; for (int i = 0; i < 6; i++) { long p = fs.Position; if (EndOfMissionMessageColor[i] != 0) { bw.Write(Convert.ToByte(EndOfMissionMessageColor[i] + 48)); } bw.Write(_endOfMissionMessages[i].ToCharArray()); bw.Write('\0'); fs.Position = p + 0x40; } fs.Position += 2; for (int i = 2; i < 6; i++) { long p = fs.Position; if (_iffHostile[i]) { bw.Write('1'); } bw.Write(_iff[i].ToCharArray()); bw.Write('\0'); fs.Position = p + 0xC; } #region Flightgroups for (int i = 0; i < FlightGroups.Count; i++) { long p = fs.Position; int j; #region Craft bw.Write(FlightGroups[i].Name.ToCharArray()); fs.Position = p + 0xC; bw.Write(FlightGroups[i].Pilot.ToCharArray()); fs.Position = p + 0x18; bw.Write(FlightGroups[i].Cargo.ToCharArray()); fs.Position = p + 0x24; bw.Write(FlightGroups[i].SpecialCargo.ToCharArray()); fs.Position = p + 0x30; if (FlightGroups[i].SpecialCargoCraft == 0) { fs.WriteByte(FlightGroups[i].NumberOfCraft); } else { fs.WriteByte((byte)(FlightGroups[i].SpecialCargoCraft - 1)); } bw.Write(FlightGroups[i].RandSpecCargo); fs.WriteByte(FlightGroups[i].CraftType); fs.WriteByte(FlightGroups[i].NumberOfCraft); fs.WriteByte(FlightGroups[i].Status1); fs.WriteByte(FlightGroups[i].Missile); fs.WriteByte(FlightGroups[i].Beam); fs.WriteByte(FlightGroups[i].IFF); fs.WriteByte(FlightGroups[i].AI); fs.WriteByte(FlightGroups[i].Markings); bw.Write(FlightGroups[i].FollowsOrders); fs.WriteByte(FlightGroups[i].Unknowns.Unknown1); fs.WriteByte(FlightGroups[i].Formation); fs.WriteByte(FlightGroups[i].FormDistance); fs.WriteByte(FlightGroups[i].GlobalGroup); fs.WriteByte(FlightGroups[i].FormLeaderDist); fs.WriteByte((byte)(FlightGroups[i].NumberOfWaves - 1)); fs.WriteByte(FlightGroups[i].Unknowns.Unknown5); fs.WriteByte(FlightGroups[i].PlayerCraft); fs.WriteByte((byte)(FlightGroups[i].Yaw * 0x100 / 360)); fs.WriteByte((byte)((FlightGroups[i].Pitch >= 90 ? FlightGroups[i].Pitch - 270 : FlightGroups[i].Pitch + 90) * 0x100 / 360)); fs.WriteByte((byte)(FlightGroups[i].Roll * 0x100 / 360)); bw.Write(FlightGroups[i].PermaDeathEnabled); fs.WriteByte(FlightGroups[i].PermaDeathID); fs.WriteByte(FlightGroups[i].Unknowns.Unknown11); #endregion #region Arr/Dep fs.WriteByte(FlightGroups[i].Difficulty); for (j = 0; j < 4; j++) { fs.WriteByte(FlightGroups[i].ArrDepTriggers[0][j]); } for (j = 0; j < 4; j++) { fs.WriteByte(FlightGroups[i].ArrDepTriggers[1][j]); } bw.Write(FlightGroups[i].AT1AndOrAT2); fs.WriteByte(FlightGroups[i].Unknowns.Unknown12); fs.WriteByte(FlightGroups[i].ArrivalDelayMinutes); fs.WriteByte(FlightGroups[i].ArrivalDelaySeconds); for (j = 0; j < 4; j++) { fs.WriteByte(FlightGroups[i].ArrDepTriggers[2][j]); } fs.WriteByte(FlightGroups[i].DepartureTimerMinutes); fs.WriteByte(FlightGroups[i].DepartureTimerSeconds); fs.WriteByte(FlightGroups[i].AbortTrigger); fs.WriteByte(FlightGroups[i].Unknowns.Unknown15); fs.WriteByte(FlightGroups[i].Unknowns.Unknown16); fs.WriteByte(FlightGroups[i].Unknowns.Unknown17); fs.WriteByte(FlightGroups[i].ArrivalCraft1); bw.Write(FlightGroups[i].ArrivalMethod1); fs.WriteByte(FlightGroups[i].DepartureCraft1); bw.Write(FlightGroups[i].DepartureMethod1); fs.WriteByte(FlightGroups[i].ArrivalCraft2); bw.Write(FlightGroups[i].ArrivalMethod2); fs.WriteByte(FlightGroups[i].DepartureCraft2); bw.Write(FlightGroups[i].DepartureMethod2); #endregion for (j = 0; j < 3; j++) { for (int k = 0; k < 18; k++) { fs.WriteByte(FlightGroups[i].Orders[j][k]); } } for (j = 0; j < 9; j++) { fs.WriteByte(FlightGroups[i].Goals[j]); } fs.Position++; for (j = 0; j < 4; j++) { for (int k = 0; k < 15; k++) { bw.Write((short)(FlightGroups[i].Waypoints[k][j] * (j == 1 ? -1 : 1))); } } bw.Write(FlightGroups[i].Unknowns.Unknown19); fs.Position++; fs.WriteByte(FlightGroups[i].Unknowns.Unknown20); bw.Write(FlightGroups[i].Unknowns.Unknown21); fs.Position = p + 0x124; } #endregion #region Messages for (int i = 0; i < Messages.Count; i++) { long p = fs.Position; if (Messages[i].Color != 0) { bw.Write((byte)(Messages[i].Color + 0x30)); } bw.Write(Messages[i].MessageString.ToCharArray()); bw.Write('\0'); fs.Position = p + 0x3F; fs.WriteByte(0); for (int j = 0; j < 4; j++) { fs.WriteByte(Messages[i].Triggers[0][j]); } for (int j = 0; j < 4; j++) { fs.WriteByte(Messages[i].Triggers[1][j]); } bw.Write(Messages[i].Short.ToCharArray()); bw.Write('\0'); fs.Position = p + 0x57; fs.WriteByte(0); fs.WriteByte(Messages[i].Delay); bw.Write(Messages[i].Trig1AndOrTrig2); } #endregion #region Globals for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { fs.WriteByte(GlobalGoals.Goals[i].Triggers[0][j]); } for (int j = 0; j < 4; j++) { fs.WriteByte(GlobalGoals.Goals[i].Triggers[1][j]); } fs.Position += 0x11; bw.Write(GlobalGoals.Goals[i].T1AndOrT2); fs.Position += 2; } #endregion #region Briefing bw.Write(Briefing.Length); bw.Write(Briefing.Unknown1); bw.Write(Briefing.StartLength); bw.Write(Briefing.EventsLength); fs.Position += 2; byte[] briefBuffer = new byte[Briefing.Events.Length * 2]; Buffer.BlockCopy(Briefing.Events, 0, briefBuffer, 0, briefBuffer.Length); bw.Write(briefBuffer); for (int i = 0; i < 32; i++) { string str_t = Briefing.BriefingTag[i].Replace("\r", ""); int j = Briefing.BriefingTag[i].Length; bw.Write((short)j); bw.Write(str_t.ToCharArray()); } for (int i = 0; i < 32; i++) { string str_t = Briefing.BriefingString[i].Replace("\r", ""); int j = Briefing.BriefingString[i].Length; bw.Write((short)j); bw.Write(str_t.ToCharArray()); } #endregion #region Questions for (int i = 0; i < 10; i++) { int j; string str_q = BriefingQuestions.PreMissQuestions[i]; string str_a = BriefingQuestions.PreMissAnswers[i]; str_a = str_a.Replace("\r", ""); str_a = str_a.Replace(']', Convert.ToChar(1)); str_a = str_a.Replace('[', Convert.ToChar(2)); j = str_q.Length + str_a.Length; if (j == 0) { bw.Write((short)0); continue; //if it doesn't exist, don't even bother with the rest of this } j++; //takes into account the q/a spacer bw.Write((short)j); bw.Write(str_q.ToCharArray()); fs.WriteByte(0xA); bw.Write(str_a.ToCharArray()); } for (int i = 0; i < 10; i++) { int j; string str_q = BriefingQuestions.PostMissQuestions[i]; string str_a = BriefingQuestions.PostMissAnswers[i]; str_a = str_a.Replace("\r", ""); str_a = str_a.Replace(']', Convert.ToChar(1)); //[JB] Debrief questions use the same highlight scheme. Added character conversions. str_a = str_a.Replace('[', Convert.ToChar(2)); j = str_q.Length + str_a.Length; if (j == 0) { bw.Write((short)0); continue; } j += 3; long p = fs.Position; bw.Write((short)j); fs.WriteByte(BriefingQuestions.PostTrigger[i]); fs.WriteByte(BriefingQuestions.PostTrigType[i]); bw.Write(str_q.ToCharArray()); fs.WriteByte(0xA); bw.Write(str_a.ToCharArray()); } #endregion bw.Write((short)0x2106); fs.WriteByte(0xFF); fs.SetLength(fs.Position); fs.Close(); } catch { if (fs != null) { fs.Close(); //Prevent object instance exception if it failed to open. } //If the stream was opened successfully but failed at any point during writing, the contents are corrupt, so restore from backup. Otherwise it's probably a different kind of access error, such as file already open. if (writerCreated && backupCreated) { File.Delete(MissionPath); File.Copy(backup, MissionPath); File.Delete(backup); } throw; } //Save completed successfully. if (backupCreated) { File.Delete(backup); } }
/// <summary>Saves the mission to <see cref="MissionFile.MissionPath"/></summary> /// <exception cref="UnauthorizedAccessException">Write permissions for <see cref="MissionFile.MissionPath"/> are denied</exception> public void Save() { //[JB] Added backup logic. See the TIE Save() function for comments. if (File.Exists(MissionPath) && (File.GetAttributes(MissionPath) & FileAttributes.ReadOnly) != 0) { throw new UnauthorizedAccessException("Cannot save, existing file is read-only."); } FileStream fs = null; string backup = MissionPath.ToLower().Replace(".xwi", "_xwi.bak"); bool backupCreated = false, writerCreated = false; if (File.Exists(MissionPath) && MissionPath.ToLower() != backup) { try { if (File.Exists(backup)) { File.Delete(backup); } File.Copy(MissionPath, backup); backupCreated = true; } catch { } } try { if (File.Exists(MissionPath)) { File.Delete(MissionPath); } fs = File.OpenWrite(MissionPath); BinaryWriter bw = new BinaryWriter(fs, System.Text.Encoding.GetEncoding(437)); //[JB] Changed encoding to IBM437 (OEM United States) to properly handle the DOS ASCII character set. writerCreated = true; bw.Write((short)0x2); //Platform bw.Write(TimeLimitMinutes); bw.Write(EndEvent); bw.Write(RndSeed); bw.Write(Location); for (int i = 0; i < 3; i++) { long p = fs.Position; bw.Write(_endOfMissionMessages[i].ToCharArray()); bw.Write('\0'); fs.Position = p + 0x40; } int numFG = 0; int numOG = 0; for (int i = 0; i < FlightGroups.Count; i++) { if (FlightGroups[i].CraftType > 0) { numFG++; } if (FlightGroups[i].ObjectType > 0) { numOG++; } } bw.Write((short)numFG); bw.Write((short)numOG); #region Flightgroups for (int i = 0; i < FlightGroups.Count; i++) { if (FlightGroups[i].CraftType == 0) { continue; } long p = fs.Position; #region Craft bw.Write(FlightGroups[i].Name.ToCharArray()); fs.Position = p + 0x10; bw.Write(FlightGroups[i].Cargo.ToCharArray()); fs.Position = p + 0x20; bw.Write(FlightGroups[i].SpecialCargo.ToCharArray()); fs.Position = p + 0x30; bw.Write((short)FlightGroups[i].SpecialCargoCraft); bw.Write((short)FlightGroups[i].CraftType); bw.Write((short)FlightGroups[i].IFF); bw.Write((short)FlightGroups[i].Status1); bw.Write((short)FlightGroups[i].NumberOfCraft); bw.Write((short)FlightGroups[i].NumberOfWaves); #endregion #region Arr/Dep bw.Write((short)FlightGroups[i].ArrivalEvent); bw.Write((short)FlightGroups[i].ArrivalDelay); bw.Write((short)FlightGroups[i].ArrivalFG); bw.Write((short)FlightGroups[i].Mothership); bw.Write((short)FlightGroups[i].ArrivalHyperspace); bw.Write((short)FlightGroups[i].DepartureHyperspace); #endregion //Waypoints (7 real waypoints, rest are virtualized BRF coordinate sets) for (int j = 0; j < 4; j++) { for (int k = 0; k < 7; k++) { bw.Write(FlightGroups[i].Waypoints[k][j] /* * (j == 1 ? -1 : 1))*/); } } //More craft info bw.Write((short)FlightGroups[i].Formation); bw.Write((short)FlightGroups[i].PlayerCraft); bw.Write((short)FlightGroups[i].AI); bw.Write(FlightGroups[i].Order); bw.Write(FlightGroups[i].DockTimeThrottle); bw.Write((short)FlightGroups[i].Markings); bw.Write((short)FlightGroups[i].Markings); //Was Unknown1. Another color value. Official missions always have the same color. bw.Write(FlightGroups[i].Objective); bw.Write(FlightGroups[i].TargetPrimary); bw.Write(FlightGroups[i].TargetSecondary); } // OBJECT GROUPS for (int i = 0; i < FlightGroups.Count; i++) { if (FlightGroups[i].ObjectType == 0) { continue; } long p = fs.Position; bw.Write(FlightGroups[i].Name.ToCharArray()); fs.Position = p + 0x10; bw.Write(FlightGroups[i].Cargo.ToCharArray()); fs.Position = p + 0x20; bw.Write(FlightGroups[i].SpecialCargo.ToCharArray()); fs.Position = p + 0x30; bw.Write(FlightGroups[i].SpecialCargoCraft); bw.Write(FlightGroups[i].ObjectType); bw.Write((short)FlightGroups[i].IFF); //The format begins to deviate here bw.Write((short)FlightGroups[i].Formation); bw.Write((short)FlightGroups[i].NumberOfCraft); bw.Write(FlightGroups[i].Waypoints[0][0]); bw.Write(FlightGroups[i].Waypoints[0][1]); bw.Write(FlightGroups[i].Waypoints[0][2]); bw.Write(FlightGroups[i].Yaw); //Conversion to/from degrees handled in the editor. This helps preserve the exact values used by pilot proving ground platforms. bw.Write(FlightGroups[i].Pitch); bw.Write(FlightGroups[i].Roll); } #endregion fs.SetLength(fs.Position); fs.Close(); } catch { if (fs != null) { fs.Close(); } if (writerCreated && backupCreated) { File.Delete(MissionPath); File.Copy(backup, MissionPath); File.Delete(backup); } throw; } if (backupCreated) { File.Delete(backup); } //Finished saving XWI file. Now save the BRF file. string BriefingPath = MissionPath; bool upper; //This stuff is merely to try and make the BRF extension match the case of the XWI, so the file names look nice and tidy. int extPos = BriefingPath.LastIndexOf('.'); if (extPos >= 0 && extPos < BriefingPath.Length - 1) { upper = char.IsUpper(BriefingPath[extPos + 1]); //Detect case from the first character of the extension. BriefingPath = BriefingPath.Remove(extPos + 1); //Strip extension so a new one can be added. } else { upper = char.IsUpper(BriefingPath[BriefingPath.Length - 1]); //If for some reason the file has no extension, detect from the last character of the name. BriefingPath += "."; } BriefingPath += upper ? "BRF" : "brf"; if (File.Exists(BriefingPath) && (File.GetAttributes(BriefingPath) & FileAttributes.ReadOnly) != 0) { throw new UnauthorizedAccessException("Cannot save briefing, existing file is read-only."); } fs = null; backup = BriefingPath.ToLower().Replace(".brf", "_brf.bak"); backupCreated = false; writerCreated = false; if (File.Exists(BriefingPath) && BriefingPath.ToLower() != backup) { try { if (File.Exists(backup)) { File.Delete(backup); } File.Copy(BriefingPath, backup); backupCreated = true; } catch { } } try { if (File.Exists(BriefingPath)) { File.Delete(BriefingPath); } fs = File.OpenWrite(BriefingPath); BinaryWriter bw = new BinaryWriter(fs, System.Text.Encoding.GetEncoding(437)); //[JB] Changed encoding to IBM437 (OEM United States) to properly handle the DOS ASCII character set. writerCreated = true; bw.Write((short)2); //Version bw.Write((short)FlightGroupsBriefing.Count); bw.Write(Briefing.MaxCoordSet); //Coordinate count; long p = 0; int wp = 0; for (int i = 0; i < Briefing.MaxCoordSet; i++) //Coordinate count { //Just in case there too many coord sets than what the editor allows, read them but only load them if the indexes are valid. if (i == 0) { wp = 0; //SP1 } else { wp = 7 + i - 1; //CS1 starts at [7], but at this point i==1 so subtract to compensate } if (wp >= 10) { wp = -1; } for (int j = 0; j < FlightGroupsBriefing.Count; j++) { for (int k = 0; k < 3; k++) { short dat = 0; if (wp >= 0) { dat = FlightGroupsBriefing[j].Waypoints[wp][k]; } bw.Write(dat); } } } for (int i = 0; i < FlightGroupsBriefing.Count; i++) { if (FlightGroupsBriefing[i].IsFlightGroup()) { bw.Write((short)FlightGroupsBriefing[i].CraftType); } else { bw.Write(FlightGroupsBriefing[i].ObjectType); } bw.Write((short)FlightGroupsBriefing[i].IFF); bw.Write((short)FlightGroupsBriefing[i].NumberOfCraft); bw.Write((short)FlightGroupsBriefing[i].NumberOfWaves); p = fs.Position; bw.Write(FlightGroupsBriefing[i].Name.ToCharArray()); fs.Position = p + 0x10; bw.Write(FlightGroupsBriefing[i].Cargo.ToCharArray()); fs.Position = p + 0x20; bw.Write(FlightGroupsBriefing[i].SpecialCargo.ToCharArray()); fs.Position = p + 0x30; bw.Write(FlightGroupsBriefing[i].SpecialCargoCraft); bw.Write(FlightGroupsBriefing[i].Yaw); bw.Write(FlightGroupsBriefing[i].Pitch); bw.Write(FlightGroupsBriefing[i].Roll); } #region WindowUISettings short count = (short)Briefing.WindowSettings.Count; bw.Write(count); for (int i = 0; i < count; i++) { for (int j = 0; j < 5; j++) { BriefingUIItem item = Briefing.WindowSettings[i].Items[j]; bw.Write(item.Top); bw.Write(item.Left); bw.Write(item.Bottom); bw.Write(item.Right); bw.Write(Convert.ToInt16(item.IsVisible)); } } #endregion WindowUISettings #region Pages bw.Write((short)Briefing.Pages.Count); for (int i = 0; i < Briefing.Pages.Count; i++) { BriefingPage pg = Briefing.GetBriefingPage(i); bw.Write(pg.Length); bw.Write(pg.EventsLength); bw.Write(pg.CoordSet); bw.Write(pg.PageType); byte[] briefBuffer = new byte[pg.EventsLength * 2]; Buffer.BlockCopy(pg.Events, 0, briefBuffer, 0, briefBuffer.Length); bw.Write(briefBuffer); } #endregion Pages bw.Write(TimeLimitMinutes); bw.Write(EndEvent); bw.Write(RndSeed); bw.Write(Briefing.MissionLocation); p = fs.Position; for (int i = 0; i < 3; i++) { bw.Write(EndOfMissionMessages[i].ToCharArray()); fs.Position = p + ((i + 1) * 64); } p = fs.Position; for (int i = 0; i < FlightGroupsBriefing.Count; i++) { fs.Position += 68; bw.Write((short)FlightGroupsBriefing[i].PlayerCraft); fs.Position = p + ((i + 1) * 90); } #region Text/Tags bw.Write((short)32); //Count for (int i = 0; i < 32; i++) { bw.Write((short)Briefing.BriefingTag[i].Length); bw.Write(Briefing.BriefingTag[i].ToCharArray()); } bw.Write((short)32); for (int i = 0; i < 32; i++) { string t = Briefing.RemoveBrackets(Briefing.BriefingString[i]); int len = t.Length; bw.Write((short)len); bw.Write(t.ToCharArray()); byte[] highlight = Briefing.TranslateStringToHighlight(Briefing.BriefingString[i]); bw.Write(highlight); } #endregion Text/Tags fs.SetLength(fs.Position); fs.Close(); } catch { if (fs != null) { fs.Close(); } if (writerCreated && backupCreated) { File.Delete(BriefingPath); File.Copy(backup, BriefingPath); File.Delete(backup); } throw; } if (backupCreated) { File.Delete(backup); } }