//============================================================================== // Web Application to handle remote administration of server // // Revised by Micheal Comeau // Revised by Ron Prestenback // © 1998-2004, Epic Games, Inc. All Rights Reserved //============================================================================== class UTServerAdmin extends WebApplication config; // TODO: // Do something with colspan.inc var config string ActiveSkin; // Each query handler represents a section in webadmin var() config array QueryHandlerClasses; var array QueryHandlers; // Global Objects var() array > WebSkins; // array of webadmin skins var() class SpectatorType; var() class DefaultWebSkinClass; var WebSkin CurrentSkin;// Currently loaded webadmin skin var UTServerAdminSpectator Spectator; // Used to get console messages var xAdminUser CurAdmin; // Currently logged admin (not thread safe) var PlayInfo GamePI; // Contains all confiurable variables var WebResponse Resp; // Non-thread safe reference to a WebResponse object // Lists var array AllGames; var array AllMaps; var array AllMutators; // All mutators as determined by .int entries var StringArray AGameType; // All available Game Types var StringArray AExcMutators; // All available Mutators (Excluded) var StringArray AIncMutators; // All Mutators currently in play var StringArray Skins; // All Webadmin custom skins // Characters which aren't rendered correctly in HTML struct HtmlChar { var string Plain; var string Coded; }; var array SpecialChars; var config string DefaultBG; // Non-highlighted items var config string HighlightedBG; // Active links // Pages var config string RootFrame; // This is the master frame divided in 2: Top = Header, bottom = frame page var config string HeaderPage; // This is the header menu var config string MessagePage; // Name of the file containing the message template var config string FramedMessagePage; // Name of file containing message template for sub-frames var config string RestartPage; // This is the page that users will be transferred to when restarting the server var string htm; var config string AdminRealm; // Used by browsers to cache login information // HTML variables used for skins var string SkinPath; // Path to use for .htm and .inc content var string SiteCSSFile; // CSS file to use var string SiteBG; // Background color for this skin var string StatusOKColor; // Color of status ok messages var string StatusErrorColor; // Color of status error messages // Table cells var config string CellLeft; // Table cell, left justified var config string CellCenter; // Table cell, center justified var config string CellRight; // Table cell, right justified var config string CellColSpan; // Spanned table cell var config string NowrapLeft; // Nowrap table cell, left justified var config string NowrapCenter; // Nowrap table cell, center justified var config string NowrapRight; // Nowrap table cell, right justified var config string RowLeft; // Table row, left justified var config string RowCenter; // Table row, center justified // Form objects var config string CheckboxInclude; var config string TextboxInclude; var config string SubmitButtonInclude; var config string RadioButtonInclude; var config string SelectInclude; var config string ResetButtonInclude; var config string HiddenInclude; var config string SkinSelectInclude; // Global localization var localized string Accept; var localized string Deny; var localized string Update; var localized string Custom; var localized string Error; var localized string NoneText; var localized string SwitchText; var localized string DeleteText; var localized string NewText; var localized string Edit; var localized string WaitTitle; var localized string MapChanging; var localized string MapChangingTo; var localized string AccessDeniedText; var localized string ErrorAuthenticating; var localized string NoPrivs; var localized string LoadingGames, LoadingMaps, Initialized; var bool bDebug; // ===================================================================================================================== // ===================================================================================================================== // Initialization // ===================================================================================================================== // ===================================================================================================================== event Init() { Super.Init(); if (SpectatorType != None) Spectator = Level.Spawn(SpectatorType); else Spectator = Level.Spawn(class'UTServerAdminSpectator'); if (Spectator != None) Spectator.Server = self; // won't change as long as the server is up and the map hasnt changed LoadMaps(); LoadGameTypes(); LoadMutators(); LoadQueryHandlers(); ReplaceText( Initialized, "%class%", string(Class) ); ReplaceText( Initialized, "%port%", string(WebServer.ListenPort) ); Log(Initialized,'WebAdmin'); } function LoadMaps() { log(LoadingMaps,'WebAdmin'); class'CacheManager'.static.GetMaplist(AllMaps); } function LoadGameTypes() { local int i; Log(LoadingGames,'WebAdmin'); // reinitialize list if needed AGameType = New(None) class'SortedStringArray'; class'CacheManager'.static.GetGameTypeList(AllGames); for (i = 0; i < AllGames.Length; i++) AGameType.Add( AllGames[i].ClassName, AllGames[i].GameName ); } function LoadMutators() { local Mutator M; local int i, id; AExcMutators = New(None) class'StringArray'; AIncMutators = New(None) class'SortedStringArray'; if ( Level.IsDemoBuild() ) return; // Load All mutators class'CacheManager'.static.GetMutatorList(AllMutators); for (i = 0; i= 0) AIncMutators.Add(AExcMutators.GetItem(id), AllMutators[id].FriendlyName); else log("Unknown Mutator in use: "@M.Class,'WebAdmin'); } } } function LoadQueryHandlers() { local int i, j; local xWebQueryHandler QH; local class QHC; LoadSkins(); for (i=0; i(DynamicLoadObject(QueryHandlerClasses[i],class'Class')); // skip invalid classes if (QHC != None) { // Make sure we dont have duplicate instance of the same class for (j=0;j TmpClass; local array > InfoClasses; local int i; if (GameType == "") { InfoClasses[0] = Level.Game.Class; GameType = string(Level.Game.Class); } else { TmpClass = class(DynamicLoadObject(GameType, class'Class')); if (TmpClass == None) { Log("Error loading gametype"@GameType,'WebAdmin'); return ""; } InfoClasses[0] = TmpClass; } if (GamePI == None) GamePI = new(None) class'PlayInfo'; if (Level.Game.AccessControl != None) InfoClasses[1] = Level.Game.AccessControl.Class; while (i < AIncMutators.Count()) { TmpClass = class(DynamicLoadObject(AllMutators[int(AIncMutators.GetItem(i++))].ClassName, class'Class')); if (TmpClass != None) InfoClasses[InfoClasses.Length] = TmpClass; } GamePI.Init( InfoClasses ); return GameType; } // ===================================================================================================================== // ===================================================================================================================== // List Initialization & Generation // ===================================================================================================================== // ===================================================================================================================== // Returns a list of all maps for this gametype function StringArray ReloadExcludeMaps(String GameType) { local int i, GameIndex; local string MapPrefix; local StringArray AMaps; local array MapRecords; if ( GameType == "" ) GameType = string(Level.Game.Class); GameIndex = GetGameCacheIndex(GameType); if ( GameIndex == -1 ) { Warn("Could not load gametype"@GameType@"for maplist."); return AMaps; } AMaps = New(None) class'SortedStringArray'; MapPrefix = AllGames[GameIndex].MapPrefix; class'CacheManager'.static.GetMapList(MapRecords, MapPrefix); for ( i = 0; i < MapRecords.Length; i++ ) if ( ValidMap(MapRecords[i].MapName) ) { if ( bDebug ) log("Adding entry to exclude maplist Item '"$i$"' Tag '"$MapRecords[i].MapName$"'"); AMaps.Add(MapRecords[i].MapName, MapRecords[i].MapName, True); } return AMaps; } // Returns a list of active maps for this gametype function StringArray ReloadIncludeMaps(StringArray ExMaps, int GameIndex, int MapListIndex) { local int i, id; local string MapName; local array Maps; local StringArray Arr; Arr = new(None) class'StringArray'; Maps = Level.Game.MaplistHandler.GetMapList(GameIndex, MapListIndex); for ( i = 0; i < Maps.Length; i++ ) { MapName = class'MaplistRecord'.static.GetBaseMapName(Maps[i]); Arr.Add( MapName $ Maps[i], MapName, True ); if ( bDebug ) log("Adding map entry to include list Item '"$MapName $ Maps[i]$"' Tag '"$MapName$"'"); // Always add the maps that are in the existing maplist, even if they aren't valid maps id = ExMaps.FindTagId(MapName); if ( id != -1 ) ExMaps.Remove(id); } return Arr; } // Only 'Mutators' has value when function is called function CreateFullMutatorList(out StringArray Mutators, out StringArray GroupsOnly) { local StringArray Grouped; local int i,j,z; local string GrpName; local string thisgroup, nextgroup; // This class provides a specialized sorting function // since mutators may be grouped - mutators in the same groups // are not allowed to be selected together Grouped = new(None) class'SortedStringArray'; // Create array sorted on GroupName...this allows to flag // the mutator for a different .inc file (radio instead of checkbox) for (i = 0; i < Mutators.Count(); i++) { j = int(Mutators.GetItem(i)); // If the mutator author forgot to configure a group name for the mutator, // generate a groupname "Z" + number, so that every mutator without group name // has its own group, and isn't grouped together if (AllMutators[j].GroupName == "") GrpName = "Z" $ string (z++); else GrpName = AllMutators[j].GroupName; Grouped.Add(string(j),GrpName $ "." $ AllMutators[j].FriendlyName $ AllMutators[j].ClassName); } // Move all grouped mutators to GroupsOnly StringArray for sorting by friendly name for (i = 0; i < Grouped.Count(); i++) { thisgroup = AllMutators[int(Grouped.GetItem(i))].GroupName; nextgroup = ""; if (thisgroup == "") continue; if (i+1 < Grouped.Count()) nextgroup = AllMutators[int(Grouped.GetItem(i+1))].GroupName; if (thisgroup ~= nextgroup) { j=i; while(nextgroup ~= thisgroup && j < Grouped.Count()) { GroupsOnly.MoveFromId(Grouped, Grouped.FindItemId(Grouped.GetItem(j))); thisgroup = nextgroup; if (j+1 == Grouped.Count()) nextgroup=""; else nextgroup = AllMutators[int(Grouped.GetItem(j+1))].GroupName; } if (j < Grouped.Count()) GroupsOnly.MoveFromId(Grouped,Grouped.FindItemId(Grouped.GetItem(j))); i=-1; } } // Move all non-grouped mutators back to Mutators StringArray // for re-sorting by Friendly Name Mutators.Reset(); for (i=0;i SkinClass; Skins = new(None) class'StringArray'; S = Level.GetNextInt("XWebAdmin.WebSkin", i++); while (S != "") { SkinClass = class(DynamicLoadObject(S, class'Class')); if (SkinClass != None) { Skins.Add(Level.GetItemName(string(SkinClass)), SkinClass.default.DisplayName); WebSkins[WebSkins.Length] = SkinClass; } S = Level.GetNextInt("XWebAdmin.WebSkin", i++); } ApplySkinSettings(); } function ApplySkinSettings() { local int i; if (Skins == None) return; if (CurrentSkin != None && Level.GetItemName(string(CurrentSkin.Class)) ~= ActiveSkin) return; i = Skins.FindItemId(ActiveSkin); if (i < 0) CurrentSkin = new(None) class'XWebAdmin.ROOstSkin'; else CurrentSkin = new(None) WebSkins[i]; if (CurrentSkin != None) CurrentSkin.Init(Self); } function ServerChangeMap(WebRequest Request, WebResponse Response, string MapName, string GameType) { local int i; local bool bConflict; local string Conflicts, Str, ShortName, Muts; if (Level.NextURL != "") { ShowMessage(Response, WaitTitle, MapChanging); } if (Request.GetVariable("Save", "") != "") { // All we need to do is override settings as required for (i = 0; i 0) { for (i = 0; i < CurrentSkin.SpecialQuery.Length; i++) { if (CurrentSkin.SpecialQuery[i] ~= Mid(Request.URI,1)) { if (CurrentSkin.HandleSpecialQuery(Request, Response)) return; break; } } } // Check whether we're supposed to handle this query switch (Mid(Request.URI, 1)) { case "": case RootFrame: QueryRootFrame(Request, Response); return; case HeaderPage: QueryHeaderPage(Request, Response); return; case RestartPage: if (!MapIsChanging()) QuerySubmitRestartPage(Request, Response); return; case SiteCSSFile: Response.SendCachedFile( Path $ SkinPath $ "/" $ Mid(Request.URI, 1), "text/css"); return; } // If not, allow each query handler a chance to process this query. Show error message if no query handlers // were able to handle the query for (i=0; i 0) GroupPage = QueryHandlers[0].DefaultPage; if (Request.GetVariable("ChangeSkin") != "") { ActiveSkin = Request.GetVariable("WebSkin", ActiveSkin); ApplySkinSettings(); SaveConfig(); } GroupPage = Request.GetVariable("Group", GroupPage); Response.Subst("HeaderURI", HeaderPage$"?Group="$GroupPage); Response.Subst("BottomURI", GroupPage); Response.Subst("ServerName", class'GameReplicationInfo'.default.ServerName); ShowFrame(Response, RootFrame); } // Generates the HTML for the top frame, which displays the available areas of webadmin and webadmin skin selector function QueryHeaderPage(WebRequest Request, WebResponse Response) { local int i; local string menu, GroupPage, Dis, CurPageTitle; Response.Subst("AdminName", CurAdmin.UserName); Response.Subst("HeaderColSpan", "2"); if (QueryHandlers.Length > 0) { GroupPage = Request.GetVariable("Group", QueryHandlers[0].DefaultPage); // We build a multi-column table for each QueryHandler menu = ""; CurPageTitle = ""; for (i=0; i"$AGameType.GetTag(i)$""; } return OptionStr; } // HTML for active mutator selection list function string GenerateMutatorOptions(String SelectedMutators) { local int i, j; local string SelectedStr, OptionStr; local array MutatorList; local StringArray SortedMutatorList; local bool bNoMutators; Split( SelectedMutators, ",", MutatorList); SortedMutatorList = new(None) class'SortedStringArray'; for(i=0; i < AllMutators.Length; i++) SortedMutatorList.Add(AllMutators[i].ClassName,AllMutators[i].FriendlyName); // Search mutator list for NONE, if found then no other mutators can be selected. SelectedStr = ""; for( j=0; j < MutatorList.Length; j++) { if( MutatorList[j] ~= "NONE" ) { SelectedStr = " selected"; bNoMutators = true; break; } } // add None options - for unloading all mutators OptionStr = ""; for(i=0; i < SortedMutatorList.Count(); i++) { SelectedStr = ""; if( !bNoMutators ) // dont select other mutators if none selected { for( j=0; j < MutatorList.Length; j++) { if( MutatorList[j] ~= SortedMutatorList.GetItem(i) ) { SelectedStr = " selected"; break; } } } OptionStr $= ""; } SortedMutatorList.Reset(); return OptionStr; } // HTML for maplist selection options function string GenerateMapListOptions(string GameType, int Active) { local int i, idx; local array Ar; local string Result, selected; idx = Level.Game.MaplistHandler.GetGameIndex(GameType); Ar = Level.Game.MaplistHandler.GetMapListNames(idx); for (i = 0; i < Ar.Length; i++) { selected =Eval(i == Active, " selected", ""); Result $= ""; } return Result; } // HTML for the dual map selection lists function string GenerateMapListSelect(StringArray MapList, StringArray MovedMaps) { local int i; local String ResponseStr, SelectedStr; if (MapList.Count() == 0) return ""; for (i = 0; i= 0) SelectedStr = " selected"; ResponseStr = ResponseStr$""; } } return ResponseStr; } // HTML for webadmin skin options function string GenerateSkinSelect() { local string S, selectedstring; local int i; if (Skins.Count() == 0) return ""; for (i = 0; i < Skins.Count(); i++) { SelectedString = Eval(CurrentSkin != None && Level.GetItemName(string(CurrentSkin.Class)) ~= Skins.GetItem(i), " selected", ""); S = S $ ""; } return S; } // ===================================================================================================================== // ===================================================================================================================== // HTML helpers // ===================================================================================================================== // ===================================================================================================================== // Replaces special characters in a regular string with HTML static function string HtmlEncode(string src) { local int i; for (i = 0; i < default.SpecialChars.Length; i++) src = Repl(src, default.SpecialChars[i].Plain, default.SpecialChars[i].Coded); return src; } // Replaces HTML special characters with regular text static function string HtmlDecode(string src) { local int i; for (i = 0; i < default.SpecialChars.Length; i++) src = Repl(src, default.SpecialChars[i].Coded, default.SpecialChars[i].Plain); return src; } // Make the specified string's length equal to MaxWidth by adding PadString to the beginning of the string function string PadLeft( string Text, int MaxWidth, optional string PadString) { local String OutStr; if (PadString == "") PadString = " "; for (OutStr = Text; Len(OutStr) < MaxWidth; OutStr = PadString $ OutStr); return Right(OutStr, MaxWidth); // in case PadStr is more than one character } // Make the specified string's length equal to MaxWidth by adding PadString to the end of the string function string PadRight(string Text, int MaxWidth, optional string PadString) { local string OutStr; if (PadString == "") PadString = " "; for (OutStr = Text; Len(OutStr) < MaxWidth; OutStr $= PadString); return Left(OutStr, MaxWidth); } // Adds the HTML necessary for showing the current map's name function MapTitle(WebResponse Response) { local string str, smap; str = Level.Game.GameReplicationInfo.GameName$" in "; if (Level.Title ~= "untitled") { smap = Level.GetURLMap(); if (Right(smap, 4) ~= ".ut2") str $= Left(smap, Len(smap) - 4); else str $= smap; } else str $= Level.Title; Response.Subst("SubTitle", str); } function bool MapIsChanging() { if (Level.NextURL != "") { ShowMessage(Resp, WaitTitle, MapChanging); return true; } return false; } // Converts the specified text into a hyperlink function string HyperLink(string url, string text, bool bEnabled, optional string target) { local string hlink; if (bEnabled) { hlink = ""$text$""; return hlink; } return text; } // Loads an .inc file into the current WebResponse object function string WebInclude(string file) { local string S; if (CurrentSkin != None) { S = CurrentSkin.HandleWebInclude(Resp, file); if (S != "") return S; } return Resp.LoadParsedUHTM(Path $ SkinPath $ "/" $ file $ ".inc"); } function bool ShowFrame(WebResponse Response, string Page) { if (CurrentSkin != None && CurrentSkin.HandleHTM(Response, Page)) return true; Response.IncludeUHTM( Path $ SkinPath $ "/" $ Page $ htm); return true; } function bool ShowPage(WebResponse Response, string Page) { if (CurrentSkin != None && CurrentSkin.HandleHTM(Response, Page)) { Response.ClearSubst(); return true; } Response.IncludeUHTM( Path $ SkinPath $ "/" $ Page $ htm); Response.ClearSubst(); return true; } function StatusError(WebResponse Response, string Message) { if (Left(Message,1) == "@") Message = Mid(Message,1); Response.Subst("Status", ""$Message$"
"); } function StatusOk(WebResponse Response, string Message) { Response.Subst("Status", ""$Message$""); } function bool StatusReport(WebResponse Response, string ErrorMessage, string SuccessMessage) { if (ErrorMessage == "") StatusOk(Response, SuccessMessage); else StatusError(Response, ErrorMessage); return ErrorMessage==""; } function ShowMessage(WebResponse Response, string Title, string Message) { if (CurrentSkin != None && CurrentSkin.HandleMessagePage(Response, Title, Message)) return; Response.Subst("Section", Title); Response.Subst("Message", Message); Response.IncludeUHTM(Path $ SkinPath $ "/" $ MessagePage $ htm); } // Framed message page (eliminates double headers) function ShowFramedMessage(WebResponse Response, string Message, bool bIsErrorMsg) { if (CurrentSkin != None && CurrentSkin.HandleFrameMessage(Response, Message, bIsErrorMsg)) return; if (bIsErrorMsg) StatusError(Response,Message); else Response.Subst("Message", Message); Response.IncludeUHTM(Path $ SkinPath $ "/" $ FramedMessagePage $ htm); } // ===================================================================================================================== // ===================================================================================================================== // Form control HTML helpers // ===================================================================================================================== // ===================================================================================================================== // Creates a checkbox, optionally greying it out (disabled) function string Checkbox(string tag, bool bChecked, optional bool bDisabled) { Resp.Subst("CheckName", tag); Resp.Subst("Checked", Eval(bChecked, " checked", "")); Resp.Subst("Disabled", Eval(bDisabled, " disabled", "")); return WebInclude(CheckboxInclude); } function string Hidden(string Tag, string Value) { Resp.Subst("HiddenName", Tag); Resp.Subst("HiddenValue", Value); return WebInclude(HiddenInclude); } // Creates a submit button function string SubmitButton(string SubmitButtonName, string SubmitButtonValue) { Resp.Subst("SubmitName", SubmitButtonName); Resp.Subst("SubmitValue", SubmitButtonValue); return WebInclude(SubmitButtonInclude); } function string ResetButton(string ResetButtonName, string ResetButtonValue) { Resp.Subst("ResetName", ResetButtonName); Resp.Subst("ResetValue", ResetButtonValue); return WebInclude(ResetButtonInclude); } // Creates a textbox, optionally providing a default value function string TextBox(string TextName, coerce string Size, coerce string MaxLength, optional string DefaultValue) { Resp.Subst("TextName", TextName); if (int(Size) > 0) Resp.Subst("TextSize", "size=\""$Size$"\""); else Resp.Subst("TextSize", ""); if (int(MaxLength) > 0) Resp.Subst("TextLength", "maxlength=\""$MaxLength$"\""); else Resp.Subst("TextLength", ""); Resp.Subst("TextValue", DefaultValue); return WebInclude(TextboxInclude); } function string RadioButton(string Group, string Value, bool bSelected) { Resp.Subst("RadioGroup", Group); Resp.Subst("RadioValue", Value); Resp.Subst("Selected", Eval(bSelected, " checked", "")); return WebInclude(RadioButtonInclude); } function string Select(string SelectName, string SelectOptions) { Resp.Subst("SelectName", SelectName); Resp.Subst("ListOptions", SelectOptions); return WebInclude(SelectInclude); } // ===================================================================================================================== // ===================================================================================================================== // Accessor / Utility Functions // ===================================================================================================================== // ===================================================================================================================== function int GetGameCacheIndex( string GameClass ) { local int i; for ( i = 0; i < AllGames.Length; i++ ) if ( AllGames[i].ClassName ~= GameClass ) return i; return -1; } // Returns whether this map is a valid map function bool ValidMap(string MapURL) { local int i; local string MapName; if ( Left(Caps(MapName),4) ~= "TUT-" ) return false; MapName = class'MaplistRecord'.static.GetBaseMapName(MapURL); for ( i = 0; i < AllMaps.Length; i++ ) { if ( AllMaps[i].MapName ~= MapName ) return true; } return false; } function FormatMapName(out string FullName, out string ShortName) { local string ext; ext = ".ut2"; if (FullName == "" && ShortName == "") return; if (FullName != "" && ShortName == "") { if (Right(FullName, 4) ~= ext) ShortName = Left(FullName, Len(FullName) - 4); else { ShortName = FullName; FullName = FullName $ ext; } } else if (FullName == "" && ShortName != "") { if (Right(ShortName,4) ~= ext) { FullName = ShortName; ShortName = Left(ShortName, Len(ShortName) - 4); } else FullName = ShortName $ ext; } else { if (!(Right(FullName,4) ~= ext)) FullName = FullName $ ext; ShortName = Left(FullName, Len(FullName) - 4); } } function String UsedMutators() { local int i; local String OutStr; while (i < AIncMutators.Count()) { if (OutStr != "") OutStr $= ","; OutStr $= AllMutators[int(AIncMutators.GetItem(i++))].ClassName; } return OutStr; } function bool HasURLOption(string ParamName, out string Value) { local string Param; local int i; Param = ParamName; while (true) { i = Instr(Param, "."); if (i < 0) break; Param = Mid(Param, i+1); } Value = Level.GetUrlOption(Param); return Value != ""; } function string NextPriv(out string PrivString) { local int pos; local string Priv; pos = Instr(PrivString, "|"); if (pos < 0) pos = Len(PrivString); EatStr(Priv, PrivString, Pos); if (PrivString != "") // Remove leading pipe char PrivString = Mid(PrivString, 1); return Priv; } function bool CanPerform(string privs) { local string priv; priv = NextPriv(privs); while (priv != "") { if (Level.Game.AccessControl.AllowPriv(priv) && CurAdmin.HasPrivilege(priv)) return true; priv = NextPriv(privs); } return false; } defaultproperties { SpectatorType=class'XWebAdmin.UTServerAdminSpectator' DefaultWebSkinClass=class'XWebAdmin.ROOstSkin' QueryHandlerClasses(0)="XWebAdmin.xWebQueryCurrent" QueryHandlerClasses(1)="XWebAdmin.xWebQueryDefaults" QueryHandlerClasses(2)="XWebAdmin.xWebQueryAdmins" RootFrame="rootframe" HeaderPage="mainmenu" MessagePage="message" RestartPage="server_restart" DefaultBG="#aaaaaa" HighlightedBG="#3a7c8c" SiteBG="#411b17"//"#243954" AdminRealm="KF Remote Admin Server" htm=".htm" SpecialChars(0)=(Plain="&",Coded="&") SpecialChars(1)=(Plain="\"",Coded=""") SpecialChars(2)=(Plain=" ",Coded=" ") SpecialChars(3)=(Plain="<",Coded="<") SpecialChars(4)=(Plain=">",Coded=">") SpecialChars(5)=(Plain="©",Coded="©") SpecialChars(6)=(Plain="™",Coded="™") SpecialChars(7)=(Plain="®",Coded="®") StatusOKColor="#33cc66" StatusErrorColor="Yellow" Accept="Accept" Deny="Deny" Update="Update" Custom="Custom" Error="Error" NoneText="None" SwitchText="Switch" DeleteText="Delete" NewText="New" Edit="Edit" AccessDeniedText="Access Denied" ErrorAuthenticating="Exception Occurred During Authentication!" NoPrivs="Your privileges are not sufficient to view this page." WaitTitle="Please Wait" MapChanging="The server is now switching maps. Please allow 10 - 15 seconds while the server changes maps." MapChangingTo="The server is now switching to map '%MapName%'. Please allow 10-15 seconds while the server changes maps." LoadingGames="Loading Game Types" LoadingMaps="Loading Available Maps" Initialized="%class% Initialized on port %port%" FramedMessagePage="frame_message" SiteCSSFile="ROOst.css" CheckboxInclude="checkbox" TextboxInclude="textbox" RadioButtonInclude="radio_button" SelectInclude="select" SubmitButtonInclude="submit_button" ResetButtonInclude="reset_button" HiddenInclude="hidden" SkinSelectInclude="mainmenu_items" CellLeft="cell_left" CellCenter="cell_center" CellRight="cell_right" CellColSpan="cell_colspan" NowrapLeft="cell_left_nowrap" NowrapCenter="cell_center_nowrap" NowrapRight="cell_right_nowrap" RowLeft="row_left" RowCenter="row_center" bDebug=False }