/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { string FormattedMessage = String.Format( "</c><c=#FFFF00>{0}</c>", Message); NWScript.Vector3 v; v.x = v.y = v.z = 0.0f; Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); Script.FloatingTextStringOnCreature(FormattedMessage, PlayerObject, CLRScriptBase.FALSE, 5.0f, CLRScriptBase.COLOR_WHITE, CLRScriptBase.COLOR_WHITE, 0.0f, v); } Database.ACR_IncrementStatistic("BROADCAST_MESSAGE"); Script.WriteTimestampedLogEntry("Received broadcast notification: " + Message); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { int SourceServerId = (SourceServer != null) ? SourceServer.ServerId : 0; Script.WriteTimestampedLogEntry(String.Format("RunScriptEvent.DispatchEvent: Executing script {0} ({1}) on request from {2}.", ScriptName, ScriptArgument, SourceServerId)); Script.AddScriptParameterInt(SourceServerId); Script.AddScriptParameterString(ScriptArgument); Script.ExecuteScriptEnhanced(ScriptName, Script.GetModule(), CLRScriptBase.TRUE); Database.ACR_IncrementStatistic("RUN_REMOTE_SCRIPT"); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { if (Database.ACR_GetPlayerID(PlayerObject) != Player.PlayerId) continue; Database.ACR_IncrementStatistic("DISCONNECT_PLAYER"); Script.WriteTimestampedLogEntry("DisconnectPlayerEvent.DispatchEvent: Disconnecting player " + Script.GetPCPlayerName(PlayerObject) + " due to IPC request."); Script.BootPC(PlayerObject); return; } Script.WriteTimestampedLogEntry("DisconnectPlayerEvent.DispatchEvent: No player '" + Player.PlayerName + "' found locally connected to disconnect."); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { string FormattedMessage = String.Format( "</c><c=#FFFF00>Server shutting down: {0}</c>", Message); NWScript.Vector3 v; v.x = v.y = v.z = 0.0f; Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); Script.FloatingTextStringOnCreature(FormattedMessage, PlayerObject, CLRScriptBase.FALSE, 5.0f, CLRScriptBase.COLOR_WHITE, CLRScriptBase.COLOR_WHITE, 0.0f, v); } Script.WriteTimestampedLogEntry("Received shutdown request: " + Message); Database.ACR_IncrementStatistic("SERVER_SHUTDOWN"); Script.SendInfrastructureIrcMessage(String.Format( "Server '{0}' shutting down or restarting: {1}", Script.GetName(Script.GetModule()), Message)); Script.DelayCommand(5.0f, delegate() { Database.ACR_FlushAllQueryQueues(); SystemInfo.ShutdownGameServer(Script); }); }
/// <summary> /// Recompile all scripts in the module. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <param name="Database">Supplies the database connection.</param> private static void CompileModuleScripts(ACR_ServerCommunicator Script, ALFA.Database Database) { ALFA.ScriptCompiler.CompilerResult Result; string CompilerOptions = Script.GetLocalString(Script.GetModule(), "ACR_MOD_COMPILER_OPTIONS"); List<string> CompilerOutput = new List<string>(); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Compiling module scripts with compiler options '{0}'...", CompilerOptions)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' is recompiling module scripts.", Script.GetName(Script.GetModule()))); Result = ALFA.ScriptCompiler.CompileScript("*.nss", CompilerOptions, delegate(string Line) { if (!String.IsNullOrWhiteSpace(Line)) { Script.WriteTimestampedLogEntry(Line); } return false; }); foreach (string Message in Result.Warnings) { try { CompilerOutput.Add(Message); } catch (Exception) { } } if (Result.Compiled) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.CompileModuleScripts: Module successfully recompiled."); Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE"); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' successfully recompiled module with {1} warning(s) for content patch deployment.", Script.GetName(Script.GetModule()), Result.Warnings.Count)); } else { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: {0} error(s) compiling module!", Result.Errors.Count)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' had {1} error(s), {2} warning(s) recompiling module for content patch deployment.", Script.GetName(Script.GetModule()), Result.Errors.Count, Result.Warnings.Count)); foreach (string Message in Result.Errors) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Error '{0}'.", Message)); try { CompilerOutput.Add(Message); } catch (Exception) { } } Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE_FAILED"); } // // Save compiler output to a temporary file for later retrieval. // try { string FileName = String.Format("{0}{1}ALFAModuleRecompile.log", Path.GetTempPath(), Path.DirectorySeparatorChar); File.WriteAllLines(FileName, CompilerOutput); } catch (Exception) { } }
/// <summary> /// Process and apply content patches. A server restart is scheduled /// if required for the content patch. /// </summary> /// <param name="ContentPatchPath">Supplies the configured content /// patch base path, from the database config table.</param> /// <param name="Database">Supplies the database object.</param> /// <param name="Script">Supplies the script object.</param> /// <param name="ConnectionString">Supplies the updater file store /// connection string.</param> /// <returns>True if a patch was applied and a reboot is required for /// it to take effect.</returns> public static bool ProcessContentPatches(string ContentPatchPath, ALFA.Database Database, ACR_ServerCommunicator Script, string ConnectionString) { bool ContentChanged = false; string Version = Database.ACR_GetHAKVersion(); List<ContentPatchFile> PatchFiles = new List<ContentPatchFile>(); ContentPatchPaths LocalPaths = new ContentPatchPaths() { HakPath = ALFA.SystemInfo.GetHakDirectory(), OverridePath = ALFA.SystemInfo.GetOverrideDirectory() + "ACR_ContentPatches" }; string RemoteContentPatchPath = String.Format("{0}{1}\\{2}", ALFA.SystemInfo.GetCentralVaultPath(), ContentPatchPath, Version); string FileStoreContentPatchPath = String.Format("{0}/{1}", ContentPatchPath, Version).Replace('\\', '/'); bool RecompileModule = false; bool SentNotification = false; Database.ACR_SQLQuery(String.Format( "SELECT `FileName`, `Location`, `Checksum`, `RecompileModule` FROM `content_patch_files` WHERE `HakVersion` = '{0}'", Database.ACR_SQLEncodeSpecialChars(Version))); while (Database.ACR_SQLFetch()) { ContentPatchFile PatchFile = new ContentPatchFile(); PatchFile.FileName = Database.ACR_SQLGetData(0); PatchFile.Location = Database.ACR_SQLGetData(1); PatchFile.Checksum = Database.ACR_SQLGetData(2); PatchFile.RecompileModule = ALFA.Database.ACR_ConvertDatabaseStringToBoolean(Database.ACR_SQLGetData(3)); if (PatchFile.Location != "override" && PatchFile.Location != "hak") { continue; } if (!ALFA.SystemInfo.IsSafeFileName(PatchFile.FileName)) continue; PatchFiles.Add(PatchFile); } if (!Directory.Exists(LocalPaths.OverridePath)) Directory.CreateDirectory(LocalPaths.OverridePath); // // Remove entries in the ACR patch override directory that are not // listed in the patch table. These may be patches for a previous // version, and are not applicable. // foreach (string DirFile in Directory.GetFiles(LocalPaths.OverridePath)) { ContentPatchFile FoundPatch = (from PF in PatchFiles where (PF.FileName.Equals(Path.GetFileName(DirFile), StringComparison.InvariantCultureIgnoreCase) && PF.Location == "override") select PF).FirstOrDefault(); if (FoundPatch == null) { Database.ACR_IncrementStatistic("CONTENT_PATCH_REMOVE_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Removing extraneous file {0} from {1}", Path.GetFileName(DirFile), LocalPaths.OverridePath)); File.Delete(DirFile); ContentChanged = true; } } // // Verify that the MD5 checksum of each of the content files in the // ACR patch override directory matches the database's expected // checksum. If not (or if the file in question didn't exist), // then copy the new version over from the central vault. // using (MD5CryptoServiceProvider MD5Csp = new MD5CryptoServiceProvider()) { foreach (ContentPatchFile PatchFile in PatchFiles) { bool Matched = false; bool Rename = false; FileUpdateMethod UpdateMethod; string LocalPath = PatchFile.GetLocalPath(LocalPaths); string RemotePath = String.Format("{0}\\{1}", RemoteContentPatchPath, PatchFile.FileName); string FileStorePath = String.Format("{0}/{1}", FileStoreContentPatchPath, PatchFile.FileName).Replace('\\', '/'); string LocalHashString = "<no such file>"; string TransferTempFilePath = LocalPath + ".patchxfer"; if (File.Exists(LocalPath)) { LocalHashString = GetFileChecksum(LocalPath, MD5Csp); Matched = (LocalHashString.ToString() == PatchFile.Checksum.ToLower()); } if (Matched) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} is up to date (checksum {1}).", PatchFile.FileName, LocalHashString)); } else { if (File.Exists(TransferTempFilePath)) { try { File.Delete(TransferTempFilePath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2}.", e, TransferTempFilePath, PatchFile.FileName)); } } Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} needs to be updated (local checksum {1}, remote checksum {2}). Copying file...", PatchFile.FileName, LocalHashString, PatchFile.Checksum)); if (PatchFile.RecompileModule) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} requires a module recompile, flagging module for recompilation.", PatchFile.FileName)); RecompileModule = true; } if (!SentNotification) { Script.SendInfrastructureIrcMessage(String.Format( "Server '{0}' is applying a content patch, and will restart shortly.", Script.GetName(Script.GetModule()))); SentNotification = true; } Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' is updating content file '{1}'.", Script.GetName(Script.GetModule()), PatchFile.FileName)); // // The file needs to be updated. Copy it over and // re-validate the checksum. If the checksum did not // match, log an error and delete the file. // try { try { // // Try first to download via the file store // provider. If that fails (e.g. the file // store is not supported), then fall back to // the traditional remote vault share transfer // mechanism. // try { DownloadContentPatchFromFileStore(FileStorePath, TransferTempFilePath, ConnectionString, Script); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ModuleContentPatcher.ProcessContentPatches: Couldn't retrieve uncompressed file {0} from Azure, falling back to file share, due to exception: {1}", FileStorePath, e)); File.Copy(RemotePath, TransferTempFilePath, true); } } catch { throw; } UpdateMethod = PatchFile.UpdateMethod; // // If we are patching a hak, rename it away so that // the file can be written to. // switch (UpdateMethod) { case FileUpdateMethod.FileUpdateMethodRename: if (File.Exists(LocalPath)) { string OldFileName = LocalPath + ".old"; if (File.Exists(OldFileName)) File.Delete(OldFileName); File.Move(LocalPath, OldFileName); Rename = true; } break; case FileUpdateMethod.FileUpdateMethodDirectReplace: break; } Database.ACR_IncrementStatistic("CONTENT_PATCH_" + PatchFile.Location.ToUpper()); try { if (Rename) File.Move(TransferTempFilePath, LocalPath); else File.Copy(TransferTempFilePath, LocalPath, true); } catch { if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename) { string OldFileName = LocalPath + ".old"; try { if (File.Exists(LocalPath)) File.Delete(LocalPath); } catch { } File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } throw; } if (GetFileChecksum(LocalPath, MD5Csp) != PatchFile.Checksum.ToLower()) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} was copied, but checksum did not match {1}! This may indicate a configuration error in the database, or file corruption in transit.", PatchFile.FileName, PatchFile.Checksum)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' had checksum mismatch for content patch file {1} after hotfix file deployment, rolling back file.", Script.GetName(Script.GetModule()), PatchFile.FileName)); if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename) { string OldFileName = LocalPath + ".old"; try { if (File.Exists(LocalPath)) File.Delete(LocalPath); } catch { } File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } Database.ACR_IncrementStatistic("CONTENT_PATCH_INCORRECT_CHECKSUM"); } else { ContentChanged = true; Database.ACR_IncrementStatistic("CONTENT_PATCH_UPDATED_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Successfully updated content patch file {0}.", PatchFile.FileName)); } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Exception {0} updating content patch file {1}.", e, PatchFile.FileName)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' encountered exception deploying content patch file {1}, rolling back file.", Script.GetName(Script.GetModule()), PatchFile.FileName)); } } if (File.Exists(TransferTempFilePath)) { try { File.Delete(TransferTempFilePath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2} after patching completed.", e, TransferTempFilePath, PatchFile.FileName)); } } } } // // Update autodownloader configuration, as necessary. // try { if (ProcessModuleDownloaderResourcesUpdates(Database, Script)) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: Autodownloader configuration updated."); ContentChanged = true; } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} updating autodownloader configuration.", e)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' encountered exception updating autodownloader configuration.", Script.GetName(Script.GetModule()))); } if (ContentChanged) { if (RecompileModule) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: A module recompile is required; recompiling module..."); CompileModuleScripts(Script, Database); } Database.ACR_IncrementStatistic("CONTENT_PATCH_REBOOT"); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' restarting after content patch deployment (old HAK ACR version {1} build date {2}, old module ACR version {3}).", Script.GetName(Script.GetModule()), Database.ACR_GetHAKVersion(), Database.ACR_GetHAKBuildDate(), Database.ACR_GetVersion())); } return ContentChanged; }
/// <summary> /// Process and apply content patches. A server restart is scheduled /// if required for the content patch. /// </summary> /// <param name="ContentPatchPath">Supplies the configured content /// patch base path, from the database config table.</param> /// <param name="Database">Supplies the database object.</param> /// <param name="Script">Supplies the script object.</param> /// <returns>True if a patch was applied and a reboot is required for /// it to take effect.</returns> public static bool ProcessContentPatches(string ContentPatchPath, ALFA.Database Database, ACR_ServerCommunicator Script) { bool ContentChanged = false; string Version = Database.ACR_GetHAKVersion(); List<ContentPatchFile> PatchFiles = new List<ContentPatchFile>(); string LocalContentPatchPath = ALFA.SystemInfo.GetOverrideDirectory() + "ACR_ContentPatches"; string LocalContentPatchHakPath = ALFA.SystemInfo.GetHakDirectory(); string RemoteContentPatchPath = String.Format("{0}{1}\\{2}", ALFA.SystemInfo.GetCentralVaultPath(), ContentPatchPath, Version); Database.ACR_SQLQuery(String.Format( "SELECT `FileName`, `Location`, `Checksum` FROM `content_patch_files` WHERE `HakVersion` = '{0}'", Database.ACR_SQLEncodeSpecialChars(Version))); while (Database.ACR_SQLFetch()) { ContentPatchFile PatchFile = new ContentPatchFile(); PatchFile.FileName = Database.ACR_SQLGetData(0); PatchFile.Location = Database.ACR_SQLGetData(1); PatchFile.Checksum = Database.ACR_SQLGetData(2); if (PatchFile.Location != "override" && PatchFile.Location != "hak") { continue; } if (!ALFA.SystemInfo.IsSafeFileName(PatchFile.FileName)) continue; PatchFiles.Add(PatchFile); } if (!Directory.Exists(LocalContentPatchPath)) Directory.CreateDirectory(LocalContentPatchPath); // // Remove entries in the ACR patch override directory that are not // listed in the patch table. These may be patches for a previous // version, and are not applicable. // foreach (string DirFile in Directory.GetFiles(LocalContentPatchPath)) { ContentPatchFile FoundPatch = (from PF in PatchFiles where (PF.FileName.Equals(Path.GetFileName(DirFile), StringComparison.InvariantCultureIgnoreCase) && PF.Location == "override") select PF).FirstOrDefault(); if (FoundPatch == null) { Database.ACR_IncrementStatistic("CONTENT_PATCH_REMOVE_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Removing extraneous file {0} from {1}", Path.GetFileName(DirFile), LocalContentPatchPath)); File.Delete(DirFile); ContentChanged = true; } } // // Verify that the MD5 checksum of each of the content files in the // ACR patch override directory matches the database's expected // checksum. If not (or if the file in question didn't exist), // then copy the new version over from the central vault. // using (MD5CryptoServiceProvider MD5Csp = new MD5CryptoServiceProvider()) { foreach (ContentPatchFile PatchFile in PatchFiles) { bool Matched = false; string LocalPath = PatchFile.GetLocalPath(LocalContentPatchPath, LocalContentPatchHakPath); string RemotePath = String.Format("{0}\\{1}", RemoteContentPatchPath, PatchFile.FileName); string LocalHashString = "<no such file>"; if (File.Exists(LocalPath) && File.Exists(RemotePath)) { LocalHashString = GetFileChecksum(LocalPath, MD5Csp); Matched = (LocalHashString.ToString() == PatchFile.Checksum.ToLower()); } if (Matched) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} is up to date (checksum {1}).", PatchFile.FileName, LocalHashString)); } else { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} needs to be updated (local checksum {1}, remote checksum {2}). Copying file...", PatchFile.FileName, LocalHashString, PatchFile.Checksum)); // // The file needs to be updated. Copy it over and // re-validate the checksum. If the checksum did not // match, log an error and delete the file. // try { // // If we are patching a hak, rename it away so that // the file can be written to. // if (PatchFile.Location == "hak" && File.Exists(LocalPath)) { string OldFileName = LocalPath + ".old"; if (File.Exists(OldFileName)) File.Delete(OldFileName); File.Move(LocalPath, OldFileName); Database.ACR_IncrementStatistic("CONTENT_PATCH_HAK"); } else if (PatchFile.Location == "override") { Database.ACR_IncrementStatistic("CONTENT_PATCH_OVERRIDE"); } try { File.Copy(RemotePath, LocalPath, true); } catch { if (PatchFile.Location == "hak" && File.Exists(LocalPath)) { string OldFileName = LocalPath + ".old"; File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } throw; } if (GetFileChecksum(LocalPath, MD5Csp) != PatchFile.Checksum.ToLower()) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} was copied, but checksum did not match {1}! This may indicate a configuration error in the database, or file corruption in transit.", PatchFile.FileName, PatchFile.Checksum)); if (PatchFile.Location == "hak") { string OldFileName = LocalPath + ".old"; File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } Database.ACR_IncrementStatistic("CONTENT_PATCH_INCORRECT_CHECKSUM"); } else { ContentChanged = true; Database.ACR_IncrementStatistic("CONTENT_PATCH_UPDATED_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Successfully updated content patch file {0}.", PatchFile.FileName)); } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Exception {0} updating content patch file {1}.", e, PatchFile.FileName)); } } } } if (ContentChanged) Database.ACR_IncrementStatistic("CONTENT_PATCH_REBOOT"); return ContentChanged; }
/// <summary> /// Recompile all scripts in the module. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <param name="Database">Supplies the database connection.</param> private static void CompileModuleScripts(ACR_ServerCommunicator Script, ALFA.Database Database) { ALFA.ScriptCompiler.CompilerResult Result; string CompilerOptions = Script.GetLocalString(Script.GetModule(), "ACR_MOD_COMPILER_OPTIONS"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Compiling module scripts with compiler options '{0}'...", CompilerOptions)); Result = ALFA.ScriptCompiler.CompileScript("*.nss", CompilerOptions, delegate(string Line) { Script.WriteTimestampedLogEntry(Line); return false; }); if (Result.Compiled) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.CompileModuleScripts: Module successfully recompiled."); Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE"); } else { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: {0} error(s) compiling module!", Result.Errors.Count)); foreach (string Message in Result.Errors) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Error '{0}'.", Message)); } Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE_FAILED"); } }