From 8c6d31f3942e5837fcc7b8111cda8ab1cbc7a5c3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Wed, 31 May 2017 21:06:42 -0400 Subject: [PATCH] TGS3 (#3) Let's just get this merged instead of having a massive PR Original PR: tgstation/tgstation#26534 --- .gitignore | 13 + README.md | 147 +- TGCommandLine/App.config | 6 + TGCommandLine/BYONDCommands.cs | 138 ++ TGCommandLine/ChatCommands.cs | 509 ++++ TGCommandLine/ConfigCommands.cs | 140 ++ TGCommandLine/DDCommands.cs | 287 +++ TGCommandLine/DMCommands.cs | 218 ++ TGCommandLine/Program.cs | 130 + TGCommandLine/Properties/AssemblyInfo.cs | 26 + TGCommandLine/RepoCommands.cs | 345 +++ TGCommandLine/RootCommands.cs | 146 ++ TGCommandLine/TGCommandLine.csproj | 65 + TGCommandLine/tgs.ico | Bin 0 -> 123198 bytes TGControlPanel/App.config | 56 + TGControlPanel/ByondPage.cs | 123 + TGControlPanel/Chat.cs | 165 ++ TGControlPanel/ConfigHelperControls.cs | 232 ++ TGControlPanel/ConfigPage.cs | 654 +++++ TGControlPanel/Main.Designer.cs | 2008 +++++++++++++++ TGControlPanel/Main.cs | 33 + TGControlPanel/Main.resx | 2197 +++++++++++++++++ TGControlPanel/Program.cs | 63 + TGControlPanel/Properties/AssemblyInfo.cs | 29 + .../Properties/Settings.Designer.cs | 50 + TGControlPanel/Properties/Settings.settings | 12 + TGControlPanel/RepoPage.cs | 342 +++ TGControlPanel/ServerPage.cs | 411 +++ TGControlPanel/TGControlPanel.csproj | 100 + TGControlPanel/tgs.ico | Bin 0 -> 123198 bytes TGS3Release.ps1 | 12 + TGServerService/App.config | 126 + TGServerService/Byond.cs | 296 +++ TGServerService/Chat.cs | 361 +++ TGServerService/Compiler.cs | 539 ++++ TGServerService/Config.cs | 1019 ++++++++ TGServerService/Discord.cs | 220 ++ TGServerService/DreamDaemon.cs | 437 ++++ TGServerService/IRC.cs | 270 ++ TGServerService/InterfaceBase.cs | 124 + TGServerService/Interop.cs | 238 ++ TGServerService/LockDependancies.txt | 12 + TGServerService/Program.cs | 76 + TGServerService/ProjectInstaller.Designer.cs | 59 + TGServerService/ProjectInstaller.cs | 28 + TGServerService/ProjectInstaller.resx | 129 + TGServerService/Properties/AssemblyInfo.cs | 29 + .../Properties/Settings.Designer.cs | 234 ++ TGServerService/Properties/Settings.settings | 63 + TGServerService/Repository.cs | 855 +++++++ TGServerService/ServerService.Designer.cs | 40 + TGServerService/ServerService.cs | 82 + TGServerService/ServerService.resx | 123 + TGServerService/TGServerService.csproj | 153 ++ TGServerService/packages.config | 12 + TGServerService/tgs.ico | Bin 0 -> 123198 bytes TGServiceInstaller/Product.wxs | 150 ++ TGServiceInstaller/TGServiceInstaller.wixproj | 72 + TGServiceInterface/Byond.cs | 96 + TGServiceInterface/Chat.cs | 262 ++ TGServiceInterface/Compiler.cs | 92 + TGServiceInterface/Config.cs | 335 +++ TGServiceInterface/DreamDaemon.cs | 188 ++ TGServiceInterface/Properties/AssemblyInfo.cs | 29 + TGServiceInterface/Repository.cs | 213 ++ TGServiceInterface/Server.cs | 106 + TGServiceInterface/TGServiceInterface.csproj | 54 + TGServiceInterface/tgs.ico | Bin 0 -> 123198 bytes TGStationServer3.sln | 77 + View TGS3 Logs.xml | 1 + appveyor.yml | 34 + tgs.ico | Bin 0 -> 123198 bytes 72 files changed, 15890 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 TGCommandLine/App.config create mode 100644 TGCommandLine/BYONDCommands.cs create mode 100644 TGCommandLine/ChatCommands.cs create mode 100644 TGCommandLine/ConfigCommands.cs create mode 100644 TGCommandLine/DDCommands.cs create mode 100644 TGCommandLine/DMCommands.cs create mode 100644 TGCommandLine/Program.cs create mode 100644 TGCommandLine/Properties/AssemblyInfo.cs create mode 100644 TGCommandLine/RepoCommands.cs create mode 100644 TGCommandLine/RootCommands.cs create mode 100644 TGCommandLine/TGCommandLine.csproj create mode 100644 TGCommandLine/tgs.ico create mode 100644 TGControlPanel/App.config create mode 100644 TGControlPanel/ByondPage.cs create mode 100644 TGControlPanel/Chat.cs create mode 100644 TGControlPanel/ConfigHelperControls.cs create mode 100644 TGControlPanel/ConfigPage.cs create mode 100644 TGControlPanel/Main.Designer.cs create mode 100644 TGControlPanel/Main.cs create mode 100644 TGControlPanel/Main.resx create mode 100644 TGControlPanel/Program.cs create mode 100644 TGControlPanel/Properties/AssemblyInfo.cs create mode 100644 TGControlPanel/Properties/Settings.Designer.cs create mode 100644 TGControlPanel/Properties/Settings.settings create mode 100644 TGControlPanel/RepoPage.cs create mode 100644 TGControlPanel/ServerPage.cs create mode 100644 TGControlPanel/TGControlPanel.csproj create mode 100644 TGControlPanel/tgs.ico create mode 100644 TGS3Release.ps1 create mode 100644 TGServerService/App.config create mode 100644 TGServerService/Byond.cs create mode 100644 TGServerService/Chat.cs create mode 100644 TGServerService/Compiler.cs create mode 100644 TGServerService/Config.cs create mode 100644 TGServerService/Discord.cs create mode 100644 TGServerService/DreamDaemon.cs create mode 100644 TGServerService/IRC.cs create mode 100644 TGServerService/InterfaceBase.cs create mode 100644 TGServerService/Interop.cs create mode 100644 TGServerService/LockDependancies.txt create mode 100644 TGServerService/Program.cs create mode 100644 TGServerService/ProjectInstaller.Designer.cs create mode 100644 TGServerService/ProjectInstaller.cs create mode 100644 TGServerService/ProjectInstaller.resx create mode 100644 TGServerService/Properties/AssemblyInfo.cs create mode 100644 TGServerService/Properties/Settings.Designer.cs create mode 100644 TGServerService/Properties/Settings.settings create mode 100644 TGServerService/Repository.cs create mode 100644 TGServerService/ServerService.Designer.cs create mode 100644 TGServerService/ServerService.cs create mode 100644 TGServerService/ServerService.resx create mode 100644 TGServerService/TGServerService.csproj create mode 100644 TGServerService/packages.config create mode 100644 TGServerService/tgs.ico create mode 100644 TGServiceInstaller/Product.wxs create mode 100644 TGServiceInstaller/TGServiceInstaller.wixproj create mode 100644 TGServiceInterface/Byond.cs create mode 100644 TGServiceInterface/Chat.cs create mode 100644 TGServiceInterface/Compiler.cs create mode 100644 TGServiceInterface/Config.cs create mode 100644 TGServiceInterface/DreamDaemon.cs create mode 100644 TGServiceInterface/Properties/AssemblyInfo.cs create mode 100644 TGServiceInterface/Repository.cs create mode 100644 TGServiceInterface/Server.cs create mode 100644 TGServiceInterface/TGServiceInterface.csproj create mode 100644 TGServiceInterface/tgs.ico create mode 100644 TGStationServer3.sln create mode 100644 View TGS3 Logs.xml create mode 100644 appveyor.yml create mode 100644 tgs.ico diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..2daa302ddd --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +#Visual studio stuff +.vscode/* +.vs/* + +#VS stuff + +*/bin/* +*/obj/* +*/Debug/* +*/Release/* + +packages/* +*.user diff --git a/README.md b/README.md index 9d035fd04d..62576f99d4 100644 --- a/README.md +++ b/README.md @@ -1 +1,146 @@ -# tgstation-server +# Tgstation Toolkit: +This is a toolset to manage a production server of /tg/Station13 (and its forks). It includes the ability to update the server without having to stop or shutdown the server (the update will take effect next round) the ability start the server and restart it if it crashes, as well as systems for fixing errors and merging GitHub Pull Requests locally. + +Generally, updates force a live tracking of the configured git repo, resetting local modifications. If you plan to make modifications, set up a new git repo to store your version of the code in, and point this script to that in the config (explained below). This can be on github or a local repo using file:/// urls. + +Requires python 2.7/3.6 to be installed for changelog generation + +### Legacy Server +* The old cmd script server can be found in the legacy tree + + +## Install +1. Either compile from source (requires .NET Framework 4.5.2, Nuget, and WiX toolset 4.0) or download the latest release from github +1. Run the installer + +## Install (GUI): +1. Launch TGControlPanel.exe. A shortcut can be found on your desktop +1. Optionally switch to the `Server` tab and change the Server Path location from C:\tgstation-server-3 to wherever you wish +1. Go to the `Repository` Tab and set the remote address and branch of the git you with to track +1. Hit the clone button +1. While waiting go to the BYOND tab and install the BYOND version you wish +1. You may also configure an IRC or discord bot for the server on the chat tab +1. Once the clone is complete you may set up a committer identity, user name, and password on the `Repository` tab for pushing changelog updates +1. Go to the `Server` tab and click the `Initialize Game Folders` button +1. Optionally change the `Project Path` Setting from tgstation to wherever the dme/dmb pair are in your repository +1. Optionally tick the Autostart box if you wish to have your server start with Windows +1. When game folder initialization is complete, click the `Copy from Repo and Compile` option + +## Install (CL example): +This process is identical to the above steps in command line mode. You can always learn more about a command using `?` i.e. `repo ?` +1. Launch TGCommandLine.exe (running with no parameters puts you in interactive mode) +1. `config move-server D:\tgstation` +1. `repo setup https://github.com/tgstation/tgstation master` +1. `byond update 511.1382` +1. `chat set-provider irc` +1. `irc nick TGS3Test` +1. `irc setup-auth NickServ "id hunter2"` Yes this is the real password, please use it only for testing +1. `chat enable` +1. `repo set-name tgstation-server` These two lines specify the changelog's committer identity. They are not mirrored in the GUI +1. `repo set-email tgstation-server@tgstation13.org` +1. `repo set-credentials` And follow the prompts +1. `dm project-name tgstation` +1. `repo python-path C:\Python27` +1. `dd autostart on` +1. Use `repo status` To check the clone job status +1. `dm initialize --wait` +1. `dm compile --wait` +1. `dd start` + +### Folders and Files (None of these should be touched): +* `Game//` + * This will house two copies of the game code, one for updating and one for live. When updating, it will automatically swap them. + +* `Static/` + * This contains the `data/` and `config/` folders from the code. They are stored here and a symbolic link is created in the `gamecode/` folders pointing to here. + * This also makes backing them up easy. (you may copy and paste your existing `data/` and `config/` folders here after the install script has ran.) + * Resetting the repository will create a backup of this and reinitalize it + +* `Game/Live/` + * This is a symbolic link pointing to current "live" folder. + * When the server is updated, we just point this to the updating folder so that the update takes place next round. + +* `Repository/` + * This contains the actual git repository, all changes in here will be overwritten during update operations, the configured branch will always be forced to track from live. + * On the first update of the day, the current code state is saved to a branch called `backup-YYYY-MM-DD` before updating, to make local reverts easy. + * Following update operations on the same day do not make branches because I was too lazy to detect such edge cases. + +* `RepoKey/` + * This contains ssh key information for automatic changelog pushing. + +* `BYOND/` + * This contains the actual BYOND installation the server uses + +* `BYOND_staging/` + * This appears when a BYOND update is queued but can't currently be applied due to usage of the current BYOND version. It will be applied at the first possible moment. Restarting the service deletes this folder + +* `BYOND_revision.zip` + * This is a queued update downloaded from BYOND, it will be unzipped into BYOND_staging and deleted. Restarting the service deletes this file + +* `prtestjob.json` + * This contains information about current test merged pull requests in the Repository folder + +### Starting the game server: +To run the game server, open the `Server` tab of the control panel and click either `Start` + +It will restart the game server if it shutdowns for any reason, giving up after 5 tries in under a minute + + +### Updating the server: +To update the server, open the `Server` tab of the control panel. Optionally tick `Commit and push Changelogs`. Click `Update (Reset Testmerge)`. (it will git pull, compile, all that jazz) + +(Note: Updating automatically does a code reset, clearing ALL changes to the local git repo, including test merges (explained below) and manual changes (This will not change any configs/settings or clear any data in the `gamedata/` folder)) + +Updates do not require the server to be shutdown, changes will apply next round if the server is currently running. + +All DM compilation will log to the server what commit they happened at + +There is also a `Update (Keep Testmerge)` option that does the same without resetting the code, used to update without clearing test merges or local changes. Prone to merge conflicts. + + +### Locally merge GitHub Pull Requests (PR test merge): +This feature currently only works if github is the remote(git server), it could be adapted for gitlab as well. + +Running these will merge the pull request then recompile the server, changes take effect the next round if the server is currently running. + +There are multiple flavors in the server tab: +* `Update and Testmerge` + * Updates the server, resetting state, and merges a PR(Pull Request) by number. +* `Testmerge` + * Merges a PR without updating the server before hand or resetting the state (can be used to test merge multiple PRs). + +You can clear all active test merges using `Reset to Origin Branch` in the `Repository` tab and the using `Copy from Repo and Compile` in the server tab (explained below) + +### Resetting, Recompiling, and troubleshooting. +* `Server` -> `Copy from Repo and Compile` + * Just recompiles the game code and stages it to apply next round +* `Server` -> `Reset and Recompile` + * Like the above but resets the git repo to the state of the last update operation (clearing any changes or test merges) (Does not reset `gamedata/` data/settings) + * Generally used to clear out test merges +* `Server` -> `Initialize Game Folders` + * Requires the server not be running, rebuilds the staging A/B folders and then does `Copy from Repo and Compile`. + +### Starting everything when the computer/server boots +* Just tick the `Autostart` option in the `Server` tab or run `dd autostart on` on the command line. As it's a windows service, it will automatically run without having to log ing + +### Configuring +* The `Config` panel of the control panel lets you modify nearly all the game configuration settings +* Not all settings can be modified from here. Although, you should never (have to) write to the service's folders directly. Instead you may use the `Download` button to download a copy of the config file locally, modify it there, then use the `Upload` button to apply it to the `Static/config` folder. You may also use the `DL Repo` button to download from the `Repository` config folder. +* The above functions are the only ones available in command line mode using `config download `, `config upload `, and `config download --repo` respectively + +### Viewing Server Logs +* Logs are stored in the Windows event viewer under `Windows Logs` -> `Application`. You'll need to filter this list for `TG Station Server` +* You can also import the custom view `View TGS3 Logs.xml` in this folder to have them automatically filtered + +### Enabling upstream changelog generation +* The repository will automatically create an ssh version of the initial origin remote and can optionally push generated changelogs to your git through it +* The repository can only authenticate using ssh public key authentication +* To enable this feature, simply create `public_key.txt` and `private_key.txt` in a folder called RepoKey in the server directory +* The private key must be in `-----BEGIN RSA PRIVATE KEY-----` format and the public key must be in `ssh-rsa` format. See github guidelines for setting this up here: https://help.github.com/articles/connecting-to-github-with-ssh/ +* The service will be able to read these files regardless of their permissions, so the responsibility is on you to set their ACL's so they can't be read by those that shouldn't + +## CONTRIBUTING + +* IF YOU MAKE ANY CHANGES TO THE CODE, BE SURE TO BUMP THE VERSION NUMBERS IN ALL 4 "AssemblyInfo.cs"s AND ON LINE 3 OF TGServiceInstaller/Product.wx +* My recommendation is bump the revision number per test release, the minor version per PR, and the major version if you rewrite the whole thing. +* To have auto deployment to github releases work, put `[TGSDeploy]` somewhere in your pull request title \ No newline at end of file diff --git a/TGCommandLine/App.config b/TGCommandLine/App.config new file mode 100644 index 0000000000..88fa4027bd --- /dev/null +++ b/TGCommandLine/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TGCommandLine/BYONDCommands.cs b/TGCommandLine/BYONDCommands.cs new file mode 100644 index 0000000000..d99b8e55e9 --- /dev/null +++ b/TGCommandLine/BYONDCommands.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using TGServiceInterface; + +namespace TGCommandLine +{ + class BYONDCommand : RootCommand + { + public BYONDCommand() + { + Keyword = "byond"; + Children = new Command[] { new BYONDUpdateCommand(), new BYONDVersionCommand(), new BYONDStatusCommand() }; + } + protected override string GetHelpText() + { + return "Manage BYOND installation"; + } + } + + class BYONDVersionCommand : Command + { + public BYONDVersionCommand() + { + Keyword = "version"; + } + public override ExitCode Run(IList parameters) + { + var type = TGByondVersion.Installed; + if (parameters.Count > 0) + if (parameters[0].ToLower() == "--staged") + type = TGByondVersion.Staged; + else if (parameters[0].ToLower() == "--latest") + type = TGByondVersion.Latest; + Console.WriteLine(Server.GetComponent().GetVersion(type) ?? "Unistalled"); + return ExitCode.Normal; + } + protected override string GetArgumentString() + { + return "[--staged|--latest]"; + } + + protected override string GetHelpText() + { + return "Print the currently installed BYOND version"; + } + } + + + class BYONDStatusCommand : Command + { + public BYONDStatusCommand() + { + Keyword = "status"; + } + public override ExitCode Run(IList parameters) + { + switch (Server.GetComponent().CurrentStatus()) + { + case TGByondStatus.Downloading: + Console.WriteLine("Downloading update..."); + break; + case TGByondStatus.Idle: + Console.WriteLine("Updater Idle"); + break; + case TGByondStatus.Staged: + Console.WriteLine("Update staged and awaiting server restart"); + break; + case TGByondStatus.Staging: + Console.WriteLine("Staging update..."); + break; + case TGByondStatus.Starting: + Console.WriteLine("Starting update..."); + break; + case TGByondStatus.Updating: + Console.WriteLine("Applying update..."); + break; + default: + Console.WriteLine("Limmexing (This is an error)."); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + protected override string GetHelpText() + { + return "Print the current status of the BYOND updater"; + } + } + + class BYONDUpdateCommand : Command + { + public BYONDUpdateCommand() + { + Keyword = "update"; + RequiredParameters = 2; + } + public override ExitCode Run(IList parameters) + { + int Major = 0, Minor = 0; + try + { + Major = Convert.ToInt32(parameters[0]); + Minor = Convert.ToInt32(parameters[1]); + } + catch + { + Console.WriteLine("Please enter version as ."); + return ExitCode.BadCommand; + } + + var BYOND = Server.GetComponent(); + if (!BYOND.UpdateToVersion(Major, Minor)) + + { + Console.WriteLine("Failed to begin update!"); + return ExitCode.ServerError; + } + + var stat = BYOND.CurrentStatus(); + while (stat != TGByondStatus.Idle && stat != TGByondStatus.Staged) + { + Thread.Sleep(100); + stat = BYOND.CurrentStatus(); + } + var res = BYOND.GetError(); + Console.WriteLine(res ?? (stat == TGByondStatus.Staged ? "Update staged and will apply next DD reboot" : "Update finished")); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + protected override string GetArgumentString() + { + return " "; + } + protected override string GetHelpText() + { + return "Updates the BYOND installation to the specified version"; + } + } +} diff --git a/TGCommandLine/ChatCommands.cs b/TGCommandLine/ChatCommands.cs new file mode 100644 index 0000000000..689a76c004 --- /dev/null +++ b/TGCommandLine/ChatCommands.cs @@ -0,0 +1,509 @@ +using System; +using System.Collections.Generic; +using TGServiceInterface; + +namespace TGCommandLine +{ + class ChatCommand : RootCommand + { + public ChatCommand() + { + Keyword = "chat"; + Children = new Command[] { new ChatAnnounceCommand(), new ChatStatusCommand(), new ChatSetAdminChannelCommand(), new ChatEnableCommand(), new ChatDisableCommand(), new ChatReconnectCommand(), new ChatListAdminsCommand(), new ChatAddminCommand(), new ChatDeadminCommand(), new ChatSetProviderCommand(), new ChatJoinCommand(), new ChatPartCommand() }; + } + protected override string GetHelpText() + { + return "Manages general chat settings"; + } + } + class ChatSetProviderCommand : Command { + public ChatSetProviderCommand() + { + Keyword = "set-provider"; + RequiredParameters = 1; + } + protected override string GetHelpText() + { + return "Set the chat provider"; + } + protected override string GetArgumentString() + { + return " [if discord, add the bot token]"; + } + public override ExitCode Run(IList parameters) + { + var Chat = Server.GetComponent(); + string res; + switch (parameters[0].ToLower()) + { + case "irc": + res = Chat.SetProviderInfo(new TGIRCSetupInfo()); + break; + case "discord": + if(parameters.Count < 2) + { + Console.WriteLine("Missing discord bot token!"); + return ExitCode.BadCommand; + } + res = Chat.SetProviderInfo(new TGDiscordSetupInfo() { BotToken = parameters[1] }); + break; + default: + Console.WriteLine("Invalid provider!"); + return ExitCode.BadCommand; + } + Console.WriteLine(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + } + class IRCCommand : RootCommand + { + public IRCCommand() + { + Keyword = "irc"; + Children = new Command[] { new IRCNickCommand(), new IRCAuthCommand(), new IRCDisableAuthCommand(), new IRCServerCommand() }; + } + public override ExitCode Run(IList parameters) + { + if (Server.GetComponent().ProviderInfo().Provider != TGChatProvider.IRC) + { + Console.WriteLine("The current provider is not IRC. Please switch providers first!"); + return ExitCode.ServerError; + } + return base.Run(parameters); + } + protected override string GetHelpText() + { + return "Manages the IRC bot"; + } + } + class IRCNickCommand : Command + { + public IRCNickCommand() + { + Keyword = "nick"; + RequiredParameters = 1; + } + + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Sets the IRC nickname"; + } + + public override ExitCode Run(IList parameters) + { + var Chat = Server.GetComponent(); + Chat.SetProviderInfo(new TGIRCSetupInfo(Chat.ProviderInfo()) + { + Nickname = parameters[0], + }); + return ExitCode.Normal; + } + } + + class ChatJoinCommand : Command + { + public ChatJoinCommand() + { + Keyword = "join"; + RequiredParameters = 1; + } + + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Joins a channel for listening and broadcasting"; + } + + public override ExitCode Run(IList parameters) + { + var IRC = Server.GetComponent(); + var channels = IRC.Channels(); + var lowerParam = parameters[0].ToLower(); + foreach (var I in channels) + { + if (I.ToLower() == lowerParam) + { + Console.WriteLine("Already in this channel!"); + return ExitCode.BadCommand; + } + } + Array.Resize(ref channels, channels.Length + 1); + channels[channels.Length - 1] = parameters[0]; + IRC.SetChannels(channels, null); + return ExitCode.Normal; + } + } + + class ChatPartCommand : Command + { + public ChatPartCommand() + { + Keyword = "part"; + RequiredParameters = 1; + } + + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Stops listening and broadcasting on a channel"; + } + public override ExitCode Run(IList parameters) + { + var IRC = Server.GetComponent(); + var channels = IRC.Channels(); + var lowerParam = parameters[0].ToLower(); + if (lowerParam[0] != '#') + lowerParam = "#" + lowerParam; + var new_channels = new List(); + foreach (var I in channels) + { + if (I.ToLower() == lowerParam) + continue; + new_channels.Add(I); + } + if (new_channels.Count == 0) + { + Console.WriteLine("Error: Cannot part from the last channel!"); + return ExitCode.BadCommand; + } + IRC.SetChannels(new_channels.ToArray(), null); + return ExitCode.Normal; + } + } + class ChatAnnounceCommand : Command + { + public ChatAnnounceCommand() + { + Keyword = "announce"; + RequiredParameters = 1; + } + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Sends a message to all connected channels"; + } + + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().SendMessage("SCP: " + parameters[0]); + if (res != null) + { + Console.WriteLine("Error: " + res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + class ChatListAdminsCommand : Command + { + public ChatListAdminsCommand() + { + Keyword = "list-admins"; + } + + protected override string GetHelpText() + { + return "List users which can use restricted commands in the admin channel"; + } + + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().ListAdmins(); + foreach (var I in res) + Console.WriteLine(I); + return ExitCode.Normal; + } + } + class ChatReconnectCommand : Command + { + public ChatReconnectCommand() + { + Keyword = "reconnect"; + } + + protected override string GetHelpText() + { + return "Restablish the chat connection"; + } + + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().Reconnect(); + if (res != null) + { + Console.WriteLine("Error: " + res); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + class ChatAddminCommand : Command + { + public ChatAddminCommand() + { + Keyword = "addmin"; + RequiredParameters = 1; + } + + protected override string GetArgumentString() + { + return "[nick]"; + } + protected override string GetHelpText() + { + return "Add a user which can use restricted commands in the admin channel"; + } + public override ExitCode Run(IList parameters) + { + var IRC = Server.GetComponent(); + var mins = new List(IRC.ListAdmins()); + var newmin = parameters[0]; + + foreach (var I in mins) + if (I.ToLower() == newmin.ToLower()) + { + Console.WriteLine(newmin + " is already an admin!"); + return ExitCode.Normal; + } + + mins.Add(newmin); + IRC.SetAdmins(mins.ToArray()); + return ExitCode.Normal; + } + } + class ChatDeadminCommand : Command + { + public ChatDeadminCommand() + { + Keyword = "deadmin"; + RequiredParameters = 1; + } + protected override string GetArgumentString() + { + return "[nick]"; + } + protected override string GetHelpText() + { + return "Remove a user which can use restricted commands in the admin channel"; + } + public override ExitCode Run(IList parameters) + { + var IRC = Server.GetComponent(); + var mins = new List(IRC.ListAdmins()); + var deadmin = parameters[0]; + + foreach (var I in mins) + if (I.ToLower() == deadmin.ToLower()) + { + mins.Remove(I); + IRC.SetAdmins(mins.ToArray()); + return ExitCode.Normal; + } + + Console.WriteLine(deadmin + " is not an admin!"); + return ExitCode.Normal; + } + } + + class IRCAuthCommand : Command + { + public IRCAuthCommand() + { + Keyword = "setup-auth"; + RequiredParameters = 2; + } + + protected override string GetArgumentString() + { + return " "; + } + protected override string GetHelpText() + { + return "Set the authentication message to send to target for identification. e.g. NickServ \"identify hunter2\""; + } + public override ExitCode Run(IList parameters) + { + var IRC = Server.GetComponent(); + IRC.SetProviderInfo(new TGIRCSetupInfo(IRC.ProviderInfo()) + { + AuthTarget = parameters[0], + AuthMessage = parameters[1] + }); + return ExitCode.Normal; + } + } + + class IRCDisableAuthCommand : Command + { + public IRCDisableAuthCommand() + { + Keyword = "disable-auth"; + } + protected override string GetHelpText() + { + return "Turns off IRC authentication"; + } + public override ExitCode Run(IList parameters) + { + var IRC = Server.GetComponent(); + IRC.SetProviderInfo(new TGIRCSetupInfo(IRC.ProviderInfo()) + { + AuthTarget = null, + AuthMessage = null, + }); + return ExitCode.Normal; + } + } + + class ChatStatusCommand : Command + { + public ChatStatusCommand() + { + Keyword = "status"; + } + protected override string GetHelpText() + { + return "Lists channels and connections status"; + } + public override ExitCode Run(IList parameters) + { + var IRC = Server.GetComponent(); + Console.WriteLine("Currently configured broadcast channels:"); + Console.WriteLine("\tAdmin Channel: " + IRC.AdminChannel()); + foreach (var I in IRC.Channels()) + Console.WriteLine("\t" + I); + string provider; + switch (IRC.ProviderInfo().Provider) + { + case TGChatProvider.Discord: + provider = "Discord"; + break; + case TGChatProvider.IRC: + provider = "IRC"; + break; + default: + provider = "Unknown"; + break; + } + Console.WriteLine("Provider: " + provider); + Console.WriteLine("Chat bot is: " + (!IRC.Enabled() ? "Disabled" : IRC.Connected() ? "Connected" : "Disconnected")); + return ExitCode.Normal; + } + } + class ChatSetAdminChannelCommand : Command + { + public ChatSetAdminChannelCommand() + { + Keyword = "set-admin-channel"; + RequiredParameters = 1; + } + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Sets the admin chat channel"; + } + public override ExitCode Run(IList parameters) + { + Server.GetComponent().SetChannels(null, parameters[0]); + return ExitCode.Normal; + } + } + class ChatEnableCommand : Command + { + public ChatEnableCommand() + { + Keyword = "enable"; + } + + protected override string GetHelpText() + { + return "Enables the chat bot"; + } + + public override ExitCode Run(IList parameters) + { + Server.GetComponent().SetEnabled(true); + return ExitCode.Normal; + } + } + class ChatDisableCommand : Command + { + public ChatDisableCommand() + { + Keyword = "disable"; + } + + protected override string GetHelpText() + { + return "Disables the chat bot"; + } + + public override ExitCode Run(IList parameters) + { + Server.GetComponent().SetEnabled(true); + return ExitCode.Normal; + } + } + + class IRCServerCommand : Command + { + public IRCServerCommand() + { + Keyword = "set-server"; + RequiredParameters = 1; + } + protected override string GetArgumentString() + { + return ":"; + } + protected override string GetHelpText() + { + return "Sets the IRC server"; + } + + public override ExitCode Run(IList parameters) + { + var splits = parameters[0].Split(':'); + if(splits.Length < 2) + { + Console.WriteLine("Invalid parameter!"); + return ExitCode.BadCommand; + } + var Chat = Server.GetComponent(); + var PI = new TGIRCSetupInfo(Chat.ProviderInfo()) + { + URL = splits[0] + }; + try + { + PI.Port = Convert.ToUInt16(splits[1]); + } + catch + { + Console.WriteLine("Invalid port number!"); + return ExitCode.BadCommand; + } + + var res = Chat.SetProviderInfo(PI); + Console.WriteLine(res ?? "Success"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + + } +} \ No newline at end of file diff --git a/TGCommandLine/ConfigCommands.cs b/TGCommandLine/ConfigCommands.cs new file mode 100644 index 0000000000..796afdd8b0 --- /dev/null +++ b/TGCommandLine/ConfigCommands.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.IO; +using TGServiceInterface; + +namespace TGCommandLine +{ + class ConfigCommand : RootCommand + { + public ConfigCommand() + { + Keyword = "config"; + Children = new Command[] { new ConfigMoveServerCommand(), new ConfigServerDirectoryCommand(), new ConfigDownloadCommand(), new ConfigUploadCommand() }; + } + protected override string GetHelpText() + { + return "Manage settings"; + } + } + + class ConfigMoveServerCommand : Command + { + public ConfigMoveServerCommand() + { + Keyword = "move-server"; + RequiredParameters = 1; + } + + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().MoveServer(parameters[0]); + Console.WriteLine(res ?? "Success"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + + protected override string GetArgumentString() + { + return ""; + } + + protected override string GetHelpText() + { + return "Move the server installation (BYOND, Repo, Game) to a new location. Nothing else may be running for this task to complete"; + } + } + + class ConfigServerDirectoryCommand : Command + { + public ConfigServerDirectoryCommand() + { + Keyword = "server-dir"; + } + + public override ExitCode Run(IList parameters) + { + Console.WriteLine(Server.GetComponent().ServerDirectory()); + return ExitCode.Normal; + } + + protected override string GetHelpText() + { + return "Print the directory the server is installed in"; + } + } + + class ConfigDownloadCommand : Command + { + public ConfigDownloadCommand() + { + Keyword = "download"; + RequiredParameters = 2; + } + + public override ExitCode Run(IList parameters) + { + var bytes = Server.GetComponent().ReadRaw(parameters[0], parameters.Count > 2 && parameters[2].ToLower() == "--repo", out string error); + if(bytes == null) + { + Console.WriteLine("Error: " + error); + return ExitCode.ServerError; + } + + try + { + File.WriteAllText(parameters[1], bytes); + } + catch (Exception e) + { + Console.WriteLine("Error: " + e.ToString()); + } + + return ExitCode.Normal; + } + protected override string GetArgumentString() + { + return " [--repo]"; + } + protected override string GetHelpText() + { + return "Downloads the specified file from the config tree and writes it to out file. --repo will fetch it from the repository instead of the game config"; + } + } + + class ConfigUploadCommand : Command + { + public ConfigUploadCommand() + { + Keyword = "upload"; + RequiredParameters = 2; + } + + public override ExitCode Run(IList parameters) + { + try + { + var res = Server.GetComponent().WriteRaw(parameters[0], File.ReadAllText(parameters[1])); + if (res != null) + { + Console.WriteLine("Error: " + res); + return ExitCode.ServerError; + } + } + catch (Exception e) + { + Console.WriteLine("Error: " + e.ToString()); + } + return ExitCode.Normal; + } + + protected override string GetArgumentString() + { + return " [--repo]"; + } + + protected override string GetHelpText() + { + return "Uploads the specified file to the config tree from source file"; + } + } +} diff --git a/TGCommandLine/DDCommands.cs b/TGCommandLine/DDCommands.cs new file mode 100644 index 0000000000..debe42b4d9 --- /dev/null +++ b/TGCommandLine/DDCommands.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using TGServiceInterface; + +namespace TGCommandLine +{ + class DDCommand : RootCommand + { + public DDCommand() + { + Keyword = "dd"; + Children = new Command[] { new DDStartCommand(), new DDStopCommand(), new DDRestartCommand(), new DDStatusCommand(), new DDAutostartCommand(), new DDPortCommand(), new DDVisibilityCommand(), new DDSecurityCommand() }; + } + protected override string GetHelpText() + { + return "Manage DreamDaemon"; + } + } + + class DDStartCommand : Command + { + public DDStartCommand() + { + Keyword = "start"; + } + + protected override string GetHelpText() + { + return "Starts the server and watchdog"; + } + + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().Start(); + Console.WriteLine(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + } + + class DDStopCommand : Command + { + public DDStopCommand() + { + Keyword = "stop"; + } + protected override string GetArgumentString() + { + return "[--graceful]"; + } + + protected override string GetHelpText() + { + return "Stops the server and watchdog optionally waiting for the current round to end"; + } + + public override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") + { + if (DD.DaemonStatus() != TGDreamDaemonStatus.Online) + { + Console.WriteLine("Error: The game is not currently running!"); + return ExitCode.ServerError; + } + DD.RequestStop(); + return ExitCode.Normal; + } + var res = DD.Stop(); + Console.WriteLine(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + } + class DDRestartCommand : Command + { + public DDRestartCommand() + { + Keyword = "restart"; + } + + protected override string GetArgumentString() + { + return "[--graceful]"; + } + public override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + if (parameters.Count > 0 && parameters[0].ToLower() == "--graceful") + { + if (DD.DaemonStatus() != TGDreamDaemonStatus.Online) + { + Console.WriteLine("Error: The game is not currently running!"); + return ExitCode.ServerError; + } + DD.RequestRestart(); + return ExitCode.Normal; + } + var res = DD.Restart(); + Console.WriteLine(res ?? "Success!"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + + protected override string GetHelpText() + { + return "Restarts the server and watchdog optionally waiting for the current round to end"; + } + } + class DDStatusCommand : Command + { + public DDStatusCommand() + { + Keyword = "status"; + } + + protected override string GetHelpText() + { + return "Gets the current status of the watchdog and server"; + } + + public override ExitCode Run(IList parameters) + { + Console.WriteLine(Server.GetComponent().StatusString(true)); + return ExitCode.Normal; + } + } + + class DDAutostartCommand : Command + { + public DDAutostartCommand() + { + Keyword = "autostart"; + RequiredParameters = 1; + } + + public override ExitCode Run(IList parameters) + { + var DD = Server.GetComponent(); + switch (parameters[0].ToLower()) + { + case "on": + DD.SetAutostart(true); + break; + case "off": + DD.SetAutostart(false); + break; + case "check": + Console.WriteLine("Autostart is: " + (DD.Autostart() ? "On" : "Off")); + break; + default: + Console.WriteLine("Invalid parameter: " + parameters[0]); + return ExitCode.BadCommand; + } + return ExitCode.Normal; + } + + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Change or check autostarting of the game server with the service"; + } + } + class DDPortCommand : Command + { + public DDPortCommand() + { + Keyword = "set-port"; + RequiredParameters = 1; + } + + public override ExitCode Run(IList parameters) + { + ushort port; + try + { + port = Convert.ToUInt16(parameters[0]); + } + catch + { + Console.WriteLine("Invalid port number!"); + return ExitCode.BadCommand; + } + + Server.GetComponent().SetPort(port); + return ExitCode.Normal; + } + + protected override string GetArgumentString() + { + return ""; + } + + protected override string GetHelpText() + { + return "Sets the port DreamDaemon will open the server on. Requires a server restart to apply and queues a graceful one up"; + } + } + + class DDVisibilityCommand : Command + { + public DDVisibilityCommand() + { + Keyword = "set-visibility"; + RequiredParameters = 1; + } + + public override ExitCode Run(IList parameters) + { + TGDreamDaemonVisibility vis; + switch (parameters[0].ToLower()) + { + case "invisible": + case "invis": + vis = TGDreamDaemonVisibility.Invisible; + break; + case "private": + case "priv": + vis = TGDreamDaemonVisibility.Private; + break; + case "public": + case "pub": + vis = TGDreamDaemonVisibility.Public; + break; + default: + Console.WriteLine("Invalid visiblity word!"); + return ExitCode.BadCommand; + } + Server.GetComponent().SetVisibility(vis); + return ExitCode.Normal; + } + + protected override string GetHelpText() + { + return "Sets the visibility option for the DreamDaemon world. Requires a server restart to apply and queues a graceful one up"; + } + + protected override string GetArgumentString() + { + return ""; + } + } + + class DDSecurityCommand : Command + { + public DDSecurityCommand() + { + Keyword = "set-security"; + RequiredParameters = 1; + } + + public override ExitCode Run(IList parameters) + { + TGDreamDaemonSecurity sec; + switch (parameters[0].ToLower()) + { + case "safe": + sec = TGDreamDaemonSecurity.Safe; + break; + case "ultra": + case "ultrasafe": + sec = TGDreamDaemonSecurity.Ultrasafe; + break; + case "trust": + case "trusted": + sec = TGDreamDaemonSecurity.Trusted; + break; + default: + Console.WriteLine("Invalid security word!"); + return ExitCode.BadCommand; + } + Server.GetComponent().SetSecurityLevel(sec); + return ExitCode.Normal; + } + + protected override string GetArgumentString() + { + return ""; + } + + protected override string GetHelpText() + { + return "Sets the visibility option for the DreamDaemon world"; + } + } +} diff --git a/TGCommandLine/DMCommands.cs b/TGCommandLine/DMCommands.cs new file mode 100644 index 0000000000..56a0e39152 --- /dev/null +++ b/TGCommandLine/DMCommands.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using TGServiceInterface; + +namespace TGCommandLine +{ + class DMCommand : RootCommand + { + public DMCommand() + { + Keyword = "dm"; + Children = new Command[] { new DMCompileCommand(), new DMInitializeCommand(), new DMStatusCommand(), new DMSetProjectNameCommand(), new DMCancelCommand() }; + } + protected override string GetHelpText() + { + return "Manage compiling the server"; + } + } + + class DMCompileCommand : Command + { + public DMCompileCommand() + { + Keyword = "compile"; + } + + public override ExitCode Run(IList parameters) + { + var DM = Server.GetComponent(); + var stat = DM.GetStatus(); + if (stat != TGCompilerStatus.Initialized) + { + Console.WriteLine("Error: Compiler is " + ((stat == TGCompilerStatus.Uninitialized) ? "unintialized!" : "busy with another task!")); + return ExitCode.ServerError; + } + + if (Server.GetComponent().GetVersion(TGByondVersion.Installed) == null) + { + Console.Write("Error: BYOND is not installed!"); + return ExitCode.ServerError; + } + + if (!DM.Compile()) + { + Console.WriteLine("Error: Unable to start compilation!"); + var err = DM.CompileError(); + if (err != null) + Console.WriteLine(err); + return ExitCode.ServerError; + } + Console.WriteLine("Compile job started"); + if (parameters.Count > 0 && parameters[0] == "--wait") + { + do + { + Thread.Sleep(1000); + } while (DM.GetStatus() == TGCompilerStatus.Compiling); + var res = DM.CompileError(); + Console.WriteLine(res ?? "Compilation successful"); + if (res != null) + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + + protected override string GetArgumentString() + { + return "[--wait]"; + } + + protected override string GetHelpText() + { + return "Starts a compile/update job optionally waiting for completion"; + } + } + class DMStatusCommand : Command + { + public DMStatusCommand() + { + Keyword = "status"; + } + + void ShowError() + { + var error = Server.GetComponent().CompileError(); + if (error != null) + Console.WriteLine("Last error: " + error); + } + + public override ExitCode Run(IList parameters) + { + var DM = Server.GetComponent(); + Console.WriteLine(String.Format("Target Project: /{0}.dme", DM.ProjectName())); + Console.Write("Compilier is currently: "); + switch (DM.GetStatus()) + { + case TGCompilerStatus.Compiling: + Console.WriteLine("Compiling..."); + break; + case TGCompilerStatus.Initialized: + Console.WriteLine("Idle"); + ShowError(); + break; + case TGCompilerStatus.Initializing: + Console.WriteLine("Setting up..."); + break; + case TGCompilerStatus.Uninitialized: + Console.WriteLine("Uninitialized"); + ShowError(); + break; + default: + Console.WriteLine("Seizing the means of production (This is an error)."); + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + + protected override string GetHelpText() + { + return "Get the current status of the compiler"; + } + } + + class DMSetProjectNameCommand : Command + { + public DMSetProjectNameCommand() + { + Keyword = "project-name"; + RequiredParameters = 1; + } + protected override string GetArgumentString() + { + return ""; + } + + protected override string GetHelpText() + { + return "Set the relative path of the .dme/.dmb to compile/run"; + } + + public override ExitCode Run(IList parameters) + { + Server.GetComponent().SetProjectName(parameters[0]); + return ExitCode.Normal; + } + } + + class DMInitializeCommand : Command + { + public DMInitializeCommand() + { + Keyword = "initialize"; + } + + protected override string GetArgumentString() + { + return "[--wait]"; + } + + protected override string GetHelpText() + { + return "Starts an initialization job optionally waiting for completion"; + } + + public override ExitCode Run(IList parameters) + { + var DM = Server.GetComponent(); + var stat = DM.GetStatus(); + if (stat == TGCompilerStatus.Compiling || stat == TGCompilerStatus.Initializing) + { + Console.WriteLine("Error: Compiler is " + ((stat == TGCompilerStatus.Initializing) ? "already initialized!" : " already running!")); + return ExitCode.ServerError; + } + if (!DM.Initialize()) + { + Console.WriteLine("Error: Unable to start initialization!"); + var err = DM.CompileError(); + if (err != null) + Console.WriteLine(err); + return ExitCode.ServerError; + } + Console.WriteLine("Initialize job started"); + if (parameters.Count > 0 && parameters[0] == "--wait") + { + do + { + Thread.Sleep(1000); + } while (DM.GetStatus() == TGCompilerStatus.Initializing); + var res = DM.CompileError(); + Console.WriteLine(res ?? "Initialization successful"); + if (res != null) + return ExitCode.ServerError; + } + return ExitCode.Normal; + } + } + + class DMCancelCommand : Command + { + public DMCancelCommand() + { + Keyword = "cancel"; + } + + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().Cancel(); + Console.WriteLine(res ?? "Success!"); + return ExitCode.Normal; //because failing cancellation implys it's already cancelled + } + + protected override string GetHelpText() + { + return "Cancels the current compilation job"; + } + } +} diff --git a/TGCommandLine/Program.cs b/TGCommandLine/Program.cs new file mode 100644 index 0000000000..9a752ebb5a --- /dev/null +++ b/TGCommandLine/Program.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TGServiceInterface; + +namespace TGCommandLine +{ + enum ExitCode + { + Normal = 0, + ConnectionError = 1, + BadCommand = 2, + ServerError = 3, + } + + abstract class Command + { + public string Keyword { get; protected set; } + public Command[] Children { get; protected set; } = { }; + public int RequiredParameters { get; protected set; } + public abstract ExitCode Run(IList parameters); + public virtual void PrintHelp() + { + var Prefixes = new List(); + var Postfixes = new List(); + int MaxPrefixLen = 0; + foreach (var c in Children) + { + var ns = c.Keyword + " " + c.GetArgumentString(); + MaxPrefixLen = Math.Max(MaxPrefixLen, ns.Length); + Prefixes.Add(ns); + Postfixes.Add(c.GetHelpText()); + } + + var Final = new List(); + for(var I = 0; I < Prefixes.Count; ++I) + { + var lp = Prefixes[I]; + for (; lp.Length < MaxPrefixLen + 1; lp += " ") ; + Final.Add(lp + "- " + Postfixes[I]); + } + Final.Sort(); + Final.ForEach(Console.WriteLine); + } + protected virtual string GetArgumentString() + { + return ""; + } + protected abstract string GetHelpText(); + } + + class Program + { + static ExitCode RunCommandLine(IList argsAsList) + { + var res = Server.VerifyConnection(); + if (res != null) + { + Console.WriteLine("Unable to connect to service!"); + return ExitCode.ConnectionError; + } + try + { + return new RootCommand().Run(argsAsList); + } + catch + { + Console.WriteLine("Service connection interrupted!"); + return ExitCode.ConnectionError; + }; + } + public static string ReadLineSecure() + { + string result = ""; + while (true) + { + ConsoleKeyInfo i = Console.ReadKey(true); + if (i.Key == ConsoleKey.Enter) + { + break; + } + else if (i.Key == ConsoleKey.Backspace) + { + if (result.Length > 0) + { + result = result.Substring(0, result.Length - 1); + Console.Write("\b \b"); + } + } + else + { + result += i.KeyChar; + Console.Write("*"); + } + } + return result; + } + + static int Main(string[] args) + { + if (args.Length != 0) + return (int)RunCommandLine(new List(args)); + + //interactive mode + while (true) + { + Console.Write("Enter command: "); + var NextCommand = Console.ReadLine(); + switch (NextCommand.ToLower()) + { + case "quit": + case "exit": + return (int)ExitCode.Normal; + default: + //linq voodoo to get quoted strings + var formattedCommand = NextCommand.Split('"') + .Select((element, index) => index % 2 == 0 // If even index + ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) // Split the item + : new string[] { element }) // Keep the entire item + .SelectMany(element => element).ToList(); + + formattedCommand = formattedCommand.Select(x => x.Trim()).ToList(); + formattedCommand.Remove(""); + RunCommandLine(formattedCommand); + break; + } + } + } + } +} \ No newline at end of file diff --git a/TGCommandLine/Properties/AssemblyInfo.cs b/TGCommandLine/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..04dee87eae --- /dev/null +++ b/TGCommandLine/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TGStation Server Commandline")] +[assembly: AssemblyDescription("CLI for the TG Station Server Service")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9ad1f086-a83e-4d14-a844-58a9471106b6")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("3.0.78.0")] +[assembly: AssemblyFileVersion("3.0.78.0")] diff --git a/TGCommandLine/RepoCommands.cs b/TGCommandLine/RepoCommands.cs new file mode 100644 index 0000000000..5184126fbb --- /dev/null +++ b/TGCommandLine/RepoCommands.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using TGServiceInterface; + +namespace TGCommandLine +{ + class RepoCommand : RootCommand + { + public RepoCommand() + { + Keyword = "repo"; + Children = new Command[] { new RepoSetupCommand(), new RepoUpdateCommand(), new RepoChangelogCommand(), new RepoPythonPathCommand(), new RepoSetEmailCommand(), new RepoSetNameCommand(), new RepoMergePRCommand(), new RepoListPRsCommand(), new RepoStatusCommand(), new RepoListBackupsCommand(), new RepoCheckoutCommand(), new RepoResetCommand() }; + } + protected override string GetHelpText() + { + return "Manage the git repository"; + } + } + + class RepoSetupCommand : Command + { + public RepoSetupCommand() + { + Keyword = "setup"; + RequiredParameters = 1; + } + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().Setup(parameters[0], parameters.Count > 1 ? parameters[1] : "master"); + if (res != null) + { + Console.WriteLine("Error: " + res); + return ExitCode.ServerError; + } + Console.WriteLine("Setting up repo. This will take a while..."); + return ExitCode.Normal; + } + protected override string GetArgumentString() + { + return " [branchname]"; + } + protected override string GetHelpText() + { + return "Clean up everything and clones the repo at git-url with optional branch name"; + } + } + + class RepoStatusCommand : Command + { + public RepoStatusCommand() + { + Keyword = "status"; + } + public override ExitCode Run(IList parameters) + { + var Repo = Server.GetComponent(); + var busy = Repo.OperationInProgress(); + if (!busy) + { + Console.WriteLine("Repo: Idle"); + var head = Repo.GetHead(out string error); + if (head == null) + head = "Error: " + error; + var branch = Repo.GetBranch(out error); + if (branch == null) + branch = "Error: " + error; + var remote = Repo.GetRemote(out error); + if (remote == null) + remote = "Error: " + error; + Console.WriteLine("Remote: " + remote); + Console.WriteLine("Branch: " + branch); + Console.WriteLine("HEAD: " + head); + } + else + { + Console.WriteLine("Repo: Busy"); + var progress = Repo.CheckoutProgress(); + if (progress != -1) + { + var eqs = ""; + for (var I = 0; I < progress / 10; ++I) + eqs += "="; + var dshs = ""; + for (var I = 0; I < 10 - (progress / 10); ++I) + eqs += "-"; + Console.WriteLine(String.Format("Progress: [{0}{1}] {2}%", eqs, dshs, progress)); + } + } + return ExitCode.Normal; + } + protected override string GetHelpText() + { + return "Shows the busy status of the repo, remote, branch, and HEAD information"; + } + } + + class RepoResetCommand : Command + { + public RepoResetCommand() + { + Keyword = "reset"; + } + public override ExitCode Run(IList parameters) + { + var result = Server.GetComponent().Reset(parameters.Count > 0 && parameters[0].ToLower() == "--origin"); + Console.WriteLine(result ?? "Success!"); + return result == null ? ExitCode.Normal : ExitCode.ServerError; + } + protected override string GetArgumentString() + { + return "[--origin]"; + } + protected override string GetHelpText() + { + return "Hard resets the repo. If a target is specified, the current branch is reset to that branch"; + } + } + + class RepoUpdateCommand : Command + { + public RepoUpdateCommand() + { + Keyword = "update"; + RequiredParameters = 1; + } + public override ExitCode Run(IList parameters) + { + bool hard; + switch (parameters[0].ToLower()) + { + case "hard": + hard = true; + break; + case "merge": + hard = false; + break; + default: + Console.WriteLine("Invalid parameter: " + parameters[0]); + return ExitCode.BadCommand; + } + var res = Server.GetComponent().Update(hard); + Console.WriteLine(res ?? "Success"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + protected override string GetHelpText() + { + return "Updates the current branch the repo is on either via a merge or hard reset"; + } + protected override string GetArgumentString() + { + return ""; + } + } + class RepoChangelogCommand : Command + { + public RepoChangelogCommand() + { + Keyword = "gen-changelog"; + } + public override ExitCode Run(IList parameters) + { + var result = Server.GetComponent().GenerateChangelog(out string error); + Console.WriteLine(error ?? "Success!"); + if (result != null) + Console.WriteLine(result); + return error == null ? ExitCode.Normal : ExitCode.ServerError; + } + + protected override string GetHelpText() + { + return "Compiles the html changelog"; + } + } + class RepoSetEmailCommand : Command + { + public RepoSetEmailCommand() + { + Keyword = "set-email"; + RequiredParameters = 1; + } + public override ExitCode Run(IList parameters) + { + Server.GetComponent().SetCommitterEmail(parameters[0]); + return ExitCode.Normal; + } + + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Set the e-mail used for commits"; + } + } + class RepoSetNameCommand : Command + { + public RepoSetNameCommand() + { + Keyword = "set-name"; + RequiredParameters = 1; + } + public override ExitCode Run(IList parameters) + { + Server.GetComponent().SetCommitterName(parameters[0]); + return ExitCode.Normal; + } + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Set the name used for commits"; + } + } + class RepoPythonPathCommand : Command + { + public RepoPythonPathCommand() + { + Keyword = "python-path"; + RequiredParameters = 1; + } + public override ExitCode Run(IList parameters) + { + Server.GetComponent().SetPythonPath(parameters[0]); + return ExitCode.Normal; + } + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Set the path to the folder containing the python 2.7 installation"; + } + } + + class RepoMergePRCommand : Command + { + public RepoMergePRCommand() + { + Keyword = "merge-pr"; + RequiredParameters = 1; + } + + public override ExitCode Run(IList parameters) + { + ushort PR; + try + { + PR = Convert.ToUInt16(parameters[0]); + } + catch + { + Console.WriteLine("Invalid PR Number!"); + return ExitCode.BadCommand; + } + var res = Server.GetComponent().MergePullRequest(PR); + Console.WriteLine(res ?? "Success"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + + protected override string GetArgumentString() + { + return ""; + } + protected override string GetHelpText() + { + return "Merge the given pull request from the origin repository into the current branch. Only supported with github remotes"; + } + } + + class RepoListPRsCommand : Command + { + public RepoListPRsCommand() + { + Keyword = "list-prs"; + } + protected override string GetHelpText() + { + return "Lists currently merge pull requests"; + } + public override ExitCode Run(IList parameters) + { + var data = Server.GetComponent().MergedPullRequests(out string error); + if (data == null) + { + Console.WriteLine(error); + return ExitCode.ServerError; + } + if (data.Count == 0) + Console.WriteLine("None!"); + else + foreach (var I in data) + Console.WriteLine(String.Format("#{0}: {2} by {3} at commit {1}", I.Number, I.Sha, I.Title, I.Author)); + return ExitCode.Normal; + } + } + + class RepoListBackupsCommand : Command + { + public RepoListBackupsCommand() + { + Keyword = "list-backups"; + } + protected override string GetHelpText() + { + return "Lists backup tags created by compilation"; + } + public override ExitCode Run(IList parameters) + { + var data = Server.GetComponent().ListBackups(out string error); + if (data == null) + { + Console.WriteLine(error); + return ExitCode.ServerError; + } + if (data.Count == 0) + Console.WriteLine("None!"); + else + foreach (var I in data) + Console.WriteLine(String.Format("{0} at commit {1}", I.Key, I.Value)); + return ExitCode.Normal; + } + } + class RepoCheckoutCommand : Command + { + public RepoCheckoutCommand() + { + Keyword = "checkout"; + RequiredParameters = 1; + } + protected override string GetHelpText() + { + return "Checks out the targeted object"; + } + public override ExitCode Run(IList parameters) + { + var res = Server.GetComponent().Checkout(parameters[0]); + Console.WriteLine(res ?? "Success"); + return res == null ? ExitCode.Normal : ExitCode.ServerError; + } + } +} diff --git a/TGCommandLine/RootCommands.cs b/TGCommandLine/RootCommands.cs new file mode 100644 index 0000000000..0dfd543222 --- /dev/null +++ b/TGCommandLine/RootCommands.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using TGServiceInterface; + +namespace TGCommandLine +{ + + class RootCommand : Command + { + bool IsRealRoot() + { + return !GetType().IsSubclassOf(typeof(RootCommand)); + } + public RootCommand() + { + if (IsRealRoot()) //stack overflows + Children = new Command[] { new UpdateCommand(), new TestmergeCommand(), new IRCCommand(), new RepoCommand(), new BYONDCommand(), new DMCommand(), new DDCommand(), new ConfigCommand(), new ChatCommand() }; + } + public override ExitCode Run(IList parameters) + { + if (parameters.Count > 0) + { + var LocalKeyword = parameters[0].Trim().ToLower(); + parameters.RemoveAt(0); + + switch (LocalKeyword) + { + case "help": + case "?": + if (!IsRealRoot()) + { + Console.WriteLine(Keyword + " commands:"); + Console.WriteLine(); + } + PrintHelp(); + return ExitCode.Normal; + default: + foreach (var c in Children) + if (c.Keyword == LocalKeyword) + { + if (parameters.Count < c.RequiredParameters) + { + Console.WriteLine("Not enough parameters!"); + return ExitCode.BadCommand; + } + return c.Run(parameters); + } + parameters.Insert(0, LocalKeyword); + break; + } + } + Console.WriteLine(String.Format("Invalid command: {0} {1}", Keyword, String.Join(" ", parameters))); + Console.WriteLine(String.Format("Type '{0}?' or '{0}help' for available commands.", Keyword != null ? Keyword + " " : "")); + return ExitCode.BadCommand; + } + + public override void PrintHelp() + { + Console.WriteLine("/tg/station 13 Server Command Line"); + Console.WriteLine("Available commands (type '?' or 'help' after command for more info):"); + Console.WriteLine(); + base.PrintHelp(); + } + + protected override string GetHelpText() + { + throw new NotImplementedException(); + } + } + + class UpdateCommand : Command + { + public UpdateCommand() + { + Keyword = "update"; + RequiredParameters = 1; + } + public override ExitCode Run(IList parameters) + { + var gen_cl = parameters.Count > 1 && parameters[1].ToLower() == "--cl"; + TGRepoUpdateMethod method; + switch (parameters[0].ToLower()) + { + case "hard": + method = TGRepoUpdateMethod.Hard; + break; + case "merge": + method = TGRepoUpdateMethod.Merge; + break; + default: + Console.WriteLine("Please specify hard or merge"); + return ExitCode.BadCommand; + } + var result = Server.GetComponent().UpdateServer(method, gen_cl); + Console.WriteLine(result ?? "Compilation started!"); + return result == null ? ExitCode.Normal : ExitCode.ServerError; + } + + protected override string GetArgumentString() + { + return " [--cl]"; + } + + protected override string GetHelpText() + { + return "Updates the server fully, optionally generating and pushing a changelog. Runs asynchronously once compilation starts"; + } + + } + + class TestmergeCommand : Command + { + public TestmergeCommand() + { + Keyword = "testmerge"; + RequiredParameters = 1; + } + public override ExitCode Run(IList parameters) + { + ushort tm; + try + { + tm = Convert.ToUInt16(parameters[0]); + if (tm == 0) + throw new Exception(); + } + catch + { + Console.WriteLine("Invalid tesmerge #: " + parameters[0]); + return ExitCode.BadCommand; + } + var result = Server.GetComponent().UpdateServer(TGRepoUpdateMethod.None, false, tm); + Console.WriteLine(result ?? "Compilation started!"); + return result == null ? ExitCode.Normal : ExitCode.ServerError; + } + protected override string GetArgumentString() + { + return ""; + } + + protected override string GetHelpText() + { + return "Merges the specified pull request and updates the server"; + } + } +} diff --git a/TGCommandLine/TGCommandLine.csproj b/TGCommandLine/TGCommandLine.csproj new file mode 100644 index 0000000000..874468395a --- /dev/null +++ b/TGCommandLine/TGCommandLine.csproj @@ -0,0 +1,65 @@ + + + + + Debug + AnyCPU + {89191F69-B18E-4B59-B72E-E12F9B6811A0} + Exe + TGCommandLine + TGCommandLine + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + tgs.ico + + + + + + + + + + + + + + + + + + {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} + TGServiceInterface + + + + + + + \ No newline at end of file diff --git a/TGCommandLine/tgs.ico b/TGCommandLine/tgs.ico new file mode 100644 index 0000000000000000000000000000000000000000..6ed69fadfcdf8deb049f66dfffc46d5361332feb GIT binary patch literal 123198 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 + + + + +
+
+
+ + + + + + + + + + 0 + + + 0 + + + + + 0 + + + + + https://github.com/tgstation/tgstation.git + + + tgstation + + + 2337 + + + master + + + False + + + C:\Program Files (x86)\BYOND\bin + + + tgstation-server + + + tgstation-server@users.noreply.github.com + + + + \ No newline at end of file diff --git a/TGControlPanel/ByondPage.cs b/TGControlPanel/ByondPage.cs new file mode 100644 index 0000000000..ef0539e184 --- /dev/null +++ b/TGControlPanel/ByondPage.cs @@ -0,0 +1,123 @@ +using System; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + partial class Main + { + string lastReadError = null; + void InitBYONDPage() + { + var BYOND = Server.GetComponent(); + var CV = BYOND.GetVersion(TGByondVersion.Installed); + if (CV == null) + CV = BYOND.GetVersion(TGByondVersion.Staged); + if (CV != null) + { + var splits = CV.Split('.'); + if(splits.Length == 2) + { + try + { + var Major = Convert.ToInt32(splits[0]); + var Minor = Convert.ToInt32(splits[1]); + MajorVersionNumeric.Value = Major; + MinorVersionNumeric.Value = Minor; + } + catch { } + } + } + + var latestVer = Server.GetComponent().GetVersion(TGByondVersion.Latest); + LatestVersionLabel.Text = latestVer; + + try + { + var splits = latestVer.Split('.'); + var maj = Convert.ToInt32(splits[0]); + MinorVersionNumeric.Value = Convert.ToInt32(splits[1]); + MajorVersionNumeric.Value = maj; + } + catch { } + + UpdateBYONDButtons(); + BYONDTimer.Start(); + } + private void UpdateButton_Click(object sender, EventArgs e) + { + UpdateBYONDButtons(); + if (!Server.GetComponent().UpdateToVersion((int)MajorVersionNumeric.Value, (int)MinorVersionNumeric.Value)) + MessageBox.Show("Unable to begin update, there is another operation in progress."); + } + + void UpdateBYONDButtons() + { + var BYOND = Server.GetComponent(); + + VersionLabel.Text = BYOND.GetVersion(TGByondVersion.Installed) ?? "Not Installed"; + + StagedVersionTitle.Visible = false; + StagedVersionLabel.Visible = false; + switch (BYOND.CurrentStatus()) + { + case TGByondStatus.Idle: + case TGByondStatus.Starting: + UpdateProgressBar.Value = 0; + StatusLabel.Text = "Idle"; + UpdateButton.Enabled = true; + UpdateProgressBar.Style = ProgressBarStyle.Blocks; + break; + case TGByondStatus.Downloading: + UpdateProgressBar.Value = 50; + StatusLabel.Text = "Downloading..."; + UpdateButton.Enabled = false; + UpdateProgressBar.Style = ProgressBarStyle.Blocks; + break; + case TGByondStatus.Staging: + UpdateProgressBar.Value = 100; + StatusLabel.Text = "Staging..."; + UpdateButton.Enabled = false; + UpdateProgressBar.Style = ProgressBarStyle.Blocks; + break; + case TGByondStatus.Staged: + StagedVersionTitle.Visible = true; + StagedVersionLabel.Visible = true; + StagedVersionLabel.Text = BYOND.GetVersion(TGByondVersion.Staged) ?? "Unknown"; + if (UpdateProgressBar.Style != ProgressBarStyle.Marquee || UpdateProgressBar.MarqueeAnimationSpeed != 50) + { + UpdateProgressBar.Style = ProgressBarStyle.Marquee; + UpdateProgressBar.MarqueeAnimationSpeed = 50; + } + StatusLabel.Text = "Staged and waiting for BYOND to shutdown..."; + UpdateButton.Enabled = true; + break; + case TGByondStatus.Updating: + UpdateProgressBar.Style = ProgressBarStyle.Marquee; + UpdateProgressBar.MarqueeAnimationSpeed = 200; + StatusLabel.Text = "Applying update..."; + UpdateButton.Enabled = false; + break; + } + } + + private void BYONDTimer_Tick(object sender, EventArgs e) + { + try + { + UpdateBYONDButtons(); + var error = Server.GetComponent().GetError(); + if (error != lastReadError) + { + lastReadError = error; + if (error != null) + MessageBox.Show("An error occurred: " + lastReadError); + } + } + catch (Exception ex) + { + Program.ServiceDisconnectException(ex); + } + } + } +} diff --git a/TGControlPanel/Chat.cs b/TGControlPanel/Chat.cs new file mode 100644 index 0000000000..d43f36f460 --- /dev/null +++ b/TGControlPanel/Chat.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + partial class Main + { + TGChatProvider modifyingProvider; + bool updatingChat = false; + + void LoadChatPage() + { + updatingChat = true; + var Chat = Server.GetComponent(); + var PI = Chat.ProviderInfo(); + modifyingProvider = PI.Provider; + switch (modifyingProvider) + { + case TGChatProvider.Discord: + var DPI = new TGDiscordSetupInfo(PI); + DiscordProviderSwitch.Select(); + AuthField1.Text = DPI.BotToken; //it's invisible so whatever + AuthField1Title.Text = "Bot Token:"; + AuthField2.Visible = false; + AuthField2Title.Visible = false; + ChatServerText.Visible = false; + ChatPortSelector.Visible = false; + ChatServerTitle.Visible = false; + ChatPortTitle.Visible = false; + ChatNicknameText.Visible = false; + ChatNicknameTitle.Visible = false; + break; + case TGChatProvider.IRC: + var IRC = new TGIRCSetupInfo(PI); + IRCProviderSwitch.Select(); + AuthField1.Text = IRC.AuthTarget; + AuthField2.Text = IRC.AuthMessage; + AuthField2.Visible = true; + AuthField2Title.Visible = true; + AuthField1Title.Text = "Auth Target:"; + AuthField2Title.Text = "Auth Message:"; + ChatServerText.Visible = true; + ChatPortSelector.Visible = true; + ChatServerTitle.Visible = true; + ChatPortTitle.Visible = true; + ChatServerText.Text = IRC.URL; + ChatPortSelector.Value = IRC.Port; + ChatNicknameText.Visible = true; + ChatNicknameTitle.Visible = true; + ChatNicknameText.Text = IRC.Nickname; + break; + default: + MessageBox.Show("This is a bug, I'll try and recover. Provider was " + modifyingProvider.ToString()); + MessageBox.Show(Chat.SetProviderInfo(new TGIRCSetupInfo()) ?? "Success!"); + LoadChatPage(); + return; + } + + var Enabled = Chat.Enabled(); + ChatEnabledCheckbox.Checked = Enabled; + if (!Enabled) + ChatStatusLabel.Text = "Disabled"; + else if (Chat.Connected()) + ChatStatusLabel.Text = "Connected"; + else + ChatStatusLabel.Text = "Disconnected"; + ChatReconnectButton.Enabled = Enabled; + + AdminChannelText.Text = Chat.AdminChannel(); + + ChatAdminsTextBox.Text = ""; + foreach (var I in Chat.ListAdmins()) + ChatAdminsTextBox.Text += I + "\r\n"; + + ChatChannelsTextBox.Text = ""; + foreach (var I in Chat.Channels()) + ChatChannelsTextBox.Text += I + "\r\n"; + updatingChat = false; + } + + private void ChatRefreshButton_Click(object sender, EventArgs e) + { + LoadChatPage(); + } + + private void ChatReconnectButton_Click(object sender, EventArgs e) + { + Server.GetComponent().Reconnect(); + LoadChatPage(); + } + + static string[] SplitByLine(TextBox t) + { + var channels = t.Text.Split('\n'); + + var finalChannels = new List(); + foreach (var I in channels) + { + var trimmed = I.Trim(); + if(trimmed != "") + finalChannels.Add(trimmed); + } + return finalChannels.ToArray(); + } + + private void DiscordProviderSwitch_CheckedChanged(object sender, EventArgs e) + { + if (!updatingChat && DiscordProviderSwitch.Checked) + { + var res = Server.GetComponent().SetProviderInfo(new TGDiscordSetupInfo()); + if (res != null) + MessageBox.Show(res); + } + } + + private void IRCProviderSwitch_CheckedChanged(object sender, EventArgs e) + { + if (!updatingChat && IRCProviderSwitch.Checked) + { + var res = Server.GetComponent().SetProviderInfo(new TGIRCSetupInfo()); + if (res != null) + MessageBox.Show(res); + } + } + + private void ChatApplyButton_Click(object sender, EventArgs e) + { + var Chat = Server.GetComponent(); + + Chat.SetChannels(SplitByLine(ChatChannelsTextBox), AdminChannelText.Text); + Chat.SetAdmins(SplitByLine(ChatAdminsTextBox)); + Chat.SetEnabled(ChatEnabledCheckbox.Checked); + + string res; + switch (modifyingProvider) + { + case TGChatProvider.Discord: + res = Chat.SetProviderInfo(new TGDiscordSetupInfo() { BotToken = AuthField1.Text }); + break; + case TGChatProvider.IRC: + res = Chat.SetProviderInfo(new TGIRCSetupInfo() { + AuthMessage = AuthField2.Text, + AuthTarget = AuthField1.Text, + Nickname = ChatNicknameText.Text, + URL = ChatServerText.Text, + Port = (ushort)ChatPortSelector.Value, + }); + break; + default: + res = "You really shouldn't be able to read this."; + break; + } + + if (res != null) + MessageBox.Show(res); + + LoadChatPage(); + } + } +} diff --git a/TGControlPanel/ConfigHelperControls.cs b/TGControlPanel/ConfigHelperControls.cs new file mode 100644 index 0000000000..dba57099de --- /dev/null +++ b/TGControlPanel/ConfigHelperControls.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + class ConfigTextBox : TextBox + { + IList ChangeList; + ConfigSetting Setting; + public ConfigTextBox(ConfigSetting c, IList cl) + { + Setting = c; + TextChanged += ConfigTextbox_TextChanged; + ChangeList = cl; + Text = Setting.Value; + Multiline = true; + Width = 560; + Height *= 3; + ScrollBars = ScrollBars.Both; + } + + private void ConfigTextbox_TextChanged(object sender, EventArgs e) + { + Setting.Value = Text; + if (!ChangeList.Contains(Setting)) + ChangeList.Add(Setting); + } + } + class JobNumeric : NumericUpDown + { + IList ChangeList; + JobSetting Setting; + bool spawn; + public JobNumeric(JobSetting c, IList cl, bool s) + { + spawn = s; + Setting = c; + ChangeList = cl; + Minimum = -1; + Maximum = 10000; + Value = spawn ? c.SpawnPositions : c.TotalPositions; + ValueChanged += JobNumeric_ValueChanged; + } + + private void JobNumeric_ValueChanged(object sender, EventArgs e) + { + if (spawn) + Setting.SpawnPositions = (int)Value; + else + Setting.TotalPositions = (int)Value; + if (!ChangeList.Contains(Setting)) + ChangeList.Add(Setting); + } + } + enum MapNumType + { + MaxPlayers, + MinPlayers, + VoteWeight, + } + class MapNumeric : NumericUpDown + { + IList ChangeList; + MapSetting Setting; + MapNumType type; + public MapNumeric(MapSetting c, IList cl, MapNumType t) + { + type = t; + Setting = c; + ChangeList = cl; + Minimum = -1; + Maximum = 10000; + switch (type) + { + case MapNumType.MaxPlayers: + Value = Setting.MaxPlayers; + break; + case MapNumType.MinPlayers: + Value = Setting.MinPlayers; + break; + case MapNumType.VoteWeight: + DecimalPlaces = 5; + Value = Convert.ToDecimal(Setting.VoteWeight); + break; + } + ValueChanged += MapNumeric_ValueChanged; + } + + private void MapNumeric_ValueChanged(object sender, EventArgs e) + { + switch (type) + { + case MapNumType.MaxPlayers: + Setting.MaxPlayers = (int)Value; + break; + case MapNumType.MinPlayers: + Setting.MinPlayers = (int)Value; + break; + case MapNumType.VoteWeight: + Setting.VoteWeight = (float)Value; + break; + } + if (!ChangeList.Contains(Setting)) + ChangeList.Add(Setting); + } + } + + class ConfigCheckBox : CheckBox + { + IList ChangeList; + ConfigSetting Setting; + public ConfigCheckBox(ConfigSetting c, IList cl) + { + Setting = c; + CheckStateChanged += ConfigCheckBox_CheckStateChanged; + ChangeList = cl; + Text = "Enabled"; + Font = new Font("Verdana", 8.0f); + ForeColor = Color.FromArgb(248, 248, 242); + Checked = Setting.Value != null; + } + + private void ConfigCheckBox_CheckStateChanged(object sender, EventArgs e) + { + Setting.Value = Checked ? "" : null; + if (!ChangeList.Contains(Setting)) + ChangeList.Add(Setting); + } + } + + class MapRadioButton : RadioButton + { + IList ChangeList; + IList AllButtons; + MapSetting Setting; + public MapRadioButton(MapSetting c, IList cl, IList others) + { + ChangeList = cl; + Setting = c; + AllButtons = others; + Font = new Font("Verdana", 8.0f); + ForeColor = Color.FromArgb(248, 248, 242); + Text = "Default"; + AllButtons.Add(this); + if (Setting.Default) + Checked = true; + CheckedChanged += MapRadioButton_CheckedChanged; + } + + private void MapRadioButton_CheckedChanged(object sender, EventArgs e) + { + if (!Checked) + return; + foreach(var I in AllButtons) + if(I != this && I.Checked) + { + I.Checked = false; + I.Setting.Default = false; + if(!ChangeList.Contains(I.Setting)) + ChangeList.Add(I.Setting); + break; + } + Setting.Default = true; + if (!ChangeList.Contains(Setting)) + ChangeList.Add(Setting); + } + } + + class MapCheckBox : CheckBox + { + IList ChangeList; + MapSetting Setting; + public MapCheckBox(MapSetting c, IList cl) + { + Setting = c; + CheckStateChanged += ConfigCheckBox_CheckStateChanged; + ChangeList = cl; + Text = "Enabled"; + Font = new Font("Verdana", 8.0f); + ForeColor = Color.FromArgb(248, 248, 242); + Checked = Setting.Enabled; + } + + private void ConfigCheckBox_CheckStateChanged(object sender, EventArgs e) + { + Setting.Enabled = Checked; + if (!ChangeList.Contains(Setting)) + ChangeList.Add(Setting); + } + } + + class ConfigAddRemoveButton : Button + { + ConfigSetting Setting; + Main main; + TGConfigType type; + bool remove; + public ConfigAddRemoveButton(ConfigSetting c, Main m, TGConfigType t) + { + Setting = c; + main = m; + type = t; + Click += ConfigAddRemoveButton_Click; + UseVisualStyleBackColor = true; + remove = Setting.ExistsInStatic || !Setting.ExistsInRepo; + Text = remove ? "Remove" : "Add"; + } + + private void ConfigAddRemoveButton_Click(object sender, EventArgs e) + { + if (remove) + { + Setting.Values = Setting.DefaultValues; + Setting.Value = null; + } + else + { + Setting.Value = Setting.DefaultValue; + Setting.Values = Setting.DefaultValues; + } + + var Result = Server.GetComponent().SetItem(type, Setting); + if (Result != null) + MessageBox.Show("Error: " + Result); + + main.RefreshCurrentPage(); + } + } +} diff --git a/TGControlPanel/ConfigPage.cs b/TGControlPanel/ConfigPage.cs new file mode 100644 index 0000000000..0a0074427b --- /dev/null +++ b/TGControlPanel/ConfigPage.cs @@ -0,0 +1,654 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + + partial class Main + { + enum ConfigIndex + { + Config = 0, + Database = 1, + Game = 2, + Jobs = 3, + Maps = 4, + Admins = 5, + } + + IList GeneralChangelist, DatabaseChangelist, GameChangelist; + IList JobsChangelist; + IList MapsChangelist; + + FlowLayoutPanel ConfigConfigFlow, DatabaseConfigFlow, GameConfigFlow, JobsConfigFlow, MapsConfigFlow; + + bool updatingAdminPerms = false; + + FlowLayoutPanel CreateFLP(Control parent) + { + var res = new FlowLayoutPanel() + { + AutoSize = true, + FlowDirection = FlowDirection.TopDown, + }; + AdjustFlow(res, parent); + parent.Controls.Add(res); + return res; + } + void AdjustFlow(FlowLayoutPanel flow, Control parent) + { + flow.MaximumSize = new Size(parent.Width - 90, 9999999); + } + void InitConfigPage() + { + ConfigPanels.SelectedIndex = Properties.Settings.Default.LastConfigPageIndex; + ConfigApply.Enabled = (ConfigIndex)ConfigPanels.SelectedIndex != ConfigIndex.Admins; + ConfigPanels.SelectedIndexChanged += ConfigPanels_SelectedIndexChanged; + + Resize += ReadjustFlow; + + ConfigConfigFlow = CreateFLP(ConfigConfigPanel); + GeneralChangelist = new List(); + + DatabaseConfigFlow = CreateFLP(DatabaseConfigPanel); + DatabaseChangelist = new List(); + + GameConfigFlow = CreateFLP(GameConfigPanel); + GameChangelist = new List(); + + JobsConfigFlow = CreateFLP(JobsConfigPanel); + JobsChangelist = new List(); + + MapsConfigFlow = CreateFLP(MapsConfigPanel); + MapsChangelist = new List(); + + AdminRanksListBox.SelectedIndexChanged += AdminRanksListBox_SelectedIndexChanged; + PermissionsListBox.ItemCheck += AdjustCurrentRankPermissions; + NegativePermissions.ItemCheck += AdjustCurrentRankPermissions; + + LoadConfig(); + } + void LoadConfig() + { + var RepoReady = Server.GetComponent().Exists(); + ConfigPanels.Visible = RepoReady; + ConfigApply.Visible = RepoReady; + ConfigDownload.Visible = RepoReady; + ConfigDownloadRepo.Visible = RepoReady; + ConfigUpload.Visible = RepoReady; + if (!RepoReady) + return; + LoadGenericConfig(TGConfigType.General); + LoadGenericConfig(TGConfigType.Database); + LoadGenericConfig(TGConfigType.Game); + LoadJobsConfig(); + LoadMapsConfig(); + LoadAdminsConfig(); + } + + private void RemoveRankButton_Click(object sender, EventArgs e) + { + var rank = (string)AdminRanksListBox.SelectedItem; + var result = Server.GetComponent().RemoveAdminRank(rank); + if (result != null) + MessageBox.Show("Error: " + result); + LoadRanksList(); + } + + private void AddRankButton_Click(object sender, EventArgs e) + { + var result = Server.GetComponent().SetAdminRank(AddRankTextBox.Text, new Dictionary()); + if (result != null) + MessageBox.Show("Error: " + result); + else + AddRankTextBox.Text = ""; + LoadRanksList(); + } + + private void ApplyAdminRankButton_Click(object sender, EventArgs e) + { + var rank = (string)AdminRanksListBox.SelectedItem; + var admin = (string)AdminsListBox.SelectedItem; + if(rank == null || admin == null) + { + MessageBox.Show("Please select a rank and admin!"); + return; + } + admin = admin.Split(' ')[0]; + var result = Server.GetComponent().Addmin(admin, rank); + if (result != null) + MessageBox.Show("Error: " + result); + LoadAdminsList(); + } + + private void DeadminButton_Click(object sender, EventArgs e) + { + var selectedAdmin = (string)AdminsListBox.SelectedItem; + if(selectedAdmin == null) + { + MessageBox.Show("You must select an admin first!"); + return; + } + var result = Server.GetComponent().Deadmin(selectedAdmin.Split(' ')[0]); + if (result != null) + MessageBox.Show("Error: " + result); + LoadAdminsList(); + } + + private void AddminButton_Click(object sender, EventArgs e) + { + var rank = (string)AdminRanksListBox.SelectedItem; + if(rank == null) + { + MessageBox.Show("You must select a rank first!"); + return; + } + var result = Server.GetComponent().Addmin(AddminTextBox.Text, rank); + if (result != null) + MessageBox.Show("Error: " + result); + else + AddminTextBox.Text = ""; + LoadAdminsList(); + } + private void AdjustCurrentRankPermissions(object sender, ItemCheckEventArgs e) + { + if (updatingAdminPerms) + return; + if (AdminRanksListBox.SelectedIndex == -1) + { + MessageBox.Show("No admin rank selected!"); + return; + } + var perms = new Dictionary(); + for (var I = 0; I < PermissionsListBox.Items.Count; ++I) + { + var negChecked = NegativePermissions.GetItemChecked(I) || (sender == NegativePermissions && e.Index == I && e.NewValue == CheckState.Checked); + var posChecked = PermissionsListBox.GetItemChecked(I) || (sender == PermissionsListBox && e.Index == I && e.NewValue == CheckState.Checked); + var perm = ((string)PermissionsListBox.Items[I]).Split(' ')[0]; + if (posChecked ^ negChecked) + perms.Add(perm, posChecked); + } + + var result = Server.GetComponent().SetAdminRank((string)AdminRanksListBox.SelectedItem, perms); + if (result != null) + MessageBox.Show("Error: " + result); + UpdatePermissionsDisplay(); + } + + private void AdminRanksListBox_SelectedIndexChanged(object sender, EventArgs e) + { + UpdatePermissionsDisplay(); + } + void UpdatePermissionsDisplay() { + var rank = (string)AdminRanksListBox.SelectedItem; + var ranks = Server.GetComponent().AdminRanks(out string error); + if (ranks != null && !ranks.ContainsKey(rank)) + error = "Could not find rank: " + rank + "!"; + if (error != null) + { + MessageBox.Show("Error: " + error); + return; + } + var ourRank = ranks[rank]; + updatingAdminPerms = true; + for (var I = 0; I < PermissionsListBox.Items.Count; ++I) + { + PermissionsListBox.SetItemChecked(I, false); + NegativePermissions.SetItemChecked(I, false); + } + foreach (var I in ourRank) + if (I.Value) + { + for (var J = 0; J < PermissionsListBox.Items.Count; ++J) + if (((string)PermissionsListBox.Items[J]).Split(' ')[0] == I.Key) + PermissionsListBox.SetItemChecked(J, true); + } + else + for (var J = 0; J < PermissionsListBox.Items.Count; ++J) + if (((string)NegativePermissions.Items[J]).Split(' ')[0] == I.Key) + NegativePermissions.SetItemChecked(J, false); + updatingAdminPerms = false; + } + + private void ConfigPanels_SelectedIndexChanged(object sender, EventArgs e) + { + ConfigApply.Enabled = (ConfigIndex)ConfigPanels.SelectedIndex != ConfigIndex.Admins; + Properties.Settings.Default.LastConfigPageIndex = ConfigPanels.SelectedIndex; + } + + void ReadjustFlow(object sender, EventArgs e) + { + AdjustFlow(ConfigConfigFlow, ConfigConfigPanel); + AdjustFlow(GameConfigFlow, GameConfigPanel); + AdjustFlow(DatabaseConfigFlow, DatabaseConfigPanel); + AdjustFlow(JobsConfigFlow, JobsConfigPanel); + AdjustFlow(MapsConfigFlow, MapsConfigPanel); + } + + void LoadGenericConfig(TGConfigType type) + { + FlowLayoutPanel flow; + IList changeList; + switch (type) + { + case TGConfigType.Database: + flow = DatabaseConfigFlow; + changeList = DatabaseChangelist; + break; + case TGConfigType.Game: + flow = GameConfigFlow; + changeList = GameChangelist; + break; + case TGConfigType.General: + flow = ConfigConfigFlow; + changeList = GeneralChangelist; + break; + default: + throw new Exception(String.Format("Invalid TGConfigType {0}", type)); + } + changeList.Clear(); + flow.Controls.Clear(); + flow.SuspendLayout(); + + var Entries = Server.GetComponent().Retrieve(type, out string error); + if (Entries != null) + foreach (var I in Entries) + HandleConfigEntry(I, flow, changeList, type); + else + flow.Controls.Add(new Label() { Text = "Unable to load related config!" }); + + flow.ResumeLayout(); + } + + void ConfigRefresh_Click(object sender, System.EventArgs e) + { + RefreshCurrentPage(); + } + + public void RefreshCurrentPage() + { + if (!ConfigPanels.Visible) + { + LoadConfig(); + return; + } + + switch ((ConfigIndex)ConfigPanels.SelectedIndex) + { + case ConfigIndex.Config: + LoadGenericConfig(TGConfigType.General); + break; + case ConfigIndex.Database: + LoadGenericConfig(TGConfigType.Database); + break; + case ConfigIndex.Game: + LoadGenericConfig(TGConfigType.Game); + break; + case ConfigIndex.Jobs: + LoadJobsConfig(); + break; + case ConfigIndex.Maps: + LoadMapsConfig(); + break; + case ConfigIndex.Admins: + LoadAdminsConfig(); + break; + } + } + + void LoadRanksList() + { + var ranks = Server.GetComponent().AdminRanks(out string error); + AdminRanksListBox.Items.Clear(); + if (ranks == null) + MessageBox.Show("Error: " + error); + else + foreach (var I in ranks) + AdminRanksListBox.Items.Add(I.Key); + if (AdminRanksListBox.Items.Count > 0 && AdminRanksListBox.SelectedIndex == -1) + { + AdminRanksListBox.SelectedIndex = 0; + RemoveRankButton.Enabled = true; + } + else + RemoveRankButton.Enabled = false; + } + + void LoadAdminsList() + { + var admins = Server.GetComponent().Admins(out string error); + AdminsListBox.Items.Clear(); + if (admins == null) + MessageBox.Show("Error: " + error); + else + foreach (var I in admins) + AdminsListBox.Items.Add(String.Format("{0} ({1})", I.Key, I.Value)); + if(AdminsListBox.Items.Count > 0 && AdminsListBox.SelectedIndex == -1) { + AdminsListBox.SelectedIndex = 0; + DeadminButton.Enabled = true; + } + else + DeadminButton.Enabled = false; + } + + void LoadAdminsConfig() + { + var perms = Server.GetComponent().ListPermissions(out string error); + PermissionsListBox.Items.Clear(); + if (perms == null) + MessageBox.Show("Error: " + error); + else + foreach (var I in perms) + { + var formattedDisplay = String.Format("{0} ({1})", I.Key, I.Value); + PermissionsListBox.Items.Add(formattedDisplay); + NegativePermissions.Items.Add(formattedDisplay); + } + LoadRanksList(); + LoadAdminsList(); + } + + void ApplyGenericConfig(IList changelist, TGConfigType type) + { + var Config = Server.GetComponent(); + foreach (var I in changelist) + { + var error = Config.SetItem(type, I); + if (error != null) + { + MessageBox.Show("An error occurred: {1}" + error); + break; + } + } + } + + void ConfigApply_Click(object sender, EventArgs e) + { + switch ((ConfigIndex)ConfigPanels.SelectedIndex) + { + case ConfigIndex.Config: + ApplyGenericConfig(GeneralChangelist, TGConfigType.General); + break; + case ConfigIndex.Database: + ApplyGenericConfig(DatabaseChangelist, TGConfigType.Database); + break; + case ConfigIndex.Game: + ApplyGenericConfig(GameChangelist, TGConfigType.Game); + break; + case ConfigIndex.Jobs: + var Config = Server.GetComponent(); + foreach (var I in JobsChangelist) + Config.SetJob(I); + break; + case ConfigIndex.Maps: + Config = Server.GetComponent(); + foreach (var I in MapsChangelist) + Config.SetMapSettings(I); + break; + case ConfigIndex.Admins: + MessageBox.Show("How were you able to click that???"); + break; + } + RefreshCurrentPage(); + } + + void LoadMapsConfig() + { + MapsConfigFlow.Controls.Clear(); + var Maps = Server.GetComponent().MapSettings(out string error); + if (Maps == null) + { + MapsConfigFlow.Controls.Add(new Label() + { + Text = "Error: " + error, + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + return; + } + + MapsConfigFlow.SuspendLayout(); + MapsConfigFlow.Controls.Add(new Label() + { + Text = "Set a player limit to 0 for it to be ignored", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + MapsConfigFlow.Controls.Add(new Label()); + var mapRadios = new List(); + foreach(var M in Maps) + { + var p = new FlowLayoutPanel() { FlowDirection = FlowDirection.LeftToRight, AutoSize = true }; + p.Controls.Add(new Label() + { + Text = M.Name + ":", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + p.Controls.Add(new MapCheckBox(M, MapsChangelist)); + p.Controls.Add(new MapRadioButton(M, MapsChangelist, mapRadios)); + MapsConfigFlow.Controls.Add(p); + p = new FlowLayoutPanel() { FlowDirection = FlowDirection.LeftToRight, AutoSize = true }; + p.Controls.Add(new Label() + { + Text = "Min Players:", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + p.Controls.Add(new MapNumeric(M, MapsChangelist, MapNumType.MinPlayers)); + p.Controls.Add(new Label() + { + Text = "Max Players:", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + p.Controls.Add(new MapNumeric(M, MapsChangelist, MapNumType.MaxPlayers)); + p.Controls.Add(new Label() + { + Text = "Vote Weight:", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + p.Controls.Add(new MapNumeric(M, MapsChangelist, MapNumType.VoteWeight)); + MapsConfigFlow.Controls.Add(p); + MapsConfigFlow.Controls.Add(new Label()); //line break + } + MapsConfigFlow.ResumeLayout(); + } + + void LoadJobsConfig() + { + JobsConfigFlow.Controls.Clear(); + var Jobs = Server.GetComponent().Jobs(out string error); + + if(Jobs == null) + { + JobsConfigFlow.Controls.Add(new Label() + { + Text = "Error: " + error, + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + return; + } + JobsConfigFlow.SuspendLayout(); + JobsConfigFlow.Controls.Add(new Label() + { + Text = "Set a value to -1 for infinite positions", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + JobsConfigFlow.Controls.Add(new Label()); + foreach (var J in Jobs) + { + JobsConfigFlow.Controls.Add(new Label() + { + Text = J.Name + ":", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + var p = new FlowLayoutPanel() { FlowDirection = FlowDirection.LeftToRight, AutoSize = true }; + p.Controls.Add(new Label() + { + Text = "Total Positions:", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + p.Controls.Add(new JobNumeric(J, JobsChangelist, false)); + p.Controls.Add(new Label() + { + Text = "Spawn Positions:", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + p.Controls.Add(new JobNumeric(J, JobsChangelist, true)); + JobsConfigFlow.Controls.Add(p); + JobsConfigFlow.Controls.Add(new Label()); //line break + } + JobsConfigFlow.ResumeLayout(); + } + + void ConfigUpload_Click(object sender, EventArgs eva) + { + var ofd = new OpenFileDialog() + { + CheckFileExists = true, + CheckPathExists = true, + DefaultExt = ".txt", + Multiselect = false, + Title = "Config Upload", + ValidateNames = true, + Filter = "Text files (*.txt)|*.txt|PNG files (*.png)|*.png|All files (*.*)|*.*", + AddExtension = false, + SupportMultiDottedExtensions = true, + }; + if (ofd.ShowDialog() != DialogResult.OK) + return; + + var fileToUpload = ofd.FileName; + + var originalFileName = Program.TextPrompt("Config Upload", "Enter the path of the destination file in the config folder:"); + if (originalFileName == null) + return; + + string fileContents = null; + string error = null; + try + { + fileContents = File.ReadAllText(fileToUpload); + } catch (Exception e) + { + error = e.ToString(); + } + if (error == null) + error = Server.GetComponent().WriteRaw(originalFileName, fileContents); + if (error != null) + MessageBox.Show("An error occurred: " + error); + } + + void DownloadConfig(string remotePath, bool repo) + { + if (remotePath == null) + return; + var text = Server.GetComponent().ReadRaw(remotePath, repo, out string error); + if (text != null) + { + + var ofd = new SaveFileDialog() + { + CheckFileExists = false, + CheckPathExists = true, + DefaultExt = ".txt", + Title = "Config Download", + ValidateNames = true, + Filter = "Text files (*.txt)|*.txt|PNG files (*.png)|*.png|All files (*.*)|*.*", + AddExtension = false, + CreatePrompt = false, + OverwritePrompt = true, + SupportMultiDottedExtensions = true, + }; + if (ofd.ShowDialog() != DialogResult.OK) + return; + + try + { + File.WriteAllText(ofd.FileName, text); + return; + } + catch (Exception e) + { + error = e.ToString(); + } + } + MessageBox.Show("An error occurred: " + error); + } + + void ConfigDownload_Click(object sender, EventArgs eva) + { + DownloadConfig(Program.TextPrompt("Config Download", "Enter the path of the source file in the config folder:"), false); + } + + void ConfigDownloadRepo_Click(object sender, EventArgs e) + { + DownloadConfig(Program.TextPrompt("Repo Config Download", "Enter the path of the source file in the repository's config folder:"), true); + } + + void HandleConfigEntry(ConfigSetting setting, FlowLayoutPanel flow, IList changelist, TGConfigType type) + { + flow.Controls.Add(new Label() + { + Text = setting.Name + (setting.ExistsInRepo ? "" : " (Does not exist in repository!)"), + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + + if (setting.ExistsInRepo) + flow.Controls.Add(new Label() + { + AutoSize = true, + Font = new Font("Verdana", 8.0f), + ForeColor = Color.FromArgb(248, 248, 242), + Text = setting.Comment + }); + + if (setting.IsMultiKey) + { + flow.Controls.Add(new Label() + { + Text = "MANUAL EDIT REQUIRED!", + AutoSize = true, + Font = new Font("Verdana", 10.0f), + ForeColor = Color.FromArgb(248, 248, 242) + }); + } + else + { + + var IsSwitch = setting.DefaultValue == "" || setting.DefaultValue == null; + + if (!IsSwitch || !setting.ExistsInRepo) + flow.Controls.Add(new ConfigAddRemoveButton(setting, this, type)); + + if (IsSwitch || setting.ExistsInStatic) + flow.Controls.Add(IsSwitch ? (Control)new ConfigCheckBox(setting, changelist) : new ConfigTextBox(setting, changelist)); + } + flow.Controls.Add(new Label()); //line break + } + } +} diff --git a/TGControlPanel/Main.Designer.cs b/TGControlPanel/Main.Designer.cs new file mode 100644 index 0000000000..83a7ed9f3d --- /dev/null +++ b/TGControlPanel/Main.Designer.cs @@ -0,0 +1,2008 @@ +namespace TGControlPanel +{ + partial class Main + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main)); + this.Panels = new System.Windows.Forms.TabControl(); + this.RepoPanel = new System.Windows.Forms.TabPage(); + this.BackupTagsList = new System.Windows.Forms.ListBox(); + this.ResetRemote = new System.Windows.Forms.Button(); + this.RecloneButton = new System.Windows.Forms.Button(); + this.PythonPathText = new System.Windows.Forms.TextBox(); + this.PythonPathLabel = new System.Windows.Forms.Label(); + this.RepoGenChangelogButton = new System.Windows.Forms.Button(); + this.TestmergeSelector = new System.Windows.Forms.NumericUpDown(); + this.TestMergeListLabel = new System.Windows.Forms.ListBox(); + this.CurrentRevisionLabel = new System.Windows.Forms.Label(); + this.RepoApplyButton = new System.Windows.Forms.Button(); + this.RepoBranchTextBox = new System.Windows.Forms.TextBox(); + this.RepoRemoteTextBox = new System.Windows.Forms.TextBox(); + this.HardReset = new System.Windows.Forms.Button(); + this.UpdateRepoButton = new System.Windows.Forms.Button(); + this.MergePRButton = new System.Windows.Forms.Button(); + this.IdentityLabel = new System.Windows.Forms.Label(); + this.TestMergeListTitle = new System.Windows.Forms.Label(); + this.RemoteNameTitle = new System.Windows.Forms.Label(); + this.BranchNameTitle = new System.Windows.Forms.Label(); + this.CurrentRevisionTitle = new System.Windows.Forms.Label(); + this.CloneRepositoryButton = new System.Windows.Forms.Button(); + this.RepoProgressBarLabel = new System.Windows.Forms.Label(); + this.RepoProgressBar = new System.Windows.Forms.ProgressBar(); + this.BYONDPanel = new System.Windows.Forms.TabPage(); + this.LatestVersionLabel = new System.Windows.Forms.Label(); + this.LatestVersionTitle = new System.Windows.Forms.Label(); + this.StagedVersionLabel = new System.Windows.Forms.Label(); + this.StagedVersionTitle = new System.Windows.Forms.Label(); + this.StatusLabel = new System.Windows.Forms.Label(); + this.VersionLabel = new System.Windows.Forms.Label(); + this.VersionTitle = new System.Windows.Forms.Label(); + this.MinorVersionLabel = new System.Windows.Forms.Label(); + this.MajorVersionLabel = new System.Windows.Forms.Label(); + this.UpdateButton = new System.Windows.Forms.Button(); + this.MinorVersionNumeric = new System.Windows.Forms.NumericUpDown(); + this.MajorVersionNumeric = new System.Windows.Forms.NumericUpDown(); + this.UpdateProgressBar = new System.Windows.Forms.ProgressBar(); + this.ServerPanel = new System.Windows.Forms.TabPage(); + this.VisibilitySelector = new System.Windows.Forms.ComboBox(); + this.VisibilityTitle = new System.Windows.Forms.Label(); + this.SecuritySelector = new System.Windows.Forms.ComboBox(); + this.SecurityTitle = new System.Windows.Forms.Label(); + this.ResetTestmerge = new System.Windows.Forms.Button(); + this.NudgePortSelector = new System.Windows.Forms.NumericUpDown(); + this.NudgePortLabel = new System.Windows.Forms.Label(); + this.ServerPathLabel = new System.Windows.Forms.Label(); + this.ServerPathTextbox = new System.Windows.Forms.TextBox(); + this.CompileCancelButton = new System.Windows.Forms.Button(); + this.ProjectPathLabel = new System.Windows.Forms.Label(); + this.projectNameText = new System.Windows.Forms.TextBox(); + this.PortLabel = new System.Windows.Forms.Label(); + this.PortSelector = new System.Windows.Forms.NumericUpDown(); + this.ServerPRLabel = new System.Windows.Forms.Label(); + this.ServerTestmergeInput = new System.Windows.Forms.NumericUpDown(); + this.TestmergeButton = new System.Windows.Forms.Button(); + this.UpdateTestmergeButton = new System.Windows.Forms.Button(); + this.UpdateMergeButton = new System.Windows.Forms.Button(); + this.UpdateHardButton = new System.Windows.Forms.Button(); + this.ServerGRestartButton = new System.Windows.Forms.Button(); + this.ServerGStopButton = new System.Windows.Forms.Button(); + this.ServerRestartButton = new System.Windows.Forms.Button(); + this.ServerStopButton = new System.Windows.Forms.Button(); + this.ServerStartButton = new System.Windows.Forms.Button(); + this.AutostartCheckbox = new System.Windows.Forms.CheckBox(); + this.CompilerStatusLabel = new System.Windows.Forms.Label(); + this.CompilerLabel = new System.Windows.Forms.Label(); + this.compileButton = new System.Windows.Forms.Button(); + this.initializeButton = new System.Windows.Forms.Button(); + this.compilerProgressBar = new System.Windows.Forms.ProgressBar(); + this.ServerStatusLabel = new System.Windows.Forms.Label(); + this.ServerStatusTitle = new System.Windows.Forms.Label(); + this.ChatPanel = new System.Windows.Forms.TabPage(); + this.ChatRefreshButton = new System.Windows.Forms.Button(); + this.ChatNicknameText = new System.Windows.Forms.TextBox(); + this.ChatNicknameTitle = new System.Windows.Forms.Label(); + this.ChatPortSelector = new System.Windows.Forms.NumericUpDown(); + this.ChatPortTitle = new System.Windows.Forms.Label(); + this.ChatServerText = new System.Windows.Forms.TextBox(); + this.ChatServerTitle = new System.Windows.Forms.Label(); + this.ChatApplyButton = new System.Windows.Forms.Button(); + this.AuthField2Title = new System.Windows.Forms.Label(); + this.AuthField1Title = new System.Windows.Forms.Label(); + this.AuthField2 = new System.Windows.Forms.TextBox(); + this.AuthField1 = new System.Windows.Forms.TextBox(); + this.AdminChannelTitle = new System.Windows.Forms.Label(); + this.AdminChannelText = new System.Windows.Forms.TextBox(); + this.ChatReconnectButton = new System.Windows.Forms.Button(); + this.ChatStatusLabel = new System.Windows.Forms.Label(); + this.ChatStatusTitle = new System.Windows.Forms.Label(); + this.ChatEnabledCheckbox = new System.Windows.Forms.CheckBox(); + this.ChatProviderSelectorPanel = new System.Windows.Forms.Panel(); + this.DiscordProviderSwitch = new System.Windows.Forms.RadioButton(); + this.IRCProviderSwitch = new System.Windows.Forms.RadioButton(); + this.ChatProviderTitle = new System.Windows.Forms.Label(); + this.ChannelsTitle = new System.Windows.Forms.Label(); + this.ChatAdminsTextBox = new System.Windows.Forms.TextBox(); + this.ChatAdminsTitle = new System.Windows.Forms.Label(); + this.ChatChannelsTextBox = new System.Windows.Forms.TextBox(); + this.ConfigPanel = new System.Windows.Forms.TabPage(); + this.ConfigApply = new System.Windows.Forms.Button(); + this.ConfigDownloadRepo = new System.Windows.Forms.Button(); + this.ConfigUpload = new System.Windows.Forms.Button(); + this.ConfigDownload = new System.Windows.Forms.Button(); + this.ConfigRefresh = new System.Windows.Forms.Button(); + this.ConfigPanels = new System.Windows.Forms.TabControl(); + this.ConfigConfigPanel = new System.Windows.Forms.TabPage(); + this.DatabaseConfigPanel = new System.Windows.Forms.TabPage(); + this.GameConfigPanel = new System.Windows.Forms.TabPage(); + this.JobsConfigPanel = new System.Windows.Forms.TabPage(); + this.MapsConfigPanel = new System.Windows.Forms.TabPage(); + this.AdminsPanel = new System.Windows.Forms.TabPage(); + this.NegativePermissionsTitle = new System.Windows.Forms.Label(); + this.NegativePermissions = new System.Windows.Forms.CheckedListBox(); + this.ApplyAdminRankButton = new System.Windows.Forms.Button(); + this.PermissionsListBox = new System.Windows.Forms.CheckedListBox(); + this.PermissionsTItle = new System.Windows.Forms.Label(); + this.RemoveRankButton = new System.Windows.Forms.Button(); + this.AddRankTextBox = new System.Windows.Forms.TextBox(); + this.AddRankButton = new System.Windows.Forms.Button(); + this.AdminRanksListBox = new System.Windows.Forms.ListBox(); + this.RanksTitle = new System.Windows.Forms.Label(); + this.DeadminButton = new System.Windows.Forms.Button(); + this.AddminTextBox = new System.Windows.Forms.TextBox(); + this.AddminButton = new System.Windows.Forms.Button(); + this.AdminsListBox = new System.Windows.Forms.ListBox(); + this.AdminsTitle = new System.Windows.Forms.Label(); + this.ConfigDisabledLabel = new System.Windows.Forms.Label(); + this.RepoBGW = new System.ComponentModel.BackgroundWorker(); + this.BYONDTimer = new System.Windows.Forms.Timer(this.components); + this.ServerTimer = new System.Windows.Forms.Timer(this.components); + this.WorldStatusChecker = new System.ComponentModel.BackgroundWorker(); + this.WorldStatusTimer = new System.Windows.Forms.Timer(this.components); + this.FullUpdateWorker = new System.ComponentModel.BackgroundWorker(); + this.Panels.SuspendLayout(); + this.RepoPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.TestmergeSelector)).BeginInit(); + this.BYONDPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.MinorVersionNumeric)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.MajorVersionNumeric)).BeginInit(); + this.ServerPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.NudgePortSelector)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.PortSelector)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.ServerTestmergeInput)).BeginInit(); + this.ChatPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.ChatPortSelector)).BeginInit(); + this.ChatProviderSelectorPanel.SuspendLayout(); + this.ConfigPanel.SuspendLayout(); + this.ConfigPanels.SuspendLayout(); + this.AdminsPanel.SuspendLayout(); + this.SuspendLayout(); + // + // Panels + // + this.Panels.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Panels.Controls.Add(this.RepoPanel); + this.Panels.Controls.Add(this.BYONDPanel); + this.Panels.Controls.Add(this.ServerPanel); + this.Panels.Controls.Add(this.ChatPanel); + this.Panels.Controls.Add(this.ConfigPanel); + this.Panels.Location = new System.Drawing.Point(12, 12); + this.Panels.Name = "Panels"; + this.Panels.SelectedIndex = 0; + this.Panels.Size = new System.Drawing.Size(876, 392); + this.Panels.TabIndex = 3; + // + // RepoPanel + // + this.RepoPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.RepoPanel.Controls.Add(this.BackupTagsList); + this.RepoPanel.Controls.Add(this.ResetRemote); + this.RepoPanel.Controls.Add(this.RecloneButton); + this.RepoPanel.Controls.Add(this.PythonPathText); + this.RepoPanel.Controls.Add(this.PythonPathLabel); + this.RepoPanel.Controls.Add(this.RepoGenChangelogButton); + this.RepoPanel.Controls.Add(this.TestmergeSelector); + this.RepoPanel.Controls.Add(this.TestMergeListLabel); + this.RepoPanel.Controls.Add(this.CurrentRevisionLabel); + this.RepoPanel.Controls.Add(this.RepoApplyButton); + this.RepoPanel.Controls.Add(this.RepoBranchTextBox); + this.RepoPanel.Controls.Add(this.RepoRemoteTextBox); + this.RepoPanel.Controls.Add(this.HardReset); + this.RepoPanel.Controls.Add(this.UpdateRepoButton); + this.RepoPanel.Controls.Add(this.MergePRButton); + this.RepoPanel.Controls.Add(this.IdentityLabel); + this.RepoPanel.Controls.Add(this.TestMergeListTitle); + this.RepoPanel.Controls.Add(this.RemoteNameTitle); + this.RepoPanel.Controls.Add(this.BranchNameTitle); + this.RepoPanel.Controls.Add(this.CurrentRevisionTitle); + this.RepoPanel.Controls.Add(this.CloneRepositoryButton); + this.RepoPanel.Controls.Add(this.RepoProgressBarLabel); + this.RepoPanel.Controls.Add(this.RepoProgressBar); + this.RepoPanel.Location = new System.Drawing.Point(4, 22); + this.RepoPanel.Name = "RepoPanel"; + this.RepoPanel.Padding = new System.Windows.Forms.Padding(3); + this.RepoPanel.Size = new System.Drawing.Size(868, 366); + this.RepoPanel.TabIndex = 0; + this.RepoPanel.Text = "Repository"; + // + // BackupTagsList + // + this.BackupTagsList.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.BackupTagsList.Items.AddRange(new object[] { + "None"}); + this.BackupTagsList.Location = new System.Drawing.Point(122, 142); + this.BackupTagsList.Name = "BackupTagsList"; + this.BackupTagsList.Size = new System.Drawing.Size(535, 95); + this.BackupTagsList.TabIndex = 34; + this.BackupTagsList.Visible = false; + // + // ResetRemote + // + this.ResetRemote.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ResetRemote.Location = new System.Drawing.Point(722, 81); + this.ResetRemote.Name = "ResetRemote"; + this.ResetRemote.Size = new System.Drawing.Size(140, 29); + this.ResetRemote.TabIndex = 33; + this.ResetRemote.Text = "Reset To Remote"; + this.ResetRemote.UseVisualStyleBackColor = true; + this.ResetRemote.Visible = false; + this.ResetRemote.Click += new System.EventHandler(this.ResetRemote_Click); + // + // RecloneButton + // + this.RecloneButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.RecloneButton.Location = new System.Drawing.Point(722, 212); + this.RecloneButton.Name = "RecloneButton"; + this.RecloneButton.Size = new System.Drawing.Size(140, 29); + this.RecloneButton.TabIndex = 32; + this.RecloneButton.Text = "Reclone"; + this.RecloneButton.UseVisualStyleBackColor = true; + this.RecloneButton.Visible = false; + this.RecloneButton.Click += new System.EventHandler(this.RecloneButton_Click); + // + // PythonPathText + // + this.PythonPathText.Location = new System.Drawing.Point(122, 112); + this.PythonPathText.Name = "PythonPathText"; + this.PythonPathText.Size = new System.Drawing.Size(535, 20); + this.PythonPathText.TabIndex = 31; + this.PythonPathText.Visible = false; + // + // PythonPathLabel + // + this.PythonPathLabel.AutoSize = true; + this.PythonPathLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.PythonPathLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.PythonPathLabel.Location = new System.Drawing.Point(6, 112); + this.PythonPathLabel.Name = "PythonPathLabel"; + this.PythonPathLabel.Size = new System.Drawing.Size(114, 18); + this.PythonPathLabel.TabIndex = 30; + this.PythonPathLabel.Text = "Python Path:"; + this.PythonPathLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.PythonPathLabel.Visible = false; + // + // RepoGenChangelogButton + // + this.RepoGenChangelogButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.RepoGenChangelogButton.Location = new System.Drawing.Point(722, 177); + this.RepoGenChangelogButton.Name = "RepoGenChangelogButton"; + this.RepoGenChangelogButton.Size = new System.Drawing.Size(140, 29); + this.RepoGenChangelogButton.TabIndex = 27; + this.RepoGenChangelogButton.Text = "Generate Changelog"; + this.RepoGenChangelogButton.UseVisualStyleBackColor = true; + this.RepoGenChangelogButton.Visible = false; + this.RepoGenChangelogButton.Click += new System.EventHandler(this.RepoGenChangelogButton_Click); + // + // TestmergeSelector + // + this.TestmergeSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.TestmergeSelector.Location = new System.Drawing.Point(722, 151); + this.TestmergeSelector.Maximum = new decimal(new int[] { + 1000000, + 0, + 0, + 0}); + this.TestmergeSelector.Name = "TestmergeSelector"; + this.TestmergeSelector.Size = new System.Drawing.Size(140, 20); + this.TestmergeSelector.TabIndex = 22; + this.TestmergeSelector.Visible = false; + // + // TestMergeListLabel + // + this.TestMergeListLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.TestMergeListLabel.HorizontalScrollbar = true; + this.TestMergeListLabel.Items.AddRange(new object[] { + "None"}); + this.TestMergeListLabel.Location = new System.Drawing.Point(122, 260); + this.TestMergeListLabel.Name = "TestMergeListLabel"; + this.TestMergeListLabel.Size = new System.Drawing.Size(535, 95); + this.TestMergeListLabel.TabIndex = 21; + this.TestMergeListLabel.Visible = false; + // + // CurrentRevisionLabel + // + this.CurrentRevisionLabel.AutoSize = true; + this.CurrentRevisionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.CurrentRevisionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.CurrentRevisionLabel.Location = new System.Drawing.Point(162, 14); + this.CurrentRevisionLabel.Name = "CurrentRevisionLabel"; + this.CurrentRevisionLabel.Size = new System.Drawing.Size(82, 18); + this.CurrentRevisionLabel.TabIndex = 20; + this.CurrentRevisionLabel.Text = "Unknown"; + this.CurrentRevisionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.CurrentRevisionLabel.Visible = false; + // + // RepoApplyButton + // + this.RepoApplyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.RepoApplyButton.Location = new System.Drawing.Point(722, 331); + this.RepoApplyButton.Name = "RepoApplyButton"; + this.RepoApplyButton.Size = new System.Drawing.Size(140, 29); + this.RepoApplyButton.TabIndex = 17; + this.RepoApplyButton.Text = "Apply Changes"; + this.RepoApplyButton.UseVisualStyleBackColor = true; + this.RepoApplyButton.Visible = false; + this.RepoApplyButton.Click += new System.EventHandler(this.RepoApplyButton_Click); + // + // RepoBranchTextBox + // + this.RepoBranchTextBox.Location = new System.Drawing.Point(122, 70); + this.RepoBranchTextBox.Name = "RepoBranchTextBox"; + this.RepoBranchTextBox.Size = new System.Drawing.Size(535, 20); + this.RepoBranchTextBox.TabIndex = 15; + this.RepoBranchTextBox.Visible = false; + // + // RepoRemoteTextBox + // + this.RepoRemoteTextBox.Location = new System.Drawing.Point(122, 44); + this.RepoRemoteTextBox.Name = "RepoRemoteTextBox"; + this.RepoRemoteTextBox.Size = new System.Drawing.Size(535, 20); + this.RepoRemoteTextBox.TabIndex = 14; + this.RepoRemoteTextBox.Visible = false; + // + // HardReset + // + this.HardReset.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.HardReset.Location = new System.Drawing.Point(722, 46); + this.HardReset.Name = "HardReset"; + this.HardReset.Size = new System.Drawing.Size(140, 29); + this.HardReset.TabIndex = 13; + this.HardReset.Text = "Reset To Origin Branch"; + this.HardReset.UseVisualStyleBackColor = true; + this.HardReset.Visible = false; + this.HardReset.Click += new System.EventHandler(this.HardReset_Click); + // + // UpdateRepoButton + // + this.UpdateRepoButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.UpdateRepoButton.Location = new System.Drawing.Point(722, 11); + this.UpdateRepoButton.Name = "UpdateRepoButton"; + this.UpdateRepoButton.Size = new System.Drawing.Size(140, 29); + this.UpdateRepoButton.TabIndex = 12; + this.UpdateRepoButton.Text = "Merge from Remote"; + this.UpdateRepoButton.UseVisualStyleBackColor = true; + this.UpdateRepoButton.Visible = false; + this.UpdateRepoButton.Click += new System.EventHandler(this.UpdateRepoButton_Click); + // + // MergePRButton + // + this.MergePRButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.MergePRButton.Location = new System.Drawing.Point(722, 116); + this.MergePRButton.Name = "MergePRButton"; + this.MergePRButton.Size = new System.Drawing.Size(140, 29); + this.MergePRButton.TabIndex = 11; + this.MergePRButton.Text = "Merge Pull Request"; + this.MergePRButton.UseVisualStyleBackColor = true; + this.MergePRButton.Visible = false; + this.MergePRButton.Click += new System.EventHandler(this.TestMergeButton_Click); + // + // IdentityLabel + // + this.IdentityLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.IdentityLabel.AutoSize = true; + this.IdentityLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.IdentityLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.IdentityLabel.Location = new System.Drawing.Point(6, 142); + this.IdentityLabel.Name = "IdentityLabel"; + this.IdentityLabel.Size = new System.Drawing.Size(116, 18); + this.IdentityLabel.TabIndex = 8; + this.IdentityLabel.Text = "Backup Tags:"; + this.IdentityLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.IdentityLabel.Visible = false; + // + // TestMergeListTitle + // + this.TestMergeListTitle.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.TestMergeListTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.TestMergeListTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.TestMergeListTitle.Location = new System.Drawing.Point(6, 260); + this.TestMergeListTitle.Name = "TestMergeListTitle"; + this.TestMergeListTitle.Size = new System.Drawing.Size(110, 41); + this.TestMergeListTitle.TabIndex = 6; + this.TestMergeListTitle.Text = "Active Test Merges:"; + this.TestMergeListTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.TestMergeListTitle.Visible = false; + // + // RemoteNameTitle + // + this.RemoteNameTitle.AutoSize = true; + this.RemoteNameTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.RemoteNameTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.RemoteNameTitle.Location = new System.Drawing.Point(6, 46); + this.RemoteNameTitle.Name = "RemoteNameTitle"; + this.RemoteNameTitle.Size = new System.Drawing.Size(78, 18); + this.RemoteNameTitle.TabIndex = 5; + this.RemoteNameTitle.Text = "Remote:"; + this.RemoteNameTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.RemoteNameTitle.Visible = false; + // + // BranchNameTitle + // + this.BranchNameTitle.AutoSize = true; + this.BranchNameTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.BranchNameTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.BranchNameTitle.Location = new System.Drawing.Point(6, 72); + this.BranchNameTitle.Name = "BranchNameTitle"; + this.BranchNameTitle.Size = new System.Drawing.Size(70, 18); + this.BranchNameTitle.TabIndex = 4; + this.BranchNameTitle.Text = "Branch:"; + this.BranchNameTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.BranchNameTitle.Visible = false; + // + // CurrentRevisionTitle + // + this.CurrentRevisionTitle.AutoSize = true; + this.CurrentRevisionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.CurrentRevisionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.CurrentRevisionTitle.Location = new System.Drawing.Point(6, 14); + this.CurrentRevisionTitle.Name = "CurrentRevisionTitle"; + this.CurrentRevisionTitle.Size = new System.Drawing.Size(150, 18); + this.CurrentRevisionTitle.TabIndex = 3; + this.CurrentRevisionTitle.Text = "Current Revision:"; + this.CurrentRevisionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.CurrentRevisionTitle.Visible = false; + // + // CloneRepositoryButton + // + this.CloneRepositoryButton.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.CloneRepositoryButton.Location = new System.Drawing.Point(311, 191); + this.CloneRepositoryButton.Name = "CloneRepositoryButton"; + this.CloneRepositoryButton.Size = new System.Drawing.Size(229, 34); + this.CloneRepositoryButton.TabIndex = 2; + this.CloneRepositoryButton.Text = "Clone Repository"; + this.CloneRepositoryButton.UseVisualStyleBackColor = true; + this.CloneRepositoryButton.Visible = false; + this.CloneRepositoryButton.Click += new System.EventHandler(this.CloneRepositoryButton_Click); + // + // RepoProgressBarLabel + // + this.RepoProgressBarLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.RepoProgressBarLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.RepoProgressBarLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.RepoProgressBarLabel.Location = new System.Drawing.Point(184, 142); + this.RepoProgressBarLabel.Name = "RepoProgressBarLabel"; + this.RepoProgressBarLabel.Size = new System.Drawing.Size(499, 46); + this.RepoProgressBarLabel.TabIndex = 1; + this.RepoProgressBarLabel.Text = "Searching for Repository..."; + this.RepoProgressBarLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // RepoProgressBar + // + this.RepoProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.RepoProgressBar.Location = new System.Drawing.Point(184, 191); + this.RepoProgressBar.Name = "RepoProgressBar"; + this.RepoProgressBar.Size = new System.Drawing.Size(499, 23); + this.RepoProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee; + this.RepoProgressBar.TabIndex = 0; + // + // BYONDPanel + // + this.BYONDPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.BYONDPanel.Controls.Add(this.LatestVersionLabel); + this.BYONDPanel.Controls.Add(this.LatestVersionTitle); + this.BYONDPanel.Controls.Add(this.StagedVersionLabel); + this.BYONDPanel.Controls.Add(this.StagedVersionTitle); + this.BYONDPanel.Controls.Add(this.StatusLabel); + this.BYONDPanel.Controls.Add(this.VersionLabel); + this.BYONDPanel.Controls.Add(this.VersionTitle); + this.BYONDPanel.Controls.Add(this.MinorVersionLabel); + this.BYONDPanel.Controls.Add(this.MajorVersionLabel); + this.BYONDPanel.Controls.Add(this.UpdateButton); + this.BYONDPanel.Controls.Add(this.MinorVersionNumeric); + this.BYONDPanel.Controls.Add(this.MajorVersionNumeric); + this.BYONDPanel.Controls.Add(this.UpdateProgressBar); + this.BYONDPanel.Location = new System.Drawing.Point(4, 22); + this.BYONDPanel.Name = "BYONDPanel"; + this.BYONDPanel.Size = new System.Drawing.Size(868, 366); + this.BYONDPanel.TabIndex = 1; + this.BYONDPanel.Text = "BYOND"; + // + // LatestVersionLabel + // + this.LatestVersionLabel.AutoSize = true; + this.LatestVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.LatestVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.LatestVersionLabel.Location = new System.Drawing.Point(425, 93); + this.LatestVersionLabel.Name = "LatestVersionLabel"; + this.LatestVersionLabel.Size = new System.Drawing.Size(82, 18); + this.LatestVersionLabel.TabIndex = 13; + this.LatestVersionLabel.Text = "Unknown"; + this.LatestVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // LatestVersionTitle + // + this.LatestVersionTitle.AutoSize = true; + this.LatestVersionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.LatestVersionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.LatestVersionTitle.Location = new System.Drawing.Point(286, 93); + this.LatestVersionTitle.Name = "LatestVersionTitle"; + this.LatestVersionTitle.Size = new System.Drawing.Size(133, 18); + this.LatestVersionTitle.TabIndex = 12; + this.LatestVersionTitle.Text = "Latest Version:"; + this.LatestVersionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // StagedVersionLabel + // + this.StagedVersionLabel.AutoSize = true; + this.StagedVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.StagedVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.StagedVersionLabel.Location = new System.Drawing.Point(425, 131); + this.StagedVersionLabel.Name = "StagedVersionLabel"; + this.StagedVersionLabel.Size = new System.Drawing.Size(82, 18); + this.StagedVersionLabel.TabIndex = 11; + this.StagedVersionLabel.Text = "Unknown"; + this.StagedVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.StagedVersionLabel.Visible = false; + // + // StagedVersionTitle + // + this.StagedVersionTitle.AutoSize = true; + this.StagedVersionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.StagedVersionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.StagedVersionTitle.Location = new System.Drawing.Point(282, 131); + this.StagedVersionTitle.Name = "StagedVersionTitle"; + this.StagedVersionTitle.Size = new System.Drawing.Size(137, 18); + this.StagedVersionTitle.TabIndex = 10; + this.StagedVersionTitle.Text = "Staged version:"; + this.StagedVersionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.StagedVersionTitle.Visible = false; + // + // StatusLabel + // + this.StatusLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.StatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.StatusLabel.Location = new System.Drawing.Point(303, 320); + this.StatusLabel.Name = "StatusLabel"; + this.StatusLabel.Size = new System.Drawing.Size(253, 37); + this.StatusLabel.TabIndex = 9; + this.StatusLabel.Text = "Idle"; + this.StatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // VersionLabel + // + this.VersionLabel.AutoSize = true; + this.VersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.VersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.VersionLabel.Location = new System.Drawing.Point(425, 56); + this.VersionLabel.Name = "VersionLabel"; + this.VersionLabel.Size = new System.Drawing.Size(82, 18); + this.VersionLabel.TabIndex = 8; + this.VersionLabel.Text = "Unknown"; + this.VersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // VersionTitle + // + this.VersionTitle.AutoSize = true; + this.VersionTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.VersionTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.VersionTitle.Location = new System.Drawing.Point(265, 56); + this.VersionTitle.Name = "VersionTitle"; + this.VersionTitle.Size = new System.Drawing.Size(154, 18); + this.VersionTitle.TabIndex = 7; + this.VersionTitle.Text = "Installed Version:"; + this.VersionTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // MinorVersionLabel + // + this.MinorVersionLabel.AutoSize = true; + this.MinorVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.MinorVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.MinorVersionLabel.Location = new System.Drawing.Point(521, 180); + this.MinorVersionLabel.Name = "MinorVersionLabel"; + this.MinorVersionLabel.Size = new System.Drawing.Size(59, 18); + this.MinorVersionLabel.TabIndex = 6; + this.MinorVersionLabel.Text = "Minor:"; + this.MinorVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // MajorVersionLabel + // + this.MajorVersionLabel.AutoSize = true; + this.MajorVersionLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.MajorVersionLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.MajorVersionLabel.Location = new System.Drawing.Point(276, 180); + this.MajorVersionLabel.Name = "MajorVersionLabel"; + this.MajorVersionLabel.Size = new System.Drawing.Size(60, 18); + this.MajorVersionLabel.TabIndex = 5; + this.MajorVersionLabel.Text = "Major:"; + this.MajorVersionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // UpdateButton + // + this.UpdateButton.Location = new System.Drawing.Point(372, 252); + this.UpdateButton.Name = "UpdateButton"; + this.UpdateButton.Size = new System.Drawing.Size(118, 28); + this.UpdateButton.TabIndex = 3; + this.UpdateButton.Text = "Update"; + this.UpdateButton.UseVisualStyleBackColor = true; + this.UpdateButton.Click += new System.EventHandler(this.UpdateButton_Click); + // + // MinorVersionNumeric + // + this.MinorVersionNumeric.Location = new System.Drawing.Point(490, 210); + this.MinorVersionNumeric.Maximum = new decimal(new int[] { + 10000, + 0, + 0, + 0}); + this.MinorVersionNumeric.Name = "MinorVersionNumeric"; + this.MinorVersionNumeric.Size = new System.Drawing.Size(120, 20); + this.MinorVersionNumeric.TabIndex = 2; + this.MinorVersionNumeric.Value = new decimal(new int[] { + 1381, + 0, + 0, + 0}); + // + // MajorVersionNumeric + // + this.MajorVersionNumeric.Location = new System.Drawing.Point(245, 210); + this.MajorVersionNumeric.Maximum = new decimal(new int[] { + 1000, + 0, + 0, + 0}); + this.MajorVersionNumeric.Name = "MajorVersionNumeric"; + this.MajorVersionNumeric.Size = new System.Drawing.Size(120, 20); + this.MajorVersionNumeric.TabIndex = 1; + this.MajorVersionNumeric.Value = new decimal(new int[] { + 511, + 0, + 0, + 0}); + // + // UpdateProgressBar + // + this.UpdateProgressBar.Location = new System.Drawing.Point(107, 286); + this.UpdateProgressBar.MarqueeAnimationSpeed = 50; + this.UpdateProgressBar.Name = "UpdateProgressBar"; + this.UpdateProgressBar.Size = new System.Drawing.Size(650, 31); + this.UpdateProgressBar.TabIndex = 0; + // + // ServerPanel + // + this.ServerPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.ServerPanel.Controls.Add(this.VisibilitySelector); + this.ServerPanel.Controls.Add(this.VisibilityTitle); + this.ServerPanel.Controls.Add(this.SecuritySelector); + this.ServerPanel.Controls.Add(this.SecurityTitle); + this.ServerPanel.Controls.Add(this.ResetTestmerge); + this.ServerPanel.Controls.Add(this.NudgePortSelector); + this.ServerPanel.Controls.Add(this.NudgePortLabel); + this.ServerPanel.Controls.Add(this.ServerPathLabel); + this.ServerPanel.Controls.Add(this.ServerPathTextbox); + this.ServerPanel.Controls.Add(this.CompileCancelButton); + this.ServerPanel.Controls.Add(this.ProjectPathLabel); + this.ServerPanel.Controls.Add(this.projectNameText); + this.ServerPanel.Controls.Add(this.PortLabel); + this.ServerPanel.Controls.Add(this.PortSelector); + this.ServerPanel.Controls.Add(this.ServerPRLabel); + this.ServerPanel.Controls.Add(this.ServerTestmergeInput); + this.ServerPanel.Controls.Add(this.TestmergeButton); + this.ServerPanel.Controls.Add(this.UpdateTestmergeButton); + this.ServerPanel.Controls.Add(this.UpdateMergeButton); + this.ServerPanel.Controls.Add(this.UpdateHardButton); + this.ServerPanel.Controls.Add(this.ServerGRestartButton); + this.ServerPanel.Controls.Add(this.ServerGStopButton); + this.ServerPanel.Controls.Add(this.ServerRestartButton); + this.ServerPanel.Controls.Add(this.ServerStopButton); + this.ServerPanel.Controls.Add(this.ServerStartButton); + this.ServerPanel.Controls.Add(this.AutostartCheckbox); + this.ServerPanel.Controls.Add(this.CompilerStatusLabel); + this.ServerPanel.Controls.Add(this.CompilerLabel); + this.ServerPanel.Controls.Add(this.compileButton); + this.ServerPanel.Controls.Add(this.initializeButton); + this.ServerPanel.Controls.Add(this.compilerProgressBar); + this.ServerPanel.Controls.Add(this.ServerStatusLabel); + this.ServerPanel.Controls.Add(this.ServerStatusTitle); + this.ServerPanel.Location = new System.Drawing.Point(4, 22); + this.ServerPanel.Name = "ServerPanel"; + this.ServerPanel.Padding = new System.Windows.Forms.Padding(3); + this.ServerPanel.Size = new System.Drawing.Size(868, 366); + this.ServerPanel.TabIndex = 2; + this.ServerPanel.Text = "Server"; + // + // VisibilitySelector + // + this.VisibilitySelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.VisibilitySelector.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.VisibilitySelector.FormattingEnabled = true; + this.VisibilitySelector.Items.AddRange(new object[] { + "Public", + "Private", + "Invisible"}); + this.VisibilitySelector.Location = new System.Drawing.Point(566, 136); + this.VisibilitySelector.Name = "VisibilitySelector"; + this.VisibilitySelector.Size = new System.Drawing.Size(121, 21); + this.VisibilitySelector.TabIndex = 40; + this.VisibilitySelector.SelectedIndexChanged += new System.EventHandler(this.VisibilitySelector_SelectedIndexChanged); + // + // VisibilityTitle + // + this.VisibilityTitle.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.VisibilityTitle.AutoSize = true; + this.VisibilityTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.VisibilityTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.VisibilityTitle.Location = new System.Drawing.Point(474, 139); + this.VisibilityTitle.Name = "VisibilityTitle"; + this.VisibilityTitle.Size = new System.Drawing.Size(86, 18); + this.VisibilityTitle.TabIndex = 39; + this.VisibilityTitle.Text = "Visibility:"; + this.VisibilityTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // SecuritySelector + // + this.SecuritySelector.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.SecuritySelector.FormattingEnabled = true; + this.SecuritySelector.Items.AddRange(new object[] { + "Trusted", + "Safe", + "Ultrasafe"}); + this.SecuritySelector.Location = new System.Drawing.Point(136, 136); + this.SecuritySelector.Name = "SecuritySelector"; + this.SecuritySelector.Size = new System.Drawing.Size(121, 21); + this.SecuritySelector.TabIndex = 38; + this.SecuritySelector.SelectedIndexChanged += new System.EventHandler(this.SecuritySelector_SelectedIndexChanged); + // + // SecurityTitle + // + this.SecurityTitle.AutoSize = true; + this.SecurityTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.SecurityTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.SecurityTitle.Location = new System.Drawing.Point(44, 139); + this.SecurityTitle.Name = "SecurityTitle"; + this.SecurityTitle.Size = new System.Drawing.Size(80, 18); + this.SecurityTitle.TabIndex = 37; + this.SecurityTitle.Text = "Security:"; + this.SecurityTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ResetTestmerge + // + this.ResetTestmerge.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.ResetTestmerge.Location = new System.Drawing.Point(602, 95); + this.ResetTestmerge.Name = "ResetTestmerge"; + this.ResetTestmerge.Size = new System.Drawing.Size(142, 28); + this.ResetTestmerge.TabIndex = 36; + this.ResetTestmerge.Text = "Reset and Recompile"; + this.ResetTestmerge.UseVisualStyleBackColor = true; + this.ResetTestmerge.Click += new System.EventHandler(this.ResetTestmerge_Click); + // + // NudgePortSelector + // + this.NudgePortSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.NudgePortSelector.Location = new System.Drawing.Point(806, 60); + this.NudgePortSelector.Maximum = new decimal(new int[] { + 65535, + 0, + 0, + 0}); + this.NudgePortSelector.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.NudgePortSelector.Name = "NudgePortSelector"; + this.NudgePortSelector.Size = new System.Drawing.Size(62, 20); + this.NudgePortSelector.TabIndex = 34; + this.NudgePortSelector.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.NudgePortSelector.ValueChanged += new System.EventHandler(this.NudgePortSelector_ValueChanged); + // + // NudgePortLabel + // + this.NudgePortLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.NudgePortLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.NudgePortLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.NudgePortLabel.Location = new System.Drawing.Point(745, 45); + this.NudgePortLabel.Name = "NudgePortLabel"; + this.NudgePortLabel.Size = new System.Drawing.Size(61, 44); + this.NudgePortLabel.TabIndex = 35; + this.NudgePortLabel.Text = "Nudge Port:"; + this.NudgePortLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ServerPathLabel + // + this.ServerPathLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.ServerPathLabel.AutoSize = true; + this.ServerPathLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ServerPathLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ServerPathLabel.Location = new System.Drawing.Point(15, 165); + this.ServerPathLabel.Name = "ServerPathLabel"; + this.ServerPathLabel.Size = new System.Drawing.Size(109, 18); + this.ServerPathLabel.TabIndex = 33; + this.ServerPathLabel.Text = "Server Path:"; + this.ServerPathLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ServerPathTextbox + // + this.ServerPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.ServerPathTextbox.Location = new System.Drawing.Point(136, 163); + this.ServerPathTextbox.Name = "ServerPathTextbox"; + this.ServerPathTextbox.Size = new System.Drawing.Size(296, 20); + this.ServerPathTextbox.TabIndex = 32; + // + // CompileCancelButton + // + this.CompileCancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CompileCancelButton.Enabled = false; + this.CompileCancelButton.Location = new System.Drawing.Point(737, 302); + this.CompileCancelButton.Name = "CompileCancelButton"; + this.CompileCancelButton.Size = new System.Drawing.Size(69, 31); + this.CompileCancelButton.TabIndex = 31; + this.CompileCancelButton.Text = "Cancel"; + this.CompileCancelButton.UseVisualStyleBackColor = true; + this.CompileCancelButton.Click += new System.EventHandler(this.CompileCancelButton_Click); + // + // ProjectPathLabel + // + this.ProjectPathLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.ProjectPathLabel.AutoSize = true; + this.ProjectPathLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ProjectPathLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ProjectPathLabel.Location = new System.Drawing.Point(445, 165); + this.ProjectPathLabel.Name = "ProjectPathLabel"; + this.ProjectPathLabel.Size = new System.Drawing.Size(115, 18); + this.ProjectPathLabel.TabIndex = 30; + this.ProjectPathLabel.Text = "Project Path:"; + this.ProjectPathLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // projectNameText + // + this.projectNameText.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.projectNameText.Location = new System.Drawing.Point(566, 163); + this.projectNameText.Name = "projectNameText"; + this.projectNameText.Size = new System.Drawing.Size(296, 20); + this.projectNameText.TabIndex = 29; + // + // PortLabel + // + this.PortLabel.AutoSize = true; + this.PortLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.PortLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.PortLabel.Location = new System.Drawing.Point(4, 60); + this.PortLabel.Name = "PortLabel"; + this.PortLabel.Size = new System.Drawing.Size(48, 18); + this.PortLabel.TabIndex = 28; + this.PortLabel.Text = "Port:"; + this.PortLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // PortSelector + // + this.PortSelector.Location = new System.Drawing.Point(59, 60); + this.PortSelector.Maximum = new decimal(new int[] { + 65535, + 0, + 0, + 0}); + this.PortSelector.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.PortSelector.Name = "PortSelector"; + this.PortSelector.Size = new System.Drawing.Size(60, 20); + this.PortSelector.TabIndex = 27; + this.PortSelector.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.PortSelector.ValueChanged += new System.EventHandler(this.PortSelector_ValueChanged); + // + // ServerPRLabel + // + this.ServerPRLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ServerPRLabel.AutoSize = true; + this.ServerPRLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ServerPRLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ServerPRLabel.Location = new System.Drawing.Point(745, 101); + this.ServerPRLabel.Name = "ServerPRLabel"; + this.ServerPRLabel.Size = new System.Drawing.Size(49, 18); + this.ServerPRLabel.TabIndex = 26; + this.ServerPRLabel.Text = "PR#:"; + this.ServerPRLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ServerTestmergeInput + // + this.ServerTestmergeInput.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ServerTestmergeInput.Location = new System.Drawing.Point(806, 99); + this.ServerTestmergeInput.Maximum = new decimal(new int[] { + 1000000, + 0, + 0, + 0}); + this.ServerTestmergeInput.Name = "ServerTestmergeInput"; + this.ServerTestmergeInput.Size = new System.Drawing.Size(62, 20); + this.ServerTestmergeInput.TabIndex = 25; + // + // TestmergeButton + // + this.TestmergeButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.TestmergeButton.Location = new System.Drawing.Point(454, 95); + this.TestmergeButton.Name = "TestmergeButton"; + this.TestmergeButton.Size = new System.Drawing.Size(142, 28); + this.TestmergeButton.TabIndex = 24; + this.TestmergeButton.Text = "Testmerge"; + this.TestmergeButton.UseVisualStyleBackColor = true; + this.TestmergeButton.Click += new System.EventHandler(this.TestmergeButton_Click); + // + // UpdateTestmergeButton + // + this.UpdateTestmergeButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.UpdateTestmergeButton.Location = new System.Drawing.Point(306, 95); + this.UpdateTestmergeButton.Name = "UpdateTestmergeButton"; + this.UpdateTestmergeButton.Size = new System.Drawing.Size(142, 28); + this.UpdateTestmergeButton.TabIndex = 23; + this.UpdateTestmergeButton.Text = "Update and Testmerge"; + this.UpdateTestmergeButton.UseVisualStyleBackColor = true; + this.UpdateTestmergeButton.Click += new System.EventHandler(this.UpdateTestmergeButton_Click); + // + // UpdateMergeButton + // + this.UpdateMergeButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.UpdateMergeButton.Location = new System.Drawing.Point(158, 95); + this.UpdateMergeButton.Name = "UpdateMergeButton"; + this.UpdateMergeButton.Size = new System.Drawing.Size(142, 28); + this.UpdateMergeButton.TabIndex = 22; + this.UpdateMergeButton.Text = "Update (KeepTestmerge)"; + this.UpdateMergeButton.UseVisualStyleBackColor = true; + this.UpdateMergeButton.Click += new System.EventHandler(this.UpdateMergeButton_Click); + // + // UpdateHardButton + // + this.UpdateHardButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.UpdateHardButton.Location = new System.Drawing.Point(7, 95); + this.UpdateHardButton.Name = "UpdateHardButton"; + this.UpdateHardButton.Size = new System.Drawing.Size(145, 28); + this.UpdateHardButton.TabIndex = 21; + this.UpdateHardButton.Text = "Update (Reset Testmerge)"; + this.UpdateHardButton.UseVisualStyleBackColor = true; + this.UpdateHardButton.Click += new System.EventHandler(this.UpdateHardButton_Click); + // + // ServerGRestartButton + // + this.ServerGRestartButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.ServerGRestartButton.Location = new System.Drawing.Point(626, 54); + this.ServerGRestartButton.Name = "ServerGRestartButton"; + this.ServerGRestartButton.Size = new System.Drawing.Size(118, 28); + this.ServerGRestartButton.TabIndex = 20; + this.ServerGRestartButton.Text = "Graceful Restart"; + this.ServerGRestartButton.UseVisualStyleBackColor = true; + this.ServerGRestartButton.Click += new System.EventHandler(this.ServerGRestartButton_Click); + // + // ServerGStopButton + // + this.ServerGStopButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.ServerGStopButton.Location = new System.Drawing.Point(502, 54); + this.ServerGStopButton.Name = "ServerGStopButton"; + this.ServerGStopButton.Size = new System.Drawing.Size(118, 28); + this.ServerGStopButton.TabIndex = 19; + this.ServerGStopButton.Text = "Graceful Stop"; + this.ServerGStopButton.UseVisualStyleBackColor = true; + this.ServerGStopButton.Click += new System.EventHandler(this.ServerGStopButton_Click); + // + // ServerRestartButton + // + this.ServerRestartButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.ServerRestartButton.Location = new System.Drawing.Point(378, 54); + this.ServerRestartButton.Name = "ServerRestartButton"; + this.ServerRestartButton.Size = new System.Drawing.Size(118, 28); + this.ServerRestartButton.TabIndex = 18; + this.ServerRestartButton.Text = "Restart"; + this.ServerRestartButton.UseVisualStyleBackColor = true; + this.ServerRestartButton.Click += new System.EventHandler(this.ServerRestartButton_Click); + // + // ServerStopButton + // + this.ServerStopButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.ServerStopButton.Location = new System.Drawing.Point(254, 54); + this.ServerStopButton.Name = "ServerStopButton"; + this.ServerStopButton.Size = new System.Drawing.Size(118, 28); + this.ServerStopButton.TabIndex = 17; + this.ServerStopButton.Text = "Stop"; + this.ServerStopButton.UseVisualStyleBackColor = true; + this.ServerStopButton.Click += new System.EventHandler(this.ServerStopButton_Click); + // + // ServerStartButton + // + this.ServerStartButton.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.ServerStartButton.Location = new System.Drawing.Point(130, 54); + this.ServerStartButton.Name = "ServerStartButton"; + this.ServerStartButton.Size = new System.Drawing.Size(118, 28); + this.ServerStartButton.TabIndex = 16; + this.ServerStartButton.Text = "Start"; + this.ServerStartButton.UseVisualStyleBackColor = true; + this.ServerStartButton.Click += new System.EventHandler(this.ServerStartButton_Click); + // + // AutostartCheckbox + // + this.AutostartCheckbox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.AutostartCheckbox.AutoSize = true; + this.AutostartCheckbox.Font = new System.Drawing.Font("Verdana", 12F); + this.AutostartCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.AutostartCheckbox.Location = new System.Drawing.Point(758, 17); + this.AutostartCheckbox.Name = "AutostartCheckbox"; + this.AutostartCheckbox.Size = new System.Drawing.Size(104, 22); + this.AutostartCheckbox.TabIndex = 15; + this.AutostartCheckbox.Text = "Autostart"; + this.AutostartCheckbox.UseVisualStyleBackColor = true; + this.AutostartCheckbox.CheckedChanged += new System.EventHandler(this.AutostartCheckbox_CheckedChanged); + // + // CompilerStatusLabel + // + this.CompilerStatusLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.CompilerStatusLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.CompilerStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.CompilerStatusLabel.Location = new System.Drawing.Point(110, 271); + this.CompilerStatusLabel.Name = "CompilerStatusLabel"; + this.CompilerStatusLabel.Size = new System.Drawing.Size(618, 28); + this.CompilerStatusLabel.TabIndex = 14; + this.CompilerStatusLabel.Text = "Idle"; + this.CompilerStatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // CompilerLabel + // + this.CompilerLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.CompilerLabel.AutoSize = true; + this.CompilerLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.CompilerLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.CompilerLabel.Location = new System.Drawing.Point(379, 203); + this.CompilerLabel.Name = "CompilerLabel"; + this.CompilerLabel.Size = new System.Drawing.Size(80, 18); + this.CompilerLabel.TabIndex = 13; + this.CompilerLabel.Text = "Compiler"; + this.CompilerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // compileButton + // + this.compileButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + this.compileButton.Enabled = false; + this.compileButton.Location = new System.Drawing.Point(456, 240); + this.compileButton.Name = "compileButton"; + this.compileButton.Size = new System.Drawing.Size(159, 28); + this.compileButton.TabIndex = 12; + this.compileButton.Text = "Copy from Repo and Compile"; + this.compileButton.UseVisualStyleBackColor = true; + this.compileButton.Click += new System.EventHandler(this.CompileButton_Click); + // + // initializeButton + // + this.initializeButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + this.initializeButton.Enabled = false; + this.initializeButton.Location = new System.Drawing.Point(219, 240); + this.initializeButton.Name = "initializeButton"; + this.initializeButton.Size = new System.Drawing.Size(159, 28); + this.initializeButton.TabIndex = 11; + this.initializeButton.Text = "Initialize Game Folders"; + this.initializeButton.UseVisualStyleBackColor = true; + this.initializeButton.Click += new System.EventHandler(this.InitializeButton_Click); + // + // compilerProgressBar + // + this.compilerProgressBar.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.compilerProgressBar.Location = new System.Drawing.Point(110, 302); + this.compilerProgressBar.MarqueeAnimationSpeed = 50; + this.compilerProgressBar.Name = "compilerProgressBar"; + this.compilerProgressBar.Size = new System.Drawing.Size(618, 31); + this.compilerProgressBar.TabIndex = 10; + // + // ServerStatusLabel + // + this.ServerStatusLabel.AutoSize = true; + this.ServerStatusLabel.Font = new System.Drawing.Font("Verdana", 10F); + this.ServerStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ServerStatusLabel.Location = new System.Drawing.Point(136, 19); + this.ServerStatusLabel.Name = "ServerStatusLabel"; + this.ServerStatusLabel.Size = new System.Drawing.Size(73, 17); + this.ServerStatusLabel.TabIndex = 9; + this.ServerStatusLabel.Text = "Unknown"; + this.ServerStatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ServerStatusTitle + // + this.ServerStatusTitle.AutoSize = true; + this.ServerStatusTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ServerStatusTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ServerStatusTitle.Location = new System.Drawing.Point(15, 17); + this.ServerStatusTitle.Name = "ServerStatusTitle"; + this.ServerStatusTitle.Size = new System.Drawing.Size(125, 18); + this.ServerStatusTitle.TabIndex = 8; + this.ServerStatusTitle.Text = "Server Status:"; + this.ServerStatusTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatPanel + // + this.ChatPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.ChatPanel.Controls.Add(this.ChatRefreshButton); + this.ChatPanel.Controls.Add(this.ChatNicknameText); + this.ChatPanel.Controls.Add(this.ChatNicknameTitle); + this.ChatPanel.Controls.Add(this.ChatPortSelector); + this.ChatPanel.Controls.Add(this.ChatPortTitle); + this.ChatPanel.Controls.Add(this.ChatServerText); + this.ChatPanel.Controls.Add(this.ChatServerTitle); + this.ChatPanel.Controls.Add(this.ChatApplyButton); + this.ChatPanel.Controls.Add(this.AuthField2Title); + this.ChatPanel.Controls.Add(this.AuthField1Title); + this.ChatPanel.Controls.Add(this.AuthField2); + this.ChatPanel.Controls.Add(this.AuthField1); + this.ChatPanel.Controls.Add(this.AdminChannelTitle); + this.ChatPanel.Controls.Add(this.AdminChannelText); + this.ChatPanel.Controls.Add(this.ChatReconnectButton); + this.ChatPanel.Controls.Add(this.ChatStatusLabel); + this.ChatPanel.Controls.Add(this.ChatStatusTitle); + this.ChatPanel.Controls.Add(this.ChatEnabledCheckbox); + this.ChatPanel.Controls.Add(this.ChatProviderSelectorPanel); + this.ChatPanel.Controls.Add(this.ChatProviderTitle); + this.ChatPanel.Controls.Add(this.ChannelsTitle); + this.ChatPanel.Controls.Add(this.ChatAdminsTextBox); + this.ChatPanel.Controls.Add(this.ChatAdminsTitle); + this.ChatPanel.Controls.Add(this.ChatChannelsTextBox); + this.ChatPanel.Location = new System.Drawing.Point(4, 22); + this.ChatPanel.Name = "ChatPanel"; + this.ChatPanel.Padding = new System.Windows.Forms.Padding(3); + this.ChatPanel.Size = new System.Drawing.Size(868, 366); + this.ChatPanel.TabIndex = 4; + this.ChatPanel.Text = "Chat"; + // + // ChatRefreshButton + // + this.ChatRefreshButton.Location = new System.Drawing.Point(174, 335); + this.ChatRefreshButton.Name = "ChatRefreshButton"; + this.ChatRefreshButton.Size = new System.Drawing.Size(112, 25); + this.ChatRefreshButton.TabIndex = 32; + this.ChatRefreshButton.Text = "Refresh"; + this.ChatRefreshButton.UseVisualStyleBackColor = true; + this.ChatRefreshButton.Click += new System.EventHandler(this.ChatRefreshButton_Click); + // + // ChatNicknameText + // + this.ChatNicknameText.Location = new System.Drawing.Point(174, 251); + this.ChatNicknameText.Name = "ChatNicknameText"; + this.ChatNicknameText.Size = new System.Drawing.Size(151, 20); + this.ChatNicknameText.TabIndex = 31; + // + // ChatNicknameTitle + // + this.ChatNicknameTitle.AutoSize = true; + this.ChatNicknameTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChatNicknameTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatNicknameTitle.Location = new System.Drawing.Point(30, 250); + this.ChatNicknameTitle.Name = "ChatNicknameTitle"; + this.ChatNicknameTitle.Size = new System.Drawing.Size(94, 18); + this.ChatNicknameTitle.TabIndex = 30; + this.ChatNicknameTitle.Text = "Nickname:"; + this.ChatNicknameTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatPortSelector + // + this.ChatPortSelector.Location = new System.Drawing.Point(174, 225); + this.ChatPortSelector.Maximum = new decimal(new int[] { + 65535, + 0, + 0, + 0}); + this.ChatPortSelector.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.ChatPortSelector.Name = "ChatPortSelector"; + this.ChatPortSelector.Size = new System.Drawing.Size(151, 20); + this.ChatPortSelector.TabIndex = 29; + this.ChatPortSelector.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + // + // ChatPortTitle + // + this.ChatPortTitle.AutoSize = true; + this.ChatPortTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChatPortTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatPortTitle.Location = new System.Drawing.Point(30, 227); + this.ChatPortTitle.Name = "ChatPortTitle"; + this.ChatPortTitle.Size = new System.Drawing.Size(48, 18); + this.ChatPortTitle.TabIndex = 28; + this.ChatPortTitle.Text = "Port:"; + this.ChatPortTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatServerText + // + this.ChatServerText.Location = new System.Drawing.Point(174, 199); + this.ChatServerText.Name = "ChatServerText"; + this.ChatServerText.Size = new System.Drawing.Size(151, 20); + this.ChatServerText.TabIndex = 27; + // + // ChatServerTitle + // + this.ChatServerTitle.AutoSize = true; + this.ChatServerTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChatServerTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatServerTitle.Location = new System.Drawing.Point(30, 201); + this.ChatServerTitle.Name = "ChatServerTitle"; + this.ChatServerTitle.Size = new System.Drawing.Size(66, 18); + this.ChatServerTitle.TabIndex = 26; + this.ChatServerTitle.Text = "Server:"; + this.ChatServerTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatApplyButton + // + this.ChatApplyButton.Location = new System.Drawing.Point(56, 335); + this.ChatApplyButton.Name = "ChatApplyButton"; + this.ChatApplyButton.Size = new System.Drawing.Size(112, 25); + this.ChatApplyButton.TabIndex = 25; + this.ChatApplyButton.Text = "Apply"; + this.ChatApplyButton.UseVisualStyleBackColor = true; + this.ChatApplyButton.Click += new System.EventHandler(this.ChatApplyButton_Click); + // + // AuthField2Title + // + this.AuthField2Title.AutoSize = true; + this.AuthField2Title.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.AuthField2Title.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.AuthField2Title.Location = new System.Drawing.Point(30, 311); + this.AuthField2Title.Name = "AuthField2Title"; + this.AuthField2Title.Size = new System.Drawing.Size(45, 18); + this.AuthField2Title.TabIndex = 24; + this.AuthField2Title.Text = "AF2:"; + this.AuthField2Title.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // AuthField1Title + // + this.AuthField1Title.AutoSize = true; + this.AuthField1Title.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.AuthField1Title.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.AuthField1Title.Location = new System.Drawing.Point(30, 285); + this.AuthField1Title.Name = "AuthField1Title"; + this.AuthField1Title.Size = new System.Drawing.Size(45, 18); + this.AuthField1Title.TabIndex = 23; + this.AuthField1Title.Text = "AF1:"; + this.AuthField1Title.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // AuthField2 + // + this.AuthField2.Location = new System.Drawing.Point(174, 309); + this.AuthField2.Name = "AuthField2"; + this.AuthField2.Size = new System.Drawing.Size(151, 20); + this.AuthField2.TabIndex = 22; + this.AuthField2.UseSystemPasswordChar = true; + // + // AuthField1 + // + this.AuthField1.Location = new System.Drawing.Point(174, 283); + this.AuthField1.Name = "AuthField1"; + this.AuthField1.Size = new System.Drawing.Size(151, 20); + this.AuthField1.TabIndex = 21; + this.AuthField1.UseSystemPasswordChar = true; + // + // AdminChannelTitle + // + this.AdminChannelTitle.AutoSize = true; + this.AdminChannelTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.AdminChannelTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.AdminChannelTitle.Location = new System.Drawing.Point(30, 174); + this.AdminChannelTitle.Name = "AdminChannelTitle"; + this.AdminChannelTitle.Size = new System.Drawing.Size(138, 18); + this.AdminChannelTitle.TabIndex = 20; + this.AdminChannelTitle.Text = "Admin Channel:"; + this.AdminChannelTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // AdminChannelText + // + this.AdminChannelText.Location = new System.Drawing.Point(174, 172); + this.AdminChannelText.Name = "AdminChannelText"; + this.AdminChannelText.Size = new System.Drawing.Size(149, 20); + this.AdminChannelText.TabIndex = 19; + // + // ChatReconnectButton + // + this.ChatReconnectButton.Enabled = false; + this.ChatReconnectButton.Location = new System.Drawing.Point(174, 141); + this.ChatReconnectButton.Name = "ChatReconnectButton"; + this.ChatReconnectButton.Size = new System.Drawing.Size(112, 25); + this.ChatReconnectButton.TabIndex = 18; + this.ChatReconnectButton.Text = "Reconnect"; + this.ChatReconnectButton.UseVisualStyleBackColor = true; + this.ChatReconnectButton.Click += new System.EventHandler(this.ChatReconnectButton_Click); + // + // ChatStatusLabel + // + this.ChatStatusLabel.AutoSize = true; + this.ChatStatusLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChatStatusLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatStatusLabel.Location = new System.Drawing.Point(171, 113); + this.ChatStatusLabel.Name = "ChatStatusLabel"; + this.ChatStatusLabel.Size = new System.Drawing.Size(82, 18); + this.ChatStatusLabel.TabIndex = 17; + this.ChatStatusLabel.Text = "Unknown"; + this.ChatStatusLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatStatusTitle + // + this.ChatStatusTitle.AutoSize = true; + this.ChatStatusTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChatStatusTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatStatusTitle.Location = new System.Drawing.Point(30, 113); + this.ChatStatusTitle.Name = "ChatStatusTitle"; + this.ChatStatusTitle.Size = new System.Drawing.Size(112, 18); + this.ChatStatusTitle.TabIndex = 16; + this.ChatStatusTitle.Text = "Chat Status:"; + this.ChatStatusTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatEnabledCheckbox + // + this.ChatEnabledCheckbox.AutoSize = true; + this.ChatEnabledCheckbox.Font = new System.Drawing.Font("Verdana", 12F); + this.ChatEnabledCheckbox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatEnabledCheckbox.Location = new System.Drawing.Point(32, 142); + this.ChatEnabledCheckbox.Name = "ChatEnabledCheckbox"; + this.ChatEnabledCheckbox.Size = new System.Drawing.Size(136, 22); + this.ChatEnabledCheckbox.TabIndex = 15; + this.ChatEnabledCheckbox.Text = "Chat Enabled"; + this.ChatEnabledCheckbox.UseVisualStyleBackColor = true; + // + // ChatProviderSelectorPanel + // + this.ChatProviderSelectorPanel.Controls.Add(this.DiscordProviderSwitch); + this.ChatProviderSelectorPanel.Controls.Add(this.IRCProviderSwitch); + this.ChatProviderSelectorPanel.Location = new System.Drawing.Point(23, 49); + this.ChatProviderSelectorPanel.Name = "ChatProviderSelectorPanel"; + this.ChatProviderSelectorPanel.Size = new System.Drawing.Size(302, 50); + this.ChatProviderSelectorPanel.TabIndex = 14; + // + // DiscordProviderSwitch + // + this.DiscordProviderSwitch.AutoSize = true; + this.DiscordProviderSwitch.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.DiscordProviderSwitch.Location = new System.Drawing.Point(220, 17); + this.DiscordProviderSwitch.Name = "DiscordProviderSwitch"; + this.DiscordProviderSwitch.Size = new System.Drawing.Size(61, 17); + this.DiscordProviderSwitch.TabIndex = 13; + this.DiscordProviderSwitch.TabStop = true; + this.DiscordProviderSwitch.Text = "Discord"; + this.DiscordProviderSwitch.UseVisualStyleBackColor = true; + this.DiscordProviderSwitch.CheckedChanged += new System.EventHandler(this.DiscordProviderSwitch_CheckedChanged); + // + // IRCProviderSwitch + // + this.IRCProviderSwitch.AutoSize = true; + this.IRCProviderSwitch.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.IRCProviderSwitch.Location = new System.Drawing.Point(14, 17); + this.IRCProviderSwitch.Name = "IRCProviderSwitch"; + this.IRCProviderSwitch.Size = new System.Drawing.Size(116, 17); + this.IRCProviderSwitch.TabIndex = 12; + this.IRCProviderSwitch.TabStop = true; + this.IRCProviderSwitch.Text = "Internet Relay Chat"; + this.IRCProviderSwitch.UseVisualStyleBackColor = true; + this.IRCProviderSwitch.CheckedChanged += new System.EventHandler(this.IRCProviderSwitch_CheckedChanged); + // + // ChatProviderTitle + // + this.ChatProviderTitle.AutoSize = true; + this.ChatProviderTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChatProviderTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatProviderTitle.Location = new System.Drawing.Point(20, 15); + this.ChatProviderTitle.Name = "ChatProviderTitle"; + this.ChatProviderTitle.Size = new System.Drawing.Size(125, 18); + this.ChatProviderTitle.TabIndex = 13; + this.ChatProviderTitle.Text = "Chat Provider:"; + this.ChatProviderTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChannelsTitle + // + this.ChannelsTitle.AutoSize = true; + this.ChannelsTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChannelsTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChannelsTitle.Location = new System.Drawing.Point(628, 15); + this.ChannelsTitle.Name = "ChannelsTitle"; + this.ChannelsTitle.Size = new System.Drawing.Size(234, 18); + this.ChannelsTitle.TabIndex = 11; + this.ChannelsTitle.Text = "Listen/Broadcast Channels:"; + this.ChannelsTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatAdminsTextBox + // + this.ChatAdminsTextBox.Location = new System.Drawing.Point(369, 49); + this.ChatAdminsTextBox.Multiline = true; + this.ChatAdminsTextBox.Name = "ChatAdminsTextBox"; + this.ChatAdminsTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.ChatAdminsTextBox.Size = new System.Drawing.Size(231, 311); + this.ChatAdminsTextBox.TabIndex = 10; + // + // ChatAdminsTitle + // + this.ChatAdminsTitle.AutoSize = true; + this.ChatAdminsTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ChatAdminsTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ChatAdminsTitle.Location = new System.Drawing.Point(366, 15); + this.ChatAdminsTitle.Name = "ChatAdminsTitle"; + this.ChatAdminsTitle.Size = new System.Drawing.Size(75, 18); + this.ChatAdminsTitle.TabIndex = 9; + this.ChatAdminsTitle.Text = "Admins:"; + this.ChatAdminsTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ChatChannelsTextBox + // + this.ChatChannelsTextBox.Location = new System.Drawing.Point(631, 49); + this.ChatChannelsTextBox.Multiline = true; + this.ChatChannelsTextBox.Name = "ChatChannelsTextBox"; + this.ChatChannelsTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.ChatChannelsTextBox.Size = new System.Drawing.Size(231, 311); + this.ChatChannelsTextBox.TabIndex = 0; + // + // ConfigPanel + // + this.ConfigPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.ConfigPanel.Controls.Add(this.ConfigApply); + this.ConfigPanel.Controls.Add(this.ConfigDownloadRepo); + this.ConfigPanel.Controls.Add(this.ConfigUpload); + this.ConfigPanel.Controls.Add(this.ConfigDownload); + this.ConfigPanel.Controls.Add(this.ConfigRefresh); + this.ConfigPanel.Controls.Add(this.ConfigPanels); + this.ConfigPanel.Controls.Add(this.ConfigDisabledLabel); + this.ConfigPanel.Location = new System.Drawing.Point(4, 22); + this.ConfigPanel.Name = "ConfigPanel"; + this.ConfigPanel.Padding = new System.Windows.Forms.Padding(3); + this.ConfigPanel.Size = new System.Drawing.Size(868, 366); + this.ConfigPanel.TabIndex = 5; + this.ConfigPanel.Text = "Config"; + // + // ConfigApply + // + this.ConfigApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ConfigApply.Location = new System.Drawing.Point(782, 26); + this.ConfigApply.Name = "ConfigApply"; + this.ConfigApply.Size = new System.Drawing.Size(63, 22); + this.ConfigApply.TabIndex = 4; + this.ConfigApply.Text = "Apply"; + this.ConfigApply.UseVisualStyleBackColor = true; + this.ConfigApply.Click += new System.EventHandler(this.ConfigApply_Click); + // + // ConfigDownloadRepo + // + this.ConfigDownloadRepo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ConfigDownloadRepo.Location = new System.Drawing.Point(782, 110); + this.ConfigDownloadRepo.Name = "ConfigDownloadRepo"; + this.ConfigDownloadRepo.Size = new System.Drawing.Size(63, 22); + this.ConfigDownloadRepo.TabIndex = 8; + this.ConfigDownloadRepo.Text = "DL Repo"; + this.ConfigDownloadRepo.UseVisualStyleBackColor = true; + this.ConfigDownloadRepo.Click += new System.EventHandler(this.ConfigDownloadRepo_Click); + // + // ConfigUpload + // + this.ConfigUpload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ConfigUpload.Location = new System.Drawing.Point(782, 138); + this.ConfigUpload.Name = "ConfigUpload"; + this.ConfigUpload.Size = new System.Drawing.Size(63, 22); + this.ConfigUpload.TabIndex = 7; + this.ConfigUpload.Text = "Upload"; + this.ConfigUpload.UseVisualStyleBackColor = true; + this.ConfigUpload.Click += new System.EventHandler(this.ConfigUpload_Click); + // + // ConfigDownload + // + this.ConfigDownload.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ConfigDownload.Location = new System.Drawing.Point(782, 82); + this.ConfigDownload.Name = "ConfigDownload"; + this.ConfigDownload.Size = new System.Drawing.Size(63, 22); + this.ConfigDownload.TabIndex = 6; + this.ConfigDownload.Text = "Download"; + this.ConfigDownload.UseVisualStyleBackColor = true; + this.ConfigDownload.Click += new System.EventHandler(this.ConfigDownload_Click); + // + // ConfigRefresh + // + this.ConfigRefresh.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ConfigRefresh.Location = new System.Drawing.Point(782, 54); + this.ConfigRefresh.Name = "ConfigRefresh"; + this.ConfigRefresh.Size = new System.Drawing.Size(63, 22); + this.ConfigRefresh.TabIndex = 5; + this.ConfigRefresh.Text = "Refresh"; + this.ConfigRefresh.UseVisualStyleBackColor = true; + this.ConfigRefresh.Click += new System.EventHandler(this.ConfigRefresh_Click); + // + // ConfigPanels + // + this.ConfigPanels.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ConfigPanels.Controls.Add(this.ConfigConfigPanel); + this.ConfigPanels.Controls.Add(this.DatabaseConfigPanel); + this.ConfigPanels.Controls.Add(this.GameConfigPanel); + this.ConfigPanels.Controls.Add(this.JobsConfigPanel); + this.ConfigPanels.Controls.Add(this.MapsConfigPanel); + this.ConfigPanels.Controls.Add(this.AdminsPanel); + this.ConfigPanels.Location = new System.Drawing.Point(-4, 0); + this.ConfigPanels.Name = "ConfigPanels"; + this.ConfigPanels.SelectedIndex = 0; + this.ConfigPanels.Size = new System.Drawing.Size(876, 370); + this.ConfigPanels.TabIndex = 0; + // + // ConfigConfigPanel + // + this.ConfigConfigPanel.AutoScroll = true; + this.ConfigConfigPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.ConfigConfigPanel.Location = new System.Drawing.Point(4, 22); + this.ConfigConfigPanel.Name = "ConfigConfigPanel"; + this.ConfigConfigPanel.Padding = new System.Windows.Forms.Padding(3); + this.ConfigConfigPanel.Size = new System.Drawing.Size(868, 344); + this.ConfigConfigPanel.TabIndex = 0; + this.ConfigConfigPanel.Text = "General"; + // + // DatabaseConfigPanel + // + this.DatabaseConfigPanel.AutoScroll = true; + this.DatabaseConfigPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.DatabaseConfigPanel.Location = new System.Drawing.Point(4, 22); + this.DatabaseConfigPanel.Name = "DatabaseConfigPanel"; + this.DatabaseConfigPanel.Padding = new System.Windows.Forms.Padding(3); + this.DatabaseConfigPanel.Size = new System.Drawing.Size(868, 344); + this.DatabaseConfigPanel.TabIndex = 1; + this.DatabaseConfigPanel.Text = "Database"; + // + // GameConfigPanel + // + this.GameConfigPanel.AutoScroll = true; + this.GameConfigPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.GameConfigPanel.Location = new System.Drawing.Point(4, 22); + this.GameConfigPanel.Name = "GameConfigPanel"; + this.GameConfigPanel.Padding = new System.Windows.Forms.Padding(3); + this.GameConfigPanel.Size = new System.Drawing.Size(868, 344); + this.GameConfigPanel.TabIndex = 2; + this.GameConfigPanel.Text = "Game"; + // + // JobsConfigPanel + // + this.JobsConfigPanel.AutoScroll = true; + this.JobsConfigPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.JobsConfigPanel.Location = new System.Drawing.Point(4, 22); + this.JobsConfigPanel.Name = "JobsConfigPanel"; + this.JobsConfigPanel.Padding = new System.Windows.Forms.Padding(3); + this.JobsConfigPanel.Size = new System.Drawing.Size(868, 344); + this.JobsConfigPanel.TabIndex = 3; + this.JobsConfigPanel.Text = "Jobs"; + // + // MapsConfigPanel + // + this.MapsConfigPanel.AutoScroll = true; + this.MapsConfigPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.MapsConfigPanel.Location = new System.Drawing.Point(4, 22); + this.MapsConfigPanel.Name = "MapsConfigPanel"; + this.MapsConfigPanel.Padding = new System.Windows.Forms.Padding(3); + this.MapsConfigPanel.Size = new System.Drawing.Size(868, 344); + this.MapsConfigPanel.TabIndex = 4; + this.MapsConfigPanel.Text = "Maps"; + // + // AdminsPanel + // + this.AdminsPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.AdminsPanel.Controls.Add(this.NegativePermissionsTitle); + this.AdminsPanel.Controls.Add(this.NegativePermissions); + this.AdminsPanel.Controls.Add(this.ApplyAdminRankButton); + this.AdminsPanel.Controls.Add(this.PermissionsListBox); + this.AdminsPanel.Controls.Add(this.PermissionsTItle); + this.AdminsPanel.Controls.Add(this.RemoveRankButton); + this.AdminsPanel.Controls.Add(this.AddRankTextBox); + this.AdminsPanel.Controls.Add(this.AddRankButton); + this.AdminsPanel.Controls.Add(this.AdminRanksListBox); + this.AdminsPanel.Controls.Add(this.RanksTitle); + this.AdminsPanel.Controls.Add(this.DeadminButton); + this.AdminsPanel.Controls.Add(this.AddminTextBox); + this.AdminsPanel.Controls.Add(this.AddminButton); + this.AdminsPanel.Controls.Add(this.AdminsListBox); + this.AdminsPanel.Controls.Add(this.AdminsTitle); + this.AdminsPanel.Location = new System.Drawing.Point(4, 22); + this.AdminsPanel.Name = "AdminsPanel"; + this.AdminsPanel.Padding = new System.Windows.Forms.Padding(3); + this.AdminsPanel.Size = new System.Drawing.Size(868, 344); + this.AdminsPanel.TabIndex = 5; + this.AdminsPanel.Text = "Admins"; + // + // NegativePermissionsTitle + // + this.NegativePermissionsTitle.AutoSize = true; + this.NegativePermissionsTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.NegativePermissionsTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.NegativePermissionsTitle.Location = new System.Drawing.Point(269, 151); + this.NegativePermissionsTitle.Name = "NegativePermissionsTitle"; + this.NegativePermissionsTitle.Size = new System.Drawing.Size(192, 18); + this.NegativePermissionsTitle.TabIndex = 31; + this.NegativePermissionsTitle.Text = "Negative Permissions:"; + this.NegativePermissionsTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // NegativePermissions + // + this.NegativePermissions.FormattingEnabled = true; + this.NegativePermissions.HorizontalScrollbar = true; + this.NegativePermissions.Location = new System.Drawing.Point(272, 172); + this.NegativePermissions.Name = "NegativePermissions"; + this.NegativePermissions.Size = new System.Drawing.Size(240, 109); + this.NegativePermissions.TabIndex = 30; + // + // ApplyAdminRankButton + // + this.ApplyAdminRankButton.Location = new System.Drawing.Point(272, 292); + this.ApplyAdminRankButton.Name = "ApplyAdminRankButton"; + this.ApplyAdminRankButton.Size = new System.Drawing.Size(240, 46); + this.ApplyAdminRankButton.TabIndex = 29; + this.ApplyAdminRankButton.Text = "Apply Selected Rank to Selected Admin"; + this.ApplyAdminRankButton.UseVisualStyleBackColor = true; + this.ApplyAdminRankButton.Click += new System.EventHandler(this.ApplyAdminRankButton_Click); + // + // PermissionsListBox + // + this.PermissionsListBox.FormattingEnabled = true; + this.PermissionsListBox.HorizontalScrollbar = true; + this.PermissionsListBox.Location = new System.Drawing.Point(272, 29); + this.PermissionsListBox.Name = "PermissionsListBox"; + this.PermissionsListBox.Size = new System.Drawing.Size(240, 109); + this.PermissionsListBox.TabIndex = 28; + // + // PermissionsTItle + // + this.PermissionsTItle.AutoSize = true; + this.PermissionsTItle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.PermissionsTItle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.PermissionsTItle.Location = new System.Drawing.Point(269, 8); + this.PermissionsTItle.Name = "PermissionsTItle"; + this.PermissionsTItle.Size = new System.Drawing.Size(113, 18); + this.PermissionsTItle.TabIndex = 27; + this.PermissionsTItle.Text = "Permissions:"; + this.PermissionsTItle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // RemoveRankButton + // + this.RemoveRankButton.Location = new System.Drawing.Point(9, 292); + this.RemoveRankButton.Name = "RemoveRankButton"; + this.RemoveRankButton.Size = new System.Drawing.Size(240, 20); + this.RemoveRankButton.TabIndex = 26; + this.RemoveRankButton.Text = "Remove Selected"; + this.RemoveRankButton.UseVisualStyleBackColor = true; + this.RemoveRankButton.Click += new System.EventHandler(this.RemoveRankButton_Click); + // + // AddRankTextBox + // + this.AddRankTextBox.Location = new System.Drawing.Point(9, 318); + this.AddRankTextBox.Name = "AddRankTextBox"; + this.AddRankTextBox.Size = new System.Drawing.Size(184, 20); + this.AddRankTextBox.TabIndex = 25; + // + // AddRankButton + // + this.AddRankButton.Location = new System.Drawing.Point(199, 318); + this.AddRankButton.Name = "AddRankButton"; + this.AddRankButton.Size = new System.Drawing.Size(50, 20); + this.AddRankButton.TabIndex = 24; + this.AddRankButton.Text = "Add"; + this.AddRankButton.UseVisualStyleBackColor = true; + this.AddRankButton.Click += new System.EventHandler(this.AddRankButton_Click); + // + // AdminRanksListBox + // + this.AdminRanksListBox.FormattingEnabled = true; + this.AdminRanksListBox.Location = new System.Drawing.Point(9, 30); + this.AdminRanksListBox.Name = "AdminRanksListBox"; + this.AdminRanksListBox.Size = new System.Drawing.Size(240, 251); + this.AdminRanksListBox.TabIndex = 23; + // + // RanksTitle + // + this.RanksTitle.AutoSize = true; + this.RanksTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.RanksTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.RanksTitle.Location = new System.Drawing.Point(6, 8); + this.RanksTitle.Name = "RanksTitle"; + this.RanksTitle.Size = new System.Drawing.Size(64, 18); + this.RanksTitle.TabIndex = 22; + this.RanksTitle.Text = "Ranks:"; + this.RanksTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // DeadminButton + // + this.DeadminButton.Location = new System.Drawing.Point(536, 292); + this.DeadminButton.Name = "DeadminButton"; + this.DeadminButton.Size = new System.Drawing.Size(240, 20); + this.DeadminButton.TabIndex = 21; + this.DeadminButton.Text = "Remove Selected"; + this.DeadminButton.UseVisualStyleBackColor = true; + this.DeadminButton.Click += new System.EventHandler(this.DeadminButton_Click); + // + // AddminTextBox + // + this.AddminTextBox.Location = new System.Drawing.Point(536, 318); + this.AddminTextBox.Name = "AddminTextBox"; + this.AddminTextBox.Size = new System.Drawing.Size(184, 20); + this.AddminTextBox.TabIndex = 20; + // + // AddminButton + // + this.AddminButton.Location = new System.Drawing.Point(726, 318); + this.AddminButton.Name = "AddminButton"; + this.AddminButton.Size = new System.Drawing.Size(50, 20); + this.AddminButton.TabIndex = 19; + this.AddminButton.Text = "Add"; + this.AddminButton.UseVisualStyleBackColor = true; + this.AddminButton.Click += new System.EventHandler(this.AddminButton_Click); + // + // AdminsListBox + // + this.AdminsListBox.FormattingEnabled = true; + this.AdminsListBox.Location = new System.Drawing.Point(536, 30); + this.AdminsListBox.Name = "AdminsListBox"; + this.AdminsListBox.Size = new System.Drawing.Size(240, 251); + this.AdminsListBox.TabIndex = 13; + // + // AdminsTitle + // + this.AdminsTitle.AutoSize = true; + this.AdminsTitle.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.AdminsTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.AdminsTitle.Location = new System.Drawing.Point(533, 8); + this.AdminsTitle.Name = "AdminsTitle"; + this.AdminsTitle.Size = new System.Drawing.Size(75, 18); + this.AdminsTitle.TabIndex = 12; + this.AdminsTitle.Text = "Admins:"; + this.AdminsTitle.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // ConfigDisabledLabel + // + this.ConfigDisabledLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ConfigDisabledLabel.AutoSize = true; + this.ConfigDisabledLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ConfigDisabledLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.ConfigDisabledLabel.Location = new System.Drawing.Point(225, 160); + this.ConfigDisabledLabel.Name = "ConfigDisabledLabel"; + this.ConfigDisabledLabel.Size = new System.Drawing.Size(428, 18); + this.ConfigDisabledLabel.TabIndex = 14; + this.ConfigDisabledLabel.Text = "Configuration is disabled until repository is set up!"; + this.ConfigDisabledLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // RepoBGW + // + this.RepoBGW.WorkerReportsProgress = true; + this.RepoBGW.WorkerSupportsCancellation = true; + // + // BYONDTimer + // + this.BYONDTimer.Interval = 1000; + this.BYONDTimer.Tick += new System.EventHandler(this.BYONDTimer_Tick); + // + // ServerTimer + // + this.ServerTimer.Interval = 10000; + this.ServerTimer.Tick += new System.EventHandler(this.ServerTimer_Tick); + // + // WorldStatusChecker + // + this.WorldStatusChecker.WorkerReportsProgress = true; + this.WorldStatusChecker.WorkerSupportsCancellation = true; + this.WorldStatusChecker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.WorldStatusChecker_DoWork); + // + // WorldStatusTimer + // + this.WorldStatusTimer.Interval = 10000; + this.WorldStatusTimer.Tick += new System.EventHandler(this.WorldStatusTimer_Tick); + // + // FullUpdateWorker + // + this.FullUpdateWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.FullUpdateWorker_DoWork); + // + // Main + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.ClientSize = new System.Drawing.Size(900, 415); + this.Controls.Add(this.Panels); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "Main"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "/tg/station 13 Server Control Panel"; + this.Panels.ResumeLayout(false); + this.RepoPanel.ResumeLayout(false); + this.RepoPanel.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.TestmergeSelector)).EndInit(); + this.BYONDPanel.ResumeLayout(false); + this.BYONDPanel.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.MinorVersionNumeric)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.MajorVersionNumeric)).EndInit(); + this.ServerPanel.ResumeLayout(false); + this.ServerPanel.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.NudgePortSelector)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.PortSelector)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.ServerTestmergeInput)).EndInit(); + this.ChatPanel.ResumeLayout(false); + this.ChatPanel.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.ChatPortSelector)).EndInit(); + this.ChatProviderSelectorPanel.ResumeLayout(false); + this.ChatProviderSelectorPanel.PerformLayout(); + this.ConfigPanel.ResumeLayout(false); + this.ConfigPanel.PerformLayout(); + this.ConfigPanels.ResumeLayout(false); + this.AdminsPanel.ResumeLayout(false); + this.AdminsPanel.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.TabControl Panels; + private System.ComponentModel.BackgroundWorker RepoBGW; + private System.Windows.Forms.TabPage BYONDPanel; + private System.Windows.Forms.TabPage ServerPanel; + private System.Windows.Forms.Button UpdateButton; + private System.Windows.Forms.NumericUpDown MinorVersionNumeric; + private System.Windows.Forms.NumericUpDown MajorVersionNumeric; + private System.Windows.Forms.ProgressBar UpdateProgressBar; + private System.Windows.Forms.Label MajorVersionLabel; + private System.Windows.Forms.Label MinorVersionLabel; + private System.Windows.Forms.Label VersionLabel; + private System.Windows.Forms.Label VersionTitle; + private System.Windows.Forms.Label StatusLabel; + private System.Windows.Forms.Timer BYONDTimer; + private System.Windows.Forms.Label StagedVersionLabel; + private System.Windows.Forms.Label StagedVersionTitle; + private System.Windows.Forms.TabPage ChatPanel; + private System.Windows.Forms.TabPage ConfigPanel; + private System.Windows.Forms.Label ServerStatusTitle; + private System.Windows.Forms.Button compileButton; + private System.Windows.Forms.Button initializeButton; + private System.Windows.Forms.ProgressBar compilerProgressBar; + private System.Windows.Forms.Label ServerStatusLabel; + private System.Windows.Forms.Timer ServerTimer; + private System.Windows.Forms.Label CompilerLabel; + private System.Windows.Forms.Label CompilerStatusLabel; + private System.ComponentModel.BackgroundWorker WorldStatusChecker; + private System.Windows.Forms.Timer WorldStatusTimer; + private System.Windows.Forms.CheckBox AutostartCheckbox; + private System.Windows.Forms.Button ServerGRestartButton; + private System.Windows.Forms.Button ServerGStopButton; + private System.Windows.Forms.Button ServerRestartButton; + private System.Windows.Forms.Button ServerStopButton; + private System.Windows.Forms.Button ServerStartButton; + private System.Windows.Forms.Label ServerPRLabel; + private System.Windows.Forms.NumericUpDown ServerTestmergeInput; + private System.Windows.Forms.Button TestmergeButton; + private System.Windows.Forms.Button UpdateTestmergeButton; + private System.Windows.Forms.Button UpdateMergeButton; + private System.Windows.Forms.Button UpdateHardButton; + private System.ComponentModel.BackgroundWorker FullUpdateWorker; + private System.Windows.Forms.TabControl ConfigPanels; + private System.Windows.Forms.TabPage ConfigConfigPanel; + private System.Windows.Forms.Button ConfigRefresh; + private System.Windows.Forms.Button ConfigApply; + private System.Windows.Forms.Button ConfigDownload; + private System.Windows.Forms.Button ConfigUpload; + private System.Windows.Forms.Button ConfigDownloadRepo; + private System.Windows.Forms.TabPage DatabaseConfigPanel; + private System.Windows.Forms.TabPage GameConfigPanel; + private System.Windows.Forms.Label PortLabel; + private System.Windows.Forms.NumericUpDown PortSelector; + private System.Windows.Forms.Label ProjectPathLabel; + private System.Windows.Forms.TextBox projectNameText; + private System.Windows.Forms.Button CompileCancelButton; + private System.Windows.Forms.Label ServerPathLabel; + private System.Windows.Forms.TextBox ServerPathTextbox; + private System.Windows.Forms.Label LatestVersionLabel; + private System.Windows.Forms.Label LatestVersionTitle; + private System.Windows.Forms.TextBox ChatChannelsTextBox; + private System.Windows.Forms.Label ChannelsTitle; + private System.Windows.Forms.TextBox ChatAdminsTextBox; + private System.Windows.Forms.Label ChatAdminsTitle; + private System.Windows.Forms.Label ChatStatusLabel; + private System.Windows.Forms.Label ChatStatusTitle; + private System.Windows.Forms.CheckBox ChatEnabledCheckbox; + private System.Windows.Forms.Panel ChatProviderSelectorPanel; + private System.Windows.Forms.RadioButton DiscordProviderSwitch; + private System.Windows.Forms.RadioButton IRCProviderSwitch; + private System.Windows.Forms.Label ChatProviderTitle; + private System.Windows.Forms.Button ChatReconnectButton; + private System.Windows.Forms.Label AdminChannelTitle; + private System.Windows.Forms.TextBox AdminChannelText; + private System.Windows.Forms.Label AuthField2Title; + private System.Windows.Forms.Label AuthField1Title; + private System.Windows.Forms.TextBox AuthField2; + private System.Windows.Forms.TextBox AuthField1; + private System.Windows.Forms.Button ChatApplyButton; + private System.Windows.Forms.NumericUpDown ChatPortSelector; + private System.Windows.Forms.Label ChatPortTitle; + private System.Windows.Forms.TextBox ChatServerText; + private System.Windows.Forms.Label ChatServerTitle; + private System.Windows.Forms.TextBox ChatNicknameText; + private System.Windows.Forms.Label ChatNicknameTitle; + private System.Windows.Forms.Button ChatRefreshButton; + private System.Windows.Forms.TabPage JobsConfigPanel; + private System.Windows.Forms.NumericUpDown NudgePortSelector; + private System.Windows.Forms.Label NudgePortLabel; + private System.Windows.Forms.TabPage MapsConfigPanel; + private System.Windows.Forms.TabPage AdminsPanel; + private System.Windows.Forms.TextBox AddminTextBox; + private System.Windows.Forms.Button AddminButton; + private System.Windows.Forms.ListBox AdminsListBox; + private System.Windows.Forms.Label AdminsTitle; + private System.Windows.Forms.Button DeadminButton; + private System.Windows.Forms.ListBox AdminRanksListBox; + private System.Windows.Forms.Label RanksTitle; + private System.Windows.Forms.Button RemoveRankButton; + private System.Windows.Forms.TextBox AddRankTextBox; + private System.Windows.Forms.Button AddRankButton; + private System.Windows.Forms.Button ApplyAdminRankButton; + private System.Windows.Forms.CheckedListBox PermissionsListBox; + private System.Windows.Forms.Label PermissionsTItle; + private System.Windows.Forms.CheckedListBox NegativePermissions; + private System.Windows.Forms.Label NegativePermissionsTitle; + private System.Windows.Forms.Label ConfigDisabledLabel; + private System.Windows.Forms.Button ResetTestmerge; + private System.Windows.Forms.TabPage RepoPanel; + private System.Windows.Forms.Button ResetRemote; + private System.Windows.Forms.Button RecloneButton; + private System.Windows.Forms.TextBox PythonPathText; + private System.Windows.Forms.Label PythonPathLabel; + private System.Windows.Forms.Button RepoGenChangelogButton; + private System.Windows.Forms.NumericUpDown TestmergeSelector; + private System.Windows.Forms.ListBox TestMergeListLabel; + private System.Windows.Forms.Label CurrentRevisionLabel; + private System.Windows.Forms.Button RepoApplyButton; + private System.Windows.Forms.TextBox RepoBranchTextBox; + private System.Windows.Forms.TextBox RepoRemoteTextBox; + private System.Windows.Forms.Button HardReset; + private System.Windows.Forms.Button UpdateRepoButton; + private System.Windows.Forms.Button MergePRButton; + private System.Windows.Forms.Label IdentityLabel; + private System.Windows.Forms.Label TestMergeListTitle; + private System.Windows.Forms.Label RemoteNameTitle; + private System.Windows.Forms.Label BranchNameTitle; + private System.Windows.Forms.Label CurrentRevisionTitle; + private System.Windows.Forms.Button CloneRepositoryButton; + private System.Windows.Forms.Label RepoProgressBarLabel; + private System.Windows.Forms.ProgressBar RepoProgressBar; + private System.Windows.Forms.ListBox BackupTagsList; + private System.Windows.Forms.ComboBox SecuritySelector; + private System.Windows.Forms.Label SecurityTitle; + private System.Windows.Forms.Label VisibilityTitle; + private System.Windows.Forms.ComboBox VisibilitySelector; + } +} + diff --git a/TGControlPanel/Main.cs b/TGControlPanel/Main.cs new file mode 100644 index 0000000000..f117674801 --- /dev/null +++ b/TGControlPanel/Main.cs @@ -0,0 +1,33 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace TGControlPanel +{ + public partial class Main : Form + { + public Main() + { + InitializeComponent(); + Panels.SelectedIndexChanged += Panels_SelectedIndexChanged; + Panels.SelectedIndex += Properties.Settings.Default.LastPageIndex; + InitRepoPage(); + InitBYONDPage(); + InitServerPage(); + LoadChatPage(); + InitConfigPage(); + } + + private void Main_Resize(object sender, EventArgs e) + { + Panels.Location = new Point(10, 10); + Panels.Width = ClientSize.Width - 20; + Panels.Height = ClientSize.Height - 20; + } + + private void Panels_SelectedIndexChanged(object sender, EventArgs e) + { + Properties.Settings.Default.LastPageIndex = Panels.SelectedIndex; + } + } +} diff --git a/TGControlPanel/Main.resx b/TGControlPanel/Main.resx new file mode 100644 index 0000000000..e34e2ce809 --- /dev/null +++ b/TGControlPanel/Main.resx @@ -0,0 +1,2197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 244, 17 + + + 416, 18 + + + 532, 18 + + + 694, 18 + + + 843, 18 + + + + + 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 + C/9BLRT/hnlp/0EtFP84Iwj+RTEYzG1dShxsXUkATDkgAE06Iig7JgzoNyIH/z4pD/9wYU3/ubKo/1RB + Kv9qWkb/+Pf3///////Hwbn/OyYL/4p+bv/c2NT/3NjT/6iek/87Jwz/e21b/1E+J/82IQb/OyYM6E06 + IihMOSAAWUcwAF1MNjs9KQ/0NSAE/2pbRv/z8vD/7evo/1JAKf9qWkb/+Pf3///////p5+T/tq6l/9vX + 0v///////////9XRy/9BLRP/in1t/25fS/81HwT/PSkP9F5NNztZRzEAAAAAAEo3H3A6JQr9NB8D/3xu + XP//////7Oro/1JAKf9qWkb/+Pf3/////////////////////////////////9XRy/9BLBP/kIR1/35w + Xv80HwP/OiUK/Uo3HnAAAAAAUkApRj4pD+M3Igf/OiUK/6iekv//////7Oro/1JAKf9qWkb/+Pf3//// + /////////////////////////////9XQy/9ALBL/k4d5/6qhlf86JQr/NyIH/z4pD+NSPyhGQy8V1jgj + CP82IQb/emxa//Tz8f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yj + mP83Igf/mI6A//b29P97bVv/NiEG/zgjCP9DLxXWRTEYxDgjCP82IQb/m5CD////////////7Oro/1JA + Kf9qWkb/+Pf3/////////////////6ackP9XRS7/WEYw/0IuFf91Z1T/4d7Z//////+bkIP/NiEG/zgj + CP9FMRjEUT4nXTsmDPU2IAX/XUw2/+3r6f//////7Oro/1JAKf9qWkb/+Pf3/////////////////6ac + kP9XRS7/WEYv/0IuFP92Z1T/4t/b/+7s6v9dTDb/NiAF/zsmDPVRPiddYlE7Gz8qENQ3Igb/SDUc/93a + 1f//////7Oro/1JAKf9qWkb/+Pf3//////////////////f29f/x7+3/8vDu/6yjmP83Igf/mo+B/9/c + 2P9INRz/NyIG/z8qENRhUTwbX044Jj8qEOA2IAX/WUcx/+3r6f//////7Oro/1JAKf9qWkb/+Pf3//// + /////////////////////////////9XQy/9ALBL/kod4/+/t6/9ZRzD/NiAF/z8qEOBfTjgmVEIqRjwn + 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 + af83Igf/e21b//X08//39vX///////v6+f+aj4L/STYd/z4pD/89KA7/PSgO/z0pDv9DLxb/dGZT/5WK + e/8+KQ//OyYL+j8rEcVQPSVpgHJhFW5eSwAAAAAAAAAAAEw6IgBgTzpiPioQ/jgjCP84Iwj/OCMI/zMe + Av9pWUT/g3Zl/zEcAP93aVb//Pv7////////////1tLN/0UyGf83Igf/Qi4U/0MvFv9DLxb/QCwS/zci + B/83IQb/kIR1/15NN/82IAX/OCMI/z4qEP5gTzliTDkhAAAAAAAAAAAAPCgNAEMvFWk5JAn/OCMI/zgj + CP86JQr/V0Uv/66lmv+Ie2v/MRwA/3dpVv/8+/v///////////+5sqn/NSAF/0k2Hv/DvLT/1tHM/9XR + y//Mx8D/Z1dD/zMdAv92aFX/cWJP/zUgBP84Iwj/OSQJ/0MvFmk8KA0AAAAAAAAAAAAAAAAAU0AphDkk + Cf84Iwj/OCIH/4x/cP/q5+T//////4p+bv8xHAD/d2lW//z7+////////////8fBuv9oWUT/fW9d/+7s + 6v////////////////+Wi3z/Mh0B/3VmU/+YjX//NyIH/zgjCP85JAn/U0EphAAAAAAAAAAAemlYAAAA + AABPPSWYOSQJ/zgjCP87Jgv/vbat////////////in1u/zEcAP93aVb//Pv7/////////////Pz8//j3 + 9v/5+Pj//v7+/////////////////5iMf/8yHQH/d2hW/7ixp/87Jgv/OCMI/zkkCf9PPSWYAAAAAHRm + UgBcSzUAZVQ/J0EtE9M4Iwj/OCMI/z0pDv/JxL3///////////+KfW7/MRwA/3dpVv/8+/v///////// + ////////////////////////////////////////mIx+/zIdAf93aFb/xb+3/z0pD/84Iwj/OCMI/0Et + E9NjUz4nW0kzAHlrWBBJNh2oOSQJ/zgjCP82IQb/U0Ap/+He2v///////////4p9bv8xHAD/d2lW//z7 + +/////////////////////////////////////////////////+YjH7/Mh0B/3VnVP/d2dX/U0Ep/zYh + Bv84Iwj/OSQJ/0g1HKh3aVcQTjsjjDsmC/k4Iwj/NyIH/0AsEv+zq6H/////////////////in1u/zEc + AP93aVb//Pv7/////////////////////////////////////////////////4t/b/8yHAD/c2RR//v7 + +v+0raP/QCwT/zciB/84Iwj/OyYL+U47I4xFMRjoOCMH/zgjCP82IAX/hHdm//r6+f////////////// + //+KfW7/MRwA/3dpVv/8+/v///////////////////////f29f/Szsj/zcjC/87Jw/+wqJ7/Tzwk/zUf + BP+MgHD///7+//v6+v+Ed2f/NiAF/zgjCP84Iwf/RTEY6E48JJs5JAn/OCMI/zUgBf97bVv/+vn5//// + /////////////4p9bv8xHAD/d2lW//z7+///////////////////////4d7Z/1A+Jv89KQ//PioQ/zkk + Cv89KQ//nJGE/+zq5///////+vn5/3ttW/81IAX/OCMI/zkkCf9OPCSbYlI9O0AsEuk4Iwf/NyIH/0Yy + Gf/Tzsj/////////////////in1u/zEcAP93aVb//Pv7///////////////////////h3tn/UD4m/z0p + D/8+KhD/OSQJ/z0pD/+ckYT/7Oro///////Tzsj/RjIZ/zciB/84Iwf/QCwS6WJSPTuooJQFRTEYoDgj + CP84Iwj/NiEG/6KYi/////////////////+KfW7/MRwA/3dpVv/8+/v///////////////////////f2 + 9f/Szsj/zcjC/87Jw/+wqJ3/Tzwk/zUfBP+MgHD//////6KYi/82IQX/OCMI/zgjCP9FMRigqJ+TBQAA + AABOOyOLOSQJ/zgjCP84Iwj/sKid/////////////////4p9bv8xHAD/d2lW//z7+/////////////// + //////////////////////////////////+Lf2//MhwA/3NkUf/+/v7/sKid/zgjCP84Iwj/OSQJ/048 + I4sAAAAAycO7BUk1Ha04Iwj/OCIH/0ArEf/MxsD/////////////////in1u/zEcAP93aVb//Pv7//// + /////////////////////////////////////////////5iMf/8yHQH/dGVS//79/f/Mx8D/QCsR/zgi + B/84Iwj/STYdrcjEvAV8b10UQzAWzDgjCP84Iwf/PikP/62kmf/9/Pz///////////+KfW7/MRwA/3dp + Vv/8+/v/////////////////////////////////////////////////mIx+/zIdAf90ZVL/+fj3/66m + m/8+KQ//OCMH/zgjCP9EMBbMfW9eFGVVQCZBLBPhNyIH/zgjCP83Igf/Qy8V/5yShf/18/L//////4p9 + bv8xHAD/d2lW//z7+/////////////z8/P/49/b/+fj4//7+/v////////////////+YjH//Mh0B/3Rl + Uv+nnZH/Qy8W/zciB/84Iwj/NyIH/0EsE+FlVUAmhHZmDE48JIE8Jw3zOCMI/zgjCP81IAX/VkQt/9bS + zf/+/v7/in1t/zEcAP93aVb//Pv7////////////x8G6/2hZRP99b13/7uzq/////////////////5aL + fP8yHQH/cWJO/3RlUv80HwP/OCMI/zgjCP88Jw3zTTsjgYF0YwxjUj0AfG5dBE88JV4/KxHkOCMI/zUg + BP91Z1T/kIV2/3NlUv9XRS//Mx4C/3dpVv/8+/v///////////+3sKb/NSAE/0k2Hv/DvLT/1tHM/9XR + y//Mx8D/Z1dD/zMdAv92aFX/cmNP/zUgBP84Iwj/PyoQ5E48JF58blwEY1I9AAAAAAByZVEA////AF5N + OEk+KhDmNR8E/3ZnVP9tXUn/LxkA/zUfBP80HgP/d2hW//z7+////////////9LNx/9CLhX/OCMI/0Iu + FP9DLxb/Qy8W/0AsEv83Igf/NyEG/5CEdf9eTTf/NiAF/z4qEOZdTTdJ////AHBhTgAAAAAAAAAAAAAA + AACBc2MAvreuA0UxGKM1IAX/cWJP/5qQgv9pWUX/Wkgy/0g1HP9/cWD//Pv7////////////+fj4/5SJ + ev9GMhn/OyYM/zwnDf89KA7/PSkO/0MvFv90ZlP/lYp8/z4qD/84Iwj/RTEYo764sAOBdGQAAAAAAAAA + AAAAAAAAAAAAAAAAAAA6JQoASzggXDkkCfxDLxb/bF1J/4ByYf+NgXL/lop8/62kmf/g3dn///////j3 + 9v/f29f/2NTP/8/KxP/CvLT/pZyQ/5WKfP+Wi33/mo+C/4l9bf9HNBv/NyIG/zolCvxLOCBcOiUKAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBdTDYkQCwS4jciB/81HwT/NR8E/zciB/88Jw3/QS0T/1pI + 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 + CP84Iwj/OCMI/zYhBf82IQX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9EMBfValpGJFZELQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCWiQDEvbUHVEIrmjolCv84Iwj/NiEF/zUgBP81IAT/NSAE/zUg + BP83Igf/OCMI/zgjCP82IQb/OSQJ/2BPOf9gUDr/OSQJ/zUgBP81IAT/NSAE/zUgBP81IAT/NSAE/zUg + BP81IAT/NiEG/zgjCP86JQr/VEEqmsG6sweek4cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl41+AODd2ARtXUleQi4U9DciB/85JAn/YE86/31v + Xf98b13/fG9d/3lrWf9KNx//NB8D/zYgBf9PPSX/n5WI/+zq5//t6+j/oZeK/21dSv94aVf/fG9d/3xv + Xf98b13/fG9d/3ttXP9wYU7/VUMs/zomC/82IQb/Qi4U9GxdSV7h3dkEl41+AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AGtaRgCNgHAFY1I9OlNBKp9EMBbvOCMI/zgj + CP8+Kg//wbuy/7Wto/+il4v/oZeL/8nEvf+so5j/alpG/4l9bf/b19L//v79////////////7Orn/763 + rv+onpP/opiM/6OYjP+jmIz/opiM/6Sajv+wp53/w721/6ifk/9OOyP/NiEG/0QwFu9TQiqfY1M+Oo2A + cQVrW0cA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wANAAAAqKCUDlxKND9JNh2fQCwS7Tkk + Cf83Igf/OCMI/zgjCP8/KxH/wLqx/11MNv81IAT/NR8E/4+DdP/+/v7/+Pf2//v7+/////////////// + ///Qy8T/Y1I9/z0pD/84Iwj/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TToh/5qPgv+2rqT/RzQb/zYh + Bv85JAn/QCwS7Uc0G59ZRzE/p52RDh4IAAD08e8AAAAAAAAAAAAAAAAAAAAAAJ+WiQCjmo0YX085sj8r + EfM4Iwj/OCMH/zgjCP84Iwj/OCMI/zgjCP8/KxH/wLqx/15NN/82IAX/NSAF/4+DdP////////////// + /////////////+zq5/9jUj3/NB8D/zgjCP84Iwj/OCIH/zgjB/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsm + C/+upZr/in5u/zYgBf84Iwj/OCMH/zgjCP8/KxHzXkw3sqGXihiflIcAAAAAAAAAAAAAAAAAAAAAAIF0 + YwB/cmEtSDUc7TchBv84Iwj/OCMI/zgjCP84Iwj/OCMI/zciB/89KQ7/wLmw/15NN/82IAX/NSAF/4+D + dP///////////////////////////8C6sf88Jw3/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm + DP83Igb/OCMI/zUgBP9sXEj/tKyi/zwoDv84Iwj/OCMI/zgjCP83IQb/SDUc7YByYS2CdGQAAAAAAAAA + AAAAAAAAAAAAAFZFLgBWRC0tPysR7TgjCP84Iwj/OCMI/zgjCP83Igf/NSAE/zwoDf9rXEj/1NDK/11M + Nv82IAX/NSAF/4+DdP///////////////////////////6KYi/82IQb/NyIG/0s4IP+6s6n/y8W+/8rE + vP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYhBv9RPyj/vLSr/0g0HP83Igf/OCMI/zgjCP84Iwj/PysR7VZE + LS1XRS4AAAAAAAAAAAAAAAAAAAAAAEEtEwBINBsxOSUK7zgjCP84Iwj/OCMI/zgjB/9ALBL/cGFO/7ix + p//y8O7/8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////5aKfP8yHAH/Mx0C/089 + Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9KNx//vreu/1NBKv83IQb/OCMI/zgj + CP84Iwj/OSUK70c0GzFBLRMAAAAAAAAAAAAAAAAAAAAAAGVUPwB+cF5eQCwS/jgiB/84Iwj/OCMI/zYg + Bf+GeWj/9/b1////////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ix + p/93aFb/d2lX/4p+bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9INBv/1tHM/4p9 + bf81IAX/OCMI/zgjCP84Igf/QCwS/n5wXl5lVD8AAAAAAAAAAAAAAAAAAAAAAFpJMwBsXEhpPysR/zgj + CP84Iwj/OCMI/zYhBv+glYj/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// + //////////////7+/v/+/f3//v79//7+/v////////////////////////////////+/uLD/PCgN/zci + Bv9HNBv/3NjT/6GYi/82IQb/OCMI/zgjCP84Iwf/PysR/2xdSWlbSTMAAAAAAAAAAAAAAAAAn5WIAAAA + AABPPSV9OiYL/zgjCP84Iwj/OCMI/zgjCP+tpZr/////////////////8fDu/1tKM/82IAX/NSAF/4+D + dP////////////////////////////////////////////////////////////////////////////// + //+/uLD/PCgN/zciBv9HNBv/3NjT/6+nnP84Iwj/OCMI/zgjCP84Iwj/OiYL/089JX0AAAAAn5SIAAAA + AAD///8AY1M+AG5fS0BFMRjnNyIH/zgjCP84Iwj/OCMI/zkkCf+8taz/////////////////8fDu/1tK + M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// + //////////////////+/uLD/PCgN/zciBv9HNBv/3dnU/764r/85JAn/OCMI/zgjCP84Iwj/NyIH/0Ux + GOdsXUlAYlE8AP///wB7bVsAgHJhH0s4IMY4Iwj/OCMI/zgjCP84Iwj/NyIH/0MvFf/OycP///////// + ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// + //////////////////////////////////+/ubD/PCgN/zciBv9HNBv/3NjT/9HMxf9DLxX/NyIH/zgj + CP84Iwj/OCMI/zgjCP9LOCDGf3JgH3ttWwCimYwNUkAomjolC/84Iwj/OCMI/zgjCP84Iwj/OCMI/5CE + df/6+vn/////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// + //////////////////////////////////////////////////++t67/PCcN/zciBv9HNBv/2tbQ//38 + /P+RhXb/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQv/UT8omqCWiQ1ZSDJ6PioQ9jgjB/84Iwj/OCMI/zgj + CP82IAX/bl9L/+3r6f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// + //////////////////////////////////////////////////////////////////+qoZX/OCMI/zci + B/9HNBv/2dXQ///////u7Or/cWJO/zYgBf84Iwj/OCMI/zgjCP84Iwf/PioP9llHMXpKNx/tNyIH/zgj + CP84Iwj/OCMI/zYhBv9UQiv/19LN////////////////////////////8fDu/1tKM/82IAX/NSAF/4+D + dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9PO + yP9fTjj/NiEG/zUgBP9PPCT/4t/c////////////2NTP/1RCK/82IQb/OCMI/zgjCP84Iwj/NyIH/0s3 + H+1XRS+1OiUK/zgjCP84Iwj/OCMI/zUgBP9uX0z/9/b1////////////////////////////8fDu/1tK + M/82IAX/NSAF/4+DdP///////////////////////////////////////////+Th3v91Z1T/aFhE/2lZ + Rf9pWUX/ZlVB/0o3Hv83Igf/OyYL/0s4IP+flYj//Pv7////////////9/b1/25fTP81IAT/OCMI/zgj + CP84Iwj/OiUK/1dGL7VrW0hMQCsS7zgjB/84Iwj/OCMI/zciB/9FMRj/zsnC//////////////////// + ////////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////////////////////9vX + 0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c1//8+/v/////////////////zsnC/0Ux + GP83Igf/OCMI/zgjCP84Iwf/QCsR72tbR0yglooMUj8ouTgjCP84Iwj/OCMI/zgjCP81IAX/iX1t//7+ + /f//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// + /////////////9vX0v9GMhn/NB4D/zUgBP81IAT/NSAE/zciB/82IAX/a1tH/9/c2P/8+/v///////// + ///+/f3/iX1t/zUgBf84Iwj/OCMI/zgjCP84Iwj/Uj8ouaGXiwwbAwAAZVVAYD0pD/o4Iwj/OCMI/zgj + CP82IQb/UD4m/+Dd2P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// + /////////////////////////////+Th3v91Z1T/aFhE/2lZRf9pWUX/ZlVB/0k2Hv83Igf/OyYL/0s4 + IP+glon//Pv7///////g3Nj/UD4m/zYhBv84Iwj/OCMI/zgjCP89KQ/6ZVVAYBsDAABtXUoAcWNPE0Et + FMY4Iwj/OCMI/zgjCP84Iwf/PSkP/8S+tv//////////////////////8fDu/1tKM/82IAX/NSAF/4+D + dP////////////////////////////////////////////7+/v/5+Pj/+fj3//n49//5+Pf/9/b1/9LO + yP9eTjj/NiEG/zUgBP9PPCT/4+Dc///////Evrb/PSkP/zgjCP84Iwj/OCMI/zgjCP9BLRTGcWJQE21d + SgCwp5wAxb63C1I/KL83Igf/OCMI/zgjCP83Igf/RzMa/9nW0P//////////////////////8fDu/1tK + M/82IAX/NSAF/4+DdP////////////////////////////////////////////////////////////// + //////////////////+qoZX/OCMI/zciB/9HNBv/2dXQ///////Z1dD/RjMa/zciB/84Iwj/OCMI/zci + B/9SQCi/xsG4C7CpngByYk8AdmdVFUUxGNU4Iwj/OCMI/zgjCP82IQb/WEYw/+vp5v////////////// + ////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////////////////////// + //////////////////////////////////++t6//PCcN/zciBv9HNBv/2dXQ///////r6eb/WEYv/zYh + Bv84Iwj/OCMI/zgjCP9FMhjVdmhVFXFjUAB+cF4Af3FgNkUxGPA3Igf/OCMI/zgjCP81IAT/b2BN//n5 + +P//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////////////////////// + //////////////////////////////////////////////////+/ubD/PCgN/zciBv9HNBv/2dXQ//// + ///5+fj/b2BN/zUgBP84Iwj/OCMI/zciB/9FMRjwf3JgNn5wXwBKNh4AXUw2Sj0oDvk4Iwj/OCMI/zgj + CP82IAX/bF1J/+7s6v//////////////////////8fDu/1tKM/82IAX/NSAF/4+DdP////////////// + //////////////////////////////////////////////////////////////////+/uLD/PCgN/zci + Bv9HNBv/2dXQ///////v7ev/bV1J/zYgBf84Iwj/OCMI/zgjCP89KA75XUw2Sko3HgA6JgsAY1M+ejwn + Df84Iwj/OCMI/zgjCP84Iwj/OiUL/3lqWP/j4N3/////////////////8fDu/1tKM/82IAX/NSAF/4+D + dP////////////////////////////////////////////////////////////////////////////// + //+/uLD/PCgN/zciBv9HNBv/2tbR/+bk4P96bFr/OyYL/zgjCP84Iwj/OCMI/zgjCP88Jw3/ZFM+ejsm + CwAAAAAATz0ljDkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zYhBv9hUDr/zsnC//7+/v//////8fDu/1tK + M/82IAX/NSAF/4+DdP////////////////////////////7+/v/+/f3//v79//7+/v////////////// + //////////////////+/uLD/PCgN/zciBv9JNR3/x8G5/21dSf82IAX/OCMI/zgjCP84Iwj/OCMI/zgj + CP85JAn/Tz0ljAAAAAAAAAAAcGFOREo2HtQ4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP83Igf/emxa//Dv + 7f//////8fDu/1tKM/82IAX/NSAF/4+DdP///////////////////////////7ixp/93aFb/d2lX/4p+ + bv/x7+3///////////////////////////+/ubD/PCgN/zciBv9JNh3/urKo/047I/83IQb/OCMI/zgj + CP84Iwj/OCMI/zgjCP9JNh3Ubl9LRAAAAADU0MsAAAAAAGFRPCROOyO3OiYL/TgjB/84Iwj/OCMI/zci + B/9KNh7/x8G5/8zGv//X087/2tbR/1hHMP82IQX/NSAF/4+DdP///////////////////////////5SJ + e/8yHAH/Mx0C/089Jf/p5+T///////////////////////////+xqZ7/OSQK/zciBv9LNx//u7Oq/088 + JP83Igb/OCMI/zgjCP84Iwf/OiYL/U47I7dhUTwkAAAAANTQywAAAAAAxL61AEUxGQCAcmEUSzggiz8r + Efo4Iwj/OCMI/zciBv9PPSX/u7Oq/048JP9EMRf/UD4m/z4qEP84Iwj/NSAF/4+DdP////////////// + /////////////52Thv82IQb/NyIG/0s4IP+6s6n/y8W+/8rEvP/KxLz/ysS9/7uzqv9hUDv/NyEG/zYh + Bv9RPyj/vLWs/0g1HP83Igf/OCMI/zgjCP8/KxH6Sjcfi31vXRRDLhUAxL61AAAAAAAAAAAAAAAAAAAA + AACFeGgAnJOFCmRUP3w+KhDzOCMI/zciBv9PPCX/urKp/0k2Hf82IQb/NiEG/zgjCP84Iwj/NSAF/4+D + dP///////////////////////////7qyqf86JQr/OCMI/zkkCf8+KhD/PysR/z8rEf8/KxH/PysR/zsm + DP83Igb/OCMI/zUgBP9rXEj/tK2i/zwoDv84Iwj/OCMI/z4qEPNjUz58mY+BCoJ2ZQAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAjIBwAJyShAhJNh61OCMI/zciBv9PPSX/urKp/0c0G/80HgP/NSAF/zYh + Bv83Igf/NSAE/4+DdP///////////////////////////+Xi3/9ZSDH/NB8E/zgjCP84Iwj/OCIH/zgj + B/84Iwf/OCMH/zgjCP84Iwj/NiEG/zsmC/+tpZr/in5u/zYgBf84Iwj/OCMI/0o3HrWckoUIjIBwAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJGEACgRAABUQituOyYL/zciB/9KNh7/x8G5/6GX + iv+BdGP/bV5K/1hHMP9KNx7/OycM/5CEdf/////////////////////////////////Fv7f/XUw2/zwo + Df83Igf/NyIH/zciB/83Igf/NyIH/zciB/85JAn/TDoh/5qPgf+2rqT/RzQb/zciB/84Iwj/OyYL/1RC + K24oEQAAnJKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF1MNgBiUjwtQCsR6Dci + B/86JQr/ZVVA/4p+bv+imIz/same/722rf+/uK//uLGn/9XRy//5+Pj///////////////////////z8 + +//9/fz/7evo/8bAuP+so5j/p52S/6Sajv+imIz/opiM/6Sajv+wp5z/w721/6ifk/9OOyP/NiEG/zgj + CP84Iwj/QCsR6GJSPC1dTDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHhq + VwCNgnIIRTIYszgjCP84Iwj/NiAF/zUgBP82IQb/OyYM/0MwFv9QPSb/ZFQ//3FiT/9+cF//4d3Z//f2 + 9f/c2NP/saie/31vXf98b13/rqWa/9vY0//5+Pf/4+Hd/4x/cP97blz/fG9d/3ttXP9wYU7/VUMs/zom + C/83Igb/OCMI/zgjCP84Iwj/RTIZs46Dcwh4a1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAMG5sAAvGQAAXEo0ejsmDP83Igf/NyIH/zgjCP84Iwj/OCMI/zciB/83IQb/NSAF/zUg + BP82IQX/YE85/21eSv9JNh3/OSQJ/zUgBP81IAT/OCMI/0g0HP9rW0f/YE86/zYgBf81HwT/NSAE/zUg + BP81IAT/NiEG/zgjCP84Iwj/NyIH/zciB/87Jgz/XEs1ei4YAADEvrQAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYRjAAbl5LLFNBKrZEMRfaQi4U9DwnDfw5JQr/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/NiEF/zUgBf83Igf/OCMI/zgjCP84Iwj/OCMI/zciB/81IAX/NiEF/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf87Jgz8QCwS9EIuFdpRPie2bl9MLFhHMQAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARTEYALGpngp6bFoaa1xIO2JS + PWRRPyiMTjsjvzolC/A4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP86JQrwTjsjv088JIxdSzVkZFM+O3FiTxqil4sKNCAGAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAPb19AD///8BnpOGCEIuFWY7JgzwOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsmC/BBLRRmnZOGCP///wH29fQAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkMrAG5dSQJGMxlmPCcN7zgjCP84Iwj/OCMI/zgj + CP84Iwf/OCMI/zsmC/87Jgv/OCMI/zciB/84Iwj/OCMI/zgjCP84Iwj/PCcM70QwF2ZmVkMCUT8oAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFlHMABxYU4CSTYdZT0o + Du84Igf/OCMJ/zwnDP1AKxHmSTYdvV1MNoleTTiJTDkhvUEtE+Y8KA39OCQJ/zciB/88KA3vSDUcZWlb + RgJUQywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABjUj0Af3FiA007InRFMhjvTjsjw11NN3xlVUAyi39vC////wH///8BlIl7C21dSTJjUz58UT8ow0cz + Gu9MOCB0eGpWA19ONwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///gf//8AAP//8A///wAA//AAAA//AAD/4AAAB/8AAP/gAAAH/wAA/8AAAAP/ + AAD/gAAAAf8AAP8AAAAA/wAA/AAAAAA/AADwAAAAAA8AAOAAAAAABwAA4AAAAAAHAADgAAAAAAcAAOAA + AAAABwAA4AAAAAAHAADgAAAAAAcAAOAAAAAABwAAwAAAAAADAACAAAAAAAEAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAIAA + AAAAAQAAgAAAAAABAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAH + AADwAAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAA/gAAAAB/AAD+AAAAAH8AAP8A + AAAA/wAA//AAAA//AAD//AAAP/8AAP/+AAB//wAA//8AAP//AAAoAAAAoAAAAEABAAABACAAAAAAAACQ + AQATCwAAEwsAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AHNjUf9zY1H/////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8Am5CD/z4pD/84Iwj/OCMI/zsnDP+NgXL/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/UD0m/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/0w4IP+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wBvXkv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2dX + RP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI2Bcv87Jgv/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/4FzYv////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8Atq2j/0k1Hf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/6eek/////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AY1Q//zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/X085/9DMxv////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wCNgXL/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/gXNi/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee + k/9DLxf/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9AKxL/m5CD//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8ApJuQ/3ttW/+Ie2r/kIN0/5eMfv+il4z/q6OW/7Ssov+9tq3/yMK6/9DMxv////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AysS+/1hHMP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9YRzD/ycS9/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wDQzMb/yMK6/722rf+0rKL/q6OW/6KXjP+XjH7/kIN0/4h7 + av97bVv/pJuQ/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADolCv84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9CLhT/Sjcf/1NA + KP9fTzn/Xk02/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/X085/1NAKP9KNx//Qi4U/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zolCv////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AGJRPf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKmg + lP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBDLxf/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wB6alj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP96bFr/////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDHwrn/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/8O9tf////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AU0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9QPSb/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8AlYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5eMfv////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKq + oP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/sqqg//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBINRz/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wCDd2X/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OSQJ/1dGLv/Iwrr/yMK6/1dGLv84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/gnRk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS/5uQg/////////////// + ////////p56T/0IuFP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP/MxsD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AWEcw/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/emxa////////////////////////////////////////////iHtq/zkk + Cf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8An5aI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZW + Qv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/dGRT/0Yz + Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/0MzG//// + ///////////////////////////////////////////////////QzMb/Xk02/zgjCP84Iwj/Qi4U/1xL + Nf9pWUX/dGRT/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3ps + Wv96bFr/emxa/3psWv9uYEz/ZlVB/048JP86JQr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Av7ev/z8qEP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// + ////////////////////////////////////////////////////////RjMZ/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf+tpJj///////////////////////////////////////// + ///////////////////////////////////KxL7///////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////8/Lxf+hl4n/Y1M+/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/PyoQ/7+3r/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wC0rKL/V0Yu/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP////////////////////////////////////////////////////////////// + /////////////////////////////3xuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/4l+ + bv////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////iX5u/zsnDP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/V0Yu/7Ss + ov////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9OPCT/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// + ////////opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP/JxL3///////// + //+Ie2r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZlZC//////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////8O9 + tf+2raP/qJ2S/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KX + jP+il4z/opeM/6Sajv+yqZ//wLmx/87Iwv//////////////////////////////////////altG/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk/6eek/////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8Am5CD/0c0G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy///////////////////////Szsj/ycS9/722 + rf+2raP/wryz//////////////////////////////////////////////////////////////////// + /////////////////////////////7Cnnf92Z1X/RjMZ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/1VDLP+Qg3T///////////////////////////+ajoH/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RzQb/52RhP////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCQg3T/QS0T/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////pp2R/0Yz + Gf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9tX0r//////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/QS0T/5CEdf////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AI2Bcv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zsnDP+kmo7///////// + ////////lIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/PCgN/4FzYv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AgXNi/zol + Cv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////VUMs/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////////9mVUH/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/3Nl + Uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AMnEvf9sXEj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////ZlZC/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/qJ2S/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/21dSf/KxL7/////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Awry0/2NT + Pv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////pp2R/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p + Dv////////////////9/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1xLNf+2raP/////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AubOq/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////0c0 + G/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/dGZU/////////////////zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7mxqP////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AKmglP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + /////////////////////////////////////////////6uhlf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9eTTb/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP+poJT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////9lVUD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/k4d3////////////m5CD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AjIBx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + ////////////////////////////////////////////////////////////////////////PCgN/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1VELf///////////9HM + x/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AHtuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OSQJ/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9/cmD/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBvXkv/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////6adkf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/PCgN/21d + Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21d + Sf9tXUn/YlI8/0MvF/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/8S+t////////////1dGLv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/b15L/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AX085/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////+PgnT/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// + ////////////////////////////////////////////////////////////////////////wbux/1E+ + J/85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+onZL///////// + //9wYU3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/19POf////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AFA+Jv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9QPSb/q6OW//////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////gnRk/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9QPib/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBCLhT/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1pIMf+2raP///////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////3ZnVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////8jCuv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv///////////5CEdf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/X043/7ato////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////9pWkX/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////UT4n/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Bc2L///////// + //+YjYD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9wYU3/zsjC//////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OiUK/3xuXf////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////2hXQ/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf///////////7KqoP86JQr/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wDPycL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ//////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////9oV0P/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/PycL/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8AwLmx/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/1tKNf////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////aFdD/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////9bSTT/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/wLmx/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALKpn/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9qW0b///////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf//////////////////////aVpF/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7KqoP////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wCimY3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/empY//////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8AlIh5/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/4d7a/////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+GeWj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/lIp7/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIV3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP+XjH7///////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf//////////////////////lot8/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4Z5aP////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wB1aFT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pp2R//////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////6SbkP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8AZ1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Ssov////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////+yqqD/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEYzGf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP/CvLT///////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf//////////////////////wry0/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AG9eS/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0czH//////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/altG/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJuQ + g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP+bkIP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AMzGwP86JQr/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf///////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ/8jC + uv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wBMOCD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn///////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9JNR3/////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBsXEj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/RTEY//////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////////////////////QzAW/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/2lZRf////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wCXjH7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1RC + Kv////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf///////////////////////////1NAKP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/mI2A/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wDGwLj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+elIb///////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + //////////////////+elIb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf/GwLj/////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ARzQb/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zkkCf9sXEj///////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////25g + TP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/RjMZ/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8AZlZC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9KNx////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf//////////////////////////////////////TToi/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9iUT3/////AP///wD///8A////AP///wD///8A////AP///wD///8Ak4d3/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jwz/zsjC//////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////g3dl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////////////////////9POyP88KA3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5KH + eP////8A////AP///wD///8A////AP///wD///8Aw762/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/opmN//////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// + ////////opeM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wLmx/////wD///8A////AP// + /wD///8A////AEYzGf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cWJP//////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////9iUjz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf////////////////////////////////////////////////92Z1X/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0UxGP////8A////AP///wD///8A////AGNUP/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////////////////////////////////////1E+J/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/YVA7/////wD///8A////AI2Bcv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OycM/9DM + xv////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////ubGo/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// + ////////////////////////PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+PgnT/////AMK8 + tP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////1pIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3tuXP///////////////////////////////////////////////////////////6ad + kf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7+3r/9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////4l8 + bP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+QhHX///////// + ////////////////////////////////////////////////////////d2hV/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9TQCj/lYp9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1A9Jv////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////3tuXP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wryz//////////////////////////////////// + //////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/l4x+//// + /wA7Jwz/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //+5s6r/hnlo/0ArEv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/UT4n//////////////////////////////////////////////////////////////////// + /////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/////wD///8Aemxa/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/w721//////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/8nEvf////////////// + /////////////////////////////////////////////////////////////8K8tP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3ttW/////8A////AM7Iwv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29eS/////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/QCsS/7u1rP////////////////////////////////////////////// + //////////////////////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Py8X/////AP// + /wD///8AYU86/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////w721/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9HNBv/hnlo//// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9eTTb/////AP///wD///8A////ALSsov84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/4p+cP////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/XEs1/722rf////////////////////////////////////////////// + ////////////////////////////////////////////////////////iX5u/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/tKyi/////wD///8A////AP///wD///8ASTYe/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu + Xf////////////////////////////////////////////////////////////////////////////// + /////////////////////////////0EtE/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/////wD///8A////AP// + /wD///8A////AJiNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6adkf////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////w721/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3///////////////////////// + /////////////////////////////////////////////////////////////////////////////6Sb + kP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5qOgf////8A////AP///wD///8A////AP///wD///8AOycM/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9QPib///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/fG5d//////////////////////////////////////////////////// + //////////////////////////////////////////////////9TQCj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwo + Df////8A////AP///wD///8A////AP///wD///8A////AHxuXf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/8G7 + sf////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/3xu + Xf////////////////////////////////////////////////////////////////////////////// + ///////////////////AubH/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP98bl3/////AP///wD///8A////AP// + /wD///8A////AP///wDSzsj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9sXEj///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////w721/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9cSzX/vbat//////////////////// + ////////////////////////////////////////////////////////////////////////aVpF/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/087I/////wD///8A////AP///wD///8A////AP///wD///8A////AGJS + PP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI//////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////8O9tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc/4d7a/////////////////////////////// + /////////////////////////////////////////////zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wC2r6T/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP+He2v///////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////DvbX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/QS0T/722rf////////////////////////////////////////////// + /////////////4Z6af84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/7avpP////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AEo3H/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QCsS//////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////w721/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP87Jwz/ysS+//////////////////////////////////////////////////////9AKxL/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/0k2Hv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wCbkIP/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+kmo7///////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////ubGo/4J0ZP8/KhD/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/1NAKP////////////// + //////////////////////////////////+il4z/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+bkIP/////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADwoDf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/bmBM//////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////3RmVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/wry0//////////////////////////////////// + ////////bmBM/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP88KA3/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wB/cmD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/42Bcv////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/5GGdv///////////////////////////////////////////4yAcf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/f3Fh/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+vp5z///////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP97blz///////// + //////////////////////////////////+tpJj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/0MzG//////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////ubOq/zkkCf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// + ////////zsjC/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wDMxsD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OSQJ//////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf////////////////////////////////////////////////84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/zcjB/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8Aq6GV/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/0w4IP////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////Y1Q//zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + ////////////////////////////////////////Sjcf/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6ujlv////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AIl8bP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9tX0r///////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// + /////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Jfm7/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wBoV0P/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/joRz//////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+DeGb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf////////////////////////////////////////////////+NgXL/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/Z1dE/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8ARzQb/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/7Cnnf////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + ////////////////////////////////////////r6ec/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0YzGf////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP/RzMf///////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// + /////////////9DMxv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AMrEvv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////OSQJ/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/8rGv/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/TToi//////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////////////////////////////////////0w4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+poJT/////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiHtq/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/29e + S/////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// + //////////////////9tX0r/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/h3tr/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AGZVQf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Qg3T///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf//////////////////////////////////////////////////////joRz/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/2ZWQv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wBGMxn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////////////////////////////////////6idkv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/z0p + Dv+2r6T///////////////////////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV//////////////////////////////////// + /////////////722rf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8AycS9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5SKe/////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf///////////////////////////////////////////6KXjP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP/KxL7/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKee + k/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/cGFN//////////////////////////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + /////////////////////////////3VoVP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qaCU//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCGemn/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9UQir///////////////////////////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV/////////////////////////////////1hH + MP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/4h7av////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8AZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0Iu + FP/CvLP///////////////////////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf//////////////////////x8C4/0UxGP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9mVUH/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEUx + GP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OiUK/6KXjP////////////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + ////////p56T/zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/QzAW//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fnFf//////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////q6OW/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP// + /wD///8A////AP///wDIwrr/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9fTjf///////////////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/yMK6/////wD///8A////AP///wD///8A////AP///wD///8A////AHxu + Xf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2 + Hv/Py8X//////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////hXdn/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/fG5d//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Ap56T/zolCv84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tjwk//////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////Z1dE/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////4V3Z/84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/m5CD/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wDEvrf/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9RPif/hnlo/3xuXf////////////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////2VVQP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////+Fd2f/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/3doVf///////////52RhP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9DLxf/xL63/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wBVRC3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9QPSb///////// + /////////////////////////////////////////////////////////////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////9nV0T/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////g3hm/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP93aFX///////// + //+dkYT/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9VRC3/////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AHNj + Uf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////ZVVA/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////3doVf84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/d2hV////////////nZGE/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9zY1H/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Alot8/zgjCP85JAn/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/52RhP///////////8G7sf+7s6v///////////////////////////////////////// + ////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////2dXRP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////9QPSb/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/4FzYv///////////5iNgP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Wi3z/////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wC7s6v/PyoQ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// + //93aFX/OCMI/zgjCP84Iwj/RjMZ/2JSPP98bl3/mI2A/7Ssov/OyML//////zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////9pWUX/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0g1HP////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///JxL3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //+QhHX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/z4pD/+2raP/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wBPPCX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////dGZU/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP9INRz///////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////U0Ao/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/mo6B////////////hXdn/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/088 + Jf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AGpb + Rv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////4J0ZP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/SDUc//// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////CvLT/U0Ao/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/6idkv///////////3BhTf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/2ZVQf////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AiX5u/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// + //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////+OhHP/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zwoDf9tXUn/bV1J/21dSf9tXUn/bV1J/21d + Sf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/21dSf9tXUn/bV1J/2JRPf9FMRj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP/Dvrb///////// + //9XRi7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/4l+bv////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wCvp5z/PCgN/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/////////////////OyYL/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OyYL/6mglP////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AM/Lxf9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/VUMs////////////0MzG/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/STYe/8/Lxf////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFVE + Lf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// + //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + ////////////////////////////////////////////////////////////////////////Qi4U/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/5GGdv///////////5uQ + g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wCpoJT/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + /////////////////////////////////////////////39yYP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP////////////////9fTzn/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/6mglP////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////OyML/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9zZVL/////////////////OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wBiUT3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+dkYT///////// + //93aFX/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////3Bh + Tf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP89KQ7/////////////////gXNi/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9jVD//////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ApJqO/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/nZGE////////////d2hV/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////Qy8X/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/pp2R/////////////////zolCv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/pJuQ/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wA4Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/52RhP///////////3doVf84Iwj/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////8rEvv8+KQ//OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//// + /////////////2ZWQv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8AX043/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+Sh3j///////// + ////////wry0/6adkf+JfGz/bV1J/1A9Jv85JAn/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/jYFy//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////z8nC/0k2Hv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/opeM/////////////////5aLfP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/19ON/////8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJ+WiP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/ZVVA//////////////////////////////////// + ///////////////////AubH/pJqO/4Z6af9qW0b/TToi/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgj + CP84Iwj/OCMI/42Bcv////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////hnlo/zsm + C/84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP9qW0b//////////////////////6ujlv84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP+floj/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8AOCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP+PgnT///////////////////////////////////////////////////////// + /////////////////////////////722rf+gl4v/g3hm/2hXQ/9JNh7/OCMI/zgjCP+NgXL///////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////npSG/2lZRf8/KhD/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUMs/4+CdP///////////////////////////5uQ + g/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP8+KQ//W0k0/3doVf+Uinv/sKme/87Iwv////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////AubH/sqqg/6adkf+il4z/opeM/6KX + jP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+il4z/opeM/6KXjP+imY3/sKme/7+3 + r//OyML//////////////////////////////////////2xcSP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Wkgx/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCZkIL/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/0EtE/9eTTb/emxa/5eMfv+0rKL/0czH//////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////jIBx/zwoDf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/5mQgv////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////ADgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2FPOv98bl3/mZCC/7ewpv////////////////////////////// + //+2r6T///////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////0MzG/6KZjf9lVUD/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wBUQir/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9GMxn/Y1M+/3VoVP9INRz/OCMI/6ujlv////////////// + /////////////////////////////////////////////////////////////62kmP9xYk//PyoQ/z8q + EP9xYk//raSY//////////////////////////////////////////////////////////////////// + ////////sKme/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/emxa/3psWv96bFr/bmBM/2ZV + Qf9OPCT/OyYL/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9UQir/////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AlIh5/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/qJ2S//////////////////////////////////// + //////////////////+il4z/ZlVB/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/WEcw/5SK + e//Py8X/////////////////////////////////////////////////raSY/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/lot8/////wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wA5JAn/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP+elIb////////////////////////////CvLT/iHtq/006Iv84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0c0G/96bFr/tq2j//// + ////////////////////////pJqO/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8ATzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/52R + hP//////r6ec/3NlUv89KQ7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP87Jgv/ZlVB/6KXjP//////opmN/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/048JP////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AI+C + dP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP+Qg3T/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wDOyML/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/0MzG//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wCKfnD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/4yAcf////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl + Uv9JNR3/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qy8X/2ZVQf+NgXL/tq2j/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl + Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP9DLxf/ZlVB/42B + cv+2raP/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDCvLT/m5CD/3Nl + Uv9JNh7/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP85JAn/OCMI/zgj + CP84Iwj/OCMI/0MwFv9mVUH/jYFy/7ato/////8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDAubH/mI2A/3Bh + Tf9CLhT/OSQJ/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Qi4U/3BhTf+YjYD/wLmx/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AEw4IP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/STYe/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8ATToi/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Sjcf/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wBRPif/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/Tjwk/////wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFNA + KP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/Tzwl//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AVEIq/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/UD0m/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wBYRzD/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/VEIq/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AFpIMf84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/VUQt/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AW0k0/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/V0Yu/////wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wBcTDb/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/QCsS/3ZnVf+wqZ7/sKme/3ZnVf9AKxL/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEw2//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AGFQO/84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/0k2Hv+Bc2L/vbat/////wD///8A////AP// + /wD///8A////AMnEvf+NgXL/Tzwl/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/XEs1/////wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8AYlE9/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zkkCf84Iwj/OCMI/zkk + Cf9fTzn/m5CD/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8Ap56T/21dSf87Jgv/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgj + CP84Iwj/X043/////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wBjVD//OCMI/zgj + CP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP8/KhD/c2VS/6+nnP////8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AL22 + rf+Bc2L/Qy8X/zgjCP84Iwj/OCMI/zgjCP84Iwj/OCMI/zgjCP84Iwj/X085/////wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AGpbRv84Iwj/OCMI/zgjCP84Iwj/UD4m/4h7 + av/CvLT/////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDPy8X/lIp7/1dG + Lv84Iwj/OCMI/zgjCP84Iwj/ZlVB/////wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8AbV9K/19POf+bkIP/////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AKeek/9tXUn/bFtH//// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// + //////////5/////////////////////////+B/////////////////////////gB/////////////// + /////////8AD////////////////////////AAD///////////////////////wAAD////////////// + ////////+AAAD//////////////////////gAAAH/////////////////////4AAAAH///////////// + ////wAf+AAAAAH/gA//////////////AAAAAAAAAAAAD/////////////4AAAAAAAAAAAAH///////// + ////AAAAAAAAAAAAAP////////////8AAAAAAAAAAAAA/////////////gAAAAAAAAAAAAB///////// + ///8AAAAAAAAAAAAAD////////////wAAAAAAAAAAAAAP///////////+AAAAAAAAAAAAAAf//////// + ///4AAAAAAAAAAAAAB////////////AAAAAAAAAAAAAAD///////////4AAAAAAAAAAAAAAH//////// + ///gAAAAAAAAAAAAAAf//////////8AAAAAAAAAAAAAAA///////////gAAAAAAAAAAAAAAB//////// + //+AAAAAAAAAAAAAAAH//////////wAAAAAAAAAAAAAAAP/////////+AAAAAAAAAAAAAAAAf/////// + //gAAAAAAAAAAAAAAAAf////////4AAAAAAAAAAAAAAAAAf///////+AAAAAAAAAAAAAAAAAAf////// + /gAAAAAAAAAAAAAAAAAAf//////4AAAAAAAAAAAAAAAAAAAf/////+AAAAAAAAAAAAAAAAAAAAf///// + AAAAAAAAAAAAAAAAAAAAAP////wAAAAAAAAAAAAAAAAAAAAAP///+AAAAAAAAAAAAAAAAAAAAAAf///4 + AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 + AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 + AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///+AAAAAAAAAAAAAAAAAAAAAAf///4 + AAAAAAAAAAAAAAAAAAAAAB////gAAAAAAAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAAAAAAAP///w + AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w + AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAP///w + AAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAAAAAAAH///A + AAAAAAAAAAAAAAAAAAAAAAP//4AAAAAAAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAB//8A + AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA + AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAAAAAAAAAAAAAAAD+AA + AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAA4AA + AAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAYAA + AAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAA+AA + AAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAD/AA + AAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA + AAAAAAAAAAAAAAAAAAAAAAAf/AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/4A + AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAAAAAAA//8A + AAAAAAAAAAAAAAAAAAAAAAD//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A + AAAAAAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAAAAAAAf/4A + AAAAAAAAAAAAAAAAAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA + AAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAAAAAAAAAAAAAAAP/wA + AAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA + AAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAH/gA + AAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAH/wA + AAAAAAAAAAAAAAAAAAAAAAA//gAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAAAAAAAAAAAAAAAB///A + AAAAAAAAAAAAAAAAAAAAAAP//+AAAAAAAAAAAAAAAAAAAAAAB///8AAAAAAAAAAAAAAAAAAAAAAP///8 + AAAAAAAAAAAAAAAAAAAAAD////4AAAAAAAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAAAAAAD///// + gAAAAAAAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAAAAAAD//////AAAAAAAAAAAAAAAAAAAA////// + 8AAAAAAAAAAAAAAAAAAAD//////4AAAAAAAAAAAAAAAAAAAf//////gAAAAAAAAAAAAAAAAAAB////// + +AAAAAAAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAAAAAAA///////wAAAAAAAAAAAAAAAAAAD////// + /AAAAAAAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAAAAAAB///////4AAAAAAAAAAAAAAAAAAH////// + /gAAAAAAAAAAAAAAAAAAf///////AAAAAAAAAAAAAAAAAAD///////8AAAAAAAAAAAAAAAAAAP////// + /wAAAAAAAAAAAAAAAAAA////////gAAAAAAAAAAAAAAAAAH///////+AAAAAAAAAAAAAAAAAAf////// + /4AAAAAAAAAAAAAAAAAB////////gAAAAAAAAAAAAAAAAAH////////AAAAAAAAAAAAAAAAAA/////// + /8AAAAAAAAAAAAAAAAAD////////8AAAAAAAAAAAAAAAAA//////////gAAAAAAAAAAAAAAB//////// + ///8AAAAAAAAAAAAAD/////////////gAAAAAAAAAAAH//////////////4AAAAAAAAAAH////////// + /////wAAAAAAAAAA////////////////gAAAAAAAAAH////////////////AAAAAAAAAA/////////// + /////+AAAAAAAAAH////////////////8AAAAAAAAA/////////////////4AAAAAAAAH/////////// + //////wAAAAAAAA//////////////////gAAAAAAAH//////////////////AAAH4AAA//////////// + //////+AAH/+AAH//////////////////8AD///AA///////////////////4B////gH//////////// + ///////x/////4////////// + + + \ No newline at end of file diff --git a/TGControlPanel/Program.cs b/TGControlPanel/Program.cs new file mode 100644 index 0000000000..4b49fa8113 --- /dev/null +++ b/TGControlPanel/Program.cs @@ -0,0 +1,63 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + static class Program + { + [STAThread] + static void Main(string [] args) + { + try + { + var res = Server.VerifyConnection(); + if (res != null) + { + MessageBox.Show("Unable to connect to service! Error: " + res); + return; + } + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Main()); + return; + } + catch (Exception e) + { + ServiceDisconnectException(e); + } + finally + { + Properties.Settings.Default.Save(); + } + } + + public static void ServiceDisconnectException(Exception e) + { + MessageBox.Show("An unhandled exception occurred. This usually means we lost connection to the service. Error" + e.ToString()); + } + + public static string TextPrompt(string caption, string text) + { + Form prompt = new Form() + { + Width = 500, + Height = 150, + FormBorderStyle = FormBorderStyle.FixedDialog, + Text = caption, + StartPosition = FormStartPosition.CenterScreen + }; + Label textLabel = new Label() { Left = 50, Top = 20, Text = text, AutoSize = true }; + TextBox textBox = new TextBox() { Left = 50, Top = 50, Width = 400 }; + Button confirmation = new Button() { Text = "Ok", Left = 350, Width = 100, Top = 70, DialogResult = DialogResult.OK }; + confirmation.Click += (sender, e) => { prompt.Close(); }; + prompt.Controls.Add(textBox); + prompt.Controls.Add(confirmation); + prompt.Controls.Add(textLabel); + prompt.AcceptButton = confirmation; + + return prompt.ShowDialog() == DialogResult.OK ? textBox.Text : null; + } + } +} diff --git a/TGControlPanel/Properties/AssemblyInfo.cs b/TGControlPanel/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1e7533ac0c --- /dev/null +++ b/TGControlPanel/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TGStation Server Control Panel")] +[assembly: AssemblyDescription("Control panel for the TG Station Server Service")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("394e7643-6b8c-416f-ab18-95ac12648cdc")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("3.0.78.0")] +[assembly: AssemblyFileVersion("3.0.78.0")] diff --git a/TGControlPanel/Properties/Settings.Designer.cs b/TGControlPanel/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..c5a46a486f --- /dev/null +++ b/TGControlPanel/Properties/Settings.Designer.cs @@ -0,0 +1,50 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TGControlPanel.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public int LastPageIndex { + get { + return ((int)(this["LastPageIndex"])); + } + set { + this["LastPageIndex"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public int LastConfigPageIndex { + get { + return ((int)(this["LastConfigPageIndex"])); + } + set { + this["LastConfigPageIndex"] = value; + } + } + } +} diff --git a/TGControlPanel/Properties/Settings.settings b/TGControlPanel/Properties/Settings.settings new file mode 100644 index 0000000000..36129940b3 --- /dev/null +++ b/TGControlPanel/Properties/Settings.settings @@ -0,0 +1,12 @@ + + + + + + 0 + + + 0 + + + \ No newline at end of file diff --git a/TGControlPanel/RepoPage.cs b/TGControlPanel/RepoPage.cs new file mode 100644 index 0000000000..eea2cb310e --- /dev/null +++ b/TGControlPanel/RepoPage.cs @@ -0,0 +1,342 @@ +using System; +using System.ComponentModel; +using System.Windows.Forms; +using System.Threading; +using TGServiceInterface; + +namespace TGControlPanel +{ + partial class Main + { + enum RepoAction { + Clone, + Checkout, + Update, + Merge, + Reset, + Test, + Wait, + GenCL, + } + + RepoAction action; + string CloneRepoURL; + string CheckoutBranch; + int TestPR; + + string repoError; + + private void InitRepoPage() + { + RepoBGW.ProgressChanged += RepoBGW_ProgressChanged; + RepoBGW.RunWorkerCompleted += RepoBGW_RunWorkerCompleted; + RepoBGW.DoWork += RepoBGW_DoWork; + BackupTagsList.MouseDoubleClick += BackupTagsList_MouseDoubleClick; + PopulateRepoFields(); + } + + private void BackupTagsList_MouseDoubleClick(object sender, MouseEventArgs e) + { + int index = BackupTagsList.IndexFromPoint(e.Location); + if (index != ListBox.NoMatches) + { + var indexText = (string)BackupTagsList.Items[index]; + if (indexText == "None" || indexText == "Unknown") + return; + var tagname = indexText.Split(':')[0]; + var spaceSplits = indexText.Split(' '); + var sha = spaceSplits[spaceSplits.Length - 1]; + + if (MessageBox.Show(String.Format("Checkout tag {0} ({1})?", tagname, sha), "Restore Backup", MessageBoxButtons.YesNo) != DialogResult.Yes) + return; + + CheckoutBranch = tagname; + DoAsyncOp(RepoAction.Checkout, "Checking out " + tagname + "..."); + } + } + + private void RepoBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + RepoProgressBar.Value = 100; + RepoProgressBar.Style = ProgressBarStyle.Blocks; + RepoPanel.UseWaitCursor = false; + PopulateRepoFields(); + } + + private void PopulateRepoFields() + { + if (repoError != null) + MessageBox.Show("An error occured: " + repoError); + + if (RepoBusyCheck()) + return; + + var Repo = Server.GetComponent(); + + RepoProgressBar.Style = ProgressBarStyle.Marquee; + RepoProgressBar.Visible = false; + RemoteNameTitle.Visible = true; + RepoRemoteTextBox.Visible = true; + BranchNameTitle.Visible = true; + RepoBranchTextBox.Visible = true; + PythonPathLabel.Visible = true; + PythonPathText.Visible = true; + PythonPathText.Text = Repo.PythonPath(); + + if (!Repo.Exists()) + { + //repo unavailable + RepoRemoteTextBox.Text = "https://github.com/tgstation/tgstation"; + RepoBranchTextBox.Text = "master"; + RepoProgressBarLabel.Text = "Unable to locate repository"; + CloneRepositoryButton.Visible = true; + } + else + { + RepoProgressBarLabel.Visible = false; + + CurrentRevisionLabel.Visible = true; + CurrentRevisionTitle.Visible = true; + IdentityLabel.Visible = true; + MergePRButton.Visible = true; + TestMergeListLabel.Visible = true; + TestMergeListTitle.Visible = true; + UpdateRepoButton.Visible = true; + BackupTagsList.Visible = true; + HardReset.Visible = true; + RepoApplyButton.Visible = true; + TestmergeSelector.Visible = true; + RepoGenChangelogButton.Visible = true; + RecloneButton.Visible = true; + ResetRemote.Visible = true; + + CurrentRevisionLabel.Text = Repo.GetHead(out string error) ?? "Unknown"; + RepoRemoteTextBox.Text = Repo.GetRemote(out error) ?? "Unknown"; + RepoBranchTextBox.Text = Repo.GetBranch(out error) ?? "Unknown"; + + var Backups = Repo.ListBackups(out error); + BackupTagsList.Items.Clear(); + if (Backups != null) + { + if (Backups.Count == 0) + BackupTagsList.Items.Add("None"); + else + foreach (var I in Backups) + BackupTagsList.Items.Add(I.Key + ": " + I.Value); + } + else + BackupTagsList.Items.Add("Unknown"); + + var PRs = Repo.MergedPullRequests(out error); + TestMergeListLabel.Items.Clear(); + if (PRs != null) + if (PRs.Count == 0) + TestMergeListLabel.Items.Add("None"); + else + foreach (var I in PRs) + TestMergeListLabel.Items.Add(String.Format("#{0}: {2} by {3} at commit {1}\r\n", I.Number, I.Sha, I.Title, I.Author)); + else + TestMergeListLabel.Items.Add("Unknown"); + } + } + + bool RepoBusyCheck() + { + if (Server.GetComponent().OperationInProgress()) + { + DoAsyncOp(RepoAction.Wait, "Waiting for repository to finish another action..."); + return true; + } + return false; + } + private void RepoBGW_ProgressChanged(object sender, ProgressChangedEventArgs e) + { + var val = e.ProgressPercentage; + if (val < 0) + { + RepoProgressBar.Style = ProgressBarStyle.Marquee; + return; + } + RepoProgressBar.Style = ProgressBarStyle.Blocks; + RepoProgressBar.Value = val; + } + + private void RepoBGW_DoWork(object sender, DoWorkEventArgs e) + { + //Only for clones + var Repo = Server.GetComponent(); + + switch (action) { + case RepoAction.Clone: + repoError = Repo.Setup(CloneRepoURL, CheckoutBranch); + break; + case RepoAction.Checkout: + repoError = Repo.Checkout(CheckoutBranch); + break; + case RepoAction.Merge: + repoError = Repo.Update(false); + break; + case RepoAction.Reset: + repoError = Repo.Reset(true); + break; + case RepoAction.Test: + repoError = Repo.MergePullRequest(TestPR); + break; + case RepoAction.Update: + repoError = Repo.Update(true); + break; + case RepoAction.Wait: + break; + case RepoAction.GenCL: + var result = Repo.GenerateChangelog(out repoError); + if(repoError != null) + repoError += ": " + result; + break; + default: + //reeee + return; + } + + do + { + Thread.Sleep(1000); + RepoBGW.ReportProgress(Repo.CheckoutProgress()); + } while (Repo.OperationInProgress()); + } + void UpdatePythonPath() + { + if (!Server.GetComponent().SetPythonPath(PythonPathText.Text)) + MessageBox.Show("Python could not be found in the selected location!"); + } + private void CloneRepositoryButton_Click(object sender, EventArgs e) + { + CloneRepo(); + } + + void CloneRepo() + { + CloneRepoURL = RepoRemoteTextBox.Text; + CheckoutBranch = RepoBranchTextBox.Text; + UpdatePythonPath(); + + DoAsyncOp(RepoAction.Clone, String.Format("Cloning {0} branch of {1}...", CheckoutBranch, CloneRepoURL)); + } + private void RecloneButton_Click(object sender, EventArgs e) + { + var DialogResult = MessageBox.Show("This will re-clone the repository, backup, and reset the Static configuration folders. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + CloneRepo(); + } + void DoAsyncOp(RepoAction ra, string message) + { + if (ra != RepoAction.Wait && RepoBusyCheck()) + return; + + CurrentRevisionLabel.Visible = false; + CurrentRevisionTitle.Visible = false; + TestMergeListLabel.Visible = false; + TestMergeListTitle.Visible = false; + RepoApplyButton.Visible = false; + UpdateRepoButton.Visible = false; + MergePRButton.Visible = false; + CloneRepositoryButton.Visible = false; + RemoteNameTitle.Visible = false; + RepoRemoteTextBox.Visible = false; + BranchNameTitle.Visible = false; + RepoBranchTextBox.Visible = false; + RepoProgressBar.Visible = true; + HardReset.Visible = false; + IdentityLabel.Visible = false; + TestmergeSelector.Visible = false; + RepoGenChangelogButton.Visible = false; + PythonPathLabel.Visible = false; + PythonPathText.Visible = false; + RecloneButton.Visible = false; + ResetRemote.Visible = false; + BackupTagsList.Visible = false; + + RepoPanel.UseWaitCursor = true; + + RepoProgressBar.Value = 0; + RepoProgressBar.Style = ProgressBarStyle.Marquee; + + RepoProgressBarLabel.Text = message; + RepoProgressBarLabel.Visible = true; + + action = ra; + repoError = null; + + RepoBGW.RunWorkerAsync(); + } + private void RepoApplyButton_Click(object sender, EventArgs e) + { + var Repo = Server.GetComponent(); + + if (RepoBusyCheck()) + return; + + var remote = Repo.GetRemote(out string error); + if (remote == null) { + MessageBox.Show("Error: " + error); + return; + } + + var Reclone = remote != RepoRemoteTextBox.Text; + if (Reclone) + { + var DialogResult = MessageBox.Show("Changing the remote URL requires a re-cloning of the repository. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + } + + if (!Reclone) + { + var branch = Repo.GetBranch(out error); + if(branch == null) + { + MessageBox.Show("Error: " + error); + return; + } + + CheckoutBranch = RepoBranchTextBox.Text; + if(branch != CheckoutBranch) + DoAsyncOp(RepoAction.Checkout, String.Format("Checking out {0}...", CheckoutBranch)); + + UpdatePythonPath(); + } + else + CloneRepositoryButton_Click(null, null); + } + private void UpdateRepoButton_Click(object sender, EventArgs e) + { + DoAsyncOp(RepoAction.Merge, "Merging origin branch..."); + } + + private void HardReset_Click(object sender, EventArgs e) + { + DoAsyncOp(RepoAction.Reset, "Resetting to origin branch..."); + } + + private void ResetRemote_Click(object sender, EventArgs e) + { + DoAsyncOp(RepoAction.Update, "Updating and resetting to remote branch..."); + } + + private void TestMergeButton_Click(object sender, EventArgs e) + { + if (TestmergeSelector.Value == 0) + { + MessageBox.Show("Invalid PR number!"); + return; + } + TestPR = (int)TestmergeSelector.Value; + DoAsyncOp(RepoAction.Test, String.Format("Merging latest commit of PR #{0}...", TestPR)); + } + + private void RepoGenChangelogButton_Click(object sender, System.EventArgs e) + { + DoAsyncOp(RepoAction.GenCL, "Generating changelog..."); + } + } +} diff --git a/TGControlPanel/ServerPage.cs b/TGControlPanel/ServerPage.cs new file mode 100644 index 0000000000..83ff322b3c --- /dev/null +++ b/TGControlPanel/ServerPage.cs @@ -0,0 +1,411 @@ +using System; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + partial class Main + { + enum FullUpdateAction + { + UpdateHard, + UpdateMerge, + UpdateHardTestmerge, + Reset, + Testmerge, + } + + FullUpdateAction fuAction; + int testmergePR; + string updateError; + bool updatingFields = false; + + string DDStatusString = null; + void InitServerPage() + { + LoadServerPage(); + ServerTimer.Start(); + WorldStatusChecker.RunWorkerAsync(); + WorldStatusChecker.RunWorkerCompleted += WorldStatusChecker_RunWorkerCompleted; + FullUpdateWorker.RunWorkerCompleted += FullUpdateWorker_RunWorkerCompleted; + ServerPathTextbox.LostFocus += ServerPathTextbox_LostFocus; + ServerPathTextbox.KeyDown += ServerPathTextbox_KeyDown; + projectNameText.LostFocus += ProjectNameText_LostFocus; + projectNameText.KeyDown += ProjectNameText_KeyDown; + } + + private void ProjectNameText_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + UpdateProjectName(); + } + + private void ServerPathTextbox_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + UpdateServerPath(); + } + + private void FullUpdateWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) + { + if (updateError != null) + MessageBox.Show(updateError); + UpdateHardButton.Enabled = true; + UpdateMergeButton.Enabled = true; + TestmergeButton.Enabled = true; + UpdateTestmergeButton.Enabled = true; + LoadServerPage(); + ServerTimer.Start(); + } + + private void WorldStatusChecker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) + { + if (DDStatusString != "Topic recieve error!" || ServerStatusLabel.Text == "OFFLINE") + ServerStatusLabel.Text = DDStatusString; + WorldStatusTimer.Start(); + } + + private void CompileCancelButton_Click(object sender, EventArgs e) + { + var res = Server.GetComponent().Cancel(); + if (res != null) + MessageBox.Show(res); + LoadServerPage(); + } + + private void ServerPathTextbox_LostFocus(object sender, EventArgs e) + { + UpdateServerPath(); + } + + void UpdateServerPath() + { + var Config = Server.GetComponent(); + if (updatingFields || ServerPathTextbox.Text.Trim() == Config.ServerDirectory()) + return; + var DialogResult = MessageBox.Show("This will move the entire server installation.", "Confim", MessageBoxButtons.YesNo); + if (DialogResult != DialogResult.Yes) + return; + MessageBox.Show(Config.MoveServer(ServerPathTextbox.Text) ?? "Success!"); + } + + void LoadServerPage() + { + var RepoExists = Server.GetComponent().Exists(); + compileButton.Visible = RepoExists; + initializeButton.Visible = RepoExists; + NudgePortSelector.Visible = RepoExists; + AutostartCheckbox.Visible = RepoExists; + PortSelector.Visible = RepoExists; + projectNameText.Visible = RepoExists; + compilerProgressBar.Visible = RepoExists; + CompilerStatusLabel.Visible = RepoExists; + CompileCancelButton.Visible = RepoExists; + NudgePortLabel.Visible = RepoExists; + CompilerLabel.Visible = RepoExists; + ProjectPathLabel.Visible = RepoExists; + ServerPRLabel.Visible = RepoExists; + ServerGStopButton.Visible = RepoExists; + ServerStartButton.Visible = RepoExists; + ServerGRestartButton.Visible = RepoExists; + ServerRestartButton.Visible = RepoExists; + PortLabel.Visible = RepoExists; + ServerStopButton.Visible = RepoExists; + TestmergeButton.Visible = RepoExists; + ServerTestmergeInput.Visible = RepoExists; + UpdateHardButton.Visible = RepoExists; + UpdateMergeButton.Visible = RepoExists; + UpdateTestmergeButton.Visible = RepoExists; + ResetTestmerge.Visible = RepoExists; + + var DM = Server.GetComponent(); + var DD = Server.GetComponent(); + var Config = Server.GetComponent(); + + if (updatingFields) + return; + + updatingFields = true; + + if (!ServerPathTextbox.Focused) + ServerPathTextbox.Text = Config.ServerDirectory(); + + if (!RepoExists) + { + updatingFields = false; + return; + } + + AutostartCheckbox.Checked = DD.Autostart(); + if (!PortSelector.Focused) + PortSelector.Value = DD.Port(); + if (!projectNameText.Focused) + projectNameText.Text = DM.ProjectName(); + + VisibilitySelector.SelectedIndex = (int)DD.VisibilityLevel(); + SecuritySelector.SelectedIndex = (int)DD.SecurityLevel(); + + var val = Config.InteropPort(out string error); + if (error != null) + { + updatingFields = false; + NudgePortSelector.Value = 4567; + MessageBox.Show("Error (I will try and recover): " + error); + } + else + { + updatingFields = false; + NudgePortSelector.Value = val; + } + + switch (DM.GetStatus()) + { + case TGCompilerStatus.Compiling: + CompilerStatusLabel.Text = "Compiling..."; + compilerProgressBar.Style = ProgressBarStyle.Marquee; + compileButton.Enabled = false; + initializeButton.Enabled = false; + CompileCancelButton.Enabled = true; + break; + case TGCompilerStatus.Initializing: + CompilerStatusLabel.Text = "Initializing..."; + compilerProgressBar.Style = ProgressBarStyle.Marquee; + compileButton.Enabled = false; + initializeButton.Enabled = false; + CompileCancelButton.Enabled = false; + break; + case TGCompilerStatus.Initialized: + CompilerStatusLabel.Text = "Idle"; + compilerProgressBar.Style = ProgressBarStyle.Blocks; + initializeButton.Enabled = true; + compileButton.Enabled = true; + CompileCancelButton.Enabled = false; + break; + case TGCompilerStatus.Uninitialized: + CompilerStatusLabel.Text = "Uninitialized"; + compilerProgressBar.Style = ProgressBarStyle.Blocks; + compileButton.Enabled = false; + initializeButton.Enabled = true; + CompileCancelButton.Enabled = false; + break; + default: + CompilerStatusLabel.Text = "Unknown!"; + compilerProgressBar.Style = ProgressBarStyle.Blocks; + initializeButton.Enabled = true; + compileButton.Enabled = true; + CompileCancelButton.Enabled = true; + break; + } + error = DM.CompileError(); + if (error != null) + MessageBox.Show("Error: " + error); + } + + private void ServerTimer_Tick(object sender, System.EventArgs e) + { + try + { + LoadServerPage(); + } + catch (Exception ex) + { + Program.ServiceDisconnectException(ex); + } + } + private void ProjectNameText_LostFocus(object sender, EventArgs e) + { + UpdateProjectName(); + } + + void UpdateProjectName() + { + if (!updatingFields) + Server.GetComponent().SetProjectName(projectNameText.Text); + } + + private void PortSelector_ValueChanged(object sender, EventArgs e) + { + if(!updatingFields) + Server.GetComponent().SetPort((ushort)PortSelector.Value); + } + + private void RunServerUpdate(FullUpdateAction fua, int tm = 0) + { + if (FullUpdateWorker.IsBusy) + return; + testmergePR = tm; + fuAction = fua; + initializeButton.Enabled = false; + compileButton.Enabled = false; + UpdateHardButton.Enabled = false; + UpdateMergeButton.Enabled = false; + TestmergeButton.Enabled = false; + UpdateTestmergeButton.Enabled = false; + compilerProgressBar.Style = ProgressBarStyle.Marquee; + switch (fuAction) + { + case FullUpdateAction.Testmerge: + CompilerStatusLabel.Text = String.Format("Testmerging pull request #{0}...", testmergePR); + break; + case FullUpdateAction.UpdateHard: + CompilerStatusLabel.Text = String.Format("Updating Server (RESET)..."); + break; + case FullUpdateAction.UpdateMerge: + CompilerStatusLabel.Text = String.Format("Updating Server (MERGE)..."); + break; + case FullUpdateAction.UpdateHardTestmerge: + CompilerStatusLabel.Text = String.Format("Updating and testmerging pull request #{0}...", testmergePR); + break; + } + ServerTimer.Stop(); + FullUpdateWorker.RunWorkerAsync(); + } + + private void InitializeButton_Click(object sender, EventArgs e) + { + if (!Server.GetComponent().Initialize()) + MessageBox.Show("Unable to start initialization!"); + LoadServerPage(); + } + private void CompileButton_Click(object sender, EventArgs e) + { + if(!Server.GetComponent().Compile()) + MessageBox.Show("Unable to start compilation!"); + LoadServerPage(); + } + //because of lol byond this can take some time... + private void WorldStatusChecker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) + { + try + { + if (!Server.GetComponent().Exists()) + DDStatusString = "NOT INSTALLED"; + else + DDStatusString = Server.GetComponent().StatusString(true); + } + catch + { + DDStatusString = "ERROR"; + } + } + + private void WorldStatusTimer_Tick(object sender, System.EventArgs e) + { + WorldStatusTimer.Stop(); + WorldStatusChecker.RunWorkerAsync(); + } + + private void AutostartCheckbox_CheckedChanged(object sender, System.EventArgs e) + { + if(!updatingFields) + Server.GetComponent().SetAutostart(AutostartCheckbox.Checked); + } + private void ServerStartButton_Click(object sender, System.EventArgs e) + { + var res = Server.GetComponent().Start(); + if (res != null) + MessageBox.Show(res); + } + + private void ServerStopButton_Click(object sender, System.EventArgs e) + { + var DialogResult = MessageBox.Show("This will immediately shut down the server. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + var res = Server.GetComponent().Stop(); + if (res != null) + MessageBox.Show(res); + } + + private void ServerRestartButton_Click(object sender, System.EventArgs e) + { + var DialogResult = MessageBox.Show("This will immediately restart the server. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + var res = Server.GetComponent().Restart(); + if (res != null) + MessageBox.Show(res); + } + + private void ServerGStopButton_Click(object sender, System.EventArgs e) + { + var DialogResult = MessageBox.Show("This will shut down the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + Server.GetComponent().RequestStop(); + } + + private void ServerGRestartButton_Click(object sender, System.EventArgs e) + { + var DialogResult = MessageBox.Show("This will restart the server when the current round ends. Continue?", "Confim", MessageBoxButtons.YesNo); + if (DialogResult == DialogResult.No) + return; + Server.GetComponent().RequestRestart(); + } + private void FullUpdateWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) + { + var Updater = Server.GetComponent(); + switch (fuAction) + { + case FullUpdateAction.Testmerge: + updateError = Updater.UpdateServer(TGRepoUpdateMethod.None, false, (ushort)testmergePR); + break; + case FullUpdateAction.UpdateHard: + updateError = Updater.UpdateServer(TGRepoUpdateMethod.Hard, true); + break; + case FullUpdateAction.UpdateHardTestmerge: + updateError = Updater.UpdateServer(TGRepoUpdateMethod.Hard, true, (ushort)testmergePR); + break; + case FullUpdateAction.UpdateMerge: + updateError = Updater.UpdateServer(TGRepoUpdateMethod.Merge, true, (ushort)testmergePR); + break; + case FullUpdateAction.Reset: + updateError = Updater.UpdateServer(TGRepoUpdateMethod.Reset, false, 0); + break; + } + } + private void ResetTestmerge_Click(object sender, EventArgs e) + { + RunServerUpdate(FullUpdateAction.Reset); + } + + private void UpdateHardButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.UpdateHard); + } + + private void UpdateTestmergeButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.UpdateHardTestmerge, (int)ServerTestmergeInput.Value); + } + + private void UpdateMergeButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.UpdateMerge); + } + private void TestmergeButton_Click(object sender, System.EventArgs e) + { + RunServerUpdate(FullUpdateAction.Testmerge, (int)ServerTestmergeInput.Value); + } + + private void NudgePortSelector_ValueChanged(object sender, EventArgs e) + { + if (!updatingFields) + Server.GetComponent().SetInteropPort((ushort)NudgePortSelector.Value); + } + + private void SecuritySelector_SelectedIndexChanged(object sender, EventArgs e) + { + if (!updatingFields) + if (!Server.GetComponent().SetSecurityLevel((TGDreamDaemonSecurity)SecuritySelector.SelectedIndex)) + MessageBox.Show("Security change will be applied after next server reboot."); + } + + private void VisibilitySelector_SelectedIndexChanged(object sender, EventArgs e) + { + if (!updatingFields) + if(!Server.GetComponent().SetVisibility((TGDreamDaemonVisibility)VisibilitySelector.SelectedIndex)) + MessageBox.Show("Visibility change will be applied after next server reboot."); + } + } +} diff --git a/TGControlPanel/TGControlPanel.csproj b/TGControlPanel/TGControlPanel.csproj new file mode 100644 index 0000000000..541b791e95 --- /dev/null +++ b/TGControlPanel/TGControlPanel.csproj @@ -0,0 +1,100 @@ + + + + + Debug + AnyCPU + {394E7643-6B8C-416F-AB18-95AC12648CDC} + WinExe + TGControlPanel + TGControlPanel + v4.5.2 + 512 + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + tgs.ico + + + + + + C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Windows.Forms.dll + + + + + Form + + + Form + + + Component + + + Form + + + Form + + + Main.cs + + + + + True + True + Settings.settings + + + Form + + + Form + + + Main.cs + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} + TGServiceInterface + + + + + + + \ No newline at end of file diff --git a/TGControlPanel/tgs.ico b/TGControlPanel/tgs.ico new file mode 100644 index 0000000000000000000000000000000000000000..6ed69fadfcdf8deb049f66dfffc46d5361332feb GIT binary patch literal 123198 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 $destination_md5sha diff --git a/TGServerService/App.config b/TGServerService/App.config new file mode 100644 index 0000000000..d7426e62f3 --- /dev/null +++ b/TGServerService/App.config @@ -0,0 +1,126 @@ + + + + +
+ + + + + + + + + tgstation + + + 1337 + + + False + + + tgstation-server + + + tgstation-server@tgstation13.org + + + C:/tgstation-server-3 + + + 0 + + + 2 + + + False + + + False + + + C:\Python27 + + + 0 + + + NEEDS INITIALIZING + + + + + theghostofwhibyl1 + + + + + + + #botbus + + + + + #botbus + + + John Madden + + + + + https://github.com/tgstation/tgstation.git + + + tgstation + + + 2337 + + + master + + + False + + + C:\Program Files (x86)\BYOND\bin + + + tgstation-server + + + tgstation-server@users.noreply.github.com + + + tgstation-server + + + Insufficient data for meaningful answer + + + hunter2 + + + 8000 + + + + + + + + + + + + + + + + diff --git a/TGServerService/Byond.cs b/TGServerService/Byond.cs new file mode 100644 index 0000000000..7b223e14e1 --- /dev/null +++ b/TGServerService/Byond.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Text.RegularExpressions; +using System.Threading; +using TGServiceInterface; + +namespace TGServerService +{ + partial class TGStationServer : ITGByond + { + const string ByondDirectory = "BYOND"; + const string StagingDirectory = "BYOND_staged"; + const string StagingDirectoryInner = "BYOND_staged/byond"; + const string RevisionDownloadPath = "BYONDRevision.zip"; + const string VersionFile = "/byond_version.dat"; + const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; + const string ByondLatestURL = "https://secure.byond.com/download/build/LATEST/"; + + const string ByondConfigDir = "BYOND_staged/BYOND/cfg"; + const string ByondDDConfig = "/daemon.txt"; + const string ByondNoPromptTrustedMode = "trusted-check 0"; + + TGByondStatus updateStat = TGByondStatus.Idle; + object ByondLock = new object(); + string lastError; + + Thread RevisionStaging; + + //Just cleanup + void InitByond() + { + CleanByondStaging(); + } + + void CleanByondStaging() + { + //linger not + if (File.Exists(RevisionDownloadPath)) + File.Delete(RevisionDownloadPath); + Program.DeleteDirectory(StagingDirectory); + } + + //Kill the thread and cleanup again + void DisposeByond() + { + lock (ByondLock) + { + if (RevisionStaging != null) + RevisionStaging.Abort(); + CleanByondStaging(); + } + } + + //requires ByondLock to be locked + bool BusyCheckNoLock() + { + switch (updateStat) + { + default: + case TGByondStatus.Starting: + case TGByondStatus.Downloading: + case TGByondStatus.Staging: + case TGByondStatus.Updating: + return true; + case TGByondStatus.Idle: + case TGByondStatus.Staged: + return false; + } + } + + //public api + public TGByondStatus CurrentStatus() + { + lock (ByondLock) + { + return updateStat; + } + } + + //public api + public string GetError() + { + lock (ByondLock) + { + var error = lastError; + lastError = null; + return error; + } + } + + //public api + public string GetVersion(TGByondVersion type) + { + try + { + lock (ByondLock) + { + if (type == TGByondVersion.Latest) + { + //get the latest version from the website + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ByondLatestURL); + var results = new List(); + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + using (StreamReader reader = new StreamReader(response.GetResponseStream())) + { + string html = reader.ReadToEnd(); + + Regex regex = new Regex("\\\"([^\"]*)\\\""); + MatchCollection matches = regex.Matches(html); + foreach (Match match in matches) + if (match.Success && match.Value.Contains("_byond.exe")) + results.Add(match.Value.Replace("\"", "").Replace("_byond.exe", "")); + } + } + results.Sort(); + results.Reverse(); + return results.Count > 0 ? results[0] : null; + } + else + { + string DirToUse = type == TGByondVersion.Staged ? StagingDirectoryInner : ByondDirectory; + if (Directory.Exists(DirToUse)) + { + string file = DirToUse + VersionFile; + if (File.Exists(file)) + return File.ReadAllText(file); + } + } + return null; + } + } + catch (Exception e) + { + return "Error: " + e.ToString(); + } + } + + //literally just for passing 2 ints to the thread function + class VersionInfo + { + public int major, minor; + } + + //does the downloading and unzipping + //calls ApplyStagedUpdate() after if the server isn't running + public void UpdateToVersionImpl(object param) + { + lock (ByondLock) { + if (updateStat != TGByondStatus.Starting) + return; + updateStat = TGByondStatus.Downloading; + } + + try + { + CleanByondStaging(); + + var vi = (VersionInfo)param; + using (var client = new WebClient()) + { + SendMessage(String.Format("BYOND: Updating to version {0}.{1}...", vi.major, vi.minor)); + + //DOWNLOADING + + try + { + client.DownloadFile(String.Format(ByondRevisionsURL, vi.major, vi.minor), RevisionDownloadPath); + } + catch + { + SendMessage("BYOND: Update download failed. Does the specified version exist?"); + lastError = String.Format("Download of BYOND version {0}.{1} failed! Does it exist?", vi.major, vi.minor); + TGServerService.WriteLog(String.Format("Failed to update BYOND to version {0}.{1}!", vi.major, vi.minor), EventLogEntryType.Warning); + lock (ByondLock) + { + updateStat = TGByondStatus.Idle; + } + return; + } + } + lock (ByondLock) + { + updateStat = TGByondStatus.Staging; + } + + //STAGING + + ZipFile.ExtractToDirectory(RevisionDownloadPath, StagingDirectory); + lock (ByondLock) + { + File.WriteAllText(StagingDirectoryInner + VersionFile, String.Format("{0}.{1}", vi.major, vi.minor)); + //IMPORTANT: SET THE BYOND CONFIG TO NOT PROMPT FOR TRUSTED MODE REEE + Directory.CreateDirectory(ByondConfigDir); + File.WriteAllText(ByondConfigDir + ByondDDConfig, ByondNoPromptTrustedMode); + } + File.Delete(RevisionDownloadPath); + + lock (ByondLock) + { + updateStat = TGByondStatus.Staged; + } + + switch (DaemonStatus()) + { + case TGDreamDaemonStatus.Offline: + if(ApplyStagedUpdate()) + lastError = null; + else + lastError = "Failed to apply update!"; + break; + default: + RequestRestart(); + lastError = "Update staged. Awaiting server restart..."; + SendMessage(String.Format("BYOND: Staging complete. Awaiting server restart...", vi.major, vi.minor)); + TGServerService.WriteLog(String.Format("BYOND update {0}.{1} staged", vi.major, vi.minor)); + break; + } + } + catch (ThreadAbortException) + { + return; + } + catch (Exception e) + { + TGServerService.WriteLog("Revision staging errror: " + e.ToString(), EventLogEntryType.Error); + lock (ByondLock) + { + updateStat = TGByondStatus.Idle; + lastError = e.ToString(); + RevisionStaging = null; + } + } + } + //public api for kicking off the update thread + public bool UpdateToVersion(int ma, int mi) + { + lock (ByondLock) + { + if (!BusyCheckNoLock()) + { + updateStat = TGByondStatus.Starting; + RevisionStaging = new Thread(new ParameterizedThreadStart(UpdateToVersionImpl)) + { + IsBackground = true //don't slow me down + }; + RevisionStaging.Start(new VersionInfo { major = ma, minor = mi }); + return true; + } + return false; + } + } + //tries to apply the staged update + public bool ApplyStagedUpdate() + { + lock (CompilerLock) + { + if (compilerCurrentStatus == TGCompilerStatus.Compiling) + return false; + lock (ByondLock) + { + if (updateStat != TGByondStatus.Staged) + return false; + updateStat = TGByondStatus.Updating; + } + try + { + Program.DeleteDirectory(ByondDirectory); + Directory.Move(StagingDirectoryInner, ByondDirectory); + Program.DeleteDirectory(StagingDirectory); + lastError = null; + SendMessage("BYOND: Update completed!"); + TGServerService.WriteLog(String.Format("BYOND update {0} completed!", GetVersion(TGByondVersion.Installed))); + return true; + } + catch (Exception e) + { + lastError = e.ToString(); + SendMessage("BYOND: Update failed!"); + TGServerService.WriteLog("BYOND update failed!"); + return false; + } + finally + { + lock(ByondLock) { + updateStat = TGByondStatus.Idle; + } + } + } + } + } +} diff --git a/TGServerService/Chat.cs b/TGServerService/Chat.cs new file mode 100644 index 0000000000..3aaf80e8dc --- /dev/null +++ b/TGServerService/Chat.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; +using System.Web.Script.Serialization; +using TGServiceInterface; + +namespace TGServerService +{ + interface ITGChatProvider : ITGChatBase, IDisposable + { + /// + /// Called with chat message info + /// + event OnChatMessage OnChatMessage; + + /// + /// Connects the chat provider + /// + /// null on success, error message on failure + string Connect(); + + /// + /// Disconnects the chat provider + /// + void Disconnect(); + + /// + /// Send a message to a channel + /// + /// The message to send + /// The channel to send to + /// null on success, error message on failure + string SendMessageDirect(string message, string channel); + } + + /// + /// Callback for the chat provider recieving a message + /// + /// The username of the speaker + /// The name of the channel + /// The message text + /// true if the bot was mentioned in the first word, false otherwise + public delegate void OnChatMessage(string speaker, string channel, string message, bool tagged); + + partial class TGStationServer : ITGChat + { + ITGChatProvider ChatProvider; + TGChatProvider currentProvider; + object ChatLock = new object(); + + public void InitChat(TGChatSetupInfo info = null) + { + if (info == null) + info = ProviderInfo(); + switch (info.Provider) + { + case TGChatProvider.Discord: + ChatProvider = new TGDiscordChatProvider(info); + break; + case TGChatProvider.IRC: + ChatProvider = new TGIRCChatProvider(info); + break; + default: + TGServerService.WriteLog(String.Format("Invalid chat provider: {0}", info.Provider), EventLogEntryType.Error); + break; + } + currentProvider = info.Provider; + ChatProvider.OnChatMessage += ChatProvider_OnChatMessage; + if (Properties.Settings.Default.ChatEnabled) + { + var res = ChatProvider.Connect(); + if (res != null) + TGServerService.WriteLog(String.Format("Unable to connect to chat! Provider {0}, Error: {1}", ChatProvider.GetType().ToString(), res)); + } + } + + private void ChatProvider_OnChatMessage(string speaker, string channel, string message, bool tagged) + { + var splits = message.Trim().Split(' '); + + var s0l = splits[0].ToLower(); + + if (s0l == "!check") + { + ChatProvider.SendMessageDirect(StatusString(HasChatAdmin(speaker, channel) == null), channel); + return; + } + + if (!tagged) + return; + + if (splits.Length == 1 && splits[0] == "") + { + ChatProvider.SendMessageDirect("Hi!", channel); + return; + } + + var asList = new List(splits); + var command = asList[0].ToLower(); + asList.RemoveAt(0); + + ChatProvider.SendMessageDirect(ChatCommand(command, speaker, channel, asList), channel); + } + + string HasChatAdmin(string speaker, string channel) + { + var Config = Properties.Settings.Default; + if (!Config.ChatAdmins.Contains(speaker.ToLower())) + return "You are not authorized to use that command!"; + if (channel.ToLower() != Config.ChatAdminChannel.ToLower()) + return "Use this command in the admin channel!"; + return null; + } + + void DisposeChat() + { + ChatProvider.Dispose(); + } + + //Do stuff with words that were spoken to us + string ChatCommand(string command, string speaker, string channel, IList parameters) + { + TGServerService.WriteLog(String.Format("IRC Command from {0}: {1} {2}", speaker, command, String.Join(" ", parameters))); + switch (command) + { + case "check": + return StatusString(HasChatAdmin(speaker, channel) == null); + case "byond": + if (parameters.Count > 0) + if (parameters[0].ToLower() == "--staged") + return GetVersion(TGByondVersion.Staged) ?? "None"; + else if (parameters[0].ToLower() == "--latest") + return GetVersion(TGByondVersion.Latest) ?? "Unknown"; + return GetVersion(TGByondVersion.Staged) ?? "Uninstalled"; + case "status": + return HasChatAdmin(speaker, channel) ?? SendCommand(SCIRCStatus); + case "adminwho": + return HasChatAdmin(speaker, channel) ?? SendCommand(SCAdminWho); + case "ahelp": + var res = HasChatAdmin(speaker, channel); + if (res != null) + return res; + if (parameters.Count < 2) + return "Usage: ahelp "; + var ckey = parameters[0]; + parameters.RemoveAt(0); + return SendPM(ckey, speaker, String.Join(" ", parameters)); + case "namecheck": + res = HasChatAdmin(speaker, channel); + if (res != null) + return res; + if (parameters.Count < 1) + return "Usage: namecheck "; + return NameCheck(parameters[0], speaker); + } + return "Unknown command: " + command; + } + + //public api + public string SetEnabled(bool enable) + { + lock (ChatLock) + { + Properties.Settings.Default.ChatEnabled = enable; + if (!enable && Connected()) + ChatProvider.Disconnect(); + else if (enable && !Connected()) + return ChatProvider.Connect(); + return null; + } + } + + //public api + public string[] Channels() + { + lock (ChatLock) + { + return CollectionToArray(Properties.Settings.Default.ChatChannels); + } + } + + //what it says on the tin + string[] CollectionToArray(StringCollection sc) + { + string[] strArray = new string[sc.Count]; + sc.CopyTo(strArray, 0); + return strArray; + } + //public api + public string AdminChannel() + { + lock (ChatLock) + { + return Properties.Settings.Default.ChatAdminChannel; + } + } + //public api + public bool Enabled() + { + lock (ChatLock) + { + return Properties.Settings.Default.ChatEnabled; + } + } + + //public api + public string[] ListAdmins() + { + lock (ChatLock) + { + return CollectionToArray(Properties.Settings.Default.ChatAdmins); + } + } + + //public api + public void SetAdmins(string[] admins) + { + var si = new StringCollection(); + foreach (var I in admins) + si.Add(I.Trim().ToLower()); + lock (ChatLock) + { + Properties.Settings.Default.ChatAdmins = si; + } + } + + //public api + public TGChatSetupInfo ProviderInfo() + { + lock (ChatLock) + { + var Config = Properties.Settings.Default; + var rawdata = Config.ChatProviderData; + if (rawdata == "NEEDS INITIALIZING") + switch ((TGChatProvider)Config.ChatProvider) + { + case TGChatProvider.Discord: + return new TGDiscordSetupInfo(); + case TGChatProvider.IRC: + return new TGIRCSetupInfo(); + default: + TGServerService.WriteLog("Invalid chat provider: " + Config.ChatProvider.ToString()); + return null; + } + + byte[] plaintext; + try + { + plaintext = ProtectedData.Unprotect(Convert.FromBase64String(Config.ChatProviderData), Convert.FromBase64String(Config.ChatProviderEntropy), DataProtectionScope.CurrentUser); + + var Deserializer = new JavaScriptSerializer(); + return new TGChatSetupInfo(Deserializer.Deserialize>(Encoding.UTF8.GetString(plaintext)), (TGChatProvider)Config.ChatProvider); + } + catch + { + Config.ChatProviderData = "NEEDS INITIALIZING"; + } + } + //if we get here we want to retry + return ProviderInfo(); + } + + //public api + public string SetProviderInfo(TGChatSetupInfo info) + { + lock (ChatLock) + { + var Serializer = new JavaScriptSerializer(); + var rawdata = Serializer.Serialize(info.DataFields); + var Config = Properties.Settings.Default; + Config.ChatProvider = (int)info.Provider; + Config.ChatProviderData = rawdata; + + byte[] plaintext = Encoding.UTF8.GetBytes(rawdata); + + // Generate additional entropy (will be used as the Initialization vector) + byte[] entropy = new byte[20]; + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(entropy); + } + + byte[] ciphertext = ProtectedData.Protect(plaintext, entropy, DataProtectionScope.CurrentUser); + + Config.ChatProviderEntropy = Convert.ToBase64String(entropy, 0, entropy.Length); + Config.ChatProviderData = Convert.ToBase64String(ciphertext, 0, ciphertext.Length); + + if (info.Provider == currentProvider) + return ChatProvider.SetProviderInfo(info); + else + { + DisposeChat(); + InitChat(info); + return null; + } + } + } + + //public api + public void SetChannels(string[] channels = null, string adminchannel = null) + { + var Config = Properties.Settings.Default; + lock (ChatLock) + { + if (channels != null) + { + var oldchannels = Config.ChatChannels; + var si = new StringCollection(); + foreach(var c in channels) + { + var working = c.Trim(); + if (working.Length == 0) + continue; + if (working[0] != '#') + working = "#" + working; + si.Add(working); + } + if (!si.Contains(Config.ChatAdminChannel)) + si.Add(Config.ChatAdminChannel); + Config.ChatChannels = si; + } + if (adminchannel != null) + Config.ChatAdminChannel = adminchannel; + if (channels != null && Connected()) + ChatProvider.SetChannels(CollectionToArray(Config.ChatChannels), null); + } + } + + //public api + public bool Connected() + { + lock (ChatLock) + { + return ChatProvider.Connected(); + } + } + + //public api + public string Reconnect() + { + lock (ChatLock) + { + if (!Properties.Settings.Default.ChatEnabled) + return "Chat is disabled!"; + return ChatProvider.Reconnect(); + } + } + + //public api + public string SendMessage(string msg, bool adminOnly = false) + { + lock (ChatLock) + { + return ChatProvider.SendMessage(msg, adminOnly); + } + } + } +} diff --git a/TGServerService/Compiler.cs b/TGServerService/Compiler.cs new file mode 100644 index 0000000000..2658a3eac9 --- /dev/null +++ b/TGServerService/Compiler.cs @@ -0,0 +1,539 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using TGServiceInterface; + +namespace TGServerService +{ + partial class TGStationServer : ITGCompiler + { + #region Win32 Shit + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, SymbolicLink dwFlags); + enum SymbolicLink + { + File = 0, + Directory = 1 + } + #endregion + + const string StaticDirs = "Static"; + const string StaticDataDir = StaticDirs + "/data"; + const string StaticConfigDir = StaticDirs + "/config"; + const string StaticBackupDir = "Static_BACKUP"; + + const string LibMySQLFile = "/libmysql.dll"; + + const string GameDir = "Game"; + const string GameDirA = GameDir + "/A"; + const string GameDirB = GameDir + "/B"; + const string GameDirLive = GameDir + "/Live"; + + const string LiveFile = "/TestLive.lk"; + const string ADirTest = GameDirA + LiveFile; + const string BDirTest = GameDirB + LiveFile; + const string LiveDirTest = GameDirLive + LiveFile; + + List copyExcludeList = new List { ".git", "data", "config", "libmysql.dll" }; //shit we handle + + object CompilerLock = new object(); + TGCompilerStatus compilerCurrentStatus; + string lastCompilerError; + + Thread CompilerThread; + bool compilationCancellationRequestation = false; + bool canCancelCompilation = false; + + //deletes leftovers and checks current status + void InitCompiler() + { + if(File.Exists(LiveDirTest)) + File.Delete(LiveDirTest); + compilerCurrentStatus = IsInitialized(); + } + + //public api + public TGCompilerStatus GetStatus() + { + lock (CompilerLock) + { + return compilerCurrentStatus; + } + } + + //public api + public string CompileError() + { + lock (CompilerLock) + { + var err = lastCompilerError; + lastCompilerError = null; + return err; + } + } + + //kills the compiler if its running + void DisposeCompiler() + { + lock (CompilerLock) + { + if (CompilerThread == null || !CompilerThread.IsAlive) + return; + CompilerThread.Abort(); //this will safely kill dm + InitCompiler(); //also cleanup + } + } + + //translates the win32 api call into an exception if it fails + void CreateSymlink(string link, string target) + { + if (!CreateSymbolicLink(new DirectoryInfo(link).FullName, new DirectoryInfo(target).FullName, File.Exists(target) ? SymbolicLink.File : SymbolicLink.Directory)) + throw new Exception(String.Format("Failed to create symlink from {0} to {1}! Error: {2}", target, link, Marshal.GetLastWin32Error())); + } + + //requires CompilerLock to be locked + bool CompilerIdleNoLock() + { + return compilerCurrentStatus == TGCompilerStatus.Uninitialized || compilerCurrentStatus == TGCompilerStatus.Initialized; + } + + //public api + public bool Initialize() + { + lock (CompilerLock) + { + if (!CompilerIdleNoLock()) + return false; + lastCompilerError = null; + compilerCurrentStatus = TGCompilerStatus.Initializing; + CompilerThread = new Thread(new ThreadStart(InitializeImpl)); + CompilerThread.Start(); + return true; + } + } + + //what is says on the tin + TGCompilerStatus IsInitialized() + { + if (File.Exists(GameDirLive + LibMySQLFile)) //its a good tell, jim + return TGCompilerStatus.Initialized; + return TGCompilerStatus.Uninitialized; + } + + //we need to remove symlinks before we can recursively delete + void CleanGameFolder() + { + if (Directory.Exists(GameDirB + LibMySQLFile)) + Directory.Delete(GameDirB + LibMySQLFile); + + if (Directory.Exists(GameDirA + "/data")) + Directory.Delete(GameDirA + "/data"); + + if (Directory.Exists(GameDirA + "/config")) + Directory.Delete(GameDirA + "/config"); + + if (Directory.Exists(GameDirA + LibMySQLFile)) + Directory.Delete(GameDirA + LibMySQLFile); + + if (Directory.Exists(GameDirB + "/data")) + Directory.Delete(GameDirB + "/data"); + + if (Directory.Exists(GameDirB + "/config")) + Directory.Delete(GameDirB + "/config"); + + if (Directory.Exists(GameDirLive)) + Directory.Delete(GameDirLive); + } + + //Initializing thread + public void InitializeImpl() + { + try + { + if (DaemonStatus() != TGDreamDaemonStatus.Offline) + { + lock (CompilerLock) + { + lastCompilerError = "Dream daemon must not be running"; + compilerCurrentStatus = IsInitialized(); + return; + } + } + + if (!Exists()) //repo + { + lock (CompilerLock) + { + lastCompilerError = "Repository is not setup!"; + compilerCurrentStatus = IsInitialized(); + return; + } + } + try + { + SendMessage("DM: Setting up symlinks..."); + CleanGameFolder(); + Program.DeleteDirectory(GameDir); + + Directory.CreateDirectory(GameDirA); + Directory.CreateDirectory(GameDirB); + + CreateSymlink(GameDirA + "/data", StaticDataDir); + CreateSymlink(GameDirB + "/data", StaticDataDir); + + CreateSymlink(GameDirA + "/config", StaticConfigDir); + CreateSymlink(GameDirB + "/config", StaticConfigDir); + + CreateSymlink(GameDirA + LibMySQLFile, StaticDirs + LibMySQLFile); + CreateSymlink(GameDirB + LibMySQLFile, StaticDirs + LibMySQLFile); + + CreateSymlink(GameDirLive, GameDirA); + + lock (CompilerLock) + { + compilerCurrentStatus = TGCompilerStatus.Compiling; + } + } + catch (ThreadAbortException) + { + return; + } + catch (Exception e) + { + lock (CompilerLock) + { + SendMessage("DM: Setup failed!"); + lastCompilerError = e.ToString(); + compilerCurrentStatus = TGCompilerStatus.Uninitialized; + return; + } + } + } + catch (ThreadAbortException) + { + return; + } + CompileImpl(); + } + + //Returns the A or B dir in which the game is NOT running + string GetStagingDir() + { + string TheDir; + if (!Directory.Exists(GameDirLive)) + TheDir = GameDirA; + else + { + File.Create(LiveDirTest).Close(); + try + { + if (File.Exists(ADirTest)) + TheDir = GameDirA; + else if (File.Exists(BDirTest)) + TheDir = GameDirB; + else + throw new Exception("Unable to determine current live directory!"); + } + finally + { + File.Delete(LiveDirTest); + } + + + TheDir = InvertDirectory(TheDir); + + } + //So TheDir is what the Live folder is NOT pointing to + //Now we need to check if DD is running that folder and swap it if necessary + + var rsclock = TheDir + "/" + Properties.Settings.Default.ProjectName + ".rsc.lk"; + if (File.Exists(rsclock)) + { + try + { + File.Delete(rsclock); + } + catch //held open by byond + { + return InvertDirectory(TheDir); + } + } + return TheDir; + } + + //I hope you can read this + string InvertDirectory(string gameDirectory) + { + if (gameDirectory == GameDirA) + return GameDirB; + else + return GameDirA; + } + + //Compiler thread + void CompileImpl() + { + try + { + if (GetVersion(TGByondVersion.Installed) == null) + { + lastCompilerError = "BYOND not installed!"; + compilerCurrentStatus = TGCompilerStatus.Initialized; + return; + } + SendMessage("DM: Compiling..."); + var resurrectee = GetStagingDir(); + + //clear out the syms first + if (Directory.Exists(resurrectee + "/data")) + Directory.Delete(resurrectee + "/data"); + + if (Directory.Exists(resurrectee + "/config")) + Directory.Delete(resurrectee + "/config"); + + if (File.Exists(resurrectee + LibMySQLFile)) + File.Delete(resurrectee + LibMySQLFile); + + Program.DeleteDirectory(resurrectee, true); + + Directory.CreateDirectory(resurrectee + "/.git/logs"); + + CreateSymlink(resurrectee + "/data", StaticDataDir); + CreateSymlink(resurrectee + "/config", StaticConfigDir); + CreateSymlink(resurrectee + LibMySQLFile, StaticDirs + LibMySQLFile); + + bool repobusy_check = false; + if (!Monitor.TryEnter(RepoLock)) + repobusy_check = true; + + if (!repobusy_check) + { + if (RepoBusy) + repobusy_check = true; + else + RepoBusy = true; + Monitor.Exit(RepoLock); + } + + if (repobusy_check) + { + SendMessage("DM: Copy aborted, repo locked!"); + lock (CompilerLock) + { + lastCompilerError = "The repo could not be locked for copying"; + compilerCurrentStatus = TGCompilerStatus.Initialized; //still fairly valid + return; + } + } + try + { + Program.CopyDirectory(RepoPath, resurrectee, copyExcludeList); + //just the tip + const string GitLogsDir = "/.git/logs"; + Program.CopyDirectory(RepoPath + GitLogsDir, resurrectee + GitLogsDir); + } + finally + { + lock (RepoLock) + { + RepoBusy = false; + } + } + + var res = CreateBackup(); + if(res != null) + lock (CompilerLock) + { + lastCompilerError = res; + compilerCurrentStatus = TGCompilerStatus.Initialized; + return; + } + + var dmeName = ProjectName() + ".dme"; + var dmePath = resurrectee + "/" + dmeName; + if (!File.Exists(dmePath)) + { + var errorMsg = String.Format("Could not find {0}!", dmeName); + SendMessage("DM: " + errorMsg); + lock (CompilerLock) + { + lastCompilerError = errorMsg; + compilerCurrentStatus = TGCompilerStatus.Initialized; + return; + } + } + + using (var DM = new Process()) //will kill the process if the thread is terminated + { + DM.StartInfo.FileName = ByondDirectory + "/bin/dm.exe"; + DM.StartInfo.Arguments = dmePath; + DM.StartInfo.UseShellExecute = false; + try + { + lock (CompilerLock) + { + if (compilationCancellationRequestation) + return; + canCancelCompilation = true; + } + + DM.Start(); + DM.WaitForExit(); + + lock (CompilerLock) + { + canCancelCompilation = false; + compilationCancellationRequestation = false; + } + } + catch + { + if (!DM.HasExited) + DM.Kill(); + throw; + } + finally + { + lock (CompilerLock) + { + canCancelCompilation = false; + } + } + + if (DM.ExitCode == 0) + { + lock (watchdogLock) + { + try + { + //gotta go fast + if (currentStatus == TGDreamDaemonStatus.Online) + { + Thread.CurrentThread.Priority = ThreadPriority.Highest; + Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; + try + { + Proc.PriorityClass = ProcessPriorityClass.Idle; + } + catch { } + } + if (Directory.Exists(GameDirLive)) + //these two lines should be atomic but this is the best we can do + Directory.Delete(GameDirLive); + CreateSymlink(GameDirLive, resurrectee); + } + finally + { + if (currentStatus == TGDreamDaemonStatus.Online) + { + try + { + Proc.PriorityClass = ProcessPriorityClass.Normal; + } + catch { } + Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.Normal; + Thread.CurrentThread.Priority = ThreadPriority.Normal; + } + } + } + SendMessage(String.Format("DM: Compile complete!{0}", DaemonStatus() == TGDreamDaemonStatus.Offline ? "" : " Server will update next round.")); + lock (CompilerLock) + { + lastCompilerError = null; + compilerCurrentStatus = TGCompilerStatus.Initialized; //still fairly valid + } + + } + else + { + SendMessage("DM: Compile failed!"); //Also happens for warnings + lock (CompilerLock) + { + lastCompilerError = "DM compile failure"; + compilerCurrentStatus = TGCompilerStatus.Initialized; + } + } + } + + } + catch (ThreadAbortException) + { + return; + } + catch (Exception e) + { + SendMessage("DM: Compiler thread crashed!"); + TGServerService.WriteLog("Compile manager errror: " + e.ToString(), EventLogEntryType.Error); + lock (CompilerLock) + { + lastCompilerError = e.ToString(); + compilerCurrentStatus = TGCompilerStatus.Initialized; //still fairly valid + } + } + finally + { + lock (CompilerLock) + { + canCancelCompilation = false; + if (compilationCancellationRequestation) + { + compilerCurrentStatus = TGCompilerStatus.Initialized; + compilationCancellationRequestation = false; + SendMessage("Compile cancelled!"); + } + } + } + } + //kicks off the compiler thread + //public api + public bool Compile() + { + lock (CompilerLock) + { + if (compilerCurrentStatus != TGCompilerStatus.Initialized) + return false; + lastCompilerError = null; + compilerCurrentStatus = TGCompilerStatus.Compiling; + CompilerThread = new Thread(new ThreadStart(CompileImpl)); + CompilerThread.Start(); + } + return true; + } + + //public api + public string ProjectName() + { + lock (CompilerLock) + { + return Properties.Settings.Default.ProjectName; + } + } + + //public api + public void SetProjectName(string projectName) + { + lock (CompilerLock) + { + Properties.Settings.Default.ProjectName = projectName; + } + } + + public string Cancel() + { + lock (CompilerLock) + { + if (compilerCurrentStatus != TGCompilerStatus.Compiling) + return "Invalid state for cancellation!"; + compilationCancellationRequestation = true; + if (canCancelCompilation) + CompilerThread.Abort(); + else + return "Compilation will be cancelled when the repo copy is complete"; + return null; + } + } + } +} diff --git a/TGServerService/Config.cs b/TGServerService/Config.cs new file mode 100644 index 0000000000..40f5366acc --- /dev/null +++ b/TGServerService/Config.cs @@ -0,0 +1,1019 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using TGServiceInterface; +using System.Threading; + +namespace TGServerService +{ + //knobs and such + partial class TGStationServer : ITGConfig + { + const string ConfigPostfix = "/config.txt"; + const string DBConfigPostfix = "/dbconfig.txt"; + const string GameConfigPostfix = "/game_options.txt"; + + const string AdminRanksConfig = StaticConfigDir + "/admin_ranks.txt"; + const string AdminRanksRepo = RepoConfig + "/admin_ranks.txt"; + const string AdminConfig = StaticConfigDir + "/admins.txt"; + const string InteropConfig = StaticConfigDir + "/server_to_tool_bridge_port.txt"; + const string MapConfig = StaticConfigDir + "/maps.txt"; + const string JobsConfig = StaticConfigDir + "/jobs.txt"; + + const string TitleImagesConfig = StaticConfigDir + "/title_screens/images"; + + object configLock = new object(); //for atomic reads/writes + + //Write out the admin assoiciations to admins.txt + string WriteMins(IDictionary current_mins) + { + string outText = ""; + foreach (var I in current_mins) + outText += I.Key + " = " + I.Value + "\r\n"; + + try + { + lock (configLock) + { + File.WriteAllText(AdminConfig, outText); + } + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + //public api + public string Addmin(string ckey, string rank) + { + var Aranks = AdminRanks(out string error); + if (Aranks != null) + { + if (Aranks.Keys.Contains(rank)) + { + var current_mins = Admins(out error); + if (current_mins != null) + { + current_mins[ckey] = rank; + return WriteMins(current_mins); + } + return error; + } + return "Rank " + rank + " does not exist"; + } + return error; + } + + //public api + public IDictionary> AdminRanks(out string error) + { + + try + { + List fileLines; + lock (configLock) + { + fileLines = new List(File.ReadAllLines(AdminRanksConfig)); + } + + var result = new Dictionary>(); + IDictionary previousPermissions = new Dictionary(); + foreach (var L in fileLines) + { + if (L.Length > 0 && L[0] == '#') + continue; + + var splits = L.Split('='); + + if (splits.Length < 2) //??? + continue; + + var rank = splits[0].Trim(); + + var tmp = new List(splits); + tmp.RemoveAt(0); + splits = String.Join(" ", tmp).Split(' '); + + var asList = new List(splits); + asList.RemoveAt(0); + + var perms = ProcessPermissions(asList, previousPermissions); + result.Add(rank, perms); + previousPermissions = perms; + } + error = null; + return result; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + //same thing the proc in admin_ranks.dm does, properly calculates string permission sets and returns them as an enum + IDictionary ProcessPermissions(IList text, IDictionary previousPermissions) + { + IDictionary permissions = new Dictionary(); + foreach(var E in text) + { + var trimmed = E.Trim(); + + if (trimmed.Length == 0) + continue; + + bool adding; + switch (trimmed[0]) + { + case '-': + adding = false; + trimmed = trimmed.Substring(1); + break; + case '+': + adding = true; + trimmed = trimmed.Substring(1); + break; + default: + adding = true; + break; + } + + if (trimmed.Length == 0) + continue; + + var perms = StringToPermission(trimmed, previousPermissions, adding); + + if(perms != null) + foreach(var perm in perms) + { + if(!permissions.ContainsKey(perm.Key)) + permissions.Add(perm); + else + { + var wasAdded = permissions[perm.Key]; + if (wasAdded == perm.Value) + continue; + //cancel each other out + permissions.Remove(perm.Key); + } + } + + } + return permissions; + } + + //basic conversion + IDictionary StringToPermission(string permstring, IDictionary oldpermissions, bool adding) + { + switch (permstring.Trim().ToUpper()) + { + case "@": + case "PREV": + return oldpermissions; + default: + return new Dictionary { { permstring, adding } }; + } + } + + //public api + public IDictionary Admins(out string error) + { + try + { + List fileLines; + lock (configLock) + { + fileLines = new List(File.ReadAllLines(AdminConfig)); + } + + var mins = new Dictionary(); + foreach (var L in fileLines) + { + var trimmed = L.Trim(); + if (L.Length == 0 || L[0] == '#') + continue; + + var splits = L.Split('='); + + if (splits.Length != 2) + continue; + var key = splits[0].Trim(); + if (!mins.ContainsKey(key)) + mins.Add(key, splits[1].Trim()); + //don't care about dupes + } + error = null; + return mins; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + //public api + public string Deadmin(string admin) + { + var current_mins = Admins(out string error); + if (current_mins != null) + { + if (current_mins.ContainsKey(admin)) + { + current_mins.Remove(admin); + return WriteMins(current_mins); + } + return null; + } + return error; + } + + //public api + public IList Jobs(out string error) + { + try + { + IList lines; + lock (configLock) + { + lines = File.ReadAllLines(JobsConfig); + } + + IList results = new List(); + + foreach (var L in lines) + { + var trimmed = L.Trim(); + if (trimmed.Length == 0 || trimmed[0] == '#') + continue; + + var splits = trimmed.Split('='); + + if (splits.Length < 2) + continue; + + var countSplits = splits[1].Split(','); + if (countSplits.Length < 2) + continue; + + int total, spawn; + try + { + total = Convert.ToInt32(countSplits[0]); + spawn = Convert.ToInt32(countSplits[1]); + } + catch + { + continue; + } + + results.Add(new JobSetting() + { + Name = splits[0], + TotalPositions = total, + SpawnPositions = spawn, + }); + } + + error = null; + return results; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + //public api + public string MoveServer(string new_location) + { + try + { + var di1 = new DirectoryInfo(Environment.CurrentDirectory); + var di2 = new DirectoryInfo(new_location); + + var copy = di1.Root.FullName != di2.Root.FullName; + + if (copy && File.Exists(PrivateKeyPath)) + return String.Format("Unable to perform a cross drive server move with the {0}. Copy aborted!", PrivateKeyPath); + + new_location = di2.FullName; + + while (di2.Parent != null) + if (di2.Parent.FullName == di1.FullName) + return "Cannot move to child of current directory!"; + else + di2 = di2.Parent; + + if (!Monitor.TryEnter(RepoLock)) + return "Repo locked!"; + try + { + if (RepoBusy) + return "Repo busy!"; + DisposeRepo(); + if (!Monitor.TryEnter(ByondLock)) + return "BYOND locked"; + try + { + if (updateStat != TGByondStatus.Idle) + return "BYOND busy!"; + if (!Monitor.TryEnter(CompilerLock)) + return "Compiler locked!"; + + try + { + if (compilerCurrentStatus != TGCompilerStatus.Uninitialized && compilerCurrentStatus != TGCompilerStatus.Initialized) + return "Compiler busy!"; + if (!Monitor.TryEnter(watchdogLock)) + return "Watchdog locked!"; + try + { + if (currentStatus != TGDreamDaemonStatus.Offline) + return "Watchdog running!"; + var Config = Properties.Settings.Default; + lock (configLock) + { + CleanGameFolder(); + Program.DeleteDirectory(GameDir); + string error = null; + if (copy) + { + Program.CopyDirectory(Config.ServerDirectory, new_location); + Directory.CreateDirectory(new_location); + Environment.CurrentDirectory = new_location; + try + { + Program.DeleteDirectory(Config.ServerDirectory); + } + catch + { + error = "The move was successful, but the path " + Config.ServerDirectory + " was unable to be deleted fully!"; + } + } + else + { + try + { + Environment.CurrentDirectory = di2.Root.FullName; + Directory.Move(Config.ServerDirectory, new_location); + Environment.CurrentDirectory = new_location; + } + catch (Exception e) + { + Environment.CurrentDirectory = Config.ServerDirectory; + return e.ToString(); + } + } + Config.ServerDirectory = new_location; + TGServerService.WriteLog("Server moved to: " + new_location); + return null; + } + } + finally + { + Monitor.Exit(watchdogLock); + } + } + finally + { + Monitor.Exit(CompilerLock); + } + } + finally + { + Monitor.Exit(ByondLock); + } + } + finally + { + Monitor.Exit(RepoLock); + } + } + catch (Exception e) + { + return e.ToString(); + } + } + //public api + public ushort InteropPort(out string error) + { + try + { + error = null; + return Convert.ToUInt16(File.ReadAllText(InteropConfig)); + } + catch (Exception e) + { + error = e.ToString(); + return 0; + } + } + + //TGConfigType to the right path, Repo or static + string ConfigTypeToPath(TGConfigType type, bool repo) + { + var path = repo ? RepoConfig : StaticConfigDir; + switch (type) + { + case TGConfigType.Database: + return path + DBConfigPostfix; + case TGConfigType.Game: + return path + GameConfigPostfix; + case TGConfigType.General: + return path + ConfigPostfix; + default: + throw new Exception("Bad TGConfigType: " + type); + } + } + + string TranslateConfigComment(string input) + { + //https://github.com/tgstation/tgstation/pull/27632#discussion_r118389053 + return input + .Replace("uncomment", "activate") + .Replace("Uncomment", "Activate") + .Replace("commented out", "deactivated") + .Replace("comment this out", "deactivate") + .Replace("Comment this out", "Deactivate") + .Replace("uncommenting", "activating") + .Replace("Uncommenting", "Activating") + .Replace("uncommented", "activated") + .Replace("Uncomment", "Activate"); + } + + //public api + public IList Retrieve(TGConfigType type, out string error) + { + try + { + IList RepoConfigData, StaticConfigData; + var repolocked = false; + if (!Monitor.TryEnter(RepoLock)) + repolocked = true; + try + { + if (!repolocked && RepoBusy) + repolocked = true; + if (repolocked) + { + error = "Repo locked!"; + return null; + } + + lock (configLock) + { + RepoConfigData = new List(File.ReadAllLines(ConfigTypeToPath(type, true))); + StaticConfigData = new List(File.ReadAllLines(ConfigTypeToPath(type, false))); + } + } + finally + { + Monitor.Exit(RepoLock); + } + + //## designates an option comment + //# designates a commented out option + + IDictionary repoConfig = new Dictionary(); + IList results = new List(); + + ConfigSetting currentSetting = new ConfigSetting(); + + foreach (var I in RepoConfigData) + { + var trimmed = I.Trim(); + if (trimmed.Length == 0) + continue; + var commented = trimmed[0] == '#'; + if (commented) + { + if (trimmed.Length == 1) + continue; + + if (trimmed[1] == '#') + { + //comment line + if (currentSetting.Comment == null) + currentSetting.Comment = TranslateConfigComment(trimmed.Substring(2).Trim()); + else + currentSetting.Comment += "\r\n" + trimmed.Substring(2).Trim(); + continue; + } + if (trimmed.Length == 2) + continue; + trimmed = trimmed.Substring(1).Trim(); + } + var splits = new List(trimmed.Split(' ')); + currentSetting.Name = splits.First().ToUpper(); + splits.RemoveAt(0); + var value = String.Join(" ", splits); + if (commented && value == "") + value = null; + currentSetting.ExistsInRepo = true; + + //multi-keying + if (repoConfig.Keys.Contains(currentSetting.Name)) + { + currentSetting = repoConfig[currentSetting.Name]; + if (!currentSetting.IsMultiKey) + { + currentSetting.IsMultiKey = true; + currentSetting.DefaultValues = new List { currentSetting.DefaultValue, value }; + currentSetting.DefaultValue = null; + } + else + currentSetting.DefaultValues.Add(value); + } + else + { + currentSetting.DefaultValue = value; + repoConfig.Add(currentSetting.Name, currentSetting); + results.Add(currentSetting); + } + currentSetting = new ConfigSetting(); + } + //gather the stuff from our config + foreach (var I in StaticConfigData) + { + + var trimmed = I.Trim(); + if (trimmed.Length == 0) + continue; + var commented = trimmed[0] == '#'; + if (trimmed.Length < 3 || trimmed[1] == '#' || commented) + continue; + if (commented) + trimmed = trimmed.Substring(1).Trim(); + var splits = new List(trimmed.Split(' ')); + var name = splits[0].ToUpper(); + if (!repoConfig.Keys.Contains(name)) + { + currentSetting = new ConfigSetting() + { + Comment = "SETTING DOES NOT EXIST IN REPOSITORY", + Name = name + }; + //don't support multikeying here + results.Add(currentSetting); + } + else if (commented) + continue; + else + currentSetting = repoConfig[name]; + currentSetting.ExistsInStatic = true; + splits.RemoveAt(0); + var value = String.Join(" ", splits); + if (currentSetting.IsMultiKey) + { + if (currentSetting.Values == null) + currentSetting.Values = new List { value }; + else + currentSetting.Values.Add(value); + } + else + currentSetting.Value = value; + } + error = null; + return results; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + //public api + public string ServerDirectory() + { + return Environment.CurrentDirectory; + } + + //public api + public string SetItem(TGConfigType type, ConfigSetting newSetting) + { + try + { + var entries = Retrieve(type, out string error); + if (entries == null) + return error; + + //serialize it + var asStrings = new List(); + bool somethingChanged = false; + foreach (var I in entries) + { + if (newSetting.Name == I.Name) + { + somethingChanged = true; + if (newSetting.Value == null && newSetting.Values == null) //unset + continue; + I.Value = newSetting.Value; + I.Values = newSetting.Values; + I.IsMultiKey = newSetting.IsMultiKey; + } + else if (!I.ExistsInStatic) + continue; + if (I.IsMultiKey) + foreach (var J in I.Values) + asStrings.Add((I.Name + " " + J).Trim()); + else + asStrings.Add((I.Name + " " + I.Value).Trim()); + } + + if (!somethingChanged) + return null; + + //write it out + lock (configLock) + { + File.WriteAllLines(ConfigTypeToPath(type, false), asStrings); + } + + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + //public api + public string SetJob(JobSetting job) + { + try + { + var entries = Jobs(out string error); + if (entries == null) + return error; + + var lines = new List(); + foreach (var J in entries) + { + var thingToUse = J.Name == job.Name ? job : J; + lines.Add(String.Format("{0}={1},{2}", thingToUse.Name, thingToUse.TotalPositions, thingToUse.SpawnPositions)); + } + + lock (configLock) + { + File.WriteAllLines(JobsConfig, lines); + } + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + //public api + public string SetInteropPort(ushort port) + { + try + { + lock (configLock) + { + File.WriteAllText(InteropConfig, port.ToString()); + } + InitInterop(); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + public IList MapSettings(out string error) + { + try + { + IList lines; + lock (configLock) + { + lines = File.ReadAllLines(MapConfig); + } + + MapSetting currentMap = null, lastDefaultMap = null; + IList results = new List(); + foreach(var L in lines) + { + var trimmed = L.Trim(); + if (trimmed.Length == 0 || trimmed[0] == '#') + continue; + var splits = trimmed.Split(' '); + switch (splits[0].ToLower()) + { + case "map": + if (splits.Length < 2) + continue; + currentMap = new MapSetting() + { + Name = splits[1], //defaults in game + Enabled = true, + VoteWeight = 1, + }; + break; + case "endmap": + if (currentMap != null) + results.Add(currentMap); + currentMap = null; + break; + case "minplayer": + case "minplayers": + if (splits.Length < 2) + continue; + if (currentMap != null) + try + { + currentMap.MinPlayers = Convert.ToInt32(splits[1]); + } + catch { + continue; + } + break; + case "maxplayer": + case "maxplayers": + if (splits.Length < 2) + continue; + if (currentMap != null) + try + { + currentMap.MaxPlayers = Convert.ToInt32(splits[1]); + } + catch + { + continue; + } + break; + case "weight": + case "voteweight": + if (splits.Length < 2) + continue; + if (currentMap != null) + try + { + currentMap.VoteWeight = float.Parse(splits[1]); + } + catch + { + continue; + } + break; + case "default": + case "defaultmap": + if (currentMap == null) + continue; + if (lastDefaultMap != null) + lastDefaultMap.Default = false; + currentMap.Default = true; + lastDefaultMap = currentMap; + break; + case "disabled": + if (currentMap == null) + continue; + currentMap.Enabled = false; + break; + } + } + error = null; + return results; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + public string SetMapSettings(MapSetting newSetting) + { + try + { + var entries = MapSettings(out string error); + if (entries == null) + return error; + + IList asStrings = new List(); + foreach (var I in entries) + { + var entryToUse = I.Name == newSetting.Name ? newSetting : I; + + asStrings.Add("map " + entryToUse.Name); + if (!entryToUse.Enabled) + asStrings.Add("\tdisabled"); + if (entryToUse.MinPlayers > 0) + asStrings.Add(String.Format("\tminplayers {0}", entryToUse.MinPlayers)); + if (entryToUse.MaxPlayers > 0) + asStrings.Add(String.Format("\tmaxplayers {0}", entryToUse.MaxPlayers)); + if (entryToUse.VoteWeight != 1) + asStrings.Add(String.Format("\tvoteweight {0}", entryToUse.VoteWeight)); + if (entryToUse.Default) + asStrings.Add("\tdefault"); + asStrings.Add("endmap"); + } + + lock (configLock) + { + File.WriteAllLines(MapConfig, asStrings); + } + + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + public string ReadRaw(string configRelativePath, bool repo, out string error) + { + try + { + var configDir = repo ? RepoConfig : StaticConfigDir; + var path = configDir + "/" + configRelativePath; + lock (configLock) { + var di1 = new DirectoryInfo(configDir); + var di2 = new DirectoryInfo(new FileInfo(path).Directory.FullName); + + var good = false; + while (di2 != null) + { + if (di2.FullName == di1.FullName) + { + good = true; + break; + } + else di2 = di2.Parent; + } + + if (!good) + { + error = "Cannot read above config directory!"; + return null; + } + + error = null; + return File.ReadAllText(path); + } + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + public string WriteRaw(string configRelativePath, string data) + { + try + { + var path = StaticConfigDir + "/" + configRelativePath; + lock (configLock) + { + var di1 = new DirectoryInfo(StaticConfigDir); + var destdir = new FileInfo(path).Directory.FullName; + var di2 = new DirectoryInfo(destdir); + + var good = false; + while (di2 != null) + { + if (di2.FullName == di1.FullName) + { + good = true; + break; + } + else di2 = di2.Parent; + } + + if (!good) + return "Cannot write above config directory!"; + Directory.CreateDirectory(destdir); + File.WriteAllText(path, data); + return null; + } + } + catch (Exception e) + { + return e.ToString(); + } + } + + public string SetTitleImage(string filename, byte[] data) + { + try + { + lock (configLock) + { + var path = StaticConfigDir + TitleImagesConfig + "/" + filename; + File.WriteAllBytes(path, data); + return null; + } + } + catch (Exception e) + { + return e.ToString(); + } + } + + public IDictionary ListPermissions(out string error) + { + try + { + IList lines; + lock (configLock) + { + lines = new List(File.ReadAllLines(AdminRanksRepo)); + } + IDictionary res = new Dictionary(); + + var inKeywordsBlock = false; + foreach(var I in lines) + { + var trimmed = I.Trim(); + if (trimmed.Length == 0 || trimmed[0] != '#') + continue; + + var splits = trimmed.Substring(1).Trim().Split(' '); + + if (splits[0] == "BEGIN_KEYWORDS") + inKeywordsBlock = true; + else if (splits[0] == "END_KEYWORDS") + inKeywordsBlock = false; + else if (inKeywordsBlock) + { + var key = splits[0]; + if (key.Length < 2 || key[0] != '+') //bad format + continue; + key = key.Substring(1); + + string description = ""; + bool found = false; + foreach (var J in splits) + if (found) + description += J + " "; + else if (J.Length == 1 && J[0] == '=') + found = true; + res.Add(key, description); + } + } + error = null; + return res; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + string WriteAdminRanks(IDictionary> ranks) + { + try + { + var lines = new List(); + foreach (var I in ranks) + { + var line = I.Key + " ="; + foreach (var J in I.Value) + line += " " + (J.Value ? "+" : "-") + J.Key; + lines.Add(line); + } + lock (configLock) + { + File.WriteAllLines(AdminRanksConfig, lines); + } + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + public string RemoveAdminRank(string rank) + { + var ranks = AdminRanks(out string error); + if (ranks == null) + return error; + if (!ranks.ContainsKey(rank)) + return "Given rank does not exist!"; + ranks.Remove(rank); + return WriteAdminRanks(ranks); + } + public string SetAdminRank(string rankName, IDictionary permissions) + { + var ranks = AdminRanks(out string error); + if (ranks == null) + return error; + if (ranks.ContainsKey(rankName)) + ranks[rankName] = permissions; + else + ranks.Add(rankName, permissions); + return WriteAdminRanks(ranks); + } + } +} diff --git a/TGServerService/Discord.cs b/TGServerService/Discord.cs new file mode 100644 index 0000000000..ebcba3d0b8 --- /dev/null +++ b/TGServerService/Discord.cs @@ -0,0 +1,220 @@ +using Discord; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using TGServiceInterface; + +namespace TGServerService +{ + class TGDiscordChatProvider : ITGChatProvider + { + public event OnChatMessage OnChatMessage; + DiscordClient client; + TGDiscordSetupInfo DiscordConfig; + object DiscordLock = new object(); + + IDictionary SeenPrivateChannels = new Dictionary(); + + public TGDiscordChatProvider(TGChatSetupInfo info) + { + Init(info); + } + + void Init(TGChatSetupInfo info) + { + DiscordConfig = new TGDiscordSetupInfo(info); + client = new DiscordClient(); + client.MessageReceived += Client_MessageReceived; + } + + private void Client_MessageReceived(object sender, MessageEventArgs e) + { + var isValidChannel = Properties.Settings.Default.ChatChannels.Contains("#" + e.Channel.Name) || e.Channel.IsPrivate; + if (!isValidChannel) + return; + + var tagged = e.Channel.IsPrivate && e.User.Name != client.CurrentUser.Name; + if (tagged && !SeenPrivateChannels.ContainsKey(e.Channel.Name)) + SeenPrivateChannels.Add(e.Channel.Name, e.Channel); + + var splits = new List(e.Message.Text.Trim().Split(' ')); + if (splits[0] == "@" + client.CurrentUser.Name) + { + splits.RemoveAt(0); + tagged = true; + } + OnChatMessage(e.User.Name, e.Channel.Name, String.Join(" ", splits), tagged); + } + + public string Connect() + { + try + { + lock (DiscordLock) + { + SeenPrivateChannels.Clear(); + client.Connect(DiscordConfig.BotToken, TokenType.Bot).Wait(); + } + return !Connected() ? "Connection failed!" : null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + public bool Connected() + { + lock (DiscordLock) + { + return client.State == ConnectionState.Connected; + } + } + + public void Disconnect() + { + try + { + if (!Connected()) + return; + lock (DiscordLock) + { + SeenPrivateChannels.Clear(); + client.Disconnect().Wait(); + } + } + catch { } + } + + public string Reconnect() + { + try + { + lock (DiscordLock) + { + SeenPrivateChannels.Clear(); + if (client.State == ConnectionState.Connected) + client.Disconnect().Wait(); + client.Connect(DiscordConfig.BotToken, TokenType.Bot).Wait(); + } + return !Connected() ? "Connection failed!" : null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + public string SendMessage(string msg, bool adminOnly = false) + { + try + { + lock (DiscordLock) + { + var tasks = new List(); + var Config = Properties.Settings.Default; + foreach (var I in client.Servers) + foreach (var J in I.TextChannels) + { + var SendToThisChannel = adminOnly ? ("#" + J.Name) == Config.ChatAdminChannel : Config.ChatChannels.Contains("#" + J.Name); + if (SendToThisChannel) + tasks.Add(J.SendMessage(msg)); + } + foreach (var I in tasks) + I.Wait(); + TGServerService.WriteLog(String.Format("Discord Send ({0}): {1}", adminOnly ? "Broadcast" : "Admin", msg)); + return null; + } + } + catch (Exception e) + { + return e.ToString(); + } + } + + public string SendMessageDirect(string message, string channel) + { + try + { + lock (DiscordLock) + { + var tasks = new List(); + var Config = Properties.Settings.Default; + if (SeenPrivateChannels.ContainsKey(channel)) + SeenPrivateChannels[channel].SendMessage(message).Wait(); + else + foreach (var I in client.Servers) + foreach (var J in I.TextChannels) + if (J.Name == channel) + J.SendMessage(message).Wait(); + TGServerService.WriteLog(String.Format("Discord Send ({0}): {1}", channel, message)); + return null; + } + } + catch (Exception e) + { + return e.ToString(); + } + } + + public void SetChannels(string[] channels = null, string adminchannel = null) + { + //noop + } + + public string SetProviderInfo(TGChatSetupInfo info) + { + try + { + lock (DiscordLock) + { + client.Dispose(); + Init(info); + if(Properties.Settings.Default.ChatEnabled) + Connect(); + } + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + client.Dispose(); + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~TGDiscordChatProvider() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/TGServerService/DreamDaemon.cs b/TGServerService/DreamDaemon.cs new file mode 100644 index 0000000000..08313278fc --- /dev/null +++ b/TGServerService/DreamDaemon.cs @@ -0,0 +1,437 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using TGServiceInterface; + +namespace TGServerService +{ + //manages the dd window. + //It's not possible to actually click it while starting it in CL mode, so in order to change visibility etc. It restarts the process when the round ends + partial class TGStationServer : ITGDreamDaemon + { + const int DDHangStartTime = 60; + const int DDBadStartTime = 10; + + Process Proc; + + object watchdogLock = new object(); + Thread DDWatchdog; + TGDreamDaemonStatus currentStatus; + ushort currentPort = 0; + + object restartLock = new object(); + bool RestartInProgress = false; + bool AwaitingShutdown = false; + + TGDreamDaemonSecurity StartingSecurity; + TGDreamDaemonVisibility StartingVisiblity; + + //Only need 1 proc instance + void InitDreamDaemon() + { + Proc = new Process(); + Proc.StartInfo.FileName = ByondDirectory + "/bin/dreamdaemon.exe"; + Proc.StartInfo.UseShellExecute = false; + + //autostart the server + if (Properties.Settings.Default.DDAutoStart) + //break this off so we don't hold up starting the service + new Thread(new ThreadStart(InitStart)); + } + + //void wrapper for Start + void InitStart() + { + Start(); + } + + //die now k thx + void DisposeDreamDaemon() + { + if (DaemonStatus() == TGDreamDaemonStatus.Online) + { + WorldAnnounce("Server service stopped"); + Thread.Sleep(1000); + } + Stop(); + } + + //public api + public TGDreamDaemonStatus DaemonStatus() + { + lock (watchdogLock) + { + return currentStatus; + } + } + + //public api + public void RequestRestart() + { + SendCommand(SCHardReboot); + } + + //public api + public void RequestStop() + { + lock (watchdogLock) + { + AwaitingShutdown = true; + } + SendCommand(SCGracefulShutdown); + } + + //public api + public string Stop() + { + Thread t; + lock (watchdogLock) + { + t = DDWatchdog; + DDWatchdog = null; + } + if (t != null && t.IsAlive) + { + t.Abort(); + t.Join(); + return null; + } + else + return "Server not running"; + } + + //public api + public void SetPort(ushort new_port) + { + lock (watchdogLock) + { + Properties.Settings.Default.ServerPort = new_port; + RequestRestart(); + } + } + + //public api + public string Restart() + { + if (DaemonStatus() == TGDreamDaemonStatus.Offline) + return Start(); + if (!Monitor.TryEnter(restartLock)) + return "Restart already in progress"; + try + { + SendMessage("DD: Hard restart triggered"); + RestartInProgress = true; + Stop(); + var res = Start(); + return res; + } + finally + { + RestartInProgress = false; + Monitor.Exit(restartLock); + } + } + + //loop that keeps the server running + void Watchdog() + { + try + { + lock (restartLock) + { + if (!RestartInProgress) + SendMessage("DD: Server started, watchdog active..."); + else + RestartInProgress = false; + } + var retries = 0; + while (true) + { + var starttime = DateTime.Now; + Proc.WaitForExit(); + + lock (watchdogLock) + { + currentStatus = TGDreamDaemonStatus.HardRebooting; + currentPort = 0; + Proc.Close(); + ShutdownInterop(); + + if (AwaitingShutdown) + return; + + if ((DateTime.Now - starttime).Seconds < DDBadStartTime) + { + ++retries; + var sleep_time = (int)Math.Min(Math.Pow(2, retries), 3600); //max of one hour + SendMessage(String.Format("DD: Watchdog server startup failed! Retrying in {0} seconds...", sleep_time)); + Thread.Sleep(sleep_time * 1000); + } + else + { + retries = 0; + SendMessage("DD: DreamDaemon crashed or exited! Rebooting..."); + } + } + + var res = StartImpl(true); + if (res != null) + throw new Exception("Hard restart failed: " + res); + } + } + catch(ThreadAbortException) + { + //No Mr bond, I expect you to die + try + { + Proc.Kill(); + Proc.Close(); + ShutdownInterop(); + } + catch + { } + } + catch (Exception e) + { + SendMessage("DD: Watchdog thread crashed!"); + TGServerService.WriteLog("Watch dog thread crashed! Exception: " + e.ToString(), EventLogEntryType.Error); + } + finally + { + lock (watchdogLock) + { + currentStatus = TGDreamDaemonStatus.Offline; + currentPort = 0; + AwaitingShutdown = false; + if (!RestartInProgress) + SendMessage("DD: Server stopped, watchdog exiting..."); + } + } + } + + //public api + public string CanStart() + { + lock (watchdogLock) + { + return CanStartImpl(); + } + } + + string CanStartImpl() + { + if (GetVersion(TGByondVersion.Installed) == null) + return "Byond is not installed!"; + var DMB = GameDirLive + "/" + Properties.Settings.Default.ProjectName + ".dmb"; + if (!File.Exists(DMB)) + return String.Format("Unable to find {0}!", DMB); + return null; + } + + //public api + public string Start() + { + if (CurrentStatus() == TGByondStatus.Staged) + { + //IMPORTANT: SLEEP FOR A MOMENT OR WONDOWS WON'T RELEASE THE FUCKING BYOND DLL HANDLES!!!! REEEEEEE + Thread.Sleep(3000); + ApplyStagedUpdate(); + } + lock (watchdogLock) + { + if (currentStatus != TGDreamDaemonStatus.Offline) + return "Server already running"; + var res = CanStartImpl(); + if (res != null) + return res; + currentPort = 0; + currentStatus = TGDreamDaemonStatus.HardRebooting; + } + return StartImpl(false); + } + + //translate the configured security level into a byond param + string SecurityWord(bool starting = false) + { + var level = starting ? StartingSecurity : (TGDreamDaemonSecurity)Properties.Settings.Default.ServerSecurity; + switch (level) + { + case TGDreamDaemonSecurity.Safe: + return "safe"; + case TGDreamDaemonSecurity.Trusted: + return "trusted"; + case TGDreamDaemonSecurity.Ultrasafe: + return "ultrasafe"; + default: + throw new Exception(String.Format("Bad DreamDaemon security level: {0}", level)); + } + } + + //same thing with visibility + string VisibilityWord(bool starting = false) + { + var level = starting ? StartingVisiblity : (TGDreamDaemonVisibility)Properties.Settings.Default.ServerVisiblity; + switch (level) + { + case TGDreamDaemonVisibility.Invisible: + return "invisible"; + case TGDreamDaemonVisibility.Private: + return "private"; + case TGDreamDaemonVisibility.Public: + return "public"; + default: + throw new Exception(String.Format("Bad DreamDaemon visibility level: {0}", level)); + } + } + + //used by Start and Watchdog to start a DD instance + string StartImpl(bool watchdog) + { + try + { + lock (watchdogLock) + { + var res = CanStartImpl(); + if (res != null) + return res; + + var Config = Properties.Settings.Default; + var DMB = GameDirLive + "/" + Config.ProjectName + ".dmb"; + + GenCommsKey(); + StartingVisiblity = (TGDreamDaemonVisibility)Config.ServerVisiblity; + StartingSecurity = (TGDreamDaemonSecurity)Config.ServerSecurity; + Proc.StartInfo.Arguments = String.Format("{0} -port {1} -close -verbose -params server_service={4} -{2} -{3}", DMB, Config.ServerPort, SecurityWord(), VisibilityWord(), serviceCommsKey); + InitInterop(); + Proc.Start(); + + if (!Proc.WaitForInputIdle(DDHangStartTime * 1000)) + { + Proc.Kill(); + Proc.Close(); + ShutdownInterop(); + currentStatus = TGDreamDaemonStatus.Offline; + currentPort = 0; + return String.Format("Server start is taking more than {0}s! Aborting!", DDHangStartTime); + } + currentPort = Config.ServerPort; + currentStatus = TGDreamDaemonStatus.Online; + if (!watchdog) + { + DDWatchdog = new Thread(new ThreadStart(Watchdog)); + DDWatchdog.Start(); + } + return null; + } + } + catch (Exception e) + { + currentStatus = TGDreamDaemonStatus.Offline; + return e.ToString(); + } + } + + //public api + public TGDreamDaemonVisibility VisibilityLevel() + { + lock (watchdogLock) + { + return (TGDreamDaemonVisibility)Properties.Settings.Default.ServerVisiblity; + } + } + + //public api + public bool SetVisibility(TGDreamDaemonVisibility NewVis) + { + var Config = Properties.Settings.Default; + var visInt = (int)NewVis; + bool needReboot; + lock (watchdogLock) + { + needReboot = Config.ServerVisiblity != visInt; + Config.ServerVisiblity = visInt; + } + if (needReboot) + RequestRestart(); + return DaemonStatus() != TGDreamDaemonStatus.Online; + } + + //public api + public TGDreamDaemonSecurity SecurityLevel() + { + lock (watchdogLock) + { + return (TGDreamDaemonSecurity)Properties.Settings.Default.ServerSecurity; + } + } + + //public api + public bool SetSecurityLevel(TGDreamDaemonSecurity level) + { + var Config = Properties.Settings.Default; + var secInt = (int)level; + bool needReboot; + lock (watchdogLock) + { + needReboot = Config.ServerSecurity != secInt; + Config.ServerSecurity = secInt; + } + if (needReboot) + RequestRestart(); + return DaemonStatus() != TGDreamDaemonStatus.Online; + } + + //public api + public bool Autostart() + { + return Properties.Settings.Default.DDAutoStart; + } + + //public api + public void SetAutostart(bool on) + { + Properties.Settings.Default.DDAutoStart = on; + } + + //public api + public string StatusString(bool includeMetaInfo) + { + const string visSecStr = " (Vis: {0}, Sec: {1})"; + string res; + var ds = DaemonStatus(); + switch (ds) + { + case TGDreamDaemonStatus.Offline: + res = "OFFLINE"; + break; + case TGDreamDaemonStatus.HardRebooting: + res = "REBOOTING"; + break; + case TGDreamDaemonStatus.Online: + res = SendCommand(SCIRCCheck); + if (includeMetaInfo) + { + string secandvis; + lock (watchdogLock) + { + secandvis = String.Format(visSecStr, VisibilityWord(true), SecurityWord(true)); + } + res += secandvis; + } + break; + default: + res = "NULL AND ERRORS"; + break; + } + if (includeMetaInfo && ds != TGDreamDaemonStatus.Online) + res += String.Format(visSecStr, VisibilityWord(), SecurityWord()); + return res; + } + + public ushort Port() + { + return Properties.Settings.Default.ServerPort; + } + } +} diff --git a/TGServerService/IRC.cs b/TGServerService/IRC.cs new file mode 100644 index 0000000000..a53c74de05 --- /dev/null +++ b/TGServerService/IRC.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using TGServiceInterface; +using Meebey.SmartIrc4net; + + +namespace TGServerService +{ + class TGIRCChatProvider : ITGChatProvider + { + const string PrivateMessageMarker = "---PRIVATE-MSG---"; + IrcFeatures irc; + int reconnectAttempt = 0; + + object IRCLock = new object(); + + TGIRCSetupInfo IRCConfig; + + public event OnChatMessage OnChatMessage; + + public TGIRCChatProvider(TGChatSetupInfo info) + { + IRCConfig = new TGIRCSetupInfo(info); + irc = new IrcFeatures() { SupportNonRfc = true }; + irc.OnChannelMessage += Irc_OnChannelMessage; + irc.OnQueryMessage += Irc_OnQueryMessage; + irc.CtcpUserInfo = "/tg/station 13 Server Service " + System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion; + } + + //public api + public string SendMessageDirect(string message, string channel) + { + try + { + if (!Connected()) + return "Disconnected."; + lock (IRCLock) + { + if (channel.Contains(PrivateMessageMarker)) + channel = channel.Replace(PrivateMessageMarker, ""); + irc.SendMessage(SendType.Message, channel, message); + } + TGServerService.WriteLog(String.Format("IRC Send ({0}): {1}", channel, message)); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + public string SetProviderInfo(TGChatSetupInfo info) + { + var convertedInfo = (TGIRCSetupInfo)info; + var serverChange = convertedInfo.URL != IRCConfig.URL || convertedInfo.Port != IRCConfig.Port; + IRCConfig = convertedInfo; + if (serverChange) + return Reconnect(); + else if (convertedInfo.Nickname != irc.Nickname) + irc.RfcNick(convertedInfo.Nickname); + Login(); + return null; + } + + public void SetChannels(string[] channels = null, string adminchannel = null) + { + lock (IRCLock) { + var channelsList = new List(channels); + var Config = Properties.Settings.Default; + foreach (var I in irc.JoinedChannels) + if (!channelsList.Contains(I)) + irc.RfcPart(I); + foreach (var I in channelsList) + if (!irc.JoinedChannels.Contains(I)) + irc.RfcJoin(I); + } + } + + //private message + private void Irc_OnQueryMessage(object sender, IrcEventArgs e) + { + OnChatMessage(e.Data.Nick, e.Data.Nick + PrivateMessageMarker, e.Data.Message, true); + } + + private void Irc_OnChannelMessage(object sender, IrcEventArgs e) + { + var formattedMessage = e.Data.Message.Trim(); + + var splits = new List(formattedMessage.Split(' ')); + var tagged = splits[0].ToLower() == irc.Nickname.ToLower(); + + if (tagged) + { + splits.RemoveAt(0); + formattedMessage = String.Join(" ", splits); + } + + OnChatMessage(e.Data.Nick, e.Data.Channel, formattedMessage, tagged); + } + //Joins configured channels + void JoinChannels() + { + foreach (var I in Properties.Settings.Default.ChatChannels) + irc.RfcJoin(I); + } + //runs the login command + void Login() + { + lock (IRCLock) + { + if (IRCConfig.AuthTarget != null) + irc.SendMessage(SendType.Message, IRCConfig.AuthTarget, IRCConfig.AuthMessage); + } + } + //public api + public string Connect() + { + if (Connected()) + return null; + lock (IRCLock) + { + try + { + try + { + irc.Connect(IRCConfig.URL, IRCConfig.Port); + reconnectAttempt = 0; + } + catch (Exception e) + { + reconnectAttempt++; + if (reconnectAttempt <= 5) + { + Thread.Sleep(5000); //Reconnecting after 5 seconds. + return Connect(); + } + else + { + return "IRC server unreachable: " + e.ToString(); + } + } + + try + { + irc.Login(IRCConfig.Nickname, IRCConfig.Nickname); + } + catch (Exception e) + { + return "Bot name is already taken: " + e.ToString(); + } + Login(); + JoinChannels(); + new Thread(new ThreadStart(IRCListen)) { IsBackground = true }.Start(); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + } + + //This is the thread that listens for irc messages + void IRCListen() + { + while (irc != null && Connected()) + try + { + irc.Listen(); + } + catch { } + } + + //public api + public string Reconnect() + { + Disconnect(); + return Connect(); + } + + //public api + public void Disconnect() + { + try + { + lock (IRCLock) + { + if (irc.IsConnected) + { + irc.RfcQuit("Twas meant to be..."); + irc.Disconnect(); + } + } + } + catch + { } + } + //public api + public bool Connected() + { + lock (IRCLock) + { + return irc != null && irc.IsConnected; + } + } + //public api + public string SendMessage(string message, bool adminOnly) + { + try + { + if (!Connected()) + return "Disconnected."; + lock (IRCLock) + { + var Config = Properties.Settings.Default; + if (adminOnly) + irc.SendMessage(SendType.Message, Config.ChatAdminChannel, message); + else + foreach (var I in Config.ChatChannels) + irc.SendMessage(SendType.Message, I, message); + } + TGServerService.WriteLog(String.Format("IRC Send{0}: {1}", adminOnly ? " (ADMIN)" : "", message)); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + Disconnect(); + irc = null; + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~TGIRCChatProvider() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/TGServerService/InterfaceBase.cs b/TGServerService/InterfaceBase.cs new file mode 100644 index 0000000000..74f9c2f1fd --- /dev/null +++ b/TGServerService/InterfaceBase.cs @@ -0,0 +1,124 @@ +using System; +using System.ServiceModel; +using System.Threading; +using TGServiceInterface; + +namespace TGServerService +{ + //I know the fact that this is one massive partial class is gonna trigger everyone + //There really was no other succinct way to do it + + //this line basically says make one instance of the service, use it multithreaded for requests, and never delete it + [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)] + partial class TGStationServer : IDisposable, ITGStatusCheck, ITGServerUpdater + { + //call partial constructors/destructors from here + //called when the service is started + public TGStationServer() + { + InitChat(); + InitByond(); + InitCompiler(); + InitDreamDaemon(); + } + + //called when the service is stopped + void RunDisposals() + { + DisposeDreamDaemon(); + DisposeCompiler(); + DisposeByond(); + DisposeRepo(); + DisposeChat(); + } + + //one stop update + public string UpdateServer(TGRepoUpdateMethod updateType, bool push_changelog_if_enabled, ushort testmerge_pr) + { + string res; + switch (updateType) + { + case TGRepoUpdateMethod.Hard: + case TGRepoUpdateMethod.Merge: + res = Update(updateType == TGRepoUpdateMethod.Hard); + if (res != null && res != RepoErrorUpToDate) + return res; + break; + case TGRepoUpdateMethod.Reset: + res = Reset(true); + if (res != null) + return res; + break; + case TGRepoUpdateMethod.None: + break; + } + + if (testmerge_pr != 0) + { + res = MergePullRequest(testmerge_pr); + if (res != null && res != RepoErrorUpToDate) + return res; + } + + GenerateChangelog(out res); + if (res != null) + return res; + + if (push_changelog_if_enabled && SSHAuth()) + { + res = Commit(); + if (res != null) + return res; + res = Push(); + if (res != null) + return res; + } + + if (!Compile()) + return "Compilation could not be started!"; + return null; + } + + //just here to test the WCF connection + public void VerifyConnection() + {} + + //mostly generated code with a call to RunDisposals() + //you don't need to open this + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + RunDisposals(); + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~TGStationServer() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/TGServerService/Interop.cs b/TGServerService/Interop.cs new file mode 100644 index 0000000000..b08dbb0fea --- /dev/null +++ b/TGServerService/Interop.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Web.Security; +using TGServiceInterface; + +namespace TGServerService +{ + //handles talking between the world and us + partial class TGStationServer + { + object topicLock = new object(); + const int CommsKeyLen = 64; + string serviceCommsKey; //regenerated every DD restart + + Thread NudgeThread; + object NudgeLock = new object(); + + //See code/modules/server_tools/server_tools.dm for command switch + const string SCHardReboot = "hard_reboot"; //requests that dreamdaemon restarts when the round ends + const string SCGracefulShutdown = "graceful_shutdown"; //requests that dreamdaemon stops when the round ends + const string SCWorldAnnounce = "world_announce"; //sends param 'message' to the world + const string SCIRCCheck = "irc_check"; //returns game stats + const string SCIRCStatus = "irc_status"; //returns admin stats + const string SCNameCheck = "namecheck"; //returns keywords lookup + const string SCAdminPM = "adminmsg"; //pms a target ckey + const string SCAdminWho = "adminwho"; //lists admins + + //raw command string sent here via world.ExportService + void HandleCommand(string cmd) + { + var splits = new List(cmd.Split(' ')); + + switch (splits[0]) + { + case "irc": + splits.RemoveAt(0); + SendMessage("GAME: " + String.Join(" ", splits)); + break; + case "killme": + //Do this is a seperate thread or we'll kill this thread in the middle of rebooting + ThreadPool.QueueUserWorkItem(_ => { Restart(); }); + break; + case "send2irc": + splits.RemoveAt(0); + SendMessage("RELAY: " + String.Join(" ", splits), true); + break; + } + } + + string SendCommand(string cmd) + { + lock (watchdogLock) + { + if (currentStatus != TGDreamDaemonStatus.Online) + return "Error: Server Offline!"; + return SendTopic(String.Format("serviceCommsKey={0};command={1}", serviceCommsKey, cmd), currentPort); + } + } + + bool WorldAnnounce(string message) + { + return SendCommand(SCWorldAnnounce + ";message=" + message) == "SUCCESS" ; + } + + string SendPM(string targetCkey, string sender, string message) + { + return SendCommand(String.Format("{3};target={0};sender={1};message={2}", targetCkey, sender, message, SCAdminPM)); + } + + string NameCheck(string targetCkey, string sender) + { + return SendCommand(String.Format("{2};target={0};sender={1}", targetCkey, sender, SCNameCheck)); + } + + //Fuckery to diddle byond with the right packet to accept our girth + string SendTopic(string topicdata, ushort port) + { + lock (topicLock) { + using (var topicSender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { SendTimeout = 5000, ReceiveTimeout = 5000 }) + { + try + { + topicSender.Connect(IPAddress.Loopback, port); + + StringBuilder stringPacket = new StringBuilder(); + stringPacket.Append((char)'\x00', 8); + stringPacket.Append('?' + topicdata); + stringPacket.Append((char)'\x00'); + string fullString = stringPacket.ToString(); + var packet = Encoding.ASCII.GetBytes(fullString); + packet[1] = 0x83; + var FinalLength = packet.Length - 4; + if (FinalLength > UInt16.MaxValue) + return "Error: Topic too long"; + + var lengthBytes = BitConverter.GetBytes((ushort)FinalLength); + + packet[2] = lengthBytes[1]; //fucking endianess + packet[3] = lengthBytes[0]; + + topicSender.Send(packet); + + string returnedString = "NULL"; + try + { + var returnedData = new byte[512]; + topicSender.Receive(returnedData); + var raw_string = Encoding.ASCII.GetString(returnedData).TrimEnd(new char[] { (char)0 }).Trim(); + if (raw_string.Length > 6) + returnedString = raw_string.Substring(5, raw_string.Length - 6).Trim(); + } + catch + { + returnedString = "Topic recieve error!"; + } + finally + { + topicSender.Shutdown(SocketShutdown.Both); + } + + TGServerService.WriteLog("Topic: \"" + topicdata + "\" Returned: " + returnedString); + return returnedString; + } + catch + { + TGServerService.WriteLog("Failed to send topic: " + topicdata, EventLogEntryType.Error); + return "Topic delivery failed!"; + } + } + } + } + + //Every time we make a new DD process we generate a new comms key for security + //It's in world.params['server_service'] + void GenCommsKey() + { + var charsToRemove = new string[] { "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", "=", "[", "{", "]", "}", ";", ":", "<", ">", "|", ".", "/", "?" }; + serviceCommsKey = String.Empty; + do { + var tmp = Membership.GeneratePassword(CommsKeyLen, 0); + foreach (var c in charsToRemove) + tmp = tmp.Replace(c, String.Empty); + serviceCommsKey += tmp; + } while (serviceCommsKey.Length < CommsKeyLen); + serviceCommsKey = serviceCommsKey.Substring(0, CommsKeyLen); + TGServerService.WriteLog("Service Comms Key set to: " + serviceCommsKey); + } + + //Start listening for nudges on the configured port + void InitInterop() + { + lock (NudgeLock) + { + ShutdownInteropNoLock(); + NudgeThread = new Thread(new ThreadStart(NudgeHandler)) { IsBackground = true }; + NudgeThread.Start(); + } + } + + void ShutdownInterop() + { + lock (NudgeLock) + { + ShutdownInteropNoLock(); + } + } + + void ShutdownInteropNoLock() + { + if (NudgeThread != null) + { + NudgeThread.Abort(); + NudgeThread.Join(); + } + } + + void NudgeHandler() + { + try + { + var np = InteropPort(out string error); + if (error != null) + { + TGServerService.WriteLog("Unable to start nudge handler! Error: " + error, EventLogEntryType.Warning); + return; + } + + using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + listener.Bind(new IPEndPoint(IPAddress.Any, np)); + listener.Listen(5); + + // Start listening for connections. + using (var clientConnected = new ManualResetEvent(false)) + { + while (true) + { + // Program is suspended while waiting for an incoming connection. + clientConnected.Reset(); + listener.BeginAccept(delegate (IAsyncResult asyncResult) + { + try + { + var handler = listener.EndAccept(asyncResult); + + var bytes = new byte[1024]; + int bytesRec = handler.Receive(bytes); + // Show the data on the console. + HandleCommand(Encoding.ASCII.GetString(bytes, 0, bytesRec)); + + handler.Shutdown(SocketShutdown.Both); + handler.Close(); + clientConnected.Set(); + } + catch (ObjectDisposedException) + { } + }, null); + clientConnected.WaitOne(); + } + } + } + } + catch (ThreadAbortException) + { + return; + } + catch (Exception e) + { + TGServerService.WriteLog("Nudge handler thread crashed: " + e.ToString(), EventLogEntryType.Error); + } + } + } +} diff --git a/TGServerService/LockDependancies.txt b/TGServerService/LockDependancies.txt new file mode 100644 index 0000000000..032a584196 --- /dev/null +++ b/TGServerService/LockDependancies.txt @@ -0,0 +1,12 @@ +RepoLock protects the repo for the full duration of short operations +RepoBusy protects the repo for long operations + +lock RepoLock and check RepoBusy to see if you can use the repo + +configLock is only for atomically reading and writing the config directory + +compilerLock protects the compilerCurrentStatus and lastCompilerError vars + +byondLock protects the updateStat and lastError vars + +watchdogLock protects the currentStatus var \ No newline at end of file diff --git a/TGServerService/Program.cs b/TGServerService/Program.cs new file mode 100644 index 0000000000..96e9721f66 --- /dev/null +++ b/TGServerService/Program.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.IO; + +namespace TGServerService +{ + public static class Program + { + static void Main() => new TGServerService(); //wondows + + //Everything in this file is just generic helpers + + //http://stackoverflow.com/questions/1701457/directory-delete-doesnt-work-access-denied-error-but-under-windows-explorer-it + public static void DeleteDirectory(string path, bool ContentsOnly = false) + { + var di = new DirectoryInfo(path); + if (!di.Exists) + return; + NormalizeAndDelete(di); + if(!ContentsOnly) + di.Delete(true); + } + static void NormalizeAndDelete(DirectoryInfo dir) + { + foreach (var subDir in dir.GetDirectories()) + { + NormalizeAndDelete(subDir); + subDir.Delete(true); + } + foreach (var file in dir.GetFiles()) + { + file.Attributes = FileAttributes.Normal; + } + } + + public static void CopyDirectory(string sourceDirName, string destDirName, IList ignore = null, bool ignoreIfNotExists = false) + { + // If the destination directory doesn't exist, create it. + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + // Get the subdirectories for the specified directory. + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + if (ignoreIfNotExists) + return; + throw new DirectoryNotFoundException( + "Source directory does not exist or could not be found: " + + sourceDirName); + } + + DirectoryInfo[] dirs = dir.GetDirectories(); + + // Get the files in the directory and copy them to the new location. + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + if (ignore != null && ignore.Contains(file.Name)) + continue; + string temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, true); + } + + // copy them and their contents to new location. + foreach (DirectoryInfo subdir in dirs) + { + if (ignore != null && ignore.Contains(subdir.Name)) + continue; + string temppath = Path.Combine(destDirName, subdir.Name); + CopyDirectory(subdir.FullName, temppath, ignore); + } + } + } +} diff --git a/TGServerService/ProjectInstaller.Designer.cs b/TGServerService/ProjectInstaller.Designer.cs new file mode 100644 index 0000000000..83118398ae --- /dev/null +++ b/TGServerService/ProjectInstaller.Designer.cs @@ -0,0 +1,59 @@ +namespace ServerService +{ + partial class ProjectInstaller + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); + this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); + // + // serviceProcessInstaller1 + // + this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; + this.serviceProcessInstaller1.Password = null; + this.serviceProcessInstaller1.Username = null; + // + // serviceInstaller1 + // + this.serviceInstaller1.ServiceName = "TGstation Server"; + this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; + this.serviceInstaller1.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.ServiceInstaller1_AfterInstall); + // + // ProjectInstaller + // + this.Installers.AddRange(new System.Configuration.Install.Installer[] { + this.serviceProcessInstaller1, + this.serviceInstaller1}); + + } + + #endregion + + private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; + private System.ServiceProcess.ServiceInstaller serviceInstaller1; + } +} \ No newline at end of file diff --git a/TGServerService/ProjectInstaller.cs b/TGServerService/ProjectInstaller.cs new file mode 100644 index 0000000000..f4d5acba77 --- /dev/null +++ b/TGServerService/ProjectInstaller.cs @@ -0,0 +1,28 @@ +using System.ComponentModel; +using System.Configuration.Install; +using System.ServiceProcess; + +namespace ServerService +{ + [RunInstaller(true)] + public partial class ProjectInstaller : Installer + { + public ProjectInstaller() + { + InitializeComponent(); + serviceInstaller1.BeforeUninstall += ServiceInstaller1_BeforeUninstall; + } + + private void ServiceInstaller1_BeforeUninstall(object sender, InstallEventArgs e) + { + } + + private void ServiceInstaller1_AfterInstall(object sender, InstallEventArgs e) + { + using (ServiceController sc = new ServiceController(serviceInstaller1.ServiceName)) + { + sc.Start(); + } + } + } +} diff --git a/TGServerService/ProjectInstaller.resx b/TGServerService/ProjectInstaller.resx new file mode 100644 index 0000000000..235f1b0bfb --- /dev/null +++ b/TGServerService/ProjectInstaller.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 56 + + + 196, 17 + + + False + + \ No newline at end of file diff --git a/TGServerService/Properties/AssemblyInfo.cs b/TGServerService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..09835ad1a8 --- /dev/null +++ b/TGServerService/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TGStation Server Service")] +[assembly: AssemblyDescription("Server Service for running BYOND games")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f32eda25-0855-411c-af5e-f0d042917e2d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("3.0.78.0")] +[assembly: AssemblyFileVersion("3.0.78.0")] diff --git a/TGServerService/Properties/Settings.Designer.cs b/TGServerService/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..f6bca1d455 --- /dev/null +++ b/TGServerService/Properties/Settings.Designer.cs @@ -0,0 +1,234 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace TGServerService.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("tgstation")] + public string ProjectName { + get { + return ((string)(this["ProjectName"])); + } + set { + this["ProjectName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1337")] + public ushort ServerPort { + get { + return ((ushort)(this["ServerPort"])); + } + set { + this["ServerPort"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool PushChangelogToGit { + get { + return ((bool)(this["PushChangelogToGit"])); + } + set { + this["PushChangelogToGit"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("tgstation-server")] + public string CommitterName { + get { + return ((string)(this["CommitterName"])); + } + set { + this["CommitterName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("tgstation-server@tgstation13.org")] + public string CommitterEmail { + get { + return ((string)(this["CommitterEmail"])); + } + set { + this["CommitterEmail"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("C:/tgstation-server-3")] + public string ServerDirectory { + get { + return ((string)(this["ServerDirectory"])); + } + set { + this["ServerDirectory"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public int ServerSecurity { + get { + return ((int)(this["ServerSecurity"])); + } + set { + this["ServerSecurity"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("2")] + public int ServerVisiblity { + get { + return ((int)(this["ServerVisiblity"])); + } + set { + this["ServerVisiblity"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool ChatEnabled { + get { + return ((bool)(this["ChatEnabled"])); + } + set { + this["ChatEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool DDAutoStart { + get { + return ((bool)(this["DDAutoStart"])); + } + set { + this["DDAutoStart"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("C:\\Python27")] + public string PythonPath { + get { + return ((string)(this["PythonPath"])); + } + set { + this["PythonPath"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public int ChatProvider { + get { + return ((int)(this["ChatProvider"])); + } + set { + this["ChatProvider"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("NEEDS INITIALIZING")] + public string ChatProviderData { + get { + return ((string)(this["ChatProviderData"])); + } + set { + this["ChatProviderData"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("\r\n\r\n theghostofwhibyl1\r\n")] + public global::System.Collections.Specialized.StringCollection ChatAdmins { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["ChatAdmins"])); + } + set { + this["ChatAdmins"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("\r\n\r\n #botbus\r\n")] + public global::System.Collections.Specialized.StringCollection ChatChannels { + get { + return ((global::System.Collections.Specialized.StringCollection)(this["ChatChannels"])); + } + set { + this["ChatChannels"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("#botbus")] + public string ChatAdminChannel { + get { + return ((string)(this["ChatAdminChannel"])); + } + set { + this["ChatAdminChannel"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("John Madden")] + public string ChatProviderEntropy { + get { + return ((string)(this["ChatProviderEntropy"])); + } + set { + this["ChatProviderEntropy"] = value; + } + } + } +} diff --git a/TGServerService/Properties/Settings.settings b/TGServerService/Properties/Settings.settings new file mode 100644 index 0000000000..b2fd845f76 --- /dev/null +++ b/TGServerService/Properties/Settings.settings @@ -0,0 +1,63 @@ + + + + + + tgstation + + + 1337 + + + False + + + tgstation-server + + + tgstation-server@tgstation13.org + + + C:/tgstation-server-3 + + + 0 + + + 2 + + + False + + + False + + + C:\Python27 + + + 0 + + + NEEDS INITIALIZING + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <string>theghostofwhibyl1</string> +</ArrayOfString> + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <string>#botbus</string> +</ArrayOfString> + + + #botbus + + + John Madden + + + \ No newline at end of file diff --git a/TGServerService/Repository.cs b/TGServerService/Repository.cs new file mode 100644 index 0000000000..3ae0eba32c --- /dev/null +++ b/TGServerService/Repository.cs @@ -0,0 +1,855 @@ +using LibGit2Sharp; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Web.Script.Serialization; +using TGServiceInterface; + +namespace TGServerService +{ + partial class TGStationServer : ITGRepository, IDisposable + { + const string RepoPath = "Repository"; + const string RepoConfig = RepoPath + "/config"; + const string RepoData = RepoPath + "/data"; + const string RepoErrorUpToDate = "Already up to date!"; + const string SSHPushRemote = "ssh_push_target"; + const string PrivateKeyPath = "RepoKey/private_key.txt"; + const string PublicKeyPath = "RepoKey/public_key.txt"; + const string PRJobFile = "prtestjob.json"; + const string CommitMessage = "Automatic changelog compile, [ci skip]"; + + object RepoLock = new object(); + bool RepoBusy = false; + bool Cloning = false; + + Repository Repo; + int currentProgress = -1; + + //public api + public bool OperationInProgress() + { + lock(RepoLock) + { + return RepoBusy; + } + } + + //public api + public int CheckoutProgress() + { + return currentProgress; + } + + //Sets up the repo object + string LoadRepo() + { + if (Repo != null) + return null; + if (!Repository.IsValid(RepoPath)) + return "Repository does not exist"; + try + { + Repo = new Repository(RepoPath); + } + catch (Exception e) + { + return e.ToString(); + } + return null; + } + + //Cleans up the repo object + void DisposeRepo() + { + if (Repo != null) + { + Repo.Dispose(); + Repo = null; + } + } + + //public api + public bool Exists() + { + lock (RepoLock) + { + return !Cloning && Repository.IsValid(RepoPath); + } + } + + //Updates the currentProgress var + //no locks required because who gives a shit, it's a fucking 32-bit integer + bool HandleTransferProgress(TransferProgress progress) + { + currentProgress = (int)(((float)progress.ReceivedObjects / progress.TotalObjects) * 100) / 2; + currentProgress += (int)(((float)progress.IndexedObjects / progress.TotalObjects) * 100) / 2; + return true; + } + + //see above + void HandleCheckoutProgress(string path, int completedSteps, int totalSteps) + { + currentProgress = (int)(((float)completedSteps / totalSteps) * 100); + } + + //For the thread parameter + private class TwoStrings + { + public string a, b; + } + + //This is the thread that resets za warldo + //clones, checksout, sets up static dir + void Clone(object twostrings) + { + //busy flag set by caller + try + { + var ts = (TwoStrings)twostrings; + var RepoURL = ts.a; + var BranchName = ts.b; + SendMessage(String.Format("REPO: {2} started: Cloning {0} branch of {1} ...", BranchName, RepoURL, Repository.IsValid(RepoPath) ? "Full reset" : "Setup")); + try + { + DisposeRepo(); + Program.DeleteDirectory(RepoPath); + DeletePRList(); + lock (configLock) + { + if (Directory.Exists(StaticDirs)) + { + int count = 1; + + string path = Path.GetDirectoryName(StaticBackupDir); + string newFullPath = StaticBackupDir; + + while (File.Exists(newFullPath) || Directory.Exists(newFullPath)) + { + string tempDirName = string.Format("{0}({1})", StaticBackupDir, count++); + newFullPath = Path.Combine(path, tempDirName); + } + + Program.CopyDirectory(StaticDirs, newFullPath); + } + Program.DeleteDirectory(StaticDirs); + } + + var Opts = new CloneOptions() + { + BranchName = BranchName, + RecurseSubmodules = true, + OnTransferProgress = HandleTransferProgress, + OnCheckoutProgress = HandleCheckoutProgress, + CredentialsProvider = GenerateGitCredentials, + }; + + Repository.Clone(RepoURL, RepoPath, Opts); + currentProgress = -1; + LoadRepo(); + + //create an ssh remote for pushing + Repo.Network.Remotes.Add(SSHPushRemote, RepoURL.Replace("git://", "ssh://").Replace("https://", "ssh://")); + + lock (configLock) + { + Program.CopyDirectory(RepoConfig, StaticConfigDir); + } + Program.CopyDirectory(RepoData, StaticDataDir, null, true); + File.Copy(RepoPath + LibMySQLFile, StaticDirs + LibMySQLFile, true); + SendMessage("REPO: Clone complete!"); + } + finally + { + currentProgress = -1; + } + } + catch(Exception e) + + { + SendMessage("REPO: Setup failed!"); + TGServerService.WriteLog("Clone error: " + e.ToString(), EventLogEntryType.Error); + } + finally + { + lock (RepoLock) + { + RepoBusy = false; + Cloning = false; + } + } + } + + //kicks off the cloning thread + //public api + public string Setup(string RepoURL, string BranchName) + { + lock (RepoLock) + { + if (RepoBusy) + return "Repo is busy!"; + lock (CompilerLock) + { + if (!CompilerIdleNoLock()) + return "Compiler is running!"; + } + if (DaemonStatus() != TGDreamDaemonStatus.Offline) + return "DreamDaemon is running!"; + if (RepoURL.Contains("ssh://") && !SSHAuth()) + return String.Format("SSH url specified but either {0} or {1} does not exist in the server directory!", PrivateKeyPath, PublicKeyPath); + RepoBusy = true; + Cloning = true; + new Thread(new ParameterizedThreadStart(Clone)) + { + IsBackground = true //make sure we don't hold up shutdown + }.Start(new TwoStrings { a = RepoURL, b = BranchName }); + return null; + } + } + + //Gets what HEAD is pointing to + string GetShaOrBranch(out string error, bool branch) + { + lock (RepoLock) + { + var result = LoadRepo(); + if (result != null) + { + error = result; + return null; + } + + try + { + error = null; + return branch ? Repo.Head.FriendlyName : Repo.Head.Tip.Sha; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + } + + //moist shleppy noises + //public api + public string GetHead(out string error) + { + return GetShaOrBranch(out error, false); + } + + //public api + public string GetBranch(out string error) + { + return GetShaOrBranch(out error, true); + } + + //public api + public string GetRemote(out string error) + { + try + { + var res = LoadRepo(); + if (res != null) + { + error = res; + return null; + } + error = null; + return Repo.Network.Remotes["origin"].Url; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + //calls git reset --hard on HEAD + //requires RepoLock + string ResetNoLock(Branch targetBranch) + { + try + { + if (targetBranch != null) + Repo.Reset(ResetMode.Hard, targetBranch.Tip); + else + Repo.Reset(ResetMode.Hard); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + //public api + public string Checkout(string sha) + { + lock (RepoLock) + { + var result = LoadRepo(); + if (result != null) + return result; + SendMessage("REPO: Checking out object: " + sha); + try + { + var Opts = new CheckoutOptions() + { + CheckoutModifiers = CheckoutModifiers.Force, + OnCheckoutProgress = HandleCheckoutProgress, + }; + Commands.Checkout(Repo, sha, Opts); + var res = ResetNoLock(null); + SendMessage("REPO: Checkout complete!"); + return res; + } + catch (Exception E) + { + SendMessage("REPO: Checkout failed!"); + return E.ToString(); + } + } + } + + //Merges a thing into HEAD, not even necessarily a branch + string MergeBranch(string branchname) + { + var mo = new MergeOptions() + { + OnCheckoutProgress = HandleCheckoutProgress + }; + var Result = Repo.Merge(branchname, MakeSig()); + currentProgress = -1; + switch (Result.Status) + { + case MergeStatus.Conflicts: + ResetNoLock(null); + SendMessage("REPO: Merge conflicted, aborted."); + return "Merge conflict occurred."; + case MergeStatus.UpToDate: + SendMessage("REPO: Merge already up to date!"); + return RepoErrorUpToDate; + } + return null; + } + + //public api + public string Update(bool reset) + { + lock (RepoLock) + { + var result = LoadRepo(); + if (result != null) + return result; + SendMessage(String.Format("REPO: Updating origin branch...({0})", reset ? "Hard Reset" : "Merge")); + try + { + if (Repo.Head == null || !Repo.Head.IsTracking) + return "Cannot update while not on a tracked branch"; + string logMessage = ""; + + var R = Repo.Network.Remotes["origin"]; + IEnumerable refSpecs = R.FetchRefSpecs.Select(X => X.Specification); + var fos = new FetchOptions() + { + CredentialsProvider = GenerateGitCredentials, + }; + fos.OnTransferProgress += HandleTransferProgress; + Commands.Fetch(Repo, R.Name, refSpecs, fos, logMessage); + + var originBranch = Repo.Head.TrackedBranch; + if (reset) + { + var error = ResetNoLock(Repo.Head.TrackedBranch); + if (error == null) + DeletePRList(); + else + SendMessage("REPO: Update failed!"); + return error; + } + return MergeBranch(originBranch.FriendlyName); + } + catch (Exception E) + { + SendMessage("REPO: Update failed!"); + return E.ToString(); + } + } + } + + string CreateBackup() + { + try + { + lock (RepoLock) + { + var res = LoadRepo(); + if (res != null) + return res; + + //Make sure we don't already have a backup at this commit + var HEAD = Repo.Head.Tip.Sha; + foreach (var T in Repo.Tags) + if (T.Target.Sha == HEAD) + return null; + + var tagName = "TGS-Compile-Backup-" + DateTime.Now.ToString("yyyy-MM-dd--HH.mm.ss"); + var tag = Repo.ApplyTag(tagName); + + if (tag != null) + { + TGServerService.WriteLog("Repo backup created at tag: " + tagName + " commit: " + HEAD); + return null; + } + return "Tag creation failed!"; + } + } + catch (Exception e) + { + return e.ToString(); + } + } + + public IDictionary ListBackups(out string error) + { + try + { + lock (RepoLock) + { + error = LoadRepo(); + if (error != null) + return null; + + var res = new Dictionary(); + foreach (var T in Repo.Tags) + if (T.FriendlyName.Contains("TGS")) + res.Add(T.FriendlyName, T.Target.Sha); + return res; + } + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + + //public api + public string Reset(bool trackedBranch) + { + lock (RepoLock) + { + var res = LoadRepo() ?? ResetNoLock(trackedBranch ? (Repo.Head.TrackedBranch ?? Repo.Head) : Repo.Head); + if (trackedBranch && res == null) + DeletePRList(); + return res; + } + } + + //Makes the LibGit2Sharp sig we'll use for committing based on the configured stuff + Signature MakeSig() + { + var Config = Properties.Settings.Default; + return new Signature(new Identity(Config.CommitterName, Config.CommitterEmail), DateTimeOffset.Now); + } + + //I wonder... + void DeletePRList() + { + if (File.Exists(PRJobFile)) + File.Delete(PRJobFile); + } + + //json_decode(file2text()) + IDictionary> GetCurrentPRList() + { + if (!File.Exists(PRJobFile)) + return new Dictionary>(); + var rawdata = File.ReadAllText(PRJobFile); + var Deserializer = new JavaScriptSerializer(); + return Deserializer.Deserialize>>(rawdata); + } + + //text2file(json_encode()) + void SetCurrentPRList(IDictionary> list) + { + var Serializer = new JavaScriptSerializer(); + var rawdata = Serializer.Serialize(list); + File.WriteAllText(PRJobFile, rawdata); + } + + //public api + public string MergePullRequest(int PRNumber) + { + lock (RepoLock) + { + var result = LoadRepo(); + if (result != null) + return result; + SendMessage(String.Format("REPO: Merging PR #{0}...", PRNumber)); + try + { + //only supported with github + var remoteUrl = Repo.Network.Remotes["origin"].Url; + if (!remoteUrl.Contains("github.com")) + return "Only supported with Github based repositories."; + + + var Refspec = new List(); + var PRBranchName = String.Format("pr-{0}", PRNumber); + var LocalBranchName = String.Format("pull/{0}/headrefs/heads/{1}", PRNumber, PRBranchName); + Refspec.Add(String.Format("pull/{0}/head:{1}", PRNumber, PRBranchName)); + var logMessage = ""; + var fo = new FetchOptions() { OnTransferProgress = HandleTransferProgress, Prune = true }; + + var branch = Repo.Branches[LocalBranchName]; + if(branch != null) + //Need to delete the branch first in case of rebase + Repo.Branches.Remove(branch); + + + Commands.Fetch(Repo, "origin", Refspec, fo, logMessage); //shitty api has no failure state for this + + currentProgress = -1; + + var Config = Properties.Settings.Default; + + + branch = Repo.Branches[LocalBranchName]; + if (branch == null) + { + SendMessage("REPO: PR could not be fetched. Does it exist?"); + return String.Format("PR #{0} could not be fetched. Does it exist?", PRNumber); + } + + //so we'll know if this fails + var Result = MergeBranch(LocalBranchName); + + if (Result == null) + { + try + { + var CurrentPRs = GetCurrentPRList(); + var PRNumberString = PRNumber.ToString(); + CurrentPRs.Remove(PRNumberString); + var newPR = new Dictionary(); + + //do some excellent remote fuckery here to get the api page + var prAPI = remoteUrl; + prAPI = prAPI.Replace("/.git", ""); + prAPI = prAPI.Replace(".git", ""); + prAPI = prAPI.Replace("github.com", "api.github.com/repos"); + prAPI += "/pulls/" + PRNumberString + ".json"; + string json; + using (var wc = new WebClient()) + { + wc.Headers.Add("user-agent", "TGStationServerService"); + json = wc.DownloadString(prAPI); + } + + var Deserializer = new JavaScriptSerializer(); + var dick = Deserializer.DeserializeObject(json) as IDictionary; + var user = dick["user"] as IDictionary; + + newPR.Add("commit", branch.Tip.Sha); + newPR.Add("author", (string)user["login"]); + newPR.Add("title", (string)dick["title"]); + CurrentPRs.Add(PRNumberString, newPR); + SetCurrentPRList(CurrentPRs); + } + catch(Exception e) + { + return "PR Merged, JSON update failed: " + e.ToString(); + } + } + return Result; + } + catch (Exception E) + { + SendMessage("REPO: PR merge failed!"); + return E.ToString(); + } + } + } + + //public api + public IList MergedPullRequests(out string error) + { + lock (RepoLock) + { + var result = LoadRepo(); + if (result != null) + { + error = result; + return null; + } + try + { + var PRRawData = GetCurrentPRList(); + IList output = new List(); + foreach (var I in GetCurrentPRList()) + output.Add(new PullRequestInfo(Convert.ToInt32(I.Key), I.Value["author"], I.Value["title"], I.Value["commit"])); + error = null; + return output; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + } + + //public api + public string GetCommitterName() + { + lock (RepoLock) + { + return Properties.Settings.Default.CommitterName; + } + } + + //public api + public void SetCommitterName(string newName) + { + lock (RepoLock) + { + Properties.Settings.Default.CommitterName = newName; + } + } + + //public api + public string GetCommitterEmail() + { + lock (RepoLock) + { + return Properties.Settings.Default.CommitterEmail; + } + } + + //public api + public void SetCommitterEmail(string newEmail) + { + lock (RepoLock) + { + Properties.Settings.Default.CommitterEmail = newEmail; + } + } + + //public api + string Commit() + { + lock (RepoLock) + { + var result = LoadRepo(); + if (result != null) + return result; + try + { + // Stage the file + Commands.Stage(Repo, "html/changelog.html"); + Commands.Stage(Repo, "html/changelogs"); + + // Create the committer's signature and commit + var authorandcommitter = MakeSig(); + + // Commit to the repository + Repo.Commit(CommitMessage, authorandcommitter, authorandcommitter); + DeletePRList(); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + } + + //public api + string Push() + { + lock (RepoLock) + { + var result = LoadRepo(); + if (result != null) + return result; + + try + { + if (!SSHAuth()) + return String.Format("Either {0} or {1} is missing from the server directory. Unable to push!", PrivateKeyPath, PublicKeyPath); + + var options = new PushOptions() + { + CredentialsProvider = GenerateGitCredentials, + }; + Repo.Network.Push(Repo.Network.Remotes[SSHPushRemote], Repo.Head.CanonicalName, options); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + } + + bool SSHAuth() + { + return File.Exists(PrivateKeyPath) && File.Exists(PublicKeyPath); + } + + Credentials GenerateGitCredentials(string url, string usernameFromUrl, SupportedCredentialTypes types) + { + var user = usernameFromUrl ?? "git"; + if (types == SupportedCredentialTypes.UsernameQuery) + return new UsernameQueryCredentials() + { + Username = user, + }; + return new SshUserKeyCredentials() + { + Username = user, + PrivateKey = PrivateKeyPath, + PublicKey = PublicKeyPath, + Passphrase = "", + }; + } + + //public api + public string GenerateChangelog(out string error) + { + return GenerateChangelogImpl(out error); + } + + //impl proc just for single level recursion + public string GenerateChangelogImpl(out string error, bool recurse = false) + { + const string ChangelogPy = RepoPath + "/tools/ss13_genchangelog.py"; + const string ChangelogHtml = RepoPath + "/html/changelog.html"; + const string ChangelogDir = RepoPath + "/html/changelogs"; + if (!Exists()) + { + error = "Repo does not exist!"; + return null; + } + + lock (RepoLock) + { + if (RepoBusy) + { + error = "Repo is busy!"; + return null; + } + if (!File.Exists(ChangelogPy)) + { + error = "Missing changelog generation script!"; + return null; + } + if (!File.Exists(ChangelogHtml)) + { + error = "Missing changelog html!"; + return null; + } + if (!Directory.Exists(ChangelogDir)) + { + error = "Missing auto changelog directory!"; + return null; + } + + var Config = Properties.Settings.Default; + + var PythonFile = Config.PythonPath + "/python.exe"; + if (!File.Exists(PythonFile)) + { + error = "Cannot locate python 2.7!"; + return null; + } + try + { + string result; + int exitCode; + using (var python = new Process()) + { + python.StartInfo.FileName = PythonFile; + python.StartInfo.Arguments = String.Format("{0} {1} {2}", ChangelogPy, ChangelogHtml, ChangelogDir); + python.StartInfo.UseShellExecute = false; + python.StartInfo.RedirectStandardOutput = true; + python.Start(); + using (StreamReader reader = python.StandardOutput) + { + result = reader.ReadToEnd(); + + } + python.WaitForExit(); + exitCode = python.ExitCode; + } + if (exitCode != 0) + { + if (recurse) + { + error = "Script failed!"; + return result; + } + //update pip deps and try again + + string PipFile = Config.PythonPath + "/scripts/pip.exe"; + bool runningBSoup = false; + while (true) + using (var pip = new Process()) + { + pip.StartInfo.FileName = PipFile; + pip.StartInfo.Arguments = !runningBSoup ? "install PyYaml" : "install beautifulsoup4"; + pip.StartInfo.UseShellExecute = false; + pip.StartInfo.RedirectStandardOutput = true; + pip.Start(); + using (StreamReader reader = pip.StandardOutput) + { + result += "\r\n---BEGIN-PIP-OUTPUT---\r\n" + reader.ReadToEnd(); + } + pip.WaitForExit(); + if (pip.ExitCode != 0) + { + error = "Script and pip failed!"; + return result; + } + + if (runningBSoup) + break; + else + runningBSoup = true; + } + //and recurse + return GenerateChangelogImpl(out error, true); + } + error = null; + return result; + } + catch (Exception e) + { + error = e.ToString(); + return null; + } + } + } + + //public api + public bool SetPythonPath(string path) + { + if (!Directory.Exists(path)) + return false; + Properties.Settings.Default.PythonPath = Path.GetFullPath(path); + return true; + } + + public string PythonPath() + { + return Properties.Settings.Default.PythonPath; + } + } +} diff --git a/TGServerService/ServerService.Designer.cs b/TGServerService/ServerService.Designer.cs new file mode 100644 index 0000000000..029f1291c9 --- /dev/null +++ b/TGServerService/ServerService.Designer.cs @@ -0,0 +1,40 @@ +namespace TGServerService +{ + partial class TGServerService + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + // + // Service1 + // + this.ServiceName = "TG Station Server"; + + } + + #endregion + } +} diff --git a/TGServerService/ServerService.cs b/TGServerService/ServerService.cs new file mode 100644 index 0000000000..66f0133c50 --- /dev/null +++ b/TGServerService/ServerService.cs @@ -0,0 +1,82 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.ServiceModel; +using System.ServiceProcess; +using TGServiceInterface; + +namespace TGServerService +{ + public partial class TGServerService : ServiceBase + { + static TGServerService ActiveService; //So everyone else can write to our eventlog + + public static void WriteLog(string message, EventLogEntryType type = EventLogEntryType.Information) + { + ActiveService.EventLog.WriteEntry(message, type); + } + + ServiceHost host; //the WCF host + + //you should seriously not add anything here + //Use OnStart instead + public TGServerService() + { + InitializeComponent(); + Run(this); + } + + //when babby is formed + protected override void OnStart(string[] args) + { + ActiveService = this; + try + { + var Config = Properties.Settings.Default; + if (!Directory.Exists(Config.ServerDirectory)) + { + EventLog.WriteEntry("Creating server directory: " + Config.ServerDirectory); + Directory.CreateDirectory(Config.ServerDirectory); + } + Environment.CurrentDirectory = Config.ServerDirectory; + + host = new ServiceHost(typeof(TGStationServer), new Uri[] { new Uri("net.pipe://localhost") }) + { + CloseTimeout = new TimeSpan(0, 0, 5) + }; //construction runs here + + foreach (var I in Server.ValidInterfaces) + AddEndpoint(I); + + host.Open(); //...or maybe here, doesn't really matter + } + catch + { + ActiveService = null; + throw; + } + } + + //shorthand for adding the WCF endpoint + void AddEndpoint(Type typetype) + { + host.AddServiceEndpoint(typetype, new NetNamedPipeBinding(), Server.MasterPipeName + "/" + typetype.Name); + } + + //when we is kill + protected override void OnStop() + { + try + { + host.Close(); //where TGStationServer.Dispose() is called + host = null; + } + catch { } + finally + { + Properties.Settings.Default.Save(); + ActiveService = null; + } + } + } +} diff --git a/TGServerService/ServerService.resx b/TGServerService/ServerService.resx new file mode 100644 index 0000000000..e5858cc294 --- /dev/null +++ b/TGServerService/ServerService.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + \ No newline at end of file diff --git a/TGServerService/TGServerService.csproj b/TGServerService/TGServerService.csproj new file mode 100644 index 0000000000..d1737b27a0 --- /dev/null +++ b/TGServerService/TGServerService.csproj @@ -0,0 +1,153 @@ + + + + + + Debug + AnyCPU + {F32EDA25-0855-411C-AF5E-F0D042917E2D} + WinExe + TGServerService + TGServerService + v4.5.2 + 512 + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + tgs.ico + + + + ..\packages\Discord.Net.0.9.6\lib\net45\Discord.Net.dll + + + ..\packages\LibGit2Sharp-SSH.1.0.22\lib\net40\LibGit2Sharp.dll + + + ..\packages\SmartIrc4net.1.1\lib\Meebey.SmartIrc4net.dll + + + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll + + + ..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll + + + + + + + + + + + + + + + + + ..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll + + + + + + + + + + + + + + True + True + Settings.settings + + + + Component + + + ProjectInstaller.cs + + + Component + + + ServerService.cs + + + + + + + Designer + + + Designer + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + ProjectInstaller.cs + + + ServerService.cs + + + + + {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} + TGServiceInterface + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/TGServerService/packages.config b/TGServerService/packages.config new file mode 100644 index 0000000000..18ef56beab --- /dev/null +++ b/TGServerService/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/TGServerService/tgs.ico b/TGServerService/tgs.ico new file mode 100644 index 0000000000000000000000000000000000000000..6ed69fadfcdf8deb049f66dfffc46d5361332feb GIT binary patch literal 123198 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TGServiceInstaller/TGServiceInstaller.wixproj b/TGServiceInstaller/TGServiceInstaller.wixproj new file mode 100644 index 0000000000..d57b13bb7d --- /dev/null +++ b/TGServiceInstaller/TGServiceInstaller.wixproj @@ -0,0 +1,72 @@ + + + + Debug + x86 + 3.10 + 154435f6-0890-42d4-9aec-b743d4fbc1cb + 2.0 + TGServiceInstaller + Package + + + bin\$(Configuration)\ + obj\$(Configuration)\ + Debug + + + bin\$(Configuration)\ + obj\$(Configuration)\ + True + + + + + + + TGControlPanel + {394e7643-6b8c-416f-ab18-95ac12648cdc} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + TGCommandLine + {89191f69-b18e-4b59-b72e-e12f9b6811a0} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + TGServerService + {f32eda25-0855-411c-af5e-f0d042917e2d} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + TGServiceInterface + {ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + + + + + + + \ No newline at end of file diff --git a/TGServiceInterface/Byond.cs b/TGServiceInterface/Byond.cs new file mode 100644 index 0000000000..4da1105271 --- /dev/null +++ b/TGServiceInterface/Byond.cs @@ -0,0 +1,96 @@ +using System.ServiceModel; + +namespace TGServiceInterface +{ + /// + /// The status of a BYOND update job + /// + public enum TGByondStatus + { + /// + /// No byond update in progress + /// + Idle, + /// + /// Preparing to update + /// + Starting, + /// + /// Revision is downloading + /// + Downloading, + /// + /// Revision is deflating + /// + Staging, + /// + /// Revision is ready and waiting for DreamDaemon reboot + /// + Staged, + /// + /// Revision is being applied + /// + Updating, + } + + /// + /// Type of byond version + /// + public enum TGByondVersion + { + /// + /// The highest version from http://www.byond.com/download/build/LATEST/ + /// + Latest, + /// + /// The version in the staging directory + /// + Staged, + /// + /// The installed version + /// + Installed, + } + + /// + /// For managing the BYOND installation the server runs + /// + [ServiceContract] + public interface ITGByond + { + /// + /// Gets the current status of any BYOND updates + /// + /// A TGByondStatus + [OperationContract] + TGByondStatus CurrentStatus(); + + /// + /// updates the used byond version to that of version major.minor + /// The change won't take place until dream daemon reboots + /// the latest parameter overrides the other two and forces an update to the latest (beta?) version + /// runs asyncronously, use CurrentStatus to see progress + /// + /// Major BYOND version. E.g. 511 + /// Minor BYOND version. E.g. 1381 + /// True if the update started, false if another operation was in progress + [OperationContract] + bool UpdateToVersion(int major, int minor); + + /// + /// Check the last update error + /// Checking this will clear the value + /// + /// The last update error, if any + [OperationContract] + string GetError(); + + /// + /// Get the currently installed version as a string formatted as Major.Minor + /// + /// The type of version to retrieve + /// null if no version is detected, the version string otherwise + [OperationContract] + string GetVersion(TGByondVersion type); + } +} diff --git a/TGServiceInterface/Chat.cs b/TGServiceInterface/Chat.cs new file mode 100644 index 0000000000..a6e2d35020 --- /dev/null +++ b/TGServiceInterface/Chat.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.ServiceModel; + +namespace TGServiceInterface +{ + /// + /// The type of chat provider + /// + public enum TGChatProvider + { + /// + /// IRC chat provider + /// + IRC = 0, + /// + /// Discord chat provider + /// + Discord, + } + + /// + /// For setting up authentication no matter the chat provider + /// + [DataContract] + [KnownType(typeof(TGIRCSetupInfo))] + [KnownType(typeof(TGDiscordSetupInfo))] + public class TGChatSetupInfo + { + /// + /// Constructs a TGChatSetupInfo from optional past data + /// + /// Optional past data + /// The number of fields in this chat provider + protected TGChatSetupInfo(TGChatSetupInfo baseInfo, int numFields) + { + DataFields = baseInfo != null ? baseInfo.DataFields : new List(numFields); + if (baseInfo == null) + for (var I = 0; I < numFields; ++I) + DataFields.Add(null); + } + + /// + /// Constructs a TGChatSetupInfo from a data list + /// + /// The data + /// The chat provider + public TGChatSetupInfo(IList DeserializedData, TGChatProvider provider) + { + DataFields = DeserializedData; + Provider = provider; + } + + /// + /// The type of provider + /// + [DataMember] + public TGChatProvider Provider { get; protected set; } + + /// + /// Raw access to the underlying data + /// + [DataMember] + public IList DataFields { get; protected set; } + } + + /// + /// Chat provider for IRC + /// + [DataContract] + public class TGIRCSetupInfo : TGChatSetupInfo + { + const int URLIndex = 0; + const int PortIndex = 1; + const int NickIndex = 2; + const int AuthTargetIndex = 3; + const int AuthMessageIndex = 4; + const int FieldsLen = 5; + + /// + /// Construct IRC setup info from optional generic info. Defaults to TGS3 on rizons IRC server + /// + /// Optional generic info + public TGIRCSetupInfo(TGChatSetupInfo baseInfo = null) : base(baseInfo, FieldsLen) + { + Provider = TGChatProvider.IRC; + if (baseInfo == null) + { + Nickname = "TGS3"; + URL = "irc.rizon.net"; + Port = 6667; + } + } + /// + /// The port of the IRC server + /// + public ushort Port { + get { return Convert.ToUInt16(DataFields[PortIndex]); } + set { DataFields[PortIndex] = value.ToString(); } + } + /// + /// The URL of the IRC server + /// + public string URL + { + get { return DataFields[URLIndex]; } + set { DataFields[URLIndex] = value; } + } + /// + /// The nickname of the IRC bot + /// + public string Nickname + { + get { return DataFields[NickIndex]; } + set { DataFields[NickIndex] = value; } + } + /// + /// The target for sending authentication messages + /// + public string AuthTarget + { + get { return DataFields[AuthTargetIndex]; } + set { DataFields[AuthTargetIndex] = value; } + } + /// + /// The authentication message + /// + public string AuthMessage + { + get { return DataFields[AuthMessageIndex]; } + set { DataFields[AuthMessageIndex] = value; } + } + } + + [DataContract] + public class TGDiscordSetupInfo : TGChatSetupInfo + { + const int BotTokenIndex = 0; + const int FieldsLen = 1; + /// + /// Construct Discord setup info from optional generic info. Default is not a valid discord bot tokent + /// + /// Optional generic info + public TGDiscordSetupInfo(TGChatSetupInfo baseInfo = null) : base(baseInfo, FieldsLen) + { + Provider = TGChatProvider.Discord; + if (baseInfo == null) + BotToken = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + } + + /// + /// The Discord bot token to use. See https://discordapp.com/developers/applications/me for registering bot accounts + /// + public string BotToken + { + get { return DataFields[BotTokenIndex]; } + set { DataFields[BotTokenIndex] = value; } + } + } + + /// + /// Used internally + /// + [ServiceContract] + public interface ITGChatBase + { + /// + /// Set the chat provider info + /// + /// + [OperationContract] + string SetProviderInfo(TGChatSetupInfo info); + + /// + /// Set the channels to join + /// + /// The names of the channels to join and broadcast to + /// The name of the channel which accepts admin commands. Need not be in the channels parameter + [OperationContract] + void SetChannels(string[] channels = null, string adminchannel = null); + + /// + /// Checks connection status + /// + /// true if connected, false otherwise + [OperationContract] + bool Connected(); + + /// + /// Reconnect to the chat service + /// + /// null on success, error message on failure + [OperationContract] + string Reconnect(); + + /// + /// Send a message to the chat service + /// + /// The message to send + /// true if the message should only be sent to the admin channel, false otherwise + /// + [OperationContract] + string SendMessage(string msg, bool adminOnly = false); + } + + /// + /// Interface for handling chat bot + /// + [ServiceContract] + public interface ITGChat : ITGChatBase + { + /// + /// Returns the chat provider info + /// + /// + [OperationContract] + TGChatSetupInfo ProviderInfo(); + /// + /// Get channels we are set to connect to, includes the admin channel + /// + /// An array of channels + [OperationContract] + string[] Channels(); + + /// + /// Get the channel from which secure commands can be sent + /// + /// The name of the channel + [OperationContract] + string AdminChannel(); + + /// + /// Check if the configuration allows the IRC bot + /// + /// true if the bot is enabled, false otherwise + [OperationContract] + bool Enabled(); + + /// + /// Enable or disable the chat provider + /// + /// true to enable, false to disable + /// null on success, connection error message on failure + [OperationContract] + string SetEnabled(bool enable); + + /// + /// Print out the users who can use admin restricted commands over IRC from the admin channel + /// + /// A list of irc nicknames + [OperationContract] + string[] ListAdmins(); + + /// + /// Set the users who can use admin restricted commands over IRC from the admin channel + /// + /// The list of irc admin nicknames + [OperationContract] + void SetAdmins(string[] nicknames); + } +} diff --git a/TGServiceInterface/Compiler.cs b/TGServiceInterface/Compiler.cs new file mode 100644 index 0000000000..434282bfcf --- /dev/null +++ b/TGServiceInterface/Compiler.cs @@ -0,0 +1,92 @@ +using System.ServiceModel; + +namespace TGServiceInterface +{ + /// + /// The status of the compiler + /// + public enum TGCompilerStatus + { + /// + /// Game folder is broken or does not exist + /// + Uninitialized, + /// + /// Game folder is being created + /// + Initializing, + /// + /// Game folder is setup, does not imply the dmb is compiled + /// + Initialized, + /// + /// Game is being compiled + /// + Compiling, + } + + /// + /// For managing the Game A/B/Live folders, compiling, and hotswapping them + /// + [ServiceContract] + public interface ITGCompiler + { + /// + /// Sets up the symlinks for hotswapping game code + /// this will reset everything (except the static directories) + /// this will not reset the static directories + /// requires the repository to be set up and locks it for the duration of the operation + /// does not compile the game + /// runs asyncronously + /// + /// true if the operation began, false if it could not start + [OperationContract] + bool Initialize(); + + /// + /// Does all the necessary actions to take the revision currently in the repository + /// and compile it to be run on the next server reboot + /// requires byond to be set up and the compiler to be initialized + /// runs asyncronously + /// + /// true if the operation began, false if it could not start + [OperationContract] + bool Compile(); + + /// + /// Cancels the current compilation + /// + /// null on success, error message on failure + [OperationContract] + string Cancel(); + + /// + /// Returns the current compiler status + /// + /// The current compiler status + [OperationContract] + TGCompilerStatus GetStatus(); + + /// + /// Returns the error message of the last operation + /// Reading this will clear the stored value + /// + /// the error message of the last operation if it failed or null if it succeeded + [OperationContract] + string CompileError(); + + /// + /// Returns the relative path of the dme the compiler will look for without the .dme part + /// + /// The relative path of the dme the compiler will look for without the .dme part + [OperationContract] + string ProjectName(); + + /// + /// Sets the relative path of the dme the compiler will look for without the .dme part + /// + /// The relative path of the dme the compiler will look for without the .dme part + [OperationContract] + void SetProjectName(string projectName); + } +} diff --git a/TGServiceInterface/Config.cs b/TGServiceInterface/Config.cs new file mode 100644 index 0000000000..4d08bf8c2b --- /dev/null +++ b/TGServiceInterface/Config.cs @@ -0,0 +1,335 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.ServiceModel; + +namespace TGServiceInterface +{ + /// + /// The type of string -> string config being modified + /// + public enum TGConfigType + { + /// + /// dbconfig.txt + /// + Database, + /// + /// game_options.txt + /// + Game, + /// + /// config.txt + /// + General, + } + + /// + /// Map configuration settings + /// + [DataContract] + public class MapSetting + { + /// + /// The name of the map + /// + [DataMember] + public string Name { get; set; } + /// + /// If this is the default voted map + /// + [DataMember] + public bool Default { get; set; } + /// + /// The voteweight of the map + /// + [DataMember] + public float VoteWeight { get; set; } + /// + /// The minimum number of players to run this map + /// + [DataMember] + public int MinPlayers { get; set; } + /// + /// The maximum number of players to run this map + /// + [DataMember] + public int MaxPlayers { get; set; } + /// + /// If the map is enabled + /// + [DataMember] + public bool Enabled { get; set; } + } + + /// + /// Used for white/blacklisting certain map files + /// + [DataContract] + public class MapEnabled + { + /// + /// The file name of the map + /// + [DataMember] + public string Filename { get; set; } + /// + /// True if the map is enabled, false otherwise + /// + [DataMember] + public bool Enabled { get; set; } + } + + /// + /// Game configuration setting + /// + [DataContract] + public class ConfigSetting + { + /// + /// The setting name + /// + [DataMember] + public string Name { get; set; } + /// + /// Comments above the setting + /// + [DataMember] + public string Comment { get; set; } + /// + /// True if this setting exists in the current configuration + /// + [DataMember] + public bool ExistsInStatic { get; set; } + /// + /// True if this setting exists in the repo's config + /// + [DataMember] + public bool ExistsInRepo { get; set; } + /// + /// True if this value appears more than once in either config + /// + [DataMember] + public bool IsMultiKey { get; set; } + /// + /// Value of the setting + /// null means unset, empty string means flag + /// + [DataMember] + public string Value { get; set; } + /// + /// For when the first word of the config setting can be repeated many times + /// Usually null + /// + [DataMember] + public List Values { get; set; } + /// + /// Value of the setting in the repo + /// null means unset, empty string means flag + /// + [DataMember] + public string DefaultValue { get; set; } + /// + /// For when the first word of the config setting can be repeated many times + /// Usually null + /// + [DataMember] + public List DefaultValues { get; set; } + } + /// + /// Setting for job populations + /// + [DataContract] + public class JobSetting + { + /// + /// The name of the job + /// + [DataMember] + public string Name { get; set; } + /// + /// Number of total positions for this job, -1 for infinite + /// + [DataMember] + public int TotalPositions { get; set; } + /// + /// Number of positions for this job when the game starts, -1 for infinite + /// + [DataMember] + public int SpawnPositions { get; set; } + } + + /// + /// For modifying the in game config + /// Most if not all of these will not apply until the next server reboot + /// + [ServiceContract] + public interface ITGConfig + { + /// + /// Gets the config settings of some config + /// + /// The type of config to retrieve + /// null on success, error message on failure + /// The list of configs on success or null on failure + [OperationContract] + IList Retrieve(TGConfigType type, out string error); + + /// + /// Sets a config setting of some config + /// + /// The type of config to retrieve + /// The updated config setting, only name and value fields are read + /// null on success, error message on failure + [OperationContract] + string SetItem(TGConfigType type, ConfigSetting newSetting); + + /// + /// List the permissions as defined in the repo's admin_ranks.txt + /// + /// null on success, error message on failure + /// Dictionary of permission strings -> description on success, null on failure + [OperationContract] + IDictionary ListPermissions(out string error); + + /// + /// Get the configured admin ranks + /// + /// null on success, error message on failure + /// A dictionary of rank -> permissions -> +/- on success, null on failure + [OperationContract] + IDictionary> AdminRanks(out string error); + + /// + /// Deletes the given admin rank + /// + /// The rank to delete + /// null on success, error message on failure + [OperationContract] + string RemoveAdminRank(string rank); + + /// + /// Adds or update an admin rank + /// + /// The name of the added rank + /// The permissions of the added rank + /// null on success, error message on failure + [OperationContract] + string SetAdminRank(string rankName, IDictionary permissions); + + /// + /// List the admins + /// + /// null on success, error message on failure + /// a dictionary of ckey -> admin rank on success, null on failure + [OperationContract] + IDictionary Admins(out string error); + + /// + /// Add or modify a ckey's admin status + /// + /// The byond ckey to modify + /// The rank of the admin + /// null on success, error message on failure + [OperationContract] + string Addmin(string ckey, string rank); + + /// + /// Remove the admin status of a ckey + /// + /// The ckey to deadmin + /// + [OperationContract] + string Deadmin(string admin); + + /// + /// List the job population limits + /// + /// null on success, error message on failure + /// A list of JobSettings + [OperationContract] + IList Jobs(out string error); + + /// + /// Set the population limits for a job + /// + /// The population limits + /// null on success, error on failure + [OperationContract] + string SetJob(JobSetting job); + + /// + /// Lists game map settings + /// + /// null on success, error on failure + /// The list of MapSettings + [OperationContract] + IList MapSettings(out string error); + + /// + /// Sets a game map's settings + /// + /// The new setting for the map + /// null on success, error on failure + [OperationContract] + string SetMapSettings(MapSetting newSetting); + + /// + /// Gets the port DD uses to talk to the service + /// + /// null on success, error message on failure + /// The port DD uses to talk to the service or 0 on failure + [OperationContract] + ushort InteropPort(out string error); + + /// + /// Sets the port DD uses to talk to the service + /// + /// The port DD uses to talk to the service + /// null on success, error message on failure + [OperationContract] + string SetInteropPort(ushort port); + + /// + /// Return the directory of the server on the host machine + /// + /// The path to the directory on success, null on failure + [OperationContract] + string ServerDirectory(); + + /// + /// Moves the entire server installation, requires no operations to be running + /// + /// The new path to place the server + /// null on success, error message on failure + [OperationContract] + string MoveServer(string new_location); + + /// + /// Upload a titlescreen image + /// + /// The name of the file saved in config/title_screens/images + /// The bytes of the file, null will delete the file + /// null on success, error message on failure + [OperationContract] + string SetTitleImage(string filename, byte[] data); + + /// + /// For when you really just need to see the raw data of the config + /// + /// The path from the configDir. E.g. config.txt + /// if true, the file will be read + /// null on success, error message on failure + /// The full text of the file on success, null on failure + [OperationContract] + string ReadRaw(string configRelativePath, bool repo, out string error); + + /// + /// For when you really just need to set the raw data of the config + /// + /// The path from the configDir. E.g. config.txt + /// The full text of the config file + /// null on success, error message on failure + [OperationContract] + string WriteRaw(string configRelativePath, string data); + } +} diff --git a/TGServiceInterface/DreamDaemon.cs b/TGServiceInterface/DreamDaemon.cs new file mode 100644 index 0000000000..a88663c363 --- /dev/null +++ b/TGServiceInterface/DreamDaemon.cs @@ -0,0 +1,188 @@ +using System.ServiceModel; + +namespace TGServiceInterface +{ + /// + /// The status of the DD instance + /// + public enum TGDreamDaemonStatus + { + /// + /// Server is not running + /// + Offline, + /// + /// Server is being rebooted + /// + HardRebooting, + /// + /// Server is running + /// + Online, + } + + /// + /// DreamDaemon's security level + /// + public enum TGDreamDaemonSecurity + { + /// + /// Server is unrestricted in terms of file access and shell commands + /// + Trusted = 0, + /// + /// Server will not be able to run shell commands or access files outside it's working directory + /// + Safe, + /// + /// Server will not be able to run shell commands or access anything but temporary files + /// + Ultrasafe + } + + /// + /// DreamDaemon's hub visibility + /// + public enum TGDreamDaemonVisibility + { + /// + /// Server will be visible on the hub + /// + Public, + /// + /// Server will not be visible on the hub to anyone but the host's friends. Since the service does not have a BYOND account, this is effectively the same as Invisible + /// + Private, + /// + /// Server will not be visible on the hub + /// + Invisible = 2, //default config + } + + /// + /// Interface for managing the actual BYOND game server + /// + [ServiceContract] + public interface ITGDreamDaemon + { + /// + /// Gets the status of DreamDaemon + /// + /// The appropriate TGDreamDaemonStatus + [OperationContract] + TGDreamDaemonStatus DaemonStatus(); + + /// + /// Returns a human readable string of the current server status + /// + /// If true, the status will include the server's current visibility and security levels + /// A human readable string of the current server status + [OperationContract] + string StatusString(bool includeMetaInfo); + + /// + /// Check if a call to Start will fail + /// Of course, be aware of race conditions with other control panels + /// + /// returns the error that would occur, null otherwise + [OperationContract] + string CanStart(); + + /// + /// Starts the server if it isn't running + /// + /// null on success or error message on failure + [OperationContract] + string Start(); + + /// + /// Immediately kills the server + /// + /// null on success or error message on failure + [OperationContract] + string Stop(); + + /// + /// Immediately kills and restarts the server + /// + /// null on success or error message on failure + [OperationContract] + string Restart(); + + /// + /// Restart the server after the currently running round ends + /// Has no effect if the server isn't running + /// + [OperationContract] + void RequestRestart(); + + /// + /// Stop the server after the currently running round ends + /// Has no effect if the server isn't running + /// + [OperationContract] + void RequestStop(); + + /// + /// Get the configured (not running) security level + /// + /// The configured (not running) security level + [OperationContract] + TGDreamDaemonSecurity SecurityLevel(); + + /// + /// Sets the security level of the server. Requires reboot to apply + /// Implies a call to RequestRestart() + /// note that anything higher than Trusted will disable interop from DD + /// + /// The new security level + /// True if the change was immediately applied, false if a graceful restart was queued + [OperationContract] + bool SetSecurityLevel(TGDreamDaemonSecurity level); + + /// + /// Get the configured (not running) visibility level + /// + /// The configured (not running) visibility level + [OperationContract] + TGDreamDaemonVisibility VisibilityLevel(); + + /// + /// Sets the visiblity level of the server. Requires reboot to apply + /// Implies a call to RequestRestart() + /// + /// The new visibility level + /// True if the change was immediately applied, false if a graceful restart was queued + [OperationContract] + bool SetVisibility(TGDreamDaemonVisibility vis); + + /// + /// Get the configured port. Not necessarily the running port if it has since changed + /// + /// The configured port + [OperationContract] + ushort Port(); + + /// + /// Set the port to host DD on. Requires reboot to apply + /// Implies a call to RequestRestart() + /// + /// The new port + [OperationContract] + void SetPort(ushort new_port); + + /// + /// Check if the watchdog will start when the service starts + /// + /// true if autostart is enabled, false otherwise + [OperationContract] + bool Autostart(); + + /// + /// Set the autostart config + /// + /// true to start the watchdog with the service, false otherwise + [OperationContract] + void SetAutostart(bool on); + } +} diff --git a/TGServiceInterface/Properties/AssemblyInfo.cs b/TGServiceInterface/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..d9905dc4d5 --- /dev/null +++ b/TGServiceInterface/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TGStation Server Service Interface")] +[assembly: AssemblyDescription("Used by user programs to access the TGStation Server Service")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ac4e7e8b-f83a-481c-a8b0-8fa4e8ae59ab")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("3.0.78.0")] +[assembly: AssemblyFileVersion("3.0.78.0")] diff --git a/TGServiceInterface/Repository.cs b/TGServiceInterface/Repository.cs new file mode 100644 index 0000000000..da6440cf1b --- /dev/null +++ b/TGServiceInterface/Repository.cs @@ -0,0 +1,213 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.ServiceModel; + +namespace TGServiceInterface +{ + /// + /// Information about a pull request + /// + [DataContract] + public class PullRequestInfo + { + /// + /// Construct a PullRequestInfo + /// + /// The PR number + /// The PR's author + /// The PR's title + /// The commit the PR was merged locally at + public PullRequestInfo(int number, string author, string title, string sha) + { + Number = number; + Author = author; + Title = title; + Sha = sha; + } + + /// + /// The PR number + /// + [DataMember] + public int Number { get; private set; } + /// + /// The PR's author + /// + [DataMember] + public string Author { get; private set; } + /// + /// The PR's title + /// + [DataMember] + public string Title { get; private set; } + /// + /// The commit the PR was merged locally at + /// + [DataMember] + public string Sha { get; private set; } + } + /// + /// Interface for managing the code repository + /// + [ServiceContract] + public interface ITGRepository + { + /// + /// If the repo is currently undergoing an operation + /// + /// true if the repo is busy, false otherwise + [OperationContract] + bool OperationInProgress(); + + /// + /// Gets the progress of repository operations, not all operations are supported + /// + /// A value between 0 and 100 representing the progress of the current operation or -1 if the operation cannot be monitored + [OperationContract] + int CheckoutProgress(); + + /// + /// Check if the repository is valid, if not Setup must be called + /// + /// true if the repository is valid, false otherwise + [OperationContract] + bool Exists(); + + /// + /// Deletes whatever may be left over and clones the repo at remote and checks out branch master + /// Will move config and data dirs to a backup location if they exist + /// runs asyncronously + /// + /// The address of the repo to clone. If ssh protocol is used, repository_private_key.txt must exist in the server directory. + /// The branch of the repo to checkout + /// null on success, error message on failure + [OperationContract] + string Setup(string remote, string branch = "master"); + + /// + /// Gets the sha of the current HEAD + /// + /// null on success, error message on failure + /// The sha of the current HEAD on success, null on failure + [OperationContract] + string GetHead(out string error); + + /// + /// Gets the name of the current branch + /// + /// null on success, error message on failure + /// The name of the current branch on success, null on failure + [OperationContract] + string GetBranch(out string error); + + /// + /// Gets the url of the current origin + /// + /// null on success, error message on failure + /// The url of the current origin on success, null on failure + [OperationContract] + string GetRemote(out string error); + + /// + /// Hard checks out the passed object name + /// + /// The branch, commit, or tag to checkout + /// null on success, error message on failure + [OperationContract] + string Checkout(string objectName); + + /// + /// Fetches the origin and merges it into the current branch + /// + /// If true, the operation will perform a hard reset instead of a merge + /// null on success, error message on failure + [OperationContract] + string Update(bool reset); + + /// + /// Runs git reset --hard + /// + /// Changes command to git reset --hard origin/branch_name if true + /// null on success, error message on failure + [OperationContract] + string Reset(bool tracked); + + /// + /// Merges the target pull request into the current branch if the remote is a github repository + /// + /// The github pull request number in the remote repository + /// null on success, error message on failure + [OperationContract] + string MergePullRequest(int PRnumber); + + //Returns a list of PR# -> Sha of the currently merged pull requests + //returns null on failure and error will be set + + /// + /// Get the currently merged pull requests. Note that switching branches will delete this list and switching back won't restore it + /// + /// null on success, error message on failure + /// A list of PullRequestInfo + [OperationContract] + IList MergedPullRequests(out string error); + + /// + /// Gets the name of the configured git committer + /// + /// The name of the configured git committer + [OperationContract] + string GetCommitterName(); + + /// + /// Sets the name of the configured git committer + /// + /// The name to set + [OperationContract] + void SetCommitterName(string newName); + + /// + /// Gets the email of the configured git committer + /// + /// The email of the configured git committer + [OperationContract] + string GetCommitterEmail(); + + /// + /// Sets the email of the configured git committer + /// + /// The email to set + [OperationContract] + void SetCommitterEmail(string newEmail); + + /// + /// Updates the html changelog + /// + /// null on success, error on failure + /// The output of the python script + [OperationContract] + string GenerateChangelog(out string error); + + /// + /// Sets the path to the python 2.7 installation + /// + /// The new path + /// true if the path exists, false otherwise + [OperationContract] + bool SetPythonPath(string path); + + /// + /// Gets the path to the python 2.7 installation + /// + /// The path to the python 2.7 installation + [OperationContract] + string PythonPath(); + + /// + /// List the tagged commits of the repo at which compiles took place + /// + /// null on success, error message on failure + /// A dictionary of tag name -> commit on success, null on failure + [OperationContract] + IDictionary ListBackups(out string error); + } +} diff --git a/TGServiceInterface/Server.cs b/TGServiceInterface/Server.cs new file mode 100644 index 0000000000..b27571c185 --- /dev/null +++ b/TGServiceInterface/Server.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.ServiceModel; +namespace TGServiceInterface +{ + public class Server + { + /// + /// List of types that can be used with GetComponen + /// + public static readonly IList ValidInterfaces = new List { typeof(ITGByond), typeof(ITGChat), typeof(ITGCompiler), typeof(ITGConfig), typeof(ITGDreamDaemon), typeof(ITGRepository), typeof(ITGServerUpdater), typeof(ITGStatusCheck) }; + + /// + /// Base name of the communication pipe + /// they are formatted as MasterPipeName/ComponentName + /// + public static string MasterPipeName = "TGStationServerService"; + + /// + /// Returns the requested server component interface. This does not guarantee a successful connection + /// + /// The type of the component to retrieve + /// + public static T GetComponent() + { + var ToT = typeof(T); + if (!ValidInterfaces.Contains(ToT)) + throw new Exception("Invalid type!"); + + return new ChannelFactory(new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 10, 0) }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", MasterPipeName, typeof(T).Name))).CreateChannel(); + } + + /// + /// Used to test if the service is avaiable on the machine + /// Note that state can technically change at any time + /// and any call to the service may throw an exception because it failed + /// + /// null on successful connection, error message on failure + public static string VerifyConnection() + { + try + { + GetComponent().VerifyConnection(); + return null; + } + catch(Exception e) + { + return e.ToString(); + } + } + } + + /// + /// Interface for checking server status + /// + [ServiceContract] + public interface ITGStatusCheck + { + /// + /// Does nothing on the server end, but if the call completes, you can be sure you are connected. WCF won't throw until you try until you actually use the API + /// + [OperationContract] + void VerifyConnection(); + } + + /// + /// How to modify the repo during the UpdateServer operation + /// + public enum TGRepoUpdateMethod + { + /// + /// Do not update the repo + /// + None, + /// + /// Update the repo by merging the origin branch + /// + Merge, + /// + /// Update the repo by hard resetting to the remote branch + /// + Hard, + /// + /// Clean the repo by hard resetting to the origin branch + /// + Reset, + } + + /// + /// One stop shop for server updates + /// + [ServiceContract] + public interface ITGServerUpdater + { + /// + /// Updates the server fully with various options as a blocking operation + /// + /// How to handle the repository during the update + /// true if the changelog should be pushed to git + /// If not zero, will testmerge the designated pull request + /// null on success, error message on failure + [OperationContract] + string UpdateServer(TGRepoUpdateMethod updateType, bool push_changelog_if_enabled, ushort testmerge_pr = 0); + } + +} diff --git a/TGServiceInterface/TGServiceInterface.csproj b/TGServiceInterface/TGServiceInterface.csproj new file mode 100644 index 0000000000..a85f942bd6 --- /dev/null +++ b/TGServiceInterface/TGServiceInterface.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {AC4E7E8B-F83A-481C-A8B0-8FA4E8AE59AB} + Library + Properties + TGServiceInterface + TGServiceInterface + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + tgs.ico + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TGServiceInterface/tgs.ico b/TGServiceInterface/tgs.ico new file mode 100644 index 0000000000000000000000000000000000000000..6ed69fadfcdf8deb049f66dfffc46d5361332feb GIT binary patch literal 123198 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 Station Server,TG1,2,3,4,0,5TrueTG Station Server Service Log \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..8aad7f7d52 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,34 @@ +pull_requests: + do_not_increment_build_number: true +skip_branch_with_pr: true +image: Visual Studio 2017 +configuration: Release +shallow_clone: true +artifacts: +- path: TGS3.zip + name: TGS3 +- path: MD5-SHA1.txt + name: MD5SHA1 +install: + - choco install fciv +before_build: + - nuget restore TGStationServer3.sln +build: + project: TGStationServer3.sln + parallel: true + verbosity: minimal +after_build: + - ps: .\TGS3Release.ps1 + - ps: if($env:APPVEYOR_REPO_COMMIT_MESSAGE -match "\[TGSDeploy\]"){$env:TGSDeploy = "Do it."} + - ps: $env:TGSVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$env:APPVEYOR_BUILD_FOLDER/TGServerService/bin/Release/TGServerService.exe").FileVersion +deploy: + release: "tgstation-server-v$(TGSVersion)" + description: 'The /tg/station server suite' + provider: GitHub + auth_token: + secure: 9Yf+FtoE7nf36V3dyVRANkNZ7og4dp/0uojCS51vrvIeNOTIM8Nq24lylgVBOhSL + artifact: TGS3,MD5SHA1 + draft: false + prerelease: false + on: + TGSDeploy: "Do it." diff --git a/tgs.ico b/tgs.ico new file mode 100644 index 0000000000000000000000000000000000000000..6ed69fadfcdf8deb049f66dfffc46d5361332feb GIT binary patch literal 123198 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