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;
}