public static bool SaveThisGameToFile(string fileName, Game game, CompileMessages errors) { FileStream ostream = File.Create(fileName); if (ostream == null) { errors.Add(new CompileError(string.Format("Cannot open file {0} for writing", fileName))); return false; } BinaryWriter writer = new BinaryWriter(ostream); WriteString(NativeConstants.GAME_FILE_SIG, NativeConstants.GAME_FILE_SIG.Length, writer); writer.Write(NativeConstants.GAME_DATA_VERSION_CURRENT); writer.Write(AGS.Types.Version.AGS_EDITOR_VERSION.Length); WriteString(AGS.Types.Version.AGS_EDITOR_VERSION, AGS.Types.Version.AGS_EDITOR_VERSION.Length, writer); // Write extended engine caps; none for this version writer.Write((int)0); // An example of writing caps (pseduo-code): // writer.Write(caps.Count); // foreach (cap in caps) // FilePutString(cap.Name); // WriteGameSetupStructBase_Aligned(writer, game); WriteString(game.Settings.GUIDAsString, NativeConstants.MAX_GUID_LENGTH, writer); WriteString(game.Settings.SaveGameFileExtension, NativeConstants.MAX_SG_EXT_LENGTH, writer); WriteString(game.Settings.SaveGameFolderName, NativeConstants.MAX_SG_FOLDER_LEN, writer); if (game.Fonts.Count > NativeConstants.MAX_FONTS) { errors.Add(new CompileError("Too many fonts")); return false; } for (int i = 0; i < game.Fonts.Count; ++i) { writer.Write((byte)(game.Fonts[i].PointSize & NativeConstants.FFLG_SIZEMASK)); } for (int i = 0; i < game.Fonts.Count; ++i) { if (game.Fonts[i].OutlineStyle == FontOutlineStyle.None) { writer.Write((sbyte)-1); } else if (game.Fonts[i].OutlineStyle == FontOutlineStyle.Automatic) { writer.Write(NativeConstants.FONT_OUTLINE_AUTO); } else { writer.Write((byte)game.Fonts[i].OutlineFont); } } for (int i = 0; i < game.Fonts.Count; ++i) { writer.Write(game.Fonts[i].VerticalOffset); } writer.Write(NativeConstants.MAX_SPRITES); byte[] spriteFlags = new byte[NativeConstants.MAX_SPRITES]; UpdateSpriteFlags(game.RootSpriteFolder, spriteFlags); for (int i = 0; i < NativeConstants.MAX_SPRITES; ++i) { writer.Write(spriteFlags[i]); } if (game.InventoryItems.Count > NativeConstants.MAX_INV) { errors.Add(new CompileError("Too many inventory items")); return false; } writer.Write(new byte[68]); // inventory item slot 0 is unused for (int i = 0; i < game.InventoryItems.Count; ++i) { WriteString(game.InventoryItems[i].Description, 24, writer); writer.Write(new byte[4]); // null terminator plus 3 bytes padding writer.Write(game.InventoryItems[i].Image); writer.Write(game.InventoryItems[i].CursorImage); writer.Write(game.InventoryItems[i].HotspotX); writer.Write(game.InventoryItems[i].HotspotY); for (int j = 0; j < 5; ++j) // write "reserved", currently unused { writer.Write(0); } writer.Write(game.InventoryItems[i].PlayerStartsWithItem ? NativeConstants.IFLG_STARTWITH : (char)0); writer.Write(new byte[3]); // 3 bytes padding } if (game.Cursors.Count > NativeConstants.MAX_CURSOR) { errors.Add(new CompileError("Too many cursors")); return false; } for (int i = 0; i < game.Cursors.Count; ++i) { char flags = (char)0; writer.Write(game.Cursors[i].Image); writer.Write((short)game.Cursors[i].HotspotX); writer.Write((short)game.Cursors[i].HotspotY); if (game.Cursors[i].Animate) { writer.Write((short)(game.Cursors[i].View - 1)); if (game.Cursors[i].AnimateOnlyOnHotspots) flags |= NativeConstants.MCF_HOTSPOT; if (game.Cursors[i].AnimateOnlyWhenMoving) flags |= NativeConstants.MCF_ANIMMOVE; } else writer.Write((short)-1); WriteString(game.Cursors[i].Name, 9, writer); writer.Write((byte)0); // null terminator if (game.Cursors[i].StandardMode) flags |= NativeConstants.MCF_STANDARD; writer.Write(flags); writer.Write(new byte[3]); // 3 bytes padding } for (int i = 0; i < game.Characters.Count; ++i) { SerializeInteractionScripts(game.Characters[i].Interactions, writer); } for (int i = 1; i <= game.InventoryItems.Count; ++i) { SerializeInteractionScripts(game.InventoryItems[i - 1].Interactions, writer); } writer.Write(game.TextParser.Words.Count); for (int i = 0; i < game.TextParser.Words.Count; ++i) { WriteStringEncrypted(writer, SafeTruncate(game.TextParser.Words[i].Word, NativeConstants.MAX_PARSER_WORD_LENGTH)); writer.Write((short)game.TextParser.Words[i].WordGroup); } if (!WriteCompiledScript(ostream, game.ScriptsToCompile.GetScriptByFilename(Script.GLOBAL_SCRIPT_FILE_NAME), errors) || !WriteCompiledScript(ostream, game.ScriptsToCompile.GetScriptByFilename(Script.DIALOG_SCRIPTS_FILE_NAME), errors)) { return false; } // Extract all the scripts we want to persist (all the non-headers, except // the global script which was already written) List<Script> scriptsToWrite = new List<Script>(); foreach (ScriptAndHeader scriptAndHeader in game.ScriptsToCompile) { Script script = scriptAndHeader.Script; if (script != null) { if ((!script.FileName.Equals(Script.GLOBAL_SCRIPT_FILE_NAME)) && (!script.FileName.Equals(Script.DIALOG_SCRIPTS_FILE_NAME))) { scriptsToWrite.Add(script); } } } writer.Write(scriptsToWrite.Count); foreach (Script script in scriptsToWrite) { if (!WriteCompiledScript(ostream, script, errors)) { return false; } } ViewsWriter viewsWriter = new ViewsWriter(writer, game); if (!viewsWriter.WriteViews(FolderHelper.GetRootViewFolder(game), game, errors)) { return false; } foreach (Character character in game.Characters) { int flags = 0; if (character.AdjustSpeedWithScaling) flags |= NativeConstants.CHF_SCALEMOVESPEED; if (character.AdjustVolumeWithScaling) flags |= NativeConstants.CHF_SCALEVOLUME; if (!character.Clickable) flags |= NativeConstants.CHF_NOINTERACT; if (!character.DiagonalLoops) flags |= NativeConstants.CHF_NODIAGONAL; if (character.MovementLinkedToAnimation) flags |= NativeConstants.CHF_ANTIGLIDE; if (!character.Solid) flags |= NativeConstants.CHF_NOBLOCKING; if (!character.TurnBeforeWalking) flags |= NativeConstants.CHF_NOTURNING; if (!character.UseRoomAreaLighting) flags |= NativeConstants.CHF_NOLIGHTING; if (!character.UseRoomAreaScaling) flags |= NativeConstants.CHF_MANUALSCALING; writer.Write(character.NormalView - 1); // defview writer.Write(character.SpeechView - 1); // talkview writer.Write(character.NormalView - 1); // view writer.Write(character.StartingRoom); // room writer.Write(0); // prevroom writer.Write(character.StartX); // x writer.Write(character.StartY); // y writer.Write(0); // wait writer.Write(flags); // flags writer.Write((short)0); // following writer.Write((short)0); // followinfo writer.Write(character.IdleView - 1); // idleview writer.Write((short)0); // idletime writer.Write((short)0); // idleleft writer.Write((short)0); // transparency writer.Write((short)0); // baseline writer.Write(0); // activeinv writer.Write(character.SpeechColor); // talkcolor writer.Write(character.ThinkingView - 1); // thinkview writer.Write((short)(character.BlinkingView - 1)); // blinkview writer.Write((short)0); // blinkinterval writer.Write((short)0); // blinktimer writer.Write((short)0); // blinkframe writer.Write(character.UniformMovementSpeed ? // walkspeed_y NativeConstants.UNIFORM_WALK_SPEED : (short)character.MovementSpeedY); writer.Write((short)0); // pic_yoffs writer.Write(0); // z writer.Write(0); // walkwait writer.Write((short)character.SpeechAnimationDelay); // speech_anim_speed writer.Write((short)0); // reserved1 writer.Write((short)0); // blocking_width writer.Write((short)0); // blocking_height writer.Write(0); // index_id writer.Write((short)0); // pic_xoffs writer.Write((short)0); // walkwaitcounter writer.Write((short)0); // loop writer.Write((short)0); // frame writer.Write((short)0); // walking writer.Write((short)0); // animating writer.Write(character.UniformMovementSpeed ? // walkspeed (short)character.MovementSpeed : (short)character.MovementSpeedX); writer.Write((short)character.AnimationDelay); // animspeed bool isPlayer = (character == game.PlayerCharacter); foreach (InventoryItem invItem in game.InventoryItems) // inv[MAX_INV] { if ((isPlayer) && (invItem.PlayerStartsWithItem)) writer.Write((short)1); else writer.Write((short)0); } if (game.InventoryItems.Count < NativeConstants.MAX_INV) { writer.Write(new byte[(NativeConstants.MAX_INV - game.InventoryItems.Count) * sizeof(short)]); } writer.Write((short)0); // actx writer.Write((short)0); // acty WriteString(character.RealName, 40, writer); // name WriteString(character.ScriptName, NativeConstants.MAX_SCRIPT_NAME_LEN, writer); // scrname writer.Write((char)1); // on writer.Write((byte)0); // alignment padding } for (int i = 0; i < NativeConstants.MAXLIPSYNCFRAMES; ++i) { WriteString(game.LipSync.CharactersPerFrame[i], 50, writer); } for (int i = 0; i < NativeConstants.MAXGLOBALMES; ++i) { if (string.IsNullOrEmpty(game.GlobalMessages[i])) continue; WriteStringEncrypted(writer, game.GlobalMessages[i]); } if (game.Dialogs.Count > NativeConstants.MAX_DIALOG) { errors.Add(new CompileError("Too many dialogs")); return false; } foreach (Dialog curDialog in game.Dialogs) { for (int i = 0; (i < NativeConstants.MAXTOPICOPTIONS) && (i < curDialog.Options.Count); ++i) { WriteString(curDialog.Options[i].Text, 150, writer); // optionnames } for (int i = curDialog.Options.Count; i < NativeConstants.MAXTOPICOPTIONS; ++i) { WriteString("", 150, writer); } for (int i = 0; (i < NativeConstants.MAXTOPICOPTIONS) && (i < curDialog.Options.Count); ++i) { DialogOption option = curDialog.Options[i]; int flags = 0; if (!option.Say) flags |= NativeConstants.DFLG_NOREPEAT; if (option.Show) flags |= NativeConstants.DFLG_ON; writer.Write(flags); // optionflags } for (int i = curDialog.Options.Count; i < NativeConstants.MAXTOPICOPTIONS; ++i) { writer.Write(0); } writer.Write(new byte[4]); // optionscripts writer.Write(new byte[NativeConstants.MAXTOPICOPTIONS * sizeof(short)]); // entrypoints writer.Write((short)0); // startupentrypoint writer.Write((short)0); // codesize writer.Write(curDialog.Options.Count); // numoptions writer.Write(curDialog.ShowTextParser ? NativeConstants.DTFLG_SHOWPARSER : 0); // topicflags } GUIsWriter guisWriter = new GUIsWriter(writer, game); guisWriter.WriteAllGUIs(); if (!WritePluginsToDisk(writer, game, errors)) { return false; } writer.Write(NativeConstants.CustomPropertyVersion.Current); writer.Write(game.PropertySchema.PropertyDefinitions.Count); foreach (CustomPropertySchemaItem schemaItem in game.PropertySchema.PropertyDefinitions) { FilePutString(schemaItem.Name, writer); writer.Write((int)schemaItem.Type); FilePutString(schemaItem.Description, writer); FilePutString(schemaItem.DefaultValue, writer); } for (int i = 0; i < game.Characters.Count; ++i) { CustomPropertiesWriter.Write(writer, game.Characters[i].Properties); } writer.Write(1); // inv slot 0 is unused, write the property header (int 1) writer.Write(0); // then write the number of props used by this inv item (int 0) for (int i = 0; i < game.InventoryItems.Count; ++i) { CustomPropertiesWriter.Write(writer, game.InventoryItems[i].Properties); } for (int i = 0; i < game.ViewCount; ++i) // ViewCount is highest numbered view { View view = game.FindViewByID(i + 1); if (view != null) FilePutNullTerminatedString(view.Name, view.Name.Length + 1, writer); else writer.Write((byte)0); // view is null, so its name is just a single NUL byte } writer.Write((byte)0); // inventory slot 0 is unused, so its name is just a single NUL byte for (int i = 0; i < game.InventoryItems.Count; ++i) { string buffer = game.InventoryItems[i].Name; FilePutNullTerminatedString(buffer, buffer.Length + 1, writer); } for (int i = 0; i < game.Dialogs.Count; ++i) { string buffer = game.Dialogs[i].Name; FilePutNullTerminatedString(buffer, buffer.Length + 1, writer); } writer.Write(game.AudioClipTypes.Count + 1); // hard coded SPEECH audio type 0 writer.Write(0); // id writer.Write(1); // reservedChannels writer.Write(0); // volume_reduction_while_speech_playing writer.Write(0); // crossfadeSpeed writer.Write(0); // reservedForFuture for (int i = 1; i < (game.AudioClipTypes.Count + 1); ++i) { writer.Write(i); // id writer.Write(game.AudioClipTypes[i - 1].MaxChannels); // reservedChannels writer.Write(game.AudioClipTypes[i - 1].VolumeReductionWhileSpeechPlaying); // volume_reduction_while_speech_playing writer.Write((int)game.AudioClipTypes[i - 1].CrossfadeClips); // crossfadeSpeed writer.Write(0); } IList<AudioClip> allClips = game.CachedAudioClipListForCompile; writer.Write(allClips.Count); for (int i = 0; i < allClips.Count; ++i) { AudioClip clip = allClips[i]; writer.Write(0); // id WriteString(SafeTruncate(clip.ScriptName, 29), 30, writer); // scriptName WriteString(SafeTruncate(clip.CacheFileNameWithoutPath, 14), 15, writer); // fileName writer.Write((byte)clip.BundlingType); // bundlingType writer.Write((byte)clip.Type); // type writer.Write((byte)clip.FileType); // fileType writer.Write(clip.ActualRepeat ? (byte)1 : (byte)0); // defaultRepeat writer.Write((byte)0); // struct alignment padding writer.Write((short)clip.ActualPriority); // defaultPriority writer.Write((short)clip.ActualVolume); // defaultVolume writer.Write(new byte[2]); // struct alignment padding writer.Write(0); // reserved } writer.Write(game.GetAudioArrayIndexFromAudioClipIndex(game.Settings.PlaySoundOnScore)); if (game.Settings.DebugMode) { writer.Write(game.Rooms.Count); for (int i = 0; i < game.Rooms.Count; ++i) { IRoom room = game.Rooms[i]; writer.Write(room.Number); if (room.Description != null) { FilePutNullTerminatedString(room.Description, 500, writer); } else writer.Write((byte)0); } } writer.Close(); GC.Collect(); return true; }
public bool WriteViews(IViewFolder folder, Game game, CompileMessages errors) { if (writer == null) { errors.Add(new CompileError("Could not write views: Invalid stream (NULL)")); return false; } foreach (View view in views) { // views are not always sequential, so we may have some null entries; // but even in that case we must write number of loops (0) to conform // to the data format short numLoops = (short)(view != null ? view.Loops.Count : 0); writer.Write(numLoops); for (int i = 0; i < numLoops; ++i) { short numFrames = (short)view.Loops[i].Frames.Count; writer.Write(numFrames); writer.Write(view.Loops[i].RunNextLoop ? NativeConstants.LOOPFLAG_RUNNEXTLOOP : 0); for (int j = 0; j < numFrames; ++j) { ViewFrame frame = view.Loops[i].Frames[j]; writer.Write(frame.Image); writer.Write((short)0); // unused x-offset writer.Write((short)0); // unused y-offset writer.Write((short)frame.Delay); writer.Write((short)0); // struct alignment padding writer.Write(frame.Flipped ? NativeConstants.VFLG_FLIPSPRITE : 0); writer.Write(frame.Sound > 0 ? game.GetAudioArrayIndexFromAudioClipIndex(frame.Sound) : -1); writer.Write(0); // unused reservedForFuture[0] writer.Write(0); // unused reservedForFuture[1] } } } return true; }