commit 548550b312227e2a85066e357c364a76314f25b4 Author: Cyd Date: Mon Jun 29 14:43:28 2026 -0500 Initial commit: TeslaSuite monorepo (TeslaConsole + TeslaLauncher) Co-locate the two cockpit-pod projects into a single repository: - Console/ : TeslaConsole, the net48 WinForms operator console (decompiled reconstruction) plus its differential + catalog test suite. - Launcher/ : TeslaLauncher, the net6 pod-side Service + Agent rewrite. Adds a combined TeslaSuite.sln, root README documenting the shared wire contract (and its current duplication, the main follow-up), and a root .gitignore. Histories were not preserved per request; this is a fresh start from the current working state of both projects. Co-Authored-By: Claude Opus 4.8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ada507 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Build output +[Bb]in/ +[Oo]bj/ + +# Launcher build.bat staging + final output +/Launcher/_stage_svc/ +/Launcher/_stage_agt/ +/Launcher/TeslaLauncher/ + +# Runtime / installer state (generated, not source) +*.InstallState +*.log + +# IDE / OS +.vs/ +.vscode/ +.idea/ +*.user +*.suo +*.userosscache +*.sln.docstates +Thumbs.db +desktop.ini +.DS_Store diff --git a/Console/.gitattributes b/Console/.gitattributes new file mode 100644 index 0000000..ea665df --- /dev/null +++ b/Console/.gitattributes @@ -0,0 +1,17 @@ +# Normalize line endings: detect text automatically +* text=auto + +# Source / project files are CRLF (Windows / Visual Studio) +*.cs text eol=crlf +*.csproj text eol=crlf +*.sln text eol=crlf +*.xml text eol=crlf +*.resx text eol=crlf +*.md text eol=crlf + +# Binary assets — never normalize, never diff as text +*.dll binary +*.exe binary +*.ico binary +*.bmp binary +*.InstallState binary diff --git a/Console/.gitignore b/Console/.gitignore new file mode 100644 index 0000000..b60a21b --- /dev/null +++ b/Console/.gitignore @@ -0,0 +1,11 @@ +# Build output +[Bb]in/ +[Oo]bj/ + +# IDE / OS +.vs/ +.vscode/ +.idea/ +*.user +Thumbs.db +.DS_Store diff --git a/Console/Properties/AssemblyInfo.cs b/Console/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..90f290c --- /dev/null +++ b/Console/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("Virtual World Entertainment")] +[assembly: AssemblyProduct("Tesla Console")] +[assembly: AssemblyCopyright("Copyright © 2009")] +[assembly: AssemblyConfiguration("")] +[assembly: Guid("581ca4b6-a91c-4d24-b9b5-207f3b5da379")] +[assembly: AssemblyFileVersion("4.11.3.0")] +[assembly: AssemblyTrademark("")] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Tesla Console")] +[assembly: AssemblyDescription("All code and UI property of Virtual World Entertainment.\r\n\r\nDeveloped by Elsewhen Studios, LLC in association with VGCorps, LLC.\r\n\r\nElsewhen Studios and the Elsewhen Wormhole are trademarks of Elsewhen Studios, LLC\r\n\r\nIncludes the WeifenLuo DockingPane library. Copyright © 2007 Weifen Luo (email: weifenluo@yahoo.com). Licensed under the MIT License - details can be found in WeifenLuo.txt included in this installation.")] +[assembly: AssemblyVersion("4.11.3.37076")] diff --git a/Console/README.md b/Console/README.md new file mode 100644 index 0000000..b670fe3 --- /dev/null +++ b/Console/README.md @@ -0,0 +1,83 @@ +# TeslaConsole + +Reconstructed C# source for the original **TeslaConsole.exe** — the operator +console that drives the cockpit pods (the counterpart to `TeslaLauncherService` +/ `TeslaLauncherAgent`). + +## Provenance + +This source was recovered by decompiling the original managed assembly with +[ILSpy](https://github.com/icsharpcode/ILSpy) (`ilspycmd` 7.2). The original +`TeslaConsole.exe` is a **.NET Framework 2.0** WinForms application +(`assets/Tesla Console/TeslaConsole.exe`, dated 2012). The reconstructed project +here is retargeted to **.NET Framework 4.8** so it builds with current tooling; +it is otherwise a faithful decompilation, not a rewrite. + +> Decompiled code is functionally equivalent to the original but not +> character-for-character identical to the lost sources. Local variable names, +> some control flow, and compiler-generated members differ. Treat this as a +> recovered baseline, not pristine source. + +## Dependencies + +The console references five assemblies, vendored as binaries under `lib/` +(copied from `assets/Tesla Console/`): + +| Assembly | Origin | +|----------|--------| +| `WeifenLuo.WinFormsUI.Docking.dll` | Third-party docking UI (open source, see `assets/Tesla Console/WeifenLuo.txt`) | +| `TeslaConsoleLaunchLib.dll` | Wire types / launch protocol (proprietary) | +| `TeslaSecureConfiguration.dll` | First-boot secure config protocol (proprietary) | +| `Munga Net.dll` | Networking helpers (proprietary) | +| `BitmapLibrary.dll` | Plasma-display bitmap rendering (proprietary) | + +These are still referenced as compiled binaries. They are also .NET 2.0 managed +assemblies and could be decompiled to source later if full-source builds are +needed. + +## Layout + +This folder is **self-contained** — it can be lifted out into its own repository +and still build and run. + +``` +TeslaConsole/ + *.cs, TeslaConsole.*/ decompiled source (by namespace) + *.resx, app.ico embedded resources + icon + TeslaConsole.csproj net48 project + RedPlanet/ runtime content (RPConfig.xml, RPStrings.xml) — copied to output + images/ source art (pod art / maps / vehicles) — reference only + installer_banner.bmp installer artwork — reference only + lib/ referenced binaries + WeifenLuo license + original/ original TeslaConsole.exe + .InstallState — reference baseline +``` + +## Building + +Requirements: .NET SDK (6.0+) — the `Microsoft.NETFramework.ReferenceAssemblies` +NuGet package supplies the net48 reference assemblies, so a standalone Framework +targeting pack is **not** required. + +``` +dotnet build TeslaConsole.csproj -c Release +``` + +Output: `bin/Release/net48/TeslaConsole.exe` (with `RedPlanet/` and all +dependency DLLs copied alongside it). + +## Runtime content + +- `RedPlanet\RPConfig.xml`, `RedPlanet\RPStrings.xml` are loaded relative to the + exe and are copied to the build output automatically. +- `Plasma Images\*.bmp` (under `%ProgramData%`) is an **optional** override set; + when absent the console renders plasma-display text procedurally, so it is not + required to build or run. + +## Notes + +- The `.resx` resources embed BinaryFormatter-serialized bitmaps. The project + sets `GenerateResourceUsePreserializedResources` and references + `System.Resources.Extensions` so these build under the modern SDK. +- Namespaces: `TeslaConsole` (UI + pod management), `TeslaConsole.RedPlanet` + (the Red Planet game scenario/mission model), `TeslaConsole.Properties` + (settings + resources). diff --git a/Console/RedPlanet/Apps.xml b/Console/RedPlanet/Apps.xml new file mode 100644 index 0000000..8f5cd40 --- /dev/null +++ b/Console/RedPlanet/Apps.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + diff --git a/Console/RedPlanet/RPConfig.xml b/Console/RedPlanet/RPConfig.xml new file mode 100644 index 0000000..90963c0 --- /dev/null +++ b/Console/RedPlanet/RPConfig.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Console/RedPlanet/RPStrings.xml b/Console/RedPlanet/RPStrings.xml new file mode 100644 index 0000000..5fbf8e4 --- /dev/null +++ b/Console/RedPlanet/RPStrings.xml @@ -0,0 +1,49 @@ + + + {time} - First into the score zone is {player}! + + {player} gets off to a roaring start as the first booster of the mission is ignited. + + It is a sorry state of affairs when {player} is the first to trash their {vehicle}. + + High glory goes to {player} as the first kill of the mission is delivered unto {damager}! + + The crowd is on their feet as {player} brings in the shortest lap of {lap_time} of the mission! + + {time} - Sparks fly as {damager} lightly damages {player}'s vehicle. + {time} - {damager} implements a light impact against {player}. + {damager} provides a light spanking to {player}'s vehicle. + {player} laughs defiantly as {damager} can only deliver light damage to {player}'s vehicle. + + {time} - Sparks fly as {damager} moderately damages {player}'s vehicle. + {player} still has a chance as {damager} delivers a moderate blow to {player}'s vehicle. + {damager} provides a moderate spanking to {player}'s vehicle. + {time} - {damager} implements a moderate impact against {player}. + + {time} - Sparks fly as {damager} heavily damages {player}'s vehicle. + {player} lets out a sob of disgust as {damager} nearly dismembers {player}'s vehicle. + {damager} provides a heavy handed spanking to {player}'s vehicle. + {time} - {damager} implements a heavy impact against {player}. + + {time} - {damager} delivers righteous kill to {player}. + {damager} rules as sheer death panic is brought down upon {player}. + {time} - {damager} delivers peace, love, rest to {player}. + The burning wreckage of {player}'s vehicle twists in the Martian wind as {damager} smiles joyously. + + Tragedy strikes {player}'s {vehicle} as the clumsy pilot ejects to safety. + {time} - Smoke roils from the {vehicle} as {player} learns a hard piloting lesson. + {player} destroys the {vehicle} and bails out. + {time} - {player} takes a risk which ends with the {vehicle} in flames. + + {time} - {player}'s ears are still ringing from the score zone buzzer. + {time} - {player} maneuvers into the score zone. + {time} - {player} scores! + {time} - {player} adds another notch, entering the score zone. + + {time} - A righteous booster is deployed by {player}. + {player}'s {vehicle} shows awesome booster power. + {time} - {player} is not babysitting on a booster. + {time} - {player}'s {vehicle} is cruising on a full booster thrust. + + ...and at the end of the mission it's: {winners} followed by {looser}. + \ No newline at end of file diff --git a/Console/TeslaConsole.Properties.Resources.resx b/Console/TeslaConsole.Properties.Resources.resx new file mode 100644 index 0000000..84468e6 --- /dev/null +++ b/Console/TeslaConsole.Properties.Resources.resx @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +text/microsoft-resx1.3System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=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run + 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offline + AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAApwsAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAAA2ElEQVQ4T52TgQ2DMAwEPQsrZAVWyApZgRWyQlZghQzn8hYfudSEtpVcIJEv/28QVZXjd1wUfz+XNXiASNEkzeobYAgABPUnQCBp1BMkUCC6rquVWYvKZTUFsLnWqq01LeWwReAJCQHeApq2bTPAvu8D0Hu3jG4VAAIbOWcDUAUVYO8WQAUpJQNQBZtxOkc/tQAATvLeYSUEDOIpHc/LshjgGiayeFPAWfvwvA0CmAUUfWTgFVA61nBPABoRJtanU+AEIkXcG6qjj+naSAVc5+nhGJ/e/ev+C7FgN+V3mDOIAAAAAElFTkSuQmCCCw==AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAADA0AAAKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAACPUlEQVRYR8WWgXGDMAxFvQIrZAVWYIWswApZgRVYgRWyQlbIClnBzffx2x9ZAtKUkjsdxBj0/CVLTjnndKQd6hwLrwBS6p+jfW7TWGxvdV4A1DFB/h1AFcC9B0BQzo3mbYE3CqSn/LXZDyE0fxWqCqDruqwGoJIsL2A/OfJpqDYB0PkwDHkcsfqU+/4ZHsDN9wTdIrvOqQDatq0UgLPL5ZIJQMf26oUPY/f7PdxNm3IAAISInFMJBZ2mqThHSCOFFkPAFZ3P5wJgZWeu2HEqBYDr9Zpvt1sB8dQIARAKvAAnBNDVW7n1GVRArsA8iMUc4KrogDkBCHVS7ZQ5ITHHhkFV4HuECBUAwOl0ygpAqXFVUBsC/GcY1lQIdwEVAITGGh9naDCuzrF6hmtJBU3I1SRUAH5cATQsgPEAoALCwFC8BYDJWhvggADWOZVaUoG7IcwBr5gAQHOhrMCYly9eGFYBvF6gH/8uKgJgd4xuXQuxugtUATpzO6TpA9GuUQCvIr4kobZZxtn2Bi1Q+CCfWwDuEJZwOkdVDAsRHvAoFjUWjtuasDZ/Uy9QsqZpSg0HuW2x5QTk5MAahHu6ivr3EkB5R3fBB0d791g+y4WVl58r3zirgOseAJCf9tvTzhawUIHDAap+/4HMS0q4CqDyQQEm4q4h0NILx3SuANyO2mzQcGCodDScAWg4CdHYCXFFL1BLGmu9x+ohHVWI5um4/bj+fzwe2bPEVXH1cEjnjB3HcOU8PSew+byjCs+KXxQQrjf3gQ03AAAAAElFTkSuQmCCCw==AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAoAsAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAAA0UlEQVQ4T52SixGEIAwFU4st0IIt0IIt2AIt0IItUFzOF+9xUVE8nYnyGZY1iaiqrM/6Ubz+DjvgASKTBskWT4BNACCIlwCBUo0epGEgOo6jhQdhjLVSys7sESClpDlnnSb82gaiWRPgb8aheZ4NsCyLGWCfJpcGvCnGaABa4CCgtLg1CCEoAFcW1gLnPvhVAADcRIC3YO9UQF34JgnzYRgMQAsCkIsdoGbU1Z+JbFk0c+ANeAhrHHuLbhVYgWMjcd7pg61Z7sJ346kKvd4/7n8ACxk1G5lsi38AAAAASUVORK5CYIILAAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA5wwAAAKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAACGElEQVRYR+WW7XHCMBBE1QIt0IJboAVacAtugRZogRZogRZogRYU1uEly1myRQLhR5jRYMSN72nvSynnnN653upcB58ApNRfd/vcpf24Xq3OHYA7BuTPAVwBPZcAAMW2ZtcCHxRIV/mnK75IoXlWqCYAm80m+xLQmCx3YN858ttQNQHgfLfb5f1ep29T6kch6LpuokDf93kYhuwAqIR9DUr75/O5Wk1NOSAAIFCgFCo5c9DD4TA6ly2hjKrMhoBTbbfbLwCpoP0SQFRKAMfjMZ9OpxGkpEYVQNLiCAA5AKAkeVRKapUgXIUqAA6IsasQFeC3A5AvEQDlgJgFWK/XeQkAUL04KiUFllSYAMSsFgTUnNAV0DOgAoj58jBAbEIOwMtrAK6CV4MglIwkpFdEUxV4bxBEBEAF4OdUoBqqOVDKbgF4LsjGoTwMNRVIxkWAUn17MtJUYqgiVE2FxSpwBXA212b9v1rVUJKljniXAz5miXOcDd6g9EKvmqiU9wWcqytWG5H+4CrWempAW+yLl5vayFytVmMPF/lkgNzujJ93gfJoLu0/DaBlzrfaFK/lt0ahk4+f2ihtdTJnVwWQ/Kz/CRCT6BlyNyeh6lnyUwkvDYG3VDnGuQNQjj5sGL2aeix1PJZ6P4tJqG/NAl/Jk82fdXpJhgo1O9+PL/ffl8sll1biVJxeDnFOzNjTN3Z+T2ACPqIK0/EDz7yQiD8N/goAAAAASUVORK5CYIILAAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAACA0AAAKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAACxKAAAsSgF3enRNAAACOUlEQVRYR8WWDZHCMBCFYwELWKgFLGChFrCABSxgAQtYwAIWcn2hr/O63SXpXTluZqedkOt+efuXlHNO37SvOsfBFwAp9cNqn7t0KfZpdWYA6pgg/w6gCuDdAyAo90b7WuCNAmmQf2n2QwjNVqFaABwOh6wGoJIsM7A+n8/nnB4D7GB4bzmtq6YuwokHQOdwdLng9C/HMxtB14IsFOi6bqFA3/f5dDqVk74D8MKHtcfjESrUlAMAIEQNQEGv12txDlUZSqvQ2xzgiY7H4wRQYh+EwCoFgNvtlu/3ewHx1AgBEArmBAHgoAagSkEtD2KWd1ES8vTMCVXBUwAya6iYLxaASU6/oQJwst/v8xoAqxQUqKkQVgEVAASpeUJPAYACwObLagDbhBSAH48A8L8eACCQjExIrYimKtDeAAcegAdqSxIArIYwB7xmAgDNhQjAyxebjFUArxVrMrKpaBtmaVqlvL5QrQJVYHLmTMiZCuPvUdVQBa8jznJAxyybkJ0N2qDwYRg/bJXSvsA96IphI8IPvIpFg8Vbryo1KlQdx7pht9uVHo6PLwbIcFOy94EW4M0AygXF3AfW3gPcMpwWX5eL6c8bpR8HgPy0cJaPKvz29EVJNy7DcgvAXxxXQ2CTagtnzUmIeoYCrIQoBFtAJW29cEznCsBy5F6OXTwxcGhsTHjiIkLjJOQwwjygJY21vuP0OCFViPbpun7Yvj+fz+zZpABPD4d0Tom5hif36fjlPYDKtKjCq9oPxBehqpwI4P8AAAAASUVORK5CYIILAAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAqgsAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAACxKAAAsSgF3enRNAAAA20lEQVQ4T52TCxWDMAxFo6UWagELtVALWMBCLWChWqYl6wtNFkbHZ5wT/r3cfCBmpra1A2P3OGSBBxBljlQk7gCHAEAQfwIIShZi+GrXLUbAgQHxNE0SBuoA3Ku17kCPADkjte0DajME+BRkUTdY11UM8FxNfhrol1JKBiilyEJA1eLUIMbI3wBvIQU+zsGnAwBIMXsKy7KwWujsGMBu9CLhOoQwBMBiB7CKuv5rIdUCec/zzLAY1sAbmHoD4lxTwEIALrugHfDt9OcXc7ANy1n4aTx04c4P5N95A6wRLe+p6EQrAAAAAElFTkSuQmCCCw==AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAuAwAAAKJUE5HDQoaCgAAAA1JSERSAAAAIAAAAB8IBgAAAIZWz4wAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAAB6UlEQVRYR81WC7GDQAw8C1ioBSxgAQu1gAUsPAtYwAIWsICFvOboQi4kfO7BzOtMSnt3bTabbHKBiAJbCO/P25vK8BONP2PvyWd0zganEsiTjpfA/w0DnxxwHjb2NAtLCth5VVWJaUC8j7Wu626pkQ2AsiwXEBIAe5MvufcXMIcMbB2nSpmLNlAuiASAUwMq8lmukOxXrhFETr2YDACIjj56UL1CAshhIQOAq5YsFhIAKMAvnRYB0cmRWq4UqMuA6X3u2SSVIqVpAce+l54sABYDHmCse0q5tQbOgNBKyZDhWgN7arHAIB0SREYjSgEcRa33GYSsh8NWbLXgM2rZA8YgjHG86htF5k1IuX+VAT5vAlgvJFudy6qXfWCvX3jAdlKAHm93OskGAHEqroLQM2OpAS2PoihoGIaErrkP+a34KB06+vh/3gTbA5DTiOZBtp2YJgBNq/yh14pzoncZYCdMP0wD0PXgpUWvW2y7DPgA1guJNRl1epRkN/5cAB769EJyrBh1rzwH4PV6RfpRiLnXLa/Ak1kgKWPHcC4BQI44W9c1wZqmIVjbtgTjfg/r+55g4ziStCBzLT9z9IwULHjn5Lr+c/l9miayLCAqRM8O4RxUYY2fOMdPyd5VVpgdZsVtRGfyd8eZXyXUKMtC5oIiAAAAAElFTkSuQmCCCw==AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAdwsAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAAAqElEQVQ4T62SgQ3EIAwDsxMrMAs7/SwMl9YURymk0H89kktblJOJI6oq5zo3xeNrtQIPECma5NP0BhgCAIF+BAgsmXaQwIFozrnJg/COf7XWm7NXAFRglYKrXSA6CwHDFVhvO87p5NFBT2YqphO6WDoIq/tPRv8/gBF7k9iHJxc3B9ZRl/8KgDSmHngHKSWbg8jBNgVmPQ4SvzdzcA3LSn4apxR2sz+eH3jDeaN7/0MSAAAAAElFTkSuQmCCCw==AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAoAsAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAAA0UlEQVQ4T52TCREDIQxF8YQFLGABC1jAAhawgLiUnyE0DGnZdmey3I+fA0dEbnyjIfx+Nj6gAc4l8q6yPQGaAEBgfwIGEy4NO1yb8xpsKNgBgIgJ+AoIIWwqSinbmNdnwE0FLH1aSolyzmvce2dFaDkBZxYc4QYNEVitlQ8CKio+KgDAe79uhgoAWmsMkQB/BaygTXcQC1FxANbE2KyDiH6McSkRFRtgRdRIGdzQQKgwY6AV6EISgFZxzYIGWIV0qQO8g3f1WX2pAbMOnjwgvecFi4E5F7ZIHrEAAAAASUVORK5CYIILAAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA9AwAAAKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAIcPAACMDwAA/VIAAIFAAAB9eQAA6YsAADzlAAAZzHM8hXcAAAo5aUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAASMedlndUVNcWh8+9d3qhzTACUobeu8AA0nuTXkVhmBlgKAMOMzSxIaICEUVEmiJIUMSA0VAkVkSxEBRUsAckCCgxGEVULG9G1ouurLz38vL746xv7bP3ufvsvc9aFwCSpy+XlwZLAZDKE/CDPJzpEZFRdOwAgAEeYIApAExWRrpfsHsIEMnLzYWeIXICXwQB8HpYvAJw09AzgE4H/5+kWel8geiYABGbszkZLBEXiDglS5Auts+KmBqXLGYYJWa+KEERy4k5YZENPvsssqOY2ak8tojFOaezU9li7hXxtkwhR8SIr4gLM7mcLBHfErFGijCVK+I34thUDjMDABRJbBdwWIkiNhExiR8S5CLi5QDgSAlfcdxXLOBkC8SXcklLz+FzExIFdB2WLt3U2ppB9+RkpXAEAsMAJiuZyWfTXdJS05m8HAAW7/xZMuLa0kVFtjS1trQ0NDMy/apQ/3Xzb0rc20V6Gfi5ZxCt/4vtr/zSGgBgzIlqs/OLLa4KgM4tAMjd+2LTOACApKhvHde/ug9NPC+JAkG6jbFxVlaWEZfDMhIX9A/9T4e/oa++ZyQ+7o/y0F058UxhioAurhsrLSVNyKdnpDNZHLrhn4f4Hwf+dR4GQZx4Dp/DE0WEiaaMy0sQtZvH5gq4aTw6l/efmvgPw/6kxbkWidL4EVBjjIDUdSpAfu0HKAoRINH7xV3/o2+++DAgfnnhKpOLc//vN/1nwaXiJYOb8DnOJSiEzhLyMxf3xM8SoAEBSAIqkAfKQB3oAENgBqyALXAEbsAb+IMQEAlWAxZIBKmAD7JAHtgECkEx2An2gGpQBxpBM2gFx0EnOAXOg0vgGrgBboP7YBRMgGdgFrwGCxAEYSEyRIHkIRVIE9KHzCAGZA+5Qb5QEBQJxUIJEA8SQnnQZqgYKoOqoXqoGfoeOgmdh65Ag9BdaAyahn6H3sEITIKpsBKsBRvDDNgJ9oFD4FVwArwGzoUL4B1wJdwAH4U74PPwNfg2PAo/g+cQgBARGqKKGCIMxAXxR6KQeISPrEeKkAqkAWlFupE+5CYyiswgb1EYFAVFRxmibFGeqFAUC7UGtR5VgqpGHUZ1oHpRN1FjqFnURzQZrYjWR9ugvdAR6AR0FroQXYFuQrejL6JvoyfQrzEYDA2jjbHCeGIiMUmYtZgSzD5MG+YcZhAzjpnDYrHyWH2sHdYfy8QKsIXYKuxR7FnsEHYC+wZHxKngzHDuuCgcD5ePq8AdwZ3BDeEmcQt4Kbwm3gbvj2fjc/Cl+EZ8N/46fgK/QJAmaBPsCCGEJMImQiWhlXCR8IDwkkgkqhGtiYFELnEjsZJ4jHiZOEZ8S5Ih6ZFcSNEkIWkH6RDpHOku6SWZTNYiO5KjyALyDnIz+QL5EfmNBEXCSMJLgi2xQaJGokNiSOK5JF5SU9JJcrVkrmSF5AnJ65IzUngpLSkXKabUeqkaqZNSI1Jz0hRpU2l/6VTpEukj0lekp2SwMloybjJsmQKZgzIXZMYpCEWd4kJhUTZTGikXKRNUDFWb6kVNohZTv6MOUGdlZWSXyYbJZsvWyJ6WHaUhNC2aFy2FVko7ThumvVuitMRpCWfJ9iWtS4aWzMstlXOU48gVybXJ3ZZ7J0+Xd5NPlt8l3yn/UAGloKcQqJClsF/hosLMUupS26WspUVLjy+9pwgr6ikGKa5VPKjYrzinpKzkoZSuVKV0QWlGmabsqJykXK58RnlahaJir8JVKVc5q/KULkt3oqfQK+m99FlVRVVPVaFqveqA6oKatlqoWr5am9pDdYI6Qz1evVy9R31WQ0XDTyNPo0XjniZek6GZqLlXs09zXktbK1xrq1an1pS2nLaXdq52i/YDHbKOg84anQadW7oYXYZusu4+3Rt6sJ6FXqJejd51fVjfUp+rv09/0ABtYG3AM2gwGDEkGToZZhq2GI4Z0Yx8jfKNOo2eG2sYRxnvMu4z/mhiYZJi0mhy31TG1Ns037Tb9HczPTOWWY3ZLXOyubv5BvMu8xfL9Jdxlu1fdseCYuFnsdWix+KDpZUl37LVctpKwyrWqtZqhEFlBDBKGJet0dbO1husT1m/tbG0Edgct/nN1tA22faI7dRy7eWc5Y3Lx+3U7Jh29Xaj9nT7WPsD9qMOqg5MhwaHx47qjmzHJsdJJ12nJKejTs+dTZz5zu3O8y42Lutczrkirh6uRa4DbjJuoW7Vbo/c1dwT3FvcZz0sPNZ6nPNEe/p47vIc8VLyYnk1e816W3mv8+71IfkE+1T7PPbV8+X7dvvBft5+u/0erNBcwVvR6Q/8vfx3+z8M0A5YE/BjICYwILAm8EmQaVBeUF8wJTgm+Ejw6xDnkNKQ+6E6ocLQnjDJsOiw5rD5cNfwsvDRCOOIdRHXIhUiuZFdUdiosKimqLmVbiv3rJyItogujB5epb0qe9WV1QqrU1afjpGMYcaciEXHhsceiX3P9Gc2MOfivOJq42ZZLqy9rGdsR3Y5e5pjxynjTMbbxZfFTyXYJexOmE50SKxInOG6cKu5L5I8k+qS5pP9kw8lf0oJT2lLxaXGpp7kyfCSeb1pymnZaYPp+umF6aNrbNbsWTPL9+E3ZUAZqzK6BFTRz1S/UEe4RTiWaZ9Zk/kmKyzrRLZ0Ni+7P0cvZ3vOZK577rdrUWtZa3vyVPM25Y2tc1pXvx5aH7e+Z4P6hoINExs9Nh7eRNiUvOmnfJP8svxXm8M3dxcoFWwsGN/isaWlUKKQXziy1XZr3TbUNu62ge3m26u2fyxiF10tNimuKH5fwiq5+o3pN5XffNoRv2Og1LJ0/07MTt7O4V0Ouw6XSZfllo3v9tvdUU4vLyp/tSdmz5WKZRV1ewl7hXtHK30ru6o0qnZWva9OrL5d41zTVqtYu712fh9739B+x/2tdUp1xXXvDnAP3Kn3qO9o0GqoOIg5mHnwSWNYY9+3jG+bmxSaips+HOIdGj0cdLi32aq5+YjikdIWuEXYMn00+uiN71y/62o1bK1vo7UVHwPHhMeefh/7/fBxn+M9JxgnWn/Q/KG2ndJe1AF15HTMdiZ2jnZFdg2e9D7Z023b3f6j0Y+HTqmeqjkte7r0DOFMwZlPZ3PPzp1LPzdzPuH8eE9Mz/0LERdu9Qb2Dlz0uXj5kvulC31OfWcv210+dcXmysmrjKud1yyvdfRb9Lf/ZPFT+4DlQMd1q+tdN6xvdA8uHzwz5DB0/qbrzUu3vG5du73i9uBw6PCdkeiR0TvsO1N3U+6+uJd5b+H+xgfoB0UPpR5WPFJ81PCz7s9to5ajp8dcx/ofBz++P84af/ZLxi/vJwqekJ9UTKpMNk+ZTZ2adp++8XTl04ln6c8WZgp/lf619rnO8x9+c/ytfzZiduIF/8Wn30teyr889GrZq565gLlHr1NfL8wXvZF/c/gt423fu/B3kwtZ77HvKz/ofuj+6PPxwafUT5/+BQOY8/y6xOjTAAAACXBIWXMAAAsSAAALEgHS3X78AAACJUlEQVRYR+WX4XXCMAyEvQIrsEJWYAVWyApZgRVYgRVYgRVYgRVczuHCRZbtpED50b6nR0hE9PkkS26IMYZv2leDY+EZQAj9/W4fu3BM9ml1ZgAamCB/DqAK4NoDICh9S35L4I0C4S5/bvZFSM27UpUB7Ha7qAagVCoPGwGfNZKlSv0X7LDVAMcjVl9R6lWArusyBfr+XgtGBapEf1+pEfR6vRZ306IaAICFsKlSwMPhEKHU6XRKweGbAJ2UVFNAqff7/a8BzudzvFwuCcRTowgAafED0BPAS4WVHj7DMCQFqIKFUCWKAFw9c0wIvFzl1mumCj6aBgVg6ghRBdhut3ENgCpFgJYKGYBWNVQABKm5Qis7VMDvAKBKlVTQgmwWoQLw5SUArRdNA1RAGpiKVQBw1t4AiFINUKmaCtwNxRrwuhwAtBZKAF692DQ0AbxZoMWowaeKli7ZUqG5C1QBdjDey4fScyZYMC8NXkecFaGOWTYhzb+3ej73nmlfYHB0xWIjwgMexWwt1FY/rSwb2/Op2ZwF6rDZbFIPx8tnRYdvj/PAeBYwQSoQbwOoHrVeOQ9Me3McndNfaZQuOfO1fNz/CxAQ8tP+J0BWXAsOmC25FxchOh/k5074aAq09SIwgysAt6O2WY5eTD0a+j4N50EaJyE+MQvUghabXmP1kIwqlPz0vn25fr/dbtGzwFVx9QjI4MwZ7+GTfnpO4DlgjSpQB4r8AJ88sTSwHqmUAAAAAElFTkSuQmCCCw==question + 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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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/zc3tf8/P77/S0vG/1dXzv9kZNb/cHDd/3x85P+IiOr/lpbv/6Sk9f+ysvn/wcH9/8/P///a2v//5OT//+vr///w8P//8/P///X1///29v//9/f///b2///y8v//7Oz//+Pj///X1///ycn//7q6//+oqP//lZX//4WF//9+fv//hob//56e//+5uf//09P///Dw///////////////////p6f//x8f//6Wl//+Fhf//gID//319//+IiP//oaH//7i4///W1v////////Pz//+7u/7/lpb6/3d39f9YWPD/YGDq/3l55P90dN7/WVnX/0dHz/9UVMf/ZWW//3l5tv+dna3/wsKk/9fXmv/NzZH/q6uH/4mJff9oaHP/TExp/zs7X/8rK1X/JydL/zIyQv88PDn/RUUw/05OKP9UVCD/U1MY/1JSEf9QUAv/T08G/1BQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/UVEC/1NTB/9VVQ3/VlYU/1hYG/9ZWSL/WVkr/1lZM/9XVzz/VVVG/1JST/9PT1n/TExj/0hIbf9DQ3f/Pj6B/zg4i/8zM5X/MTGf/zU1qf9AQLL/TU27/1lZxP9mZsz/c3PU/4KC2/+Tk+L/paXp/7m57//OzvT/4OD5/+/v/f/6+v////////////////////////////////////////////////////////////////////////z8///09P//5ub//9PT//+/v///qKj//5CQ//+AgP//h4f//6Oj//+/v///3Nz///n5//////////////v7///c3P//ubn//5WV//+Bgf//f3///4iI//+hof//uLj//9LS///7+////Pz//8fH//+fn/7/f3/5/15e9f9fX+//enrp/3V14/9ZWdz/SUnV/1hYzf9pacT/f3+8/6Sks//Jyan/29ug/8nJlv+kpIz/gYGC/2BgeP9JSW7/ODhk/ygoWv8sLFD/ODhG/0JCPf9MTDT/VFQr/1VVI/9TUxv/UlIU/1FRDv9QUAj/Tk4CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9NTQT/Tk4J/1BQEP9QUBb/UFAe/1BQJv9PTy7/TU03/0tLQP9ISEr/RUVU/0FBXv89PWj/ODhy/zIyfP8tLYf/LS2R/zMzm/8/P6T/TEyu/1hYt/9lZcD/dXXJ/4iI0f+cnNn/s7Pg/8rK5//h4e3/8/Pz////+P////3//////////////////Pz///r6///5+f//+vr///39////////////////////////////////////////////////////////+vr//+rq///T0///ubn//5yc//+EhP//i4v//6ur///Jyf//6en///////////////////Dw///Ly///pqb//4aG//9/f///iYn//6Gh//+5uf//0dH///f3////////0tL//6en//+Ghv3/Y2P4/2Bg8/97e+7/dnbo/1lZ4f9MTNr/XV3S/25uyv+FhcH/q6u4/9HRr//d3aX/w8Ob/52dkv95eYf/WVl9/0VFc/8zM2n/KSle/zMzVP8+Pkr/SUlB/1NTOP9XVy//VlYm/1RUHv9TUxf/UVEQ/1FRCv9PTwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/QkIB/0NDBv9FRQv/RkYS/0ZGGf9FRSH/RUUp/0NDMv9BQTv/Pj5E/zs7Tv83N1j/MzNi/y0tbP8pKXf/KSmB/zExi/88PJb/SUmg/1VVqv9kZLP/dna8/4uLxf+ios7/ubnW/9LS3v/q6uX//Pzr////8f////f//Pz8//Ly///o6P//3t7//9fX///V1f//1dX//9nZ///e3v//4eH//+Pj///n5///7Oz///Pz///5+f////////////////////////////////////////r6///k5P//xsb//6Wl//+IiP//lZX//7e3///Y2P//+Pj//////////////v7//97e//+4uP//kZH//39///+Hh///oqL//7u7///T0///9fX////////b2///rq7//4yM//9pafz/YmL3/3x88v93d+z/WVnl/1BQ3v9jY9f/c3PP/4yMxv+zs73/2Ni0/93dqv+8vKH/lZWW/3FxjP9UVIL/QkJ4/y8vbf8tLWP/OjpZ/0VFT/9QUEX/WFg7/1hYMv9WVin/VVUh/1NTGf9SUhL/Tk4M/0lJBv9FRQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP85OQL/OjoI/zs7Df88PBT/PDwc/zs7I/85OSz/Nzc1/zU1Pv8yMkj/Li5S/ygoXP8lJWb/JiZx/y4ue/85OYb/RESQ/1FRmv9hYaT/dHSu/4qKuP+hocH/ubnK/9PT0//t7dv//v7i////6f/6+u//7Oz1/9vb+v/Ly///vr7//7i4//+2tv//trb//7i4//+7u///vr7//76+//+9vf//v7///8LC///Hx///zs7//9jY///j4///8fH///z8///////////////////////////////////w8P//z8///6mp//+Pj///pKT//8jI///r6///////////////////8PD//8jI//+fn///gYH//4SE//+goP//vr7//9bW///09P///////+Li//+1tf//kpL//21t//9kZPv/fX32/3Z28P9YWOr/VVXj/2ho2/95edP/lJTL/729wv/g4Ln/2tqv/7Ozpf+MjJv/aGiR/1BQh/89PXz/LS1y/zMzZ/9AQF3/TExS/1dXSf9aWj//WFg1/1dXLf9VVST/U1Mc/05OFf9HRw7/QUEI/z09AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/zAwBP8xMQn/MjIQ/zIyFv8xMR7/MDAm/y4uL/8sLDj/KChC/yQkS/8hIVX/IyNg/yoqav81NXX/QEB//0xMiv9cXJX/cHCf/4aGqf+dnbP/tbW8/8/Pxv/q6s7//f3X////3//y8ub/39/t/8rK8/+3t/j/qqr9/6Oj//+goP//n5///52d//+dnf//nZ3//5+f//+cnP//mZn//5iY//+Zmf//nJz//6Gh//+oqP//sbH//729///Ly///3Nz///Dw///+/v/////////////////////////////29v//0tL//6mp//+bm///uLj//97e///9/f/////////////8/P//19f//62t//+Hh///goL//56e//+9vf//29v///b2////////6Oj//7u7//+Xl///cXH//2dn/v9/f/n/dHTz/1hY7f9bW+f/b2/f/4CA2P+dnc//x8fG/+bmvf/U1LT/qqqq/4ODoP9hYZX/TEyL/zg4gP8uLnb/Oztr/0dHYf9TU1b/W1tM/1paQv9ZWTn/V1cw/1VVJ/9OTh//RkYX/0BAEP86Ogr/NTUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8mJgH/JycF/ygoC/8oKBH/KCgZ/ycnIP8lJSn/IiIy/x8fO/8dHUX/ICBP/ycnWf8xMWP/Oztu/0dHef9XV4P/amqO/4CAmf+WlqP/ra2t/8bGt//i4sH/+vrK//z80//t7dv/19fj/8DA6v+rq/D/nJz2/5SU+/+QkP//jIz//4iI//+EhP//goL//4SE//+Fhf//hIT//4KC//+Bgf//gID//39///+AgP//gYH//4WF//+Li///lZX//6Oj//+1tf//ysr//+Pj///6+v/////////////////////////////39///zs7//6qq//+urv//0tL///f3///////////////////l5f//ubn//4+P//+Bgf//nZ3//729///c3P//+fn////////r6///wMD//5ub//90dP//amr//4CA/P9ycvf/WFjx/2Ji6v91deP/h4fb/6en0//S0sv/6enB/8rKuP+goK7/eXmk/1xcmv9ISI//NDSE/zMzev9CQm//Tk5k/1paWv9dXU//W1tF/1lZPP9XVzL/T08p/0dHIf8/Pxn/ODgS/zIyC/8tLQb/KysBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/x0dAf8eHgf/Hh4N/x4eE/8dHRv/Gxsj/xoaK/8aGjT/HR0+/yUlSP8tLVL/Nzdc/0JCZ/9SUnL/ZGR9/3h4h/+NjZL/pKSd/7y8p//W1rH/8fG7//v7xf/t7c7/1dXW/76+3v+np+b/lJTt/4qK8/+EhPn/fn7+/3d3//9ycv//cnL//3h4//9+fv//gID//4CA//9/f///gID//4OD//+IiP//kJD//5eX//+dnf//oaH//6Ki//+hof//oKD//6am//+ysv//wsL//9ra///29v/////////////////////////////y8v//x8f//7W1///IyP//8PD///////////////////Hx///ExP//l5f//4KC//+env//v7///97e///7+////////+7u///ExP//np7//3Z2//9ubv//gYH//29v+f9aWvT/aWnu/3x85/+Pj9//s7PX/97ezv/o6MX/v7+8/5WVsv9vb6j/WFid/0NDk/8yMoj/Ozt9/0lJc/9WVmj/Xl5d/11dU/9cXEn/WVk//1FRNf9ISCz/Pz8j/zg4G/8xMRT/KysN/yUlB/8iIgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/ExMD/xQUCP8UFA7/FBQV/xUVHf8YGCX/HR0u/yMjN/8qKkH/MzNL/z4+Vf9NTWD/Xl5q/3Bwdf+EhID/mZmL/7Cwlv/JyaD/5OSr//b2tf/v77//2NjI/8HB0f+pqdr/kpLi/4OD6f98fPD/dXX2/2xs/P9lZf//Z2f//3Nz//9+fv//goL//4GB//+AgP//goL//4qK//+YmP//p6f//7a2///Dw///z8///9jY///e3v//4uL//+Hh///d3f//1dX//87O///R0f//2dn//+bm///7+//////////////////////////////n5///y8v//8jI///q6v//////////////////+Pj//87O//+fn///hIT//6Cg///Bwf//4uL///39////////8PD//8bG//+fn///eHj//3Nz//+Cgv//amr8/19f9/9ycvH/hITq/5mZ4v/Bwdr/6urS/+Dgyf+zs7//iYm1/2hoq/9TU6H/PT2W/zU1jP9DQ4H/UFB2/11da/9gYGD/Xl5W/1xcS/9VVUH/Sko4/0BALv84OCb/MDAd/ykpFv8jIw//HR0I/xkZAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8REQT/ExMJ/xUVEP8ZGRf/HR0f/yMjJ/8pKTD/MTE5/zw8Q/9JSU3/WVlY/2lpY/97e23/j494/6Skg/+7u47/1NSZ/+zspP/x8a7/39+4/8fHwv+wsMv/l5fU/4KC3f93d+X/b2/s/2Zm8/9dXfn/Xl7+/2xs//98fP//g4P//4KC//9/f///goL//46O//+goP//tLT//8fH///Z2f//6ur///X1///8/P///////////////////////////////////Pz///f3///6+v///Pz///7+//////////////////////////////39///n5///19f//+fn///////////////////9/f//1dX//6Sk//+IiP//pKT//8bG///m5v/////////////x8f//yMj//6Cg//94eP//eHj//4CA//9nZ/7/Zmb5/3t78/+MjO3/paXl/9DQ3v/x8dX/1NTM/6amw/9+frn/YmKv/01NpP84OJr/OzuP/0tLhP9ZWXn/YmJu/2BgY/9fX1n/WFhO/01NRP9CQjr/OTkx/zAwKP8oKB//ISEX/xoaEP8WFgr/FBQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xsbBf8eHgv/ISER/yUlGP8rKyD/MzMp/z09Mv9ISDz/VVVG/2RkUP90dFr/hYVl/5mZcP+trXv/xcWG/9zckf/s7Jz/5uan/9DQsf+4uLv/oaHF/4iIzv92dtf/bGzg/2Nj6P9ZWe//VVX1/2Fh+/92dv//gYH//4OD//+AgP//e3v//4WF//+env//trb//8zM///i4v//9PT///7+//////////////////////////////////////////////////////////////////////////////////////////////////////////////7+///w8P//7e3////////////////////////b2///qKj//42N//+qqv//zMz//+3t//////////////Hx///IyP//np7//3l5//99ff//fHz//2Vl//9wcPv/hIT2/5WV7/+zs+j/4eHg//Hx2P/Gxs//mZnG/3R0vP9dXbL/Rkan/zg4nf9ERJL/U1OH/2BgfP9jY3H/YWFm/1xcW/9QUFH/RUVG/zo6PP8xMTP/KCgq/yAgIf8aGhn/GRkS/xsbC/8cHAX/EhIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8nJwH/LCwG/y4uDP8zMxP/OTka/0BAIv9KSiv/VFQ0/2BgPv9ubkj/fn5S/4+PXf+hoWj/trZz/83Nfv/g4In/6OiU/9vbn//Dw6r/ra20/5SUvv98fMj/bGzR/2Nj2v9YWOL/T0/q/1RU8f9oaPf/fHz9/4OD//+Cgv//enr//3Z2//+Kiv//q6v//8bG///f3///9fX////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7+////////////////////////93d//+pqf//lJT//7Ky///U1P//9PT/////////////7e3//8bG//+bm///enr//4GB//93d///aGj//3t7/f+Ojvj/oKDx/8PD6//w8OP/5+fb/7a20v+Li8n/bW2//1dXtf8/P6r/PT2g/01Nlf9cXIr/ZWV//2Rkc/9gYGj/VVVe/0hIU/89PUj/MzM+/ykpNf8fHyv/HBwj/x4eGv8iIhP/IyMM/yYmBv8hIQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/z8/Av8/Pwf/QkIN/0dHFP9OThv/VlYk/2BgLP9razb/eHhA/4eHSv+Xl1T/qqpf/76+av/S0nX/39+A/+Dgi//Pz5b/ubmh/6KirP+Kirb/cnLB/2Njyv9aWtT/T0/c/0lJ5f9WVuz/bW3z/39/+f+Dg/7/f3///3Fx//9zc///kJD//7Ky///S0v//7e3///7+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////3d3//6qq//+env//vb3//97e///8/P/////////////m5v//wcH//5eX//98fP//goL//3Fx//9xcf//hob//5iY+f+trfP/19ft//j45f/W1t3/pqbU/39/y/9nZ8H/T0+3/zw8rf9GRqL/VlaX/2NjjP9mZoH/Y2N2/1paa/9MTGD/QEBV/zU1S/8qKkD/ISE2/x8fLf8kJCT/KCgc/ysrFP8vLw3/NTUH/zs7AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/UFAC/1JSCP9WVg7/W1sV/2NjHf9rayX/dnYu/4KCN/+QkEH/oKBM/7KyVv/FxWH/09Ns/9vbd//Y2IL/xsaO/7Cwmf+ZmaT/gYGu/2pquf9cXMP/U1PN/0dH1v9HR97/WVnn/3Bw7v+BgfX/g4P7/3p6//9paf//cXH//5KS//+0tP//2Nj///b2///////////////////////////////////8/P//9/f///Ly///v7///8fH///z8///////////////////////////////////////////////////////////////////////////////////////////////////////////////////Z2f//rKz//6mp///Jyf//6+v//////////////f3//97e//+6uv//kpL//35+//9/f///cXH//35+//+Skv//o6P7/76+9f/s7O7/8/Pn/8PD3/+Vldb/d3fN/19fxP9GRrr/Pz+v/09PpP9fX5n/aGiO/2dng/9fX3j/UVFt/0REYv84OFf/LCxM/yMjQv8jIzj/KSkv/y0tJf8xMR3/OTkV/0BADv9GRgj/S0sCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9iYgP/ZGQI/2lpD/9vbxb/d3ce/4GBJv+MjC//mZk5/6ioQ/+5uU3/yMhY/9DQY//V1W7/0NB5/729hP+oqJD/kpKb/3l5pv9jY7D/Vla7/0xMxf9BQc//RkbY/1pa4P9xcej/gYHw/4KC9v91dfz/Y2P//29v//+Rkf//s7P//9fX///5+f////////////////////////39///y8v//5eX//9fX///Ly///xsb//9HR///s7P///////////////////////////////////////////////////////////////////////////////////////////////////////////////////v7//9PT//+zs///t7f//9jY///4+P/////////////4+P//1NT//7Cw//+MjP//gID//3p6//94eP//jIz//52d//+vr/z/09P3//v78P/i4un/r6/h/4iI2P9vb8//VlbF/0FBu/9JSbH/Wlqm/2dnm/9qapD/ZGSF/1ZWev9ISG//Oztj/y4uWf8lJU7/JydD/y0tOf8yMjD/Nzcn/0FBHv9KShb/UVEP/1dXCf9cXAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/3R0A/93dwn/fX0P/4ODF/+MjB//lpYn/6OjMP+xsTr/vr5E/8fHT//MzFn/0NBk/8nJb/+1tXv/oaGG/4uLkf9zc5z/XV2n/1FRsv9GRrz/PT3G/0VF0P9aWtn/cHDi/4GB6v+AgPH/cXH3/15e/f9qav//jY3//66u///T0///+Pj///////////////////39///w8P//3d3//8vL//+6uv//qqr//6ur///Cwv//5eX////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4+P//0tL//729///IyP//6ur//////////////////+7u///IyP//pKT//4WF//+AgP//enr//4aG//+Zmf//qqr//8DA/v/s7Pj/+vrx/8vL6v+dneL/fn7a/2Zm0f9MTMf/Q0O9/1RUs/9kZKj/bGyd/2hokv9cXIf/TU17/z8/cP8yMmX/Jyda/yoqT/8xMUX/NjY7/z09Mf9ISCj/U1Mf/1tbF/9jYxD/aGgJ/2xsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/hoYE/4qKCv+QkBD/mJgX/6GhH/+srCj/trYx/76+O//CwkX/yMhQ/8vLW//BwWX/rq5x/5ubfP+FhYf/bm6T/1lZnv9MTKn/QkKz/zk5vv9DQ8j/WFjR/25u2/+AgOP/gIDr/29v8v9bW/j/ZGT+/4aG//+oqP//zMz///X1///////////////////29v//4uL//8zM//+4uP//oqL//5KS//+fn///xMT//+vr///+/v////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////T0///Z2f//yMj//93d///7+///////////////////4OD//7u7//+Xl///gYH//35+//+Dg///lZX//6en//+3t///19f+//7++f/o6PP/tLTr/4+P5P91ddv/XFzS/0ZGyf9NTb7/X1+0/2xsqf9sbJ7/YWGT/1JSiP9DQ33/NTVx/yoqZv8tLVv/NDRQ/zo6Rv9CQjz/Tk4y/1paKf9kZCD/bW0Y/3R0Ef95eQr/fX0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+ZmQT/np4K/6SkEf+qqhj/sbEg/7a2Kf+6ujL/vr48/8PDRv/FxVH/urpb/6ioZv+VlXL/gYF9/2pqiP9VVZT/SEif/z4+qv82NrX/QUG//1ZWyf9ra9P/f3/c/39/5P9ubuz/WVnz/1xc+f99ff//np7//8LC///x8f//////////////////7+///9fX///AwP//qqr//5GR//+Hh///oaH//9DQ///39///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+vr//+Hh///Y2P//9fX///////////////////X1///Q0P//rKz//4qK//9/f///g4P//5OT//+lpf//tbX//8nJ///y8v///Pz6/87O8/+goOz/hITl/2tr3P9QUNP/SEjK/1lZwP9qarX/cHCq/2dnoP9XV5T/SEiJ/zk5fv8tLXL/Ly9n/zc3XP8+PlH/RkZH/1RUPP9hYTP/bGwp/3Z2If9/fxn/hYUR/4uLCv+PjwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/6SkBP+npwr/q6sR/6+vGP+ysiD/trYp/7q6M/+/vzz/v79H/7KyUf+hoVz/j49n/3x8cv9mZn7/UVGJ/0VFlP86OqD/MjKr/z4+tv9SUsD/Z2fK/3x80/9/f9z/bm7l/1lZ7f9VVfT/c3P6/5SU//+2tv//6en//////////////f3//+rq///R0f//ubn//6Cg//+IiP//goL//6Sk///U1P//+/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6ur///Hx////////////////////////5eX//7+///+bm///gYH//4KC//+Tk///pKT//7W1///ExP//4+P////////n5/r/tLT0/5OT7f95eeX/Xl7d/0pK1P9UVMr/ZmbB/3Fxtv9sbKv/XFyg/0xMlf89PYr/MDB+/zExc/86Omj/QUFd/0lJUv9YWEf/Z2c9/3NzM/9+fir/iIgh/5GRGf+YmBH/nJwL/52dBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/pKQF/6ioC/+rqxH/r68Z/7KyIf+2tir/u7sz/7i4Pf+qqkf/m5tR/4qKXP93d2f/Y2Nz/05Of/9BQYr/NzeV/y8voP87O6v/T0+2/2JiwP94eMr/gIDU/3Bw3f9bW+X/T0/t/2dn9P+IiPr/qan//9vb//////////////r6///m5v//z8///7a2//+cnP//hYX//4CA//+goP//0ND///n5///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6+v////////////////////////X1///Q0P//rKz//4mJ//9/f///kJD//6am//+2tv//xcX//9nZ///8/P//+vr//8nJ+/+hofT/h4fu/2xs5v9SUt7/T0/V/2Jiy/9xccH/cXG3/2JirP9RUaH/QUGW/zMzi/80NH//PT1z/0REaP9NTV3/XFxS/2xsSP96ej3/hoYz/5GRKv+bmyH/oqIZ/6OjEv+hoQv/np4F/5WVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+kpAX/qKgL/6urEf+vrxn/s7Mh/7W1Kv+vrzP/oqI9/5SUR/+FhVL/c3Nd/2BgaP9LS3T/Pj5+/zU1iv8tLZb/Nzeh/0pKrP9dXbb/cnLB/4CAy/9yctT/Xl7d/0xM5v9aWu3/e3v0/5qa+//IyP//+/v////////7+///39///83N//+3t///m5v//4SE//9+fv//l5f//8XF///z8//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9/f//39///7q6//+Wlv//gID//4yM//+jo///uLj//8jI///X1///9fX////////e3v//sLD7/5SU9f95ee7/Xl7m/09P3v9dXdX/b2/L/3V1wf9nZ7f/VVWs/0VFof82Npb/NjaL/0BAgP9HR3T/UFBo/19fXf9xcVL/gIBI/42NPf+amjT/pKQq/6ioIf+mphn/o6MS/6GhC/+engX/lZUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/6SkBf+oqAv/q6sR/6+vGf+uriH/pqYq/5qaM/+NjT3/f39H/29vUv9dXV3/SUlo/zw8dP8yMn7/KiqK/zQ0lv9GRqH/WFis/2xstv9+fsH/dXXL/2Ji1P9NTd3/Tk7m/21t7f+Li/T/s7P7/+3t/////////v7//9/f///IyP//t7f//56e//+Fhf//fn7//4yM//+2tv//5ub//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+rq///Fxf//oaH//4OD//+Kiv//oaH//7i4///MzP//2tr///Hx////////7u7//8DA//+goPv/hYX1/2pq7v9TU+b/WVne/21t1f93d8v/bGzB/1pat/9JSaz/OTmh/zk5lv9CQov/S0uA/1NTdP9jY2j/dXVd/4WFUv+UlEj/oqI9/6ysNP+srCr/qakh/6amGf+joxL/oaEL/56eBf+WlgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/paUF/6ioC/+pqRH/pKQZ/5ycIf+RkSr/hoYz/3l5Pf9qakf/WVlR/0ZGXP85OWf/MDBz/ygofv8wMIr/QUGV/1NToP9lZav/eXm2/3h4wP9lZcv/UlLU/0ZG3f9eXuX/fHzt/5+f9P/W1vr////////////l5f//xcX//7S0//+hof//iYn//39///+Dg///pKT//9TU///8/P////////////////////////7+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w8P//zMz//6io//+Jif//i4v//6Ki//+4uP//zc3//+Dg///x8f////////n5///Ozv//rKz//5GR+/91dfT/Wlru/1dX5v9qat3/eXnV/3Fxy/9eXsH/TEy3/zw8rP87O6H/RUWW/05Oi/9WVn//ZmZz/3l5aP+Kil3/mppS/6mpSP+xsT3/sLAz/6ysKv+pqSH/pqYZ/6OjEv+hoQv/np4F/5WVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+goAT/nZ0K/5iYEf+RkRj/iIgh/35+Kf9ycjP/ZGQ8/1VVR/9ERFH/NjZc/y4uZ/8lJXL/LCx+/z09if9NTZT/X1+g/3Nzq/96erX/amrA/1dXyv9ERNT/T0/d/21t5f+MjO3/u7v0//T0+v//////8fH//8fH//+ysv//oKD//4yM//+AgP//f3///5GR//+9vf//7e3////////////////////////7+///6ur///39////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8fH//8/P//+srP//jo7//5CQ//+mpv//urr//87O///j4///9vb////////+/v//2tr//7e3//+bm///f3/6/2Nj9P9YWO3/aGjl/3l53f91ddT/YmLK/1BQwf8/P7b/Pj6r/0lJoP9RUZX/WlqK/2lpfv99fXP/j49o/6CgXf+wsFL/trZH/7KyPf+vrzP/rKwq/6mpIf+mphn/o6MR/6GhC/+engUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4+PBP+Kigr/hYUR/35+GP91dSD/a2sp/15eMv9QUDz/QEBG/zMzUf8rK1v/IyNm/ykpcv84OH3/SEiI/1lZlP9sbJ//enqq/29vtf9dXb//SUnJ/0JC0/9dXdz/enrk/6Gh7P/a2vP////5//39///S0v//srL//6Cg//+MjP//gID//4CA//+Dg///paX//9TU///8/P///////////////////f3//97e///Y2P//+/v//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+3t///Ly///rKz//5aW//+Zmf//ra3//8DA///T0///5+f///n5/////////////+Tk///AwP//pKT//4mJ//9sbPr/Wlrz/2dn7P96euX/eHjc/2Vl0/9SUsn/QkK//0JCtf9MTKr/VVWf/11dlP9tbYn/gYF+/5SUcv+mpmf/trZc/7q6Uf+2tkf/srI8/6+vM/+srCn/qakh/6WlGP+goBH/m5sK/5eXBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/fHwE/3h4Cf9zcxD/a2sX/2JiH/9XVyj/S0sx/zw8O/8wMEX/KSlQ/yEhWv8mJmX/NDRx/0NDfP9TU4f/ZGST/3Z2nv9zc6n/YmKz/1BQvv8+Psj/TEzS/2lp2/+JieP/vLzr//T08v////n/5eX+/7i4//+iov//jo7//319//+AgP//gID//42N//+4uP//5+f////////////////////////n5///w8P//8/P///4+P////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////n5///j4///x8f//7Cw//+iov//p6f//7m5///Kyv//29v//+7u///9/f/////////////q6v//yMj//6ys//+QkP//c3P+/15e+f9nZ/L/enrr/3p64/9oaNv/VFTS/0VFyP9GRr7/UFC0/1lZqf9iYp7/cXGT/4aGiP+ZmX3/rKxx/7u7Zv+9vVv/ublQ/7W1Rv+ysjz/rq4y/6ioKf+fnyD/lpYY/4+PEP+JiQr/hYUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9rawP/ZmYJ/2FhD/9ZWRf/UFAf/0REJ/83NzD/LCw6/yYmRP8fH0//IyNZ/zExZP8/P2//Tk57/15ehv9vb5H/dXWc/2dnp/9WVrL/Q0O8/z4+xv9ZWdD/dXXZ/56e4v/W1ur////x//n59//IyP3/pqb//5OT//99ff//eXn//4KC//+Bgf//nJz//8jI///19f//////////////////9PT//8zM//+wsP//zs7///7+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////8/P//5eX//87O//+8vP//s7P//7q6///Jyf//19f//+fn///39///////////////////7u7//87O//+ysv//lpb//3l5//9jY/3/amr4/3x88f98fOr/aWni/1VV2v9HR9H/SkrH/1VVvf9eXrP/Zmao/3Z2nf+Li5L/n5+G/7Oze//BwXD/wcFl/7y8Wv+5uU//tbVF/6ysOv+goDH/lZUo/4uLH/+Dgxf/fHwQ/3d3Cf90dAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1lZA/9VVQj/T08P/0dHFv89PR7/MjIm/ygoL/8iIjn/HBxD/yEhTf8tLVj/Oztj/0lJbv9YWHn/aGiE/3R0kP9tbZv/XFym/0tLsP86Orv/R0fF/2Njz/+Dg9j/tbXg/+3t6P////D/4eH2/7Gx/P+YmP//g4P//3Fx//9+fv//gYH//4WF//+qqv//19f///39///////////////////g4P//trb//6Oj///W1v////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Ly///b2///zMz//8nJ///S0v//3t7//+rq///29v///v7///////////////////Dw///S0v//tbX//5qa//99ff//Z2f//21t/P9+fvb/fHzw/2lp6f9VVeH/SkrY/1BQz/9bW8X/Y2O7/2xssf97e6b/kZGb/6amkP+6uoX/xsZ6/8TEbv/AwGP/vLxY/7OzTv+lpUP/l5c5/4uLMP+AgCf/d3ce/29vFv9qag//ZWUJ/2JiAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/SEgC/0NDB/89PQ7/NTUV/ysrHf8jIyX/Hh4u/xoaN/8fH0H/KytL/zc3Vv9ERGH/UlJs/2Jid/9vb4L/cHCO/2Jimf9SUqT/Pz+u/zk5uf9SUsP/bW3N/5aW1v/Ly9///f3n//n57v/Hx/X/n5/7/4uL//90dP//cHD//4KC//9/f///iYn//7a2///i4v//////////////////9PT//87O//+jo///n5///97e///////////////////////////////////////////////////////////////////////////////////////////////////////////////////9/f//8PD//+np///r6///8PD///b2///8/P////////////////////////39///t7f//0tL//7e3//+bm///f3///2tr//9ycv//gID7/3t79f9oaO7/VVXn/05O3/9WVtb/YWHN/2lpw/9ycrn/goKv/5iYpP+trZn/wcGO/8rKg//IyHj/w8Nt/7y8Yv+urlf/nZ1M/46OQv+BgTj/dnYu/2xsJf9jYx3/XFwV/1dXDv9TUwj/UVECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP83NwL/MTEH/ysrDf8kJBT/Hh4b/xoaJP8XFyz/Hh42/ykpQP80NEr/QEBU/01NX/9bW2r/amp1/3BwgP9oaIv/WFiW/0dHof82Nqz/QEC3/1pawf96esr/qanU/+Dg3P///+X/5eXs/7Gx8/+Skvn/fn7//2ho//91df//g4P//3x8//+Njf//v7///+zs///////////////////n5///v7///5WV//+hof//4uL////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////39///5OT//8/P//+1tf//mpr//39///9wcP//eXn//4KC//95efn/ZWXz/1VV7P9UVOX/Xl7d/2dn1P9wcMv/eXnB/4qKt/+goK3/tbWi/8nJl//Pz4z/y8uB/8XFdv+4uGv/pqZg/5WVVf+Ghkr/eHhA/2xsNv9hYS3/WFgk/1BQHP9JSRT/REQN/0FBB/9AQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/yUlAf8gIAb/HBwM/xgYEv8VFRr/FhYi/x0dK/8nJzT/MTE+/z09SP9JSVL/VlZd/2RkaP9tbXP/a2t+/15eif9OTpT/PT2f/zQ0qv9JSbT/Y2O+/4mJyP+8vNH/8fHa//z84v/Pz+r/oKDx/4aG9/9xcf3/Y2P//3x8//+Cgv//eXn//5KS///ExP//8vL//////////////Pz//9ra//+ysv//i4v//6Ki///i4v////////////////////////7+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7+///7Oz//9jY///Fxf//sLD//5aW//99ff//dnb//39///+Bgf//dHT9/2Fh9/9YWPH/XV3q/2dn4/9vb9r/d3fS/4GByP+Tk7//qam0/7+/qv/R0Z//1NSU/87Oif/Dw37/sbFz/56eaP+NjV3/fX1T/29vSP9iYj7/VlY1/0xMK/9DQyP/PDwa/zY2E/8xMQz/Li4G/y8vAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xQUBf8SEgv/EhIR/xYWGP8eHiD/JiYp/zAwMv86Ojz/RUVG/1FRUP9fX1r/aGhl/2trcP9kZHv/VVWG/0VFkf80NJz/ODin/1BQsf9tbbv/mprF/83Nz//8/Nf/7e3g/7u76P+Rke//fHz1/2Vl+/9lZf//gID//4GB//91df//lZX//8fH///29v/////////////19f//0ND//6mp//+Fhf//oaH//97e////////////////////////8/P//+/v///9/f/////////////////////////////////////////////////////////////////////////////////////////////////////////////4+P//6+v//9ra///Jyf//uLj//6am//+Pj///fn7//319//+Cgv//fX3//21t//9fX/v/X1/1/2ho7/9wcOj/eHjg/39/2P+Li8//n5/F/7W1vP/KyrL/2Nin/9jYnf/OzpL/vb2H/6mpfP+VlXH/hIRm/3R0W/9lZVD/V1dG/0tLPP9AQDP/ODgp/zExIf8rKxn/JycR/yMjC/8hIQX/ICABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/Dw8E/xMTCf8YGBD/Hx8X/ycnH/8vLyf/ODgw/0JCOf9NTUP/WlpN/2VlWP9oaGL/Z2dt/1tbeP9MTIP/PDyO/y8vmf8/P6T/V1eu/3t7uP+pqcL/3d3M//7+1f/b293/qqrl/4aG7P9ycvP/XFz5/2pq/v+Cgv//fX3//3Jy//+Xl///x8f///b2/////////////+/v///Jyf//oqL//4KC//+env//2Nj////////////////////////v7///1dX//+Tk///+/v///////////////////////////////////v7///j4///y8v//+Pj///39///////////////////+/v//+/v///b2///t7f//4eH//9PT///Fxf//trb//6en//+Wlv//h4f//39///+Bgf//gID//3V1//9oaP//ZGT+/2tr+f9zc/P/e3vs/4KC5f+Jid3/l5fV/6yszP/CwsL/1ta5/9/frv/Z2aT/ysqZ/7S0j/+goIT/jY15/3t7bv9qamP/W1tY/01NTv9BQUT/ODg6/zExMP8rKyf/JiYf/yEhF/8dHRD/GRkK/xcXBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8XFwP/HBwI/yEhDv8nJxX/Ly8d/zc3Jf9AQC7/Sko3/1ZWQf9hYUv/ZWVV/2dnYP9gYGr/UlJ1/0REgP8zM4v/MDCW/0ZGoP9gYKv/iIi1/7e3v//r68j/+PjR/8rK2v+bm+L/fX3p/2ho8P9XV/b/b2/8/4OD//95ef//b2///5eX///Fxf//9fX/////////////6en//8TE//+dnf//gID//5iY///Ozv///Pz///////////////////Ly///Jyf//xsb//+jo//////////////////////////////39///q6v//3t7//9TU///Ozv//0dH//9fX///b2///29v//9jY///S0v//y8v//8LC//+3t///rKz//5+f//+Skv//h4f//4GB//+AgP//f3///3h4//9wcP//bW3//3Jy//95efz/gID2/4eH8P+Njen/lZXi/6am2v+7u9L/0dHJ/+Liv//j47X/1dWr/8DAof+qqpb/lpaL/4ODgP9wcHb/X19r/1FRYP9FRVb/PDxL/zQ0Qf8tLTf/JiYu/yAgJf8bGx3/GRkW/xcXD/8WFgj/FRUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/yAgAf8kJAf/KSkN/zAwE/83Nxv/Pz8j/0hIK/9SUjT/XV0+/2JiSP9lZVL/Y2Nc/1hYZ/9KSnL/Ozt9/ywsh/81NZL/TEyd/2pqp/+VlbH/xMS7//T0xf/r687/u7vW/4+P3/90dOb/X1/t/1VV9P9zc/n/hIT+/3R0//9tbf//lpb//8LC///z8//////////////l5f//wcH//5ub//9/f///kZH//8PD///09P//////////////////9/f//8vL//+xsf//xMT///Pz//////////////////////////////f3///c3P//x8f//7q6//+vr///qan//6en//+np///pqb//6Oj//+dnf//l5f//4+P//+Hh///goL//39///9/f///f3///3x8//95ef//eXn//319//+Dg///iYn+/46O+f+Tk/T/mprt/6am5v+4uN//zs7X/+Lizv/r68X/4eG7/8vLsv+1taf/n5+d/4uLkv93d4j/ZWV9/1ZWcv9KSmj/QEBd/zg4Uv8vL0j/Jyc+/yEhNf8eHiz/Hh4j/x8fG/8gIBT/ICAN/x8fB/8fHwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/KioB/ywsBf8xMQv/NzcR/z8/Gf9HRyD/UFAp/1paMv9gYDv/YWFF/2RkT/9cXFn/UFBj/0JCbv8zM3n/KiqD/zs7jv9SUpn/dnaj/6Ghrf/Q0Lf/+PjB/93dyv+urtP/hITb/21t4/9XV+r/Vlbw/3Z29v+Dg/z/cHD//2pq//+Tk///vr7///Hx/////////////+Pj//+/v///mpr//4CA//+Jif//trb//+fn///////////////////8/P//19f//62t//+mpv//zc3///b2//////////////////////////////n5///i4v//ysr//7i4//+pqf//nZ3//5OT//+Njf//iIj//4WF//+Dg///goL//4OD//+Ghv//iYn//4qK//+Kiv//i4v//46O//+Rkf//lZX//5mZ//+dnfz/oqL2/6ur8P+7u+r/z8/j/+Pj2//w8NP/6enK/9XVwf++vrf/p6eu/5KSo/9+fpn/a2uP/1xchP9PT3n/RUVv/zw8ZP8yMlr/KipP/yQkRf8iIjz/JCQy/ycnKf8oKCH/KSkZ/yoqEv8pKQv/KCgF/ykpAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/NDQE/zk5Cf8/Pw//R0cW/09PHv9YWCb/XV0v/19fOP9hYUH/YGBL/1RUVf9ISGD/Oztq/ysrdf8sLH//QECK/1palf+Cgp//rKyp/9vbs//19b3/0NDG/6Kiz/97e9f/Zmbf/1BQ5v9YWO3/eHjz/4KC+f9sbP3/Z2f//4+P//+4uP//7e3////////9/f//4eH//8DA//+bm///gYH//4OD//+oqP//19f///39///////////////////j4///urr//5iY//+mpv//0dH///T0//////////////////////////////7+///v7///3Nz//8zM//+/v///tLT//6ys//+mpv//oaH//5+f//+fn///oKD//6Ki//+kpP//o6P//6Oj//+kpP//pqb//6io//+qqv//rq7+/7W1+f/Dw/P/1dXt/+np5v/19d//8PDX/9zcz//ExMb/rq69/5iYs/+EhKn/cXGf/2Fhlf9VVYr/SkqA/0BAdf82Nmv/LS1g/ycnVv8nJ0z/KipC/y0tOP8wMC//MTEn/zMzHv8zMxf/MzMQ/zIyCv8xMQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP89PQL/QUEH/0dHDf9OThT/VlYb/1tbI/9cXCz/Xl41/2BgPv9ZWUj/TU1S/0FBXP8zM2b/JiZx/zAwe/9FRYb/Y2OQ/4yMmv+2tqX/5OSu/+7uuP/ExMH/mJjK/3R00/9fX9v/TEzi/1lZ6f95ee//gYH1/2lp+v9jY///ior//7Gx///n5/////////v7///c3P//wMD//56e//+Cgv//gID//5mZ///Gxv//8fH///////////////////Dw///Ly///paX//42N//+oqP//zc3//+3t///////////////////////////////////7+///7+///+Li///X1///zs7//8jI///Dw///wcH//7+////AwP//wMD//76+//+7u///u7v//7u7//+7u///vr7//8bG///S0vr/4+P1//Pz8P/6+un/8/Pi/9/f2//Hx9P/sbHK/5ubwv+Hh7j/dXWv/2Zmpf9aWpv/T0+Q/0VFhv86Onz/MDBx/ysrZ/8rK1z/Ly9S/zMzSP82Nj//OTk1/zo6LP88PCT/PDwc/z09FP88PA7/OzsI/zo6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/0ZGAf9JSQb/T08L/1VVEv9YWBn/Wloh/1tbKf9eXjH/W1s7/1FRRP9GRk7/OjpY/ywsYv8lJWz/NTV3/0tLgf9ubov/lZWW/7+/oP/q6qr/5eWz/7q6vf+Pj8b/bW3O/1lZ1v9ISN7/Wlrl/3p66/+AgPH/Z2f3/19f/P+Dg///qan//9/f/////////Pz//9nZ//+9vf//oaH//4WF//9/f///jIz//7S0///e3v///////////////////Pz//9zc//+6uv//lZX//4iI//+kpP//xMT//+Hh///39////////////////////////////////////f3///f3///w8P//6en//+Tk///h4f//3t7//93d///Z2f//1dX//9TU///U1P//2Nj//+Dg///q6v//9fX8//399//8/PL/8PDs/9zc5f/Fxd7/sLDW/5ubzv+IiMb/eHi9/2trs/9fX6r/VFSg/0lJlv8+Poz/NDSB/y4ud/8wMG3/NDRi/zk5WP88PE7/Pz9E/0JCO/9ERDL/RUUp/0ZGIf9GRhn/RkYS/0VFDP9ERAb/Q0MBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1FRBP9UVAn/VlYP/1dXFv9ZWR7/W1sm/1xcLv9UVDf/SkpA/z8/Sv8zM1T/JSVe/ycnaP86OnL/U1N8/3d3hv+enpH/x8eb/+zspf/b267/sbG3/4eHwP9nZ8n/U1PR/0VF2f9bW+D/eXnn/4CA7f9lZfP/W1v4/319/f+hof//1dX////////+/v//2Nj//7u7//+goP//hob//39///+Dg///oqL//8nJ///x8f//////////////////7u7//8zM//+trf//jIz//4SE//+cnP//t7f//9DQ///m5v//9vb//////////////////////////////////////////////v7///z8///7+///+Pj///b2///29v//+Pj///z8/////////////////f/19fj/5ubz/9HR7f+9vef/qqrg/5iY2f+IiNH/eXnJ/21twf9iYrj/V1eu/0xMpf9AQJv/NzeR/zIyh/80NH3/OTly/z4+aP9CQl7/RUVU/0lJSv9LS0H/TU03/09PLv9QUCb/UFAe/1BQF/9PTxD/Tk4K/01NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/U1MC/1NTB/9VVQ3/V1cU/1hYG/9aWiL/VlYq/01NM/9DQzz/ODhG/ywsT/8hIVn/Kytj/z4+bf9bW3f/gICB/6Wli//Pz5X/6+uf/9LSqf+pqbL/gIC7/2JixP9OTsz/Q0PU/1tb2/95eeL/f3/p/2Vl7/9WVvT/dXX5/5mZ/f/Jyf///v7////////b2///urr//6Cg//+Fhf//f3///4CA//+QkP//tbX//9vb///8/P/////////////8/P//4OD//8LC//+lpf//iIj//4CA//+Rkf//qKj//729///Q0P//4eH//+7u///4+P///f3////////////////////////////////////////////////////////+/v//+Pj//+7u///h4f3/0ND5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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4AAAf////////////////gAAAAf///////////////AAAAAA//////////////+AAAAAAB/////////////+AAAAAAAH////////////+AAAAAAAAf///////////+AAAAAAAAB///////////+AAAAAAAAAH//////////+AAAAAAAAAAf//////////AAAAAAAAAAD//////////gAAAAAAAAAAP/////////gAAAAAAAAAAB/////////wAAAAAAAAAAAP////////4AAAAAAAAAAAB////////8AAAAAAAAAAAAP///////+AAAAAAAAAAAAB////////AAAAAAAAAAAAAP///////gAAAAAAAAAAAAB///////wAAAAAAAAAAAAAP//////4AAAAAAAAAAAAAB//////8AAAAAAAAAAAAAAP/////+AAAAAAAAAAAAAAB//////gAAAAAAAAAAAAAAf/////wAAAAAAAAAAAAAAD/////4AAAAAAAAAAAAAAAf////+AAAAAAAAAAAAAAAD/////AAAAAAAAAAAAAAAA/////gAAAAAAAAAAAAAAAH////4AAAAAAAAAAAAAAAB////8AAAAAAAAAAAAAAAAP////AAAAAAAAAAAAAAAAD////gAAAAAAAAAAAAAAAAf///4AAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAP///gAAAAAAAAAAAAAAAAB///4AAAAAAAAAAAAAAAAAf//+AAAAAAAAAAAAAAAAAH///AAAAAAAAAAAAAAAAAA///wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAD//+AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAH//4AAAAAAAAAAAAAAAAAB//+AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAB//wAAAAAAAAAAAAAAAAAAf/8AAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAB//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//gAAAAAAAAAAAAAAAAAD//4AAAAAAAAAAAAAAAAAB//+AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAH//4AAAAAAAAAAAAAAAAAB///AAAAAAAAAAAAAAAAAA///wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAD///gAAAAAAAAAAAAAAAAB///4AAAAAAAAAAAAAAAAAf//+AAAAAAAAAAAAAAAAAH///wAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAA////gAAAAAAAAAAAAAAAAf///4AAAAAAAAAAAAAAAAH////AAAAAAAAAAAAAAAAD////wAAAAAAAAAAAAAAAA////+AAAAAAAAAAAAAAAAf////gAAAAAAAAAAAAAAAH////8AAAAAAAAAAAAAAAD/////gAAAAAAAAAAAAAAA/////4AAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAP/////4AAAAAAAAAAAAAAH/////+AAAAAAAAAAAAAAB//////wAAAAAAAAAAAAAA//////+AAAAAAAAAAAAAAf//////wAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAH///////wAAAAAAAAAAAAD///////+AAAAAAAAAAAAB////////wAAAAAAAAAAAA////////+AAAAAAAAAAAAf////////wAAAAAAAAAAAP////////+AAAAAAAAAAAH/////////4AAAAAAAAAAH//////////AAAAAAAAAAD//////////8AAAAAAAAAB///////////gAAAAAAAAB///////////+AAAAAAAAB////////////4AAAAAAAB/////////////gAAAAAAB/////////////+AAAAAAB//////////////8AAAAAD///////////////4AAAAH////////////////8AAA///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8oAAAAQAAAAIAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADuDAwB/w4OAv8oKAT/TEwF/3BwBv+Skgf/np4I/56eCP+ZmQf/e3sG/1VVBf8uLgT8DAwC6gkJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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/2Fhev9qaoz/cXGe/3V1r/93d7//dXXN/3Jy2v9wcOb/cHDv/3Jy+P92dv7/enr//3t7//95ef//d3f//4GB//+env//yMj///f3////////1NT//5WV//94eP//d3f//3Z2//+kpP7/8PD4/8zM8P92dub/UFDb/3Bwzv9dXb//QECw/11dn/+amo3/ysp6/7W1aP95eVX/RUVD/yUlMv8jIyL/MzMU/0BACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/SUkD/1VVDf9XVxv/WVkq/15eO/9jY03/aGhg/2xsc/9tbYb/aWmZ/2Fhq/9WVrz/T0/M/01N2v9SUuf/XFzx/2ho+v90dP//fn7//4WF//+IiP//iYn//4WF//9+fv//gYH//5+f///Nzf//+/v////////Z2f//lZX//319//94eP//mJj//97e///p6fv/jo7y/1hY5/9ubtv/Y2PN/0VFvf9kZKz/o6Oa/9HRh/+srHT/a2th/zo6Tv8jIzv/MDAq/0FBG/9NTQ7/SUkDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1NTB/9XVxP/W1sh/19fMv9hYUT/YWFX/15ea/9WVn//SkqT/0BApv89Pbj/RUXJ/1dX2P9vb+b/i4vx/6en+/+/v///0ND//9vb///h4f//4uL//9ra///Jyf//rq7//4+P//+AgP//m5v//9TU/////////////87O//+Njf//fHz//5SU///Pz///+Pj//6en+/9jY/L/bGzm/2Zm2f9LS8n/bW25/6+vp//V1ZP/oKCA/11dbP8zM1j/KSlF/z09M/9PTyL/U1MT/09PBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/05OAf9TUwv/VFQZ/1VVKP9TUzr/Tk5N/0dHYf88PHX/MzOK/zQ0nv9DQ7H/XV3D/39/1P+oqOP/0dHv/+/v+v/+/v////////////////////////////////////////n5///g4P//sLD//4eH//+jo///5eX////////29v//sLD//39///+UlP//ycn///z8//+9vf//b2/7/21t8P9oaOP/UVHU/3d3xP+9vbL/1dWf/5GRi/9RUXb/Li5i/zQ0Tv9MTDv/V1cp/1RUGf9SUgz/UFACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9CQgT/QUEQ/0FBHv8/Py//OTlC/zExVf8pKWr/LCx//z4+lP9bW6j/gYG7/7Kyzf/j493//Pzs//b29//i4v//0dH//8rK///MzP//0ND//9TU///e3v//7u7///7+//////////////r6///Kyv//k5P//729///8/P///////9XV//+Li///kpL//8vL///+/v//zc3//3t7//9vb/j/aGjs/1pa3v+EhM7/zc28/87Oqf9/f5X/R0eA/y8va/9DQ1b/V1dC/1lZMP9VVR//Tk4Q/0dHBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/LS0H/y0tFP8rKyP/JSU1/yEhSP8lJV3/NjZy/1JSiP94eJ3/qKix/97exP/4+Nb/5+fm/8HB8/+jo/7/k5P//4+P//+QkP//j4///4qK//+Kiv//kZH//6Gh//++vv//5eX//////////////////8/P//+pqf//6en////////w8P//np7//46O///Ozv///////9jY//+EhP//c3P+/2dn9P9lZeb/lZXX/93dxf+/v7L/bm6e/z4+iP82NnP/UlJe/11dSf9ZWTb/T08k/0BAFP81NQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADvBwcB/xgYCv8ZGRj/GRko/x8fO/8vL0//SEhk/2pqev+VlY//yMil/+/vuf/m5sz/t7fd/4+P7P94ePn/cHD//3Z2//98fP//fX3//4aG//+amv//rq7//7y8///Bwf//v7///8fH///o6P//////////////////zMz//9ra/////////v7//7Gx//+Pj///0tL////////e3v//ior//3d3//9nZ/r/c3Pt/6ur3v/m5s3/qKi6/19fpf85OZD/RUV6/11dZf9fX1D/U1M7/0BAKf8wMBn/JCQL5gsLAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wYGAv8YGA3/ICAc/y4uLf9CQkD/Xl5U/4GBav+srID/29uW/+npq//FxcD/kpLT/29v5P9eXvL/ZWX+/3p6//9+fv//iYn//6ys///V1f//8vL////////////////////////9/f///Pz///////////////////n5///q6v////////////+/v///l5f//9vb////////39///42N//94eP//amr+/4aG8//Hx+T/4uLT/46Owf9SUqz/OzuX/1ZWgf9jY2v/WlpV/0REQP8wMC3/ICAc/xgYDfwKCgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8sLAT/OjoQ/0dHH/9aWjD/dHRE/5WVWf+9vW//39+F/9jYnP+pqbH/eXnF/1pa2P9RUen/amr3/4CA//94eP//kJD//87O///39///////////////////////////////////////////////////////////////////////////////////w8P//6am///q6v///////9nZ//+MjP//dnb//3Z2//+dnff/5OTp/8vL2f93d8b/R0ey/0hInP9kZIb/YmJw/0tLWv8zM0X/ISEx/yAgH/8nJxD/JycEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/V1cF/2NjEv9yciH/iYk0/6enSP/Hx13/2dlz/8XFiv+WlqD/aGi2/0tLyv9KStz/b2/t/39/+v9tbf//j4///9ra//////////////7+///t7f//3Nz//+rq/////////////////////////////////////////////////////////////8PD//+/v////Pz////////IyP//iIj//3d3//+Li///vr77//Hx7f+oqN3/ZGTL/0NDtv9cXKH/aWmK/1VVdP84OF7/JiZI/ykpNP82NiL/RUUS/01NBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/35+Bv+LixP/np4j/7OzNv/Hx0r/zs5g/7W1dv+IiI3/XV2j/0FBuf9GRs3/cHDg/3197/9jY/3/hYX//9XV/////////////+np//+/v///pKT//8jI///39/////////////////////////////////////////////////////////39///Nzf//4uL////////29v//sLD//39///+Fhf//qKj//+Xl/f/d3fD/iIjg/1NTzv9QULr/bGyk/2Bgjv9AQHf/Kyth/zExS/9BQTb/VlYk/2lpFP9wcAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+fnwf/q6sV/7a2Jf/AwDf/wcFM/6enYv9+fnj/VFSP/zo6pv9AQLv/a2vQ/3194v9dXfH/dHT+/8TE/////////Pz//9fX//+hof//jo7//87O///////////////////////////////////////////////////////////////////+/v//6en///7+////////3d3//5OT//+Dg///o6P//87O///4+P7/sbHy/25u4v9NTdD/Z2e8/2pqpv9JSZD/MTF5/zY2Y/9JSU3/ZGQ4/3x8Jf+NjRX/kZEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/paUH/6+vFf+3tyX/s7M4/5qaTf91dWP/Tk56/zQ0kP85Oaf/YmK8/3x80P9cXOP/X1/y/6en///5+f//+vr//9DQ//+amv//g4P//8nJ////////////////////////////////////////////////////////////////////////////////////////+Pj//7Ky//+Dg///o6P//8jI///4+P//2dn//4yM8/9ZWeP/X1/R/3Jyvf9TU6f/NzeR/zw8ev9QUGT/bm5O/4yMOf+goCb/paUV/52dCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/6WlB/+rqxX/oqIl/4uLOP9sbE3/SUlj/y8vev8yMpD/Vlan/3l5vP9kZND/Tk7j/4OD8v/i4v///v7//8vL//+env//fn7//66u///8/P///////////////////////////////////////////////////////////////////////////////////////8jI//+MjP//n5///87O///19f//8vL//6mp//9tbfP/XFzj/3V10f9dXb3/PT2n/0FBkf9WVnr/d3dk/5mZTv+trTn/ra0m/6amFf+cnAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+UlAf/jY0U/3p6Jf9iYjf/Q0NM/yoqYv8rK3j/S0uP/3Fxpv9ubrv/SEjQ/2Bg4v+1tfH////+/9TU//+env//gID//42N///g4P/////////////r6////f3//////////////////////////////////////////////////////////////Pz//87O//+YmP//pqb//9PT///5+f///f3//8HB//+Bgf7/X1/y/3V14v9lZdD/RES8/0hIpv9dXZD/f395/6WlYv+3t03/tLQ4/6ysJf+lpRX/mJgHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/bm4G/2dnE/9UVCP/Ojo2/yUlSv8lJWD/QEB2/2Rkjf90dKP/UVG5/0ZGzf+BgeD/5ubv//Dw/f+kpP//gID//319//+rq///+/v////////p6f//x8f///7+////////////////////////////////////////////////////////9/f//83N//+urv//v7///+Tk///+/v///////9DQ//+QkP//aGj9/3Z28P9paeD/SkrO/1BQuv9lZaT/iYmO/7Gxd//BwWH/urpL/62tNv+bmyT/iooU/3x8BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/0lJBf9CQhL/Ly8h/x8fM/8gIEf/ODhd/1dXc/9xcYr/YGCg/z4+tv9WVsr/qqrc//397f/Dw/r/g4P//3Z2//+EhP//x8f/////////////v7///76+/////////////////////////////////////////////////////////////+zs///c3P//6Oj///r6/////////v7//9bW//+YmP//cnL//3l5+v9pae3/UFDd/1payv9wcLb/lpah/76+iv/KynT/vLxe/6OjSP+IiDT/cnIi/2NjEv9XVwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8jIwT/Hx8Q/xcXH/8dHTD/MjJE/0xMWf9oaG//amqF/0lJnP87O7H/b2/G/9PT2P/w8On/m5v3/25u//95ef//h4f//9nZ////////8fH//6Ki///Bwf//////////////////////////////////////////////////////////////////////////////////8vL//8zM//+YmP//enr//3l5//9nZ/f/Wlrp/2ho2f9/f8b/p6ey/83NnP/Pz4b/tbVw/5OTWv90dEX/W1sx/0lJH/89PRD/MDAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/CAgC/xMTDf8dHRz/Ly8t/0REQP9dXVT/a2tq/1lZgP83N5b/RUWr/4yMwP/t7dP/0dHk/35+8v9lZf7/fHz//4aG///g4P///////+Hh//+Skv//vb3/////////////7Oz///j4///////////////////9/f//+/v///////////////////v7///t7f//0tL//6+v//+Pj///fHz//3V1//9oaP7/a2vy/3t75P+UlNP/v7/A/9vbrP/NzZb/p6eA/4CAav9gYFX/R0dA/zQ0Lf8nJxz/Hx8N/xERAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AkJAf8jIwr/Ly8Y/0BAKP9VVTv/ZWVP/2JiZP9GRnr/MDCP/1VVpf+qqrn/8/PM/7Cw3v9qau3/ZWX5/3p6//+Dg///3t7////////W1v//i4v//66u/////////////93d///Jyf//////////////////6+v//8rK///Bwf//wcH//7y8//+wsP//nZ3//4uL//+AgP//e3v//3h4//95ef//hIT5/5WV7f+0tN7/29vM/+Dguf++vqX/kZGQ/2trev9OTmT/ODhP/ygoO/8gICn/HBwY/xwcCuUJCQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/MDAH/z8/FP9QUCP/Xl41/2NjSP9TU13/NTVy/zIyiP9oaJ3/xMSx/+rqxP+Wltb/W1vm/2ho8/93d/7/fHz//9jY////////09P//4uL//+amv//8fH////////t7f//qKj//83N///////////////////m5v//wcH//6en//+YmP//kJD//4+P//+Tk///lZX//5aW//+cnP//p6f+/76+8//e3ub/6+vW/9HRxf+iorH/eXmd/1lZiP9AQHP/Ly9d/ycnSf8oKDb/LCwk/y4uFP8qKgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/z09BP9PTxD/WVke/15eL/9aWkH/Q0NV/yoqav87O3//fHyU/9bWqP/Z2bv/goLN/1FR3v9qauz/c3P4/3Jy///Nzf///////8/P//+QkP//h4f//9TU/////////v7//8DA//+Skv//yMj///j4//////////////39///t7f//3d3//9TU///R0f//zs7//8vL///R0f//3t7//+7u+P/w8Oz/1tbe/6qqzf+Cgrz/Y2Oo/0lJlP83N3//Ly9q/zMzVv86OkL/QEAv/0JCH/9CQhD/NjYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9ISAH/VlYL/1lZGP9bWyj/TEw6/zQ0Tf8lJWH/SEh1/5CQiv/f357/xsax/3Nzw/9JSdT/amrj/3Fx7/9nZ/r/urr////////Ozv//kpL//35+//+srP//9vb////////p6f//paX//4eH//+vr///3Nz///b2//////////////////////////////39///6+v//9fX//+Xl+v/IyO//o6Pj/4KC1P9nZ8P/T0+x/z4+nv84OIr/PT12/0dHYf9PT03/UlI6/1RUKf9TUxn/UVEL/kVFAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1dXBv9XVxP/UFAh/z4+Mv8nJ0T/KChX/1hYa/+hoX//4eGT/7a2pv9oaLj/Q0PJ/2ho2P9xceb/XFzx/6Ki+//9/f//1dX//5KS//97e///i4v//8zM/////////////9fX//+env//gYH//5CQ//+trf//x8f//9fX///d3f//3Nz//9XV///Kyv//urr//6Wl+/+OjvH/d3fm/2Ji2P9QUMn/RES4/0NDpv9LS5P/VVV//1tba/9eXlj/XV1E/1paMv9XVyL/VFQT/1NTBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9VVQL/T08N/0FBGv8vLyr/HR07/zExTf9nZ2D/rq5z/93dhv+oqJn/X1+r/z4+vP9kZMz/c3Pa/1NT5/+Hh/H/7Oz6/+Xl//+YmP//dnb//319//+Tk///19f////////8/P//0dH//6Ki//+Cgv//fn7//4eH//+MjP//jIz//4mJ//+EhP//e3v//3Bw+v9lZfH/Wlrn/1NT2v9RUcz/Vla9/15erP9lZZr/Z2eH/2ZmdP9iYmD/XV1N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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////////////////////////////////gAH///////wAAD//////8AAAD//////AAAAD/////wAAAAD////+AAAAAH////wAAAAAP///8AAAAAAP///gAAAAAAf//+AAAAAAB///wAAAAAAD//+AAAAAAAH//wAAAAAAAP//AAAAAAAA//4AAAAAAAB//gAAAAAAAH/8AAAAAAAAP/wAAAAAAAA/+AAAAAAAAB/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/gAAAAAAAAf+AAAAAAAAB/4AAAAAAAAH/wAAAAAAAA//AAAAAAAAD/+AAAAAAAAf/4AAAAAAAB//wAAAAAAAP//AAAAAAAA//+AAAAAAAH//8AAAAAAA///4AAAAAAH///gAAAAAAf///AAAAAAD////AAAAAA////+AAAAAH////8AAAAA/////8AAAAP/////8AAAD//////8AAA///////+AAf//////////////////////////////////////////////KAAAADAAAABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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/0ZGG/9CQjH/OTlK/y4uZf8uLoH/Q0Od/29vt/+urs//5OTk//X19f/r6///3d3//9ra///c3P//4uL///Ly//////////////Ly//+xsf//paX///f3///+/v//qKj//4uL///Y2P//7+///4eH//9ubvb/XFzl/3p60P/R0bj/ra2e/1BQgv8wMGb/TExL/1paMv9VVRv/Tk4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgWFgH/KSkO/yYmIv8iIjn/JCRU/zg4cP9iYo3/oaGp/+Dgw//u7tv/yMjv/56e/v+MjP//ior//4mJ//+IiP//kZH//6Gh///AwP//7e3////////9/f//vLz//9jY////////zMz//42N///b2///+Pj//5WV//9xcf//YmLv/5GR3P/d3cT/lJSq/0JCjv89PXH/W1tV/1tbOv9LSyL/ODgOzxkZAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPkGBgP/FhYS/x4eKP8wMEH/UlJd/4WFev/FxZf/6uqz/8jIzf+Li+T/amr3/29v//94eP//hYX//6am///Kyv//4eH//+fn///i4v//5+f///7+////////9/f//9/f////////4uL//5aW///g4P///Pz//5yc//9ycv//bW34/7Gx5f/b287/d3e0/zw8mP9RUXr/YmJd/1BQQv81NSj/ICAT9Q0NAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8jIwX/ODgW/05OLf9xcUf/oaFk/9XVgf/a2p//oKC7/2Vl1f9WVuv/cHD9/3l5//+Vlf//2Nj/////////////////////////////////////////////////////////////6Oj//6am///w8P//+fn//5ub//9ycv//g4P9/9XV7P/Dw9b/XV28/0REoP9jY4L/W1tk/zk5SP8iIi3/ICAX/x8fBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9ZWQf/cXEa/46OMf+0tEz/1dVp/8TEh/+FhaX/UFDB/05O2/91dfH/cnL//5KS///q6v////////n5///a2v//39//////////////////////////////////////////////5OT//8LC////////6Oj//4+P//96ev//qan//+rq8f+Zmdv/TEzC/1lZpv9mZoj/Q0Nq/ygoTP8uLjL/Q0Ma/0xMBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+Pjwn/pqYc/7y8NP/Kyk//r69t/3R0i/9DQ6n/RkbF/3V13/9qavT/f3///+Xl////////3d3//6Sk//+5uf//9PT/////////////////////////////////////////////6ur//+zs////////x8f//4GB//+Xl///3Nz//9fX9P9wcN//UFDG/2xsqv9QUIz/Ly9t/zg4UP9VVTT/c3Mc/39/CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+mpgn/trYd/7m5Nf+dnVH/aWlv/zs7jf88PKv/b2/H/2lp4f9lZfb/zMz////////S0v//jo7//66u///+/v/////////////////////////////////////////////////////////////y8v//mJj//5SU///Ozv//9PT//6Gh9v9ZWeH/aWnI/15erP84OI7/QEBv/2NjUf+Kijb/oaEd/5ycCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+jown/pKQd/4qKNf9fX1H/NDRv/zIyjf9jY6v/b2/H/1BQ4f+dnfb//v7//9DQ//+Pj///k5P///X1//////////////////////////////////////////////////////////////v7//+ysv//lJT//8/P///9/f//ysr//3Bw9v9mZuH/aWnI/0FBrP9HR47/bW1v/5ycUf+xsTb/rKwd/52dCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9/fwj/cnIc/1JSNP8uLk//KSlt/1NTi/9zc6n/Tk7F/2Vl3//f3/T/4+P//5KS//99ff//x8f////////v7///6en/////////////////////////////////////////////9PT//76+//+pqf//2tr////////h4f//iYn//2pq9P9tbd//SkrG/1BQqv94eIz/q6tt/729UP+ysjT/oaEc/4yMCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9KSgf/Pj4a/yQkMf8iIkz/Q0Np/21th/9dXaX/QkLB/5eX2//29vH/qKj//3Z2//+MjP//6ur////////Bwf//4uL/////////////////////////////////////////////4eH//9nZ///19f///////+fn//+YmP//c3P//25u8f9TU9v/XV3C/4iIpv+8vIj/x8dq/62tTP+MjDH/cXEa/1paBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8XFwX/GBgW/x0dLf85OUf/Xl5k/2pqgf9BQZ//UVG7/8nJ1f/e3uv/e3v9/3Jy//+YmP//+vr///X1//+hof//6Oj////////////////////////////////////////////////////////8/P//1tb//5qa//94eP//bW39/19f7P9xcdb/n5+8/87On//IyIL/nZ1k/3FxSP9RUS3/PDwX/yUlBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMBAQP/Hx8S/zQ0KP9SUkH/aGhd/1RUev80NJf/cHCz/+bmzf+1teT/ZWX3/3R0//+Xl////v7//+Xl//+Skv//4uL////////h4f//9vb/////////////6en//+Hh///l5f//39///8nJ//+np///iIj//3h4//9ycv//dXX3/5KS5f/Cws7/3Ny0/7u7l/+EhHr/V1dd/zg4Qf8lJSj/HBwT7AQEAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgnJwH/NTUN/0tLIv9gYDn/YGBU/zw8cP83N43/k5Op/+npw/+QkNv/XFzv/3R0/v+Pj///+vr//9/f//+Kiv//ysr////////b2///urr///z8////////7u7//8PD//+mpv//lpb//46O//+Njf//jo7//5OT//+kpP7/xsbv/+Li2//T08T/nJyp/2dnjf9CQnH/LCxU/yYmOv8oKCL/KCgO4CoqAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/R0cI/1lZG/9fXzH/Tk5K/ysrZf9GRoH/sLCd/9zct/91dc//WFjk/3Jy9v+AgP//8fH//93d//+Kiv//paX///7+///5+f//p6f//7Cw///x8f/////////////x8f//4uL//93d///a2v//3Nz//+bm///r6/b/2dnk/6ioz/90dLj/TU2d/zc3gv8zM2b/OztL/0NDMf9GRhv/Pj4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/Tk4E/1lZE/9UVCj/OTk/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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////////////////////wAP///////4AAH//////+AAAH//////wAAAP/////+AAAAf/////gAAAAf////8AAAAA/////wAAAAD////+AAAAAH////wAAAAAP///+AAAAAAf///4AAAAAB////AAAAAAD///8AAAAAAP///wAAAAAA///+AAAAAAB///4AAAAAAH///gAAAAAAf//+AAAAAAB///4AAAAAAH///gAAAAAAf//+AAAAAAB///4AAAAAAH///gAAAAAAf//+AAAAAAB///4AAAAAAH///gAAAAAAf///AAAAAAD///8AAAAAAP///wAAAAAA////gAAAAAH///+AAAAAAf///8AAAAAD////4AAAAAf////wAAAAD/////AAAAAP////+AAAAB/////+AAAAf/////8AAAD//////4AAAf//////4AAH///////8AD////////////////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//Pz//+8vP//b2///7q68v+xsdD/R0en/2Bgev9JSU7/IyMn/xsbCQAAAAAAAAAAAAAAAAAAAAD/amoM/5eXLP/Hx1X/vr6C/2hosP9TU9n/bW35/5yc///8/P//7Oz//9bW///8/P/////////////////////////////Y2P///////6Oj//+Njf//3t76/39/2f9XV7D/WFiD/y0tVv88PC3/VVUMAAAAAAAAAAAAAAAAAAAAAP+oqA3/v78v/6OjWf9TU4f/Rka0/2tr3f99ffz/8/P//9TU//+lpf//8PD////////////////////////////////////////h4f//kJD//9XV///Hx/3/YGDe/2Jitf87O4f/Tk5Z/4aGMP+ZmQ4AAAAAAAAAAAAAAAAAAAAA/52dDf+Ghi//RkZZ/zU1h/9pabT/Wlrd/8nJ/P/Y2P//i4v//+Li////////////////////////////////////////8PD//6mp///T0///7u7//4OD/f9mZt7/Skq1/1xch/+goFn/trYv/6KiDgAAAAAAAAAAAAAAAAAAAAD/U1MM/zU1LP8nJ1X/XV2C/1hYsP94eNn/5ub5/4mJ//+hof///////9jY//////////////////////////////39///Y2P//6+v///r6//+goP//a2v6/1dX2f9wcLD/traD/729Vf+Tkyz/a2sMAAAAAAAAAAAAAAAAAAAAAPsMDAj/Hx8m/0pKTf9mZnn/QECm/7Oz0P+9vfL/a2v//7q6///39///t7f////////9/f/////////////9/f////////v7///f3///nZ3//29v//9ra/L/lZXQ/8zMp/+xsXr/bW1O/z8/J/caGgkAAAAAAAAAAAAAAAAAAAAA7x8fBP9AQB7/Y2ND/0pKbf9KSpn/09PC/4yM5v9nZ///uLj//+zs//+hof///////9jY///x8f///////9jY//+4uP//pKT//5CQ//+MjP//oaH//8nJ5v/OzsL/jY2Z/0lJbf8rK0P/JCQe6R0dBAAAAAAAAAAAAAAAAAAAAADdV1cB/1ZWFP9aWjX/Ly9d/2lph//V1bD/bGzU/2Rk8v+kpP//6en//42N///h4f//7u7//6en///e3v///v7///Pz///p6f//5eX/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/5OT///v7///vr7//+3t/////////////////////////////9LS//+pqf//z8///2lp2f9QUJ//Q0Nh/3x8Kf+xsQQAAAAAAAAAAP+zswT/e3so/zs7YP9SUp7/ZmbY/9LS//+np///0tL/////////////////////////////7u7//8DA///r6///lZX//1lZ2f9ZWZ//oaFh/6+vKf+3twQAAAAAAAAAAPNnZwL/Hx8j/0FBWf9ZWZb/h4fQ/8HB/P+IiP//8PD//9nZ////////////////////////9PT///Dw//+oqP//Zmb8/3p60P+5uZf/paVa/15eJOlrawMAAAAAAAAAAAAAAAD/LS0a/19fTP9CQob/sbG//46O7/+Kiv//5eX//8DA///39///7u7////////Y2P//wMD//5+f//+Rkf//srLv/8HBwP9/f4f/OjpM/x0dGgAAAAAAAAAAAAAAAAAAAAD8SEgO/01NOf9DQ2//wMCn/2xs2P97e/z/2tr//52d///y8v//wMD//9DQ///s7P//4uL//9jY///Hx/z/mJjY/1pap/9DQ3D/S0s6/0BADgAAAAAAAAAAAAAAAAAAAADgSEgE/ygoI/9dXVP/vb2G/1lZt/9kZOD/yMj8/5GR//+np///7e3//6qq//+Kiv//i4v//319/P9lZeD/V1e3/1ZWh/9TU1P/UFAkyjY2BAAAAAAAAAAAAAAAAAAAAAAAAAAA/xMTDv+EhDP/sbFg/0lJjv9YWLf/lpbY/7S07/9kZPv/k5P//9DQ//+/v/v/kJDv/3Bw2P9dXbf/Tk6O/z4+Yf8uLjT8FBQOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiRcXAf+mphX/nZ05/zk5YP9ZWYb/VFSn/8XFv/95edD/WVnY/2ho2P+Hh9D/ra2//7u7p/+4uIb/ra1h/5ubOv95eRbFVVUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOypqQL/fX0V/ykpM/9VVVP/QUFv/3p6hv/AwJb/V1ee/09Pnv9YWJb/RkaG/0dHb/9eXlP/goI0/5ubFs+MjAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKFRUB+hMTDv9PTyP/TU05/zMzTP+mpln/pqZg/0FBYf8/P1n/XFxM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ErrorReports + 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online + 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go + AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAABAMAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAJ6SURBVDhPpZNZSJRRGIb/iy66skiNQjCyRCwQA0OqixClRU0pt0JJE8qtBUMx12ZcR50Z1ykVc0adRh2tLMWwQigUidByocTULCgqxdRQye3pn5Esy0Dw4rk5nPc57/k4RwCE9bCmcFqh1lmap+NaZjkZKj3xck3Cr0PXJAiKUuEVpggwhC7Flpkni7LoVLVPYFQhQp02x0+Ev2mqkSn+d7Xw+KIT3hEKPMNyjQL6uzXMjNca+TGmY25Ejb5cQb02NXk1iVe4Es/QPNxDChBqK5RMj1WtCC98LmKuN5Kqskwqi9NRFyZTkpvETXkcBbIY0fl78EK1Jpvpr5rlkxfFMP1x0BuyKv8IbpfK+P6pxFjbEG6uyzbyUJ9Fg07Gvcp09OoUdCVSylXXuZWXQLEyDlV2rLGNUFmSzuT7fAy1h1ol1FcpWRiSwnDqSt6Ja4Px0BcFryPoqvHlSrBHhaBWpTAxkAkf5NyvVtD+SL606c8rvAqEDm/m29yYeezE2wo7Lp49OiEK9gqleRK+vZEy3JZIvU7O4qAEekKXBF3BYvA08+0ezLQcYbLpEKN1+5CGWXPGxSzXMEyhSJnIaHcsd7TZdD4Tm/RFioLz0BnA4vOTzD49zlTzYcbqHfhSbUtH2mb83axwsDGxMQpUWXH0tCRxV5sFA0nwUqz7wofZVnemnjgx3uDIiN6Oj5rd9MpNkYTswn6PRePyU87PiKG2PJPeB+F05AjLtMs30CLbSGPyJmoSzFHHWHDj6g78XXcuOtpbui4LclKjyZFcICM2iPjLfobJcs7XhVPHDuB80I79dtbYWllgud2MrVtM2GZqsvIhrecrG7I/Ab0P0TsVu2j5AAAAAElFTkSuQmCCCw==bad + AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA9QEAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAFrSURBVDhPzZM/SAJhGMZdChqirTFaWiIhKIqWCIJwcgkhoUkIgjQagjIqsLK/HJQoRWJmEkopHlhG2R9qsBISocIQF40MChG8NM8rnu5zu4YLcqjhG3+/53ufl1cCQFLKKwkmwf9MQHk/sUgXMOvOQedkMGFPY3TjBcPmZwytPmLQlMh870swAoHv4x+4S3AIPBRweMPCG2ThCrCw+LOQKnZ4Xli6QECSo08cglGuCDov8ljz5bDiZTBnj6FOviUuIN++jXM4CrHYPsvDyIN6ZwZj1leoqTBqutbFBWTmqwgH9yWffPCO+V0Cp6AxJqEYOUV1u0FcQArzh1nYTvJYpt+gtabRb0hCORODbGAPVS1L4gLStueahXE/i0l7CmoTD+ujkGtD6OxzoaJRLy4gq9o8ZjBti0BDhaAcP+eTPehQOdDaY0ZZg05cQPbcpvKhqZdGfbcDtTJLce7K5gWUS6d+FvzmqP7+Fr4AXCP0fk5gGmQAAAAASUVORK5CYIILAAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAOgEAAAKJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACxIAAAsSAdLdfvwAAACwSURBVDhPY/j//z8DJRir5rL92W7EGorVgIQZEf+JNQSnAZZl+kQZgtWAqClB/zfcWvyfGEOwGhA8wRtkO1GGYDXAp8sZbAAIRy0KwOsSrAY4Nlv8z9gQC9Zs26z/X6dY4b9EGjeIVkOPHawGmNfooWjmT2DBqhlkGFYD9EqV4Tbj04zTAJV8KbCzCWnGaYB0hiBRmnEaIJLMidPPRAUittDGlTcoyok4vUBsTgSpAwAk/cTp4tUWzAAAAABJRU5ErkJgggs= \ No newline at end of file diff --git a/Console/TeslaConsole.Properties/Resources.cs b/Console/TeslaConsole.Properties/Resources.cs new file mode 100644 index 0000000..bf9bfad --- /dev/null +++ b/Console/TeslaConsole.Properties/Resources.cs @@ -0,0 +1,306 @@ +using System.CodeDom.Compiler; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.Resources; +using System.Runtime.CompilerServices; + +namespace TeslaConsole.Properties; + +[DebuggerNonUserCode] +[CompilerGenerated] +[GeneratedCode("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] +internal class Resources +{ + private static ResourceManager resourceMan; + + private static CultureInfo resourceCulture; + + [EditorBrowsable(EditorBrowsableState.Advanced)] + internal static ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + ResourceManager resourceManager = (resourceMan = new ResourceManager("TeslaConsole.Properties.Resources", typeof(Resources).Assembly)); + } + return resourceMan; + } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + internal static CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + + internal static Bitmap Add + { + get + { + object @object = ResourceManager.GetObject("Add", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap Blank + { + get + { + object @object = ResourceManager.GetObject("Blank", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap DeleteHS + { + get + { + object @object = ResourceManager.GetObject("DeleteHS", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap Error + { + get + { + object @object = ResourceManager.GetObject("Error", resourceCulture); + return (Bitmap)@object; + } + } + + internal static string ErrorReportsDir => ResourceManager.GetString("ErrorReportsDir", resourceCulture); + + internal static Bitmap install + { + get + { + object @object = ResourceManager.GetObject("install", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap openHS + { + get + { + object @object = ResourceManager.GetObject("openHS", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap Play + { + get + { + object @object = ResourceManager.GetObject("Play", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodBad16 + { + get + { + object @object = ResourceManager.GetObject("PodBad16", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodBad32 + { + get + { + object @object = ResourceManager.GetObject("PodBad32", resourceCulture); + return (Bitmap)@object; + } + } + + internal static string PodBadImageKey => ResourceManager.GetString("PodBadImageKey", resourceCulture); + + internal static Bitmap PodGo16 + { + get + { + object @object = ResourceManager.GetObject("PodGo16", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodGo32 + { + get + { + object @object = ResourceManager.GetObject("PodGo32", resourceCulture); + return (Bitmap)@object; + } + } + + internal static string PodGoImageKey => ResourceManager.GetString("PodGoImageKey", resourceCulture); + + internal static Bitmap PodOffline16 + { + get + { + object @object = ResourceManager.GetObject("PodOffline16", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodOffline32 + { + get + { + object @object = ResourceManager.GetObject("PodOffline32", resourceCulture); + return (Bitmap)@object; + } + } + + internal static string PodOfflineImageKey => ResourceManager.GetString("PodOfflineImageKey", resourceCulture); + + internal static Bitmap PodOfflineQuestion32 + { + get + { + object @object = ResourceManager.GetObject("PodOfflineQuestion32", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodOnline16 + { + get + { + object @object = ResourceManager.GetObject("PodOnline16", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodOnline32 + { + get + { + object @object = ResourceManager.GetObject("PodOnline32", resourceCulture); + return (Bitmap)@object; + } + } + + internal static string PodOnlineImageKey => ResourceManager.GetString("PodOnlineImageKey", resourceCulture); + + internal static Bitmap PodQuestion16 + { + get + { + object @object = ResourceManager.GetObject("PodQuestion16", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodQuestion32 + { + get + { + object @object = ResourceManager.GetObject("PodQuestion32", resourceCulture); + return (Bitmap)@object; + } + } + + internal static string PodQuestionImageKey => ResourceManager.GetString("PodQuestionImageKey", resourceCulture); + + internal static Bitmap PodRun16 + { + get + { + object @object = ResourceManager.GetObject("PodRun16", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap PodRun32 + { + get + { + object @object = ResourceManager.GetObject("PodRun32", resourceCulture); + return (Bitmap)@object; + } + } + + internal static string PodRunImageKey => ResourceManager.GetString("PodRunImageKey", resourceCulture); + + internal static Bitmap Power + { + get + { + object @object = ResourceManager.GetObject("Power", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap RefreshDoc + { + get + { + object @object = ResourceManager.GetObject("RefreshDoc", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap saveHS + { + get + { + object @object = ResourceManager.GetObject("saveHS", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap square_throbber + { + get + { + object @object = ResourceManager.GetObject("square_throbber", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Bitmap Stop + { + get + { + object @object = ResourceManager.GetObject("Stop", resourceCulture); + return (Bitmap)@object; + } + } + + internal static Icon swirl + { + get + { + object @object = ResourceManager.GetObject("swirl", resourceCulture); + return (Icon)@object; + } + } + + internal static Bitmap WebRefreshHH + { + get + { + object @object = ResourceManager.GetObject("WebRefreshHH", resourceCulture); + return (Bitmap)@object; + } + } + + internal Resources() + { + } +} diff --git a/Console/TeslaConsole.Properties/Settings.cs b/Console/TeslaConsole.Properties/Settings.cs new file mode 100644 index 0000000..8205c42 --- /dev/null +++ b/Console/TeslaConsole.Properties/Settings.cs @@ -0,0 +1,14 @@ +using System.CodeDom.Compiler; +using System.Configuration; +using System.Runtime.CompilerServices; + +namespace TeslaConsole.Properties; + +[CompilerGenerated] +[GeneratedCode("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] +internal sealed class Settings : ApplicationSettingsBase +{ + private static Settings defaultInstance = (Settings)SettingsBase.Synchronized(new Settings()); + + public static Settings Default => defaultInstance; +} diff --git a/Console/TeslaConsole.RedPlanet/BoostEvent.cs b/Console/TeslaConsole.RedPlanet/BoostEvent.cs new file mode 100644 index 0000000..43d1dcd --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/BoostEvent.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class BoostEvent : MissionEvent +{ + public readonly int BoosterOn; + + public override IEnumerable InvolvedPilots => new RPPlayer[1] { ReportingPilot }; + + public BoostEvent(RPPlayer reportingPilot, TimeSpan missionTime, int boosterOn) + : base(reportingPilot, missionTime) + { + BoosterOn = boosterOn; + } + + private BoostEvent() + { + } +} diff --git a/Console/TeslaConsole.RedPlanet/DamagedEvent.cs b/Console/TeslaConsole.RedPlanet/DamagedEvent.cs new file mode 100644 index 0000000..7814b92 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/DamagedEvent.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class DamagedEvent : MissionEvent +{ + public readonly RPPlayer Damager; + + public readonly int DamageLoss; + + public readonly int PointsTransfered; + + public override IEnumerable InvolvedPilots => new RPPlayer[2] { ReportingPilot, Damager }; + + public DamagedEvent(RPPlayer reportingPilot, TimeSpan missionTime, RPPlayer damager, int damageLoss, int pointsTransfered) + : base(reportingPilot, missionTime) + { + Damager = damager; + DamageLoss = damageLoss; + PointsTransfered = pointsTransfered; + } + + private DamagedEvent() + { + } +} diff --git a/Console/TeslaConsole.RedPlanet/DeathRaceScenario.cs b/Console/TeslaConsole.RedPlanet/DeathRaceScenario.cs new file mode 100644 index 0000000..2fc5901 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/DeathRaceScenario.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +public class DeathRaceScenario : Scenario +{ + private Dictionary mColors = new Dictionary(); + + private Dictionary mBadges = new Dictionary(); + + public Dictionary Colors => mColors; + + public Dictionary Badges => mBadges; + + internal DeathRaceScenario(XmlNode scenarioNode) + : base(scenarioNode) + { + foreach (XmlNode childNode in scenarioNode.ChildNodes) + { + switch (childNode.Name) + { + case "color": + RPConfig.AddKeyNameNode(childNode, mColors); + break; + case "badge": + RPConfig.AddKeyNameNode(childNode, mBadges); + break; + } + } + } +} diff --git a/Console/TeslaConsole.RedPlanet/FootballScenario.cs b/Console/TeslaConsole.RedPlanet/FootballScenario.cs new file mode 100644 index 0000000..5ea7227 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/FootballScenario.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +public class FootballScenario : Scenario +{ + private Dictionary mTeams = new Dictionary(); + + private Dictionary mPositions = new Dictionary(); + + public Dictionary Teams => mTeams; + + public Dictionary Positions => mPositions; + + internal FootballScenario(XmlNode scenarioNode) + : base(scenarioNode) + { + foreach (XmlNode childNode in scenarioNode.ChildNodes) + { + switch (childNode.Name) + { + case "position": + RPConfig.AddKeyNameNode(childNode, mPositions); + break; + case "team": + { + RPTeam rPTeam = new RPTeam(childNode); + mTeams.Add(rPTeam.Key, rPTeam); + break; + } + } + } + } +} diff --git a/Console/TeslaConsole.RedPlanet/KilledEvent.cs b/Console/TeslaConsole.RedPlanet/KilledEvent.cs new file mode 100644 index 0000000..3e5cd50 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/KilledEvent.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class KilledEvent : MissionEvent +{ + public readonly RPPlayer Killer; + + public override IEnumerable InvolvedPilots => new RPPlayer[2] { ReportingPilot, Killer }; + + public KilledEvent(RPPlayer reportingPilot, TimeSpan missionTime, RPPlayer killer) + : base(reportingPilot, missionTime) + { + Killer = killer; + } + + private KilledEvent() + { + } +} diff --git a/Console/TeslaConsole.RedPlanet/MissionEvent.cs b/Console/TeslaConsole.RedPlanet/MissionEvent.cs new file mode 100644 index 0000000..73330ac --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/MissionEvent.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal abstract class MissionEvent +{ + public readonly TimeSpan GameTime; + + public readonly RPPlayer ReportingPilot; + + public abstract IEnumerable InvolvedPilots { get; } + + public MissionEvent(RPPlayer reportingPilot, TimeSpan missionTime) + { + ReportingPilot = reportingPilot; + GameTime = missionTime; + } + + protected MissionEvent() + { + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPCamera.cs b/Console/TeslaConsole.RedPlanet/RPCamera.cs new file mode 100644 index 0000000..5d54766 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPCamera.cs @@ -0,0 +1,28 @@ +using System; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class RPCamera : RPParticipant +{ + public RPCamera(string podHostAddress, HostType hostType) + : base(podHostAddress, hostType) + { + if (hostType != HostType.MissionReviewHostType) + { + throw new ArgumentOutOfRangeException("hostType"); + } + } + + protected sealed override void AppendParticipantSpecificData(int index, StringBuilder sb) + { + sb.Append("dropzone=one\n"); + sb.Append("name=Camera\n"); + sb.Append("loadzones=0\n"); + sb.Append("vehicle=camera\n"); + sb.Append("color=Black\n"); + sb.Append("badge=None\n"); + sb.Append("team=Camera\n"); + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPConfig.cs b/Console/TeslaConsole.RedPlanet/RPConfig.cs new file mode 100644 index 0000000..d491cfa --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPConfig.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +public static class RPConfig +{ + private static readonly DeathRaceScenario mRace; + + private static readonly FootballScenario mFootball; + + private static readonly Dictionary mMaps; + + private static readonly Dictionary mVehicles; + + private static readonly Dictionary mTimesOfDay; + + private static readonly Dictionary mWeather; + + public static DeathRaceScenario DeathRace => mRace; + + public static FootballScenario Football => mFootball; + + public static Dictionary Maps => mMaps; + + public static Dictionary Vehicles => mVehicles; + + public static Dictionary TimesOfDay => mTimesOfDay; + + public static Dictionary Weather => mWeather; + + static RPConfig() + { + mRace = null; + mFootball = null; + mMaps = new Dictionary(); + mVehicles = new Dictionary(); + mTimesOfDay = new Dictionary(); + mWeather = new Dictionary(); + XmlNode scenarioNode = null; + XmlNode scenarioNode2 = null; + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.Load(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "RedPlanet\\RPConfig.xml")); + foreach (XmlNode childNode in xmlDocument.DocumentElement.ChildNodes) + { + switch (childNode.Name) + { + case "race": + scenarioNode = childNode; + break; + case "football": + scenarioNode2 = childNode; + break; + case "map": + { + RPMap rPMap = new RPMap(childNode); + mMaps.Add(rPMap.Key, rPMap); + break; + } + case "vehicle": + { + RPVehicle rPVehicle = new RPVehicle(childNode); + mVehicles.Add(rPVehicle.Key, rPVehicle); + break; + } + case "time": + AddKeyNameNode(childNode, mTimesOfDay); + break; + case "weather": + AddKeyNameNode(childNode, mWeather); + break; + } + } + mRace = new DeathRaceScenario(scenarioNode); + mFootball = new FootballScenario(scenarioNode2); + } + + internal static void AddKeyNameNode(XmlNode node, Dictionary list) + { + list.Add(node.Attributes["key"].InnerText, node.Attributes["name"].InnerText); + } + + public static string MapNameOrKey(string mapKey) + { + if (!mMaps.ContainsKey(mapKey)) + { + return mapKey; + } + return mMaps[mapKey].Name; + } + + public static string VehicleNameOrKey(string vehicleKey) + { + if (!mVehicles.ContainsKey(vehicleKey)) + { + return vehicleKey; + } + return mVehicles[vehicleKey].Name; + } + + public static string TimeOfDayNameOrKey(string timeOfDayKey) + { + if (!mTimesOfDay.ContainsKey(timeOfDayKey)) + { + return timeOfDayKey; + } + return mTimesOfDay[timeOfDayKey]; + } + + public static string WeatherNameOrKey(string weatherKey) + { + if (!mWeather.ContainsKey(weatherKey)) + { + return weatherKey; + } + return mWeather[weatherKey]; + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPDefaults.cs b/Console/TeslaConsole.RedPlanet/RPDefaults.cs new file mode 100644 index 0000000..8ad21af --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPDefaults.cs @@ -0,0 +1,416 @@ +using System; +using System.IO; +using System.Windows.Forms; +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +internal static class RPDefaults +{ + public abstract class ScenarioDefaults + { + private bool mScoreCompression; + + private TimeSpan mMissionLength = new TimeSpan(0, 10, 0); + + private string mMapKey; + + private string mVehicleKey; + + private string mTimeOfDayKey; + + private string mWeatherKey; + + private int mLaunchDelay = 15; + + public bool ScoreCompression + { + get + { + return mScoreCompression; + } + set + { + mScoreCompression = value; + } + } + + public TimeSpan MissionLength + { + get + { + return mMissionLength; + } + set + { + mMissionLength = value; + } + } + + public string MapKey + { + get + { + return mMapKey; + } + set + { + mMapKey = value; + } + } + + public string VehicleKey + { + get + { + return mVehicleKey; + } + set + { + mVehicleKey = value; + } + } + + public string TimeOfDayKey + { + get + { + return mTimeOfDayKey; + } + set + { + mTimeOfDayKey = value; + } + } + + public string WeatherKey + { + get + { + return mWeatherKey; + } + set + { + mWeatherKey = value; + } + } + + public int LaunchDelay + { + get + { + return mLaunchDelay; + } + set + { + mLaunchDelay = value; + } + } + + public ScenarioDefaults() + { + } + + public abstract void Save(XmlNode parrentNode); + + protected void SaveCommonData(XmlNode scenarioNode) + { + scenarioNode.AppendChild(scenarioNode.OwnerDocument.CreateElement("ScoreCompression")).InnerText = mScoreCompression.ToString(); + scenarioNode.AppendChild(scenarioNode.OwnerDocument.CreateElement("MissionLength")).InnerText = mMissionLength.ToString(); + if (!string.IsNullOrEmpty(mMapKey)) + { + scenarioNode.AppendChild(scenarioNode.OwnerDocument.CreateElement("MapKey")).InnerText = mMapKey; + } + if (!string.IsNullOrEmpty(mVehicleKey)) + { + scenarioNode.AppendChild(scenarioNode.OwnerDocument.CreateElement("VehicleKey")).InnerText = mVehicleKey; + } + if (!string.IsNullOrEmpty(mTimeOfDayKey)) + { + scenarioNode.AppendChild(scenarioNode.OwnerDocument.CreateElement("TimeOfDayKey")).InnerText = mTimeOfDayKey; + } + if (!string.IsNullOrEmpty(mWeatherKey)) + { + scenarioNode.AppendChild(scenarioNode.OwnerDocument.CreateElement("WeatherKey")).InnerText = mWeatherKey; + } + scenarioNode.AppendChild(scenarioNode.OwnerDocument.CreateElement("LaunchDelay")).InnerText = mLaunchDelay.ToString(); + } + + public abstract void Parse(XmlNode scenarioNode); + + protected bool ParseCommonData(XmlNode node) + { + switch (node.Name) + { + case "ScoreCompression": + { + if (bool.TryParse(node.InnerText, out var result2)) + { + mScoreCompression = result2; + } + return true; + } + case "MissionLength": + { + if (TimeSpan.TryParse(node.InnerText, out var result3)) + { + mMissionLength = result3; + } + return true; + } + case "MapKey": + if (!string.IsNullOrEmpty(node.InnerText)) + { + mMapKey = node.InnerText; + } + return true; + case "VehicleKey": + if (!string.IsNullOrEmpty(node.InnerText)) + { + mVehicleKey = node.InnerText; + } + return true; + case "TimeOfDayKey": + if (!string.IsNullOrEmpty(node.InnerText)) + { + mTimeOfDayKey = node.InnerText; + } + return true; + case "WeatherKey": + if (!string.IsNullOrEmpty(node.InnerText)) + { + mWeatherKey = node.InnerText; + } + return true; + case "LaunchDelay": + { + if (int.TryParse(node.InnerText, out var result)) + { + mLaunchDelay = result; + } + return true; + } + default: + return false; + } + } + } + + public class DeathRaceDefaults : ScenarioDefaults + { + public const string DeathRaceNode = "DeathRaceDefaults"; + + private string mColorKey; + + private string mBadgeKey; + + public string ColorKey + { + get + { + return mColorKey; + } + set + { + mColorKey = value; + } + } + + public string BadgeKey + { + get + { + return mBadgeKey; + } + set + { + mBadgeKey = value; + } + } + + public override void Save(XmlNode parrentNode) + { + XmlNode xmlNode = parrentNode.AppendChild(parrentNode.OwnerDocument.CreateElement("DeathRaceDefaults")); + SaveCommonData(xmlNode); + if (!string.IsNullOrEmpty(mColorKey)) + { + xmlNode.AppendChild(xmlNode.OwnerDocument.CreateElement("ColorKey")).InnerText = mColorKey; + } + if (!string.IsNullOrEmpty(mBadgeKey)) + { + xmlNode.AppendChild(xmlNode.OwnerDocument.CreateElement("BadgeKey")).InnerText = mBadgeKey; + } + } + + public override void Parse(XmlNode scenarioNode) + { + foreach (XmlNode childNode in scenarioNode.ChildNodes) + { + if (ParseCommonData(childNode)) + { + continue; + } + switch (childNode.Name) + { + case "ColorKey": + if (!string.IsNullOrEmpty(childNode.InnerText)) + { + mColorKey = childNode.InnerText; + } + break; + case "BadgeKey": + if (!string.IsNullOrEmpty(childNode.InnerText)) + { + mBadgeKey = childNode.InnerText; + } + break; + } + } + } + } + + public class FootballDefaults : ScenarioDefaults + { + public const string FootballNode = "FootballDefaults"; + + private string mTeamKey; + + private string mPositionKey; + + public string TeamKey + { + get + { + return mTeamKey; + } + set + { + mTeamKey = value; + } + } + + public string PositionKey + { + get + { + return mPositionKey; + } + set + { + mPositionKey = value; + } + } + + public override void Save(XmlNode parrentNode) + { + XmlNode xmlNode = parrentNode.AppendChild(parrentNode.OwnerDocument.CreateElement("FootballDefaults")); + SaveCommonData(xmlNode); + if (!string.IsNullOrEmpty(mTeamKey)) + { + xmlNode.AppendChild(xmlNode.OwnerDocument.CreateElement("TeamKey")).InnerText = mTeamKey; + } + if (!string.IsNullOrEmpty(mPositionKey)) + { + xmlNode.AppendChild(xmlNode.OwnerDocument.CreateElement("PositionKey")).InnerText = mPositionKey; + } + } + + public override void Parse(XmlNode scenarioNode) + { + foreach (XmlNode childNode in scenarioNode.ChildNodes) + { + if (ParseCommonData(childNode)) + { + continue; + } + switch (childNode.Name) + { + case "TeamKey": + if (!string.IsNullOrEmpty(childNode.InnerText)) + { + mTeamKey = childNode.InnerText; + } + break; + case "PositionKey": + if (!string.IsNullOrEmpty(childNode.InnerText)) + { + mPositionKey = childNode.InnerText; + } + break; + } + } + } + } + + private static readonly string sDefaultsFilePath; + + private static readonly DeathRaceDefaults sDeathRace; + + private static readonly FootballDefaults sFootball; + + public static DeathRaceDefaults DeathRace => sDeathRace; + + public static FootballDefaults Football => sFootball; + + static RPDefaults() + { + sDefaultsFilePath = Path.Combine(Program.GetCommonAppDataDirectory(), "RPDefaults.rpd"); + sDeathRace = new DeathRaceDefaults(); + sFootball = new FootballDefaults(); + if (File.Exists(sDefaultsFilePath)) + { + try + { + Import(sDefaultsFilePath); + } + catch (Exception) + { + MessageBox.Show("The Red Planet defaults file appears to be corrupt. Please open the Red Planet Defaults Dialog and save to create a new defaults file.", "Error Loading Red Planet Defaults!", MessageBoxButtons.OK); + } + } + } + + public static void Import(string fileName) + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.Load(fileName); + foreach (XmlNode childNode in xmlDocument.DocumentElement.ChildNodes) + { + switch (childNode.Name) + { + case "DeathRaceDefaults": + sDeathRace.Parse(childNode); + break; + case "FootballDefaults": + sFootball.Parse(childNode); + break; + } + } + } + + public static void Save() + { + try + { + Export(sDefaultsFilePath); + } + catch (Exception) + { + MessageBox.Show("The Red Planet defaults file could not be saved. These defaults will only be remembered until the application is closed.", "Error Saving Red Planet Defaults!", MessageBoxButtons.OK); + } + } + + public static void Export(string filePath) + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.AppendChild(xmlDocument.CreateElement("RPDefaults")); + sDeathRace.Save(xmlDocument.DocumentElement); + sFootball.Save(xmlDocument.DocumentElement); + string directoryName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + xmlDocument.Save(filePath); + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPDefaultsDialog.cs b/Console/TeslaConsole.RedPlanet/RPDefaultsDialog.cs new file mode 100644 index 0000000..c2cf8f8 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPDefaultsDialog.cs @@ -0,0 +1,619 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +using TeslaConsole.Properties; + +namespace TeslaConsole.RedPlanet; + +public class RPDefaultsDialog : Form +{ + private IContainer components; + + private TableLayoutPanel mMainTable; + + private Label label2; + + private Label label3; + + private Label label4; + + private Label label5; + + private Label label6; + + private Label label7; + + private Label label8; + + private Label label9; + + private Label label1; + + private Label label10; + + private ComboBox mDeathRaceScoreCompression; + + private ComboBox mFootballScoreCompression; + + private ComboBox mFootballPosition; + + private ComboBox mDeathRaceBadge; + + private ComboBox mFootballTeam; + + private ComboBox mDeathRaceColor; + + private ComboBox mFootballWeather; + + private ComboBox mDeathRaceWeather; + + private ComboBox mFootballTimeOfDay; + + private ComboBox mDeathRaceTimeOfDay; + + private ComboBox mDeathRaceMap; + + private ComboBox mFootballMap; + + private ComboBox mDeathRaceVehicle; + + private ComboBox mFootballVehicle; + + private TableLayoutPanel mButtonsTable; + + private Button mCancel; + + private Button mOK; + + private MaskedTextBox mDeathRaceMissionLength; + + private MaskedTextBox mFootballMissionLength; + + private Label label11; + + private NumericUpDown mDeathraceLaunchDelay; + + private NumericUpDown mFootballLaunchDelay; + + private static RPDefaultsDialog mSingletonDialog; + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.mMainTable = new System.Windows.Forms.TableLayoutPanel(); + this.mFootballPosition = new System.Windows.Forms.ComboBox(); + this.mDeathRaceBadge = new System.Windows.Forms.ComboBox(); + this.mFootballTeam = new System.Windows.Forms.ComboBox(); + this.mDeathRaceColor = new System.Windows.Forms.ComboBox(); + this.label8 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.mButtonsTable = new System.Windows.Forms.TableLayoutPanel(); + this.mCancel = new System.Windows.Forms.Button(); + this.mOK = new System.Windows.Forms.Button(); + this.label5 = new System.Windows.Forms.Label(); + this.mDeathRaceVehicle = new System.Windows.Forms.ComboBox(); + this.mFootballVehicle = new System.Windows.Forms.ComboBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.mDeathRaceMap = new System.Windows.Forms.ComboBox(); + this.mDeathRaceTimeOfDay = new System.Windows.Forms.ComboBox(); + this.mDeathRaceScoreCompression = new System.Windows.Forms.ComboBox(); + this.mDeathRaceWeather = new System.Windows.Forms.ComboBox(); + this.mDeathRaceMissionLength = new System.Windows.Forms.MaskedTextBox(); + this.mFootballMap = new System.Windows.Forms.ComboBox(); + this.mFootballTimeOfDay = new System.Windows.Forms.ComboBox(); + this.mFootballScoreCompression = new System.Windows.Forms.ComboBox(); + this.mFootballMissionLength = new System.Windows.Forms.MaskedTextBox(); + this.mFootballWeather = new System.Windows.Forms.ComboBox(); + this.label11 = new System.Windows.Forms.Label(); + this.mDeathraceLaunchDelay = new System.Windows.Forms.NumericUpDown(); + this.mFootballLaunchDelay = new System.Windows.Forms.NumericUpDown(); + this.mMainTable.SuspendLayout(); + this.mButtonsTable.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)this.mDeathraceLaunchDelay).BeginInit(); + ((System.ComponentModel.ISupportInitialize)this.mFootballLaunchDelay).BeginInit(); + base.SuspendLayout(); + this.mMainTable.ColumnCount = 3; + this.mMainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50f)); + this.mMainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50f)); + this.mMainTable.Controls.Add(this.mFootballLaunchDelay, 2, 9); + this.mMainTable.Controls.Add(this.mFootballPosition, 2, 8); + this.mMainTable.Controls.Add(this.mDeathRaceBadge, 1, 8); + this.mMainTable.Controls.Add(this.mFootballTeam, 2, 7); + this.mMainTable.Controls.Add(this.mDeathRaceColor, 1, 7); + this.mMainTable.Controls.Add(this.label8, 0, 7); + this.mMainTable.Controls.Add(this.label9, 0, 8); + this.mMainTable.Controls.Add(this.label1, 1, 0); + this.mMainTable.Controls.Add(this.label10, 2, 0); + this.mMainTable.Controls.Add(this.mButtonsTable, 0, 10); + this.mMainTable.Controls.Add(this.label5, 0, 6); + this.mMainTable.Controls.Add(this.mDeathRaceVehicle, 1, 6); + this.mMainTable.Controls.Add(this.mFootballVehicle, 2, 6); + this.mMainTable.Controls.Add(this.label4, 0, 1); + this.mMainTable.Controls.Add(this.label7, 0, 2); + this.mMainTable.Controls.Add(this.label2, 0, 4); + this.mMainTable.Controls.Add(this.label6, 0, 3); + this.mMainTable.Controls.Add(this.label3, 0, 5); + this.mMainTable.Controls.Add(this.mDeathRaceMap, 1, 1); + this.mMainTable.Controls.Add(this.mDeathRaceTimeOfDay, 1, 3); + this.mMainTable.Controls.Add(this.mDeathRaceScoreCompression, 1, 4); + this.mMainTable.Controls.Add(this.mDeathRaceWeather, 1, 2); + this.mMainTable.Controls.Add(this.mDeathRaceMissionLength, 1, 5); + this.mMainTable.Controls.Add(this.mFootballMap, 2, 1); + this.mMainTable.Controls.Add(this.mFootballTimeOfDay, 2, 3); + this.mMainTable.Controls.Add(this.mFootballScoreCompression, 2, 4); + this.mMainTable.Controls.Add(this.mFootballMissionLength, 2, 5); + this.mMainTable.Controls.Add(this.mFootballWeather, 2, 2); + this.mMainTable.Controls.Add(this.label11, 0, 9); + this.mMainTable.Controls.Add(this.mDeathraceLaunchDelay, 1, 9); + this.mMainTable.Dock = System.Windows.Forms.DockStyle.Fill; + this.mMainTable.Location = new System.Drawing.Point(3, 3); + this.mMainTable.Name = "mMainTable"; + this.mMainTable.RowCount = 11; + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMainTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.mMainTable.Size = new System.Drawing.Size(478, 298); + this.mMainTable.TabIndex = 0; + this.mFootballPosition.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballPosition.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mFootballPosition.FormattingEnabled = true; + this.mFootballPosition.Location = new System.Drawing.Point(302, 204); + this.mFootballPosition.Name = "mFootballPosition"; + this.mFootballPosition.Size = new System.Drawing.Size(173, 21); + this.mFootballPosition.TabIndex = 15; + this.mDeathRaceBadge.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceBadge.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDeathRaceBadge.FormattingEnabled = true; + this.mDeathRaceBadge.Location = new System.Drawing.Point(123, 204); + this.mDeathRaceBadge.Name = "mDeathRaceBadge"; + this.mDeathRaceBadge.Size = new System.Drawing.Size(173, 21); + this.mDeathRaceBadge.TabIndex = 14; + this.mFootballTeam.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballTeam.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mFootballTeam.FormattingEnabled = true; + this.mFootballTeam.Location = new System.Drawing.Point(302, 177); + this.mFootballTeam.Name = "mFootballTeam"; + this.mFootballTeam.Size = new System.Drawing.Size(173, 21); + this.mFootballTeam.TabIndex = 13; + this.mDeathRaceColor.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceColor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDeathRaceColor.FormattingEnabled = true; + this.mDeathRaceColor.Location = new System.Drawing.Point(123, 177); + this.mDeathRaceColor.Name = "mDeathRaceColor"; + this.mDeathRaceColor.Size = new System.Drawing.Size(173, 21); + this.mDeathRaceColor.TabIndex = 12; + this.label8.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(51, 181); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(66, 13); + this.label8.TabIndex = 7; + this.label8.Text = "Color/Team:"; + this.label9.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(34, 208); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(83, 13); + this.label9.TabIndex = 8; + this.label9.Text = "Badge/Position:"; + this.label1.Anchor = System.Windows.Forms.AnchorStyles.None; + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(177, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(65, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Death Race"; + this.label10.Anchor = System.Windows.Forms.AnchorStyles.None; + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(366, 0); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(44, 13); + this.label10.TabIndex = 9; + this.label10.Text = "Football"; + this.mButtonsTable.ColumnCount = 2; + this.mMainTable.SetColumnSpan(this.mButtonsTable, 3); + this.mButtonsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.mButtonsTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mButtonsTable.Controls.Add(this.mCancel, 1, 0); + this.mButtonsTable.Controls.Add(this.mOK, 0, 0); + this.mButtonsTable.Dock = System.Windows.Forms.DockStyle.Fill; + this.mButtonsTable.Location = new System.Drawing.Point(0, 254); + this.mButtonsTable.Margin = new System.Windows.Forms.Padding(0); + this.mButtonsTable.Name = "mButtonsTable"; + this.mButtonsTable.RowCount = 1; + this.mButtonsTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.mButtonsTable.Size = new System.Drawing.Size(478, 44); + this.mButtonsTable.TabIndex = 27; + this.mCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.mCancel.Location = new System.Drawing.Point(400, 19); + this.mCancel.Name = "mCancel"; + this.mCancel.Size = new System.Drawing.Size(75, 22); + this.mCancel.TabIndex = 1; + this.mCancel.Text = "&Cancel"; + this.mCancel.UseVisualStyleBackColor = true; + this.mCancel.Click += new System.EventHandler(mCancel_Click); + this.mOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.mOK.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.mOK.Location = new System.Drawing.Point(319, 19); + this.mOK.Name = "mOK"; + this.mOK.Size = new System.Drawing.Size(75, 22); + this.mOK.TabIndex = 0; + this.mOK.Text = "&OK"; + this.mOK.UseVisualStyleBackColor = true; + this.mOK.Click += new System.EventHandler(mOK_Click); + this.label5.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(72, 154); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(45, 13); + this.label5.TabIndex = 4; + this.label5.Text = "Vehicle:"; + this.mDeathRaceVehicle.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceVehicle.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDeathRaceVehicle.FormattingEnabled = true; + this.mDeathRaceVehicle.Location = new System.Drawing.Point(123, 150); + this.mDeathRaceVehicle.Name = "mDeathRaceVehicle"; + this.mDeathRaceVehicle.Size = new System.Drawing.Size(173, 21); + this.mDeathRaceVehicle.TabIndex = 10; + this.mFootballVehicle.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballVehicle.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mFootballVehicle.FormattingEnabled = true; + this.mFootballVehicle.Location = new System.Drawing.Point(302, 150); + this.mFootballVehicle.Name = "mFootballVehicle"; + this.mFootballVehicle.Size = new System.Drawing.Size(173, 21); + this.mFootballVehicle.TabIndex = 11; + this.label4.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(86, 20); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(31, 13); + this.label4.TabIndex = 3; + this.label4.Text = "Map:"; + this.label7.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(66, 47); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(51, 13); + this.label7.TabIndex = 6; + this.label7.Text = "Weather:"; + this.label2.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(16, 101); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(101, 13); + this.label2.TabIndex = 1; + this.label2.Text = "Score Compression:"; + this.label6.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(48, 74); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(69, 13); + this.label6.TabIndex = 5; + this.label6.Text = "Time Of Day:"; + this.label3.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(36, 127); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(81, 13); + this.label3.TabIndex = 2; + this.label3.Text = "Mission Length:"; + this.mDeathRaceMap.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceMap.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDeathRaceMap.FormattingEnabled = true; + this.mDeathRaceMap.Location = new System.Drawing.Point(123, 16); + this.mDeathRaceMap.Name = "mDeathRaceMap"; + this.mDeathRaceMap.Size = new System.Drawing.Size(173, 21); + this.mDeathRaceMap.TabIndex = 0; + this.mDeathRaceTimeOfDay.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceTimeOfDay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDeathRaceTimeOfDay.FormattingEnabled = true; + this.mDeathRaceTimeOfDay.Location = new System.Drawing.Point(123, 70); + this.mDeathRaceTimeOfDay.Name = "mDeathRaceTimeOfDay"; + this.mDeathRaceTimeOfDay.Size = new System.Drawing.Size(173, 21); + this.mDeathRaceTimeOfDay.TabIndex = 4; + this.mDeathRaceScoreCompression.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceScoreCompression.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDeathRaceScoreCompression.FormattingEnabled = true; + this.mDeathRaceScoreCompression.Location = new System.Drawing.Point(123, 97); + this.mDeathRaceScoreCompression.Name = "mDeathRaceScoreCompression"; + this.mDeathRaceScoreCompression.Size = new System.Drawing.Size(173, 21); + this.mDeathRaceScoreCompression.TabIndex = 6; + this.mDeathRaceWeather.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceWeather.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mDeathRaceWeather.FormattingEnabled = true; + this.mDeathRaceWeather.Location = new System.Drawing.Point(123, 43); + this.mDeathRaceWeather.Name = "mDeathRaceWeather"; + this.mDeathRaceWeather.Size = new System.Drawing.Size(173, 21); + this.mDeathRaceWeather.TabIndex = 2; + this.mDeathRaceMissionLength.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mDeathRaceMissionLength.Location = new System.Drawing.Point(123, 124); + this.mDeathRaceMissionLength.Name = "mDeathRaceMissionLength"; + this.mDeathRaceMissionLength.Size = new System.Drawing.Size(173, 20); + this.mDeathRaceMissionLength.TabIndex = 8; + this.mFootballMap.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballMap.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mFootballMap.FormattingEnabled = true; + this.mFootballMap.Location = new System.Drawing.Point(302, 16); + this.mFootballMap.Name = "mFootballMap"; + this.mFootballMap.Size = new System.Drawing.Size(173, 21); + this.mFootballMap.TabIndex = 1; + this.mFootballTimeOfDay.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballTimeOfDay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mFootballTimeOfDay.FormattingEnabled = true; + this.mFootballTimeOfDay.Location = new System.Drawing.Point(302, 70); + this.mFootballTimeOfDay.Name = "mFootballTimeOfDay"; + this.mFootballTimeOfDay.Size = new System.Drawing.Size(173, 21); + this.mFootballTimeOfDay.TabIndex = 5; + this.mFootballScoreCompression.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballScoreCompression.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mFootballScoreCompression.FormattingEnabled = true; + this.mFootballScoreCompression.Location = new System.Drawing.Point(302, 97); + this.mFootballScoreCompression.Name = "mFootballScoreCompression"; + this.mFootballScoreCompression.Size = new System.Drawing.Size(173, 21); + this.mFootballScoreCompression.TabIndex = 7; + this.mFootballMissionLength.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballMissionLength.Location = new System.Drawing.Point(302, 124); + this.mFootballMissionLength.Name = "mFootballMissionLength"; + this.mFootballMissionLength.Size = new System.Drawing.Size(173, 20); + this.mFootballMissionLength.TabIndex = 9; + this.mFootballWeather.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFootballWeather.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mFootballWeather.FormattingEnabled = true; + this.mFootballWeather.Location = new System.Drawing.Point(302, 43); + this.mFootballWeather.Name = "mFootballWeather"; + this.mFootballWeather.Size = new System.Drawing.Size(173, 21); + this.mFootballWeather.TabIndex = 3; + this.label11.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(3, 228); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(114, 26); + this.label11.TabIndex = 28; + this.label11.Text = "Autotranslocate Delay:"; + this.label11.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + this.mDeathraceLaunchDelay.Dock = System.Windows.Forms.DockStyle.Fill; + this.mDeathraceLaunchDelay.Location = new System.Drawing.Point(123, 231); + this.mDeathraceLaunchDelay.Name = "mDeathraceLaunchDelay"; + this.mDeathraceLaunchDelay.Size = new System.Drawing.Size(173, 20); + this.mDeathraceLaunchDelay.TabIndex = 31; + this.mFootballLaunchDelay.Dock = System.Windows.Forms.DockStyle.Fill; + this.mFootballLaunchDelay.Location = new System.Drawing.Point(302, 231); + this.mFootballLaunchDelay.Name = "mFootballLaunchDelay"; + this.mFootballLaunchDelay.Size = new System.Drawing.Size(173, 20); + this.mFootballLaunchDelay.TabIndex = 32; + base.AcceptButton = this.mOK; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(484, 304); + base.Controls.Add(this.mMainTable); + base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + base.MaximizeBox = false; + base.Name = "RPDefaultsDialog"; + base.Padding = new System.Windows.Forms.Padding(3); + this.Text = "Red Planet Defaults"; + this.mMainTable.ResumeLayout(false); + this.mMainTable.PerformLayout(); + this.mButtonsTable.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)this.mDeathraceLaunchDelay).EndInit(); + ((System.ComponentModel.ISupportInitialize)this.mFootballLaunchDelay).EndInit(); + base.ResumeLayout(false); + } + + public static void ShowSingleton() + { + if (mSingletonDialog == null || mSingletonDialog.IsDisposed) + { + mSingletonDialog = new RPDefaultsDialog(); + } + mSingletonDialog.Show(); + mSingletonDialog.Focus(); + mSingletonDialog.WindowState = FormWindowState.Normal; + } + + private RPDefaultsDialog() + { + InitializeComponent(); + base.Icon = Resources.swirl; + BuildScoreCompressionOptions(mDeathRaceScoreCompression, RPDefaults.DeathRace.ScoreCompression); + BuildScoreCompressionOptions(mFootballScoreCompression, RPDefaults.Football.ScoreCompression); + BuildMissionLengthControl(mDeathRaceMissionLength, RPDefaults.DeathRace.MissionLength); + BuildMissionLengthControl(mFootballMissionLength, RPDefaults.Football.MissionLength); + BuildMapOption(mDeathRaceMap, RPConfig.DeathRace.Maps, RPDefaults.DeathRace.MapKey); + BuildMapOption(mFootballMap, RPConfig.Football.Maps, RPDefaults.Football.MapKey); + BuildGenericOption(mDeathRaceTimeOfDay, RPConfig.DeathRace.TimesOfDay, RPDefaults.DeathRace.TimeOfDayKey); + BuildGenericOption(mFootballTimeOfDay, RPConfig.Football.TimesOfDay, RPDefaults.Football.TimeOfDayKey); + BuildGenericOption(mDeathRaceWeather, RPConfig.DeathRace.Weather, RPDefaults.DeathRace.WeatherKey); + BuildGenericOption(mFootballWeather, RPConfig.Football.Weather, RPDefaults.Football.WeatherKey); + BuildVehicleOption(mDeathRaceVehicle, RPConfig.DeathRace.Vehicles, RPDefaults.DeathRace.VehicleKey); + BuildVehicleOption(mFootballVehicle, RPConfig.Football.Vehicles, RPDefaults.Football.VehicleKey); + BuildGenericOption(mDeathRaceColor, RPConfig.DeathRace.Colors, RPDefaults.DeathRace.ColorKey); + BuildTeamOption(mFootballTeam, RPConfig.Football.Teams, RPDefaults.Football.TeamKey); + BuildGenericOption(mDeathRaceBadge, RPConfig.DeathRace.Badges, RPDefaults.DeathRace.BadgeKey); + BuildGenericOption(mFootballPosition, RPConfig.Football.Positions, RPDefaults.Football.PositionKey); + BuildDelayControl(mDeathraceLaunchDelay, RPDefaults.DeathRace.LaunchDelay); + BuildDelayControl(mFootballLaunchDelay, RPDefaults.Football.LaunchDelay); + } + + private static void BuildScoreCompressionOptions(ComboBox comboBox, bool defaultValue) + { + comboBox.DisplayMember = "Value"; + comboBox.ValueMember = "Key"; + comboBox.Items.Add(new KeyValuePair(key: false, "Off")); + comboBox.Items.Add(new KeyValuePair(key: true, "On")); + comboBox.SelectedIndex = (defaultValue ? 1 : 0); + } + + private static void BuildMissionLengthControl(MaskedTextBox maskedTextBox, TimeSpan defaultValue) + { + maskedTextBox.Mask = "00:00:00"; + maskedTextBox.ValidatingType = typeof(TimeSpan); + maskedTextBox.Text = defaultValue.ToString(); + } + + private static void BuildDelayControl(NumericUpDown maskedTextBox, int defaultValue) + { + maskedTextBox.Value = defaultValue; + } + + private static void BuildMapOption(ComboBox comboBox, IEnumerable> options, string defaultKey) + { + int selectedIndex = 0; + int num = 0; + comboBox.DisplayMember = "Name"; + comboBox.ValueMember = "Key"; + foreach (KeyValuePair option in options) + { + comboBox.Items.Add(option.Value); + if (option.Key == defaultKey) + { + selectedIndex = num; + } + num++; + } + comboBox.SelectedIndex = selectedIndex; + } + + private static void BuildVehicleOption(ComboBox comboBox, IEnumerable> options, string defaultKey) + { + int selectedIndex = 0; + int num = 0; + comboBox.DisplayMember = "Name"; + comboBox.ValueMember = "Key"; + foreach (KeyValuePair option in options) + { + comboBox.Items.Add(option.Value); + if (option.Key == defaultKey) + { + selectedIndex = num; + } + num++; + } + comboBox.SelectedIndex = selectedIndex; + } + + private static void BuildTeamOption(ComboBox comboBox, IEnumerable> options, string defaultKey) + { + int selectedIndex = 0; + int num = 0; + comboBox.DisplayMember = "Name"; + comboBox.ValueMember = "Key"; + foreach (KeyValuePair option in options) + { + comboBox.Items.Add(option.Value); + if (option.Key == defaultKey) + { + selectedIndex = num; + } + num++; + } + comboBox.SelectedIndex = selectedIndex; + } + + private static void BuildGenericOption(ComboBox comboBox, IEnumerable> options, string defaultKey) + { + int selectedIndex = 0; + int num = 0; + comboBox.DisplayMember = "Value"; + comboBox.ValueMember = "Key"; + foreach (KeyValuePair option in options) + { + comboBox.Items.Add(option); + if (option.Key == defaultKey) + { + selectedIndex = num; + } + num++; + } + comboBox.SelectedIndex = selectedIndex; + } + + private void mCancel_Click(object sender, EventArgs e) + { + Close(); + } + + private void mOK_Click(object sender, EventArgs e) + { + RPDefaults.DeathRace.ScoreCompression = ExtractSelectedKey(mDeathRaceScoreCompression); + RPDefaults.Football.ScoreCompression = ExtractSelectedKey(mFootballScoreCompression); + RPDefaults.DeathRace.MissionLength = (TimeSpan)mDeathRaceMissionLength.ValidateText(); + RPDefaults.Football.MissionLength = (TimeSpan)mFootballMissionLength.ValidateText(); + RPDefaults.DeathRace.MapKey = ExtractSelectedMapKey(mDeathRaceMap); + RPDefaults.Football.MapKey = ExtractSelectedMapKey(mFootballMap); + RPDefaults.DeathRace.TimeOfDayKey = ExtractSelectedKey(mDeathRaceTimeOfDay); + RPDefaults.Football.TimeOfDayKey = ExtractSelectedKey(mFootballTimeOfDay); + RPDefaults.DeathRace.WeatherKey = ExtractSelectedKey(mDeathRaceWeather); + RPDefaults.Football.WeatherKey = ExtractSelectedKey(mFootballWeather); + RPDefaults.DeathRace.VehicleKey = ExtractVehicleKey(mDeathRaceVehicle); + RPDefaults.Football.VehicleKey = ExtractVehicleKey(mFootballVehicle); + RPDefaults.DeathRace.ColorKey = ExtractSelectedKey(mDeathRaceColor); + RPDefaults.Football.TeamKey = ExtractTeamKey(mFootballTeam); + RPDefaults.DeathRace.BadgeKey = ExtractSelectedKey(mDeathRaceBadge); + RPDefaults.Football.PositionKey = ExtractSelectedKey(mFootballPosition); + RPDefaults.DeathRace.LaunchDelay = (int)mDeathraceLaunchDelay.Value; + RPDefaults.Football.LaunchDelay = (int)mFootballLaunchDelay.Value; + RPDefaults.Save(); + Hide(); + } + + private static T ExtractSelectedKey(ComboBox comboBox) + { + if (comboBox.SelectedItem is KeyValuePair) + { + return ((KeyValuePair)comboBox.SelectedItem).Key; + } + return (T)comboBox.SelectedItem; + } + + private static string ExtractSelectedMapKey(ComboBox comboBox) + { + if (comboBox.SelectedItem is RPMap) + { + return ((RPMap)comboBox.SelectedItem).Key; + } + return (string)comboBox.SelectedItem; + } + + private static string ExtractVehicleKey(ComboBox comboBox) + { + if (comboBox.SelectedItem is RPVehicle) + { + return ((RPVehicle)comboBox.SelectedItem).Key; + } + return (string)comboBox.SelectedItem; + } + + private static string ExtractTeamKey(ComboBox comboBox) + { + if (comboBox.SelectedItem is RPTeam) + { + return ((RPTeam)comboBox.SelectedItem).Key; + } + return (string)comboBox.SelectedItem; + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPFootballMission.cs b/Console/TeslaConsole.RedPlanet/RPFootballMission.cs new file mode 100644 index 0000000..b9db9d7 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPFootballMission.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class RPFootballMission : RPMission +{ + private readonly List mFootballPlayers = new List(); + + public IEnumerable FootballPlayers => mFootballPlayers; + + public override IEnumerable Players => mFootballPlayers.ToArray(); + + public override int PlayerCount => mFootballPlayers.Count; + + public RPFootballMission(string mapKey, string timeOfDayKey, string weatherKey, bool scoreCompression, TimeSpan gameLength) + : base(RPConfig.Football.Key, mapKey, timeOfDayKey, weatherKey, scoreCompression, gameLength) + { + } + + public void AddFootballTeam(IEnumerable footballTeam) + { + bool flag = false; + string text = null; + foreach (RPFootballPlayer item in footballTeam) + { + if (text == null) + { + text = item.TeamKey; + foreach (RPFootballPlayer mFootballPlayer in mFootballPlayers) + { + if (mFootballPlayer.TeamKey == item.TeamKey) + { + throw new ArgumentException("The provided RPFootballPlayers' team has already been added", "footballTeam"); + } + } + } + else if (text != item.TeamKey) + { + throw new ArgumentException("The provided RPFootballPlayers were not on the same team", "footballTeam"); + } + if (item.PositionKey == "runner") + { + if (flag) + { + throw new ArgumentException("The provided RPFootballPlayers contained more than one runner", "footballTeam"); + } + flag = true; + } + } + if (!flag) + { + throw new ArgumentException("The provided RPFootballPlayers did not contain a runner", "footballTeam"); + } + mFootballPlayers.AddRange(footballTeam); + } + + protected override void AppendMissionSpecificData(StringBuilder egg) + { + Dictionary> dictionary = new Dictionary>(); + foreach (RPFootballPlayer mFootballPlayer in mFootballPlayers) + { + if (!dictionary.ContainsKey(mFootballPlayer.TeamKey)) + { + dictionary.Add(mFootballPlayer.TeamKey, new List()); + } + dictionary[mFootballPlayer.TeamKey].Add(mFootballPlayer); + } + int num = 1; + StringBuilder stringBuilder = new StringBuilder("[teams]\n"); + StringBuilder stringBuilder2 = new StringBuilder(); + StringBuilder stringBuilder3 = new StringBuilder("[teambitmap]\n"); + StringBuilder stringBuilder4 = new StringBuilder(); + StringBuilder stringBuilder5 = new StringBuilder(); + foreach (KeyValuePair> item in dictionary) + { + stringBuilder.AppendFormat("team=team::{0}\n", item.Key); + stringBuilder2.AppendFormat("[team::{0}]\n", item.Key); + stringBuilder2.AppendFormat("bitmapindex={0}\n", num++); + stringBuilder2.AppendFormat("pilots=pilots::{0}\n", item.Key); + stringBuilder3.AppendFormat("bitmap=BitMap::Small::team::{0}\n", item.Key); + stringBuilder4.AppendFormat("[BitMap::Small::team::{0}]\n", item.Key); + stringBuilder4.Append(PlasmaBitmaps.GenerateString(64, 16, item.Key)); + stringBuilder4.Append("width=4\n"); + stringBuilder5.AppendFormat("[pilots::{0}]\n", item.Key); + foreach (RPFootballPlayer item2 in item.Value) + { + stringBuilder5.AppendFormat("pilot={0}\n", item2.PodHostAddress); + } + } + egg.Append(stringBuilder); + egg.Append(stringBuilder2); + egg.Append(stringBuilder5); + egg.Append(stringBuilder3); + egg.Append(stringBuilder4); + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPFootballPlayer.cs b/Console/TeslaConsole.RedPlanet/RPFootballPlayer.cs new file mode 100644 index 0000000..f44ded6 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPFootballPlayer.cs @@ -0,0 +1,37 @@ +using System; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class RPFootballPlayer : RPPlayer +{ + private readonly string mTeamKey; + + private readonly string mPositionKey; + + public string TeamKey => mTeamKey; + + public string PositionKey => mPositionKey; + + public RPFootballPlayer(string podHostAddress, string name, string vehicleKey, string teamKey, string positionKey, string colorKey) + : base(podHostAddress, name, vehicleKey, colorKey, "None") + { + if (teamKey == null) + { + throw new ArgumentNullException("teamKey"); + } + if (positionKey == null) + { + throw new ArgumentNullException("positionKey"); + } + mTeamKey = teamKey; + mPositionKey = positionKey; + } + + protected sealed override void AppendMissionSpecificPilot(int index, StringBuilder sb) + { + sb.AppendFormat("team=team::{0}\n", mTeamKey); + sb.AppendFormat("position={0}\n", mPositionKey); + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPGame.cs b/Console/TeslaConsole.RedPlanet/RPGame.cs new file mode 100644 index 0000000..388eba6 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPGame.cs @@ -0,0 +1,1739 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using Munga.Net; +using WeifenLuo.WinFormsUI.Docking; + +namespace TeslaConsole.RedPlanet; + +public class RPGame : DockContent +{ + private class DragDropData + { + public int StartRow; + + public int EndRow; + + public bool RightClick; + + public DragDropData(int startRow, bool rightClick) + { + StartRow = startRow; + RightClick = rightClick; + } + } + + private class GameStateData + { + public bool EggMessageSent; + + public bool RunMessageSent; + + public bool AbortMessageSent; + + public bool StopMessageSent; + + public bool PodAppearsReset; + + public void Reset() + { + EggMessageSent = (RunMessageSent = (AbortMessageSent = (StopMessageSent = (PodAppearsReset = false)))); + } + } + + private enum RPGameState + { + Idle, + Load, + AutoTransCount, + Run, + Reset + } + + private static readonly Color sDataLockedColor = Color.LightGray; + + private static readonly ApplicationState[] sStopToIdleStates = new ApplicationState[7] + { + ApplicationState.CreatingMission, + ApplicationState.LaunchingMission, + ApplicationState.LoadingMission, + ApplicationState.ResumingMission, + ApplicationState.RunningMission, + ApplicationState.SuspendingMission, + ApplicationState.WaitingForLaunch + }; + + private static readonly Random sRand = new Random(); + + private Site mSite; + + private readonly bool mFootballMode; + + private readonly string mGameName; + + private EggFileMessage[] mEggFileMessages; + + private RPGameState mCurrentState; + + private RPGameState mRequestedState; + + private TimeSpan mMissionLength; + + private DateTime mGameStart; + + private bool mNormalMissionEnd; + + private bool mProcessingMissionEndStateChangeReq; + + private int mRemainingUntilTranslocate; + + private Dictionary mGameStateData = new Dictionary(); + + private RPMissionRecorder mMissionRecorder; + + private Dictionary mMissionPlayers; + + private LogOutputWindow mLogWindow; + + private RPMissionResults mLastMissionResults; + + private IContainer components; + + private GroupBox mMissionPropertiesGroupBox; + + private Label mIssuesLabel; + + private Label mMissionLengthDisplayLabel; + + private Label mMissionLengthTextLabel; + + private CheckBox mScoreCompression; + + private ComboBox mWeather; + + private ComboBox mMap; + + private ComboBox mTimeOfDay; + + private Label mTimeOfDayLabel; + + private Label mWeatherLabel; + + private Label mMapLabel; + + private GroupBox mPilotsGroupBox; + + private TableLayoutPanel mMissionPropertiesTable; + + private DataGridView mPilotsDataGrid; + + private Button mGoButton; + + private ContextMenuStrip mDragDropContextMenu; + + private ToolStripMenuItem mSwapPilots; + + private ToolStripMenuItem mShiftPilots; + + private Timer mNetworkTimer; + + private DataGridViewCheckBoxColumn mEnabledColumn; + + private DataGridViewImageColumn mStatusIconColumn; + + private DataGridViewTextBoxColumn mStatusTextColumn; + + private DataGridViewTextBoxColumn mPilotColumn; + + private DataGridViewComboBoxColumn mVehicleColumn; + + private DataGridViewComboBoxColumn mColorColumn; + + private DataGridViewComboBoxColumn mBadgeColumn; + + private ContextMenuStrip mResetContextMenu; + + private ToolStripMenuItem resetToolStripMenuItem; + + private Button mPrintLastMission; + + private CheckBox mAutoPrint; + + private CheckBox mAutoTranslocateCheckbox; + + private Button mRandomMap; + + private Button mRandomWeather; + + private Button mRandomTimeOfDay; + + private TextBox mMissionLengthTextBox; + + private Timer tmrAutoTranslocate; + + private string GameStatusText + { + set + { + if (string.IsNullOrEmpty(value)) + { + string text3 = (base.TabText = (Text = mGameName)); + } + else + { + string text6 = (base.TabText = (Text = $"{mGameName} [{value}]")); + } + } + } + + private RPGame() + { + InitializeComponent(); + } + + internal RPGame(Site site, bool footballMode) + : this() + { + mFootballMode = footballMode; + mSite = site; + SuspendLayout(); + mGameName = (mFootballMode ? "Martian Football" : "Death Race"); + GameStatusText = null; + int selectedIndex; + int num = (selectedIndex = 0); + string text = (mFootballMode ? RPDefaults.Football.MapKey : RPDefaults.DeathRace.MapKey); + mMap.DisplayMember = "Name"; + mMap.ValueMember = "Key"; + foreach (RPMap item in mFootballMode ? RPConfig.Football.Maps.Values : RPConfig.DeathRace.Maps.Values) + { + mMap.Items.Add(item); + if (item.Key == text) + { + selectedIndex = num; + } + num++; + } + mMap.SelectedIndex = selectedIndex; + num = (selectedIndex = 0); + text = (mFootballMode ? RPDefaults.Football.WeatherKey : RPDefaults.DeathRace.WeatherKey); + mWeather.DisplayMember = "Value"; + mWeather.ValueMember = "Key"; + foreach (KeyValuePair item2 in mFootballMode ? RPConfig.Football.Weather : RPConfig.DeathRace.Weather) + { + mWeather.Items.Add(item2); + if (item2.Key == text) + { + selectedIndex = num; + } + num++; + } + mWeather.SelectedIndex = selectedIndex; + num = (selectedIndex = 0); + text = (mFootballMode ? RPDefaults.Football.TimeOfDayKey : RPDefaults.DeathRace.TimeOfDayKey); + mTimeOfDay.DisplayMember = "Value"; + mTimeOfDay.ValueMember = "Key"; + foreach (KeyValuePair item3 in mFootballMode ? RPConfig.Football.TimesOfDay : RPConfig.DeathRace.TimesOfDay) + { + mTimeOfDay.Items.Add(item3); + if (item3.Key == text) + { + selectedIndex = num; + } + num++; + } + mTimeOfDay.SelectedIndex = selectedIndex; + mScoreCompression.Checked = (mFootballMode ? RPDefaults.Football.ScoreCompression : RPDefaults.DeathRace.ScoreCompression); + mMissionLengthTextBox.Text = (mFootballMode ? RPDefaults.Football.MissionLength : RPDefaults.DeathRace.MissionLength).TotalMinutes.ToString(); + num = (selectedIndex = 0); + text = (mFootballMode ? RPDefaults.Football.VehicleKey : RPDefaults.DeathRace.VehicleKey); + mVehicleColumn.DisplayMember = "Name"; + mVehicleColumn.ValueMember = "Key"; + foreach (RPVehicle item4 in mFootballMode ? RPConfig.Football.Vehicles.Values : RPConfig.DeathRace.Vehicles.Values) + { + mVehicleColumn.Items.Add(item4); + if (item4.Key == text) + { + selectedIndex = num; + } + num++; + } + mVehicleColumn.Tag = selectedIndex; + num = (selectedIndex = 0); + text = (mFootballMode ? RPDefaults.Football.TeamKey : RPDefaults.DeathRace.ColorKey); + if (mFootballMode) + { + mColorColumn.HeaderText = "Team"; + } + mColorColumn.DisplayMember = (mFootballMode ? "Name" : "Value"); + mColorColumn.ValueMember = "Key"; + if (mFootballMode) + { + foreach (RPTeam value in RPConfig.Football.Teams.Values) + { + mColorColumn.Items.Add(value); + if (value.Key == text) + { + selectedIndex = num; + } + num++; + } + } + else + { + foreach (KeyValuePair color in RPConfig.DeathRace.Colors) + { + mColorColumn.Items.Add(color); + if (color.Key == text) + { + selectedIndex = num; + } + num++; + } + } + mColorColumn.Tag = selectedIndex; + num = (selectedIndex = 0); + text = (mFootballMode ? RPDefaults.Football.PositionKey : RPDefaults.DeathRace.BadgeKey); + if (mFootballMode) + { + mBadgeColumn.HeaderText = "Position"; + } + mBadgeColumn.DisplayMember = "Value"; + mBadgeColumn.ValueMember = "Key"; + foreach (KeyValuePair item5 in mFootballMode ? RPConfig.Football.Positions : RPConfig.DeathRace.Badges) + { + mBadgeColumn.Items.Add(item5); + if (item5.Key == text) + { + selectedIndex = num; + } + num++; + } + mBadgeColumn.Tag = selectedIndex; + List> list = new List>(); + foreach (Squad squad in mSite.Squads) + { + foreach (Pod pod in squad.Pods) + { + if (pod.HostType == HostType.GameMachineHostType) + { + BuildPodRow(squad, pod); + } + else if (pod.HostType == HostType.MissionReviewHostType) + { + list.Add(new Tuple(squad, pod)); + } + } + } + foreach (Tuple item6 in list) + { + BuildPodRow(item6.A, item6.B); + } + CheckAllValues(); + ResumeLayout(); + } + + private void BuildPodRow(Squad squad, Pod pod) + { + int index = mPilotsDataGrid.Rows.Add(); + DataGridViewRow row = mPilotsDataGrid.Rows[index]; + row.Tag = pod; + row.HeaderCell.Value = $"{squad.Name} - {pod.Name}"; + row.Cells[mEnabledColumn.Index].Value = false; + if (pod.HostType == HostType.GameMachineHostType) + { + row.Cells[mVehicleColumn.Index].Value = mVehicleColumn.Items[(int)mVehicleColumn.Tag]; + row.Cells[mColorColumn.Index].Value = mColorColumn.Items[(int)mColorColumn.Tag]; + row.Cells[mBadgeColumn.Index].Value = mBadgeColumn.Items[(int)mBadgeColumn.Tag]; + } + else + { + row.Cells[mVehicleColumn.Index].Value = ""; + row.Cells[mColorColumn.Index].Value = ""; + row.Cells[mBadgeColumn.Index].Value = ""; + row.Cells[mPilotColumn.Index].ReadOnly = true; + row.Cells[mVehicleColumn.Index].ReadOnly = true; + row.Cells[mColorColumn.Index].ReadOnly = true; + row.Cells[mBadgeColumn.Index].ReadOnly = true; + } + mGameStateData[pod.MungaGame] = new GameStateData(); + SetPodStatus(row, pod.MungaGame); + pod.MungaGame.OnPodStatusUpdated += delegate(object sender, EventArgs e) + { + if (!base.IsDisposed) + { + SetPodStatus(row, (MungaGame)sender); + } + }; + } + + protected override void OnClosing(CancelEventArgs e) + { + if (mCurrentState != 0 && MessageBox.Show("An RP game controlled by this window is currently in progress. If you choose to continue, the game will end immediately and no score sheets will be printed. Are you sure you wish to procede?", "Game Running", MessageBoxButtons.OKCancel) == DialogResult.Cancel) + { + e.Cancel = true; + } + else + { + base.OnClosing(e); + } + } + + protected override void OnClosed(EventArgs e) + { + foreach (Squad squad in mSite.Squads) + { + foreach (Pod pod in squad.Pods) + { + ForcePodRelease(pod.MungaGame); + pod.MungaGame.ReleaseRequest(this); + } + } + base.OnClosed(e); + } + + private bool IsAllPodsAppState(ApplicationState state) + { + foreach (DataGridViewRow item in (IEnumerable)mPilotsDataGrid.Rows) + { + if ((bool)item.Cells[mEnabledColumn.Index].Value) + { + MungaGame mungaGame = (MungaGame)item.HeaderCell.Tag; + if (!mungaGame.IsPodAppState(ApplicationID.RPL4, state)) + { + return false; + } + } + } + return true; + } + + private static string GetStringOrKey(DataGridViewCell cell) + { + if (cell.Value is string) + { + return (string)cell.Value; + } + return ((KeyValuePair)cell.Value).Key; + } + + private void NetworkScan(object sender, EventArgs e) + { + if (mCurrentState == RPGameState.Run && mRequestedState == RPGameState.Run && !mProcessingMissionEndStateChangeReq) + { + mProcessingMissionEndStateChangeReq = true; + TimeSpan timeRemaining = mMissionLength - (DateTime.Now - mGameStart); + if (timeRemaining.Ticks < 0) + { + mNormalMissionEnd = true; + RequestState(RPGameState.Idle); + timeRemaining = new TimeSpan(0L); + } + string gameStatusText = FormatTimeRemaining(timeRemaining); + mMissionLengthDisplayLabel.Text = gameStatusText; + GameStatusText = gameStatusText; + mProcessingMissionEndStateChangeReq = false; + } + bool flag = true; + foreach (DataGridViewRow item in (IEnumerable)mPilotsDataGrid.Rows) + { + Pod pod = (Pod)item.Tag; + if (mRequestedState == RPGameState.Idle) + { + GetPodReleased(pod.MungaGame); + if (pod.MungaGame.IsOwner(this)) + { + flag = false; + } + } + else if (!(bool)item.Cells[mEnabledColumn.Index].Value) + { + if (pod.MungaGame.IsOwner(this)) + { + GetPodReleased(pod.MungaGame); + } + } + else + { + GameStateData gameStateData = mGameStateData[pod.MungaGame]; + if (!gameStateData.PodAppearsReset) + { + if (!pod.MungaGame.IsOwned) + { + pod.MungaGame.TakeOwnership(this); + } + if (!pod.MungaGame.IsOwner(this)) + { + flag = false; + } + else if (!pod.MungaGame.IsPodOperational(ApplicationID.RPL4)) + { + flag = false; + } + else if (mRequestedState == RPGameState.Load) + { + switch (pod.MungaGame.AppState.Value) + { + case ApplicationState.RunningMission: + if (!gameStateData.AbortMessageSent) + { + pod.MungaGame.Send(0, 1, new AbortMissionMessage(0)); + gameStateData.AbortMessageSent = true; + } + break; + case ApplicationState.WaitingForEgg: + if (!pod.MungaGame.EggAcknowledged && pod.MungaGame.EggDelayTimer < DateTime.Now && pod.MungaGame.EggSent.AddSeconds(5.0) < DateTime.Now) + { + LogMessage("Egg Sent."); + EggFileMessage[] array = mEggFileMessages; + foreach (EggFileMessage message in array) + { + pod.MungaGame.Send(0, 1, message); + } + pod.MungaGame.EggSent = DateTime.Now; + } + break; + } + if (!pod.MungaGame.IsPodAppState(ApplicationID.RPL4, ApplicationState.WaitingForLaunch)) + { + flag = false; + } + } + else if (mRequestedState == RPGameState.Run) + { + switch (pod.MungaGame.AppState.Value) + { + case ApplicationState.WaitingForEgg: + gameStateData.PodAppearsReset = true; + GetPodReleased(pod.MungaGame); + break; + case ApplicationState.WaitingForLaunch: + if (!gameStateData.RunMessageSent) + { + pod.MungaGame.Send(0, 1, new RunMissionMessage()); + gameStateData.RunMessageSent = true; + } + break; + } + if (!pod.MungaGame.IsPodAppState(ApplicationID.RPL4, ApplicationState.RunningMission)) + { + flag = false; + } + } + } + } + if (!pod.MungaGame.IsOwner(this)) + { + continue; + } + for (MetaMessage metaMessage = pod.MungaGame.Receive(); metaMessage != null; metaMessage = pod.MungaGame.Receive()) + { + LogMessage(pod, metaMessage); + if (metaMessage.Message is VTVBoosterMessage) + { + VTVBoosterMessage vTVBoosterMessage = (VTVBoosterMessage)metaMessage.Message; + mMissionRecorder.PlayerBoost(mMissionPlayers[vTVBoosterMessage.PlayerHostID], vTVBoosterMessage.BoosterOn); + } + else if (metaMessage.Message is VTVDamagedMessage) + { + VTVDamagedMessage vTVDamagedMessage = (VTVDamagedMessage)metaMessage.Message; + mMissionRecorder.PlayerDamaged(mMissionPlayers[vTVDamagedMessage.PlayerHostID], mMissionPlayers[vTVDamagedMessage.DamagerHostID], vTVDamagedMessage.DamageLoss, vTVDamagedMessage.PointsTransfered); + } + else if (metaMessage.Message is VTVKilledMessage) + { + VTVKilledMessage vTVKilledMessage = (VTVKilledMessage)metaMessage.Message; + mMissionRecorder.PlayerKilled(mMissionPlayers[vTVKilledMessage.PlayerHostID], mMissionPlayers[vTVKilledMessage.KillerHostID]); + } + else if (metaMessage.Message is VTVScoredMessage) + { + VTVScoredMessage vTVScoredMessage = (VTVScoredMessage)metaMessage.Message; + mMissionRecorder.PlayerScored(mMissionPlayers[vTVScoredMessage.PlayerHostID]); + } + else if (metaMessage.Message is VTVScoreUpdateMessage) + { + VTVScoreUpdateMessage vTVScoreUpdateMessage = (VTVScoreUpdateMessage)metaMessage.Message; + mMissionRecorder.PlayerScoreUpdate(mMissionPlayers[vTVScoreUpdateMessage.PlayerHostID], vTVScoreUpdateMessage.PlayerScore); + } + else if (metaMessage.Message is EndMissionMessage) + { + EndMissionMessage endMissionMessage = (EndMissionMessage)metaMessage.Message; + mMissionRecorder.PlayerFinalScore(mMissionPlayers[endMissionMessage.PlayerHostID], endMissionMessage.FinalScore); + } + } + } + if (mCurrentState != mRequestedState && flag) + { + StateChangeApproved(); + } + } + + private void LogMessage(string message) + { + if (mLogWindow != null && !mLogWindow.IsDisposed) + { + mLogWindow.LogMessage(message); + } + } + + private void LogMessage(Pod pod, MetaMessage msg) + { + if (mLogWindow == null || mLogWindow.IsDisposed) + { + return; + } + if (msg.Message is AcknowledgeEggFileMessage) + { + mLogWindow.LogMessage("{0}: {1}: {2}: Egg ACK", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name); + } + else if (msg.Message is EndMissionMessage) + { + EndMissionMessage endMissionMessage = (EndMissionMessage)msg.Message; + mLogWindow.LogMessage("{0}: {1}: {2}: Final Score {3}", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name, endMissionMessage.FinalScore); + } + else if (msg.Message is ReadyToRunMessage) + { + mLogWindow.LogMessage("{0}: {1}: {2}", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name); + } + else if (msg.Message is VTVScoredMessage) + { + mLogWindow.LogMessage("{0}: {1}: {2}", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name); + } + else if (msg.Message is VTVKilledMessage) + { + VTVKilledMessage vTVKilledMessage = (VTVKilledMessage)msg.Message; + mLogWindow.LogMessage("{0}: {1}: {2}: {3} killed {4}", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name, vTVKilledMessage.KillerHostID, vTVKilledMessage.PlayerHostID); + } + else if (msg.Message is VTVBoosterMessage) + { + mLogWindow.LogMessage("{0}: {1}: {2}: {3} is {4}boosting", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name, ((VTVBoosterMessage)msg.Message).PlayerHostID, (((VTVBoosterMessage)msg.Message).BoosterOn == 1) ? "" : "not "); + } + else if (msg.Message is VTVScoreUpdateMessage) + { + mLogWindow.LogMessage("{0}: {1}: {2}: {3} Score = {4}", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name, ((VTVScoreUpdateMessage)msg.Message).PlayerHostID, ((VTVScoreUpdateMessage)msg.Message).PlayerScore); + } + else if (msg.Message is VTVDamagedMessage) + { + VTVDamagedMessage vTVDamagedMessage = (VTVDamagedMessage)msg.Message; + if (vTVDamagedMessage.DamageLoss != 0) + { + mLogWindow.LogMessage("{0}: {1}: {2}: {3} Damaged {4} For {5} Points.", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name, vTVDamagedMessage.DamagerHostID, vTVDamagedMessage.PlayerHostID, vTVDamagedMessage.PointsTransfered); + } + } + else if (msg.Message is MechKilledMessage || msg.Message is MechScoreUpdateMessage || msg.Message is MechDamagedMessage || msg.Message is BTTeamScoreUpdateMessage || msg.Message is MechDeathWithoutHonorMessage) + { + mLogWindow.LogMessage("{0}: {1}: {2}", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name); + } + else if (msg.Message is EndMissionMessage) + { + mLogWindow.LogMessage("{0}: {1}: {2}: {3}: Final Score = {4}", pod.Name, msg.Header.ClientID, msg.Message.GetType().Name, ((EndMissionMessage)msg.Message).PlayerHostID, ((EndMissionMessage)msg.Message).FinalScore); + } + } + + private string FormatTimeRemaining(TimeSpan timeRemaining) + { + long num = (long)timeRemaining.TotalMinutes; + long num2 = (long)(timeRemaining.TotalSeconds - (double)(num * 60)); + return $"{num:00}:{num2:00}"; + } + + private void ForcePodRelease(MungaGame munga) + { + GetPodReleased(munga); + munga.ReleaseOwnership(this); + } + + private void GetPodReleased(MungaGame munga) + { + if (!munga.IsOwner(this)) + { + return; + } + if (!munga.IsPodOperational(ApplicationID.RPL4)) + { + munga.ReleaseOwnership(this); + return; + } + GameStateData gameStateData = mGameStateData[munga]; + switch (munga.AppState.Value) + { + case ApplicationState.WaitingForLaunch: + if (!gameStateData.AbortMessageSent) + { + munga.Send(0, 1, new AbortMissionMessage(0)); + gameStateData.AbortMessageSent = true; + } + break; + case ApplicationState.RunningMission: + if (!gameStateData.StopMessageSent) + { + munga.Send(0, 1, new StopMissionMessage(0)); + gameStateData.StopMessageSent = true; + } + break; + case ApplicationState.WaitingForEgg: + munga.ReleaseOwnership(this); + break; + case ApplicationState.LoadingMission: + case ApplicationState.LaunchingMission: + break; + } + } + + private void StateChangeApproved() + { + switch (mRequestedState) + { + case RPGameState.Idle: + EndMission(); + GameStatusText = null; + mGoButton.Text = "Load"; + mEggFileMessages = null; + SwitchControlsMode(editMode: true); + CheckAllValues(); + mCurrentState = RPGameState.Idle; + break; + case RPGameState.Load: + GameStatusText = "Loaded"; + mGoButton.Text = "Run Mission"; + mGoButton.Enabled = true; + foreach (GameStateData value in mGameStateData.Values) + { + value.Reset(); + } + mCurrentState = RPGameState.Load; + if (mAutoTranslocateCheckbox.Checked) + { + int num = (mFootballMode ? RPDefaults.Football.LaunchDelay : RPDefaults.DeathRace.LaunchDelay); + if (num == 0) + { + mGoButton.Text = "Launching..."; + GameStatusText = "Launching..."; + mGoButton.Enabled = false; + RequestState(RPGameState.Run); + } + else + { + mGoButton.Text = $"Launching in {num}..."; + GameStatusText = mGoButton.Text; + mRemainingUntilTranslocate = num; + tmrAutoTranslocate.Start(); + } + } + break; + case RPGameState.Run: + mGameStart = DateTime.Now; + mGoButton.Text = "Stop Mission"; + mGoButton.Enabled = true; + mCurrentState = RPGameState.Run; + mMissionRecorder.SetMissionStart(); + mMissionLengthDisplayLabel.ForeColor = Color.DarkGreen; + break; + case RPGameState.AutoTransCount: + break; + } + } + + private void mGoButton_Click(object sender, EventArgs e) + { + switch (mCurrentState) + { + case RPGameState.Idle: + RequestState(RPGameState.Load); + break; + case RPGameState.Load: + tmrAutoTranslocate.Stop(); + RequestState(RPGameState.Run); + break; + case RPGameState.Run: + mNormalMissionEnd = false; + RequestState(RPGameState.Idle); + break; + case RPGameState.AutoTransCount: + break; + } + } + + private void resetToolStripMenuItem_Click(object sender, EventArgs e) + { + RequestState(RPGameState.Reset); + } + + private void RequestState(RPGameState state) + { + switch (state) + { + case RPGameState.Load: + { + mMissionLength = ParseMissionLength(); + string key = ((RPMap)mMap.SelectedItem).Key; + string key2 = ((KeyValuePair)mTimeOfDay.SelectedItem).Key; + string key3 = ((KeyValuePair)mWeather.SelectedItem).Key; + RPMission rPMission = (mFootballMode ? ((RPMission)new RPFootballMission(key, key2, key3, mScoreCompression.Checked, mMissionLength)) : ((RPMission)new RPRaceMission(key, key2, key3, mScoreCompression.Checked, mMissionLength))); + mMissionRecorder = new RPMissionRecorder(rPMission); + mMissionPlayers = new Dictionary(); + int num = 2; + Dictionary> dictionary = new Dictionary>(); + foreach (DataGridViewRow item in (IEnumerable)mPilotsDataGrid.Rows) + { + if (!(bool)item.Cells[mEnabledColumn.Index].Value) + { + continue; + } + Pod pod = (Pod)item.Tag; + if (pod.HostType == HostType.GameMachineHostType) + { + string value; + if (mFootballMode) + { + RPTeam rPTeam = ((item.Cells[mColorColumn.Index].Value is RPTeam) ? ((RPTeam)item.Cells[mColorColumn.Index].Value) : RPConfig.Football.Teams[(string)item.Cells[mColorColumn.Index].Value]); + string stringOrKey = GetStringOrKey(item.Cells[mBadgeColumn.Index]); + RPFootballPlayer rPFootballPlayer = new RPFootballPlayer(pod.IPAddress.ToString(), value = (string)item.Cells[mPilotColumn.Index].Value, (item.Cells[mVehicleColumn.Index].Value is string) ? ((string)item.Cells[mVehicleColumn.Index].Value) : ((RPVehicle)item.Cells[mVehicleColumn.Index].Value).Key, rPTeam.Key, stringOrKey, (stringOrKey == "runner") ? rPTeam.RunnerColor : rPTeam.TeamColor); + if (!dictionary.ContainsKey(rPFootballPlayer.TeamKey)) + { + dictionary[rPFootballPlayer.TeamKey] = new List(); + } + dictionary[rPFootballPlayer.TeamKey].Add(rPFootballPlayer); + } + else + { + RPRacePlayer rPRacePlayer = new RPRacePlayer(pod.IPAddress.ToString(), value = (string)item.Cells[mPilotColumn.Index].Value, (item.Cells[mVehicleColumn.Index].Value is string) ? ((string)item.Cells[mVehicleColumn.Index].Value) : ((RPVehicle)item.Cells[mVehicleColumn.Index].Value).Key, GetStringOrKey(item.Cells[mColorColumn.Index]), GetStringOrKey(item.Cells[mBadgeColumn.Index])); + ((RPRaceMission)rPMission).RacePlayers.Add(rPRacePlayer); + mMissionPlayers.Add(num++, rPRacePlayer); + } + PilotNameCache.PilotNames.Add(value); + } + else + { + rPMission.Cameras.Add(new RPCamera(pod.IPAddress.ToString(), pod.HostType)); + } + } + if (mFootballMode) + { + foreach (List value2 in dictionary.Values) + { + ((RPFootballMission)rPMission).AddFootballTeam(value2); + foreach (RPFootballPlayer item2 in value2) + { + mMissionPlayers.Add(num++, item2); + } + } + } + mEggFileMessages = rPMission.ToEggFileMessages(); + mRequestedState = RPGameState.Load; + SwitchControlsMode(editMode: false); + GameStatusText = "Loading..."; + mGoButton.Text = "Loading..."; + mGoButton.Enabled = false; + break; + } + case RPGameState.Run: + mRequestedState = RPGameState.Run; + GameStatusText = "Launching..."; + mGoButton.Text = "Launching..."; + mGoButton.Enabled = false; + break; + case RPGameState.Idle: + mRequestedState = RPGameState.Idle; + mMissionLengthDisplayLabel.ForeColor = Color.Red; + GameStatusText = "Stopping Game..."; + mGoButton.Text = "Stopping Game..."; + mGoButton.Enabled = false; + break; + case RPGameState.Reset: + foreach (Squad squad in mSite.Squads) + { + foreach (Pod pod2 in squad.Pods) + { + ForcePodRelease(pod2.MungaGame); + } + } + mRequestedState = RPGameState.Idle; + StateChangeApproved(); + break; + case RPGameState.AutoTransCount: + break; + } + } + + private void EndMission() + { + try + { + mLastMissionResults = mMissionRecorder.EndMission(mNormalMissionEnd ? mMissionLength : (DateTime.Now - mGameStart)); + try + { + mLastMissionResults.Save(); + } + catch (Exception ex) + { + if (Debugger.IsAttached) + { + throw; + } + Program.LogExceptionAndShowDialog(ex, "Mission results failed to save.", "Error Saving Mission Results"); + } + if (mAutoPrint.Checked) + { + try + { + mLastMissionResults.GetPrintDocument().Print(); + } + catch (Exception ex2) + { + if (Debugger.IsAttached) + { + throw; + } + Program.LogExceptionAndShowDialog(ex2, "Mission results failed to print.", "Error Printing Mission Results"); + } + } + mPrintLastMission.Enabled = true; + } + catch (Exception ex3) + { + if (Debugger.IsAttached) + { + throw; + } + Program.LogExceptionAndShowDialog(ex3, "Failed to create mission results.", "Error Creating Mission Results"); + } + mMissionRecorder = null; + mMissionPlayers = null; + } + + private void SwitchControlsMode(bool editMode) + { + ComboBox comboBox = mMap; + ComboBox comboBox2 = mWeather; + ComboBox comboBox3 = mTimeOfDay; + CheckBox checkBox = mScoreCompression; + Button button = mRandomMap; + Button button2 = mRandomWeather; + bool flag2 = (mRandomTimeOfDay.Enabled = editMode); + bool flag4 = (button2.Enabled = flag2); + bool flag6 = (button.Enabled = flag4); + bool flag8 = (checkBox.Enabled = flag6); + bool flag10 = (comboBox3.Enabled = flag8); + bool enabled = (comboBox2.Enabled = flag10); + comboBox.Enabled = enabled; + mMissionLengthDisplayLabel.Text = FormatTimeRemaining(mMissionLength); + mMissionLengthDisplayLabel.ForeColor = Color.Orange; + mMissionLengthTextBox.Visible = editMode; + mMissionLengthDisplayLabel.Visible = !editMode; + int column = mMissionPropertiesTable.GetColumn(mMissionLengthTextBox); + mMissionPropertiesTable.ColumnStyles[column].Width = 0f; + mMissionPropertiesTable.ColumnStyles[column].SizeType = ((!editMode) ? SizeType.Absolute : SizeType.AutoSize); + int column2 = mMissionPropertiesTable.GetColumn(mMissionLengthDisplayLabel); + mMissionPropertiesTable.ColumnStyles[column2].Width = 0f; + mMissionPropertiesTable.ColumnStyles[column2].SizeType = (editMode ? SizeType.Absolute : SizeType.AutoSize); + mPilotsDataGrid.EditMode = (editMode ? DataGridViewEditMode.EditOnKeystrokeOrF2 : DataGridViewEditMode.EditProgrammatically); + Color backColor = (editMode ? Color.White : sDataLockedColor); + foreach (DataGridViewRow item in (IEnumerable)mPilotsDataGrid.Rows) + { + item.Cells[mEnabledColumn.Index].Style.BackColor = backColor; + if (((Pod)item.Tag).HostType == HostType.GameMachineHostType) + { + item.Cells[mPilotColumn.Index].Style.BackColor = backColor; + item.Cells[mVehicleColumn.Index].Style.BackColor = backColor; + item.Cells[mColorColumn.Index].Style.BackColor = backColor; + item.Cells[mBadgeColumn.Index].Style.BackColor = backColor; + Color foreColor = (((bool)item.Cells[mEnabledColumn.Index].Value) ? Color.Black : Color.DarkGray); + item.Cells[mPilotColumn.Index].Style.ForeColor = foreColor; + item.Cells[mVehicleColumn.Index].Style.ForeColor = foreColor; + item.Cells[mColorColumn.Index].Style.ForeColor = foreColor; + item.Cells[mBadgeColumn.Index].Style.ForeColor = foreColor; + } + } + if (editMode) + { + CheckAllValues(); + } + } + + private void SetPodStatus(DataGridViewRow row, MungaGame munga) + { + if (munga.IsOwned && !munga.IsOwner(this)) + { + row.Cells[mStatusIconColumn.Index].Value = ImageCache.PodQuestion16; + row.Cells[mStatusTextColumn.Index].Value = "In Use"; + } + else + { + row.Cells[mStatusIconColumn.Index].Value = munga.Image; + row.Cells[mStatusTextColumn.Index].Value = munga.StatusMessage; + } + if (mCurrentState == RPGameState.Idle && mRequestedState == RPGameState.Idle) + { + CheckAllValues(); + } + } + + private void CheckAllValues() + { + foreach (DataGridViewRow item in (IEnumerable)mPilotsDataGrid.Rows) + { + item.Cells[mPilotColumn.Index].ErrorText = ""; + item.Cells[mBadgeColumn.Index].ErrorText = ""; + } + Dictionary dictionary = new Dictionary(); + uint num = 0u; + uint num2 = 0u; + uint num3 = 0u; + bool flag = false; + for (int i = 0; i < mPilotsDataGrid.Rows.Count; i++) + { + DataGridViewRow dataGridViewRow2 = mPilotsDataGrid.Rows[i]; + Pod pod = (Pod)dataGridViewRow2.Tag; + if (!(bool)dataGridViewRow2.Cells[mEnabledColumn.Index].Value) + { + GreyOutRow(dataGridViewRow2); + continue; + } + if (!pod.MungaGame.IsPodAppState(ApplicationID.RPL4, ApplicationState.WaitingForEgg)) + { + num++; + } + if (pod.HostType == HostType.GameMachineHostType) + { + num2++; + DataGridViewCell dataGridViewCell = dataGridViewRow2.Cells[mPilotColumn.Index]; + dataGridViewCell.Style.ForeColor = mPilotColumn.DefaultCellStyle.ForeColor; + dataGridViewRow2.Cells[mVehicleColumn.Index].Style.ForeColor = mVehicleColumn.DefaultCellStyle.ForeColor; + dataGridViewRow2.Cells[mColorColumn.Index].Style.ForeColor = mColorColumn.DefaultCellStyle.ForeColor; + dataGridViewRow2.Cells[mBadgeColumn.Index].Style.ForeColor = mBadgeColumn.DefaultCellStyle.ForeColor; + dataGridViewRow2.Cells[mVehicleColumn.Index].Style.BackColor = mVehicleColumn.DefaultCellStyle.BackColor; + dataGridViewRow2.Cells[mColorColumn.Index].Style.BackColor = mColorColumn.DefaultCellStyle.BackColor; + dataGridViewRow2.Cells[mBadgeColumn.Index].Style.BackColor = mBadgeColumn.DefaultCellStyle.BackColor; + if (dataGridViewCell.ErrorText == "") + { + if (dataGridViewCell.Value == null || (string)dataGridViewCell.Value == "") + { + dataGridViewCell.ErrorText = "Pilot name can not be blank."; + num3++; + continue; + } + for (int j = i + 1; j < mPilotsDataGrid.Rows.Count; j++) + { + DataGridViewRow dataGridViewRow3 = mPilotsDataGrid.Rows[j]; + DataGridViewCell dataGridViewCell2 = dataGridViewRow3.Cells[mPilotColumn.Index]; + if (((Pod)dataGridViewRow3.Tag).HostType == HostType.GameMachineHostType && (bool)dataGridViewRow3.Cells[mEnabledColumn.Index].Value && dataGridViewCell2.ErrorText == "" && dataGridViewCell.Value.Equals(dataGridViewCell2.Value)) + { + string text3 = (dataGridViewCell.ErrorText = (dataGridViewCell2.ErrorText = "A pilot with this name already exists.")); + flag = true; + } + } + } + if (!mFootballMode) + { + continue; + } + DataGridViewCell dataGridViewCell3 = dataGridViewRow2.Cells[mBadgeColumn.Index]; + string text4 = ((dataGridViewRow2.Cells[mColorColumn.Index].Value is string) ? ((string)dataGridViewRow2.Cells[mColorColumn.Index].Value) : ((RPTeam)dataGridViewRow2.Cells[mColorColumn.Index].Value).Key); + string stringOrKey = GetStringOrKey(dataGridViewCell3); + if (!dictionary.ContainsKey(text4)) + { + dictionary.Add(text4, 0u); + } + if (!(stringOrKey == "runner")) + { + continue; + } + dictionary[text4]++; + for (int k = i + 1; k < mPilotsDataGrid.Rows.Count; k++) + { + DataGridViewRow dataGridViewRow4 = mPilotsDataGrid.Rows[k]; + if (dataGridViewRow4.Cells[mEnabledColumn.Index].Value.Equals(true)) + { + DataGridViewCell dataGridViewCell4 = dataGridViewRow4.Cells[mBadgeColumn.Index]; + string text5 = ((dataGridViewRow4.Cells[mColorColumn.Index].Value is string) ? ((string)dataGridViewRow4.Cells[mColorColumn.Index].Value) : ((RPTeam)dataGridViewRow4.Cells[mColorColumn.Index].Value).Key); + string stringOrKey2 = GetStringOrKey(dataGridViewCell4); + if (text4 == text5 && stringOrKey == stringOrKey2) + { + string text8 = (dataGridViewCell3.ErrorText = (dataGridViewCell4.ErrorText = "A team can not have more than one runner.")); + } + } + } + } + else + { + GreyOutRow(dataGridViewRow2); + } + } + StringBuilder stringBuilder = new StringBuilder(); + foreach (KeyValuePair item2 in dictionary) + { + string name = RPConfig.Football.Teams[item2.Key].Name; + if (item2.Value == 0) + { + stringBuilder.AppendLine($"The {name} team does not have a runner."); + } + else if (item2.Value > 1) + { + stringBuilder.AppendLine($"The {name} team has more than one runner."); + } + } + if (num2 < 1) + { + stringBuilder.AppendLine("You should put at least one player in the game."); + } + if (num2 > 8) + { + stringBuilder.AppendLine("Red Planet only supports 8 players in a game."); + } + if (num3 != 0) + { + stringBuilder.AppendLine((num3 > 1) ? "Some pilots have a blank name." : "A pilot has a blank name."); + } + if (flag) + { + stringBuilder.AppendLine("Some pilots have duplicate names."); + } + if (num != 0) + { + stringBuilder.AppendLine((num > 1) ? "Some pods are not ready." : "A pod is not ready."); + } + if (ParseMissionLength() == TimeSpan.MinValue) + { + stringBuilder.AppendLine("The mission length is not valid."); + } + else if (ParseMissionLength() < TimeSpan.FromSeconds(10.0)) + { + stringBuilder.AppendLine("The mission length must be at least 10 seconds."); + } + mGoButton.Enabled = stringBuilder.Length <= 0; + mIssuesLabel.Text = stringBuilder.ToString(); + } + + private void GreyOutRow(DataGridViewRow row) + { + row.Cells[mPilotColumn.Index].Style.ForeColor = Color.DarkGray; + row.Cells[mVehicleColumn.Index].Style.ForeColor = Color.DarkGray; + row.Cells[mColorColumn.Index].Style.ForeColor = Color.DarkGray; + row.Cells[mBadgeColumn.Index].Style.ForeColor = Color.DarkGray; + if (((Pod)row.Tag).HostType != 0) + { + row.Cells[mPilotColumn.Index].Style.BackColor = sDataLockedColor; + } + row.Cells[mVehicleColumn.Index].Style.BackColor = sDataLockedColor; + row.Cells[mColorColumn.Index].Style.BackColor = sDataLockedColor; + row.Cells[mBadgeColumn.Index].Style.BackColor = sDataLockedColor; + } + + private void mMissionLengthTextBox_TextChanged(object sender, EventArgs e) + { + CheckAllValues(); + } + + private TimeSpan ParseMissionLength() + { + if (!double.TryParse(mMissionLengthTextBox.Text, out var result)) + { + return TimeSpan.MinValue; + } + try + { + return TimeSpan.FromMinutes(result); + } + catch (OverflowException) + { + return TimeSpan.MinValue; + } + } + + private void mPilotsDataGrid_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e) + { + if (e.ColumnIndex == mPilotColumn.Index && !(bool)mPilotsDataGrid.Rows[e.RowIndex].Cells[mEnabledColumn.Index].Value) + { + mPilotsDataGrid.Rows[e.RowIndex].Cells[mPilotColumn.Index].Style.ForeColor = mPilotColumn.DefaultCellStyle.ForeColor; + } + } + + private void mPilotsDataGrid_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) + { + if (mPilotsDataGrid.EditingControl is TextBox textBox) + { + textBox.AutoCompleteCustomSource = PilotNameCache.PilotNames; + textBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend; + textBox.AutoCompleteSource = AutoCompleteSource.CustomSource; + } + } + + private void mPilotsDataGrid_CellEndEdit(object sender, DataGridViewCellEventArgs e) + { + bool? flag = null; + DataGridViewRow dataGridViewRow = mPilotsDataGrid.Rows[e.RowIndex]; + if (e.ColumnIndex == mPilotColumn.Index) + { + if (dataGridViewRow.Cells[mPilotColumn.Index].Value != null && (string)dataGridViewRow.Cells[mPilotColumn.Index].Value != "") + { + dataGridViewRow.Cells[mEnabledColumn.Index].Value = true; + flag = true; + } + else + { + dataGridViewRow.Cells[mEnabledColumn.Index].Value = false; + flag = false; + } + } + else if (e.ColumnIndex == mEnabledColumn.Index) + { + flag = ((!(bool)dataGridViewRow.Cells[mEnabledColumn.Index].Value) ? new bool?(false) : new bool?(true)); + } + if (flag.HasValue) + { + if (flag.Value) + { + ((Pod)dataGridViewRow.Tag).MungaGame.MakeRequested(this); + } + else + { + ((Pod)dataGridViewRow.Tag).MungaGame.ReleaseRequest(this); + } + } + CheckAllValues(); + } + + private void mPilotsDataGrid_KeyUp(object sender, KeyEventArgs e) + { + if ((e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back) && mPilotsDataGrid.SelectedCells.Count == 1 && mPilotsDataGrid.SelectedCells[0].ColumnIndex == mPilotColumn.Index) + { + mPilotsDataGrid.SelectedCells[0].Value = ""; + DataGridViewRow dataGridViewRow = mPilotsDataGrid.Rows[mPilotsDataGrid.SelectedCells[0].RowIndex]; + dataGridViewRow.Cells[mEnabledColumn.Index].Value = false; + ((Pod)dataGridViewRow.Tag).MungaGame.ReleaseRequest(this); + CheckAllValues(); + } + } + + private void mPilotsDataGrid_MouseDown(object sender, MouseEventArgs e) + { + if (mPilotsDataGrid.FirstDisplayedScrollingRowIndex < 0) + { + return; + } + for (int i = mPilotsDataGrid.FirstDisplayedScrollingRowIndex; i < mPilotsDataGrid.Rows.Count && ((Pod)mPilotsDataGrid.Rows[i].Tag).HostType == HostType.GameMachineHostType; i++) + { + if (mPilotsDataGrid.GetCellDisplayRectangle(-1, i, cutOverflow: true).Contains(e.Location)) + { + mPilotsDataGrid.DoDragDrop(new DragDropData(i, e.Button == MouseButtons.Right), DragDropEffects.Move); + break; + } + } + } + + private void mPilotsDataGrid_DragOver(object sender, DragEventArgs e) + { + if (mPilotsDataGrid.FirstDisplayedScrollingRowIndex >= 0) + { + for (int i = mPilotsDataGrid.FirstDisplayedScrollingRowIndex; i < mPilotsDataGrid.Rows.Count && ((Pod)mPilotsDataGrid.Rows[i].Tag).HostType == HostType.GameMachineHostType; i++) + { + if (!mPilotsDataGrid.GetCellDisplayRectangle(-1, i, cutOverflow: true).Contains(mPilotsDataGrid.PointToClient(new Point(e.X, e.Y)))) + { + continue; + } + if (e.Data.GetDataPresent(typeof(DragDropData))) + { + DragDropData dragDropData = (DragDropData)e.Data.GetData(typeof(DragDropData)); + if (dragDropData.StartRow != i) + { + e.Effect = DragDropEffects.Move; + } + else + { + e.Effect = DragDropEffects.None; + } + } + return; + } + } + e.Effect = DragDropEffects.None; + } + + private void mPilotsDataGrid_DragDrop(object sender, DragEventArgs e) + { + for (int i = mPilotsDataGrid.FirstDisplayedScrollingRowIndex; i < mPilotsDataGrid.Rows.Count; i++) + { + if (!mPilotsDataGrid.GetCellDisplayRectangle(-1, i, cutOverflow: true).Contains(mPilotsDataGrid.PointToClient(new Point(e.X, e.Y)))) + { + continue; + } + if (!e.Data.GetDataPresent(typeof(DragDropData))) + { + break; + } + DragDropData dragDropData = (DragDropData)e.Data.GetData(typeof(DragDropData)); + if (dragDropData.StartRow != i) + { + if (dragDropData.RightClick) + { + dragDropData.EndRow = i; + mDragDropContextMenu.Tag = dragDropData; + mDragDropContextMenu.Show(e.X, e.Y); + } + else + { + SwapPilots(i, dragDropData.StartRow, update: true); + } + } + break; + } + } + + private void swapToolStripMenuItem_Click(object sender, EventArgs e) + { + if (mDragDropContextMenu.Tag is DragDropData) + { + DragDropData dragDropData = (DragDropData)mDragDropContextMenu.Tag; + SwapPilots(dragDropData.StartRow, dragDropData.EndRow, update: true); + } + } + + private void shiftToolStripMenuItem_Click(object sender, EventArgs e) + { + if (!(mDragDropContextMenu.Tag is DragDropData)) + { + return; + } + DragDropData dragDropData = (DragDropData)mDragDropContextMenu.Tag; + if (dragDropData.StartRow < dragDropData.EndRow) + { + for (int i = dragDropData.StartRow; i < dragDropData.EndRow; i++) + { + SwapPilots(i, i + 1, i + 1 == dragDropData.EndRow); + } + return; + } + for (int num = dragDropData.StartRow; num > dragDropData.EndRow; num--) + { + SwapPilots(num, num - 1, num - 1 == dragDropData.EndRow); + } + } + + private void SwapPilots(int row1, int row2, bool update) + { + foreach (DataGridViewColumn column in mPilotsDataGrid.Columns) + { + if (column != mStatusIconColumn && column != mStatusTextColumn && column != mEnabledColumn) + { + object value = mPilotsDataGrid[column.Index, row1].Value; + mPilotsDataGrid[column.Index, row1].Value = mPilotsDataGrid[column.Index, row2].Value; + mPilotsDataGrid[column.Index, row2].Value = value; + } + } + if (update) + { + CheckAllValues(); + mPilotsDataGrid.Refresh(); + } + } + + private void mPrintLastMission_Click(object sender, EventArgs e) + { + mLastMissionResults.GetPrintDocument().Print(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Modifiers == Keys.Control && e.KeyCode == Keys.L) + { + if (mLogWindow == null || mLogWindow.IsDisposed) + { + mLogWindow = new LogOutputWindow(); + } + mLogWindow.Show(); + e.Handled = true; + } + base.OnKeyDown(e); + } + + private void mRandomMap_Click(object sender, EventArgs e) + { + mMap.SelectedIndex = sRand.Next(mMap.Items.Count); + } + + private void mRandomWeather_Click(object sender, EventArgs e) + { + mWeather.SelectedIndex = sRand.Next(mWeather.Items.Count); + } + + private void mRandomTimeOfDay_Click(object sender, EventArgs e) + { + mTimeOfDay.SelectedIndex = sRand.Next(mTimeOfDay.Items.Count); + } + + private void tmrAutoTranslocate_Tick(object sender, EventArgs e) + { + mRemainingUntilTranslocate--; + if (mRemainingUntilTranslocate < 1) + { + RequestState(RPGameState.Run); + tmrAutoTranslocate.Stop(); + } + else + { + string text2 = (GameStatusText = $"Launching in {mRemainingUntilTranslocate}..."); + mGoButton.Text = text2; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.mMissionPropertiesGroupBox = new System.Windows.Forms.GroupBox(); + this.mMissionPropertiesTable = new System.Windows.Forms.TableLayoutPanel(); + this.mIssuesLabel = new System.Windows.Forms.Label(); + this.mMissionLengthTextLabel = new System.Windows.Forms.Label(); + this.mMap = new System.Windows.Forms.ComboBox(); + this.mMissionLengthDisplayLabel = new System.Windows.Forms.Label(); + this.mWeather = new System.Windows.Forms.ComboBox(); + this.mTimeOfDay = new System.Windows.Forms.ComboBox(); + this.mMapLabel = new System.Windows.Forms.Label(); + this.mWeatherLabel = new System.Windows.Forms.Label(); + this.mTimeOfDayLabel = new System.Windows.Forms.Label(); + this.mScoreCompression = new System.Windows.Forms.CheckBox(); + this.mGoButton = new System.Windows.Forms.Button(); + this.mResetContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.resetToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mPrintLastMission = new System.Windows.Forms.Button(); + this.mAutoPrint = new System.Windows.Forms.CheckBox(); + this.mAutoTranslocateCheckbox = new System.Windows.Forms.CheckBox(); + this.mRandomMap = new System.Windows.Forms.Button(); + this.mRandomWeather = new System.Windows.Forms.Button(); + this.mRandomTimeOfDay = new System.Windows.Forms.Button(); + this.mMissionLengthTextBox = new System.Windows.Forms.TextBox(); + this.mPilotsGroupBox = new System.Windows.Forms.GroupBox(); + this.mPilotsDataGrid = new System.Windows.Forms.DataGridView(); + this.mEnabledColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.mStatusIconColumn = new System.Windows.Forms.DataGridViewImageColumn(); + this.mStatusTextColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.mPilotColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.mVehicleColumn = new System.Windows.Forms.DataGridViewComboBoxColumn(); + this.mColorColumn = new System.Windows.Forms.DataGridViewComboBoxColumn(); + this.mBadgeColumn = new System.Windows.Forms.DataGridViewComboBoxColumn(); + this.mDragDropContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.mSwapPilots = new System.Windows.Forms.ToolStripMenuItem(); + this.mShiftPilots = new System.Windows.Forms.ToolStripMenuItem(); + this.mNetworkTimer = new System.Windows.Forms.Timer(this.components); + this.tmrAutoTranslocate = new System.Windows.Forms.Timer(this.components); + this.mMissionPropertiesGroupBox.SuspendLayout(); + this.mMissionPropertiesTable.SuspendLayout(); + this.mResetContextMenu.SuspendLayout(); + this.mPilotsGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)this.mPilotsDataGrid).BeginInit(); + this.mDragDropContextMenu.SuspendLayout(); + base.SuspendLayout(); + this.mMissionPropertiesGroupBox.Controls.Add(this.mMissionPropertiesTable); + this.mMissionPropertiesGroupBox.Dock = System.Windows.Forms.DockStyle.Top; + this.mMissionPropertiesGroupBox.Location = new System.Drawing.Point(8, 8); + this.mMissionPropertiesGroupBox.Name = "mMissionPropertiesGroupBox"; + this.mMissionPropertiesGroupBox.Padding = new System.Windows.Forms.Padding(5); + this.mMissionPropertiesGroupBox.Size = new System.Drawing.Size(1040, 105); + this.mMissionPropertiesGroupBox.TabIndex = 10; + this.mMissionPropertiesGroupBox.TabStop = false; + this.mMissionPropertiesGroupBox.Text = "Mission Properties"; + this.mMissionPropertiesTable.AutoSize = true; + this.mMissionPropertiesTable.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.mMissionPropertiesTable.ColumnCount = 8; + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 0f)); + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMissionPropertiesTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.mMissionPropertiesTable.Controls.Add(this.mIssuesLabel, 7, 0); + this.mMissionPropertiesTable.Controls.Add(this.mMissionLengthTextLabel, 3, 1); + this.mMissionPropertiesTable.Controls.Add(this.mMap, 1, 0); + this.mMissionPropertiesTable.Controls.Add(this.mMissionLengthDisplayLabel, 5, 1); + this.mMissionPropertiesTable.Controls.Add(this.mWeather, 1, 1); + this.mMissionPropertiesTable.Controls.Add(this.mTimeOfDay, 1, 2); + this.mMissionPropertiesTable.Controls.Add(this.mMapLabel, 0, 0); + this.mMissionPropertiesTable.Controls.Add(this.mWeatherLabel, 0, 1); + this.mMissionPropertiesTable.Controls.Add(this.mTimeOfDayLabel, 0, 2); + this.mMissionPropertiesTable.Controls.Add(this.mScoreCompression, 3, 0); + this.mMissionPropertiesTable.Controls.Add(this.mGoButton, 3, 2); + this.mMissionPropertiesTable.Controls.Add(this.mPrintLastMission, 6, 2); + this.mMissionPropertiesTable.Controls.Add(this.mAutoPrint, 6, 1); + this.mMissionPropertiesTable.Controls.Add(this.mAutoTranslocateCheckbox, 6, 0); + this.mMissionPropertiesTable.Controls.Add(this.mRandomMap, 2, 0); + this.mMissionPropertiesTable.Controls.Add(this.mRandomWeather, 2, 1); + this.mMissionPropertiesTable.Controls.Add(this.mRandomTimeOfDay, 2, 2); + this.mMissionPropertiesTable.Controls.Add(this.mMissionLengthTextBox, 4, 1); + this.mMissionPropertiesTable.Dock = System.Windows.Forms.DockStyle.Fill; + this.mMissionPropertiesTable.GrowStyle = System.Windows.Forms.TableLayoutPanelGrowStyle.AddColumns; + this.mMissionPropertiesTable.Location = new System.Drawing.Point(5, 18); + this.mMissionPropertiesTable.MinimumSize = new System.Drawing.Size(0, 85); + this.mMissionPropertiesTable.Name = "mMissionPropertiesTable"; + this.mMissionPropertiesTable.RowCount = 4; + this.mMissionPropertiesTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMissionPropertiesTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMissionPropertiesTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.mMissionPropertiesTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.mMissionPropertiesTable.Size = new System.Drawing.Size(1030, 85); + this.mMissionPropertiesTable.TabIndex = 0; + this.mIssuesLabel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mIssuesLabel.AutoSize = true; + this.mIssuesLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25f, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); + this.mIssuesLabel.ForeColor = System.Drawing.Color.Red; + this.mIssuesLabel.Location = new System.Drawing.Point(642, 0); + this.mIssuesLabel.Name = "mIssuesLabel"; + this.mMissionPropertiesTable.SetRowSpan(this.mIssuesLabel, 4); + this.mIssuesLabel.Size = new System.Drawing.Size(385, 85); + this.mIssuesLabel.TabIndex = 0; + this.mMissionLengthTextLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mMissionLengthTextLabel.AutoSize = true; + this.mMissionLengthTextLabel.Location = new System.Drawing.Point(311, 37); + this.mMissionLengthTextLabel.Name = "mMissionLengthTextLabel"; + this.mMissionLengthTextLabel.Size = new System.Drawing.Size(106, 13); + this.mMissionLengthTextLabel.TabIndex = 7; + this.mMissionLengthTextLabel.Text = "Mission Length (min):"; + this.mMap.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mMap.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mMap.FormattingEnabled = true; + this.mMap.Location = new System.Drawing.Point(76, 4); + this.mMap.Name = "mMap"; + this.mMap.Size = new System.Drawing.Size(198, 21); + this.mMap.TabIndex = 0; + this.mMissionLengthDisplayLabel.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mMissionLengthDisplayLabel.AutoSize = true; + this.mMissionLengthDisplayLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12f, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); + this.mMissionLengthDisplayLabel.ForeColor = System.Drawing.Color.Green; + this.mMissionLengthDisplayLabel.Location = new System.Drawing.Point(529, 33); + this.mMissionLengthDisplayLabel.Name = "mMissionLengthDisplayLabel"; + this.mMissionLengthDisplayLabel.Size = new System.Drawing.Size(1, 20); + this.mMissionLengthDisplayLabel.TabIndex = 9; + this.mMissionLengthDisplayLabel.Text = "00:00"; + this.mMissionLengthDisplayLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.mMissionLengthDisplayLabel.Visible = false; + this.mWeather.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mWeather.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mWeather.FormattingEnabled = true; + this.mWeather.Location = new System.Drawing.Point(76, 33); + this.mWeather.Name = "mWeather"; + this.mWeather.Size = new System.Drawing.Size(198, 21); + this.mWeather.TabIndex = 1; + this.mTimeOfDay.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mTimeOfDay.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.mTimeOfDay.FormattingEnabled = true; + this.mTimeOfDay.Location = new System.Drawing.Point(76, 62); + this.mTimeOfDay.Name = "mTimeOfDay"; + this.mTimeOfDay.Size = new System.Drawing.Size(198, 21); + this.mTimeOfDay.TabIndex = 2; + this.mMapLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mMapLabel.AutoSize = true; + this.mMapLabel.Location = new System.Drawing.Point(3, 8); + this.mMapLabel.Name = "mMapLabel"; + this.mMapLabel.Size = new System.Drawing.Size(31, 13); + this.mMapLabel.TabIndex = 0; + this.mMapLabel.Text = "Map:"; + this.mWeatherLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mWeatherLabel.AutoSize = true; + this.mWeatherLabel.Location = new System.Drawing.Point(3, 37); + this.mWeatherLabel.Name = "mWeatherLabel"; + this.mWeatherLabel.Size = new System.Drawing.Size(51, 13); + this.mWeatherLabel.TabIndex = 2; + this.mWeatherLabel.Text = "Weather:"; + this.mTimeOfDayLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mTimeOfDayLabel.AutoSize = true; + this.mTimeOfDayLabel.Location = new System.Drawing.Point(3, 66); + this.mTimeOfDayLabel.Name = "mTimeOfDayLabel"; + this.mTimeOfDayLabel.Size = new System.Drawing.Size(67, 13); + this.mTimeOfDayLabel.TabIndex = 4; + this.mTimeOfDayLabel.Text = "Time of Day:"; + this.mScoreCompression.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mScoreCompression.AutoSize = true; + this.mMissionPropertiesTable.SetColumnSpan(this.mScoreCompression, 3); + this.mScoreCompression.Location = new System.Drawing.Point(311, 6); + this.mScoreCompression.Name = "mScoreCompression"; + this.mScoreCompression.Size = new System.Drawing.Size(117, 17); + this.mScoreCompression.TabIndex = 6; + this.mScoreCompression.Text = "Score Compression"; + this.mScoreCompression.UseVisualStyleBackColor = true; + this.mGoButton.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mMissionPropertiesTable.SetColumnSpan(this.mGoButton, 3); + this.mGoButton.ContextMenuStrip = this.mResetContextMenu; + this.mGoButton.Location = new System.Drawing.Point(311, 61); + this.mGoButton.Name = "mGoButton"; + this.mGoButton.Size = new System.Drawing.Size(212, 23); + this.mGoButton.TabIndex = 10; + this.mGoButton.Text = "Load"; + this.mGoButton.UseVisualStyleBackColor = true; + this.mGoButton.Click += new System.EventHandler(mGoButton_Click); + this.mResetContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[1] { this.resetToolStripMenuItem }); + this.mResetContextMenu.Name = "mResetContextMenu"; + this.mResetContextMenu.Size = new System.Drawing.Size(103, 26); + this.resetToolStripMenuItem.Name = "resetToolStripMenuItem"; + this.resetToolStripMenuItem.Size = new System.Drawing.Size(102, 22); + this.resetToolStripMenuItem.Text = "&Reset"; + this.resetToolStripMenuItem.Click += new System.EventHandler(resetToolStripMenuItem_Click); + this.mPrintLastMission.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mPrintLastMission.AutoSize = true; + this.mPrintLastMission.Enabled = false; + this.mPrintLastMission.Location = new System.Drawing.Point(529, 61); + this.mPrintLastMission.Name = "mPrintLastMission"; + this.mPrintLastMission.Size = new System.Drawing.Size(107, 23); + this.mPrintLastMission.TabIndex = 11; + this.mPrintLastMission.Text = "Print Last Mission"; + this.mPrintLastMission.UseVisualStyleBackColor = true; + this.mPrintLastMission.Click += new System.EventHandler(mPrintLastMission_Click); + this.mAutoPrint.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mAutoPrint.AutoSize = true; + this.mAutoPrint.Location = new System.Drawing.Point(529, 35); + this.mAutoPrint.Name = "mAutoPrint"; + this.mAutoPrint.Size = new System.Drawing.Size(72, 17); + this.mAutoPrint.TabIndex = 9; + this.mAutoPrint.Text = "Auto Print"; + this.mAutoPrint.UseVisualStyleBackColor = true; + this.mAutoTranslocateCheckbox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.mAutoTranslocateCheckbox.AutoSize = true; + this.mAutoTranslocateCheckbox.Location = new System.Drawing.Point(529, 6); + this.mAutoTranslocateCheckbox.Name = "mAutoTranslocateCheckbox"; + this.mAutoTranslocateCheckbox.Size = new System.Drawing.Size(107, 17); + this.mAutoTranslocateCheckbox.TabIndex = 8; + this.mAutoTranslocateCheckbox.Text = "Auto Translocate"; + this.mAutoTranslocateCheckbox.UseVisualStyleBackColor = true; + this.mRandomMap.Location = new System.Drawing.Point(280, 3); + this.mRandomMap.Name = "mRandomMap"; + this.mRandomMap.Size = new System.Drawing.Size(25, 23); + this.mRandomMap.TabIndex = 3; + this.mRandomMap.Text = "R"; + this.mRandomMap.UseVisualStyleBackColor = true; + this.mRandomMap.Click += new System.EventHandler(mRandomMap_Click); + this.mRandomWeather.Location = new System.Drawing.Point(280, 32); + this.mRandomWeather.Name = "mRandomWeather"; + this.mRandomWeather.Size = new System.Drawing.Size(25, 23); + this.mRandomWeather.TabIndex = 4; + this.mRandomWeather.Text = "R"; + this.mRandomWeather.UseVisualStyleBackColor = true; + this.mRandomWeather.Click += new System.EventHandler(mRandomWeather_Click); + this.mRandomTimeOfDay.Location = new System.Drawing.Point(280, 61); + this.mRandomTimeOfDay.Name = "mRandomTimeOfDay"; + this.mRandomTimeOfDay.Size = new System.Drawing.Size(25, 23); + this.mRandomTimeOfDay.TabIndex = 5; + this.mRandomTimeOfDay.Text = "R"; + this.mRandomTimeOfDay.UseVisualStyleBackColor = true; + this.mRandomTimeOfDay.Click += new System.EventHandler(mRandomTimeOfDay_Click); + this.mMissionLengthTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mMissionLengthTextBox.Location = new System.Drawing.Point(423, 33); + this.mMissionLengthTextBox.Name = "mMissionLengthTextBox"; + this.mMissionLengthTextBox.Size = new System.Drawing.Size(100, 20); + this.mMissionLengthTextBox.TabIndex = 12; + this.mMissionLengthTextBox.TextChanged += new System.EventHandler(mMissionLengthTextBox_TextChanged); + this.mPilotsGroupBox.Controls.Add(this.mPilotsDataGrid); + this.mPilotsGroupBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.mPilotsGroupBox.Location = new System.Drawing.Point(8, 113); + this.mPilotsGroupBox.Name = "mPilotsGroupBox"; + this.mPilotsGroupBox.Padding = new System.Windows.Forms.Padding(5); + this.mPilotsGroupBox.Size = new System.Drawing.Size(1040, 425); + this.mPilotsGroupBox.TabIndex = 11; + this.mPilotsGroupBox.TabStop = false; + this.mPilotsGroupBox.Text = "Pilots"; + this.mPilotsDataGrid.AllowDrop = true; + this.mPilotsDataGrid.AllowUserToAddRows = false; + this.mPilotsDataGrid.AllowUserToDeleteRows = false; + this.mPilotsDataGrid.AllowUserToResizeColumns = false; + this.mPilotsDataGrid.AllowUserToResizeRows = false; + this.mPilotsDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.mPilotsDataGrid.Columns.AddRange(this.mEnabledColumn, this.mStatusIconColumn, this.mStatusTextColumn, this.mPilotColumn, this.mVehicleColumn, this.mColorColumn, this.mBadgeColumn); + this.mPilotsDataGrid.Dock = System.Windows.Forms.DockStyle.Fill; + this.mPilotsDataGrid.Location = new System.Drawing.Point(5, 18); + this.mPilotsDataGrid.MultiSelect = false; + this.mPilotsDataGrid.Name = "mPilotsDataGrid"; + this.mPilotsDataGrid.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders; + this.mPilotsDataGrid.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.CellSelect; + this.mPilotsDataGrid.ShowEditingIcon = false; + this.mPilotsDataGrid.Size = new System.Drawing.Size(1030, 402); + this.mPilotsDataGrid.TabIndex = 0; + this.mPilotsDataGrid.MouseDown += new System.Windows.Forms.MouseEventHandler(mPilotsDataGrid_MouseDown); + this.mPilotsDataGrid.CellBeginEdit += new System.Windows.Forms.DataGridViewCellCancelEventHandler(mPilotsDataGrid_CellBeginEdit); + this.mPilotsDataGrid.DragOver += new System.Windows.Forms.DragEventHandler(mPilotsDataGrid_DragOver); + this.mPilotsDataGrid.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(mPilotsDataGrid_CellEndEdit); + this.mPilotsDataGrid.EditingControlShowing += new System.Windows.Forms.DataGridViewEditingControlShowingEventHandler(mPilotsDataGrid_EditingControlShowing); + this.mPilotsDataGrid.KeyUp += new System.Windows.Forms.KeyEventHandler(mPilotsDataGrid_KeyUp); + this.mPilotsDataGrid.DragDrop += new System.Windows.Forms.DragEventHandler(mPilotsDataGrid_DragDrop); + this.mEnabledColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells; + this.mEnabledColumn.HeaderText = "Enabled"; + this.mEnabledColumn.Name = "mEnabledColumn"; + this.mEnabledColumn.Width = 52; + this.mStatusIconColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.mStatusIconColumn.HeaderText = "Status"; + this.mStatusIconColumn.Name = "mStatusIconColumn"; + this.mStatusIconColumn.ReadOnly = true; + this.mStatusIconColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.mStatusIconColumn.Width = 43; + this.mStatusTextColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.mStatusTextColumn.HeaderText = "Status Text"; + this.mStatusTextColumn.MinimumWidth = 100; + this.mStatusTextColumn.Name = "mStatusTextColumn"; + this.mStatusTextColumn.ReadOnly = true; + this.mPilotColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.mPilotColumn.HeaderText = "Pilot"; + this.mPilotColumn.MinimumWidth = 100; + this.mPilotColumn.Name = "mPilotColumn"; + this.mPilotColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.mVehicleColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.mVehicleColumn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.mVehicleColumn.HeaderText = "Vehicle"; + this.mVehicleColumn.MinimumWidth = 100; + this.mVehicleColumn.Name = "mVehicleColumn"; + this.mColorColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.mColorColumn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.mColorColumn.HeaderText = "Color"; + this.mColorColumn.MinimumWidth = 100; + this.mColorColumn.Name = "mColorColumn"; + this.mBadgeColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.mBadgeColumn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.mBadgeColumn.HeaderText = "Badge"; + this.mBadgeColumn.MinimumWidth = 100; + this.mBadgeColumn.Name = "mBadgeColumn"; + this.mDragDropContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[2] { this.mSwapPilots, this.mShiftPilots }); + this.mDragDropContextMenu.Name = "contextMenuStrip1"; + this.mDragDropContextMenu.Size = new System.Drawing.Size(103, 48); + this.mSwapPilots.Name = "mSwapPilots"; + this.mSwapPilots.Size = new System.Drawing.Size(102, 22); + this.mSwapPilots.Text = "Swap"; + this.mSwapPilots.Click += new System.EventHandler(swapToolStripMenuItem_Click); + this.mShiftPilots.Name = "mShiftPilots"; + this.mShiftPilots.Size = new System.Drawing.Size(102, 22); + this.mShiftPilots.Text = "Shift"; + this.mShiftPilots.Click += new System.EventHandler(shiftToolStripMenuItem_Click); + this.mNetworkTimer.Enabled = true; + this.mNetworkTimer.Tick += new System.EventHandler(NetworkScan); + this.tmrAutoTranslocate.Interval = 1000; + this.tmrAutoTranslocate.Tick += new System.EventHandler(tmrAutoTranslocate_Tick); + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(1056, 546); + base.Controls.Add(this.mPilotsGroupBox); + base.Controls.Add(this.mMissionPropertiesGroupBox); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25f, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + base.KeyPreview = true; + base.Name = "RPGame"; + base.Padding = new System.Windows.Forms.Padding(8); + base.TabText = "Red Planet Game"; + this.Text = "Red Planet Game"; + this.mMissionPropertiesGroupBox.ResumeLayout(false); + this.mMissionPropertiesGroupBox.PerformLayout(); + this.mMissionPropertiesTable.ResumeLayout(false); + this.mMissionPropertiesTable.PerformLayout(); + this.mResetContextMenu.ResumeLayout(false); + this.mPilotsGroupBox.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)this.mPilotsDataGrid).EndInit(); + this.mDragDropContextMenu.ResumeLayout(false); + base.ResumeLayout(false); + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPMap.cs b/Console/TeslaConsole.RedPlanet/RPMap.cs new file mode 100644 index 0000000..6e92716 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPMap.cs @@ -0,0 +1,44 @@ +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +public class RPMap +{ + private readonly string mKey; + + private readonly string mName; + + private readonly string mImagePath; + + public string Key => mKey; + + public string Name => mName; + + public Image Image + { + get + { + if (mImagePath == null) + { + return null; + } + return ImageCache.GetImage(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), mImagePath)); + } + } + + internal RPMap(XmlNode mapNode) + { + XmlAttribute xmlAttribute = mapNode.Attributes["image"]; + mKey = mapNode.Attributes["key"].InnerText; + mName = mapNode.Attributes["name"].InnerText; + mImagePath = xmlAttribute?.InnerText; + } + + public override string ToString() + { + return mName; + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPMission.cs b/Console/TeslaConsole.RedPlanet/RPMission.cs new file mode 100644 index 0000000..0124c3f --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPMission.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Munga.Net; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal abstract class RPMission +{ + private string mScenarioKey; + + private string mMapKey; + + private string mTimeOfDayKey; + + private string mWeatherKey; + + private bool mScoreCompression; + + private TimeSpan mGameLength; + + private readonly List mCameras = new List(); + + public string ScenarioKey => mScenarioKey; + + public string MapKey => mMapKey; + + public string TimeOfDayKey => mTimeOfDayKey; + + public string WeatherKey => mWeatherKey; + + public bool ScoreCompression => mScoreCompression; + + public TimeSpan GameLength => mGameLength; + + public abstract IEnumerable Players { get; } + + public abstract int PlayerCount { get; } + + public List Cameras => mCameras; + + protected RPMission(string scenarioKey, string mapKey, string timeOfDayKey, string weatherKey, bool scoreCompression, TimeSpan gameLength) + { + if (scenarioKey == null) + { + throw new ArgumentNullException("scenarioKey"); + } + if (mapKey == null) + { + throw new ArgumentNullException("mapKey"); + } + if (timeOfDayKey == null) + { + throw new ArgumentNullException("timeOfDayKey"); + } + if (weatherKey == null) + { + throw new ArgumentNullException("weatherKey"); + } + if (gameLength <= TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException("gameLength"); + } + mScenarioKey = scenarioKey; + mMapKey = mapKey; + mTimeOfDayKey = timeOfDayKey; + mWeatherKey = weatherKey; + mScoreCompression = scoreCompression; + mGameLength = gameLength; + } + + public EggFileMessage[] ToEggFileMessages() + { + string text = ToEggString(); + text = text.Replace("\r\n", "\0"); + text = text.Replace('\n', '\0'); + byte[] bytes = Encoding.ASCII.GetBytes(text); + EggFileMessage[] array = new EggFileMessage[(bytes.Length + 999) / 1000]; + byte[] array2 = new byte[1000]; + for (int i = 0; i < array.Length; i++) + { + int num = i * 1000; + int num2 = bytes.Length - num; + if (num2 > 1000) + { + num2 = 1000; + } + Buffer.BlockCopy(bytes, num, array2, 0, num2); + array[i] = new EggFileMessage(i, bytes.Length, num2, array2); + } + return array; + } + + public string ToEggString() + { + StringBuilder stringBuilder = new StringBuilder(); + List list = new List(); + stringBuilder.Append("[mission]\n"); + stringBuilder.Append("adventure=Red Planet\n"); + stringBuilder.AppendFormat("map={0}\n", mMapKey); + stringBuilder.AppendFormat("scenario={0}\n", mScenarioKey); + stringBuilder.AppendFormat("time={0}\n", mTimeOfDayKey); + stringBuilder.AppendFormat("weather={0}\n", mWeatherKey); + stringBuilder.AppendFormat("temperature=0\n"); + stringBuilder.AppendFormat("compression={0}\n", mScoreCompression ? "1" : "0"); + stringBuilder.AppendFormat("length={0}\n", mGameLength.TotalSeconds); + stringBuilder.Append("[pilots]\n"); + foreach (RPPlayer player in Players) + { + stringBuilder.AppendFormat("pilot={0}\n", player.PodHostAddress); + } + foreach (RPCamera camera in Cameras) + { + stringBuilder.AppendFormat("pilot={0}\n", camera.PodHostAddress); + } + int num = 1; + foreach (RPPlayer player2 in Players) + { + player2.AppendParticipant(num++, stringBuilder); + list.Add(player2.Name); + } + foreach (RPCamera camera2 in Cameras) + { + camera2.AppendParticipant(num++, stringBuilder); + list.Add("Camera"); + } + AppendMissionSpecificData(stringBuilder); + stringBuilder.Append("[largebitmap]\n"); + foreach (string item in list) + { + stringBuilder.AppendFormat("bitmap=BitMap::Large::{0}\n", item); + } + stringBuilder.Append("[smallbitmap]\n"); + foreach (string item2 in list) + { + stringBuilder.AppendFormat("bitmap=BitMap::Small::{0}\n", item2); + } + foreach (string item3 in list) + { + stringBuilder.AppendFormat("[BitMap::Large::{0}]\n", item3); + stringBuilder.Append(PlasmaBitmaps.GenerateString(128, 32, item3)); + stringBuilder.Append("width=8\n"); + stringBuilder.AppendFormat("[BitMap::Small::{0}]\n", item3); + stringBuilder.Append(PlasmaBitmaps.GenerateString(64, 16, item3)); + stringBuilder.Append("width=4\n"); + } + AppendOrdinals(stringBuilder); + return stringBuilder.ToString(); + } + + protected abstract void AppendMissionSpecificData(StringBuilder egg); + + private static void AppendOrdinals(StringBuilder sb) + { + sb.Append("[ordinals]\n"); + sb.Append("bitmap=Ordinal::BitMap::1\n"); + sb.Append("bitmap=Ordinal::BitMap::2\n"); + sb.Append("bitmap=Ordinal::BitMap::3\n"); + sb.Append("bitmap=Ordinal::BitMap::4\n"); + sb.Append("[Ordinal::BitMap::1]\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=000038000003C000001FF00000000F00\n"); + sb.Append("bitmap=0000F8000003C000003FF80000000F00\n"); + sb.Append("bitmap=0001F8000003C00000707C0000000F00\n"); + sb.Append("bitmap=0001F8000003C00000603C0000000F00\n"); + sb.Append("bitmap=00007801FC0FF00000003C3DF807FF00\n"); + sb.Append("bitmap=00007803FE0FF00000003C3FFC0FFF00\n"); + sb.Append("bitmap=00007803C703C00000003C3E3C1F0F00\n"); + sb.Append("bitmap=000078078303C0000000783C1E1E0F00\n"); + sb.Append("bitmap=000078078003C0000000783C1E1E0F00\n"); + sb.Append("bitmap=00007807C003C0000000F03C1E1E0F00\n"); + sb.Append("bitmap=00007807F003C0000001E03C1E1E0F00\n"); + sb.Append("bitmap=00007803FC03C0000003C03C1E1E0F00\n"); + sb.Append("bitmap=00007801FE03C0000007803C1E1E0F00\n"); + sb.Append("bitmap=000078007F03C000000F003C1E1E0F00\n"); + sb.Append("bitmap=000078001F03C000001E003C1E1E0F00\n"); + sb.Append("bitmap=000078000F03C000003C003C1E1E0F00\n"); + sb.Append("bitmap=000078060F03C0000078003C1E1E0F00\n"); + sb.Append("bitmap=000078071E03E0000078003C1E1F1F00\n"); + sb.Append("bitmap=00007803FE01F000007FFC3C1E0FFF00\n"); + sb.Append("bitmap=00007801FC00F000007FFC3C1E07EF00\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("x=128\n"); + sb.Append("y=32\n"); + sb.Append("width=8\n"); + sb.Append("[Ordinal::BitMap::2]\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=001FFF0000003C0000003C03C0780000\n"); + sb.Append("bitmap=001FFF0000003C0000007C03C0780000\n"); + sb.Append("bitmap=00000E0000003C000000FC03C0780000\n"); + sb.Append("bitmap=00003C0000003C000001BC03C0780000\n"); + sb.Append("bitmap=0000700F3E1FFC000003BC0FF07BF000\n"); + sb.Append("bitmap=0001E00F7E3FFC0000073C0FF07FF800\n"); + sb.Append("bitmap=0003800FFE7C3C0000063C03C07C7800\n"); + sb.Append("bitmap=0007F80FFE783C00000C3C03C0783C00\n"); + sb.Append("bitmap=0007FE0F80783C0000183C03C0783C00\n"); + sb.Append("bitmap=00001E0F00783C0000383C03C0783C00\n"); + sb.Append("bitmap=00000F0F00783C0000703C03C0783C00\n"); + sb.Append("bitmap=00000F0F00783C00007FFF03C0783C00\n"); + sb.Append("bitmap=00000F0F00783C00007FFF03C0783C00\n"); + sb.Append("bitmap=00000F0F00783C0000003C03C0783C00\n"); + sb.Append("bitmap=00000F0F00783C0000003C03C0783C00\n"); + sb.Append("bitmap=00000F0F00783C0000003C03C0783C00\n"); + sb.Append("bitmap=00180F0F00783C0000003C03C0783C00\n"); + sb.Append("bitmap=001C1F0F007C7C0000003C03E0783C00\n"); + sb.Append("bitmap=000FFE0F003FFC0000003C01F0783C00\n"); + sb.Append("bitmap=0007FC0F001FBC0000003C00F0783C00\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("x=128\n"); + sb.Append("y=32\n"); + sb.Append("width=8\n"); + sb.Append("[Ordinal::BitMap::3]\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=001FFF03C07800000000FC03C0780000\n"); + sb.Append("bitmap=001FFF03C07800000003FC03C0780000\n"); + sb.Append("bitmap=001E0003C07800000007C003C0780000\n"); + sb.Append("bitmap=001E0003C0780000000F0003C0780000\n"); + sb.Append("bitmap=001E000FF07BF000000F000FF07BF000\n"); + sb.Append("bitmap=001E000FF07FF800001E000FF07FF800\n"); + sb.Append("bitmap=001E0003C07C7800001E0003C07C7800\n"); + sb.Append("bitmap=001FFC03C0783C00001EFC03C0783C00\n"); + sb.Append("bitmap=001FFE03C0783C00001FFE03C0783C00\n"); + sb.Append("bitmap=00001F03C0783C00001F1F03C0783C00\n"); + sb.Append("bitmap=00000F03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=00000F03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=00000F03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=00000F03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=00000F03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=00000F03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=00180F03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=001C1F03E0783C00001F1F03E0783C00\n"); + sb.Append("bitmap=000FFE01F0783C00000FFE01F0783C00\n"); + sb.Append("bitmap=0007FC00F0783C000007FC00F0783C00\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("x=128\n"); + sb.Append("y=32\n"); + sb.Append("width=8\n"); + sb.Append("[Ordinal::BitMap::4]\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=001FFF03C07800000007FC03C0780000\n"); + sb.Append("bitmap=001FFF03C0780000000FFE03C0780000\n"); + sb.Append("bitmap=00000F03C0780000001F1F03C0780000\n"); + sb.Append("bitmap=00000F03C0780000001E0F03C0780000\n"); + sb.Append("bitmap=00000F0FF07BF000001E0F0FF07BF000\n"); + sb.Append("bitmap=00001F0FF07FF800001E0F0FF07FF800\n"); + sb.Append("bitmap=00001E03C07C7800001E0F03C07C7800\n"); + sb.Append("bitmap=00003E03C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=00003C03C0783C00000F1E03C0783C00\n"); + sb.Append("bitmap=00003C03C0783C000007FC03C0783C00\n"); + sb.Append("bitmap=00007803C0783C000007FC03C0783C00\n"); + sb.Append("bitmap=00007803C0783C00000F1E03C0783C00\n"); + sb.Append("bitmap=00007803C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=0000F003C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=0000F003C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=0000F003C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=0000F003C0783C00001E0F03C0783C00\n"); + sb.Append("bitmap=0000F003E0783C00000F1E03E0783C00\n"); + sb.Append("bitmap=0000F001F0783C00000FFE01F0783C00\n"); + sb.Append("bitmap=0000F000F0783C000007FC00F0783C00\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("bitmap=00000000000000000000000000000000\n"); + sb.Append("x=128\n"); + sb.Append("y=32\n"); + sb.Append("width=8\n"); + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPMissionRecorder.cs b/Console/TeslaConsole.RedPlanet/RPMissionRecorder.cs new file mode 100644 index 0000000..da8c320 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPMissionRecorder.cs @@ -0,0 +1,101 @@ +using System; + +namespace TeslaConsole.RedPlanet; + +internal class RPMissionRecorder +{ + private RPMissionResults mMissionResults; + + private bool mStartSet; + + public RPMissionRecorder(RPMission mission) + { + mMissionResults = new RPMissionResults(mission); + } + + public void SetMissionStart() + { + if (mStartSet) + { + throw new InvalidOperationException(); + } + mMissionResults.mMissionStart = DateTime.Now; + mStartSet = true; + } + + private void ValidatePlayer(RPPlayer player, string param) + { + if (player == null) + { + throw new ArgumentNullException(param); + } + foreach (RPPlayer player2 in mMissionResults.Mission.Players) + { + if (player2 == player) + { + return; + } + } + throw new ArgumentException(param); + } + + private TimeSpan GetMissionTime() + { + if (!mStartSet) + { + return TimeSpan.Zero; + } + return DateTime.Now - mMissionResults.mMissionStart; + } + + public void PlayerScored(RPPlayer reportingPlayer) + { + ValidatePlayer(reportingPlayer, "reportingPlayer"); + TimeSpan missionTime = GetMissionTime(); + mMissionResults.mEvents.Add(new ScoredEvent(reportingPlayer, missionTime)); + } + + public void PlayerKilled(RPPlayer reportingPlayer, RPPlayer killer) + { + ValidatePlayer(reportingPlayer, "reportingPlayer"); + ValidatePlayer(killer, "killer"); + TimeSpan missionTime = GetMissionTime(); + mMissionResults.mEvents.Add(new KilledEvent(reportingPlayer, missionTime, killer)); + } + + public void PlayerBoost(RPPlayer reportingPlayer, int boosterOn) + { + ValidatePlayer(reportingPlayer, "reportingPlayer"); + TimeSpan missionTime = GetMissionTime(); + mMissionResults.mEvents.Add(new BoostEvent(reportingPlayer, missionTime, boosterOn)); + } + + public void PlayerScoreUpdate(RPPlayer reportingPlayer, int score) + { + ValidatePlayer(reportingPlayer, "reportingPlayer"); + TimeSpan missionTime = GetMissionTime(); + mMissionResults.mEvents.Add(new ScoreUpdateEvent(reportingPlayer, missionTime, score)); + } + + public void PlayerDamaged(RPPlayer reportingPlayer, RPPlayer damager, int damageLoss, int pointsTransfered) + { + ValidatePlayer(reportingPlayer, "reportingPlayer"); + ValidatePlayer(damager, "damager"); + TimeSpan missionTime = GetMissionTime(); + mMissionResults.mEvents.Add(new DamagedEvent(reportingPlayer, missionTime, damager, damageLoss, pointsTransfered)); + } + + public void PlayerFinalScore(RPPlayer reportingPlayer, int finalScore) + { + ValidatePlayer(reportingPlayer, "reportingPlayer"); + mMissionResults.mFinalScores.Add(reportingPlayer, finalScore); + } + + public RPMissionResults EndMission(TimeSpan missionLength) + { + mMissionResults.mActuallMissionTime = missionLength; + RPMissionResults result = mMissionResults; + mMissionResults = null; + return result; + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPMissionResults.cs b/Console/TeslaConsole.RedPlanet/RPMissionResults.cs new file mode 100644 index 0000000..7f6247c --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPMissionResults.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Printing; +using System.IO; +using System.IO.Compression; +using System.Runtime.Serialization.Formatters.Binary; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class RPMissionResults +{ + private RPMission mMission; + + internal DateTime mMissionStart; + + internal TimeSpan mActuallMissionTime; + + internal readonly List mEvents = new List(); + + internal readonly Dictionary mFinalScores = new Dictionary(); + + public RPMission Mission => mMission; + + public DateTime MissionStart => mMissionStart; + + public TimeSpan ActuallMissionTime => mActuallMissionTime; + + public IEnumerable Events => mEvents; + + internal RPMissionResults(RPMission mission) + { + mMission = mission; + } + + public static string GetRPMissionsDirectory() + { + string text = Path.Combine(Program.GetCommonAppDataDirectory(), "RP Missions"); + if (!Directory.Exists(text)) + { + Directory.CreateDirectory(text); + } + return text; + } + + public static RPMissionResults Load(string file) + { + using GZipStream gZipStream = new GZipStream(File.OpenRead(file), CompressionMode.Decompress); + BinaryFormatter binaryFormatter = new BinaryFormatter(); + RPMissionResults result = (RPMissionResults)binaryFormatter.Deserialize(gZipStream); + gZipStream.Close(); + return result; + } + + public void Save() + { + string path = Path.Combine(GetRPMissionsDirectory(), mMissionStart.ToString("yyyy-MM-dd HH-mm-ss.ffff") + ".rpm"); + using GZipStream gZipStream = new GZipStream(File.OpenWrite(path), CompressionMode.Compress); + BinaryFormatter binaryFormatter = new BinaryFormatter(); + binaryFormatter.Serialize(gZipStream, this); + gZipStream.Close(); + } + + private RPPlayer GetScoreReportingPlayer(RPPlayer player) + { + if (player is RPFootballPlayer) + { + RPFootballPlayer rPFootballPlayer = (RPFootballPlayer)player; + if (rPFootballPlayer.PositionKey != "runner") + { + RPFootballMission rPFootballMission = (RPFootballMission)mMission; + { + foreach (RPFootballPlayer footballPlayer in rPFootballMission.FootballPlayers) + { + if (footballPlayer.PositionKey == "runner" && footballPlayer.TeamKey == rPFootballPlayer.TeamKey) + { + return footballPlayer; + } + } + return player; + } + } + } + return player; + } + + public int GetScore(RPPlayer player) + { + return GetScore(player, TimeSpan.MaxValue); + } + + public int GetScore(RPPlayer player, TimeSpan gameTime) + { + RPPlayer scoreReportingPlayer = GetScoreReportingPlayer(player); + if (gameTime == TimeSpan.MaxValue && mFinalScores.ContainsKey(scoreReportingPlayer)) + { + return mFinalScores[scoreReportingPlayer]; + } + int result = 1000; + TimeSpan timeSpan = TimeSpan.MinValue; + foreach (MissionEvent mEvent in mEvents) + { + if (mEvent is ScoreUpdateEvent && mEvent.ReportingPilot == scoreReportingPlayer && mEvent.GameTime > timeSpan && mEvent.GameTime <= gameTime) + { + result = ((ScoreUpdateEvent)mEvent).Score; + timeSpan = mEvent.GameTime; + } + } + return result; + } + + public int GetFinalPlace(RPPlayer player) + { + int num = 1; + int score = GetScore(player, TimeSpan.MaxValue); + if (mMission is RPFootballMission) + { + RPFootballMission rPFootballMission = (RPFootballMission)mMission; + RPFootballPlayer rPFootballPlayer = (RPFootballPlayer)player; + { + foreach (RPFootballPlayer footballPlayer in rPFootballMission.FootballPlayers) + { + if (footballPlayer.PositionKey == "runner" && footballPlayer.TeamKey != rPFootballPlayer.TeamKey && GetScore(footballPlayer, TimeSpan.MaxValue) > score) + { + num++; + } + } + return num; + } + } + foreach (RPPlayer player2 in mMission.Players) + { + if (player2 != player && GetScore(player2, TimeSpan.MaxValue) > score) + { + num++; + } + } + return num; + } + + public RPPlayer[][] GetChartOrder() + { + return GetChartOrder(TimeSpan.MaxValue); + } + + public RPPlayer[][] GetChartOrder(TimeSpan gameTime) + { + List> list = new List>(); + if (mMission is RPFootballMission) + { + RPFootballMission rPFootballMission = (RPFootballMission)mMission; + Dictionary> dictionary = new Dictionary>(); + foreach (RPFootballPlayer footballPlayer in rPFootballMission.FootballPlayers) + { + if (!dictionary.ContainsKey(footballPlayer.TeamKey)) + { + dictionary.Add(footballPlayer.TeamKey, new List()); + } + if (footballPlayer.PositionKey == "runner") + { + dictionary[footballPlayer.TeamKey].Insert(0, footballPlayer); + } + else + { + dictionary[footballPlayer.TeamKey].Add(footballPlayer); + } + } + foreach (List value2 in dictionary.Values) + { + list.Add(new Tuple(value2.ToArray(), GetScore(value2[0], gameTime))); + } + } + else + { + foreach (RPPlayer player in Mission.Players) + { + list.Add(new Tuple(new RPPlayer[1] { player }, GetScore(player, gameTime))); + } + } + for (int i = 0; i < list.Count - 1; i++) + { + for (int j = i + 1; j < list.Count; j++) + { + if (list[i].B < list[j].B || (list[i].B == list[j].B && string.CompareOrdinal(list[i].A[0].Name, list[j].A[0].Name) > 0)) + { + Tuple value = list[i]; + list[i] = list[j]; + list[j] = value; + } + } + } + List list2 = new List(); + foreach (Tuple item in list) + { + list2.Add(item.A); + } + return list2.ToArray(); + } + + public PrintDocument GetPrintDocument() + { + return new RPPrintDocument(this); + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPParticipant.cs b/Console/TeslaConsole.RedPlanet/RPParticipant.cs new file mode 100644 index 0000000..24b6f0d --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPParticipant.cs @@ -0,0 +1,31 @@ +using System; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal abstract class RPParticipant +{ + private readonly string mPodHostAddress; + + private readonly HostType mHostType; + + public string PodHostAddress => mPodHostAddress; + + public HostType HostType => mHostType; + + protected RPParticipant(string podHostAddress, HostType hostType) + { + mPodHostAddress = podHostAddress; + mHostType = hostType; + } + + public void AppendParticipant(int index, StringBuilder sb) + { + sb.AppendFormat("[{0}]\n", mPodHostAddress); + sb.AppendFormat("hostType={0}\n", (int)mHostType); + AppendParticipantSpecificData(index, sb); + } + + protected abstract void AppendParticipantSpecificData(int index, StringBuilder sb); +} diff --git a/Console/TeslaConsole.RedPlanet/RPPlayer.cs b/Console/TeslaConsole.RedPlanet/RPPlayer.cs new file mode 100644 index 0000000..9faa7e0 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPPlayer.cs @@ -0,0 +1,67 @@ +using System; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal abstract class RPPlayer : RPParticipant +{ + private readonly string mName; + + private readonly string mVehicleKey; + + private readonly string mColorKey; + + private readonly string mBadgeKey; + + public string Name => mName; + + public string VehicleKey => mVehicleKey; + + public string ColorKey => mColorKey; + + public string BadgeKey => mBadgeKey; + + protected RPPlayer(string podHostAddress, string name, string vehicleKey, string colorKey, string badgeKey) + : base(podHostAddress, HostType.GameMachineHostType) + { + if (podHostAddress == null) + { + throw new ArgumentNullException("podHostAddress"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (vehicleKey == null) + { + throw new ArgumentNullException("vehicleKey"); + } + if (colorKey == null) + { + throw new ArgumentNullException("colorKey"); + } + if (badgeKey == null) + { + throw new ArgumentNullException("badgeKey"); + } + mName = name; + mVehicleKey = vehicleKey; + mColorKey = colorKey; + mBadgeKey = badgeKey; + } + + protected sealed override void AppendParticipantSpecificData(int index, StringBuilder sb) + { + sb.Append("dropzone=one\n"); + sb.AppendFormat("name={0}\n", mName); + sb.AppendFormat("bitmapindex={0}\n", index); + sb.Append("loadzones=1\n"); + sb.AppendFormat("vehicle={0}\n", mVehicleKey); + sb.AppendFormat("color={0}\n", mColorKey); + sb.AppendFormat("badge={0}\n", mBadgeKey); + AppendMissionSpecificPilot(index, sb); + } + + protected abstract void AppendMissionSpecificPilot(int index, StringBuilder sb); +} diff --git a/Console/TeslaConsole.RedPlanet/RPPrintDocument.cs b/Console/TeslaConsole.RedPlanet/RPPrintDocument.cs new file mode 100644 index 0000000..a384f6f --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPPrintDocument.cs @@ -0,0 +1,816 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Printing; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +public class RPPrintDocument : PrintDocument +{ + private const int sThinBorderSize = 3; + + private const int sThickBorderSize = 14; + + private const int sChampherSize = 14; + + private const double sSmallRectWidthDropFraction = 0.5721493440968718; + + private const double sSmallRectHeightDropFraction = 245.0 / 372.0; + + private const string sSansSerifFontName = "Helvetica LT Std"; + + private const string sSerifFontName = "Courier New"; + + private readonly RPMissionResults mMissionResults; + + private readonly RPPlayer[][] mFinalRanking; + + private readonly RPPlayer[] mPrintOrder; + + private int mCurrentPlayer; + + private Dictionary> mPlayerLines; + + private int[] mChartRows; + + private Dictionary mChartCols; + + internal RPPrintDocument(RPMissionResults missionResults) + { + mMissionResults = missionResults; + base.DocumentName = string.Format("Red Planet {0} mission at {1} on {2}", (mMissionResults.Mission is RPFootballMission) ? "Football" : "Death Race", mMissionResults.MissionStart.ToLongTimeString(), mMissionResults.MissionStart.ToLongDateString()); + mFinalRanking = mMissionResults.GetChartOrder(); + List list = new List(); + for (int i = 0; i < mFinalRanking.Length; i++) + { + list.Add(mFinalRanking[i][0]); + } + for (int j = 0; j < mFinalRanking.Length; j++) + { + for (int k = 1; k < mFinalRanking[j].Length; k++) + { + list.Add(mFinalRanking[j][k]); + } + } + mPrintOrder = list.ToArray(); + } + + protected override void OnQueryPageSettings(QueryPageSettingsEventArgs e) + { + e.PageSettings.Margins = new Margins(50, 50, 50, 50); + e.PageSettings.Landscape = true; + base.OnQueryPageSettings(e); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + } + + protected override void OnBeginPrint(PrintEventArgs e) + { + mCurrentPlayer = 0; + base.OnBeginPrint(e); + } + + protected override void OnPrintPage(PrintPageEventArgs e) + { + RPPlayer rPPlayer = mPrintOrder[mCurrentPlayer]; + List list = new List(); + foreach (MissionEvent @event in mMissionResults.Events) + { + if (Contains(@event.InvolvedPilots, rPPlayer)) + { + list.Add(@event); + } + } + for (int i = 0; i < list.Count - 1; i++) + { + for (int j = i + 1; j < list.Count; j++) + { + if (list[i].GameTime > list[j].GameTime) + { + MissionEvent value = list[i]; + list[i] = list[j]; + list[j] = value; + } + } + } + int score = mMissionResults.GetScore(rPPlayer); + int finalPlace = mMissionResults.GetFinalPlace(rPPlayer); + Size size = new Size(e.PageBounds.Width - 100, e.PageBounds.Height - 100); + if (size.Width > e.MarginBounds.Width) + { + size.Width = e.MarginBounds.Width; + } + if (size.Height > e.MarginBounds.Height) + { + size.Height = e.MarginBounds.Height; + } + Rectangle rectangle = new Rectangle((e.MarginBounds.Width - size.Width) / 2 + e.MarginBounds.Left, (e.MarginBounds.Height - size.Height) / 2 + e.MarginBounds.Top, size.Width, size.Height); + Rectangle inputRect = rectangle; + inputRect.Inflate(-14, -14); + Rectangle rectangle2 = rectangle; + rectangle2.Inflate(-3, -3); + Rectangle inputRect2 = rectangle2; + inputRect2.Inflate(-14, -14); + Rectangle rectangle3 = rectangle2; + rectangle3.Inflate(-7, -7); + Rectangle inputRect3 = rectangle3; + inputRect3.Inflate(-14, -14); + Rectangle rectangle4 = rectangle2; + rectangle4.Inflate(-14, -14); + Rectangle inputRect4 = rectangle4; + inputRect4.Inflate(-14, -14); + Rectangle rectangle5 = rectangle4; + rectangle5.Inflate(-3, -3); + Rectangle inputRect5 = rectangle5; + inputRect5.Inflate(-14, -14); + int widthDrop = (int)(0.5721493440968718 * (double)rectangle3.Width + 0.5); + int heightDrop = (int)(245.0 / 372.0 * (double)rectangle3.Height + 0.5); + Rectangle rectangle6 = RectDrop(rectangle, widthDrop, heightDrop); + Rectangle rectangle7 = RectDrop(inputRect, widthDrop, heightDrop); + Rectangle rectangle8 = RectDrop(rectangle2, widthDrop, heightDrop); + Rectangle rectangle9 = RectDrop(inputRect2, widthDrop, heightDrop); + Rectangle rectangle10 = RectDrop(rectangle3, widthDrop, heightDrop); + Rectangle rectangle11 = RectDrop(inputRect3, widthDrop, heightDrop); + Rectangle rectangle12 = RectDrop(rectangle4, widthDrop, heightDrop); + Rectangle rectangle13 = RectDrop(inputRect4, widthDrop, heightDrop); + Rectangle rectangle14 = RectDrop(rectangle5, widthDrop, heightDrop); + Rectangle rectangle15 = RectDrop(inputRect5, widthDrop, heightDrop); + using (GraphicsPath graphicsPath = new GraphicsPath()) + { + graphicsPath.AddLine(rectangle.Left, inputRect.Bottom, rectangle.Left, inputRect.Top); + graphicsPath.AddLine(inputRect.Left, rectangle.Top, inputRect.Right, rectangle.Top); + graphicsPath.AddLine(rectangle3.Right, rectangle3.Top, inputRect3.Left, rectangle3.Top); + graphicsPath.AddLine(rectangle3.Left, inputRect3.Top, rectangle3.Left, rectangle3.Bottom); + graphicsPath.StartFigure(); + graphicsPath.AddLine(rectangle3.Left, inputRect5.Bottom, rectangle5.Left, inputRect5.Bottom); + graphicsPath.AddLine(inputRect5.Left, rectangle5.Bottom, inputRect5.Right, rectangle5.Bottom); + graphicsPath.AddLine(rectangle5.Right, inputRect5.Bottom, rectangle5.Right, rectangle3.Top); + graphicsPath.AddLine(rectangle3.Right, rectangle3.Top, rectangle3.Right, inputRect3.Bottom); + graphicsPath.AddLine(inputRect3.Right, rectangle3.Bottom, rectangle3.Left, rectangle3.Bottom); + graphicsPath.StartFigure(); + graphicsPath.AddLine(rectangle10.Left, rectangle15.Bottom, rectangle14.Left, rectangle15.Bottom); + graphicsPath.AddLine(rectangle15.Left, rectangle14.Bottom, rectangle15.Right, rectangle14.Bottom); + graphicsPath.AddLine(rectangle14.Right, rectangle15.Bottom, rectangle14.Right, rectangle10.Top); + graphicsPath.AddLine(rectangle10.Right, rectangle10.Top, rectangle10.Right, rectangle11.Bottom); + graphicsPath.AddLine(rectangle11.Right, rectangle10.Bottom, rectangle10.Left, rectangle10.Bottom); + e.Graphics.FillPath(Brushes.LightGray, graphicsPath); + } + using (GraphicsPath graphicsPath2 = new GraphicsPath()) + { + graphicsPath2.AddLine(rectangle2.Left, inputRect2.Bottom, rectangle.Left, inputRect.Bottom); + graphicsPath2.AddLine(inputRect.Left, rectangle.Bottom, inputRect.Right, rectangle.Bottom); + graphicsPath2.AddLine(rectangle.Right, inputRect.Bottom, rectangle.Right, inputRect.Top); + graphicsPath2.AddLine(inputRect.Right, rectangle.Top, inputRect2.Right, rectangle2.Top); + graphicsPath2.AddLine(inputRect2.Right, rectangle3.Top, rectangle3.Right, inputRect3.Top); + graphicsPath2.AddLine(rectangle3.Right, inputRect3.Bottom, inputRect3.Right, rectangle3.Bottom); + graphicsPath2.AddLine(inputRect2.Left, rectangle3.Bottom, rectangle3.Left, inputRect2.Bottom); + graphicsPath2.StartFigure(); + graphicsPath2.AddLine(rectangle12.Left, rectangle13.Bottom, rectangle14.Left, rectangle15.Bottom); + graphicsPath2.AddLine(rectangle14.Left, rectangle15.Top, rectangle15.Left, rectangle14.Top); + graphicsPath2.AddLine(rectangle15.Right, rectangle14.Top, rectangle14.Right, rectangle15.Top); + graphicsPath2.AddLine(rectangle12.Right, rectangle13.Top, rectangle10.Right, rectangle13.Top); + graphicsPath2.AddLine(rectangle10.Right, rectangle10.Top, rectangle11.Left, rectangle10.Top); + graphicsPath2.AddLine(rectangle10.Left, rectangle11.Top, rectangle10.Left, rectangle13.Bottom); + graphicsPath2.StartFigure(); + graphicsPath2.AddLine(rectangle4.Left, inputRect4.Bottom, rectangle5.Left, inputRect5.Bottom); + graphicsPath2.AddLine(rectangle5.Left, rectangle6.Bottom + 14, inputRect5.Left, rectangle6.Bottom); + graphicsPath2.AddLine(rectangle7.Right, rectangle6.Bottom, rectangle6.Right, rectangle7.Bottom); + graphicsPath2.AddLine(rectangle6.Right, inputRect5.Top, rectangle6.Right + 14, rectangle5.Top); + graphicsPath2.AddLine(inputRect5.Right, rectangle5.Top, rectangle5.Right, inputRect5.Top); + graphicsPath2.AddLine(rectangle4.Right, inputRect4.Top, rectangle3.Right, inputRect4.Top); + graphicsPath2.AddLine(rectangle3.Right, inputRect3.Top, inputRect3.Right, rectangle3.Top); + graphicsPath2.AddLine(rectangle10.Right, rectangle3.Top, rectangle10.Right, rectangle11.Bottom); + graphicsPath2.AddLine(rectangle11.Right, rectangle10.Bottom, inputRect3.Left, rectangle10.Bottom); + graphicsPath2.AddLine(rectangle3.Left, rectangle10.Bottom, rectangle3.Left, inputRect4.Bottom); + e.Graphics.FillPath(Brushes.DarkGray, graphicsPath2); + } + using (GraphicsPath graphicsPath3 = new GraphicsPath()) + { + graphicsPath3.AddLine(rectangle2.Left, inputRect2.Bottom, rectangle2.Left, inputRect2.Top); + graphicsPath3.AddLine(inputRect2.Left, rectangle2.Top, inputRect2.Right, rectangle2.Top); + graphicsPath3.AddLine(rectangle2.Right, inputRect2.Top, rectangle2.Right, inputRect2.Bottom); + graphicsPath3.AddLine(inputRect2.Right, rectangle2.Bottom, inputRect2.Left, rectangle2.Bottom); + graphicsPath3.StartFigure(); + graphicsPath3.AddLine(rectangle12.Left, rectangle13.Bottom, rectangle12.Left, rectangle13.Top); + graphicsPath3.AddLine(rectangle13.Left, rectangle12.Top, rectangle13.Right, rectangle12.Top); + graphicsPath3.AddLine(rectangle12.Right, rectangle13.Top, rectangle12.Right, rectangle13.Bottom); + graphicsPath3.AddLine(rectangle13.Right, rectangle12.Bottom, rectangle13.Left, rectangle12.Bottom); + graphicsPath3.StartFigure(); + graphicsPath3.AddLine(rectangle4.Left, inputRect4.Bottom, rectangle4.Left, rectangle8.Bottom + 14); + graphicsPath3.AddLine(rectangle13.Left, rectangle8.Bottom, rectangle9.Right, rectangle8.Bottom); + graphicsPath3.AddLine(rectangle8.Right, rectangle9.Bottom, rectangle8.Right, inputRect4.Top); + graphicsPath3.AddLine(rectangle8.Right + 14, rectangle4.Top, inputRect4.Right, rectangle4.Top); + graphicsPath3.AddLine(rectangle4.Right, inputRect4.Top, rectangle4.Right, inputRect4.Bottom); + graphicsPath3.AddLine(inputRect4.Right, rectangle4.Bottom, inputRect4.Left, rectangle4.Bottom); + e.Graphics.FillPath(Brushes.Gray, graphicsPath3); + } + using Font font3 = new Font("Helvetica LT Std", 12f); + using Font font = new Font("Helvetica LT Std", 8f); + Rectangle rectangle16 = rectangle14; + rectangle16.X += 175; + rectangle16.Width -= 178; + rectangle16.Y += 58; + rectangle16.Height -= 86; + RPVehicle rPVehicle = (RPConfig.Vehicles.ContainsKey(rPPlayer.VehicleKey) ? RPConfig.Vehicles[rPPlayer.VehicleKey] : null); + Image image = rPVehicle?.Image; + if (image != null) + { + e.Graphics.DrawImage(image, GetCenteredScaledRect(rectangle16, image.Size)); + } + else + { + StringFormat stringFormat = new StringFormat(); + stringFormat.LineAlignment = StringAlignment.Center; + stringFormat.Alignment = StringAlignment.Center; + using (StringFormat format = stringFormat) + { + Rectangle rectangle17 = rectangle16; + rectangle17.Inflate(-20, -20); + e.Graphics.DrawString("Null-flux resonation is disrupting contact with the camera drone. Recon immagry could not be obtained for this vehicle.", font, Brushes.Black, rectangle17, format); + } + e.Graphics.DrawRectangle(Pens.Black, rectangle16); + } + using (Font font2 = new Font("Helvetica LT Std", 18f, FontStyle.Bold)) + { + using StringFormat stringFormat2 = new StringFormat(); + stringFormat2.Alignment = StringAlignment.Center; + stringFormat2.LineAlignment = StringAlignment.Far; + e.Graphics.DrawString(rPPlayer.Name, font2, Brushes.Black, rectangle14.Left + rectangle14.Width / 2, rectangle16.Top - 10, stringFormat2); + } + using (StringFormat stringFormat3 = new StringFormat()) + { + stringFormat3.Alignment = StringAlignment.Center; + e.Graphics.DrawString((rPVehicle != null) ? rPVehicle.Name : rPPlayer.VehicleKey, font3, Brushes.Black, rectangle16.Left + rectangle16.Width / 2, rectangle16.Bottom + 5, stringFormat3); + } + int num = rectangle15.Left - 7; + Rectangle rectangle18 = new Rectangle(num, rectangle16.Top, rectangle16.Left - num, rectangle15.Bottom - rectangle16.Top); + using (Font font4 = new Font("Helvetica LT Std", 16f, FontStyle.Bold)) + { + using StringFormat stringFormat4 = new StringFormat(); + stringFormat4.Alignment = StringAlignment.Center; + StringBuilder stringBuilder = new StringBuilder(); + if (rPPlayer is RPFootballPlayer) + { + RPFootballPlayer rPFootballPlayer = (RPFootballPlayer)rPPlayer; + stringBuilder.AppendLine(RPConfig.Football.Teams.ContainsKey(rPFootballPlayer.TeamKey) ? RPConfig.Football.Teams[rPFootballPlayer.TeamKey].Name : rPFootballPlayer.TeamKey); + stringBuilder.AppendLine(RPConfig.Football.Positions.ContainsKey(rPFootballPlayer.PositionKey) ? RPConfig.Football.Positions[rPFootballPlayer.PositionKey] : rPFootballPlayer.PositionKey); + } + stringBuilder.AppendLine(string.Format("{0}{1} Place", finalPlace, finalPlace switch + { + 3 => "rd", + 2 => "nd", + 1 => "st", + _ => "th", + })); + e.Graphics.DrawString(stringBuilder.ToString(), font4, Brushes.Black, rectangle18, stringFormat4); + } + using (StringFormat stringFormat5 = new StringFormat()) + { + stringFormat5.LineAlignment = StringAlignment.Far; + stringFormat5.SetTabStops(0f, new float[1] { 80f }); + int laps = 0; + TimeSpan fastestLap = TimeSpan.Zero; + int deaths = 0; + int kills = 0; + GetPlayerStats(out laps, out fastestLap, out deaths, out kills, rPPlayer, list); + StringBuilder stringBuilder2 = new StringBuilder(); + if (rPPlayer is RPFootballPlayer) + { + stringBuilder2.AppendLine($"Team Score:\t{score}"); + stringBuilder2.AppendLine(); + } + else + { + stringBuilder2.AppendLine($"Score:\t{score}"); + stringBuilder2.AppendLine(); + stringBuilder2.AppendLine($"Laps:\t{laps}"); + stringBuilder2.AppendLine(); + stringBuilder2.AppendLine($"Fastest Lap:\t{RPStrings.GetTimeString(fastestLap)}"); + stringBuilder2.AppendLine(); + } + stringBuilder2.AppendLine($"Deaths:\t{deaths}"); + stringBuilder2.AppendLine(); + stringBuilder2.AppendLine($"Kills:\t{kills}"); + e.Graphics.DrawString(stringBuilder2.ToString(), font, Brushes.Black, rectangle18, stringFormat5); + } + int num2 = rectangle5.Top + 40; + Rectangle rectangle19 = new Rectangle(rectangle6.Right + 5, num2, rectangle5.Right - rectangle6.Right - 10, rectangle6.Bottom - 5 - num2); + using (StringFormat stringFormat6 = new StringFormat()) + { + stringFormat6.Alignment = StringAlignment.Center; + e.Graphics.DrawString("Mission Highlights", font3, Brushes.Black, (rectangle19.Right + rectangle19.Left) / 2, rectangle5.Top + 15, stringFormat6); + } + using (Font font5 = new Font("Courier New", 10f)) + { + string text = mMissionResults.mMissionStart.ToString() + " "; + string s = text; + string text2 = ""; + if (mMissionResults.Mission is RPRaceMission) + { + List list2 = new List(); + RPPlayer[][] array = mFinalRanking; + foreach (RPPlayer[] array2 in array) + { + list2.Add(array2[0].Name); + } + text2 = RPStrings.Recap(list2.ToArray()); + } + Random random = new Random(); + List list3 = new List(list); + SortedList sortedList = new SortedList(); + string text3 = text + text2; + while (e.Graphics.MeasureString(text3, font5, rectangle19.Width).Height < (float)rectangle19.Height) + { + s = text3; + if (list3.Count < 1) + { + break; + } + int index = random.Next(list3.Count); + MissionEvent missionEvent = list3[index]; + list3.RemoveAt(index); + string text4 = null; + if (missionEvent is BoostEvent && ((BoostEvent)missionEvent).BoosterOn == 1) + { + text4 = RPStrings.Boost(missionEvent.GameTime, missionEvent.ReportingPilot.Name, RPConfig.VehicleNameOrKey(missionEvent.ReportingPilot.VehicleKey)); + } + else if (missionEvent is ScoredEvent) + { + text4 = RPStrings.Score(missionEvent.GameTime, missionEvent.ReportingPilot.Name, RPConfig.VehicleNameOrKey(missionEvent.ReportingPilot.VehicleKey)); + } + else if (missionEvent is DamagedEvent) + { + DamagedEvent damagedEvent = (DamagedEvent)missionEvent; + if (damagedEvent.DamageLoss > 0) + { + text4 = ((damagedEvent.DamageLoss < 10) ? RPStrings.LightDamage(damagedEvent.GameTime, damagedEvent.ReportingPilot.Name, RPConfig.VehicleNameOrKey(damagedEvent.ReportingPilot.VehicleKey), damagedEvent.Damager.Name) : ((damagedEvent.DamageLoss >= 100) ? RPStrings.HeavyDamage(damagedEvent.GameTime, damagedEvent.ReportingPilot.Name, RPConfig.VehicleNameOrKey(damagedEvent.ReportingPilot.VehicleKey), damagedEvent.Damager.Name) : RPStrings.ModerateDamage(damagedEvent.GameTime, damagedEvent.ReportingPilot.Name, RPConfig.VehicleNameOrKey(damagedEvent.ReportingPilot.VehicleKey), damagedEvent.Damager.Name))); + } + } + else if (missionEvent is KilledEvent) + { + KilledEvent killedEvent = (KilledEvent)missionEvent; + text4 = ((killedEvent.ReportingPilot != killedEvent.Killer) ? RPStrings.Kill(killedEvent.GameTime, killedEvent.ReportingPilot.Name, RPConfig.VehicleNameOrKey(killedEvent.ReportingPilot.VehicleKey), killedEvent.Killer.Name) : RPStrings.SelfKill(killedEvent.GameTime, killedEvent.ReportingPilot.Name, RPConfig.VehicleNameOrKey(killedEvent.ReportingPilot.VehicleKey))); + } + if (text4 != null) + { + TimeSpan gameTime; + for (gameTime = missionEvent.GameTime; sortedList.ContainsKey(gameTime); gameTime += TimeSpan.FromTicks(1L)) + { + } + sortedList.Add(gameTime, text4); + } + StringBuilder stringBuilder3 = new StringBuilder(text); + foreach (KeyValuePair item in sortedList) + { + if (stringBuilder3.Length > 0) + { + stringBuilder3.Append(" "); + } + stringBuilder3.Append(item.Value); + } + if (stringBuilder3.Length > 0) + { + stringBuilder3.Append(" "); + } + stringBuilder3.Append(text2); + text3 = stringBuilder3.ToString(); + } + e.Graphics.DrawString(s, font5, Brushes.Black, rectangle19); + } + int num3 = rectangle6.Bottom + 5; + Rectangle rectangle20 = new Rectangle(inputRect5.Left, num3, inputRect5.Width, rectangle5.Bottom - num3); + Rectangle rectangle21 = new Rectangle(rectangle20.Right - 250, rectangle20.Top, 250, rectangle20.Height - 20); + Image image2 = (RPConfig.Maps.ContainsKey(mMissionResults.Mission.MapKey) ? RPConfig.Maps[mMissionResults.Mission.MapKey] : null)?.Image; + if (image2 != null) + { + e.Graphics.DrawImage(image2, GetCenteredScaledRect(rectangle21, image2.Size)); + } + else + { + StringFormat stringFormat7 = new StringFormat(); + stringFormat7.LineAlignment = StringAlignment.Center; + stringFormat7.Alignment = StringAlignment.Center; + using (StringFormat format2 = stringFormat7) + { + Rectangle rectangle22 = rectangle21; + rectangle22.Inflate(-20, -20); + e.Graphics.DrawString("The surveillance satellite is currently on a transitional orbit. Satellite imagery could not be obtained for this map.", font, Brushes.Black, rectangle22, format2); + } + e.Graphics.DrawRectangle(Pens.Black, rectangle21); + } + Rectangle rect = new Rectangle(rectangle20.Left, rectangle20.Top, rectangle21.Left - 5 - rectangle20.Left, 92); + Dictionary dictionary = new Dictionary(); + foreach (RPPlayer player in mMissionResults.Mission.Players) + { + if (player != rPPlayer) + { + dictionary.Add(player, new int[4]); + } + } + foreach (MissionEvent event2 in mMissionResults.Events) + { + if (event2 is KilledEvent) + { + KilledEvent killedEvent2 = (KilledEvent)event2; + if (killedEvent2.ReportingPilot != killedEvent2.Killer) + { + if (killedEvent2.Killer == rPPlayer) + { + dictionary[killedEvent2.ReportingPilot][3]++; + } + else if (killedEvent2.ReportingPilot == rPPlayer) + { + dictionary[killedEvent2.Killer][2]++; + } + } + } + else + { + if (!(event2 is DamagedEvent)) + { + continue; + } + DamagedEvent damagedEvent2 = (DamagedEvent)event2; + if (damagedEvent2.ReportingPilot != damagedEvent2.Damager) + { + if (damagedEvent2.Damager == rPPlayer) + { + dictionary[damagedEvent2.ReportingPilot][1] += damagedEvent2.DamageLoss; + } + else if (damagedEvent2.ReportingPilot == rPPlayer) + { + dictionary[damagedEvent2.Damager][0] += damagedEvent2.DamageLoss; + } + } + } + } + using (Pen pen = new Pen(Color.Gray, 2f)) + { + using Font font6 = new Font("Helvetica LT Std", 6f, FontStyle.Regular); + using StringFormat stringFormat8 = new StringFormat(); + e.Graphics.DrawRectangle(Pens.Gray, rect); + int num4 = 120; + int num5 = 14; + int num6 = rect.Left + 1; + int num7 = rect.Left + num4; + int num8 = (rect.Width - num4) / Math.Max(1, dictionary.Count); + if (dictionary.Count == 0) + { + e.Graphics.DrawLine(pen, num7, rect.Top, num7, rect.Bottom); + } + int num9 = rect.Bottom; + for (int l = 0; l < 5; l++) + { + num9 -= num5; + e.Graphics.DrawLine(Pens.Gray, rect.Left, num9, rect.Right, num9); + } + int num10 = rect.Top + 1; + stringFormat8.LineAlignment = StringAlignment.Near; + e.Graphics.DrawString("Pilot", font6, Brushes.Black, num6, num10, stringFormat8); + int num11 = rect.Bottom - num5 / 2; + int num12 = num11 - num5; + int num13 = num12 - num5; + int num14 = num13 - num5; + int num15 = num14 - num5; + stringFormat8.LineAlignment = StringAlignment.Center; + e.Graphics.DrawString("Score", font6, Brushes.Black, num6, num15, stringFormat8); + e.Graphics.DrawString("Damage You Received From", font6, Brushes.Black, num6, num14, stringFormat8); + e.Graphics.DrawString("Damage You Inflicted On", font6, Brushes.Black, num6, num13, stringFormat8); + e.Graphics.DrawString("Times You Were Killed By", font6, Brushes.Black, num6, num12, stringFormat8); + e.Graphics.DrawString("Times You Killed", font6, Brushes.Black, num6, num11, stringFormat8); + int num16 = num7; + foreach (KeyValuePair item2 in dictionary) + { + int num17 = num16 + 1; + e.Graphics.DrawLine(pen, num16, rect.Top, num16, rect.Bottom); + stringFormat8.LineAlignment = StringAlignment.Near; + e.Graphics.DrawString(item2.Key.Name, font6, Brushes.Black, num17, num10, stringFormat8); + stringFormat8.LineAlignment = StringAlignment.Center; + e.Graphics.DrawString(mMissionResults.GetScore(item2.Key).ToString(), font6, Brushes.Black, num17, num15, stringFormat8); + e.Graphics.DrawString(item2.Value[0].ToString(), font6, Brushes.Black, num17, num14, stringFormat8); + e.Graphics.DrawString(item2.Value[1].ToString(), font6, Brushes.Black, num17, num13, stringFormat8); + e.Graphics.DrawString(item2.Value[2].ToString(), font6, Brushes.Black, num17, num12, stringFormat8); + e.Graphics.DrawString(item2.Value[3].ToString(), font6, Brushes.Black, num17, num11, stringFormat8); + num16 += num8; + } + } + int num18 = rectangle20.Left + 60; + int num19 = rect.Bottom + 120; + Rectangle rectangle23 = new Rectangle(num18, num19, rect.Right - 75 - num18, rectangle20.Bottom - 85 - num19); + if (mPlayerLines == null) + { + mPlayerLines = new Dictionary>(); + RPPlayer[][] array = mFinalRanking; + foreach (RPPlayer[] array3 in array) + { + RPPlayer[] array4 = array3; + foreach (RPPlayer key in array4) + { + mPlayerLines.Add(key, new List()); + } + } + mChartRows = new int[mPlayerLines.Count]; + float num20 = (float)rectangle23.Height / (float)mPlayerLines.Count; + for (int n = 0; n < mChartRows.Length; n++) + { + mChartRows[n] = (int)(num20 * (float)n + 0.5f) + rectangle23.Top; + } + mChartCols = new Dictionary(); + TimeSpan timeSpan = mMissionResults.ActuallMissionTime.Subtract(TimeSpan.FromSeconds(10.0)); + TimeSpan timeSpan2 = TimeSpan.FromSeconds(15.0); + for (TimeSpan timeSpan3 = timeSpan2; timeSpan3 < timeSpan; timeSpan3 += timeSpan2) + { + mChartCols.Add(timeSpan3, (int)((float)timeSpan3.Ticks / (float)mMissionResults.ActuallMissionTime.Ticks * (float)rectangle23.Width + 0.5f) + rectangle23.Left); + } + int num21 = 0; + array = mFinalRanking; + foreach (RPPlayer[] array5 in array) + { + RPPlayer[] array4 = array5; + foreach (RPPlayer key2 in array4) + { + mPlayerLines[key2].Add(new Point(rectangle23.Left, mChartRows[num21++])); + } + } + foreach (KeyValuePair mChartCol in mChartCols) + { + num21 = 0; + array = mMissionResults.GetChartOrder(mChartCol.Key); + foreach (RPPlayer[] array6 in array) + { + RPPlayer[] array4 = array6; + foreach (RPPlayer key3 in array4) + { + mPlayerLines[key3].Add(new Point(mChartCol.Value, mChartRows[num21++])); + } + } + } + num21 = 0; + array = mFinalRanking; + foreach (RPPlayer[] array7 in array) + { + RPPlayer[] array4 = array7; + foreach (RPPlayer key4 in array4) + { + mPlayerLines[key4].Add(new Point(rectangle23.Right, mChartRows[num21++])); + } + } + } + using (Font font7 = new Font("Helvetica LT Std", 8f)) + { + using (StringFormat stringFormat9 = new StringFormat()) + { + stringFormat9.LineAlignment = StringAlignment.Center; + foreach (KeyValuePair> mPlayerLine in mPlayerLines) + { + if (mPlayerLine.Key != rPPlayer) + { + DrawPlayerLine(e.Graphics, mPlayerLine.Key, mPlayerLine.Value.ToArray(), rectangle23.Right + 5, Pens.LightGray, Brushes.LightGray, font7, stringFormat9); + } + } + DrawPlayerLine(e.Graphics, rPPlayer, mPlayerLines[rPPlayer].ToArray(), rectangle23.Right + 5, Pens.Black, Brushes.Black, font7, stringFormat9); + } + using Pen pen2 = new Pen(Color.Black, 1.5f); + using (Font font8 = new Font("Courier New", 8f)) + { + int num22 = 5; + int num23 = 3; + e.Graphics.DrawLines(pen2, new Point[3] + { + new Point(rectangle23.Left, rectangle23.Top), + new Point(rectangle23.Left, rectangle23.Bottom), + new Point(rectangle23.Right, rectangle23.Bottom) + }); + using StringFormat stringFormat10 = new StringFormat(); + stringFormat10.Alignment = StringAlignment.Far; + stringFormat10.LineAlignment = StringAlignment.Center; + int num24 = 0; + int num25 = 0; + RPPlayer[][] array = mFinalRanking; + foreach (RPPlayer[] array8 in array) + { + int num26 = mChartRows[num25]; + e.Graphics.DrawLine(pen2, rectangle23.Left - num22, num26, rectangle23.Left + num22, num26); + Graphics graphics = e.Graphics; + int num27 = ++num25; + graphics.DrawString(num27.ToString(), font8, Brushes.Black, rectangle23.Left - 10, num26, stringFormat10); + RPPlayer[] array4 = array8; + for (int m = 0; m < array4.Length; m++) + { + _ = array4[m]; + num24++; + } + } + int num28 = rectangle23.Bottom + 23; + stringFormat10.Alignment = StringAlignment.Center; + stringFormat10.LineAlignment = StringAlignment.Near; + e.Graphics.DrawString("0", font8, Brushes.Black, rectangle23.Left, num28, stringFormat10); + foreach (KeyValuePair mChartCol2 in mChartCols) + { + if (mChartCol2.Key.Seconds == 0) + { + e.Graphics.DrawLine(pen2, mChartCol2.Value, rectangle23.Bottom - num22, mChartCol2.Value, rectangle23.Bottom + num22); + e.Graphics.DrawString(mChartCol2.Key.Minutes.ToString(), font8, Brushes.Black, mChartCol2.Value, num28, stringFormat10); + } + else + { + e.Graphics.DrawLine(pen2, mChartCol2.Value, rectangle23.Bottom - num23, mChartCol2.Value, rectangle23.Bottom); + } + } + e.Graphics.DrawLine(pen2, rectangle23.Right, rectangle23.Bottom - num22, rectangle23.Right, rectangle23.Bottom + num22); + e.Graphics.DrawString(((int)(mMissionResults.ActuallMissionTime.TotalMinutes + 0.5)).ToString(), font8, Brushes.Black, rectangle23.Right, num28, stringFormat10); + e.Graphics.DrawString("Time", font, Brushes.Black, (rectangle23.Left + rectangle23.Right) / 2, rectangle23.Bottom + 68, stringFormat10); + stringFormat10.LineAlignment = StringAlignment.Center; + e.Graphics.DrawString("P\r\nl\r\na\r\nc\r\ne", font, Brushes.Black, rectangle23.Left - 54, (rectangle23.Bottom + rectangle23.Top) / 2, stringFormat10); + int num29 = rectangle23.Top - 70; + int num30 = rectangle23.Left + 10; + int num31 = (rectangle23.Right + rectangle23.Left) / 2 - 30; + int num32 = rectangle23.Right - 85; + stringFormat10.Alignment = StringAlignment.Near; + DrawScoreMarker(e.Graphics, pen2, num30, num29, 15f); + e.Graphics.DrawString("Score", font7, Brushes.Black, num30 + 15, num29, stringFormat10); + DrawKillMarker(e.Graphics, pen2, num31, num29, 15f); + e.Graphics.DrawString("Kill", font7, Brushes.Black, num31 + 15, num29, stringFormat10); + DrawDeathMarker(e.Graphics, pen2, num32, num29, 15f); + e.Graphics.DrawString("Death", font7, Brushes.Black, num32 + 15, num29, stringFormat10); + } + foreach (MissionEvent event3 in mMissionResults.Events) + { + if (event3 is ScoredEvent) + { + DrawScoreMarker(e.Graphics, (event3.ReportingPilot == rPPlayer) ? Pens.Black : Pens.LightGray, GetMarkerPosition(event3.GameTime, mPlayerLines[event3.ReportingPilot], rectangle23), 10f); + } + else if (event3 is KilledEvent) + { + KilledEvent killedEvent3 = (KilledEvent)event3; + if (killedEvent3.Killer != killedEvent3.ReportingPilot) + { + DrawKillMarker(e.Graphics, (killedEvent3.ReportingPilot == rPPlayer || killedEvent3.Killer == rPPlayer) ? Pens.Black : Pens.LightGray, GetMarkerPosition(killedEvent3.GameTime, mPlayerLines[killedEvent3.Killer], rectangle23), 10f); + } + DrawDeathMarker(e.Graphics, (killedEvent3.ReportingPilot == rPPlayer || killedEvent3.Killer == rPPlayer) ? Pens.Black : Pens.LightGray, GetMarkerPosition(killedEvent3.GameTime, mPlayerLines[killedEvent3.ReportingPilot], rectangle23), 10f); + } + } + } + e.HasMorePages = ++mCurrentPlayer < mPrintOrder.Length; + base.OnPrintPage(e); + } + + private PointF GetMarkerPosition(TimeSpan time, List chartLine, RectangleF chartArea) + { + TimeSpan timeSpan = TimeSpan.Zero; + TimeSpan timeSpan2 = mMissionResults.ActuallMissionTime; + int num = 0; + foreach (KeyValuePair mChartCol in mChartCols) + { + timeSpan2 = mChartCol.Key; + if (mChartCol.Key < time) + { + num++; + timeSpan = timeSpan2; + continue; + } + break; + } + if (timeSpan == timeSpan2) + { + timeSpan2 = mMissionResults.ActuallMissionTime; + } + float num2 = ((float)time.Ticks - (float)timeSpan.Ticks) / ((float)timeSpan2.Ticks - (float)timeSpan.Ticks); + PointF pointF = chartLine[num]; + PointF pointF2 = chartLine[num + 1]; + return new PointF((pointF2.X - pointF.X) * num2 + pointF.X, (pointF2.Y - pointF.Y) * num2 + pointF.Y); + } + + private static void DrawKillMarker(Graphics graphics, Pen pen, PointF point, float size) + { + DrawKillMarker(graphics, pen, point.X, point.Y, size); + } + + private static void DrawKillMarker(Graphics graphics, Pen pen, float x, float y, float size) + { + float y2 = y + size / 2f; + graphics.DrawPolygon(pen, new PointF[3] + { + new PointF(x - size / 2f, y2), + new PointF(x + size / 2f, y2), + new PointF(x, y - size / 2f) + }); + } + + private static void DrawDeathMarker(Graphics graphics, Pen pen, PointF point, float size) + { + DrawDeathMarker(graphics, pen, point.X, point.Y, size); + } + + private static void DrawDeathMarker(Graphics graphics, Pen pen, float x, float y, float size) + { + graphics.DrawRectangle(pen, x - size / 2f, y - size / 2f, size, size); + } + + private static void DrawScoreMarker(Graphics graphics, Pen pen, PointF point, float size) + { + DrawScoreMarker(graphics, pen, point.X, point.Y, size); + } + + private static void DrawScoreMarker(Graphics graphics, Pen pen, float x, float y, float size) + { + graphics.DrawEllipse(pen, x - size / 2f, y - size / 2f, size, size); + } + + private static void DrawPlayerLine(Graphics graphics, RPPlayer player, Point[] points, int playerNameX, Pen pen, Brush brush, Font font, StringFormat format) + { + graphics.DrawLines(pen, points); + graphics.DrawString(player.Name, font, brush, playerNameX, points[points.Length - 1].Y, format); + } + + private static void GetPlayerStats(out int laps, out TimeSpan fastestLap, out int deaths, out int kills, RPPlayer player, IEnumerable relaventEventsSorted) + { + laps = 0; + fastestLap = TimeSpan.MaxValue; + deaths = 0; + kills = 0; + bool flag = true; + TimeSpan timeSpan = TimeSpan.Zero; + foreach (MissionEvent item in relaventEventsSorted) + { + if (item.ReportingPilot == player) + { + if (item is ScoredEvent) + { + if (flag) + { + flag = false; + } + else + { + TimeSpan timeSpan2 = item.GameTime - timeSpan; + if (timeSpan2 < fastestLap) + { + fastestLap = timeSpan2; + } + laps++; + } + timeSpan = item.GameTime; + } + else if (item is KilledEvent) + { + flag = true; + deaths++; + } + } + else if (item is KilledEvent && ((KilledEvent)item).Killer == player) + { + kills++; + } + } + if (fastestLap == TimeSpan.MaxValue) + { + fastestLap = TimeSpan.Zero; + } + } + + private static void DrawRedXBox(Graphics g, Rectangle rect) + { + g.DrawRectangle(Pens.Red, rect); + g.DrawLine(Pens.Red, rect.Left, rect.Top, rect.Right, rect.Bottom); + g.DrawLine(Pens.Red, rect.Left, rect.Bottom, rect.Right, rect.Top); + } + + private static Rectangle RectDrop(Rectangle inputRect, int widthDrop, int heightDrop) + { + return new Rectangle(inputRect.X, inputRect.Y, inputRect.Width - widthDrop, inputRect.Height - heightDrop); + } + + private static Rectangle GetCenteredScaledRect(Rectangle bounds, Size originalSize) + { + float num = (float)bounds.Width / (float)bounds.Height; + float num2 = (float)originalSize.Width / (float)originalSize.Height; + Size size = bounds.Size; + if (num > num2) + { + size.Width = (int)((float)size.Height * num2 + 0.5f); + } + else + { + size.Height = (int)((float)size.Width / num2 + 0.5f); + } + return new Rectangle(bounds.X + (bounds.Width - size.Width) / 2, bounds.Y + (bounds.Height - size.Height) / 2, size.Width, size.Height); + } + + private static bool Contains(IEnumerable players, RPPlayer player) + { + foreach (RPPlayer player2 in players) + { + if (player2 == player) + { + return true; + } + } + return false; + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPRaceMission.cs b/Console/TeslaConsole.RedPlanet/RPRaceMission.cs new file mode 100644 index 0000000..674c844 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPRaceMission.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class RPRaceMission : RPMission +{ + private readonly List mRacePlayers = new List(); + + public List RacePlayers => mRacePlayers; + + public override IEnumerable Players => mRacePlayers.ToArray(); + + public override int PlayerCount => mRacePlayers.Count; + + public RPRaceMission(string mapKey, string timeOfDayKey, string weatherKey, bool scoreCompression, TimeSpan gameLength) + : base(RPConfig.DeathRace.Key, mapKey, timeOfDayKey, weatherKey, scoreCompression, gameLength) + { + } + + protected override void AppendMissionSpecificData(StringBuilder egg) + { + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPRacePlayer.cs b/Console/TeslaConsole.RedPlanet/RPRacePlayer.cs new file mode 100644 index 0000000..eef435e --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPRacePlayer.cs @@ -0,0 +1,17 @@ +using System; +using System.Text; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class RPRacePlayer : RPPlayer +{ + public RPRacePlayer(string podHostAddress, string name, string vehicleKey, string colorKey, string badgeKey) + : base(podHostAddress, name, vehicleKey, colorKey, badgeKey) + { + } + + protected sealed override void AppendMissionSpecificPilot(int index, StringBuilder sb) + { + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPTeam.cs b/Console/TeslaConsole.RedPlanet/RPTeam.cs new file mode 100644 index 0000000..97556ef --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPTeam.cs @@ -0,0 +1,30 @@ +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +public class RPTeam +{ + private string mKey; + + private string mName; + + private string mRunnerColor; + + private string mTeamColor; + + public string Key => mKey; + + public string Name => mName; + + public string RunnerColor => mRunnerColor; + + public string TeamColor => mTeamColor; + + internal RPTeam(XmlNode teamNode) + { + mKey = teamNode.Attributes["key"].InnerText; + mName = teamNode.Attributes["name"].InnerText; + mRunnerColor = teamNode.Attributes["runnerColor"].InnerText; + mTeamColor = teamNode.Attributes["teamColor"].InnerText; + } +} diff --git a/Console/TeslaConsole.RedPlanet/RPVehicle.cs b/Console/TeslaConsole.RedPlanet/RPVehicle.cs new file mode 100644 index 0000000..d6cfd54 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/RPVehicle.cs @@ -0,0 +1,44 @@ +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +public class RPVehicle +{ + private readonly string mKey; + + private readonly string mName; + + private readonly string mImagePath; + + public string Key => mKey; + + public string Name => mName; + + public Image Image + { + get + { + if (mImagePath == null) + { + return null; + } + return ImageCache.GetImage(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), mImagePath)); + } + } + + internal RPVehicle(XmlNode vehicleNode) + { + XmlAttribute xmlAttribute = vehicleNode.Attributes["image"]; + mKey = vehicleNode.Attributes["key"].InnerText; + mName = vehicleNode.Attributes["name"].InnerText; + mImagePath = xmlAttribute?.InnerText; + } + + public override string ToString() + { + return mName; + } +} diff --git a/Console/TeslaConsole.RedPlanet/Scenario.cs b/Console/TeslaConsole.RedPlanet/Scenario.cs new file mode 100644 index 0000000..89c5a91 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/Scenario.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Xml; + +namespace TeslaConsole.RedPlanet; + +public class Scenario +{ + private string mKey; + + private string mName; + + private Dictionary mMaps = new Dictionary(); + + private Dictionary mVehicles = new Dictionary(); + + private Dictionary mTimesOfDay = new Dictionary(); + + private Dictionary mWeather = new Dictionary(); + + public string Key => mKey; + + public string Name => mName; + + public Dictionary Maps => mMaps; + + public Dictionary Vehicles => mVehicles; + + public Dictionary TimesOfDay => mTimesOfDay; + + public Dictionary Weather => mWeather; + + internal Scenario(XmlNode scenarioNode) + { + mKey = scenarioNode.Name; + mName = scenarioNode.Attributes["name"].InnerText; + List> list = new List>(); + foreach (XmlNode childNode in scenarioNode.ChildNodes) + { + if (childNode.Name == "invalid") + { + list.Add(new KeyValuePair(childNode.Attributes["type"].InnerText, childNode.Attributes["key"].InnerText)); + } + } + foreach (RPMap value in RPConfig.Maps.Values) + { + if (!IsInvalid("map", value.Key, list)) + { + mMaps.Add(value.Key, value); + } + } + foreach (RPVehicle value2 in RPConfig.Vehicles.Values) + { + if (!IsInvalid("vehicle", value2.Key, list)) + { + mVehicles.Add(value2.Key, value2); + } + } + foreach (KeyValuePair item in RPConfig.TimesOfDay) + { + if (!IsInvalid("time", item.Key, list)) + { + mTimesOfDay.Add(item.Key, item.Value); + } + } + foreach (KeyValuePair item2 in RPConfig.Weather) + { + if (!IsInvalid("weather", item2.Key, list)) + { + mWeather.Add(item2.Key, item2.Value); + } + } + } + + private static bool IsInvalid(string type, string value, List> invalid) + { + foreach (KeyValuePair item in invalid) + { + if (item.Key == type && item.Value == value) + { + return true; + } + } + return false; + } +} diff --git a/Console/TeslaConsole.RedPlanet/ScoreUpdateEvent.cs b/Console/TeslaConsole.RedPlanet/ScoreUpdateEvent.cs new file mode 100644 index 0000000..5714af0 --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/ScoreUpdateEvent.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class ScoreUpdateEvent : MissionEvent +{ + public readonly int Score; + + public override IEnumerable InvolvedPilots => new RPPlayer[1] { ReportingPilot }; + + public ScoreUpdateEvent(RPPlayer reportingPilot, TimeSpan missionTime, int score) + : base(reportingPilot, missionTime) + { + Score = score; + } + + private ScoreUpdateEvent() + { + } +} diff --git a/Console/TeslaConsole.RedPlanet/ScoredEvent.cs b/Console/TeslaConsole.RedPlanet/ScoredEvent.cs new file mode 100644 index 0000000..54e246b --- /dev/null +++ b/Console/TeslaConsole.RedPlanet/ScoredEvent.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole.RedPlanet; + +[Serializable] +internal class ScoredEvent : MissionEvent +{ + public override IEnumerable InvolvedPilots => new RPPlayer[1] { ReportingPilot }; + + public ScoredEvent(RPPlayer reportingPilot, TimeSpan missionTime) + : base(reportingPilot, missionTime) + { + } + + private ScoredEvent() + { + } +} diff --git a/Console/TeslaConsole.SitePanel.resx b/Console/TeslaConsole.SitePanel.resx new file mode 100644 index 0000000..12be50a --- /dev/null +++ b/Console/TeslaConsole.SitePanel.resx @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +text/microsoft-resx1.3System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=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 \ No newline at end of file diff --git a/Console/TeslaConsole.TeslaConsoleForm.resx b/Console/TeslaConsole.TeslaConsoleForm.resx new file mode 100644 index 0000000..46d8baa --- /dev/null +++ b/Console/TeslaConsole.TeslaConsoleForm.resx @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +text/microsoft-resx1.3System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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/0BAmv85OaP/Njas/zc3tf8/P77/S0vG/1dXzv9kZNb/cHDd/3x85P+IiOr/lpbv/6Sk9f+ysvn/wcH9/8/P///a2v//5OT//+vr///w8P//8/P///X1///29v//9/f///b2///y8v//7Oz//+Pj///X1///ycn//7q6//+oqP//lZX//4WF//9+fv//hob//56e//+5uf//09P///Dw///////////////////p6f//x8f//6Wl//+Fhf//gID//319//+IiP//oaH//7i4///W1v////////Pz//+7u/7/lpb6/3d39f9YWPD/YGDq/3l55P90dN7/WVnX/0dHz/9UVMf/ZWW//3l5tv+dna3/wsKk/9fXmv/NzZH/q6uH/4mJff9oaHP/TExp/zs7X/8rK1X/JydL/zIyQv88PDn/RUUw/05OKP9UVCD/U1MY/1JSEf9QUAv/T08G/1BQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/UVEC/1NTB/9VVQ3/VlYU/1hYG/9ZWSL/WVkr/1lZM/9XVzz/VVVG/1JST/9PT1n/TExj/0hIbf9DQ3f/Pj6B/zg4i/8zM5X/MTGf/zU1qf9AQLL/TU27/1lZxP9mZsz/c3PU/4KC2/+Tk+L/paXp/7m57//OzvT/4OD5/+/v/f/6+v////////////////////////////////////////////////////////////////////////z8///09P//5ub//9PT//+/v///qKj//5CQ//+AgP//h4f//6Oj//+/v///3Nz///n5//////////////v7///c3P//ubn//5WV//+Bgf//f3///4iI//+hof//uLj//9LS///7+////Pz//8fH//+fn/7/f3/5/15e9f9fX+//enrp/3V14/9ZWdz/SUnV/1hYzf9pacT/f3+8/6Sks//Jyan/29ug/8nJlv+kpIz/gYGC/2BgeP9JSW7/ODhk/ygoWv8sLFD/ODhG/0JCPf9MTDT/VFQr/1VVI/9TUxv/UlIU/1FRDv9QUAj/Tk4CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9NTQT/Tk4J/1BQEP9QUBb/UFAe/1BQJv9PTy7/TU03/0tLQP9ISEr/RUVU/0FBXv89PWj/ODhy/zIyfP8tLYf/LS2R/zMzm/8/P6T/TEyu/1hYt/9lZcD/dXXJ/4iI0f+cnNn/s7Pg/8rK5//h4e3/8/Pz////+P////3//////////////////Pz///r6///5+f//+vr///39////////////////////////////////////////////////////////+vr//+rq///T0///ubn//5yc//+EhP//i4v//6ur///Jyf//6en///////////////////Dw///Ly///pqb//4aG//9/f///iYn//6Gh//+5uf//0dH///f3////////0tL//6en//+Ghv3/Y2P4/2Bg8/97e+7/dnbo/1lZ4f9MTNr/XV3S/25uyv+FhcH/q6u4/9HRr//d3aX/w8Ob/52dkv95eYf/WVl9/0VFc/8zM2n/KSle/zMzVP8+Pkr/SUlB/1NTOP9XVy//VlYm/1RUHv9TUxf/UVEQ/1FRCv9PTwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/QkIB/0NDBv9FRQv/RkYS/0ZGGf9FRSH/RUUp/0NDMv9BQTv/Pj5E/zs7Tv83N1j/MzNi/y0tbP8pKXf/KSmB/zExi/88PJb/SUmg/1VVqv9kZLP/dna8/4uLxf+ios7/ubnW/9LS3v/q6uX//Pzr////8f////f//Pz8//Ly///o6P//3t7//9fX///V1f//1dX//9nZ///e3v//4eH//+Pj///n5///7Oz///Pz///5+f////////////////////////////////////////r6///k5P//xsb//6Wl//+IiP//lZX//7e3///Y2P//+Pj//////////////v7//97e//+4uP//kZH//39///+Hh///oqL//7u7///T0///9fX////////b2///rq7//4yM//9pafz/YmL3/3x88v93d+z/WVnl/1BQ3v9jY9f/c3PP/4yMxv+zs73/2Ni0/93dqv+8vKH/lZWW/3FxjP9UVIL/QkJ4/y8vbf8tLWP/OjpZ/0VFT/9QUEX/WFg7/1hYMv9WVin/VVUh/1NTGf9SUhL/Tk4M/0lJBv9FRQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP85OQL/OjoI/zs7Df88PBT/PDwc/zs7I/85OSz/Nzc1/zU1Pv8yMkj/Li5S/ygoXP8lJWb/JiZx/y4ue/85OYb/RESQ/1FRmv9hYaT/dHSu/4qKuP+hocH/ubnK/9PT0//t7dv//v7i////6f/6+u//7Oz1/9vb+v/Ly///vr7//7i4//+2tv//trb//7i4//+7u///vr7//76+//+9vf//v7///8LC///Hx///zs7//9jY///j4///8fH///z8///////////////////////////////////w8P//z8///6mp//+Pj///pKT//8jI///r6///////////////////8PD//8jI//+fn///gYH//4SE//+goP//vr7//9bW///09P///////+Li//+1tf//kpL//21t//9kZPv/fX32/3Z28P9YWOr/VVXj/2ho2/95edP/lJTL/729wv/g4Ln/2tqv/7Ozpf+MjJv/aGiR/1BQh/89PXz/LS1y/zMzZ/9AQF3/TExS/1dXSf9aWj//WFg1/1dXLf9VVST/U1Mc/05OFf9HRw7/QUEI/z09AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/zAwBP8xMQn/MjIQ/zIyFv8xMR7/MDAm/y4uL/8sLDj/KChC/yQkS/8hIVX/IyNg/yoqav81NXX/QEB//0xMiv9cXJX/cHCf/4aGqf+dnbP/tbW8/8/Pxv/q6s7//f3X////3//y8ub/39/t/8rK8/+3t/j/qqr9/6Oj//+goP//n5///52d//+dnf//nZ3//5+f//+cnP//mZn//5iY//+Zmf//nJz//6Gh//+oqP//sbH//729///Ly///3Nz///Dw///+/v/////////////////////////////29v//0tL//6mp//+bm///uLj//97e///9/f/////////////8/P//19f//62t//+Hh///goL//56e//+9vf//29v///b2////////6Oj//7u7//+Xl///cXH//2dn/v9/f/n/dHTz/1hY7f9bW+f/b2/f/4CA2P+dnc//x8fG/+bmvf/U1LT/qqqq/4ODoP9hYZX/TEyL/zg4gP8uLnb/Oztr/0dHYf9TU1b/W1tM/1paQv9ZWTn/V1cw/1VVJ/9OTh//RkYX/0BAEP86Ogr/NTUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8mJgH/JycF/ygoC/8oKBH/KCgZ/ycnIP8lJSn/IiIy/x8fO/8dHUX/ICBP/ycnWf8xMWP/Oztu/0dHef9XV4P/amqO/4CAmf+WlqP/ra2t/8bGt//i4sH/+vrK//z80//t7dv/19fj/8DA6v+rq/D/nJz2/5SU+/+QkP//jIz//4iI//+EhP//goL//4SE//+Fhf//hIT//4KC//+Bgf//gID//39///+AgP//gYH//4WF//+Li///lZX//6Oj//+1tf//ysr//+Pj///6+v/////////////////////////////39///zs7//6qq//+urv//0tL///f3///////////////////l5f//ubn//4+P//+Bgf//nZ3//729///c3P//+fn////////r6///wMD//5ub//90dP//amr//4CA/P9ycvf/WFjx/2Ji6v91deP/h4fb/6en0//S0sv/6enB/8rKuP+goK7/eXmk/1xcmv9ISI//NDSE/zMzev9CQm//Tk5k/1paWv9dXU//W1tF/1lZPP9XVzL/T08p/0dHIf8/Pxn/ODgS/zIyC/8tLQb/KysBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/x0dAf8eHgf/Hh4N/x4eE/8dHRv/Gxsj/xoaK/8aGjT/HR0+/yUlSP8tLVL/Nzdc/0JCZ/9SUnL/ZGR9/3h4h/+NjZL/pKSd/7y8p//W1rH/8fG7//v7xf/t7c7/1dXW/76+3v+np+b/lJTt/4qK8/+EhPn/fn7+/3d3//9ycv//cnL//3h4//9+fv//gID//4CA//9/f///gID//4OD//+IiP//kJD//5eX//+dnf//oaH//6Ki//+hof//oKD//6am//+ysv//wsL//9ra///29v/////////////////////////////y8v//x8f//7W1///IyP//8PD///////////////////Hx///ExP//l5f//4KC//+env//v7///97e///7+////////+7u///ExP//np7//3Z2//9ubv//gYH//29v+f9aWvT/aWnu/3x85/+Pj9//s7PX/97ezv/o6MX/v7+8/5WVsv9vb6j/WFid/0NDk/8yMoj/Ozt9/0lJc/9WVmj/Xl5d/11dU/9cXEn/WVk//1FRNf9ISCz/Pz8j/zg4G/8xMRT/KysN/yUlB/8iIgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/ExMD/xQUCP8UFA7/FBQV/xUVHf8YGCX/HR0u/yMjN/8qKkH/MzNL/z4+Vf9NTWD/Xl5q/3Bwdf+EhID/mZmL/7Cwlv/JyaD/5OSr//b2tf/v77//2NjI/8HB0f+pqdr/kpLi/4OD6f98fPD/dXX2/2xs/P9lZf//Z2f//3Nz//9+fv//goL//4GB//+AgP//goL//4qK//+YmP//p6f//7a2///Dw///z8///9jY///e3v//4uL//+Hh///d3f//1dX//87O///R0f//2dn//+bm///7+//////////////////////////////n5///y8v//8jI///q6v//////////////////+Pj//87O//+fn///hIT//6Cg///Bwf//4uL///39////////8PD//8bG//+fn///eHj//3Nz//+Cgv//amr8/19f9/9ycvH/hITq/5mZ4v/Bwdr/6urS/+Dgyf+zs7//iYm1/2hoq/9TU6H/PT2W/zU1jP9DQ4H/UFB2/11da/9gYGD/Xl5W/1xcS/9VVUH/Sko4/0BALv84OCb/MDAd/ykpFv8jIw//HR0I/xkZAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8REQT/ExMJ/xUVEP8ZGRf/HR0f/yMjJ/8pKTD/MTE5/zw8Q/9JSU3/WVlY/2lpY/97e23/j494/6Skg/+7u47/1NSZ/+zspP/x8a7/39+4/8fHwv+wsMv/l5fU/4KC3f93d+X/b2/s/2Zm8/9dXfn/Xl7+/2xs//98fP//g4P//4KC//9/f///goL//46O//+goP//tLT//8fH///Z2f//6ur///X1///8/P///////////////////////////////////Pz///f3///6+v///Pz///7+//////////////////////////////39///n5///19f//+fn///////////////////9/f//1dX//6Sk//+IiP//pKT//8bG///m5v/////////////x8f//yMj//6Cg//94eP//eHj//4CA//9nZ/7/Zmb5/3t78/+MjO3/paXl/9DQ3v/x8dX/1NTM/6amw/9+frn/YmKv/01NpP84OJr/OzuP/0tLhP9ZWXn/YmJu/2BgY/9fX1n/WFhO/01NRP9CQjr/OTkx/zAwKP8oKB//ISEX/xoaEP8WFgr/FBQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xsbBf8eHgv/ISER/yUlGP8rKyD/MzMp/z09Mv9ISDz/VVVG/2RkUP90dFr/hYVl/5mZcP+trXv/xcWG/9zckf/s7Jz/5uan/9DQsf+4uLv/oaHF/4iIzv92dtf/bGzg/2Nj6P9ZWe//VVX1/2Fh+/92dv//gYH//4OD//+AgP//e3v//4WF//+env//trb//8zM///i4v//9PT///7+//////////////////////////////////////////////////////////////////////////////////////////////////////////////7+///w8P//7e3////////////////////////b2///qKj//42N//+qqv//zMz//+3t//////////////Hx///IyP//np7//3l5//99ff//fHz//2Vl//9wcPv/hIT2/5WV7/+zs+j/4eHg//Hx2P/Gxs//mZnG/3R0vP9dXbL/Rkan/zg4nf9ERJL/U1OH/2BgfP9jY3H/YWFm/1xcW/9QUFH/RUVG/zo6PP8xMTP/KCgq/yAgIf8aGhn/GRkS/xsbC/8cHAX/EhIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8nJwH/LCwG/y4uDP8zMxP/OTka/0BAIv9KSiv/VFQ0/2BgPv9ubkj/fn5S/4+PXf+hoWj/trZz/83Nfv/g4In/6OiU/9vbn//Dw6r/ra20/5SUvv98fMj/bGzR/2Nj2v9YWOL/T0/q/1RU8f9oaPf/fHz9/4OD//+Cgv//enr//3Z2//+Kiv//q6v//8bG///f3///9fX////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7+////////////////////////93d//+pqf//lJT//7Ky///U1P//9PT/////////////7e3//8bG//+bm///enr//4GB//93d///aGj//3t7/f+Ojvj/oKDx/8PD6//w8OP/5+fb/7a20v+Li8n/bW2//1dXtf8/P6r/PT2g/01Nlf9cXIr/ZWV//2Rkc/9gYGj/VVVe/0hIU/89PUj/MzM+/ykpNf8fHyv/HBwj/x4eGv8iIhP/IyMM/yYmBv8hIQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/z8/Av8/Pwf/QkIN/0dHFP9OThv/VlYk/2BgLP9razb/eHhA/4eHSv+Xl1T/qqpf/76+av/S0nX/39+A/+Dgi//Pz5b/ubmh/6KirP+Kirb/cnLB/2Njyv9aWtT/T0/c/0lJ5f9WVuz/bW3z/39/+f+Dg/7/f3///3Fx//9zc///kJD//7Ky///S0v//7e3///7+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////3d3//6qq//+env//vb3//97e///8/P/////////////m5v//wcH//5eX//98fP//goL//3Fx//9xcf//hob//5iY+f+trfP/19ft//j45f/W1t3/pqbU/39/y/9nZ8H/T0+3/zw8rf9GRqL/VlaX/2NjjP9mZoH/Y2N2/1paa/9MTGD/QEBV/zU1S/8qKkD/ISE2/x8fLf8kJCT/KCgc/ysrFP8vLw3/NTUH/zs7AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/UFAC/1JSCP9WVg7/W1sV/2NjHf9rayX/dnYu/4KCN/+QkEH/oKBM/7KyVv/FxWH/09Ns/9vbd//Y2IL/xsaO/7Cwmf+ZmaT/gYGu/2pquf9cXMP/U1PN/0dH1v9HR97/WVnn/3Bw7v+BgfX/g4P7/3p6//9paf//cXH//5KS//+0tP//2Nj///b2///////////////////////////////////8/P//9/f///Ly///v7///8fH///z8///////////////////////////////////////////////////////////////////////////////////////////////////////////////////Z2f//rKz//6mp///Jyf//6+v//////////////f3//97e//+6uv//kpL//35+//9/f///cXH//35+//+Skv//o6P7/76+9f/s7O7/8/Pn/8PD3/+Vldb/d3fN/19fxP9GRrr/Pz+v/09PpP9fX5n/aGiO/2dng/9fX3j/UVFt/0REYv84OFf/LCxM/yMjQv8jIzj/KSkv/y0tJf8xMR3/OTkV/0BADv9GRgj/S0sCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9iYgP/ZGQI/2lpD/9vbxb/d3ce/4GBJv+MjC//mZk5/6ioQ/+5uU3/yMhY/9DQY//V1W7/0NB5/729hP+oqJD/kpKb/3l5pv9jY7D/Vla7/0xMxf9BQc//RkbY/1pa4P9xcej/gYHw/4KC9v91dfz/Y2P//29v//+Rkf//s7P//9fX///5+f////////////////////////39///y8v//5eX//9fX///Ly///xsb//9HR///s7P///////////////////////////////////////////////////////////////////////////////////////////////////////////////////v7//9PT//+zs///t7f//9jY///4+P/////////////4+P//1NT//7Cw//+MjP//gID//3p6//94eP//jIz//52d//+vr/z/09P3//v78P/i4un/r6/h/4iI2P9vb8//VlbF/0FBu/9JSbH/Wlqm/2dnm/9qapD/ZGSF/1ZWev9ISG//Oztj/y4uWf8lJU7/JydD/y0tOf8yMjD/Nzcn/0FBHv9KShb/UVEP/1dXCf9cXAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/3R0A/93dwn/fX0P/4ODF/+MjB//lpYn/6OjMP+xsTr/vr5E/8fHT//MzFn/0NBk/8nJb/+1tXv/oaGG/4uLkf9zc5z/XV2n/1FRsv9GRrz/PT3G/0VF0P9aWtn/cHDi/4GB6v+AgPH/cXH3/15e/f9qav//jY3//66u///T0///+Pj///////////////////39///w8P//3d3//8vL//+6uv//qqr//6ur///Cwv//5eX////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4+P//0tL//729///IyP//6ur//////////////////+7u///IyP//pKT//4WF//+AgP//enr//4aG//+Zmf//qqr//8DA/v/s7Pj/+vrx/8vL6v+dneL/fn7a/2Zm0f9MTMf/Q0O9/1RUs/9kZKj/bGyd/2hokv9cXIf/TU17/z8/cP8yMmX/Jyda/yoqT/8xMUX/NjY7/z09Mf9ISCj/U1Mf/1tbF/9jYxD/aGgJ/2xsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/hoYE/4qKCv+QkBD/mJgX/6GhH/+srCj/trYx/76+O//CwkX/yMhQ/8vLW//BwWX/rq5x/5ubfP+FhYf/bm6T/1lZnv9MTKn/QkKz/zk5vv9DQ8j/WFjR/25u2/+AgOP/gIDr/29v8v9bW/j/ZGT+/4aG//+oqP//zMz///X1///////////////////29v//4uL//8zM//+4uP//oqL//5KS//+fn///xMT//+vr///+/v////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////T0///Z2f//yMj//93d///7+///////////////////4OD//7u7//+Xl///gYH//35+//+Dg///lZX//6en//+3t///19f+//7++f/o6PP/tLTr/4+P5P91ddv/XFzS/0ZGyf9NTb7/X1+0/2xsqf9sbJ7/YWGT/1JSiP9DQ33/NTVx/yoqZv8tLVv/NDRQ/zo6Rv9CQjz/Tk4y/1paKf9kZCD/bW0Y/3R0Ef95eQr/fX0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+ZmQT/np4K/6SkEf+qqhj/sbEg/7a2Kf+6ujL/vr48/8PDRv/FxVH/urpb/6ioZv+VlXL/gYF9/2pqiP9VVZT/SEif/z4+qv82NrX/QUG//1ZWyf9ra9P/f3/c/39/5P9ubuz/WVnz/1xc+f99ff//np7//8LC///x8f//////////////////7+///9fX///AwP//qqr//5GR//+Hh///oaH//9DQ///39///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+vr//+Hh///Y2P//9fX///////////////////X1///Q0P//rKz//4qK//9/f///g4P//5OT//+lpf//tbX//8nJ///y8v///Pz6/87O8/+goOz/hITl/2tr3P9QUNP/SEjK/1lZwP9qarX/cHCq/2dnoP9XV5T/SEiJ/zk5fv8tLXL/Ly9n/zc3XP8+PlH/RkZH/1RUPP9hYTP/bGwp/3Z2If9/fxn/hYUR/4uLCv+PjwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/6SkBP+npwr/q6sR/6+vGP+ysiD/trYp/7q6M/+/vzz/v79H/7KyUf+hoVz/j49n/3x8cv9mZn7/UVGJ/0VFlP86OqD/MjKr/z4+tv9SUsD/Z2fK/3x80/9/f9z/bm7l/1lZ7f9VVfT/c3P6/5SU//+2tv//6en//////////////f3//+rq///R0f//ubn//6Cg//+IiP//goL//6Sk///U1P//+/v/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6ur///Hx////////////////////////5eX//7+///+bm///gYH//4KC//+Tk///pKT//7W1///ExP//4+P////////n5/r/tLT0/5OT7f95eeX/Xl7d/0pK1P9UVMr/ZmbB/3Fxtv9sbKv/XFyg/0xMlf89PYr/MDB+/zExc/86Omj/QUFd/0lJUv9YWEf/Z2c9/3NzM/9+fir/iIgh/5GRGf+YmBH/nJwL/52dBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/pKQF/6ioC/+rqxH/r68Z/7KyIf+2tir/u7sz/7i4Pf+qqkf/m5tR/4qKXP93d2f/Y2Nz/05Of/9BQYr/NzeV/y8voP87O6v/T0+2/2JiwP94eMr/gIDU/3Bw3f9bW+X/T0/t/2dn9P+IiPr/qan//9vb//////////////r6///m5v//z8///7a2//+cnP//hYX//4CA//+goP//0ND///n5///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6+v////////////////////////X1///Q0P//rKz//4mJ//9/f///kJD//6am//+2tv//xcX//9nZ///8/P//+vr//8nJ+/+hofT/h4fu/2xs5v9SUt7/T0/V/2Jiy/9xccH/cXG3/2JirP9RUaH/QUGW/zMzi/80NH//PT1z/0REaP9NTV3/XFxS/2xsSP96ej3/hoYz/5GRKv+bmyH/oqIZ/6OjEv+hoQv/np4F/5WVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+kpAX/qKgL/6urEf+vrxn/s7Mh/7W1Kv+vrzP/oqI9/5SUR/+FhVL/c3Nd/2BgaP9LS3T/Pj5+/zU1iv8tLZb/Nzeh/0pKrP9dXbb/cnLB/4CAy/9yctT/Xl7d/0xM5v9aWu3/e3v0/5qa+//IyP//+/v////////7+///39///83N//+3t///m5v//4SE//9+fv//l5f//8XF///z8//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9/f//39///7q6//+Wlv//gID//4yM//+jo///uLj//8jI///X1///9fX////////e3v//sLD7/5SU9f95ee7/Xl7m/09P3v9dXdX/b2/L/3V1wf9nZ7f/VVWs/0VFof82Npb/NjaL/0BAgP9HR3T/UFBo/19fXf9xcVL/gIBI/42NPf+amjT/pKQq/6ioIf+mphn/o6MS/6GhC/+engX/lZUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/6SkBf+oqAv/q6sR/6+vGf+uriH/pqYq/5qaM/+NjT3/f39H/29vUv9dXV3/SUlo/zw8dP8yMn7/KiqK/zQ0lv9GRqH/WFis/2xstv9+fsH/dXXL/2Ji1P9NTd3/Tk7m/21t7f+Li/T/s7P7/+3t/////////v7//9/f///IyP//t7f//56e//+Fhf//fn7//4yM//+2tv//5ub//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+rq///Fxf//oaH//4OD//+Kiv//oaH//7i4///MzP//2tr///Hx////////7u7//8DA//+goPv/hYX1/2pq7v9TU+b/WVne/21t1f93d8v/bGzB/1pat/9JSaz/OTmh/zk5lv9CQov/S0uA/1NTdP9jY2j/dXVd/4WFUv+UlEj/oqI9/6ysNP+srCr/qakh/6amGf+joxL/oaEL/56eBf+WlgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/paUF/6ioC/+pqRH/pKQZ/5ycIf+RkSr/hoYz/3l5Pf9qakf/WVlR/0ZGXP85OWf/MDBz/ygofv8wMIr/QUGV/1NToP9lZav/eXm2/3h4wP9lZcv/UlLU/0ZG3f9eXuX/fHzt/5+f9P/W1vr////////////l5f//xcX//7S0//+hof//iYn//39///+Dg///pKT//9TU///8/P////////////////////////7+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w8P//zMz//6io//+Jif//i4v//6Ki//+4uP//zc3//+Dg///x8f////////n5///Ozv//rKz//5GR+/91dfT/Wlru/1dX5v9qat3/eXnV/3Fxy/9eXsH/TEy3/zw8rP87O6H/RUWW/05Oi/9WVn//ZmZz/3l5aP+Kil3/mppS/6mpSP+xsT3/sLAz/6ysKv+pqSH/pqYZ/6OjEv+hoQv/np4F/5WVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+goAT/nZ0K/5iYEf+RkRj/iIgh/35+Kf9ycjP/ZGQ8/1VVR/9ERFH/NjZc/y4uZ/8lJXL/LCx+/z09if9NTZT/X1+g/3Nzq/96erX/amrA/1dXyv9ERNT/T0/d/21t5f+MjO3/u7v0//T0+v//////8fH//8fH//+ysv//oKD//4yM//+AgP//f3///5GR//+9vf//7e3////////////////////////7+///6ur///39////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8fH//8/P//+srP//jo7//5CQ//+mpv//urr//87O///j4///9vb////////+/v//2tr//7e3//+bm///f3/6/2Nj9P9YWO3/aGjl/3l53f91ddT/YmLK/1BQwf8/P7b/Pj6r/0lJoP9RUZX/WlqK/2lpfv99fXP/j49o/6CgXf+wsFL/trZH/7KyPf+vrzP/rKwq/6mpIf+mphn/o6MR/6GhC/+engUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/4+PBP+Kigr/hYUR/35+GP91dSD/a2sp/15eMv9QUDz/QEBG/zMzUf8rK1v/IyNm/ykpcv84OH3/SEiI/1lZlP9sbJ//enqq/29vtf9dXb//SUnJ/0JC0/9dXdz/enrk/6Gh7P/a2vP////5//39///S0v//srL//6Cg//+MjP//gID//4CA//+Dg///paX//9TU///8/P///////////////////f3//97e///Y2P//+/v//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+3t///Ly///rKz//5aW//+Zmf//ra3//8DA///T0///5+f///n5/////////////+Tk///AwP//pKT//4mJ//9sbPr/Wlrz/2dn7P96euX/eHjc/2Vl0/9SUsn/QkK//0JCtf9MTKr/VVWf/11dlP9tbYn/gYF+/5SUcv+mpmf/trZc/7q6Uf+2tkf/srI8/6+vM/+srCn/qakh/6WlGP+goBH/m5sK/5eXBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/fHwE/3h4Cf9zcxD/a2sX/2JiH/9XVyj/S0sx/zw8O/8wMEX/KSlQ/yEhWv8mJmX/NDRx/0NDfP9TU4f/ZGST/3Z2nv9zc6n/YmKz/1BQvv8+Psj/TEzS/2lp2/+JieP/vLzr//T08v////n/5eX+/7i4//+iov//jo7//319//+AgP//gID//42N//+4uP//5+f////////////////////////n5///w8P//8/P///4+P////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////n5///j4///x8f//7Cw//+iov//p6f//7m5///Kyv//29v//+7u///9/f/////////////q6v//yMj//6ys//+QkP//c3P+/15e+f9nZ/L/enrr/3p64/9oaNv/VFTS/0VFyP9GRr7/UFC0/1lZqf9iYp7/cXGT/4aGiP+ZmX3/rKxx/7u7Zv+9vVv/ublQ/7W1Rv+ysjz/rq4y/6ioKf+fnyD/lpYY/4+PEP+JiQr/hYUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9rawP/ZmYJ/2FhD/9ZWRf/UFAf/0REJ/83NzD/LCw6/yYmRP8fH0//IyNZ/zExZP8/P2//Tk57/15ehv9vb5H/dXWc/2dnp/9WVrL/Q0O8/z4+xv9ZWdD/dXXZ/56e4v/W1ur////x//n59//IyP3/pqb//5OT//99ff//eXn//4KC//+Bgf//nJz//8jI///19f//////////////////9PT//8zM//+wsP//zs7///7+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////8/P//5eX//87O//+8vP//s7P//7q6///Jyf//19f//+fn///39///////////////////7u7//87O//+ysv//lpb//3l5//9jY/3/amr4/3x88f98fOr/aWni/1VV2v9HR9H/SkrH/1VVvf9eXrP/Zmao/3Z2nf+Li5L/n5+G/7Oze//BwXD/wcFl/7y8Wv+5uU//tbVF/6ysOv+goDH/lZUo/4uLH/+Dgxf/fHwQ/3d3Cf90dAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1lZA/9VVQj/T08P/0dHFv89PR7/MjIm/ygoL/8iIjn/HBxD/yEhTf8tLVj/Oztj/0lJbv9YWHn/aGiE/3R0kP9tbZv/XFym/0tLsP86Orv/R0fF/2Njz/+Dg9j/tbXg/+3t6P////D/4eH2/7Gx/P+YmP//g4P//3Fx//9+fv//gYH//4WF//+qqv//19f///39///////////////////g4P//trb//6Oj///W1v////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Ly///b2///zMz//8nJ///S0v//3t7//+rq///29v///v7///////////////////Dw///S0v//tbX//5qa//99ff//Z2f//21t/P9+fvb/fHzw/2lp6f9VVeH/SkrY/1BQz/9bW8X/Y2O7/2xssf97e6b/kZGb/6amkP+6uoX/xsZ6/8TEbv/AwGP/vLxY/7OzTv+lpUP/l5c5/4uLMP+AgCf/d3ce/29vFv9qag//ZWUJ/2JiAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/SEgC/0NDB/89PQ7/NTUV/ysrHf8jIyX/Hh4u/xoaN/8fH0H/KytL/zc3Vv9ERGH/UlJs/2Jid/9vb4L/cHCO/2Jimf9SUqT/Pz+u/zk5uf9SUsP/bW3N/5aW1v/Ly9///f3n//n57v/Hx/X/n5/7/4uL//90dP//cHD//4KC//9/f///iYn//7a2///i4v//////////////////9PT//87O//+jo///n5///97e///////////////////////////////////////////////////////////////////////////////////////////////////////////////////9/f//8PD//+np///r6///8PD///b2///8/P////////////////////////39///t7f//0tL//7e3//+bm///f3///2tr//9ycv//gID7/3t79f9oaO7/VVXn/05O3/9WVtb/YWHN/2lpw/9ycrn/goKv/5iYpP+trZn/wcGO/8rKg//IyHj/w8Nt/7y8Yv+urlf/nZ1M/46OQv+BgTj/dnYu/2xsJf9jYx3/XFwV/1dXDv9TUwj/UVECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP83NwL/MTEH/ysrDf8kJBT/Hh4b/xoaJP8XFyz/Hh42/ykpQP80NEr/QEBU/01NX/9bW2r/amp1/3BwgP9oaIv/WFiW/0dHof82Nqz/QEC3/1pawf96esr/qanU/+Dg3P///+X/5eXs/7Gx8/+Skvn/fn7//2ho//91df//g4P//3x8//+Njf//v7///+zs///////////////////n5///v7///5WV//+hof//4uL////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////39///5OT//8/P//+1tf//mpr//39///9wcP//eXn//4KC//95efn/ZWXz/1VV7P9UVOX/Xl7d/2dn1P9wcMv/eXnB/4qKt/+goK3/tbWi/8nJl//Pz4z/y8uB/8XFdv+4uGv/pqZg/5WVVf+Ghkr/eHhA/2xsNv9hYS3/WFgk/1BQHP9JSRT/REQN/0FBB/9AQAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/yUlAf8gIAb/HBwM/xgYEv8VFRr/FhYi/x0dK/8nJzT/MTE+/z09SP9JSVL/VlZd/2RkaP9tbXP/a2t+/15eif9OTpT/PT2f/zQ0qv9JSbT/Y2O+/4mJyP+8vNH/8fHa//z84v/Pz+r/oKDx/4aG9/9xcf3/Y2P//3x8//+Cgv//eXn//5KS///ExP//8vL//////////////Pz//9ra//+ysv//i4v//6Ki///i4v////////////////////////7+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7+///7Oz//9jY///Fxf//sLD//5aW//99ff//dnb//39///+Bgf//dHT9/2Fh9/9YWPH/XV3q/2dn4/9vb9r/d3fS/4GByP+Tk7//qam0/7+/qv/R0Z//1NSU/87Oif/Dw37/sbFz/56eaP+NjV3/fX1T/29vSP9iYj7/VlY1/0xMK/9DQyP/PDwa/zY2E/8xMQz/Li4G/y8vAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xQUBf8SEgv/EhIR/xYWGP8eHiD/JiYp/zAwMv86Ojz/RUVG/1FRUP9fX1r/aGhl/2trcP9kZHv/VVWG/0VFkf80NJz/ODin/1BQsf9tbbv/mprF/83Nz//8/Nf/7e3g/7u76P+Rke//fHz1/2Vl+/9lZf//gID//4GB//91df//lZX//8fH///29v/////////////19f//0ND//6mp//+Fhf//oaH//97e////////////////////////8/P//+/v///9/f/////////////////////////////////////////////////////////////////////////////////////////////////////////////4+P//6+v//9ra///Jyf//uLj//6am//+Pj///fn7//319//+Cgv//fX3//21t//9fX/v/X1/1/2ho7/9wcOj/eHjg/39/2P+Li8//n5/F/7W1vP/KyrL/2Nin/9jYnf/OzpL/vb2H/6mpfP+VlXH/hIRm/3R0W/9lZVD/V1dG/0tLPP9AQDP/ODgp/zExIf8rKxn/JycR/yMjC/8hIQX/ICABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/Dw8E/xMTCf8YGBD/Hx8X/ycnH/8vLyf/ODgw/0JCOf9NTUP/WlpN/2VlWP9oaGL/Z2dt/1tbeP9MTIP/PDyO/y8vmf8/P6T/V1eu/3t7uP+pqcL/3d3M//7+1f/b293/qqrl/4aG7P9ycvP/XFz5/2pq/v+Cgv//fX3//3Jy//+Xl///x8f///b2/////////////+/v///Jyf//oqL//4KC//+env//2Nj////////////////////////v7///1dX//+Tk///+/v///////////////////////////////////v7///j4///y8v//+Pj///39///////////////////+/v//+/v///b2///t7f//4eH//9PT///Fxf//trb//6en//+Wlv//h4f//39///+Bgf//gID//3V1//9oaP//ZGT+/2tr+f9zc/P/e3vs/4KC5f+Jid3/l5fV/6yszP/CwsL/1ta5/9/frv/Z2aT/ysqZ/7S0j/+goIT/jY15/3t7bv9qamP/W1tY/01NTv9BQUT/ODg6/zExMP8rKyf/JiYf/yEhF/8dHRD/GRkK/xcXBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8XFwP/HBwI/yEhDv8nJxX/Ly8d/zc3Jf9AQC7/Sko3/1ZWQf9hYUv/ZWVV/2dnYP9gYGr/UlJ1/0REgP8zM4v/MDCW/0ZGoP9gYKv/iIi1/7e3v//r68j/+PjR/8rK2v+bm+L/fX3p/2ho8P9XV/b/b2/8/4OD//95ef//b2///5eX///Fxf//9fX/////////////6en//8TE//+dnf//gID//5iY///Ozv///Pz///////////////////Ly///Jyf//xsb//+jo//////////////////////////////39///q6v//3t7//9TU///Ozv//0dH//9fX///b2///29v//9jY///S0v//y8v//8LC//+3t///rKz//5+f//+Skv//h4f//4GB//+AgP//f3///3h4//9wcP//bW3//3Jy//95efz/gID2/4eH8P+Njen/lZXi/6am2v+7u9L/0dHJ/+Liv//j47X/1dWr/8DAof+qqpb/lpaL/4ODgP9wcHb/X19r/1FRYP9FRVb/PDxL/zQ0Qf8tLTf/JiYu/yAgJf8bGx3/GRkW/xcXD/8WFgj/FRUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/yAgAf8kJAf/KSkN/zAwE/83Nxv/Pz8j/0hIK/9SUjT/XV0+/2JiSP9lZVL/Y2Nc/1hYZ/9KSnL/Ozt9/ywsh/81NZL/TEyd/2pqp/+VlbH/xMS7//T0xf/r687/u7vW/4+P3/90dOb/X1/t/1VV9P9zc/n/hIT+/3R0//9tbf//lpb//8LC///z8//////////////l5f//wcH//5ub//9/f///kZH//8PD///09P//////////////////9/f//8vL//+xsf//xMT///Pz//////////////////////////////f3///c3P//x8f//7q6//+vr///qan//6en//+np///pqb//6Oj//+dnf//l5f//4+P//+Hh///goL//39///9/f///f3///3x8//95ef//eXn//319//+Dg///iYn+/46O+f+Tk/T/mprt/6am5v+4uN//zs7X/+Lizv/r68X/4eG7/8vLsv+1taf/n5+d/4uLkv93d4j/ZWV9/1ZWcv9KSmj/QEBd/zg4Uv8vL0j/Jyc+/yEhNf8eHiz/Hh4j/x8fG/8gIBT/ICAN/x8fB/8fHwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/KioB/ywsBf8xMQv/NzcR/z8/Gf9HRyD/UFAp/1paMv9gYDv/YWFF/2RkT/9cXFn/UFBj/0JCbv8zM3n/KiqD/zs7jv9SUpn/dnaj/6Ghrf/Q0Lf/+PjB/93dyv+urtP/hITb/21t4/9XV+r/Vlbw/3Z29v+Dg/z/cHD//2pq//+Tk///vr7///Hx/////////////+Pj//+/v///mpr//4CA//+Jif//trb//+fn///////////////////8/P//19f//62t//+mpv//zc3///b2//////////////////////////////n5///i4v//ysr//7i4//+pqf//nZ3//5OT//+Njf//iIj//4WF//+Dg///goL//4OD//+Ghv//iYn//4qK//+Kiv//i4v//46O//+Rkf//lZX//5mZ//+dnfz/oqL2/6ur8P+7u+r/z8/j/+Pj2//w8NP/6enK/9XVwf++vrf/p6eu/5KSo/9+fpn/a2uP/1xchP9PT3n/RUVv/zw8ZP8yMlr/KipP/yQkRf8iIjz/JCQy/ycnKf8oKCH/KSkZ/yoqEv8pKQv/KCgF/ykpAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/NDQE/zk5Cf8/Pw//R0cW/09PHv9YWCb/XV0v/19fOP9hYUH/YGBL/1RUVf9ISGD/Oztq/ysrdf8sLH//QECK/1palf+Cgp//rKyp/9vbs//19b3/0NDG/6Kiz/97e9f/Zmbf/1BQ5v9YWO3/eHjz/4KC+f9sbP3/Z2f//4+P//+4uP//7e3////////9/f//4eH//8DA//+bm///gYH//4OD//+oqP//19f///39///////////////////j4///urr//5iY//+mpv//0dH///T0//////////////////////////////7+///v7///3Nz//8zM//+/v///tLT//6ys//+mpv//oaH//5+f//+fn///oKD//6Ki//+kpP//o6P//6Oj//+kpP//pqb//6io//+qqv//rq7+/7W1+f/Dw/P/1dXt/+np5v/19d//8PDX/9zcz//ExMb/rq69/5iYs/+EhKn/cXGf/2Fhlf9VVYr/SkqA/0BAdf82Nmv/LS1g/ycnVv8nJ0z/KipC/y0tOP8wMC//MTEn/zMzHv8zMxf/MzMQ/zIyCv8xMQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP89PQL/QUEH/0dHDf9OThT/VlYb/1tbI/9cXCz/Xl41/2BgPv9ZWUj/TU1S/0FBXP8zM2b/JiZx/zAwe/9FRYb/Y2OQ/4yMmv+2tqX/5OSu/+7uuP/ExMH/mJjK/3R00/9fX9v/TEzi/1lZ6f95ee//gYH1/2lp+v9jY///ior//7Gx///n5/////////v7///c3P//wMD//56e//+Cgv//gID//5mZ///Gxv//8fH///////////////////Dw///Ly///paX//42N//+oqP//zc3//+3t///////////////////////////////////7+///7+///+Li///X1///zs7//8jI///Dw///wcH//7+////AwP//wMD//76+//+7u///u7v//7u7//+7u///vr7//8bG///S0vr/4+P1//Pz8P/6+un/8/Pi/9/f2//Hx9P/sbHK/5ubwv+Hh7j/dXWv/2Zmpf9aWpv/T0+Q/0VFhv86Onz/MDBx/ysrZ/8rK1z/Ly9S/zMzSP82Nj//OTk1/zo6LP88PCT/PDwc/z09FP88PA7/OzsI/zo6AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/0ZGAf9JSQb/T08L/1VVEv9YWBn/Wloh/1tbKf9eXjH/W1s7/1FRRP9GRk7/OjpY/ywsYv8lJWz/NTV3/0tLgf9ubov/lZWW/7+/oP/q6qr/5eWz/7q6vf+Pj8b/bW3O/1lZ1v9ISN7/Wlrl/3p66/+AgPH/Z2f3/19f/P+Dg///qan//9/f/////////Pz//9nZ//+9vf//oaH//4WF//9/f///jIz//7S0///e3v///////////////////Pz//9zc//+6uv//lZX//4iI//+kpP//xMT//+Hh///39////////////////////////////////////f3///f3///w8P//6en//+Tk///h4f//3t7//93d///Z2f//1dX//9TU///U1P//2Nj//+Dg///q6v//9fX8//399//8/PL/8PDs/9zc5f/Fxd7/sLDW/5ubzv+IiMb/eHi9/2trs/9fX6r/VFSg/0lJlv8+Poz/NDSB/y4ud/8wMG3/NDRi/zk5WP88PE7/Pz9E/0JCO/9ERDL/RUUp/0ZGIf9GRhn/RkYS/0VFDP9ERAb/Q0MBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1FRBP9UVAn/VlYP/1dXFv9ZWR7/W1sm/1xcLv9UVDf/SkpA/z8/Sv8zM1T/JSVe/ycnaP86OnL/U1N8/3d3hv+enpH/x8eb/+zspf/b267/sbG3/4eHwP9nZ8n/U1PR/0VF2f9bW+D/eXnn/4CA7f9lZfP/W1v4/319/f+hof//1dX////////+/v//2Nj//7u7//+goP//hob//39///+Dg///oqL//8nJ///x8f//////////////////7u7//8zM//+trf//jIz//4SE//+cnP//t7f//9DQ///m5v//9vb//////////////////////////////////////////////v7///z8///7+///+Pj///b2///29v//+Pj///z8/////////////////f/19fj/5ubz/9HR7f+9vef/qqrg/5iY2f+IiNH/eXnJ/21twf9iYrj/V1eu/0xMpf9AQJv/NzeR/zIyh/80NH3/OTly/z4+aP9CQl7/RUVU/0lJSv9LS0H/TU03/09PLv9QUCb/UFAe/1BQF/9PTxD/Tk4K/01NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/U1MC/1NTB/9VVQ3/V1cU/1hYG/9aWiL/VlYq/01NM/9DQzz/ODhG/ywsT/8hIVn/Kytj/z4+bf9bW3f/gICB/6Wli//Pz5X/6+uf/9LSqf+pqbL/gIC7/2JixP9OTsz/Q0PU/1tb2/95eeL/f3/p/2Vl7/9WVvT/dXX5/5mZ/f/Jyf///v7////////b2///urr//6Cg//+Fhf//f3///4CA//+QkP//tbX//9vb///8/P/////////////8/P//4OD//8LC//+lpf//iIj//4CA//+Rkf//qKj//729///Q0P//4eH//+7u///4+P///f3////////////////////////////////////////////////////////+/v//+Pj//+7u///h4f3/0ND5/8DA9P+wsO//oKDp/5KS4v+Ghtz/enrU/29vzP9kZMT/WVm7/01Nsv9CQqn/OTmf/zc3lv86Ooz/Pz+C/0REeP9ISG7/TExj/09PWf9TU1D/VVVG/1dXPf9XVzT/V1cr/1ZWI/9VVRv/U1MU/1JSDf9RUQj/T08CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9UVAH/U1MF/1RUC/9WVhH/WFgY/1ZWH/9OTif/RUUv/zw8OP8xMUH/JSVL/yAgVP8wMF7/RERo/2Rkcv+IiHz/ra2G/9XVkP/o6Jr/ysqj/6KirP95ebX/XV2+/0pKx/9BQc//WlrW/3d33f9+fuT/ZWXq/1NT8P9tbfX/j4/5/7u7/v/29v///////+Dg//+6uv//oaH//4aG//99ff//gYH//4OD//+hof//xcX//+jo///////////////////09P//19f//7u7//+hof//iIj//35+//+Ghv//lpb//6mp//+5uf//xsb//9LS///d3f//5eX//+zs///v7///8PD//+7u///s7P//6en//+Xl///f3///19f//87O///Dw///uLj9/62t+f+iovX/l5fw/4yM6v+CguT/eHjd/21t1v9jY8//V1fH/0xMvv9CQrb/PDyt/zw8o/9AQJr/RkaQ/0pKhv9PT3z/U1Ny/1dXaP9aWl7/W1tV/1xcS/9bW0L/Wlo5/1hYMP9WVif/VVUf/1RUGP9SUhH/UVEL/1BQBf9QUAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9SUgP/VFQI/1ZWDv9WVhX/T08c/0dHI/8/Pyv/NTU0/ysrPf8fH0b/IyNP/zQ0Wf9MTGP/bW1t/4+Pdv+zs4D/2dmK/+LilP/Cwp3/m5um/3R0sP9ZWbj/RkbB/0BAyf9ZWdD/dXXY/35+3/9nZ+X/UFDr/2Rk8P+GhvX/ra35/+rq/f//////6en//7u7//+jo///iIj//3d3//+Bgf//f3///4mJ//+trf//z8////Hx///////////////////t7f//0dH//7m5//+goP//ior//39///+AgP//h4f//5OT//+goP//ra3//7e3//+/v///xMT//8bG///Gxv//xcX//8LC//++vv//ubn//7Oz//+trf//pqb//56e/f+Wlvn/jo71/4WF8P98fOv/cnLl/2ho3/9eXtj/U1PR/0pKyf9DQ8H/QUG4/0REsP9ISKf/Tk6d/1JSlP9WVor/WlqB/15ed/9gYG3/YGBj/19fWf9eXlD/XFxG/1paPf9YWDT/V1cs/1ZWJP9UVBz/U1MV/1JSD/9RUQn/T08DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1NTAf9TUwb/VFQL/09PEv9ISBj/QEAg/zg4J/8uLjD/JCQ4/xwcQf8nJ0r/ODhU/1RUXf90dGf/lpZx/7q6ev/b24T/3NyO/7y8l/+WlqD/b2+p/1VVsv9DQ7v/Pj7D/1dXyv9zc9L/f3/Z/2ho3/9PT+X/Wlrr/3x88P+fn/X/2dn5/////f/09P//wsL//6Wl//+MjP//dHT//3t7//+Cgv//fX3//4+P//+zs///1NT///T0//////////////39///o6P//0ND//7m5//+jo///jo7//4KC//9/f///gID//4OD//+Kiv//kJD//5WV//+amv//nZ3//56e//+env//nJz//5qa//+Wlv//kZH//4yM//+Ghv3/f3/5/3h49f9wcPD/Z2fr/15e5f9WVt//Tk7Z/0lJ0v9ISMv/SkrD/05Ou/9TU7L/V1eq/1tbof9fX5f/YmKO/2RkhP9lZXv/ZGRx/2JiZ/9gYF7/Xl5U/11dS/9bW0L/Wlo5/1hYMP9XVyj/VlYg/1RUGf9TUxL/UVEM/1BQBv9PTwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1FRBP9NTQn/R0cP/0FBFf85ORz/MTEj/ygoK/8dHTT/HR08/ysrRf8/P07/XFxY/3t7Yf+cnGv/wMB0/9vbfv/X14f/tbWR/5GRmv9ra6P/UlKs/0BAtP88PLz/VVXE/29vzP9+ftP/amrZ/1BQ4P9SUuX/cnLr/5KS8P/GxvT//Pz4//39/P/Nzf//qKj//5GR//92dv//cHD//4KC//9/f///eXn//5SU//+0tP//09P///Ly//////////////v7///l5f//z8///7q6//+mpv//kZH//4KC//9+fv//gID//4GB//9/f///fX3//3t7//95ef//eHj//3d3//92dv//dHT//3Fx//9ubv//aWn8/2Rk+P9fX/T/W1vw/1ZW6/9TU+X/UVHg/1FR2f9UVNP/V1fM/1tbxP9fX7z/Y2O0/2ZmrP9paaP/amqa/2pqkf9paYj/Z2d+/2Vldf9iYmv/YGBi/15eWP9cXE//WlpG/1hYPf9WVjT/VFQs/1NTJP9SUhz/UlIV/1FRD/9RUQn/UFAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/TEwB/0VFBv9AQAz/OTkS/zIyGP8qKiD/ISEn/xkZL/8gIDf/Ly9A/0dHSf9kZFL/goJb/6KiZf/ExG7/2dl3/9HRgf+wsIr/jIyT/2honP9PT6X/PT2t/zo6tv9SUr3/bGzF/35+zP9sbNP/UlLZ/0tL3/9oaOX/hYXq/7Ky7//t7fP////3/93d+v+urv3/lZX//319//9oaP//eHj//4OD//95ef//eHj//5aW//+zs///zs7//+7u//////////////f3///f3///ysr//7e3//+kpP//kpL//4GB//94eP//enr//39///+Cgv//goL//39///97e///dnb//3Fx//9tbf//amr9/2dn+v9kZPf/Y2Pz/2Ji7/9iYur/Y2Pl/2Vl3/9nZ9n/aWnT/2xszP9ubsX/b2+9/3Bwtv9wcK3/b2+l/21tnP9qapP/Z2eK/2Rkgf9hYXj/Xl5u/1paZf9XV1z/VFRT/1FRSv9OTkH/TEw4/0pKMP9JSSj/SUkg/0hIGf9JSRL/SUkM/0tLB/9LSwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/Pj4E/zg4Cf8yMg7/KysV/yQkG/8aGiP/GBgr/yQkM/82Njv/UFBE/2trTP+JiVX/qKhf/8jIaP/W1nH/zMx6/6urg/+IiIz/ZGSV/0xMnv86Oqb/ODiu/09Ptv9oaL7/fHzF/29vzP9WVtP/RkbZ/1xc3/96euT/n5/p/9nZ7f////L/7u71/7q6+f+amvz/hIT+/2tr//9oaP//f3///4KC//9ycv//d3f//5SU//+vr///x8f//+fn//////////////f3///d3f//x8f//7a2//+lpf//lZX//4SE//92dv//b2///3Bw//92dv//e3v//39///+Bgf//goL+/4GB+/+AgPj/f3/1/35+8f98fO3/fHzp/3t75P96et//enrZ/3l50/94eMz/dnbF/3R0vv9xcbb/bW2v/2lppv9lZZ7/YWGV/11djP9ZWYP/VVV6/1FRcf9OTmj/S0tf/0hIVv9FRU3/Q0NE/0FBO/9AQDP/Pz8r/z4+I/8+Phz/Pj4V/z8/D/9AQAn/QUEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP83NwH/MTEG/ysrC/8lJRH/HR0Y/xUVHv8bGyb/KSku/z4+Nv9YWD7/c3NH/4+PT/+urlj/ysph/9PTav/GxnP/pqZ8/4SEhf9hYY7/SkqW/zg4n/82Nqf/TEyv/2Rkt/96er7/cnLF/1pazP9ERNL/UVHY/25u3f+Pj+L/wsLn//f36//7++//y8vz/6Gh9v+Kivn/c3P8/19f/v9wcP//goL//35+//9ra///dXX//5CQ//+oqP//v7///93d///7+/////////v7///h4f//x8f//7a2//+oqP//mZn//4uL//99ff//cHD//2dn//9jY///ZWX+/2ho/P9sbPn/cHD2/3Jy8/9zc+//dHTr/3R05/9zc+L/cnLd/3Bw2P9tbdL/amrM/2dnxf9kZL7/YWG3/11dr/9ZWaf/Vlaf/1JSl/9PT47/S0uF/0hIff9FRXT/QUFr/z8/Yv88PFn/OjpQ/zg4R/82Nj7/NTU2/zQ0Lv8zMyb/MzMf/zMzGP80NBL/NTUM/zc3Bv86OgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8qKgP/JCQI/x4eDv8WFhT/FRUa/yAgIf8wMCn/R0cw/19fOP96ekH/lZVJ/7OzUv/Kylv/z89j/8HBbP+hoXX/gYF+/19fhv9HR4//NjaX/zQ0n/9JSaf/YGCv/3d3tv90dL3/XV3E/0ZGyv9HR9D/Y2PW/39/2/+srOD/4uLl////6f/g4O3/rq7w/5CQ8/97e/b/ZGT5/15e+/93d/3/g4P+/3h4//9lZf//cXH//4qK//+goP//tLT//8/P///x8f/////////////s7P//z8///7q6//+rq///np7//5KS//+Ghv7/enr9/29v+/9lZfn/Xl72/1lZ8/9XV/D/Vlbt/1ZW6f9WVuX/V1fg/1ZW2/9WVtb/VVXQ/1NTyv9RUcT/T0+9/01Ntv9KSq//SEin/0VFoP9CQpf/Pz+P/zw8h/85OX7/NjZ1/zQ0bf8xMWT/Ly9b/y0tUv8rK0r/KipB/ykpOf8oKDH/KCgp/ygoIv8oKBv/KSkU/yoqDv8sLAn/Li4EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/yMjAf8dHQX/FhYK/xISEP8ZGRb/JiYd/zk5JP9QUCv/Z2cz/4CAO/+bm0P/uLhL/8jIVP/Ly13/vLxl/52dbv9+fnb/XV1//0VFh/81NZD/MTGY/0ZGn/9cXKf/c3Ou/3Z2tf9hYbz/SkrD/0BAyf9XV8//cXHU/5eX2f/Jyd7/+vri//Pz5v/Dw+r/mZnt/4KC8P9tbfP/WVn1/2Ji9/97e/n/g4P7/3Nz/P9hYf3/a2v+/4KC//+Wlv//qKj//7+////g4P//+vr////////5+f//4OD+/8bG/f+ysvz/pKT7/5iY+f+Ojvf/hIT1/3t78/9ycvD/amrt/2Ji6v9bW+b/VVXi/1BQ3v9MTNn/SUnU/0ZGz/9ERMn/QkLD/0BAvP8+Prb/PDyu/zk5p/83N6D/NTWY/zMzkP8xMYj/Ly9//y0td/8rK27/Kipm/ygoXf8mJlT/JCRM/yIiRP8gIDv/Hx8z/x0dLP8cHCT/HBwd/x0dFv8fHxD/ISEL/yMjBv8mJgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xUVAv8QEAf/FBQM/x8fEv8vLxj/Q0Mf/1hYJv9vby3/h4c1/6KiPf+8vEX/xcVN/8jIVv+3t17/mZlm/3t7b/9aWnf/Q0N//zMziP8vL5D/Q0OX/1hYn/9ubqb/d3et/2VltP9PT7v/PT3B/0tLx/9lZcz/g4PR/7Ky1v/k5Nv//v7f/9nZ4/+qqub/iorp/3Z27P9gYO//U1Px/2dn8/99ffX/goL2/3Bw9/9dXfj/ZGT5/3l5+v+Li/r/nJz6/62t+//Jyfr/5+f6//z8+f////j/9PT3/9zc9v/ExPX/sbHz/6Oj8f+Xl+//jY3s/4WF6f99feb/dnbj/3Bw3/9qatv/ZGTW/19f0f9aWsz/VlbH/1JSwf9OTrv/S0u0/0hIrf9GRqb/Q0Of/0FBl/8/P5D/PT2I/zs7gP86Onf/ODhv/zY2Z/80NF7/MjJW/zAwTv8tLUX/Kys9/ygoNf8lJS7/ISEm/x4eH/8aGhn/FxcT/xYWDf8XFwj/GhoDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xERBP8aGgn/JycO/zk5FP9MTBr/YWEh/3d3KP+Oji//qKg3/729P//Cwkf/xMRP/7KyV/+VlV//eHhn/1hYb/9BQXf/MTF//y0th/9AQI//VFSW/2lpnv92dqX/aWms/1RUsv8/P7j/QEC+/1lZxP9ycsn/m5vO/8rK0//4+Nf/7u7b/8DA3/+Xl+L/fn7l/2tr6P9WVur/U1Ps/2pq7v9+fvD/gIDx/25u8v9aWvP/XFz0/25u9P9/f/T/j4/0/52d9P+ysvT/zc3z/+jo8v/6+vH////w//T07v/g4Oz/y8vq/7m56P+qquX/nZ3i/5KS3v+Jidv/goLX/3t70/91dc7/cHDJ/2trxP9nZ77/Y2O4/19fsv9cXKz/WFil/1ZWnv9TU5f/UFCP/05Oh/9LS4D/SUl4/0dHcP9FRWj/QkJf/0BAV/8+Pk//OztH/zg4P/81NTf/MjIw/y8vKP8rKyH/Jycb/yIiFP8eHg//GBgJ/xMTBP8TEwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/FxcB/yMjBv8zMwr/REQQ/1ZWFv9qahz/f38j/5WVKv+urjH/vLw4/7+/QP/BwUj/ra1Q/5GRWP90dF//VlZn/z8/b/8vL3f/Kip//zw8hv9QUI7/ZGSV/3R0nP9sbKP/WFip/0REsP85ObX/TU27/2RkwP+GhsX/srLK/+Hhz//7+9P/19fW/6ys2v+IiN3/dHTg/2Fh4v9OTuX/VFTn/2pq6P99fer/f3/r/21t7P9aWu3/VVXt/2Ji7f9zc+3/gYHt/46O7f+dnez/srLr/8rK6v/h4ej/8/Pn//z85f/39+L/6Ojg/9fX3f/Hx9r/ubnW/62t0/+ios//mZnK/5GRxf+JicD/g4O7/319tf94eLD/dHSp/3Bwo/9sbJz/aWmV/2Zmjv9jY4f/YWF//15ed/9cXHD/Wlpo/1hYYP9WVlj/U1NQ/1FRSP9OTkD/Sko5/0ZGMf9CQir/Pj4j/zg4HP8zMxb/LS0Q/yYmC/8gIAb/GhoCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/Ly8C/z4+B/9OTgz/YGAR/3NzF/+Hhx3/nZ0k/7KyK/+6ujL/vb05/729Qf+oqEj/jY1Q/3FxWP9UVGD/PT1n/y4ub/8oKHb/OTl+/0xMhf9fX4z/cXGT/29vmv9dXaD/Skqm/zc3rP9BQbL/WFi3/3JyvP+bm8H/xsbG//Lyyv/t7c7/w8PR/5ub1P98fNf/a2va/1hY3P9JSd7/VVXg/2pq4v98fOP/fn7k/25u5f9bW+X/UFDm/1ZW5v9lZeX/c3Pl/39/5P+KiuP/mZni/6ys4P/Bwd7/1NTc/+Xl2v/y8tf/9fXU//Dw0f/l5c7/2dnK/83Nxv/CwsH/ubm8/7Cwt/+oqLL/oqKs/5ycpv+WlqD/kZGa/42Nk/+JiYz/hYWF/4KCfv9/f3f/fHxv/3l5Z/92dmD/c3NY/3BwUP9tbUn/aWlB/2VlOv9hYTL/XFwr/1dXJP9SUh7/TEwY/0VFEv89PQz/NTUH/y0tAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/SUkE/1lZCP9qag3/fHwT/4+PGP+kpB//tLQl/7a2LP+6ujP/uLg6/6KiQf+IiEn/bm5Q/1JSWP87O1//LCxm/yYmbv82NnX/SEh8/1tbg/9tbYr/cHCR/2Fhl/9PT53/Ozuj/zc3qf9MTK7/YmKz/4WFuP+urrz/2dnB//b2xf/a2sj/srLM/42Nz/9zc9H/Y2PU/1BQ1v9GRtj/VVXZ/2ho2/96etz/fn7c/29v3f9eXt3/T0/d/0xM3f9XV9z/ZGTc/3Bw2/95edn/g4PY/5GR1v+iotT/s7PR/8PDzv/S0sv/39/I/+joxf/s7MH/6uq8/+TkuP/d3bP/1tau/87Oqf/Hx6P/wMCd/7q6l/+1tZH/r6+K/6qqg/+lpXz/oaF1/52dbv+ZmWf/lZVf/5GRWP+NjVD/iYlJ/4WFQv+BgTr/fHwz/3d3LP9xcSX/a2sf/2RkGf9dXRP/VlYN/05OCP9ERAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9XVwH/ZGQF/3R0Cf+FhQ7/mJgT/6urGf+ysh//tLQm/7i4LP+zszP/nZ06/4SEQf9ra0j/UFBQ/zk5V/8qKl7/JCRl/zQ0bP9FRXP/V1d6/2hogf9wcIf/ZWWO/1NTlP9BQZr/MjKf/0BApf9VVar/cHCu/5eXs/++vrf/6Oi7/+7uv//JycL/paXF/4KCyP9ra8r/W1vN/0lJz/9ERND/VFTR/2Vl0/92dtP/fX3U/3Fx1P9hYdT/UlLU/0hI0/9KStP/VVXR/2Bg0P9qas7/cnLN/3t7yv+Ghsj/lJTF/6Ojwv+wsL//vLy7/8fHt//R0bP/2Niu/93dqv/e3qX/3d2f/9ramv/W1pT/0tKO/87Oh//KyoH/xsZ6/8LCc/++vm3/ubll/7W1Xv+xsVf/rKxQ/6enSf+iokL/nZ06/5eXM/+RkS3/i4sm/4WFIP9+fhr/dnYU/29vDv9mZgn/XV0F/1VVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9wcAH/fn4F/4+PCv+hoQ//ra0U/6+vGv+xsSD/tbUm/62tLP+XlzP/f386/2dnQf9NTUj/NjZP/ykpVv8iIl3/MTFj/0JCav9TU3H/ZGR3/25ufv9oaIT/WFiK/0dHkP80NJX/NDSb/0lJoP9eXqT/gYGp/6enrf/NzbH/7++1/+DguP+7u7v/mZm+/3h4wf9lZcP/VVXF/0RExv9CQsj/UVHJ/2Fhyv9ycsr/e3vL/3R0y/9mZsr/WFjK/0tLyf9DQ8j/R0fG/1BQxf9ZWcP/YWHB/2hovv9vb7v/eXm4/4WFtf+RkbH/nJyt/6amqf+vr6T/uLig/8DAm//GxpX/y8uQ/87Oiv/OzoT/zc1+/8vLeP/JyXH/xsZq/8PDZP/BwV3/vr5W/7y8T/+5uUj/trZB/7OzOv+vrzT/q6st/6amJv+goCD/mJga/5CQFP+IiA//f38K/3V1Bv9tbQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+JiQL/mZkG/6enC/+qqg//ra0V/6+vGv+ysiD/p6cm/5GRLP97ezP/Y2M5/0pKQP80NEf/JydN/yEhVP8vL1v/Pz9h/09PaP9gYG7/a2t0/2pqev9cXID/S0uG/zo6i/8uLpH/PT2W/1BQmv9sbJ//kJCj/7S0p//a2qv/7Oyu/9PTsf+wsLT/jo63/3Fxuf9fX7v/UFC8/0BAvv8/P7//Tk7A/11dwP9sbMH/eHjB/3Z2wP9qasD/XV2//1FRvv9FRbz/Pz+7/0FBuf9JSbf/UVG0/1hYsf9eXq7/Y2Or/2trp/90dKP/fn6f/4iImv+RkZb/mZmR/6GhjP+oqIb/rq6A/7S0ev+6unT/vr5u/8DAaP/BwWH/wMBb/729VP+7u07/uLhH/7a2QP+zszr/sbEz/6+vLf+trSb/q6sg/6mpGv+mphX/oKAQ/5iYC/+Ojgb/hIQCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+hoQL/pqYG/6ioC/+qqhD/ra0V/66uGv+goCD/i4sm/3V1LP9fXzL/R0c4/zExP/8kJEX/Hx9M/ywsUv88PFj/S0tf/1xcZf9oaGv/aWlx/2Bgdv9QUHz/QECB/y8vh/8yMov/RUWQ/1palf97e5n/nZ2d/8DAoP/i4qT/5OSn/8bGqv+mpqz/hoau/2pqsP9aWrL/S0uz/z09tf88PLX/S0u2/1lZtv9mZrb/c3O2/3Z2tv9ubrX/YmKz/1dXsv9MTLD/QUGu/zs7rP87O6r/QUGn/0hIpP9OTqD/U1Od/1hYmf9dXZX/ZGSQ/2xsi/91dYf/fX2B/4WFfP+MjHf/kpJx/5iYa/+enmX/pKRf/6qqWP+vr1L/s7NM/7W1Rf+1tT//tLQ5/7GxMv+vryz/ra0m/6urIP+pqRr/p6cV/6WlEP+jowv/oaEH/5ubAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+kpAP/pqYH/6ioC/+rqxD/qakV/5iYGv+EhB//b28l/1paK/9DQzH/Ly83/yIiPf8dHUP/KytJ/zo6T/9ISFX/WFhb/2VlYf9oaGf/Y2Nt/1RUcv9ERHf/NDR8/ysrgf86Oob/S0uK/2Zmjv+IiJL/qamW/8vLmf/k5Jz/2tqf/7y8of+dnaT/f3+m/2Vlp/9VVan/R0eq/zk5q/85Oav/R0es/1RUrP9gYKv/bW2r/3R0qv9xcan/aGin/11dpv9TU6T/SUmh/z8/n/84OJz/NTWZ/zg4lv8+PpL/RESO/0hIiv9MTIb/UFCB/1VVfP9cXHf/ZGRy/2trbf9zc2f/eXlh/4CAXP+Hh1b/jY1Q/5KSSv+YmEP/n589/6WlN/+qqjH/ra0r/62tJf+rqyD/qaka/6enFf+lpRD/o6ML/6GhB/+fnwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+jowP/pqYH/6mpC/+hoQ//j48U/319Gf9paR7/VVUk/z8/Kv8rKy//Hx81/xwcO/8pKUH/NzdH/0ZGTP9UVFL/YmJY/2ZmXf9kZGP/WFho/0lJbf86OnL/Kip3/y4ue/9AQH//VFSE/3R0h/+UlIv/s7OO/9PTkf/g4JT/0dGW/7Ozmf+Wlpv/eXmc/2Bgnv9RUZ//RESg/zY2oP82NqH/Q0Oh/09PoP9bW6D/Zmaf/29vnv9wcJz/a2ub/2Njmf9ZWZb/UFCU/0dHkf8+Po7/NjaL/zExh/8xMYT/NDSA/zk5e/8+Pnf/QkJy/0VFbf9JSWj/Tk5j/1RUXf9cXFj/ZGRS/2trTf9yckf/eHhB/39/O/+GhjX/jIww/5OTKv+amiT/oqIf/6enGv+npxT/paUQ/6OjC/+hoQf/n58DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+kpAP/pKQG/5iYCv+Hhw//dXUT/2JiGP9PTx3/Ojoj/ygoKP8dHS3/Gxsz/ygoOP82Nj7/Q0NE/1FRSf9fX07/ZGRU/2RkWf9bW17/TU1j/z4+aP8vL2z/Jydx/zU1df9GRnn/YGB8/39/gP+enoP/vb2G/9fXif/b24v/yMiO/6urkP+QkJH/dHST/1xclP9OTpX/QUGV/zQ0lv8zM5b/Pz+V/0pKlf9VVZT/YGCT/2pqkf9tbZD/bGyO/2dni/9fX4n/VlaG/01Ng/9FRYD/PT19/zU1ef8vL3X/LCxx/ywsbP8vL2j/NDRj/zg4Xv87O1n/Pj5U/0JCT/9ISEn/UFBE/1hYPv9fXzn/Z2cz/25uLv92dij/fX0j/4WFHv+NjRn/lpYU/56eD/+jowv/oaEH/56eAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+cnAL/jY0G/35+Cv9tbQ7/W1sT/0lJF/81NRz/JCQh/xoaJv8aGiv/Jycw/zQ0Nv9BQTv/T09A/1xcRf9iYkr/Y2NP/15eVP9QUFn/QkJe/zQ0Yv8mJmb/Kytq/zs7bv9PT3L/bGx1/4qKeP+np3v/xcV+/9bWgP/V1YL/wMCE/6Wlhv+Kiof/cHCI/1lZif9KSor/Pj6K/zIyiv8wMIr/OjqJ/0ZGiP9QUIf/WlqG/2RkhP9paYL/amqA/2hofv9jY3v/XFx4/1NTdf9LS3L/RERu/z09av82Nmb/Li5i/ykpXv8mJln/JydU/ysrUP8vL0v/MjJG/zU1QP85OTv/Pz82/0hIMf9QUCv/WFgm/2FhIf9paRz/cnIX/3p6E/+EhA7/jY0K/5eXBv+dnQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+DgwL/dHQF/2RkCf9TUw3/QkIR/y8vFv8gIBr/Fxcf/xoaJP8nJyn/MzMu/0BAM/9NTTj/Wlo8/2BgQf9hYUb/X19L/1NTT/9GRlT/OTlY/yoqXP8kJGD/MTFj/0FBZ/9aWmr/d3dt/5OTcP+vr3P/ysp1/9PTd//Pz3n/ubl7/56efP+FhX3/bGx+/1ZWfv9HR3//PDx//zAwf/8sLH7/NjZ9/0FBfP9LS3v/VFR5/15ed/9lZXX/Z2dz/2ZmcP9kZG3/YGBq/1lZZ/9RUWT/Skpg/0NDXP88PFj/NTVU/y4uT/8oKEv/IyNG/yEhQf8jIz3/Jyc4/ysrM/8uLi7/MzMp/zo6JP9ERB//TU0b/1ZWFv9fXxL/aWkN/3NzCf99fQb/h4cCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9rawH/W1sF/0tLCP86Ogz/KCgQ/xsbFP8UFBj/Gxsd/ycnIf8zMyb/Pz8q/0tLL/9YWDT/Xl44/19fPf9fX0H/VlZG/0lJSv88PE7/Ly9S/yIiVf8oKFn/NjZc/0pKYP9lZWL/gYFl/5ubaP+3t2r/y8ts/87Obv/JyW//srJx/5mZcv+BgXL/aWlz/1NTc/9ERHP/OTlz/y0tc/8pKXL/MjJx/z09b/9GRm7/T09s/1hYav9gYGj/ZGRl/2NjY/9jY2D/YWFc/11dWf9WVlb/Tk5S/0dHTv9AQEr/OjpG/zMzQf8tLT3/JiY5/yAgNP8dHS//Hh4r/yIiJv8mJiL/Kiod/zAwGf86OhT/REQQ/09PDP9ZWQj/Y2MF/29vAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9TUwH/QkIE/zIyB/8hIQr/FxcO/xMTEv8cHBb/Jyca/zIyH/8+PiP/Skon/1ZWK/9cXDD/XV00/15eOP9YWDz/TExA/z8/RP8zM0j/JSVL/yAgT/8tLVL/PT1V/1VVWP9wcFr/iopd/6SkX/++vmH/yclj/8rKZP/CwmX/q6tm/5OTZ/99fWj/ZmZo/1BQaP9BQWj/NjZn/ysrZ/8mJmb/Ly9k/zk5Y/9CQmH/Skpf/1NTXf9bW1v/YGBY/2FhVf9gYFL/YGBP/15eS/9ZWUj/UlJE/0tLQP9ERDz/Pj44/zc3NP8xMTD/Kios/yQkJ/8dHSP/GRkf/xsbG/8fHxb/IyMS/ykpDv80NAv/Pz8H/0pKBP9WVgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/KSkC/xsbBv8SEgn/ExMM/x4eEP8oKBT/MzMY/z09HP9JSSD/VVUj/1tbJ/9bWyv/XFwv/1lZM/9OTjf/QkI7/zY2Pv8qKkH/Hh5F/yUlSP8yMkv/RkZN/2BgUP95eVL/kpJU/6ysVv/BwVj/xcVZ/8bGWv+8vFv/paVc/46OXf94eF3/YmJd/01NXP8+Plz/NDRb/ykpWv8kJFn/LCxY/zU1Vv8+PlT/RkZS/05OUP9XV03/XV1L/15eSP9dXUX/XV1C/1xcPv9aWjv/VVU3/05OM/9HRzD/QEAs/zo6KP8zMyT/LCwg/yYmHP8fHxj/GBgU/xYWEP8ZGQ3/Hh4J/yUlBv8wMAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/FBQB/w8PBP8WFgf/ICAK/ykpDv80NBH/Pj4V/0hIGP9UVBz/WVkg/1lZI/9bWyf/WVkr/1BQLv9ERDH/ODg1/y0tOP8gIDv/HR0+/yoqQf86OkP/UlJG/2pqSP+Cgkr/mppM/7OzTf/BwU//wcFQ/8LCUf+1tVH/n59S/4mJUv90dFL/X19S/0pKUf87O1H/MTFQ/yYmT/8iIk3/KSlM/zIySv87O0j/Q0NG/0tLQ/9TU0H/Wlo+/1xcO/9bWzj/Wlo1/1paMv9ZWS7/VlYr/1BQJ/9JSST/QkIg/zs7HP80NBn/LS0V/ycnEf8gIA7/GBgL/xMTB/8VFQT/GxsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xkZAv8iIgX/KysI/zU1C/8/Pw7/SUkS/1NTFf9YWBj/WFgc/1lZH/9ZWSL/UVEm/0VFKf86Oiz/Ly8v/yMjMv8aGjT/IiI3/zAwOf9FRTz/XV0+/3R0QP+Li0H/o6ND/7i4RP+9vUX/vb1G/729R/+urkf/mJhH/4ODR/9vb0f/W1tH/0ZGRv83N0X/LS1E/yMjQ/8fH0H/JydA/zAwPv84ODz/QEA5/0hIN/9QUDT/V1cy/1paL/9ZWSz/WFgp/1hYJv9YWCP/VlYf/1FRHP9JSRn/QkIV/zs7Ev80NA//LS0M/yYmCP8fHwb/FxcD/xISAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/yUlAf8tLQP/NjYG/0BACf9KSgz/U1MP/1ZWEv9WVhX/V1cY/1hYG/9RUR7/RkYh/zw8I/8xMSb/JiYp/xoaK/8cHC7/KCgw/zo6Mv9RUTT/aGg2/35+N/+UlDn/q6s6/7m5O/+5uTz/uro8/7e3Pf+mpj3/kZE9/319Pf9paTz/VlY8/0JCO/8zMzr/KSk5/x8fN/8dHTb/JiY0/y8vMv82NjD/Pj4u/0ZGK/9OTin/VVUm/1hYJP9XVyH/VlYe/1ZWG/9VVRj/VVUV/1BQEv9JSQ//QUEM/zo6Cf8zMwb/KysD/yUlAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP85OQH/QkIE/0tLBv9TUwn/VFQL/1VVDv9VVRH/VlYT/1FRFv9GRhn/PDwb/zIyHv8oKCD/HBwj/xgYJf8iIif/MTEp/0dHK/9cXCz/cnIu/4iIL/+enjD/srIx/7a2Mv+1tTP/trYz/7GxM/+enjP/iooz/3Z2Mv9jYzL/UFAx/z09MP8uLi//JSUu/xwcLP8dHSv/JiYp/y4uJ/82NiX/PT0j/0VFIP9NTR7/VFQc/1ZWGf9VVRb/VFQU/1RUEf9TUw7/U1MM/05OCf9HRwb/Pz8E/zg4AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/Tk4B/1JSBP9TUwb/U1MI/1RUC/9VVQ3/UFAP/0ZGEv88PBT/MjIW/ygoGf8eHhv/FRUd/xwcH/8pKSD/PT0i/1NTJP9oaCX/fX0m/5KSJ/+npyj/srIp/7KyKf+ysin/s7Mq/6mpKv+VlSn/goIp/29vKf9cXCj/Skon/zc3Jv8pKSX/ICAk/xgYIv8dHSD/JiYf/y4uHf81NRv/PT0Z/0VFFv9NTRT/U1MS/1RUEP9TUw3/UlIL/1JSCP9SUgb/UVEE/0xMAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1NTAf9SUgP/U1MF/1RUB/9PTwn/RUUL/zw8Df8yMg//KSkR/x8fE/8VFRX/FxcX/yMjGP82Nhr/S0sb/19fHP90dB7/iIgf/52dH/+trSD/r68g/6+vIf+vryH/rq4h/5+fIf+MjCD/eXkg/2ZmH/9UVB//QkIe/zAwHf8kJBv/Ghoa/xcXGP8eHhf/JycV/y8vE/82NhH/Pj4Q/0ZGDv9OTgv/UlIJ/1JSB/9RUQX/UVED/1BQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9UVAH/UlIC/01NBP9ERAb/OzsH/zIyCf8oKAv/Hx8N/xUVDv8UFBD/Hx8R/zAwE/9ERBT/WFgV/2xsFv+AgBf/lJQX/6enGP+srBj/q6sZ/6ysGf+srBn/pqYZ/5SUGP+BgRj/b28X/11dF/9LSxb/OTkV/ygoFP8dHRP/FRUR/xgYEP8gIA7/KCgN/zAwC/84OAn/QEAI/0hIBv9PTwT/UVEC/1JSAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/0NDAf86OgL/MDAE/ycnBf8eHgf/FBQI/xERCf8bGwv/LCwM/z8/Df9TUw7/ZmYP/3l5D/+MjBD/oKAQ/6mpEf+pqRH/qKgR/6mpEf+oqBH/m5sR/4iIEf92dhD/ZGQP/1NTD/9BQQ7/Ly8N/yAgDP8WFgv/ExMJ/xsbCP8jIwf/KysF/zMzBP87OwL/REQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/JSUB/x4eAf8TEwL/Dw8E/xkZBf8pKQb/PDwH/09PCP9hYQj/dHQJ/4eHCf+amgr/pqYK/6amCv+mpgv/paUL/6amCv+goAr/jo4K/3x8Cv9qagn/WFgI/0dHCP81NQf/JCQG/xgYBf8REQT/FhYD/x8fAv8oKAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ykpAf87OwL/TU0C/19fA/9xcQP/g4ME/5aWBP+iogT/o6ME/6OjBf+jowX/o6ME/6GhBP+SkgT/gIAE/25uA/9dXQP/S0sC/zs7Av8lJQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4AAAf////////////////gAAAAf///////////////AAAAAA//////////////+AAAAAAB/////////////+AAAAAAAH////////////+AAAAAAAAf///////////+AAAAAAAAB///////////+AAAAAAAAAH//////////+AAAAAAAAAAf//////////AAAAAAAAAAD//////////gAAAAAAAAAAP/////////gAAAAAAAAAAB/////////wAAAAAAAAAAAP////////4AAAAAAAAAAAB////////8AAAAAAAAAAAAP///////+AAAAAAAAAAAAB////////AAAAAAAAAAAAAP///////gAAAAAAAAAAAAB///////wAAAAAAAAAAAAAP//////4AAAAAAAAAAAAAB//////8AAAAAAAAAAAAAAP/////+AAAAAAAAAAAAAAB//////gAAAAAAAAAAAAAAf/////wAAAAAAAAAAAAAAD/////4AAAAAAAAAAAAAAAf////+AAAAAAAAAAAAAAAD/////AAAAAAAAAAAAAAAA/////gAAAAAAAAAAAAAAAH////4AAAAAAAAAAAAAAAB////8AAAAAAAAAAAAAAAAP////AAAAAAAAAAAAAAAAD////gAAAAAAAAAAAAAAAAf///4AAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAP///gAAAAAAAAAAAAAAAAB///4AAAAAAAAAAAAAAAAAf//+AAAAAAAAAAAAAAAAAH///AAAAAAAAAAAAAAAAAA///wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAD//+AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAH//4AAAAAAAAAAAAAAAAAB//+AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAB//wAAAAAAAAAAAAAAAAAAf/8AAAAAAAAAAAAAAAAAAH//AAAAAAAAAAAAAAAAAAB//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//AAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAP//gAAAAAAAAAAAAAAAAAD//4AAAAAAAAAAAAAAAAAB//+AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAH//4AAAAAAAAAAAAAAAAAB///AAAAAAAAAAAAAAAAAA///wAAAAAAAAAAAAAAAAAP//8AAAAAAAAAAAAAAAAAD///gAAAAAAAAAAAAAAAAB///4AAAAAAAAAAAAAAAAAf//+AAAAAAAAAAAAAAAAAH///wAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAA////gAAAAAAAAAAAAAAAAf///4AAAAAAAAAAAAAAAAH////AAAAAAAAAAAAAAAAD////wAAAAAAAAAAAAAAAA////+AAAAAAAAAAAAAAAAf////gAAAAAAAAAAAAAAAH////8AAAAAAAAAAAAAAAD/////gAAAAAAAAAAAAAAA/////4AAAAAAAAAAAAAAAf/////AAAAAAAAAAAAAAAP/////4AAAAAAAAAAAAAAH/////+AAAAAAAAAAAAAAB//////wAAAAAAAAAAAAAA//////+AAAAAAAAAAAAAAf//////wAAAAAAAAAAAAAP//////+AAAAAAAAAAAAAH///////wAAAAAAAAAAAAD///////+AAAAAAAAAAAAB////////wAAAAAAAAAAAA////////+AAAAAAAAAAAAf////////wAAAAAAAAAAAP////////+AAAAAAAAAAAH/////////4AAAAAAAAAAH//////////AAAAAAAAAAD//////////8AAAAAAAAAB///////////gAAAAAAAAB///////////+AAAAAAAAB////////////4AAAAAAAB/////////////gAAAAAAB/////////////+AAAAAAB//////////////8AAAAAD///////////////4AAAAH////////////////8AAA///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8oAAAAQAAAAIAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADuDAwB/w4OAv8oKAT/TEwF/3BwBv+Skgf/np4I/56eCP+ZmQf/e3sG/1VVBf8uLgT8DAwC6gkJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/UdHAf89PQT/Ly8H/yMjCv8WFg3/JSUQ/0VFEv9paRT/jo4V/6enFf+oqBX/pqYV/4qKFP9iYhL/OzsQ/xwcDf8aGgv/Li4H/0JCBP9PTwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/VVUD/1RUB/9TUwv/Tk4Q/z8/FP8vLxj/Hh4c/x0dH/8zMyL/VlYk/3x8Jf+ioib/sLAm/6+vJf+bmyT/cXEi/0hIH/8kJBz/GxsY/y0tFP9BQRD/UFAL/1FRB/9HRwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8vLwP/QkII/05ODf9VVRP/VlYZ/1dXH/9PTyT/QEAp/y8vLf8fHzH/JSU0/z8/Nv9kZDj/jIw5/7CwOf+4uDj/r682/4eHNP9aWjH/MTEt/x0dKf8rKyT/QUEf/1JSGf9UVBP/UlIO/0hICP84OAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xYWAf8YGAb/JSUN/zMzFP9BQRv/UFAi/1lZKf9cXC//XFw2/1RUO/9ERED/MjJF/yMjSP8rK0v/R0dN/25uTv+amk7/vLxN/7+/S/+kpEj/c3NF/0REQP8kJDv/JiY2/z8/MP9TUyn/V1ci/1RUG/9FRRT/MTEN/x8fBv8UFAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/z8/A/8oKAr/GhoR/xkZGf8iIiL/MDAq/z4+Mv9MTDr/WVlC/2BgSf9jY0//XFxV/0tLWv84OF3/KChg/zExYv9NTWT/d3dk/6enYv/Hx2H/wMBe/5OTWv9eXlX/NDRQ/yQkSf85OUL/UlI7/1tbM/9XVyr/RUUi/y8vGv8bGxL/Hx8K/zk5AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/3R0Bf9eXg3/SUkV/zY2Hv8nJyj/Hx8y/yAgO/8oKET/NDRN/0NDVv9TU13/YGBk/2hoav9lZXD/VlZ0/0BAd/8uLnn/NjZ6/1NTev9/f3n/s7N3/9HRdP+4uHD/gIBr/0tLZf8qKl7/MTFW/05OTv9dXUX/Wlo7/0dHMv8uLij/HBwf/ykpFv9ISA3/Z2cFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/o6MB/6WlBv+VlQ//g4MY/3JyIv9hYS3/UFA4/0FBQ/80NE3/KytY/ygoYf8rK2r/NjZz/0ZGev9YWID/aGiG/25uiv9iYo3/SkqQ/zQ0kf87O5H/WVmQ/4mJjv/Cwor/1taG/6mpgf9qanr/PDxz/ywsa/9GRmL/Xl5Y/19fTv9LS0P/Ly84/yAgLf8yMiP/VlYZ/3p6D/+bmwb8oKABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD3m5sB/6enB/+pqRD/ra0a/6urJf+hoTH/lJQ9/4eHSf94eFX/aWlg/1paa/9LS3b/Pz9//zY2iP8zM5D/ODiW/0lJnP9fX6D/cXGk/25upv9VVaf/Ozun/0FBpv9hYaT/lpah/9LSnP/T05f/k5OQ/1ZWiP8yMoD/PDx2/1tbbP9kZGH/UFBV/zIySf8kJD3/Ojox/2JiJf+Kihr/oqIQ/6KiB/ygoAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/52dBv+mphD/rq4b/7W1Jv+7uzP/wMBA/8LCTf/AwFr/ubln/6+vc/+ion//kZGK/35+lP9ra53/WVml/0lJrP8/P7H/Pz+2/1BQuf9ra7z/dna9/2Bgvf9CQrz/SUm5/2xstv+oqLL/4eGs/8LCpf94eJ3/RESV/zU1i/9UVID/Z2d0/1dXaP82Nlv/KChO/0FBQP9sbDP/mJgn/6mpG/+joxD/oaEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/2trBf96eg//i4sa/5mZJv+lpTT/r69C/7m5UP/Cwl7/y8ts/9TUev/b24f/39+T/97env/V1aj/xMSx/6uruf+OjsD/c3PG/1xcyv9LS83/SkrQ/2Ji0f95edH/aGjQ/0hIzv9TU8r/fHzG/8HBwP/n57r/paWy/2Bgqf85OZ//SUmT/2hoh/9eXnr/Oztt/ywsX/9GRlD/dXVC/6OjNP+urif/qKga/5WVD/92dgUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/zY2A/9GRg3/VVUY/2FhJf9razP/dHRC/3x8Uf+EhGD/i4tv/5OTfv+bm4z/pqaZ/7Ozpv/Dw7H/1dW7/+bmxP/w8Mz/6+vT/9LS2P+rq9z/hYXg/2dn4v9SUuP/W1vj/3l54v9tbeD/Tk7d/2Ji2f+SktP/39/N/9bWxf+Cgrz/S0uy/0BApv9lZZr/ZmaN/0FBf/8wMHD/S0th/3x8Uf+rq0L/s7Mz/6enJf+EhBn/X18N/0NDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/g0NAf8aGgr/JCQV/y4uIv82NjH/PDxA/0FBUP9FRWD/SUlw/01NgP9SUo//V1ee/15eq/9nZ7j/cnLD/4GBzf+UlNb/ra3d/83N5P/s7On/+fnt/+Tk7/+1tfH/iIjz/2Vl8/9dXfH/enrw/2xs7f9WVun/dnbk/7S03v/w8Nf/rq7O/2RkxP9BQbn/XV2s/2xsn/9ISJD/MzOB/09Pcf+Dg2H/s7NQ/7i4Qf+fnzH/c3Mj/0xMFv8sLAr6EhIBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8dHQb/GBgR/xgYHv8bGy3/Hh49/yIiTf8lJV7/KSlv/y0tgP8xMZD/NTWg/zo6r/8/P7z/RETJ/0pK1P9RUd3/XFzm/2tr7P9/f/L/mpr3/7+/+v/q6vz//v7+/9/f/v+pqf7/enr+/2Vl/f98fPr/aGj3/2Vl8v+Rke3/4ODm/9zc3v+CgtT/TEzJ/1RUvf9xcbD/UFCh/zc3kf9TU4H/iopw/7u7X/+6uk7/lJQ9/2NjLf86Oh//Hx8S/xkZBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8uLgP/MDAN/y4uGf8tLSj/Li44/zExSf81NVr/Ozts/0FBfv9ISI//UVGg/1lZsP9gYL//ZmbM/2lp2P9ra+P/a2vs/2ho8/9mZvn/Z2f+/3Bw//+EhP//pKT//9DQ///7+///9/f//8TE//+MjP//b2///3x8//9nZ/7/f3/6/7298//09Oz/pqbj/19f2f9PT83/cnK//1dXsf88PKH/WFiQ/5GRf//Dw23/urpb/4eHSf9TUzj/LS0o/xsbGv8lJQ3/NDQDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/R0cH/0VFFP9FRSL/R0cx/0tLQv9RUVT/WFhn/2Fhev9qaoz/cXGe/3V1r/93d7//dXXN/3Jy2v9wcOb/cHDv/3Jy+P92dv7/enr//3t7//95ef//d3f//4GB//+env//yMj///f3////////1NT//5WV//94eP//d3f//3Z2//+kpP7/8PD4/8zM8P92dub/UFDb/3Bwzv9dXb//QECw/11dn/+amo3/ysp6/7W1aP95eVX/RUVD/yUlMv8jIyL/MzMU/0BACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/SUkD/1VVDf9XVxv/WVkq/15eO/9jY03/aGhg/2xsc/9tbYb/aWmZ/2Fhq/9WVrz/T0/M/01N2v9SUuf/XFzx/2ho+v90dP//fn7//4WF//+IiP//iYn//4WF//9+fv//gYH//5+f///Nzf//+/v////////Z2f//lZX//319//94eP//mJj//97e///p6fv/jo7y/1hY5/9ubtv/Y2PN/0VFvf9kZKz/o6Oa/9HRh/+srHT/a2th/zo6Tv8jIzv/MDAq/0FBG/9NTQ7/SUkDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1NTB/9XVxP/W1sh/19fMv9hYUT/YWFX/15ea/9WVn//SkqT/0BApv89Pbj/RUXJ/1dX2P9vb+b/i4vx/6en+/+/v///0ND//9vb///h4f//4uL//9ra///Jyf//rq7//4+P//+AgP//m5v//9TU/////////////87O//+Njf//fHz//5SU///Pz///+Pj//6en+/9jY/L/bGzm/2Zm2f9LS8n/bW25/6+vp//V1ZP/oKCA/11dbP8zM1j/KSlF/z09M/9PTyL/U1MT/09PBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/05OAf9TUwv/VFQZ/1VVKP9TUzr/Tk5N/0dHYf88PHX/MzOK/zQ0nv9DQ7H/XV3D/39/1P+oqOP/0dHv/+/v+v/+/v////////////////////////////////////////n5///g4P//sLD//4eH//+jo///5eX////////29v//sLD//39///+UlP//ycn///z8//+9vf//b2/7/21t8P9oaOP/UVHU/3d3xP+9vbL/1dWf/5GRi/9RUXb/Li5i/zQ0Tv9MTDv/V1cp/1RUGf9SUgz/UFACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9CQgT/QUEQ/0FBHv8/Py//OTlC/zExVf8pKWr/LCx//z4+lP9bW6j/gYG7/7Kyzf/j493//Pzs//b29//i4v//0dH//8rK///MzP//0ND//9TU///e3v//7u7///7+//////////////r6///Kyv//k5P//729///8/P///////9XV//+Li///kpL//8vL///+/v//zc3//3t7//9vb/j/aGjs/1pa3v+EhM7/zc28/87Oqf9/f5X/R0eA/y8va/9DQ1b/V1dC/1lZMP9VVR//Tk4Q/0dHBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/LS0H/y0tFP8rKyP/JSU1/yEhSP8lJV3/NjZy/1JSiP94eJ3/qKix/97exP/4+Nb/5+fm/8HB8/+jo/7/k5P//4+P//+QkP//j4///4qK//+Kiv//kZH//6Gh//++vv//5eX//////////////////8/P//+pqf//6en////////w8P//np7//46O///Ozv///////9jY//+EhP//c3P+/2dn9P9lZeb/lZXX/93dxf+/v7L/bm6e/z4+iP82NnP/UlJe/11dSf9ZWTb/T08k/0BAFP81NQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADvBwcB/xgYCv8ZGRj/GRko/x8fO/8vL0//SEhk/2pqev+VlY//yMil/+/vuf/m5sz/t7fd/4+P7P94ePn/cHD//3Z2//98fP//fX3//4aG//+amv//rq7//7y8///Bwf//v7///8fH///o6P//////////////////zMz//9ra/////////v7//7Gx//+Pj///0tL////////e3v//ior//3d3//9nZ/r/c3Pt/6ur3v/m5s3/qKi6/19fpf85OZD/RUV6/11dZf9fX1D/U1M7/0BAKf8wMBn/JCQL5gsLAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wYGAv8YGA3/ICAc/y4uLf9CQkD/Xl5U/4GBav+srID/29uW/+npq//FxcD/kpLT/29v5P9eXvL/ZWX+/3p6//9+fv//iYn//6ys///V1f//8vL////////////////////////9/f///Pz///////////////////n5///q6v////////////+/v///l5f//9vb////////39///42N//94eP//amr+/4aG8//Hx+T/4uLT/46Owf9SUqz/OzuX/1ZWgf9jY2v/WlpV/0REQP8wMC3/ICAc/xgYDfwKCgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8sLAT/OjoQ/0dHH/9aWjD/dHRE/5WVWf+9vW//39+F/9jYnP+pqbH/eXnF/1pa2P9RUen/amr3/4CA//94eP//kJD//87O///39///////////////////////////////////////////////////////////////////////////////////w8P//6am///q6v///////9nZ//+MjP//dnb//3Z2//+dnff/5OTp/8vL2f93d8b/R0ey/0hInP9kZIb/YmJw/0tLWv8zM0X/ISEx/yAgH/8nJxD/JycEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/V1cF/2NjEv9yciH/iYk0/6enSP/Hx13/2dlz/8XFiv+WlqD/aGi2/0tLyv9KStz/b2/t/39/+v9tbf//j4///9ra//////////////7+///t7f//3Nz//+rq/////////////////////////////////////////////////////////////8PD//+/v////Pz////////IyP//iIj//3d3//+Li///vr77//Hx7f+oqN3/ZGTL/0NDtv9cXKH/aWmK/1VVdP84OF7/JiZI/ykpNP82NiL/RUUS/01NBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/35+Bv+LixP/np4j/7OzNv/Hx0r/zs5g/7W1dv+IiI3/XV2j/0FBuf9GRs3/cHDg/3197/9jY/3/hYX//9XV/////////////+np//+/v///pKT//8jI///39/////////////////////////////////////////////////////////39///Nzf//4uL////////29v//sLD//39///+Fhf//qKj//+Xl/f/d3fD/iIjg/1NTzv9QULr/bGyk/2Bgjv9AQHf/Kyth/zExS/9BQTb/VlYk/2lpFP9wcAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+fnwf/q6sV/7a2Jf/AwDf/wcFM/6enYv9+fnj/VFSP/zo6pv9AQLv/a2vQ/3194v9dXfH/dHT+/8TE/////////Pz//9fX//+hof//jo7//87O///////////////////////////////////////////////////////////////////+/v//6en///7+////////3d3//5OT//+Dg///o6P//87O///4+P7/sbHy/25u4v9NTdD/Z2e8/2pqpv9JSZD/MTF5/zY2Y/9JSU3/ZGQ4/3x8Jf+NjRX/kZEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/paUH/6+vFf+3tyX/s7M4/5qaTf91dWP/Tk56/zQ0kP85Oaf/YmK8/3x80P9cXOP/X1/y/6en///5+f//+vr//9DQ//+amv//g4P//8nJ////////////////////////////////////////////////////////////////////////////////////////+Pj//7Ky//+Dg///o6P//8jI///4+P//2dn//4yM8/9ZWeP/X1/R/3Jyvf9TU6f/NzeR/zw8ev9QUGT/bm5O/4yMOf+goCb/paUV/52dCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/6WlB/+rqxX/oqIl/4uLOP9sbE3/SUlj/y8vev8yMpD/Vlan/3l5vP9kZND/Tk7j/4OD8v/i4v///v7//8vL//+env//fn7//66u///8/P///////////////////////////////////////////////////////////////////////////////////////8jI//+MjP//n5///87O///19f//8vL//6mp//9tbfP/XFzj/3V10f9dXb3/PT2n/0FBkf9WVnr/d3dk/5mZTv+trTn/ra0m/6amFf+cnAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+UlAf/jY0U/3p6Jf9iYjf/Q0NM/yoqYv8rK3j/S0uP/3Fxpv9ubrv/SEjQ/2Bg4v+1tfH////+/9TU//+env//gID//42N///g4P/////////////r6////f3//////////////////////////////////////////////////////////////Pz//87O//+YmP//pqb//9PT///5+f///f3//8HB//+Bgf7/X1/y/3V14v9lZdD/RES8/0hIpv9dXZD/f395/6WlYv+3t03/tLQ4/6ysJf+lpRX/mJgHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/bm4G/2dnE/9UVCP/Ojo2/yUlSv8lJWD/QEB2/2Rkjf90dKP/UVG5/0ZGzf+BgeD/5ubv//Dw/f+kpP//gID//319//+rq///+/v////////p6f//x8f///7+////////////////////////////////////////////////////////9/f//83N//+urv//v7///+Tk///+/v///////9DQ//+QkP//aGj9/3Z28P9paeD/SkrO/1BQuv9lZaT/iYmO/7Gxd//BwWH/urpL/62tNv+bmyT/iooU/3x8BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/0lJBf9CQhL/Ly8h/x8fM/8gIEf/ODhd/1dXc/9xcYr/YGCg/z4+tv9WVsr/qqrc//397f/Dw/r/g4P//3Z2//+EhP//x8f/////////////v7///76+/////////////////////////////////////////////////////////////+zs///c3P//6Oj///r6/////////v7//9bW//+YmP//cnL//3l5+v9pae3/UFDd/1payv9wcLb/lpah/76+iv/KynT/vLxe/6OjSP+IiDT/cnIi/2NjEv9XVwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8jIwT/Hx8Q/xcXH/8dHTD/MjJE/0xMWf9oaG//amqF/0lJnP87O7H/b2/G/9PT2P/w8On/m5v3/25u//95ef//h4f//9nZ////////8fH//6Ki///Bwf//////////////////////////////////////////////////////////////////////////////////8vL//8zM//+YmP//enr//3l5//9nZ/f/Wlrp/2ho2f9/f8b/p6ey/83NnP/Pz4b/tbVw/5OTWv90dEX/W1sx/0lJH/89PRD/MDAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/CAgC/xMTDf8dHRz/Ly8t/0REQP9dXVT/a2tq/1lZgP83N5b/RUWr/4yMwP/t7dP/0dHk/35+8v9lZf7/fHz//4aG///g4P///////+Hh//+Skv//vb3/////////////7Oz///j4///////////////////9/f//+/v///////////////////v7///t7f//0tL//6+v//+Pj///fHz//3V1//9oaP7/a2vy/3t75P+UlNP/v7/A/9vbrP/NzZb/p6eA/4CAav9gYFX/R0dA/zQ0Lf8nJxz/Hx8N/xERAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AkJAf8jIwr/Ly8Y/0BAKP9VVTv/ZWVP/2JiZP9GRnr/MDCP/1VVpf+qqrn/8/PM/7Cw3v9qau3/ZWX5/3p6//+Dg///3t7////////W1v//i4v//66u/////////////93d///Jyf//////////////////6+v//8rK///Bwf//wcH//7y8//+wsP//nZ3//4uL//+AgP//e3v//3h4//95ef//hIT5/5WV7f+0tN7/29vM/+Dguf++vqX/kZGQ/2trev9OTmT/ODhP/ygoO/8gICn/HBwY/xwcCuUJCQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/MDAH/z8/FP9QUCP/Xl41/2NjSP9TU13/NTVy/zIyiP9oaJ3/xMSx/+rqxP+Wltb/W1vm/2ho8/93d/7/fHz//9jY////////09P//4uL//+amv//8fH////////t7f//qKj//83N///////////////////m5v//wcH//6en//+YmP//kJD//4+P//+Tk///lZX//5aW//+cnP//p6f+/76+8//e3ub/6+vW/9HRxf+iorH/eXmd/1lZiP9AQHP/Ly9d/ycnSf8oKDb/LCwk/y4uFP8qKgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/z09BP9PTxD/WVke/15eL/9aWkH/Q0NV/yoqav87O3//fHyU/9bWqP/Z2bv/goLN/1FR3v9qauz/c3P4/3Jy///Nzf///////8/P//+QkP//h4f//9TU/////////v7//8DA//+Skv//yMj///j4//////////////39///t7f//3d3//9TU///R0f//zs7//8vL///R0f//3t7//+7u+P/w8Oz/1tbe/6qqzf+Cgrz/Y2Oo/0lJlP83N3//Ly9q/zMzVv86OkL/QEAv/0JCH/9CQhD/NjYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9ISAH/VlYL/1lZGP9bWyj/TEw6/zQ0Tf8lJWH/SEh1/5CQiv/f357/xsax/3Nzw/9JSdT/amrj/3Fx7/9nZ/r/urr////////Ozv//kpL//35+//+srP//9vb////////p6f//paX//4eH//+vr///3Nz///b2//////////////////////////////39///6+v//9fX//+Xl+v/IyO//o6Pj/4KC1P9nZ8P/T0+x/z4+nv84OIr/PT12/0dHYf9PT03/UlI6/1RUKf9TUxn/UVEL/kVFAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1dXBv9XVxP/UFAh/z4+Mv8nJ0T/KChX/1hYa/+hoX//4eGT/7a2pv9oaLj/Q0PJ/2ho2P9xceb/XFzx/6Ki+//9/f//1dX//5KS//97e///i4v//8zM/////////////9fX//+env//gYH//5CQ//+trf//x8f//9fX///d3f//3Nz//9XV///Kyv//urr//6Wl+/+OjvH/d3fm/2Ji2P9QUMn/RES4/0NDpv9LS5P/VVV//1tba/9eXlj/XV1E/1paMv9XVyL/VFQT/1NTBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9VVQL/T08N/0FBGv8vLyr/HR07/zExTf9nZ2D/rq5z/93dhv+oqJn/X1+r/z4+vP9kZMz/c3Pa/1NT5/+Hh/H/7Oz6/+Xl//+YmP//dnb//319//+Tk///19f////////8/P//0dH//6Ki//+Cgv//fn7//4eH//+MjP//jIz//4mJ//+EhP//e3v//3Bw+v9lZfH/Wlrn/1NT2v9RUcz/Vla9/15erP9lZZr/Z2eH/2ZmdP9iYmD/XV1N/1lZO/9WVir/VFQb/1JSDf9VVQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/z8/B/8zMxP/ISEh/xwcMf8+PkL/d3dU/7m5Z//W1nn/np6M/1lZnv86Oq//Xl6//3V1zf9OTtr/bW3m/8vL7//39/j/qKj+/3R0//93d///eXn//5KS///Q0P///f3///n5///MzP//o6P//4SE//92dv//dnb//3h4//94eP//dXX+/3Fx+P9vb+//bm7m/25u2v9wcM3/cXG//29vr/9qap7/ZGSM/1xcev9VVWf/T09V/0pKQ/9GRjL/REQi/0REFP9HRwgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8qKgL/JCQM/xYWGf8kJCj/T083/4WFSP/AwFr/zc1s/5WVfv9TU4//NTWg/1dXsP93d7//UFDM/1dX2P+iouP/+fns/8TE8/9+fvn/ZWX+/319//9wcP//iYn//7+////z8////Pz//9PT//+pqf//i4v//3V1//9paf7/ZWX5/2Vl8/9mZuz/Zmbj/2Rk2P9gYMz/W1u//1RUsP9NTaD/RkaQ/z8/fv86Omz/NDRa/zAwSf8uLjj/LS0o/y4uGf8wMA3/NDQDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xYWBv8WFhH/MzMe/2BgLf+Tkzz/w8NN/8TEXv+NjW//T0+A/zExkP9PT6D/dXWv/1hYvP9GRsn/e3vU/9vb3f/o6Ob/lJTt/2Rk8v9mZvf/fX36/2dn/P95ef7/paX+/9nZ/v/7+/7/7Oz8/8PD+v+goPf/h4fy/3R07P9lZeb/Wlrd/1JS1P9LS8n/Rka9/0FBr/88PKD/ODiR/zQ0gP8xMW//LS1e/ykpTf8kJD3/ICAt/xwcHv8aGhL/HR0GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8SEgH/JSUK/0hIFf9wcCL/oKAw/8LCQP+7u1D/hYVg/0tLcP8tLYD/R0eP/29vnv9iYqv/Pj64/1xcw/+qqs3/9PTW/7u73f93d+T/VVXp/2pq7f97e+//YGDx/2Zm8v+GhvL/rq7x/9zc7//09O3/6urp/87O5P+wsN3/mZnW/4eHzf96esP/cHC4/2dnrP9gYJ7/W1uP/1ZWgP9RUXD/TU1g/0hIUP9DQ0D/PDwx/zQ0Iv8qKhX/Hh4K+w8PAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/0BAA/9cXAz/goIY/6urJf++vjP/sbFB/35+UP9HR2D/KSlv/0BAfv9oaIz/aWmZ/0FBpv9ERLH/fX27/9XVxP/l5cz/l5fT/2Nj2P9MTNz/amrg/3p64v9eXuP/VFTj/2ho4v+Cgt//pKTc/8nJ2P/j49P/6enM/+HhxP/S0rv/wsKx/7S0pv+oqJn/np6M/5WVfv+Ojm//h4dg/39/Uf94eEL/b28z/2RkJf9YWBj/SUkN/zs7AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/dnYF/5WVD/+wsBr/t7cm/6enM/92dkH/QkJQ/yUlXv85OWz/X195/2xshv9MTJP/NTWe/1lZqP+hobH/6em5/8jIwP9/f8X/VFTK/0ZGzf9lZc//eXnQ/2Rk0P9MTM//Tk7N/15eyv9ycsX/iYnA/6Ojuf+7u7H/zMyo/9TUnv/W1pP/0tKH/8zMev/FxWz/vb1e/7a2UP+urkL/paU0/5qaJv+NjRr/fHwP/21tBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+npwb/rKwQ/7GxGv+bmyb/bW0z/z09QP8iIk3/NDRa/1dXZ/9ra3P/V1d//zQ0iv8+PpT/dHSd/8DApf/m5qv/ra2x/3Bwtv9KSrn/QEC7/1xcvP90dLz/bGy7/1NTuf9CQrb/QkKx/01Nq/9aWqX/amqd/3t7lP+MjIr/nJx//6mpc/+zs2f/uLha/7u7Tf+5uUD/tbUz/7CwJv+rqxv/paUQ/52dBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9oqIB/6mpBv+pqRD/jIwa/2JiJf83NzD/Hh48/zAwSP9QUFT/Z2dg/15ea/89PXX/Li5//1BQiP+Ojo//0tKW/9fXnP+amqD/ZGSj/0NDpv86Oqf/UlKn/2trpv9wcKP/YGCg/0tLnP87O5b/NjaP/zo6iP9CQn//TU11/1lZa/9nZ2D/dnZU/4SESf+RkT3/nZ0x/6enJf+qqhr/pqYQ/6OjB/+qqgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+iogH/n58G/3p6Dv9WVhj/Li4i/xoaLf8tLTf/SkpC/2JiTf9hYVf/SEhh/ysrav82NnL/ZmZ6/6WlgP/X14X/xsaK/42Njf9cXI//PT2Q/zMzkP9HR4//X1+N/2xsiv9oaIX/WVmA/0dHev84OHL/Li5q/ywsYf8vL1f/NzdN/0FBQv9PTzj/X18t/3BwIv+BgRj/k5MP/6OjBvebmwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9sbAX/R0cM/yQkFf8XFx7/LCwo/0ZGMf9dXTv/YWFE/1BQTf8yMlX/JiZd/0ZGZP98fGr/trZv/9PTc/+4uHb/g4N4/1ZWev84OHr/LS14/z4+dv9TU3P/Y2Nv/2Zmav9gYGT/VFRd/0VFVf82Nk3/KipE/yIiO/8iIjH/KSko/zY2Hv9ISBX/XFwN/3JyBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/zg4A/8YGAr/FxcR/y0tGf9ERCH/WVkq/15eMv9UVDr/OjpB/yIiSP8uLk//WlpU/5CQWf+/v13/yspg/6urYv96emP/UFBj/zMzYv8oKGD/NjZd/0lJWf9ZWVT/YWFP/19fSP9ZWUL/Tk46/z8/Mv8xMSr/JCQi/xoaGf8bGxH/JycK/0FBAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/EREB/xwcBv8vLwz/REQT/1ZWGv9bWyH/VlYo/0BAL/8nJzX/ICA7/0BAQP9wcET/oaFH/8HBSv+/v0z/np5N/3FxTf9JSUz/LS1K/yIiSP8wMET/QkJA/1JSO/9bWzX/W1sv/1lZKP9RUSL/Q0Mb/zU1E/8mJg3/GRkG/xMTAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/LS0C/0ZGB/9VVQ3/V1cT/1VVGP9CQh7/LCwj/xoaKP8sLC3/VlYw/4SENP+urjb/u7s3/7OzOP+Pjzj/ZmY3/0FBNv8lJTT/Hh4w/y4uLf8+Pij/Tk4k/1dXHv9WVhn/VVUT/1BQDf9AQAf/LS0DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/VFQC/1dXBv9TUwv/QkIQ/y4uFP8ZGRj/Hx8c/0REH/9ubiH/mZkj/7KyJf+zsyX/paUl/39/Jf9YWCP/NDQh/xwcH/8dHRz/Li4Y/z4+FP9OThD/VFQL/1NTB/9HRwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8R0cB/zk5BP8pKQf/GRkK/xcXDf83NxD/X18S/4eHE/+oqBT/rKwV/6urFf+QkBT/a2sT/0ZGEv8kJBD/FRUN/yEhCv8zMwf/RUUE/1FRAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6wkJAf8JCQL/KioD/1JSBf94eAb/mpoH/6KiB/+jowf/lZUH/3JyBv9NTQX/JiYE/AcHAugJCQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////////////////////////////////gAH///////wAAD//////8AAAD//////AAAAD/////wAAAAD////+AAAAAH////wAAAAAP///8AAAAAAP///gAAAAAAf//+AAAAAAB///wAAAAAAD//+AAAAAAAH//wAAAAAAAP//AAAAAAAA//4AAAAAAAB//gAAAAAAAH/8AAAAAAAAP/wAAAAAAAA/+AAAAAAAAB/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/gAAAAAAAAf+AAAAAAAAB/4AAAAAAAAH/wAAAAAAAA//AAAAAAAAD/+AAAAAAAAf/4AAAAAAAB//wAAAAAAAP//AAAAAAAA//+AAAAAAAH//8AAAAAAA///4AAAAAAH///gAAAAAAf///AAAAAAD////AAAAAA////+AAAAAH////8AAAAA/////8AAAAP/////8AAAD//////8AAA///////+AAf//////////////////////////////////////////////KAAAADAAAABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAD/R0cJ/0ZGG/9CQjH/OTlK/y4uZf8uLoH/Q0Od/29vt/+urs//5OTk//X19f/r6///3d3//9ra///c3P//4uL///Ly//////////////Ly//+xsf//paX///f3///+/v//qKj//4uL///Y2P//7+///4eH//9ubvb/XFzl/3p60P/R0bj/ra2e/1BQgv8wMGb/TExL/1paMv9VVRv/Tk4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANgWFgH/KSkO/yYmIv8iIjn/JCRU/zg4cP9iYo3/oaGp/+Dgw//u7tv/yMjv/56e/v+MjP//ior//4mJ//+IiP//kZH//6Gh///AwP//7e3////////9/f//vLz//9jY////////zMz//42N///b2///+Pj//5WV//9xcf//YmLv/5GR3P/d3cT/lJSq/0JCjv89PXH/W1tV/1tbOv9LSyL/ODgOzxkZAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPkGBgP/FhYS/x4eKP8wMEH/UlJd/4WFev/FxZf/6uqz/8jIzf+Li+T/amr3/29v//94eP//hYX//6am///Kyv//4eH//+fn///i4v//5+f///7+////////9/f//9/f////////4uL//5aW///g4P///Pz//5yc//9ycv//bW34/7Gx5f/b287/d3e0/zw8mP9RUXr/YmJd/1BQQv81NSj/ICAT9Q0NAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8jIwX/ODgW/05OLf9xcUf/oaFk/9XVgf/a2p//oKC7/2Vl1f9WVuv/cHD9/3l5//+Vlf//2Nj/////////////////////////////////////////////////////////////6Oj//6am///w8P//+fn//5ub//9ycv//g4P9/9XV7P/Dw9b/XV28/0REoP9jY4L/W1tk/zk5SP8iIi3/ICAX/x8fBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9ZWQf/cXEa/46OMf+0tEz/1dVp/8TEh/+FhaX/UFDB/05O2/91dfH/cnL//5KS///q6v////////n5///a2v//39//////////////////////////////////////////////5OT//8LC////////6Oj//4+P//96ev//qan//+rq8f+Zmdv/TEzC/1lZpv9mZoj/Q0Nq/ygoTP8uLjL/Q0Ma/0xMBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+Pjwn/pqYc/7y8NP/Kyk//r69t/3R0i/9DQ6n/RkbF/3V13/9qavT/f3///+Xl////////3d3//6Sk//+5uf//9PT/////////////////////////////////////////////6ur//+zs////////x8f//4GB//+Xl///3Nz//9fX9P9wcN//UFDG/2xsqv9QUIz/Ly9t/zg4UP9VVTT/c3Mc/39/CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+mpgn/trYd/7m5Nf+dnVH/aWlv/zs7jf88PKv/b2/H/2lp4f9lZfb/zMz////////S0v//jo7//66u///+/v/////////////////////////////////////////////////////////////y8v//mJj//5SU///Ozv//9PT//6Gh9v9ZWeH/aWnI/15erP84OI7/QEBv/2NjUf+Kijb/oaEd/5ycCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+jown/pKQd/4qKNf9fX1H/NDRv/zIyjf9jY6v/b2/H/1BQ4f+dnfb//v7//9DQ//+Pj///k5P///X1//////////////////////////////////////////////////////////////v7//+ysv//lJT//8/P///9/f//ysr//3Bw9v9mZuH/aWnI/0FBrP9HR47/bW1v/5ycUf+xsTb/rKwd/52dCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9/fwj/cnIc/1JSNP8uLk//KSlt/1NTi/9zc6n/Tk7F/2Vl3//f3/T/4+P//5KS//99ff//x8f////////v7///6en/////////////////////////////////////////////9PT//76+//+pqf//2tr////////h4f//iYn//2pq9P9tbd//SkrG/1BQqv94eIz/q6tt/729UP+ysjT/oaEc/4yMCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP9KSgf/Pj4a/yQkMf8iIkz/Q0Np/21th/9dXaX/QkLB/5eX2//29vH/qKj//3Z2//+MjP//6ur////////Bwf//4uL/////////////////////////////////////////////4eH//9nZ///19f///////+fn//+YmP//c3P//25u8f9TU9v/XV3C/4iIpv+8vIj/x8dq/62tTP+MjDH/cXEa/1paBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8XFwX/GBgW/x0dLf85OUf/Xl5k/2pqgf9BQZ//UVG7/8nJ1f/e3uv/e3v9/3Jy//+YmP//+vr///X1//+hof//6Oj////////////////////////////////////////////////////////8/P//1tb//5qa//94eP//bW39/19f7P9xcdb/n5+8/87On//IyIL/nZ1k/3FxSP9RUS3/PDwX/yUlBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPMBAQP/Hx8S/zQ0KP9SUkH/aGhd/1RUev80NJf/cHCz/+bmzf+1teT/ZWX3/3R0//+Xl////v7//+Xl//+Skv//4uL////////h4f//9vb/////////////6en//+Hh///l5f//39///8nJ//+np///iIj//3h4//9ycv//dXX3/5KS5f/Cws7/3Ny0/7u7l/+EhHr/V1dd/zg4Qf8lJSj/HBwT7AQEAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgnJwH/NTUN/0tLIv9gYDn/YGBU/zw8cP83N43/k5Op/+npw/+QkNv/XFzv/3R0/v+Pj///+vr//9/f//+Kiv//ysr////////b2///urr///z8////////7u7//8PD//+mpv//lpb//46O//+Njf//jo7//5OT//+kpP7/xsbv/+Li2//T08T/nJyp/2dnjf9CQnH/LCxU/yYmOv8oKCL/KCgO4CoqAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/R0cI/1lZG/9fXzH/Tk5K/ysrZf9GRoH/sLCd/9zct/91dc//WFjk/3Jy9v+AgP//8fH//93d//+Kiv//paX///7+///5+f//p6f//7Cw///x8f/////////////x8f//4uL//93d///a2v//3Nz//+bm///r6/b/2dnk/6ioz/90dLj/TU2d/zc3gv8zM2b/OztL/0NDMf9GRhv/Pj4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/Tk4E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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////////////////////////wAP///////4AAH//////+AAAH//////wAAAP/////+AAAAf/////gAAAAf////8AAAAA/////wAAAAD////+AAAAAH////wAAAAAP///+AAAAAAf///4AAAAAB////AAAAAAD///8AAAAAAP///wAAAAAA///+AAAAAAB///4AAAAAAH///gAAAAAAf//+AAAAAAB///4AAAAAAH///gAAAAAAf//+AAAAAAB///4AAAAAAH///gAAAAAAf//+AAAAAAB///4AAAAAAH///gAAAAAAf///AAAAAAD///8AAAAAAP///wAAAAAA////gAAAAAH///+AAAAAAf///8AAAAAD////4AAAAAf////wAAAAD/////AAAAAP////+AAAAB/////+AAAAf/////8AAAD//////4AAAf//////4AAH///////8AD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//39////////ubn///Pz//+8vP//b2///7q68v+xsdD/R0en/2Bgev9JSU7/IyMn/xsbCQAAAAAAAAAAAAAAAAAAAAD/amoM/5eXLP/Hx1X/vr6C/2hosP9TU9n/bW35/5yc///8/P//7Oz//9bW///8/P/////////////////////////////Y2P///////6Oj//+Njf//3t76/39/2f9XV7D/WFiD/y0tVv88PC3/VVUMAAAAAAAAAAAAAAAAAAAAAP+oqA3/v78v/6OjWf9TU4f/Rka0/2tr3f99ffz/8/P//9TU//+lpf//8PD////////////////////////////////////////h4f//kJD//9XV///Hx/3/YGDe/2Jitf87O4f/Tk5Z/4aGMP+ZmQ4AAAAAAAAAAAAAAAAAAAAA/52dDf+Ghi//RkZZ/zU1h/9pabT/Wlrd/8nJ/P/Y2P//i4v//+Li////////////////////////////////////////8PD//6mp///T0///7u7//4OD/f9mZt7/Skq1/1xch/+goFn/trYv/6KiDgAAAAAAAAAAAAAAAAAAAAD/U1MM/zU1LP8nJ1X/XV2C/1hYsP94eNn/5ub5/4mJ//+hof///////9jY//////////////////////////////39///Y2P//6+v///r6//+goP//a2v6/1dX2f9wcLD/traD/729Vf+Tkyz/a2sMAAAAAAAAAAAAAAAAAAAAAPsMDAj/Hx8m/0pKTf9mZnn/QECm/7Oz0P+9vfL/a2v//7q6///39///t7f////////9/f/////////////9/f////////v7///f3///nZ3//29v//9ra/L/lZXQ/8zMp/+xsXr/bW1O/z8/J/caGgkAAAAAAAAAAAAAAAAAAAAA7x8fBP9AQB7/Y2ND/0pKbf9KSpn/09PC/4yM5v9nZ///uLj//+zs//+hof///////9jY///x8f///////9jY//+4uP//pKT//5CQ//+MjP//oaH//8nJ5v/OzsL/jY2Z/0lJbf8rK0P/JCQe6R0dBAAAAAAAAAAAAAAAAAAAAADdV1cB/1ZWFP9aWjX/Ly9d/2lph//V1bD/bGzU/2Rk8v+kpP//6en//42N///h4f//7u7//6en///e3v///v7/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/6SkYP9RUZ7/WlrY/5OT///v7///vr7//+3t/////////////////////////////9LS//+pqf//z8///2lp2f9QUJ//Q0Nh/3x8Kf+xsQQAAAAAAAAAAP+zswT/e3so/zs7YP9SUp7/ZmbY/9LS//+np///0tL/////////////////////////////7u7//8DA///r6///lZX//1lZ2f9ZWZ//oaFh/6+vKf+3twQAAAAAAAAAAPNnZwL/Hx8j/0FBWf9ZWZb/h4fQ/8HB/P+IiP//8PD//9nZ////////////////////////9PT///Dw//+oqP//Zmb8/3p60P+5uZf/paVa/15eJOlrawMAAAAAAAAAAAAAAAD/LS0a/19fTP9CQob/sbG//46O7/+Kiv//5eX//8DA///39///7u7////////Y2P//wMD//5+f//+Rkf//srLv/8HBwP9/f4f/OjpM/x0dGgAAAAAAAAAAAAAAAAAAAAD8SEgO/01NOf9DQ2//wMCn/2xs2P97e/z/2tr//52d///y8v//wMD//9DQ///s7P//4uL//9jY///Hx/z/mJjY/1pap/9DQ3D/S0s6/0BADgAAAAAAAAAAAAAAAAAAAADgSEgE/ygoI/9dXVP/vb2G/1lZt/9kZOD/yMj8/5GR//+np///7e3//6qq//+Kiv//i4v//319/P9lZeD/V1e3/1ZWh/9TU1P/UFAkyjY2BAAAAAAAAAAAAAAAAAAAAAAAAAAA/xMTDv+EhDP/sbFg/0lJjv9YWLf/lpbY/7S07/9kZPv/k5P//9DQ//+/v/v/kJDv/3Bw2P9dXbf/Tk6O/z4+Yf8uLjT8FBQOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiRcXAf+mphX/nZ05/zk5YP9ZWYb/VFSn/8XFv/95edD/WVnY/2ho2P+Hh9D/ra2//7u7p/+4uIb/ra1h/5ubOv95eRbFVVUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOypqQL/fX0V/ykpM/9VVVP/QUFv/3p6hv/AwJb/V1ee/09Pnv9YWJb/RkaG/0dHb/9eXlP/goI0/5ubFs+MjAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKFRUB+hMTDv9PTyP/TU05/zMzTP+mpln/pqZg/0FBYf8/P1n/XFxM/01NOf8pKSP4FBQOyV9fAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANpNTQT4Pj4O/xgYGv9YWCP/tLQo/4CAKP8jIyP/KCga+0pKDu5fXwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOxpaQL/t7cE/7S0BPRycgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Qf/D/0H+AH9B+AAfQfAAD0HgAAdB4AAHQcAAA0HAAANBwAADQYAAAUGAAAFBgAABQYAAAUHAAANBwAADQcAAA0HgAAdB4AAHQfAAD0H4AB9B/gB/Qf/D/0H///9BKAAAABAAAAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANpXVwf5MjIU/3BwHf+dnR36NDQU4VpaCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3IaGA/8yMh3/Pz9B/0xMYP9GRnH/nZ1x/2xsYP9HR0L7KiodzGpqAwAAAAAAAAAAAAAAAAAAAAAAAAAAwVxcA/+UlCf/pKRg/5mZlP+Li7r/bm7N/2Njzf+oqLr/W1uV/0dHYP+QkCjDcnIEAAAAAAAAAAAAAAAAAAAAAP8pKR3/RUVg/1VVpv9wcN//lpb8/7y8//+fn///mZn8/5GR3/9XV6f/oqJg/zY2HQAAAAAAAAAAAAAAAOhaWgf/SEhB/1tblP+Skt7/tbX//8bG///IyP//39///7m5//+1tf//cXHf/5iYlf8/P0LqYmIHAAAAAAAAAAD/MTEU/2xsYP+pqbn/mZn+/7i4///f3/////////v7///j4///yMj//5aW/v+Li7r/S0tg/jMzFAAAAAAAAAAA/5+fHf+ennD/YWHN/5+f///f3////Pz//////////////////8jI//+8vP//bm7N/0dHcf9xcR0AAAAAAAAAAP9vbx3/RERw/25uzf+9vf//x8f///////////////////z8///e3v//n5///2Rkzf+cnHH/m5sdAAAAAAAAAAD/MDAU/01NX/+Kirr/lpb+/8jI///j4///+/v////////f3///ubn//5qa/v+np7r/bW1g/jU1FAAAAAAAAAAA41xcB/8+PkH/mZmU/3Fx3/+1tf//ubn//9/f///IyP//xsb//7W1//+Rkd//XFyU/0dHQeFVVQcAAAAAAAAAAAAAAAD/MTEc/6WlX/9WVqb/kZHf/5qa/P+env//vb3//5eX/P9zc9//WFim/0dHYP8sLB0AAAAAAAAAAAAAAAAAAAAAsFpaA/+goCf/RUVf/1palP+oqLr/ZGTN/21tzf+Kirr/l5eU/6KiYP+UlCfZeHgDAAAAAAAAAAAAAAAAAAAAAAAAAACwTEwD/igoHP9JSUH/aWlf/5+fcP9GRnD/TExg/0BAQfoxMR3HdnYDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADeV1cH+DExFP+cnB3/dHQd+TExFOBhYQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//6xB+B+sQeAHrEHAA6xBwAOsQYABrEGAAaxBgAGsQYABrEGAAaxBgAGsQcADrEHAA6xB4AesQfgfrEH//6xBCw==AAEAAAD/////AQAAAAAAAAAMAgAAAFFTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0yLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABNTeXN0ZW0uRHJhd2luZy5JY29uAgAAAAhJY29uRGF0YQhJY29uU2l6ZQcEAhNTeXN0ZW0uRHJhd2luZy5TaXplAgAAAAIAAAAJAwAAAAX8////E1N5c3RlbS5EcmF3aW5nLlNpemUCAAAABXdpZHRoBmhlaWdodAAACAgCAAAAAAAAAAAAAAAPAwAAAJYnAAACAAABAAYAICAQAAAAAADoAgAAZgAAABAQEAAAAAAAKAEAAE4DAAAgIAAAAQAIAKgIAAB2BAAAEBAAAAEACABoBQAAHg0AACAgAAABACAAqBAAAIYSAAAQEAAAAQAgAGgEAAAuIwAAKAAAACAAAABAAAAAAQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP///wAiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIoiIiIiIiIiIiIiIiIiIiIiCIigiIiIozMzMzMzMyCIogiIoIiIiKM7m5ubm5sgiKIIiKCIiIijObm5ubm7IIiiCIigiIiIozubm5ubmyCIogiIoIiIiKM5ubm5ubsgiKIIiKCIiIijO5ubm5ubIIiiIiIiIiIiIzm5ubm5uyCIogRERERERGM7u7u7u7sgiKIHZWVlZWRjMzMzMzMzIIiiB1ZWVlZUYiIiIiIiIiIiIgdlZWVlZGDMzMzMzMzMzOIHVlZWVlRg/uLi4uLi4uDiB2VlZWVkYP7uLi4uLi4s4gdWVlZWVGD+4uLi4uLi4OIHZWVlZWRg/u4uLi4uLiziB1ZWVlZUYP7i4uLi4uLg4gdlZWVlZGD+7i4uLi4uLOIHVlZWVlRg/uLi4uLi4uDiB3d3d3d0YP7uLi4uLi4s4gRERERERGD+4uLi4uLi4OIiIiIiIiIg/u4uLi4uLiziCIiIiIiIoP7i4uLi4uLg4giIiIiIiKD+7i4uLi4uLOIIiIiIiIig/uLi4uLi4uDiCIiIiIiIoP7u7u7u7u7s4giIiIiIiKD//////////OIIiIiIiIigzMzMzMzMzMziIiIiIiIiIiIiIiIiIiIiIIiIiIiIiIiIiIiIiIiIiIv//////////AAAAAHv4AA57+AAOe/gADnv4AA57+AAOe/gADgAAAA4AAAAOAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAB/+AAAf/gAAH/4AAB/+AAAf/gAAAAAAAD/////KAAAABAAAAAgAAAAAQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP///wAiIiIiIiIiIoiIiIiIiIiIgigijMzMyCiCKCKM5mbIKIiIiIzu7sgogRERjMzMyCiB2ZGIiIiIiIHZkYMzMzM4gdmRg/u7uziB3dGD+7u7OIEREYP7u7s4iIiIg/u7uziCIiKD+7u7OIIiIoP///84giIigzMzMziIiIiIiIiIiP//KCIAACjObALm5mwCIigAAoiIAAKIzgAAbm4AACIoAAAREQAAGM4AAO7uAAAiKHwAWVl8ABjMfADMzAAAIigoAAAAIAAAAEAAAAABAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADA3MAA8MqmAKo/KgD/PyoAAF8qAFVfKgCqXyoA/18qAAB/KgBVfyoAqn8qAP9/KgAAnyoAVZ8qAKqfKgD/nyoAAL8qAFW/KgCqvyoA/78qAADfKgBV3yoAqt8qAP/fKgAA/yoAVf8qAKr/KgD//yoAAABVAFUAVQCqAFUA/wBVAAAfVQBVH1UAqh9VAP8fVQAAP1UAVT9VAKo/VQD/P1UAAF9VAFVfVQCqX1UA/19VAAB/VQBVf1UAqn9VAP9/VQAAn1UAVZ9VAKqfVQD/n1UAAL9VAFW/VQCqv1UA/79VAADfVQBV31UAqt9VAP/fVQAA/1UAVf9VAKr/VQD//1UAAAB/AFUAfwCqAH8A/wB/AAAffwBVH38Aqh9/AP8ffwAAP38AVT9/AKo/fwD/P38AAF9/AFVffwCqX38A/19/AAB/fwBVf38Aqn9/AP9/fwAAn38AVZ9/AKqffwD/n38AAL9/AFW/fwCqv38A/79/AADffwBV338Aqt9/AP/ffwAA/38AVf9/AKr/fwD//38AAACqAFUAqgCqAKoA/wCqAAAfqgBVH6oAqh+qAP8fqgAAP6oAVT+qAKo/qgD/P6oAAF+qAFVfqgCqX6oA/1+qAAB/qgBVf6oAqn+qAP9/qgAAn6oAVZ+qAKqfqgD/n6oAAL+qAFW/qgCqv6oA/7+qAADfqgBV36oAqt+qAP/fqgAA/6oAVf+qAKr/qgD//6oAAADUAFUA1ACqANQA/wDUAAAf1ABVH9QAqh/UAP8f1AAAP9QAVT/UAKo/1AD/P9QAAF/UAFVf1ACqX9QA/1/UAAB/1ABVf9QAqn/UAP9/1AAAn9QAVZ/UAKqf1AD/n9QAAL/UAFW/1ACqv9QA/7/UAADf1ABV39QAqt/UAP/f1AAA/9QAVf/UAKr/1AD//9QAVQD/AKoA/wAAH/8AVR//AKof/wD/H/8AAD//AFU//wCqP/8A/z//AABf/wBVX/8Aql//AP9f/wAAf/8AVX//AKp//wD/f/8AAJ//AFWf/wCqn/8A/5//AAC//wBVv/8Aqr//AP+//wAA3/8AVd//AKrf/wD/3/8AVf//AKr//wD/zMwA/8z/AP//MwD//2YA//+ZAP//zAAAfwAAVX8AAKp/AAD/fwAAAJ8AAFWfAACqnwAA/58AAAC/AABVvwAAqr8AAP+/AAAA3wAAVd8AAKrfAAD/3wAAVf8AAKr/AAAAACoAVQAqAKoAKgD/ACoAAB8qAFUfKgCqHyoA/x8qAAA/KgBVPyoA8Pv/AKSgoACAgIAAAAD/AAD/AAAA//8A/wAAAAAAAAD//wAA////AP39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39qoYIqoYIhqoIqgiqCaoIqgiqhqqGhoYIhoYIqv39/f0I/f39/ar9/f39/YY2Ng4yDg4ODgoOCgoKCgqG/f39/Yb9/f39CP39/f39qjY7Ozs3Nzc3NjMSMjIOCqr9/f39qv39/f2G/f39/f0IN19fOzs3Nzc3NjcODg4KCP39/f0I/f39/ar9/f39/ao6X19fXzs7Ozc3NzY3NgqG/f39/Yb9/f39CP39/f39hl9jY19jX187Ozs7Nzc3Dqr9/f39qv39/f2G/f39/f0IOodjh19jX19fXztfOzcOCP39/f0ICAmqCAiqCKoICapfCYdjh2ODY19fXzs7Ow6q/f39/QhITEwoSCUoKSQoqmMJCYcJCWNjY2NfY19fNgj9/f39qkyZmZmYmJRwlCmqX19fXl9fX186WzY3Njc2gv39/f0JcJ2dmZmZlJmUJAmqCaoJhggIqggICKoIqggI/f39/YZwnp2dnZmZmJVMqnx8fHx8fFR8VHhUVFRUVKr9/f39CHChoZ2dnZ2ZmUwJfKSkxqSkxqSkpKSkpKBUCP39/f2qcKLDoqGdnZ2ZTKp8ysakxqSkxqSkxqSkpFSq/f39/QiUpqbDoqHEnZ1Mq3ykqMakyqSkxqSkpKSkVAj9/f39hpTIyKbHoqGhoXAIfMrLpMqkxqSkxqTGpKRUqv39/f0IlMymyKbIpcShcAh8y6jKpMqkxsqkpKSkxlQI/f39/aqUzMzMyKbIpqJwqnzLy8qpxsqkpMakxqSkeAj9/f39CJSUlJSUlJSUlJQJgMupy8qpysqkyqSkxqRUqv39/f2GCKoIqgiqCKoIhgigrcvPqcuoy8qkxsqkxnyG/f39/ar9/f39/f39/f39qnzPz6nLy8uoyqnKpKTKVAj9/f39CP39/f39/f39/f0IfNDPz8+py8upyqjGyqR8hv39/f2G/f39/f39/f39/Qik0K7P0M+ty8vLy6jKpXyq/f39/ar9/f39/f39/f39CHzQ09Ctz8/Pqcupy6jKeAj9/f39CP39/f39/f39/f2qoNPQ0NPQ0M/Qz8vLy6l8CP39/f2G/f39/f39/f39/QmkfKR8oHx8fHx8fHx8fHyG/f39/aoIqgiqCKoIqgiqCKoIqgiqCKoIqgiqCKoIqgj9/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f3////////////////AAAAD3vgAA974AAPe+AAD3vgAA974AAPe+AADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA9/4AAPf+AAD3/gAA9/4AAPf+AAD3/gAA8AAAAP//////////ygAAAAQAAAAIAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAgICAAMDcwADwyqYAqj8qAP8/KgAAXyoAVV8qAKpfKgD/XyoAAH8qAFV/KgCqfyoA/38qAACfKgBVnyoAqp8qAP+fKgAAvyoAVb8qAKq/KgD/vyoAAN8qAFXfKgCq3yoA/98qAAD/KgBV/yoAqv8qAP//KgAAAFUAVQBVAKoAVQD/AFUAAB9VAFUfVQCqH1UA/x9VAAA/VQBVP1UAqj9VAP8/VQAAX1UAVV9VAKpfVQD/X1UAAH9VAFV/VQCqf1UA/39VAACfVQBVn1UAqp9VAP+fVQAAv1UAVb9VAKq/VQD/v1UAAN9VAFXfVQCq31UA/99VAAD/VQBV/1UAqv9VAP//VQAAAH8AVQB/AKoAfwD/AH8AAB9/AFUffwCqH38A/x9/AAA/fwBVP38Aqj9/AP8/fwAAX38AVV9/AKpffwD/X38AAH9/AFV/fwCqf38A/39/AACffwBVn38Aqp9/AP+ffwAAv38AVb9/AKq/fwD/v38AAN9/AFXffwCq338A/99/AAD/fwBV/38Aqv9/AP//fwAAAKoAVQCqAKoAqgD/AKoAAB+qAFUfqgCqH6oA/x+qAAA/qgBVP6oAqj+qAP8/qgAAX6oAVV+qAKpfqgD/X6oAAH+qAFV/qgCqf6oA/3+qAACfqgBVn6oAqp+qAP+fqgAAv6oAVb+qAKq/qgD/v6oAAN+qAFXfqgCq36oA/9+qAAD/qgBV/6oAqv+qAP//qgAAANQAVQDUAKoA1AD/ANQAAB/UAFUf1ACqH9QA/x/UAAA/1ABVP9QAqj/UAP8/1AAAX9QAVV/UAKpf1AD/X9QAAH/UAFV/1ACqf9QA/3/UAACf1ABVn9QAqp/UAP+f1AAAv9QAVb/UAKq/1AD/v9QAAN/UAFXf1ACq39QA/9/UAAD/1ABV/9QAqv/UAP//1ABVAP8AqgD/AAAf/wBVH/8Aqh//AP8f/wAAP/8AVT//AKo//wD/P/8AAF//AFVf/wCqX/8A/1//AAB//wBVf/8Aqn//AP9//wAAn/8AVZ//AKqf/wD/n/8AAL//AFW//wCqv/8A/7//AADf/wBV3/8Aqt//AP/f/wBV//8Aqv//AP/MzAD/zP8A//8zAP//ZgD//5kA///MAAB/AABVfwAAqn8AAP9/AAAAnwAAVZ8AAKqfAAD/nwAAAL8AAFW/AACqvwAA/78AAADfAABV3wAAqt8AAP/fAABV/wAAqv8AAAAAKgBVACoAqgAqAP8AKgAAHyoAVR8qAKofKgD/HyoAAD8qAFU/KgDw+/8ApKCgAICAgAAAAP8AAP8AAAD//wD/AAAAAAAAAP//AAD///8A/f39/f39/f39/f39/f39/f0IhgiqCKoICKoICKaGCP39qv39hv2GNg4ODjII/ar9/Yb9/ar9qjdjXzsOCP2G/f0IhquGCAleCWNfNob9qv39qkxMTEgIX19fX18I/Qj9/QhwnZlMqoYIqggIqgiG/f2qcKadcAl8fFQDVFQDqv39CHDMpnCqfMvLysrKVAj9/QiUlHBwCYDPy8/LylSG/f2GqoYIqgig0M/Py8t8qv39CP39/f2GpNDQ0M/PfAn9/ar9/f39qqT20NDQ0Hyq/f2G/f39/QmkpKSloKR8CP39CKoIhgiqCIYIqgiGCKr9/f39/f39/f39/f39/f39/f//hv2AAf0ItAX9/bQFX2OABWNfgAU7O4ABNzeAAf39gAGq/YAB/YaAAf39vAE6h7wBX2O8AV9fgAE7N////f0oAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAwr/B/7Z3Sf+zckT/rm0//6toO/+nYjb/pF4y/6BZLv+dVCr/mlEn/5dNI/+VSiH/kkce/5FEHP+RRBz/kUUb/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/AAAAAAAAAAAAAAAAAAAAAAAAAADCv8H/v4JS//+aZv//lWD/+5Bc//WLV//uh1P/54FO/997S//Wdkb/zXBD/8VrQP+9Zj3/tGI5/65dN/+RRRz/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8AAAAAAAAAAAAAAAAAAAAAAAAAAMK/wf/GjFv//6Rz//+fbf//m2f//5Zh//yRXf/3jVj/8IhV/+mDUP/hfUz/2HhI/9ByRP/HbED/v2c9/5VJIf/Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAwr/B/86WZP//r4L//6p7//+mdf//oW7//5xo//+XYv/9kl7/+I5a//KJVf/rhFH/4n5N/9t4SP/Sc0X/mlEm/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/AAAAAAAAAAAAAAAAAAAAAAAAAADCv8H/1J9s//+4kf//tIv//6+E//+rff//p3f//6Jw//+eav//mWT//pRf//qQWv/0i1b/7IVS/+V/Tv+gWC7/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8AAAAAAAAAAAAAAAAAAAAAAAAAAMK/wf/apnP//7+d//+7mP//uJL//7WM//+whv//rH///6d4//+jcf//n2v//5ll//+VYP/6kVv/9YxY/6diN//Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/96teP//wqL//8Gi//+/nv//vJn//7mT//+2jv//sYj//66A//+pev//pHP//6Bt//+bZ///l2L/r20//8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/xYXev8XF3b/GBVx/xkUbf8ZFGr/GhNm/xoSY/8bEV//HBFd/xwQW//Cv8H/4K96///Cov//wqL//8Ki///Cov//wJ///72b//+6lf//t4///7KJ//+ugv//qnv//6V0//+hbv+3d0n/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/FRqE/0dN1v8/RNL/Nz3Q/y40zv8nLcz/ISfK/xwhyf8WHMf/GxJh/8K/wf/gr3r/4K96/+Cvev/gr3r/3614/9yqdf/apnL/16Nw/9Sea//Rmmj/zZZk/8qRX//GjFz/w4dW/7+CUv/Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8SHZD/WF3a/05U1/9FS9X/PUPS/zU70P8uM83/JyzL/yAmyf8aFGn/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/xAfnP9obt7/YGTc/1Zb2f9NU9f/RUrU/ztB0v80OdD/LDHO/xgWcv/Cv8H/Dn+n/w18pP8MeqH/DHie/wt1m/8Kc5j/CXGV/wlvk/8JbJD/CGqN/wdpi/8HZ4j/BmWH/wZkhf8GYoP/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/DiKp/3l+4/9vdeH/Zmze/11i2/9UWtn/S1HW/0NI1P86P9H/Fhh9/8K/wf8Ogar/Barp/wGo6P8Apef/AKPm/wCi5P8An+L/AJ7h/wCd3/8AnN7/AJnc/wCY2/8AmNn/AJbX/wZjhP/Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8MJbX/iI7n/4CF5v93fOP/bnPg/2Vr3f9bYdv/UljY/0lP1v8UGoj/wr/B/w+Erf8Lrur/Bqvq/wOo6f8Apuf/AKTm/wCi5f8AoOT/AJ/i/wCd4f8AnN//AJrd/wCZ2/8AmNr/BmWH/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wkowP+WnOz/jpTq/4aL6P9+hOX/dXri/2xx4P9jaN3/WV/b/xEek//Cv8H/EIaw/xay7P8Or+z/Cavr/wWq6v8Bp+j/AKbn/wCj5f8AoeT/AJ/j/wCe4f8AnOD/AJve/wCa3f8HZ4n/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/CCrK/6Ko7/+coe7/lZrr/42T6f+Fiub/fIHl/3N54v9rcN//ECGg/8K/wf8QiLP/I7nu/xq07f8Ssez/C63r/war6v8Cqen/AKbo/wCk5v8AouX/AKHk/wCf4f8AneH/AJzf/whoi//Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8GLNP/q7Hy/6as8P+hpu//mp/u/5OY6/+LkOj/g4nm/3qA5P8NI6z/wr/B/xCKtv8xvvD/J7rv/x627f8Vsuz/Dq/s/wmr6/8Equn/Aafo/wCl5/8Ao+X/AKHk/wCf4v8AnuH/CGqO/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wUu2/+vtPP/r7Tz/6qv8v+mq/D/oKXv/5me7f+Sl+v/io/p/wsmt//Cv8H/Eo24/0HF8f82wfD/LLzv/yK47v8atO3/EbHs/wut6/8Gq+r/A6np/wCm6P8Apeb/AKLl/wCh5P8IbJD/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/BC/h/wQv3/8FL9z/BS3Z/wYt1v8GLNL/ByvP/wgqy/8IKcb/CSnC/8K/wf8Sjrv/Uszy/0fH8f87w/H/Mb7v/ye67/8et+7/FbPt/w6v6/8IrOv/BKnp/wGo6P8Apef/AKPl/wluk//Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/xKRvf9j0/P/WM/z/0zK8f9BxfH/N8Hw/yy87/8iuO7/GbTt/xGx7P8Lruv/Bqrq/wOo6f8Apuf/CnGV/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCv8H/E5LA/3Ta8/9q1fP/XtHz/1LM8v9Hx/H/O8Pw/zG+7/8nu+//Hrbt/xay7f8Or+v/CKzq/wSq6f8Kc5j/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMK/wf8UlMH/hOD1/3rc9f9v2PP/ZNTy/1jO8v9NyvH/Qsbx/zbB8P8svO//I7ju/xm07f8SsOz/C67r/wt2m//Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwr/B/xSWw/+T5vb/iuL1/3/e9P912vT/adbz/13R8/9SzPL/R8jx/zzD8P8xvvD/J7rv/x627v8Vsuz/C3ie/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADCv8H/FJbG/57r9/+X6Pb/juT1/4Th9f963fX/b9j0/2PT8/9Yz/L/TMrx/0HF8f83wO//LLzv/yK47v8MeqH/wr/B/wAAAAAAAAAAAAAAAAAAAADCv8H/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMK/wf8VmMf/qO/3/6Lt9/+b6vb/kub2/4rj9f9/3vX/dNrz/2rV8/9d0fP/Uszy/0fI8f88w/D/Mr7v/w19pP/Cv8H/AAAAAAAAAAAAAAAAAAAAAMK/wf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwr/B/xWZyP8UmMf/FZfF/xSVw/8TlML/E5K//xOQvf8Sjrv/EYy4/xGKtv8QiLL/D4Ww/w+Erf8Pgar/Dn+n/8K/wf8AAAAAAAAAAAAAAAAAAAAAwr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/8K/wf/Cv8H/wr/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////8AAAAPe+AAD3vgAA974AAPe+AAD3vgAA974AAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAAD3/gAA9/4AAPf+AAD3/gAA9/4AAPf+AADwAAAA///////////KAAAABAAAAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP8AAAAAAAAAAMDAwP8AAAAAAAAAAMDAwP8AAAAAwMDA/8F2R/+9bj//umc6/7diNf+3YjX/wMDA/wAAAADAwMD/AAAAAAAAAADAwMD/AAAAAAAAAADAwMD/AAAAAMDAwP/RkmD//7aP//+ldP/8kl3/vW0//8DAwP8AAAAAwMDA/wAAAAAAAAAAwMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/3ap2///Cov//to7//6V0/8uJWP/AwMD/AAAAAMDAwP8AAAAAAAAAAMDAwP8THI7/FBqF/xYYfP8XFnP/wMDA/+Cvev/gr3r/4K96/92qdv/ao3D/wMDA/wAAAADAwMD/AAAAAAAAAADAwMD/ECCd/2Fn3P8zOc//FRmC/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/wAAAAAAAAAAwMDA/w0krP+Pler/YWbd/xIcj//AwMD/DHmf/wpzmP8Ib5L/B2uO/wdqjf8Gao3/B2qN/8DAwP8AAAAAAAAAAMDAwP8KJrv/r7Tz/5CU6v8PIJ//wMDA/w+Dq/87y/z/Kcb8/xrD/P8QwPv/EMD7/wdqjf/AwMD/AAAAAAAAAADAwMD/CCrI/woowP8LJrf/DSSu/8DAwP8Sjbj/Zdb9/0/Q/P88y/v/Kcf7/xrC+/8IbZD/wMDA/wAAAAAAAAAAwMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/FpfG/43h/f962/3/Zdb8/0/Q/P87zPz/CXSZ/8DAwP8AAAAAAAAAAMDAwP8AAAAAAAAAAAAAAAAAAAAAwMDA/xifz/+u6f7/n+X9/47h/f953P3/ZNb9/w19pP/AwMD/AAAAAAAAAADAwMD/AAAAAAAAAAAAAAAAAAAAAMDAwP8apNX/uez+/7ns/v+u6f7/oOX9/43h/f8Rh7H/wMDA/wAAAAAAAAAAwMDA/wAAAAAAAAAAAAAAAAAAAADAwMD/GqTV/xqk1f8apNX/GaHR/xecy/8WmMb/FJK+/8DAwP8AAAAAAAAAAMDAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAgAEAALQFwf+0BQAAgAUAAIAFAACAAQAAgAHB/4ABAACAAQAAgAEAALwBAAC8AQAAvAHB/4ABbP///5H/Cw== \ No newline at end of file diff --git a/Console/TeslaConsole.csproj b/Console/TeslaConsole.csproj new file mode 100644 index 0000000..1ea7af3 --- /dev/null +++ b/Console/TeslaConsole.csproj @@ -0,0 +1,69 @@ + + + TeslaConsole + False + WinExe + True + net48 + Preview + True + app.ico + + + disable + disable + + true + + $(NoWarn);CS0649 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + lib\WeifenLuo.WinFormsUI.Docking.dll + + + lib\TeslaSecureConfiguration.dll + + + lib\Munga Net.dll + + + lib\TeslaConsoleLaunchLib.dll + + + lib\BitmapLibrary.dll + + + diff --git a/Console/TeslaConsole/AlertButtonClickEventArgs.cs b/Console/TeslaConsole/AlertButtonClickEventArgs.cs new file mode 100644 index 0000000..19bfde6 --- /dev/null +++ b/Console/TeslaConsole/AlertButtonClickEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class AlertButtonClickEventArgs : EventArgs +{ + private DialogResult mDialogResult; + + private string mInput; + + public DialogResult DialogResult => mDialogResult; + + public string Input => mInput; + + public AlertButtonClickEventArgs(DialogResult dialogResult, string input) + { + mDialogResult = dialogResult; + mInput = input; + } +} diff --git a/Console/TeslaConsole/AppControlGrid.cs b/Console/TeslaConsole/AppControlGrid.cs new file mode 100644 index 0000000..637b590 --- /dev/null +++ b/Console/TeslaConsole/AppControlGrid.cs @@ -0,0 +1,473 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using Tesla.Net; +using TeslaConsole.Properties; + +namespace TeslaConsole; + +public class AppControlGrid : UserControl +{ + private class ApplicationInstanceGroup + { + public Guid LaunchKey { get; set; } + + public string AppDisplayName { get; set; } + + public List RunningPods { get; private set; } + + public List StoppedPods { get; private set; } + + public string RunningKey => "running:" + LaunchKey; + + public string StoppedKey => "stopped:" + LaunchKey; + + public ApplicationInstanceGroup(LaunchPair launchPair) + { + LaunchKey = launchPair.LaunchKey; + AppDisplayName = launchPair.DisplayName; + RunningPods = new List(); + StoppedPods = new List(); + } + + public void AddInstance(PodAppInstance instance) + { + if (instance.IsRunning) + { + RunningPods.Add(instance.PodId); + } + else + { + StoppedPods.Add(instance.PodId); + } + } + + public void StartPod(Guid podId) + { + if (StoppedPods.Contains(podId)) + { + StoppedPods.Remove(podId); + if (!RunningPods.Contains(podId)) + { + RunningPods.Add(podId); + } + } + } + + public void StartAllPods() + { + List list = new List(StoppedPods); + foreach (Guid item in list) + { + StartPod(item); + } + } + + public void StopPod(Guid podId) + { + if (RunningPods.Contains(podId)) + { + RunningPods.Remove(podId); + if (!StoppedPods.Contains(podId)) + { + StoppedPods.Add(podId); + } + } + } + + public void StopAllPods() + { + List list = new List(RunningPods); + foreach (Guid item in list) + { + StopPod(item); + } + } + + public override bool Equals(object obj) + { + if (obj is ApplicationInstanceGroup) + { + return LaunchKey == ((ApplicationInstanceGroup)obj).LaunchKey; + } + return base.Equals(obj); + } + + public override int GetHashCode() + { + return LaunchKey.GetHashCode(); + } + } + + private Dictionary mAppGroups = new Dictionary(); + + private IContainer components; + + private NoSelectDataGridView grdApps; + + private DataGridViewLinkColumn colAction; + + private DataGridViewTextBoxColumn colStatus; + + private DataGridViewTextBoxColumn colName; + + private DataGridViewImageColumn colError; + + public bool ShowCounts { get; set; } + + public event EventHandler LaunchApp; + + public event EventHandler KillApp; + + public AppControlGrid() + { + InitializeComponent(); + } + + private void grdApps_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0 || e.RowIndex >= grdApps.Rows.Count) + { + return; + } + DataGridViewRow dataGridViewRow = grdApps.Rows[e.RowIndex]; + string text = (string)dataGridViewRow.Tag; + ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[new Guid(text.Split(':')[1])]; + if (!(dataGridViewRow.Cells[e.ColumnIndex] is DataGridViewLinkCell dataGridViewLinkCell)) + { + return; + } + foreach (DataGridViewRow item in (IEnumerable)grdApps.Rows) + { + item.Cells[3].Value = Resources.Blank; + ((List)item.Cells[3].Tag).Clear(); + } + Rectangle cellDisplayRectangle = grdApps.GetCellDisplayRectangle(0, e.RowIndex, cutOverflow: false); + PictureBox pictureBox = new PictureBox(); + pictureBox.Name = text; + pictureBox.Image = Resources.square_throbber; + pictureBox.SizeMode = PictureBoxSizeMode.CenterImage; + pictureBox.Location = cellDisplayRectangle.Location; + pictureBox.Size = cellDisplayRectangle.Size; + pictureBox.Tag = e.RowIndex; + grdApps.Controls.Add(pictureBox); + pictureBox.BringToFront(); + if ((string)dataGridViewLinkCell.Value == "Start") + { + if (this.LaunchApp != null) + { + this.LaunchApp(this, new AppEventArgs(applicationInstanceGroup.LaunchKey, applicationInstanceGroup.StoppedPods)); + } + } + else if ((string)dataGridViewLinkCell.Value == "Stop" && this.KillApp != null) + { + this.KillApp(this, new AppEventArgs(applicationInstanceGroup.LaunchKey, applicationInstanceGroup.RunningPods)); + } + } + + private void grdApps_Scroll(object sender, ScrollEventArgs e) + { + foreach (Control control in grdApps.Controls) + { + if (control is PictureBox) + { + Rectangle cellDisplayRectangle = grdApps.GetCellDisplayRectangle(0, (int)control.Tag, cutOverflow: false); + control.Location = cellDisplayRectangle.Location; + control.Size = cellDisplayRectangle.Size; + control.BringToFront(); + } + } + } + + public void Clear() + { + mAppGroups.Clear(); + grdApps.Rows.Clear(); + grdApps.Controls.Clear(); + } + + public void SetData(IEnumerable instances) + { + Clear(); + foreach (PodAppInstance instance in instances) + { + if (!mAppGroups.TryGetValue(instance.LaunchPair.LaunchKey, out var value)) + { + value = new ApplicationInstanceGroup(instance.LaunchPair); + mAppGroups.Add(instance.LaunchPair.LaunchKey, value); + } + value.AddInstance(instance); + } + Refresh(); + grdApps.Sort(grdApps.Columns[2], ListSortDirection.Ascending); + } + + public void StartApp(Guid podId, Guid appId) + { + if (mAppGroups.ContainsKey(appId)) + { + ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId]; + applicationInstanceGroup.StartPod(podId); + RefreshAppGroup(applicationInstanceGroup); + Resort(); + } + } + + public void StartAppFailed(Guid appId, string podDisplayName) + { + if (mAppGroups.ContainsKey(appId)) + { + ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId]; + FlagError(applicationInstanceGroup.StoppedKey, podDisplayName, "The following pods failed to stop:"); + } + } + + public void StopApp(Guid podId, Guid appId) + { + if (mAppGroups.ContainsKey(appId)) + { + ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId]; + applicationInstanceGroup.StopPod(podId); + RefreshAppGroup(applicationInstanceGroup); + Resort(); + } + } + + public void StopAppFailed(Guid appId, string podDisplayName) + { + if (mAppGroups.ContainsKey(appId)) + { + ApplicationInstanceGroup applicationInstanceGroup = mAppGroups[appId]; + FlagError(applicationInstanceGroup.RunningKey, podDisplayName, "The following pods failed to stop:"); + } + } + + private void FlagError(string rowKey, string podDisplayName, string error) + { + Control[] array = grdApps.Controls.Find(rowKey, searchAllChildren: false); + foreach (Control value in array) + { + grdApps.Controls.Remove(value); + } + foreach (DataGridViewRow item in (IEnumerable)grdApps.Rows) + { + if (object.Equals(item.Tag, rowKey)) + { + ((List)item.Cells[3].Tag).Add(podDisplayName); + RefreshRowError(item, error); + } + } + } + + public override void Refresh() + { + base.Refresh(); + grdApps.SuspendLayout(); + foreach (ApplicationInstanceGroup value in mAppGroups.Values) + { + RefreshAppGroup(value); + } + grdApps.ResumeLayout(); + grdApps.AutoResizeColumns(); + Resort(); + } + + private void RefreshAppGroup(ApplicationInstanceGroup group) + { + bool flag = false; + bool flag2 = false; + for (int num = grdApps.Rows.Count - 1; num >= 0; num--) + { + DataGridViewRow dataGridViewRow = grdApps.Rows[num]; + if (object.Equals(dataGridViewRow.Tag, group.RunningKey)) + { + flag = true; + if (group.RunningPods.Count > 0) + { + RefreshRow(dataGridViewRow, isRunning: true, group.RunningPods.Count); + } + else + { + RemoveRow(num); + } + } + else if (object.Equals(dataGridViewRow.Tag, group.StoppedKey)) + { + flag2 = true; + if (group.StoppedPods.Count > 0) + { + RefreshRow(dataGridViewRow, isRunning: false, group.StoppedPods.Count); + } + else + { + RemoveRow(num); + } + } + } + if (!flag && group.RunningPods.Count > 0) + { + grdApps.Rows.Add(CreateRow(group, isRunning: true)); + } + if (!flag2 && group.StoppedPods.Count > 0) + { + grdApps.Rows.Add(CreateRow(group, isRunning: false)); + } + } + + private void Resort() + { + if (grdApps.SortOrder != 0) + { + grdApps.Sort(grdApps.SortedColumn, (grdApps.SortOrder != SortOrder.Ascending) ? ListSortDirection.Descending : ListSortDirection.Ascending); + } + } + + private DataGridViewRow CreateRow(ApplicationInstanceGroup group, bool isRunning) + { + DataGridViewRow dataGridViewRow = new DataGridViewRow(); + dataGridViewRow.Tag = (isRunning ? group.RunningKey : group.StoppedKey); + dataGridViewRow.Cells.Add(new DataGridViewLinkCell()); + dataGridViewRow.Cells.Add(new DataGridViewTextBoxCell()); + DataGridViewTextBoxCell dataGridViewTextBoxCell = new DataGridViewTextBoxCell(); + dataGridViewTextBoxCell.Value = group.AppDisplayName; + dataGridViewRow.Cells.Add(dataGridViewTextBoxCell); + DataGridViewImageCell dataGridViewImageCell = new DataGridViewImageCell(); + dataGridViewImageCell.Value = Resources.Blank; + dataGridViewImageCell.Tag = new List(); + dataGridViewRow.Cells.Add(dataGridViewImageCell); + RefreshRow(dataGridViewRow, isRunning, isRunning ? group.RunningPods.Count : group.StoppedPods.Count); + return dataGridViewRow; + } + + private void RefreshRow(DataGridViewRow row, bool isRunning, int count) + { + DataGridViewLinkCell dataGridViewLinkCell = (DataGridViewLinkCell)row.Cells[0]; + dataGridViewLinkCell.Value = (isRunning ? "Stop" : "Start"); + Color color2 = (dataGridViewLinkCell.VisitedLinkColor = (isRunning ? Color.Red : Color.Green)); + Color color5 = (dataGridViewLinkCell.LinkColor = (dataGridViewLinkCell.ActiveLinkColor = color2)); + DataGridViewTextBoxCell dataGridViewTextBoxCell = (DataGridViewTextBoxCell)row.Cells[1]; + if (ShowCounts) + { + dataGridViewTextBoxCell.Value = count + (isRunning ? " Running" : " Stopped"); + } + else + { + dataGridViewTextBoxCell.Value = (isRunning ? "Running" : "Stopped"); + } + dataGridViewTextBoxCell.Style.ForeColor = (isRunning ? Color.Green : Color.Black); + } + + private void RefreshRowError(DataGridViewRow row, string errorPrefix) + { + row.Cells[3].Value = Resources.Error; + StringBuilder stringBuilder = new StringBuilder(errorPrefix); + foreach (string item in (List)row.Cells[3].Tag) + { + stringBuilder.AppendLine(); + stringBuilder.Append(" - "); + stringBuilder.Append(item); + } + row.Cells[3].ToolTipText = stringBuilder.ToString(); + } + + private void RemoveRow(int index) + { + Control[] array = grdApps.Controls.Find((string)grdApps.Rows[index].Tag, searchAllChildren: false); + foreach (Control value in array) + { + grdApps.Controls.Remove(value); + } + grdApps.Rows.RemoveAt(index); + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); + this.grdApps = new TeslaConsole.NoSelectDataGridView(); + this.colAction = new System.Windows.Forms.DataGridViewLinkColumn(); + this.colStatus = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colName = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colError = new System.Windows.Forms.DataGridViewImageColumn(); + ((System.ComponentModel.ISupportInitialize)this.grdApps).BeginInit(); + base.SuspendLayout(); + this.grdApps.AllowUserToAddRows = false; + this.grdApps.AllowUserToDeleteRows = false; + this.grdApps.AllowUserToResizeRows = false; + this.grdApps.BackgroundColor = System.Drawing.Color.White; + this.grdApps.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.grdApps.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None; + this.grdApps.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single; + this.grdApps.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.grdApps.Columns.AddRange(this.colAction, this.colStatus, this.colName, this.colError); + this.grdApps.Dock = System.Windows.Forms.DockStyle.Fill; + this.grdApps.EditMode = System.Windows.Forms.DataGridViewEditMode.EditProgrammatically; + this.grdApps.GridColor = System.Drawing.Color.White; + this.grdApps.Location = new System.Drawing.Point(0, 0); + this.grdApps.MultiSelect = false; + this.grdApps.Name = "grdApps"; + this.grdApps.ReadOnly = true; + this.grdApps.RowHeadersVisible = false; + this.grdApps.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.grdApps.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.grdApps.Size = new System.Drawing.Size(366, 215); + this.grdApps.TabIndex = 8; + this.grdApps.Scroll += new System.Windows.Forms.ScrollEventHandler(grdApps_Scroll); + this.grdApps.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(grdApps_CellContentClick); + this.colAction.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + dataGridViewCellStyle.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; + this.colAction.DefaultCellStyle = dataGridViewCellStyle; + this.colAction.Frozen = true; + this.colAction.HeaderText = "Action"; + this.colAction.LinkColor = System.Drawing.Color.Blue; + this.colAction.MinimumWidth = 50; + this.colAction.Name = "colAction"; + this.colAction.ReadOnly = true; + this.colAction.VisitedLinkColor = System.Drawing.Color.Blue; + this.colAction.Width = 50; + this.colStatus.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleCenter; + dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25f, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); + this.colStatus.DefaultCellStyle = dataGridViewCellStyle2; + this.colStatus.Frozen = true; + this.colStatus.HeaderText = "Status"; + this.colStatus.MinimumWidth = 50; + this.colStatus.Name = "colStatus"; + this.colStatus.ReadOnly = true; + this.colStatus.Width = 62; + this.colName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + this.colName.DefaultCellStyle = dataGridViewCellStyle3; + this.colName.HeaderText = "Application Name"; + this.colName.MinimumWidth = 100; + this.colName.Name = "colName"; + this.colName.ReadOnly = true; + this.colError.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; + this.colError.HeaderText = ""; + this.colError.MinimumWidth = 20; + this.colError.Name = "colError"; + this.colError.ReadOnly = true; + this.colError.Width = 20; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.Controls.Add(this.grdApps); + base.Name = "AppControlGrid"; + base.Size = new System.Drawing.Size(366, 215); + ((System.ComponentModel.ISupportInitialize)this.grdApps).EndInit(); + base.ResumeLayout(false); + } +} diff --git a/Console/TeslaConsole/AppEventArgs.cs b/Console/TeslaConsole/AppEventArgs.cs new file mode 100644 index 0000000..148def0 --- /dev/null +++ b/Console/TeslaConsole/AppEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole; + +public class AppEventArgs : EventArgs +{ + public Guid ApplicationId { get; private set; } + + public List PodIds { get; private set; } + + public AppEventArgs(Guid applicationId, IEnumerable podIds) + { + ApplicationId = applicationId; + PodIds = new List(podIds); + } +} diff --git a/Console/TeslaConsole/AppRegistry.cs b/Console/TeslaConsole/AppRegistry.cs new file mode 100644 index 0000000..5f8a0a7 --- /dev/null +++ b/Console/TeslaConsole/AppRegistry.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using System.Xml; +using Tesla.Net; + +namespace TeslaConsole; + +/// Role a launch entry fills; only meaningful for products that prompt with the start-params dialog. +public enum AppHostType +{ + None, + GameClient, + LiveCamera, + MissionReview +} + +/// One launchable application a product registers on a pod. +public class LaunchEntryDefinition +{ + public Guid LaunchKey; + + public string DisplayName = ""; + + public string Exe = ""; + + /// Arguments; the token "{res}" expands to " -res W H" when a custom resolution is chosen, else "". + public string Args = ""; + + /// Optional; when null/empty the working directory is derived from . + public string WorkingDirectory; + + public bool AutoRestart = true; + + public AppHostType HostType; + + /// Builds the wire for this entry, expanding {res} from the optional resolution. + public LaunchData ToLaunchData(Size? resolution) + { + string resArg = resolution.HasValue + ? $" -res {resolution.Value.Width} {resolution.Value.Height}" + : ""; + LaunchData data = default(LaunchData); + data.LaunchPair.DisplayName = DisplayName; + data.LaunchPair.LaunchKey = LaunchKey; + data.ExeFile = Exe; + data.Arguments = (Args ?? "").Replace("{res}", resArg); + data.WorkingDirectory = string.IsNullOrEmpty(WorkingDirectory) + ? Path.GetDirectoryName(Exe) + : WorkingDirectory; + data.AutoRestart = AutoRestart; + return data; + } +} + +/// An installable product: an install/package key plus the launch entries it registers. +public class ProductDefinition +{ + public Guid Id; + + public string Name = ""; + + public string MenuText = ""; + + /// When true, installing prompts for resolution + roles and only selected-role entries are registered. + public bool HostTypeDialog; + + public List Entries = new List(); +} + +/// +/// Data-driven catalog of installable products, loaded from RedPlanet\Apps.xml (next to the exe). +/// Replaces the products that were hardcoded in . +/// +public static class AppRegistry +{ + private static readonly List mProducts = new List(); + + private static readonly Dictionary mById = new Dictionary(); + + public static IReadOnlyList Products => mProducts; + + public static string CatalogPath => + Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "RedPlanet\\Apps.xml"); + + static AppRegistry() + { + Reload(); + } + + public static ProductDefinition GetProduct(Guid id) + { + return mById.TryGetValue(id, out var product) ? product : null; + } + + /// (Re)loads the catalog from . Safe if the file is missing — yields an empty catalog. + public static void Reload() + { + LoadFromFile(CatalogPath); + } + + /// Loads the catalog from an explicit path. Safe if the file is missing — yields an empty catalog. + public static void LoadFromFile(string path) + { + mProducts.Clear(); + mById.Clear(); + if (!File.Exists(path)) + { + return; + } + XmlDocument doc = new XmlDocument(); + doc.Load(path); + foreach (XmlNode node in doc.DocumentElement.ChildNodes) + { + if (node.NodeType == XmlNodeType.Element && node.Name == "Product") + { + ProductDefinition product = ParseProduct(node); + mProducts.Add(product); + mById[product.Id] = product; + } + } + } + + /// Adds a product to the catalog and persists the whole catalog to disk. + public static void AddProduct(ProductDefinition product) + { + mProducts.Add(product); + mById[product.Id] = product; + Save(); + } + + /// Writes the catalog back to . + public static void Save() + { + XmlDocument doc = new XmlDocument(); + doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null)); + doc.AppendChild(doc.CreateComment( + " Tesla Console product catalog. Managed by Site Management (Add Product...). ")); + XmlElement root = doc.CreateElement("AppCatalog"); + doc.AppendChild(root); + foreach (ProductDefinition product in mProducts) + { + XmlElement pe = doc.CreateElement("Product"); + pe.SetAttribute("id", product.Id.ToString("D")); + pe.SetAttribute("name", product.Name); + pe.SetAttribute("menuText", product.MenuText); + pe.SetAttribute("hostTypeDialog", product.HostTypeDialog ? "true" : "false"); + foreach (LaunchEntryDefinition entry in product.Entries) + { + XmlElement le = doc.CreateElement("Launch"); + le.SetAttribute("key", entry.LaunchKey.ToString("D")); + le.SetAttribute("displayName", entry.DisplayName); + le.SetAttribute("exe", entry.Exe); + le.SetAttribute("args", entry.Args ?? ""); + if (!string.IsNullOrEmpty(entry.WorkingDirectory)) + { + le.SetAttribute("workingDirectory", entry.WorkingDirectory); + } + le.SetAttribute("autoRestart", entry.AutoRestart ? "true" : "false"); + le.SetAttribute("hostType", entry.HostType.ToString()); + pe.AppendChild(le); + } + root.AppendChild(pe); + } + Directory.CreateDirectory(Path.GetDirectoryName(CatalogPath)); + doc.Save(CatalogPath); + } + + private static ProductDefinition ParseProduct(XmlNode node) + { + ProductDefinition product = new ProductDefinition + { + Id = ParseGuid(Attr(node, "id")), + Name = Attr(node, "name"), + MenuText = Attr(node, "menuText"), + HostTypeDialog = ParseBool(Attr(node, "hostTypeDialog")) + }; + if (string.IsNullOrEmpty(product.MenuText)) + { + product.MenuText = product.Name + "..."; + } + foreach (XmlNode child in node.ChildNodes) + { + if (child.NodeType == XmlNodeType.Element && child.Name == "Launch") + { + product.Entries.Add(ParseLaunch(child)); + } + } + return product; + } + + private static LaunchEntryDefinition ParseLaunch(XmlNode node) + { + return new LaunchEntryDefinition + { + LaunchKey = ParseGuid(Attr(node, "key")), + DisplayName = Attr(node, "displayName"), + Exe = Attr(node, "exe"), + Args = Attr(node, "args"), + WorkingDirectory = HasAttr(node, "workingDirectory") ? Attr(node, "workingDirectory") : null, + AutoRestart = !HasAttr(node, "autoRestart") || ParseBool(Attr(node, "autoRestart")), + HostType = ParseHostType(Attr(node, "hostType")) + }; + } + + private static bool HasAttr(XmlNode node, string name) + { + return node.Attributes?[name] != null; + } + + private static string Attr(XmlNode node, string name) + { + return node.Attributes?[name]?.InnerText ?? ""; + } + + private static Guid ParseGuid(string value) + { + return Guid.TryParse(value, out var guid) ? guid : Guid.Empty; + } + + private static bool ParseBool(string value) + { + return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) || value == "1"; + } + + private static AppHostType ParseHostType(string value) + { + return Enum.TryParse(value, ignoreCase: true, out var result) ? result : AppHostType.None; + } +} diff --git a/Console/TeslaConsole/ApplicationCompletedEventArgs.cs b/Console/TeslaConsole/ApplicationCompletedEventArgs.cs new file mode 100644 index 0000000..a7cb899 --- /dev/null +++ b/Console/TeslaConsole/ApplicationCompletedEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel; + +namespace TeslaConsole; + +public class ApplicationCompletedEventArgs : AsyncCompletedEventArgs +{ + private Guid mAppId; + + public Guid ApplicationId => mAppId; + + public ApplicationCompletedEventArgs(Guid appId, Exception e, bool canceled, object state) + : base(e, canceled, state) + { + mAppId = appId; + } +} diff --git a/Console/TeslaConsole/AsyncControlState.cs b/Console/TeslaConsole/AsyncControlState.cs new file mode 100644 index 0000000..f2c3676 --- /dev/null +++ b/Console/TeslaConsole/AsyncControlState.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel; + +namespace TeslaConsole; + +internal class AsyncControlState +{ + public AsyncOperation asyncOp; + + public Exception ex; + + public AsyncControlState(Exception ex, AsyncOperation asyncOp) + { + this.ex = ex; + this.asyncOp = asyncOp; + } +} +internal class AsyncControlState : AsyncControlState +{ + public T state = default(T); + + public AsyncControlState(T state, Exception ex, AsyncOperation asyncOp) + : base(ex, asyncOp) + { + this.state = state; + } +} diff --git a/Console/TeslaConsole/AsyncDelegates.cs b/Console/TeslaConsole/AsyncDelegates.cs new file mode 100644 index 0000000..396c7a7 --- /dev/null +++ b/Console/TeslaConsole/AsyncDelegates.cs @@ -0,0 +1,26 @@ +using System.Threading; + +namespace TeslaConsole; + +public class AsyncDelegates +{ + public T Worker; + + public SendOrPostCallback OnProgressReport; + + public SendOrPostCallback OnCompleted; + + public SendOrPostCallback CompletionMethod; + + public AsyncDelegates(SendOrPostCallback completed, SendOrPostCallback completionMethod) + : this((SendOrPostCallback)null, completed, completionMethod) + { + } + + public AsyncDelegates(SendOrPostCallback progressReport, SendOrPostCallback completed, SendOrPostCallback completionMethod) + { + OnProgressReport = progressReport; + OnCompleted = completed; + CompletionMethod = completionMethod; + } +} diff --git a/Console/TeslaConsole/Cached.cs b/Console/TeslaConsole/Cached.cs new file mode 100644 index 0000000..6a1b84c --- /dev/null +++ b/Console/TeslaConsole/Cached.cs @@ -0,0 +1,43 @@ +using System; + +namespace TeslaConsole; + +internal class Cached where T : class +{ + private T mItem; + + private DateTime mNextExpireTime; + + private TimeSpan mCacheDuration; + + private object mSyncRoot = new object(); + + public T Item + { + get + { + return mItem; + } + set + { + mItem = value; + mNextExpireTime = DateTime.Now + mCacheDuration; + } + } + + public bool IsExpired => DateTime.Now > mNextExpireTime; + + public object SyncRoot => mSyncRoot; + + public Cached(TimeSpan cacheDuration) + { + mItem = null; + mNextExpireTime = DateTime.MinValue; + mCacheDuration = cacheDuration; + } + + public void Expire() + { + mNextExpireTime = DateTime.MinValue; + } +} diff --git a/Console/TeslaConsole/HostType.cs b/Console/TeslaConsole/HostType.cs new file mode 100644 index 0000000..e5b0d6b --- /dev/null +++ b/Console/TeslaConsole/HostType.cs @@ -0,0 +1,8 @@ +namespace TeslaConsole; + +public enum HostType +{ + GameMachineHostType = 0, + MissionReviewHostType = 2, + ConsoleHostType = 3 +} diff --git a/Console/TeslaConsole/HostTypeHelper.cs b/Console/TeslaConsole/HostTypeHelper.cs new file mode 100644 index 0000000..6098da1 --- /dev/null +++ b/Console/TeslaConsole/HostTypeHelper.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; + +namespace TeslaConsole; + +public class HostTypeHelper +{ + private static readonly Dictionary mCache; + + private HostType mHostType; + + public HostType HostType => mHostType; + + static HostTypeHelper() + { + mCache = new Dictionary(); + foreach (object value in Enum.GetValues(typeof(HostType))) + { + HostType hostType = (HostType)value; + mCache.Add(hostType, new HostTypeHelper(hostType)); + } + } + + public static explicit operator HostTypeHelper(HostType hostType) + { + return mCache[hostType]; + } + + public static explicit operator HostType(HostTypeHelper helper) + { + return helper.HostType; + } + + public static bool operator ==(HostTypeHelper a, HostTypeHelper b) + { + return a.mHostType == b.mHostType; + } + + public static bool operator !=(HostTypeHelper a, HostTypeHelper b) + { + return !(a == b); + } + + public static HostTypeHelper Parse(string str) + { + foreach (HostTypeHelper value in mCache.Values) + { + if (value.ToString() == str) + { + return value; + } + } + try + { + HostType hostType = (HostType)Enum.Parse(typeof(HostType), str); + return (HostTypeHelper)hostType; + } + catch (Exception) + { + } + throw new ArgumentException(); + } + + private HostTypeHelper(HostType hostType) + { + mHostType = hostType; + } + + public override bool Equals(object obj) + { + if (obj is HostTypeHelper && ((HostTypeHelper)obj).mHostType == mHostType) + { + return true; + } + return false; + } + + public override int GetHashCode() + { + return (int)mHostType; + } + + public override string ToString() + { + return mHostType switch + { + HostType.GameMachineHostType => "Game Machine", + HostType.MissionReviewHostType => "Mission Review", + HostType.ConsoleHostType => "Console", + _ => mHostType.ToString(), + }; + } +} diff --git a/Console/TeslaConsole/ImageCache.cs b/Console/TeslaConsole/ImageCache.cs new file mode 100644 index 0000000..418319b --- /dev/null +++ b/Console/TeslaConsole/ImageCache.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Drawing; +using TeslaConsole.Properties; + +namespace TeslaConsole; + +internal class ImageCache +{ + private static readonly Bitmap mPodBad16; + + private static readonly Bitmap mPodGo16; + + private static readonly Bitmap mPodOffline16; + + private static readonly Bitmap mPodOnline16; + + private static readonly Bitmap mPodQuestion16; + + private static readonly Bitmap mPodRun16; + + private static Dictionary mCache; + + public static Bitmap PodBad16 => mPodBad16; + + public static Bitmap PodGo16 => mPodGo16; + + public static Bitmap PodOffline16 => mPodOffline16; + + public static Bitmap PodOnline16 => mPodOnline16; + + public static Bitmap PodQuestion16 => mPodQuestion16; + + public static Bitmap PodRun16 => mPodRun16; + + static ImageCache() + { + mCache = new Dictionary(); + mPodBad16 = Resources.PodBad16; + mPodGo16 = Resources.PodGo16; + mPodOffline16 = Resources.PodOffline16; + mPodOnline16 = Resources.PodOnline16; + mPodQuestion16 = Resources.PodQuestion16; + mPodRun16 = Resources.PodRun16; + } + + public static Image GetImage(string path) + { + lock (mCache) + { + if (!mCache.ContainsKey(path)) + { + try + { + mCache.Add(path, Image.FromFile(path)); + } + catch + { + } + } + return mCache.ContainsKey(path) ? mCache[path] : null; + } + } +} diff --git a/Console/TeslaConsole/InstallProductCompletedEventArgs.cs b/Console/TeslaConsole/InstallProductCompletedEventArgs.cs new file mode 100644 index 0000000..e86ec66 --- /dev/null +++ b/Console/TeslaConsole/InstallProductCompletedEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; + +namespace TeslaConsole; + +public class InstallProductCompletedEventArgs : AsyncCompletedEventArgs +{ + private string mUncPath; + + public string UncPath + { + get + { + RaiseExceptionIfNecessary(); + return mUncPath; + } + } + + public InstallProductCompletedEventArgs(string uncPath, Exception e, bool canceled, object state) + : base(e, canceled, state) + { + mUncPath = uncPath; + } +} diff --git a/Console/TeslaConsole/InstallProductProgressEventArgs.cs b/Console/TeslaConsole/InstallProductProgressEventArgs.cs new file mode 100644 index 0000000..47a7227 --- /dev/null +++ b/Console/TeslaConsole/InstallProductProgressEventArgs.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace TeslaConsole; + +public class InstallProductProgressEventArgs : ProgressChangedEventArgs +{ + private string mStatus = ""; + + public string Status => mStatus; + + public InstallProductProgressEventArgs(string status, int progressPercentage, object userState) + : base(progressPercentage, userState) + { + mStatus = status; + } +} diff --git a/Console/TeslaConsole/LogOutputWindow.cs b/Console/TeslaConsole/LogOutputWindow.cs new file mode 100644 index 0000000..f193224 --- /dev/null +++ b/Console/TeslaConsole/LogOutputWindow.cs @@ -0,0 +1,66 @@ +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class LogOutputWindow : Form +{ + private IContainer components; + + private TextBox txtOutput; + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.txtOutput = new System.Windows.Forms.TextBox(); + base.SuspendLayout(); + this.txtOutput.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtOutput.Location = new System.Drawing.Point(0, 0); + this.txtOutput.Multiline = true; + this.txtOutput.Name = "txtOutput"; + this.txtOutput.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtOutput.Size = new System.Drawing.Size(663, 451); + this.txtOutput.TabIndex = 0; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(663, 451); + base.Controls.Add(this.txtOutput); + base.Name = "LogOutputWindow"; + this.Text = "Log Output Window"; + base.ResumeLayout(false); + base.PerformLayout(); + } + + public LogOutputWindow() + { + InitializeComponent(); + } + + public void LogMessage(string message, params object[] args) + { + LogMessage(string.Format(message, args)); + } + + public void LogMessage(string message) + { + if (!txtOutput.IsDisposed) + { + int textLength = txtOutput.TextLength; + message = $"\r\n{message}"; + if (textLength + message.Length > txtOutput.MaxLength) + { + txtOutput.Text = txtOutput.Text.Substring(message.Length); + } + txtOutput.AppendText(message); + } + } +} diff --git a/Console/TeslaConsole/MigrateIsolatedStorage.cs b/Console/TeslaConsole/MigrateIsolatedStorage.cs new file mode 100644 index 0000000..d38ec94 --- /dev/null +++ b/Console/TeslaConsole/MigrateIsolatedStorage.cs @@ -0,0 +1,57 @@ +using System.Collections; +using System.ComponentModel; +using System.Configuration.Install; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace TeslaConsole; + +[RunInstaller(true)] +public class MigrateIsolatedStorage : Installer +{ + private IContainer components; + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + components = new Container(); + } + + public MigrateIsolatedStorage() + { + InitializeComponent(); + } + + public override void Commit(IDictionary savedState) + { + base.Commit(savedState); + base.Context.LogMessage("Migrating IsolatedStorage"); + string path = base.Context.Parameters["targetdir"]; + string fileName = Path.GetFileName(Assembly.GetExecutingAssembly().CodeBase); + string text = Path.Combine(path, fileName); + base.Context.LogMessage("Running \"" + text + "\" /migrateIsoStore"); + using Process process = new Process(); + process.StartInfo.FileName = text; + process.StartInfo.Arguments = "/migrateIsoStore"; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + if (!process.Start()) + { + throw new InstallException("Unable to start IsolatedStorage migrator process."); + } + process.WaitForExit(); + if (process.ExitCode != 0) + { + throw new InstallException("IsolatedStorage migrator exited with code " + process.ExitCode); + } + } +} diff --git a/Console/TeslaConsole/MungaConnectionState.cs b/Console/TeslaConsole/MungaConnectionState.cs new file mode 100644 index 0000000..64034b5 --- /dev/null +++ b/Console/TeslaConsole/MungaConnectionState.cs @@ -0,0 +1,8 @@ +namespace TeslaConsole; + +public enum MungaConnectionState +{ + Disconnected, + Connecting, + Connected +} diff --git a/Console/TeslaConsole/MungaGame.cs b/Console/TeslaConsole/MungaGame.cs new file mode 100644 index 0000000..ae8a1ff --- /dev/null +++ b/Console/TeslaConsole/MungaGame.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; +using Munga.Net; + +namespace TeslaConsole; + +public class MungaGame +{ + private readonly object mInternalLock = new object(); + + private readonly Pod mPod; + + private Image mImage; + + private string mStatusMessage; + + private MungaSocket mSocket; + + private DateTime mNextStateQuery = DateTime.MinValue; + + private ApplicationState? mAppState; + + private ApplicationID? mAppID; + + private DateTime mEggDelayTimer = DateTime.MinValue; + + private DateTime mEggSent = DateTime.MinValue; + + private bool mEggAck; + + private MungaConnectionState mState; + + private readonly List mRequestors = new List(); + + private Form mOwner; + + public Image Image => mImage; + + public string StatusMessage => mStatusMessage; + + public Pod Pod => mPod; + + public ApplicationState? AppState => mAppState; + + public ApplicationID? AppID => mAppID; + + public DateTime EggDelayTimer + { + get + { + return mEggDelayTimer; + } + set + { + mEggDelayTimer = value; + } + } + + public DateTime EggSent + { + get + { + return mEggSent; + } + set + { + mEggSent = value; + } + } + + public bool EggAcknowledged => mEggAck; + + public MungaConnectionState State + { + get + { + lock (mInternalLock) + { + if (mState == MungaConnectionState.Connected) + { + if (mSocket != null && mSocket.Connected) + { + return MungaConnectionState.Connected; + } + Disconnect(); + } + return mState; + } + } + private set + { + lock (mInternalLock) + { + mState = value; + } + } + } + + public bool Requested + { + get + { + lock (mInternalLock) + { + return mRequestors.Count > 0; + } + } + } + + public bool IsOwned + { + get + { + if (mOwner != null && mOwner.IsDisposed) + { + mOwner = null; + } + return mOwner != null; + } + } + + public event EventHandler OnPodStatusUpdated; + + public void QueryStateIfNeeded() + { + DateTime now = DateTime.Now; + if (mNextStateQuery < now) + { + Send(0, 1, new StateQueryMessage(1)); + mNextStateQuery = now.AddSeconds(1.0); + } + } + + public bool IsPodAppState(ApplicationID app, ApplicationState state) + { + return IsPodAppState(app, new ApplicationState[1] { state }); + } + + public bool IsPodAppState(ApplicationID app, IEnumerable states) + { + if (IsPodOperational(app)) + { + foreach (ApplicationState state in states) + { + if (mAppState.Value == state) + { + return true; + } + } + } + return false; + } + + public bool IsPodOperational(ApplicationID app) + { + if (State == MungaConnectionState.Connected && mAppID.HasValue && mAppID.Value == app) + { + return mAppState.HasValue; + } + return false; + } + + public void MakeRequested(object requestor) + { + lock (mInternalLock) + { + if (!mRequestors.Contains(requestor)) + { + mRequestors.Add(requestor); + if (mRequestors.Count == 1) + { + Connect(); + } + } + } + } + + public void ReleaseRequest(object requestor) + { + lock (mInternalLock) + { + if (mRequestors.Contains(requestor)) + { + mRequestors.Remove(requestor); + if (mRequestors.Count == 0) + { + Disconnect(); + } + } + } + } + + public bool IsOwner(Form owner) + { + if (mOwner != null && mOwner.IsDisposed) + { + mOwner = null; + } + return mOwner == owner; + } + + public void TakeOwnership(Form owner) + { + lock (mInternalLock) + { + if (mOwner == null) + { + mOwner = owner; + ForceStatusUpdate(); + } + } + } + + public void ReleaseOwnership(Form owner) + { + lock (mInternalLock) + { + if (mOwner == owner) + { + mOwner = null; + ForceStatusUpdate(); + } + } + } + + public MungaGame(Pod pod) + { + mPod = pod; + } + + public void SetStatus(Image img, string statusMessage) + { + lock (mInternalLock) + { + if (mImage != img || mStatusMessage != statusMessage) + { + mImage = img; + mStatusMessage = statusMessage; + ForceStatusUpdate(); + } + } + } + + private void ForceStatusUpdate() + { + if (this.OnPodStatusUpdated != null) + { + this.OnPodStatusUpdated(this, EventArgs.Empty); + } + } + + private void Connect() + { + lock (mInternalLock) + { + if (State != 0) + { + return; + } + State = MungaConnectionState.Connecting; + ShutdownSocket(); + mSocket = new MungaSocket(); + mSocket.BeginConnect(mPod.IPAddress, 1501, delegate(IAsyncResult ar) + { + lock (mInternalLock) + { + try + { + mSocket.EndConnect(ar); + State = MungaConnectionState.Connected; + } + catch + { + Disconnect(); + } + } + }, null); + } + } + + private void Disconnect() + { + lock (mInternalLock) + { + State = MungaConnectionState.Disconnected; + mAppState = null; + mAppID = null; + mNextStateQuery = DateTime.MinValue; + ShutdownSocket(); + if (Requested) + { + Connect(); + } + } + } + + private void ShutdownSocket() + { + if (mSocket != null) + { + try + { + mSocket.Shutdown(); + } + catch + { + } + mSocket = null; + } + } + + public MetaMessage Receive() + { + lock (mInternalLock) + { + try + { + MetaMessage metaMessage = mSocket.Receive(); + if (metaMessage != null) + { + if (metaMessage.Message is StateResponseMessage) + { + StateResponseMessage stateResponseMessage = (StateResponseMessage)metaMessage.Message; + mAppID = stateResponseMessage.Application; + if (stateResponseMessage.ApplicationState == ApplicationState.WaitingForEgg && stateResponseMessage.ApplicationState != mAppState) + { + mEggDelayTimer = DateTime.Now.AddSeconds(5.0); + mEggSent = DateTime.MinValue; + mEggAck = false; + } + mAppState = stateResponseMessage.ApplicationState; + } + else if (metaMessage.Message is AcknowledgeEggFileMessage) + { + mEggAck = true; + } + } + return metaMessage; + } + catch + { + Disconnect(); + return null; + } + } + } + + public void Send(int gameID, int fromHost, MungaMessage message) + { + lock (mInternalLock) + { + try + { + mSocket.Send(gameID, fromHost, message); + } + catch + { + Disconnect(); + } + } + } +} diff --git a/Console/TeslaConsole/NoDoubleClickTreeView.cs b/Console/TeslaConsole/NoDoubleClickTreeView.cs new file mode 100644 index 0000000..0a0aeca --- /dev/null +++ b/Console/TeslaConsole/NoDoubleClickTreeView.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class NoDoubleClickTreeView : TreeView +{ + protected override void WndProc(ref Message m) + { + if (m.Msg == 515) + { + m.Result = IntPtr.Zero; + } + else + { + base.WndProc(ref m); + } + } +} diff --git a/Console/TeslaConsole/NoSelectDataGridView.cs b/Console/TeslaConsole/NoSelectDataGridView.cs new file mode 100644 index 0000000..0773b59 --- /dev/null +++ b/Console/TeslaConsole/NoSelectDataGridView.cs @@ -0,0 +1,10 @@ +using System.Windows.Forms; + +namespace TeslaConsole; + +internal class NoSelectDataGridView : DataGridView +{ + protected override void SetSelectedRowCore(int rowIndex, bool selected) + { + } +} diff --git a/Console/TeslaConsole/NotificationAlert.cs b/Console/TeslaConsole/NotificationAlert.cs new file mode 100644 index 0000000..ca04465 --- /dev/null +++ b/Console/TeslaConsole/NotificationAlert.cs @@ -0,0 +1,503 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class NotificationAlert : Form +{ + private const double FadeInDuration = 0.3; + + private const double FadeOutDuration = 0.3; + + private const int AlertSpacing = 0; + + private static List mAlerts = new List(); + + private double mDoubleTop; + + private double mDestTop; + + private bool mMoving; + + private IContainer components; + + private PictureBox picIcon; + + private Label lblMessage; + + private Button cmdRightButton; + + private Button cmdCenterButton; + + private Button cmdLeftButton; + + private Timer tmrFadeIn; + + private Timer tmrFadeOut; + + private Timer tmrSlideDown; + + private TextBox txtInput; + + private Timer tmrSlideUp; + + public string Title + { + get + { + return base.Text; + } + set + { + base.Text = value; + } + } + + public override string Text + { + get + { + if (lblMessage == null) + { + return null; + } + return lblMessage.Text; + } + set + { + if (lblMessage != null) + { + lblMessage.Text = value; + } + } + } + + public new Image Icon + { + get + { + return picIcon.Image; + } + set + { + picIcon.Image = value; + } + } + + public bool LeftButton + { + get + { + return cmdLeftButton.Visible; + } + set + { + cmdLeftButton.Visible = value; + } + } + + public string LeftButtonText + { + get + { + return cmdLeftButton.Text; + } + set + { + cmdLeftButton.Text = value; + } + } + + public DialogResult LeftButtonDialogResult + { + get + { + return cmdLeftButton.DialogResult; + } + set + { + cmdLeftButton.DialogResult = value; + } + } + + public bool CenterButton + { + get + { + return cmdCenterButton.Visible; + } + set + { + cmdCenterButton.Visible = value; + } + } + + public string CenterButtonText + { + get + { + return cmdCenterButton.Text; + } + set + { + cmdCenterButton.Text = value; + } + } + + public DialogResult CenterButtonDialogResult + { + get + { + return cmdCenterButton.DialogResult; + } + set + { + cmdCenterButton.DialogResult = value; + } + } + + public bool RightButton + { + get + { + return cmdRightButton.Visible; + } + set + { + cmdRightButton.Visible = value; + } + } + + public string RightButtonText + { + get + { + return cmdRightButton.Text; + } + set + { + cmdRightButton.Text = value; + } + } + + public DialogResult RightButtonDialogResult + { + get + { + return cmdRightButton.DialogResult; + } + set + { + cmdRightButton.DialogResult = value; + } + } + + public bool ShowInput + { + get + { + return txtInput.Visible; + } + set + { + if (value != txtInput.Visible) + { + txtInput.Visible = value; + if (value) + { + base.Height += txtInput.Height + txtInput.Margin.Bottom; + } + else + { + base.Height -= txtInput.Height + txtInput.Margin.Bottom; + } + } + } + } + + public event EventHandler AlertButtonClick; + + public NotificationAlert() + { + InitializeComponent(); + mAlerts.Add(this); + base.Opacity = 0.01; + ShowInput = false; + } + + public new void Show() + { + mMoving = true; + base.Left = Screen.PrimaryScreen.WorkingArea.Right - base.Width - 30; + base.Top = Screen.PrimaryScreen.WorkingArea.Bottom - base.Height - 30; + mMoving = false; + base.Opacity = 0.01; + base.Show(); + tmrFadeIn.Stop(); + tmrFadeIn.Start(); + int num = mAlerts.IndexOf(this); + if (num > 0) + { + NotificationAlert notificationAlert = mAlerts[num - 1]; + notificationAlert.mDoubleTop = notificationAlert.Top; + notificationAlert.mDestTop = base.Top - notificationAlert.Height; + notificationAlert.tmrSlideUp.Tag = num - 1; + notificationAlert.tmrSlideUp.Stop(); + notificationAlert.tmrSlideUp.Start(); + } + } + + private void tmrFadeIn_Tick(object sender, EventArgs e) + { + double num = (double)tmrFadeIn.Interval / 1000.0; + base.Opacity += 3.3333333333333335 * num; + if (base.Opacity >= 1.0) + { + base.Opacity = 1.0; + tmrFadeIn.Stop(); + } + } + + private void tmrFadeOut_Tick(object sender, EventArgs e) + { + double num = (double)tmrFadeIn.Interval / 1000.0; + base.Opacity -= 3.3333333333333335 * num; + if (base.Opacity <= 0.01) + { + base.Opacity = 0.01; + Close(); + tmrFadeOut.Stop(); + } + } + + private void tmrSlideUp_Tick(object sender, EventArgs e) + { + double dT = (double)tmrSlideUp.Interval / 1000.0; + SlideUp((int)tmrSlideUp.Tag, dT); + if (mDoubleTop <= mDestTop) + { + tmrSlideUp.Stop(); + } + } + + private void tmrSlideDown_Tick(object sender, EventArgs e) + { + double dT = (double)tmrSlideDown.Interval / 1000.0; + SlideDown((int)tmrSlideDown.Tag, dT); + if (mDoubleTop >= mDestTop) + { + tmrSlideDown.Stop(); + } + } + + private void NotificationAlert_FormClosing(object sender, FormClosingEventArgs e) + { + if (e.CloseReason != CloseReason.UserClosing || !(base.Opacity > 0.01)) + { + return; + } + e.Cancel = true; + tmrFadeOut.Stop(); + tmrFadeOut.Start(); + int num = mAlerts.IndexOf(this); + if (num > 0) + { + NotificationAlert notificationAlert = mAlerts[num - 1]; + notificationAlert.mDoubleTop = notificationAlert.Top; + if (num == mAlerts.Count - 1) + { + notificationAlert.mDestTop = (double)(Screen.PrimaryScreen.WorkingArea.Bottom - notificationAlert.Height) - 30.0; + } + else + { + notificationAlert.mDestTop = mAlerts[num + 1].Top - notificationAlert.Height; + } + notificationAlert.tmrSlideDown.Tag = num - 1; + notificationAlert.tmrSlideDown.Stop(); + notificationAlert.tmrSlideDown.Start(); + } + } + + private static void SlideUp(int index, double dT) + { + if (index >= 0 && index <= mAlerts.Count) + { + for (int i = 0; i <= index; i++) + { + NotificationAlert notificationAlert = mAlerts[i]; + notificationAlert.mDoubleTop -= (double)notificationAlert.Height / 0.3 * dT; + notificationAlert.mMoving = true; + notificationAlert.Top = (int)Math.Round(notificationAlert.mDoubleTop); + notificationAlert.mMoving = false; + } + } + } + + private static void SlideDown(int index, double dT) + { + if (index >= 0 && index <= mAlerts.Count) + { + for (int num = index; num >= 0; num--) + { + NotificationAlert notificationAlert = mAlerts[num]; + notificationAlert.mDoubleTop += (double)notificationAlert.Height / 0.3 * dT; + notificationAlert.mMoving = true; + notificationAlert.Top = (int)Math.Round(notificationAlert.mDoubleTop); + notificationAlert.mMoving = false; + } + } + } + + private void NotificationAlert_Move(object sender, EventArgs e) + { + if (mMoving) + { + return; + } + int num = mAlerts.IndexOf(this); + mAlerts.Remove(this); + if (num > 0) + { + NotificationAlert notificationAlert = mAlerts[num - 1]; + notificationAlert.mDoubleTop = notificationAlert.Top; + if (num == mAlerts.Count) + { + notificationAlert.mDestTop = (double)(Screen.PrimaryScreen.WorkingArea.Bottom - notificationAlert.Height) - 30.0; + } + else + { + notificationAlert.mDestTop = mAlerts[num].Top - notificationAlert.Height; + } + notificationAlert.tmrSlideDown.Tag = num - 1; + notificationAlert.tmrSlideDown.Stop(); + notificationAlert.tmrSlideDown.Start(); + } + } + + private void cmdButton_Click(object sender, EventArgs e) + { + if (sender is Button button && this.AlertButtonClick != null) + { + this.AlertButtonClick(this, new AlertButtonClickEventArgs(button.DialogResult, ShowInput ? txtInput.Text : null)); + } + } + + private void txtInput_TextChanged(object sender, EventArgs e) + { + int selectionStart = txtInput.SelectionStart; + int selectionLength = txtInput.SelectionLength; + txtInput.Text = txtInput.Text.ToUpper(); + txtInput.Select(selectionStart, selectionLength); + } + + private void txtInput_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == '\r') + { + cmdCenterButton.PerformClick(); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.picIcon = new System.Windows.Forms.PictureBox(); + this.lblMessage = new System.Windows.Forms.Label(); + this.cmdRightButton = new System.Windows.Forms.Button(); + this.cmdCenterButton = new System.Windows.Forms.Button(); + this.cmdLeftButton = new System.Windows.Forms.Button(); + this.tmrFadeIn = new System.Windows.Forms.Timer(this.components); + this.tmrFadeOut = new System.Windows.Forms.Timer(this.components); + this.tmrSlideDown = new System.Windows.Forms.Timer(this.components); + this.txtInput = new System.Windows.Forms.TextBox(); + this.tmrSlideUp = new System.Windows.Forms.Timer(this.components); + ((System.ComponentModel.ISupportInitialize)this.picIcon).BeginInit(); + base.SuspendLayout(); + this.picIcon.Location = new System.Drawing.Point(12, 12); + this.picIcon.Name = "picIcon"; + this.picIcon.Size = new System.Drawing.Size(40, 40); + this.picIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picIcon.TabIndex = 0; + this.picIcon.TabStop = false; + this.lblMessage.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.lblMessage.Location = new System.Drawing.Point(58, 12); + this.lblMessage.Name = "lblMessage"; + this.lblMessage.Size = new System.Drawing.Size(206, 40); + this.lblMessage.TabIndex = 1; + this.lblMessage.Text = "Message"; + this.cmdRightButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.cmdRightButton.Location = new System.Drawing.Point(189, 77); + this.cmdRightButton.Name = "cmdRightButton"; + this.cmdRightButton.Size = new System.Drawing.Size(75, 23); + this.cmdRightButton.TabIndex = 2; + this.cmdRightButton.Text = "Cancel"; + this.cmdRightButton.UseVisualStyleBackColor = true; + this.cmdRightButton.Click += new System.EventHandler(cmdButton_Click); + this.cmdCenterButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.cmdCenterButton.Location = new System.Drawing.Point(108, 77); + this.cmdCenterButton.Name = "cmdCenterButton"; + this.cmdCenterButton.Size = new System.Drawing.Size(75, 23); + this.cmdCenterButton.TabIndex = 3; + this.cmdCenterButton.Text = "Okay"; + this.cmdCenterButton.UseVisualStyleBackColor = true; + this.cmdCenterButton.Visible = false; + this.cmdCenterButton.Click += new System.EventHandler(cmdButton_Click); + this.cmdLeftButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.cmdLeftButton.Location = new System.Drawing.Point(27, 77); + this.cmdLeftButton.Name = "cmdLeftButton"; + this.cmdLeftButton.Size = new System.Drawing.Size(75, 23); + this.cmdLeftButton.TabIndex = 4; + this.cmdLeftButton.Text = "Yes"; + this.cmdLeftButton.UseVisualStyleBackColor = true; + this.cmdLeftButton.Visible = false; + this.cmdLeftButton.Click += new System.EventHandler(cmdButton_Click); + this.tmrFadeIn.Interval = 50; + this.tmrFadeIn.Tick += new System.EventHandler(tmrFadeIn_Tick); + this.tmrFadeOut.Interval = 50; + this.tmrFadeOut.Tick += new System.EventHandler(tmrFadeOut_Tick); + this.tmrSlideDown.Interval = 50; + this.tmrSlideDown.Tick += new System.EventHandler(tmrSlideDown_Tick); + this.txtInput.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtInput.Location = new System.Drawing.Point(61, 54); + this.txtInput.Name = "txtInput"; + this.txtInput.Size = new System.Drawing.Size(203, 20); + this.txtInput.TabIndex = 5; + this.txtInput.Visible = false; + this.txtInput.TextChanged += new System.EventHandler(txtInput_TextChanged); + this.txtInput.KeyPress += new System.Windows.Forms.KeyPressEventHandler(txtInput_KeyPress); + this.tmrSlideUp.Interval = 50; + this.tmrSlideUp.Tick += new System.EventHandler(tmrSlideUp_Tick); + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(276, 112); + base.Controls.Add(this.txtInput); + base.Controls.Add(this.cmdLeftButton); + base.Controls.Add(this.cmdCenterButton); + base.Controls.Add(this.cmdRightButton); + base.Controls.Add(this.lblMessage); + base.Controls.Add(this.picIcon); + base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + base.Name = "NotificationAlert"; + base.ShowInTaskbar = false; + base.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.Text = "NotificationAlert"; + base.TopMost = true; + base.Move += new System.EventHandler(NotificationAlert_Move); + base.FormClosing += new System.Windows.Forms.FormClosingEventHandler(NotificationAlert_FormClosing); + ((System.ComponentModel.ISupportInitialize)this.picIcon).EndInit(); + base.ResumeLayout(false); + base.PerformLayout(); + } +} diff --git a/Console/TeslaConsole/PilotNameCache.cs b/Console/TeslaConsole/PilotNameCache.cs new file mode 100644 index 0000000..67f71b2 --- /dev/null +++ b/Console/TeslaConsole/PilotNameCache.cs @@ -0,0 +1,10 @@ +using System.Windows.Forms; + +namespace TeslaConsole; + +public static class PilotNameCache +{ + private static readonly AutoCompleteStringCollection mPilotNames = new AutoCompleteStringCollection(); + + public static AutoCompleteStringCollection PilotNames => mPilotNames; +} diff --git a/Console/TeslaConsole/PlasmaBitmaps.cs b/Console/TeslaConsole/PlasmaBitmaps.cs new file mode 100644 index 0000000..0b00b1a --- /dev/null +++ b/Console/TeslaConsole/PlasmaBitmaps.cs @@ -0,0 +1,140 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; +using System.IO; +using System.Text; + +namespace TeslaConsole; + +public class PlasmaBitmaps +{ + public const string DefaultFontFamily = "Microsoft Sans Serif"; + + private static bool sEnableCustomBitmaps; + + public static bool EnableCustomBitmaps + { + get + { + return sEnableCustomBitmaps; + } + set + { + sEnableCustomBitmaps = value; + } + } + + public static void GenerateBitmaps(out Bitmap large, out Bitmap small, string str) + { + GenerateBitmaps(out large, out small, "Microsoft Sans Serif", str); + } + + public static void GenerateBitmaps(out Bitmap large, out Bitmap small, string fontName, string str) + { + large = GenerateBitmap(128, 32, fontName, str); + small = GenerateBitmap(64, 16, fontName, str); + } + + public static Bitmap GenerateBitmap(int width, int height, string str) + { + return GenerateBitmap(width, height, "Microsoft Sans Serif", str); + } + + public static Bitmap GenerateBitmap(int width, int height, string fontName, string str) + { + if (sEnableCustomBitmaps) + { + string text = Path.Combine(Program.GetCommonAppDataDirectory(), $"Plasma Images\\{str}_{width}x{height}.bmp"); + if (File.Exists(text)) + { + try + { + Bitmap bitmap = (Bitmap)Image.FromFile(text); + if (bitmap.Width == width && bitmap.Height == height) + { + return bitmap; + } + bitmap.Dispose(); + } + catch + { + } + } + } + Bitmap bitmap2 = new Bitmap(width, height); + using Graphics graphics = Graphics.FromImage(bitmap2); + graphics.SmoothingMode = SmoothingMode.None; + graphics.CompositingQuality = CompositingQuality.AssumeLinear; + graphics.InterpolationMode = InterpolationMode.NearestNeighbor; + graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; + graphics.Clear(Color.Black); + int num = 24; + while (true) + { + using Font font = new Font(fontName, num); + SizeF sizeF = graphics.MeasureString(str, font); + if (sizeF.Width <= (float)bitmap2.Width && sizeF.Height <= (float)bitmap2.Height) + { + Rectangle rectangle = new Rectangle(0, 0, bitmap2.Width, bitmap2.Height); + StringFormat stringFormat = new StringFormat(); + stringFormat.Alignment = StringAlignment.Center; + stringFormat.LineAlignment = StringAlignment.Center; + graphics.DrawString(str, font, Brushes.White, rectangle, stringFormat); + return bitmap2; + } + num = ((num <= 2) ? (num / 2) : (num - 2)); + } + } + + public static void GenerateStrings(out string large, out string small, string str) + { + GenerateStrings(out large, out small, "Microsoft Sans Serif", str); + } + + public static void GenerateStrings(out string large, out string small, string fontName, string str) + { + large = GenerateString(128, 32, fontName, str); + small = GenerateString(64, 16, fontName, str); + } + + public static string GenerateString(int width, int height, string str) + { + return GenerateString(width, height, "Microsoft Sans Serif", str); + } + + public static string GenerateString(int width, int height, string fontName, string str) + { + using Bitmap bitmap = GenerateBitmap(width, height, fontName, str); + return ConvertBitmap(bitmap); + } + + public static string ConvertBitmap(Bitmap bitmap) + { + if (bitmap.Width % 8 != 0 || bitmap.Height % 8 != 0) + { + throw new ArgumentOutOfRangeException("bitmap"); + } + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < bitmap.Height; i++) + { + stringBuilder.Append("bitmap="); + for (int j = 0; j < bitmap.Width / 8; j++) + { + byte b = 0; + for (int k = 0; k < 8; k++) + { + if (bitmap.GetPixel(j * 8 + k, i).GetBrightness() >= 0.5f) + { + b = (byte)(b | (byte)(128 >> k)); + } + } + stringBuilder.Append(b.ToString("X2")); + } + stringBuilder.Append("\n"); + } + stringBuilder.AppendFormat("x={0}\n", bitmap.Width); + stringBuilder.AppendFormat("y={0}\n", bitmap.Height); + return stringBuilder.ToString(); + } +} diff --git a/Console/TeslaConsole/PlasmaFontTool.cs b/Console/TeslaConsole/PlasmaFontTool.cs new file mode 100644 index 0000000..033a56d --- /dev/null +++ b/Console/TeslaConsole/PlasmaFontTool.cs @@ -0,0 +1,157 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Text; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class PlasmaFontTool : Form +{ + private IContainer components; + + private TableLayoutPanel tableLayoutPanel1; + + private PlasmaView mLargePlasma; + + private PlasmaView mSmallPlasma; + + private TableLayoutPanel tableLayoutPanel2; + + private TextBox mText; + + private Label mTextLabel; + + private Label mFontLabel; + + private ComboBox mFont; + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.mLargePlasma = new TeslaConsole.PlasmaView(); + this.mSmallPlasma = new TeslaConsole.PlasmaView(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.mText = new System.Windows.Forms.TextBox(); + this.mTextLabel = new System.Windows.Forms.Label(); + this.mFontLabel = new System.Windows.Forms.Label(); + this.mFont = new System.Windows.Forms.ComboBox(); + this.tableLayoutPanel1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + base.SuspendLayout(); + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 66.66666f)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33334f)); + this.tableLayoutPanel1.Controls.Add(this.mLargePlasma, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.mSmallPlasma, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 1, 0); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 3; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25f)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50f)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25f)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(1035, 425); + this.tableLayoutPanel1.TabIndex = 0; + this.mLargePlasma.BackColor = System.Drawing.Color.Black; + this.mLargePlasma.Dock = System.Windows.Forms.DockStyle.Fill; + this.mLargePlasma.Enabled = false; + this.mLargePlasma.ForeColor = System.Drawing.Color.DarkOrange; + this.mLargePlasma.Location = new System.Drawing.Point(3, 3); + this.mLargePlasma.Name = "mLargePlasma"; + this.tableLayoutPanel1.SetRowSpan(this.mLargePlasma, 3); + this.mLargePlasma.Size = new System.Drawing.Size(683, 419); + this.mLargePlasma.TabIndex = 0; + this.mSmallPlasma.BackColor = System.Drawing.Color.Black; + this.mSmallPlasma.Dock = System.Windows.Forms.DockStyle.Fill; + this.mSmallPlasma.Enabled = false; + this.mSmallPlasma.ForeColor = System.Drawing.Color.DarkOrange; + this.mSmallPlasma.Location = new System.Drawing.Point(692, 109); + this.mSmallPlasma.Name = "mSmallPlasma"; + this.mSmallPlasma.Size = new System.Drawing.Size(340, 206); + this.mSmallPlasma.TabIndex = 1; + this.tableLayoutPanel2.ColumnCount = 2; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.tableLayoutPanel2.Controls.Add(this.mText, 1, 1); + this.tableLayoutPanel2.Controls.Add(this.mTextLabel, 0, 1); + this.tableLayoutPanel2.Controls.Add(this.mFontLabel, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.mFont, 1, 0); + this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel2.Location = new System.Drawing.Point(692, 3); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 3; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(340, 100); + this.tableLayoutPanel2.TabIndex = 2; + this.mText.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mText.Location = new System.Drawing.Point(40, 30); + this.mText.Name = "mText"; + this.mText.Size = new System.Drawing.Size(297, 20); + this.mText.TabIndex = 0; + this.mText.TextChanged += new System.EventHandler(RebuildImages); + this.mTextLabel.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mTextLabel.AutoSize = true; + this.mTextLabel.Location = new System.Drawing.Point(3, 33); + this.mTextLabel.Name = "mTextLabel"; + this.mTextLabel.Size = new System.Drawing.Size(31, 13); + this.mTextLabel.TabIndex = 1; + this.mTextLabel.Text = "Text:"; + this.mFontLabel.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFontLabel.AutoSize = true; + this.mFontLabel.Location = new System.Drawing.Point(3, 7); + this.mFontLabel.Name = "mFontLabel"; + this.mFontLabel.Size = new System.Drawing.Size(31, 13); + this.mFontLabel.TabIndex = 2; + this.mFontLabel.Text = "Font:"; + this.mFont.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mFont.FormattingEnabled = true; + this.mFont.Location = new System.Drawing.Point(40, 3); + this.mFont.Name = "mFont"; + this.mFont.Size = new System.Drawing.Size(297, 21); + this.mFont.TabIndex = 3; + this.mFont.SelectedIndexChanged += new System.EventHandler(RebuildImages); + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(1035, 425); + base.Controls.Add(this.tableLayoutPanel1); + base.Name = "PlasmaFontTool"; + this.Text = "Plasma Font Tool"; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + base.ResumeLayout(false); + } + + public PlasmaFontTool() + { + InitializeComponent(); + FontFamily[] families = new InstalledFontCollection().Families; + foreach (FontFamily fontFamily in families) + { + mFont.Items.Add(fontFamily.Name); + } + mFont.SelectedItem = "Microsoft Sans Serif"; + } + + private void RebuildImages(object sender, EventArgs e) + { + PlasmaBitmaps.GenerateBitmaps(out var large, out var small, mFont.Text, mText.Text); + mLargePlasma.SetBitmap(large); + mSmallPlasma.SetBitmap(small); + large.Dispose(); + small.Dispose(); + } +} diff --git a/Console/TeslaConsole/Pod.cs b/Console/TeslaConsole/Pod.cs new file mode 100644 index 0000000..ac1e7d2 --- /dev/null +++ b/Console/TeslaConsole/Pod.cs @@ -0,0 +1,313 @@ +using System; +using System.Drawing; +using System.IO; +using System.Net; +using System.Runtime.CompilerServices; + +namespace TeslaConsole; + +[Serializable] +public class Pod : IDisposable +{ + [NonSerialized] + private EventHandler mOnOnlineChanged; + + private Guid mId = Guid.NewGuid(); + + private IPAddress mIPAddress = IPAddress.Any; + + private IPAddress mGateway = IPAddress.Any; + + private IPAddress mDns = IPAddress.Any; + + private IPAddress mSubnet = IPAddress.Any; + + private string mHostName = ""; + + private byte[] mKey; + + private byte[] mMacAddress; + + private string mName; + + private string mPodArtPath; + + private HostType mHostType; + + private bool mOnline; + + [NonSerialized] + private Image mPodArtImage; + + [NonSerialized] + private MungaGame mMungaGame; + + public Guid ID + { + get + { + return mId; + } + set + { + mId = value; + } + } + + public bool IsLocal + { + get + { + if (mKey != null) + { + return mKey.Length > 0; + } + return false; + } + } + + public IPAddress IPAddress + { + get + { + return mIPAddress; + } + set + { + mIPAddress = value; + } + } + + public IPAddress DNS + { + get + { + return mDns; + } + set + { + mDns = value; + } + } + + public IPAddress Gateway + { + get + { + return mGateway; + } + set + { + mGateway = value; + } + } + + public IPAddress Subnet + { + get + { + return mSubnet; + } + set + { + mSubnet = value; + } + } + + public string HostName + { + get + { + return mHostName; + } + set + { + mHostName = value; + } + } + + public byte[] Key + { + get + { + return mKey; + } + set + { + mKey = value; + } + } + + public string Name + { + get + { + return mName; + } + set + { + mName = value; + } + } + + public byte[] MacAddress + { + get + { + return mMacAddress; + } + set + { + mMacAddress = value; + } + } + + public string PodArtFilePath + { + get + { + return mPodArtPath; + } + set + { + mPodArtPath = value; + } + } + + public HostType HostType + { + get + { + return mHostType; + } + set + { + mHostType = value; + } + } + + public Image PodArtImage + { + get + { + if (mPodArtImage == null) + { + try + { + if (mPodArtPath == null || !File.Exists(mPodArtPath)) + { + return null; + } + mPodArtImage = Image.FromFile(mPodArtPath); + } + catch + { + } + } + return mPodArtImage; + } + } + + public bool Online + { + get + { + return mOnline; + } + set + { + if (value != mOnline) + { + mOnline = value; + if (mOnOnlineChanged != null) + { + mOnOnlineChanged(this, EventArgs.Empty); + } + } + } + } + + public MungaGame MungaGame + { + get + { + if (mMungaGame == null) + { + mMungaGame = new MungaGame(this); + } + return mMungaGame; + } + } + + public event EventHandler OnOnlineChanged + { + [MethodImpl(MethodImplOptions.Synchronized)] + add + { + mOnOnlineChanged = (EventHandler)Delegate.Combine(mOnOnlineChanged, value); + } + [MethodImpl(MethodImplOptions.Synchronized)] + remove + { + mOnOnlineChanged = (EventHandler)Delegate.Remove(mOnOnlineChanged, value); + } + } + + public Pod() + { + mMungaGame = new MungaGame(this); + } + + public Pod Clone() + { + Pod pod = new Pod(); + pod.mId = mId; + pod.mIPAddress = mIPAddress; + pod.mGateway = mGateway; + pod.mDns = mDns; + pod.mSubnet = mSubnet; + pod.mHostName = mHostName; + pod.mKey = mKey; + pod.mMacAddress = mMacAddress; + pod.mName = mName; + pod.mPodArtPath = mPodArtPath; + pod.mHostType = mHostType; + pod.mOnline = mOnline; + pod.mPodArtImage = mPodArtImage; + pod.mMungaGame = mMungaGame; + return pod; + } + + public bool MacAddressEquals(byte[] macAddress) + { + if (mMacAddress == null) + { + return macAddress == null; + } + if (mMacAddress.Length != macAddress.Length) + { + return false; + } + for (int i = 0; i < macAddress.Length; i++) + { + if (mMacAddress[i] != macAddress[i]) + { + return false; + } + } + return true; + } + + ~Pod() + { + Dispose(); + } + + public void Dispose() + { + if (mPodArtImage != null) + { + mPodArtImage.Dispose(); + } + } +} diff --git a/Console/TeslaConsole/PodActionDialog.cs b/Console/TeslaConsole/PodActionDialog.cs new file mode 100644 index 0000000..5e42c58 --- /dev/null +++ b/Console/TeslaConsole/PodActionDialog.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class PodActionDialog : Form +{ + private delegate void ActionCompleted(); + + private class ActionItem + { + private readonly PodInfo mPodInfo; + + private readonly Action mAction; + + private readonly ActionCompleted mActionCompleted; + + private readonly ListViewItem rListViewItem; + + private bool mDone; + + private bool mCanceled; + + private bool mError; + + public ListViewItem ListViewItem => rListViewItem; + + public bool Done + { + get + { + return mDone; + } + set + { + if (!mDone && value) + { + mDone = true; + mActionCompleted(); + } + } + } + + public bool Canceled + { + get + { + return mCanceled; + } + set + { + if (!mDone && !mCanceled && value) + { + mCanceled = true; + Done = true; + } + } + } + + public bool Error => mError; + + public ActionItem(PodInfo podInfo, Action action, ActionCompleted complete) + { + mPodInfo = podInfo; + mAction = action; + mActionCompleted = complete; + rListViewItem = new ListViewItem(new string[2] + { + mPodInfo.Pod.Name, + "Connecting..." + }); + } + + public void Begin() + { + mPodInfo.EnqueueConnectAction(DoAction); + } + + private void DoAction(PodInfo pod) + { + if (!mCanceled) + { + try + { + mAction(pod); + } + catch (Exception ex) + { + rListViewItem.SubItems[1].Text = ex.Message; + mError = true; + } + if (!mError) + { + rListViewItem.SubItems[1].Text = "Complete"; + } + Done = true; + } + } + } + + private IContainer components; + + private Button mCancel; + + private ListView mActionList; + + private ColumnHeader mPodColumn; + + private ColumnHeader mStatusColumn; + + private readonly List mActionItems = new List(); + + private bool mDone; + + private bool mError; + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.mCancel = new System.Windows.Forms.Button(); + this.mActionList = new System.Windows.Forms.ListView(); + this.mPodColumn = new System.Windows.Forms.ColumnHeader(); + this.mStatusColumn = new System.Windows.Forms.ColumnHeader(); + base.SuspendLayout(); + this.mCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.mCancel.Location = new System.Drawing.Point(377, 247); + this.mCancel.Name = "mCancel"; + this.mCancel.Size = new System.Drawing.Size(75, 23); + this.mCancel.TabIndex = 0; + this.mCancel.Text = "Cancel"; + this.mCancel.UseVisualStyleBackColor = true; + this.mCancel.Click += new System.EventHandler(mCancel_Click); + this.mActionList.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.mActionList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[2] { this.mPodColumn, this.mStatusColumn }); + this.mActionList.GridLines = true; + this.mActionList.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.mActionList.Location = new System.Drawing.Point(12, 12); + this.mActionList.MultiSelect = false; + this.mActionList.Name = "mActionList"; + this.mActionList.Size = new System.Drawing.Size(440, 229); + this.mActionList.TabIndex = 1; + this.mActionList.UseCompatibleStateImageBehavior = false; + this.mActionList.View = System.Windows.Forms.View.Details; + this.mPodColumn.Text = "Pod"; + this.mPodColumn.Width = 300; + this.mStatusColumn.Text = "Status"; + this.mStatusColumn.Width = 120; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(464, 282); + base.Controls.Add(this.mActionList); + base.Controls.Add(this.mCancel); + base.Name = "PodActionDialog"; + this.Text = "Performing Action..."; + base.ResumeLayout(false); + } + + public static void Show(ICollection pods, Action action, string title) + { + if (pods.Count > 0) + { + PodActionDialog podActionDialog = new PodActionDialog(pods, action, title); + if (!podActionDialog.IsDisposed) + { + podActionDialog.Show(); + } + } + } + + private PodActionDialog() + { + InitializeComponent(); + } + + private PodActionDialog(IEnumerable pods, Action action, string title) + : this() + { + Text = title; + foreach (PodInfo pod in pods) + { + ActionItem actionItem = new ActionItem(pod, action, ItemDone); + mActionItems.Add(actionItem); + mActionList.Items.Add(actionItem.ListViewItem); + } + foreach (ActionItem mActionItem in mActionItems) + { + mActionItem.Begin(); + } + if (mActionItems.Count < 1) + { + ItemDone(); + } + } + + private void ItemDone() + { + if (mDone) + { + return; + } + foreach (ActionItem mActionItem in mActionItems) + { + if (!mActionItem.Done) + { + return; + } + if (mActionItem.Error) + { + mError = true; + } + } + mDone = true; + if (!mError) + { + Close(); + } + else + { + mCancel.Text = "OK"; + } + } + + private void mCancel_Click(object sender, EventArgs e) + { + Close(); + } + + protected override void OnClosing(CancelEventArgs e) + { + if (!mDone) + { + if (MessageBox.Show("Are you sure you wish to cancel this action?", "Cancel?", MessageBoxButtons.YesNoCancel) == DialogResult.Yes) + { + foreach (ActionItem mActionItem in mActionItems) + { + mActionItem.Canceled = true; + } + } + e.Cancel = true; + } + else + { + base.OnClosing(e); + } + } +} diff --git a/Console/TeslaConsole/PodAppInstance.cs b/Console/TeslaConsole/PodAppInstance.cs new file mode 100644 index 0000000..0974fc2 --- /dev/null +++ b/Console/TeslaConsole/PodAppInstance.cs @@ -0,0 +1,13 @@ +using System; +using Tesla.Net; + +namespace TeslaConsole; + +public class PodAppInstance +{ + public LaunchPair LaunchPair { get; set; } + + public Guid PodId { get; set; } + + public bool IsRunning { get; set; } +} diff --git a/Console/TeslaConsole/PodConfigurationException.cs b/Console/TeslaConsole/PodConfigurationException.cs new file mode 100644 index 0000000..a64f1d4 --- /dev/null +++ b/Console/TeslaConsole/PodConfigurationException.cs @@ -0,0 +1,16 @@ +using System; + +namespace TeslaConsole; + +public class PodConfigurationException : Exception +{ + private Pod mPod; + + public Pod Pod => mPod; + + public PodConfigurationException(string message, Pod pod) + : base(message) + { + mPod = pod; + } +} diff --git a/Console/TeslaConsole/PodInfo.cs b/Console/TeslaConsole/PodInfo.cs new file mode 100644 index 0000000..278886a --- /dev/null +++ b/Console/TeslaConsole/PodInfo.cs @@ -0,0 +1,910 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net; +using System.ServiceProcess; +using System.Threading; +using Tesla.Net; + +namespace TeslaConsole; + +public class PodInfo : Component +{ + private delegate void ConnectWorkerEventHandler(AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); + + private delegate void LaunchAppWorkerEventHandler(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); + + private delegate void KillAppWorkerEventHandler(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); + + private delegate void ShutdownWorkerEventHandler(bool restart, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); + + private delegate void InstallProductWorkerEventHandler(string uncPath, SendOrPostCallback progressCallback, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); + + private delegate void UninstallProductWorkerEventHandler(AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); + + private delegate void ServiceWorkerEventHandler(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); + + private const int ManagePort = 53290; + + private static Dictionary mPods = new Dictionary(); + + private AsyncDelegates connectDelegates; + + private SendOrPostCallback onLaunchAppCompletedDelegate; + + private SendOrPostCallback launchAppCompletionMethodDelegate; + + private SendOrPostCallback onKillAppCompletedDelegate; + + private SendOrPostCallback killAppCompletionMethodDelegate; + + private AsyncDelegates installProductDelegates; + + private AsyncDelegates uninstallProductDelegates; + + private AsyncDelegates serviceGetDelegates; + + private AsyncDelegates serviceStartDelegates; + + private AsyncDelegates serviceStopDelegates; + + private Pod mPod; + + private IPodManagerConnection mConnection = new PodManagerConnection(); + + private object mConnectSyncRoot = new object(); + + private AsyncOperation mConnectAsyncOp; + + private Exception mLastConnectionException; + + private Cached mAllApps = new Cached(TimeSpan.FromMinutes(30.0)); + + private Cached> mRunningApps = new Cached>(TimeSpan.FromSeconds(60.0)); + + private float mVolume = float.NaN; + + private readonly List> rConnectActions = new List>(); + + private IContainer components; + + public Pod Pod => mPod; + + public bool IsConnecting => mConnectAsyncOp != null; + + public bool IsConnected + { + get + { + if (!IsConnecting && mConnection != null) + { + return mConnection.IsOpen; + } + return false; + } + } + + public Exception LastConnectionException => mLastConnectionException; + + public LaunchData[] AllApplications + { + get + { + if (mAllApps.IsExpired && mConnection.IsOpen) + { + try + { + mAllApps.Item = mConnection.GetInstalledApps(); + } + catch + { + mConnection.Close(); + mAllApps.Expire(); + return new LaunchData[0]; + } + } + return mAllApps.Item; + } + } + + public LaunchedAppData[] RunningApplications + { + get + { + if (!mConnection.IsOpen) + { + return new LaunchedAppData[0]; + } + if (mRunningApps.IsExpired) + { + try + { + lock (mRunningApps.SyncRoot) + { + mRunningApps.Item = new List(mConnection.GetLaunchedApps()); + } + } + catch + { + mConnection.Close(); + lock (mRunningApps.SyncRoot) + { + mRunningApps.Expire(); + } + return new LaunchedAppData[0]; + } + } + if (mRunningApps.Item == null) + { + return null; + } + return mRunningApps.Item.ToArray(); + } + } + + public float Volume + { + get + { + if (float.IsNaN(mVolume) && mConnection.IsOpen) + { + try + { + mVolume = mConnection.VolumeLevel; + } + catch + { + mConnection.Close(); + mVolume = float.NaN; + } + } + return mVolume; + } + set + { + if (mConnection.IsOpen) + { + mVolume = value; + try + { + mConnection.VolumeLevel = value; + } + catch + { + mConnection.Close(); + mVolume = float.NaN; + } + } + } + } + + public bool IsServiceRunning + { + get + { + try + { + ServiceController serviceController = new ServiceController("TeslaLauncherService", mPod.IPAddress.ToString()); + return serviceController.Status == ServiceControllerStatus.Running; + } + catch + { + return false; + } + } + } + + public event EventHandler ConnectCompleted; + + public event EventHandler LaunchApplicationCompleted; + + public event EventHandler KillApplicationCompleted; + + public event EventHandler InstallProductProgress; + + public event EventHandler InstallProductCompleted; + + public event EventHandler UninstallProductCompleted; + + public event EventHandler ServiceStatusGetCompleted; + + public event EventHandler ServiceStartCompleted; + + public event EventHandler ServiceStopCompleted; + + public PodInfo() + { + InitializeComponent(); + InitializeDelegates(); + } + + public PodInfo(IContainer container) + { + container.Add(this); + InitializeComponent(); + InitializeDelegates(); + } + + public PodInfo(Pod pod) + : this(pod, register: false) + { + } + + public PodInfo(Pod pod, bool register) + : this() + { + mPod = pod; + if (register) + { + RegisterPod(this); + } + } + + protected virtual void InitializeDelegates() + { + connectDelegates = new AsyncDelegates(ConnectCompletedInternal, ConnectCompletionMethod); + onLaunchAppCompletedDelegate = LaunchAppCompleted; + launchAppCompletionMethodDelegate = LaunchAppCompletionMethod; + onKillAppCompletedDelegate = KillAppCompleted; + killAppCompletionMethodDelegate = KillAppCompletionMethod; + installProductDelegates = new AsyncDelegates(InstallProductReportProgress, InstallProductInternalCompleted, InstallProductCompletionMethod); + uninstallProductDelegates = new AsyncDelegates(UninstallProductInternalCompleted, UninstallProductCompletionMethod); + serviceGetDelegates = new AsyncDelegates(ServiceStatusGetCompletedInternal, ServiceStatusGetCompletionMethod); + serviceStartDelegates = new AsyncDelegates(ServiceStartCompletedInternal, ServiceStartCompletionMethod); + serviceStopDelegates = new AsyncDelegates(ServiceStopCompletedInternal, ServiceStopCompletionMethod); + } + + public static void RegisterPod(PodInfo pod) + { + mPods[pod.Pod.ID] = pod; + } + + public static PodInfo GetPod(Guid id) + { + PodInfo value = null; + if (mPods.TryGetValue(id, out value)) + { + return value; + } + return null; + } + + public bool EnqueueConnectAction(Action action) + { + if (IsConnected) + { + action(this); + return true; + } + rConnectActions.Add(action); + return false; + } + + public void Disconnect() + { + if (mConnectAsyncOp != null) + { + CancelConnectAsync(); + } + else if (IsConnected) + { + mConnection.Close(); + } + mLastConnectionException = null; + mAllApps.Expire(); + mRunningApps.Expire(); + mVolume = float.NaN; + } + + public void Refresh() + { + if (!IsConnected) + { + throw new InvalidOperationException("Cannot refresh pod info while disconnected."); + } + try + { + FullUpdateData fullUpdateData = mConnection.FullUpdate(); + mAllApps.Item = fullUpdateData.InstalledApps; + lock (mRunningApps.SyncRoot) + { + mRunningApps.Item = new List(fullUpdateData.LaunchedApps); + } + mVolume = fullUpdateData.VolumeLevel; + } + catch + { + mConnection.Close(); + mAllApps.Expire(); + lock (mRunningApps.SyncRoot) + { + mRunningApps.Expire(); + } + mVolume = float.NaN; + } + } + + public bool IsAppRunning(Guid appId) + { + LaunchedAppData[] runningApplications = RunningApplications; + return Array.Exists(runningApplications, (LaunchedAppData app) => app.LaunchKey == appId); + } + + public void AddApp(LaunchData appData) + { + if (!IsConnected) + { + throw new InvalidOperationException("Application cannot be added while disconnected."); + } + try + { + mConnection.InstallApp(appData); + } + catch (Exception ex) + { + Program.LogException(ex, "Add Application Failed."); + mConnection.Close(); + } + } + + public void Shutdown(bool restart) + { + try + { + mConnection.Shutdown(restart); + } + catch (Exception ex) + { + Program.LogExceptionAndShowDialog(ex, "Remote Shutdown Failed.", "Remote Shutdown Failed."); + } + } + + public void ClearStore() + { + mConnection.ClearStore(); + } + + private void ConnectCompletedInternal(object operationState) + { + AsyncCompletedEventArgs e = operationState as AsyncCompletedEventArgs; + OnConnectCompleted(e); + } + + protected void OnConnectCompleted(AsyncCompletedEventArgs e) + { + if (IsConnected) + { + foreach (Action rConnectAction in rConnectActions) + { + rConnectAction(this); + } + rConnectActions.Clear(); + } + if (this.ConnectCompleted != null) + { + this.ConnectCompleted(this, e); + } + } + + private void ConnectCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + AsyncCompletedEventArgs asyncCompletedEventArgs = new AsyncCompletedEventArgs(asyncControlState.ex, cancelled: false, asyncControlState.asyncOp.UserSuppliedState); + mLastConnectionException = asyncCompletedEventArgs.Error; + lock (mConnectSyncRoot) + { + if (mConnectAsyncOp != null) + { + mConnectAsyncOp.PostOperationCompleted(connectDelegates.OnCompleted, asyncCompletedEventArgs); + mConnectAsyncOp = null; + } + } + } + + private void ConnectWorker(AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + mAllApps.Expire(); + mRunningApps.Expire(); + mVolume = float.NaN; + try + { + if (!mConnection.IsOpen) + { + if (mPod.IPAddress == IPAddress.None) + { + throw new ArgumentException("Pod IP address cannot be empty.", "IPAddress"); + } + if (mPod.Key == null || mPod.Key.Length == 0) + { + throw new ArgumentException("Pod encryption key cannot be empty.", "Key"); + } + mConnection.Open(new IPEndPoint(mPod.IPAddress, 53290), mPod.Key); + } + } + catch (Exception ex2) + { + ex = ex2; + } + AsyncControlState state = new AsyncControlState(ex, asyncOp); + completionMethodDelegate(state); + } + + public void ConnectAsync(object userState) + { + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + lock (mConnectSyncRoot) + { + if (mConnectAsyncOp != null) + { + throw new InvalidOperationException("Already connecting. Cannot connect again."); + } + mConnectAsyncOp = asyncOp; + } + ThreadPool.QueueUserWorkItem(delegate + { + ConnectWorker(asyncOp, connectDelegates.CompletionMethod); + }); + } + + public void CancelConnectAsync() + { + lock (mConnectSyncRoot) + { + if (mConnectAsyncOp != null) + { + AsyncCompletedEventArgs arg = new AsyncCompletedEventArgs(null, cancelled: true, mConnectAsyncOp.UserSuppliedState); + mConnectAsyncOp.PostOperationCompleted(connectDelegates.OnCompleted, arg); + mConnectAsyncOp = null; + } + } + } + + private void LaunchAppCompleted(object operationState) + { + ApplicationCompletedEventArgs e = operationState as ApplicationCompletedEventArgs; + OnLaunchApplicationCompleted(e); + } + + protected void OnLaunchApplicationCompleted(ApplicationCompletedEventArgs e) + { + if (this.LaunchApplicationCompleted != null) + { + this.LaunchApplicationCompleted(this, e); + } + } + + private void LaunchAppCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + ApplicationCompletedEventArgs arg = new ApplicationCompletedEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); + asyncControlState.asyncOp.PostOperationCompleted(onLaunchAppCompletedDelegate, arg); + } + + private void LaunchAppWorker(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + try + { + int processId = mConnection.LaunchApp(appId); + lock (mRunningApps.SyncRoot) + { + if (mRunningApps.Item == null) + { + mRunningApps.Item = new List(); + } + mRunningApps.Item.Add(new LaunchedAppData + { + ProcessId = processId, + LaunchKey = appId + }); + } + } + catch (Exception ex2) + { + ex = ex2; + } + completionMethodDelegate(new AsyncControlState(appId, ex, asyncOp)); + } + + public void LaunchApplicationAsync(Guid appId, object userState) + { + if (!IsConnected) + { + throw new InvalidOperationException("Cannot launch application while disconnected."); + } + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + ThreadPool.QueueUserWorkItem(delegate + { + LaunchAppWorker(appId, asyncOp, launchAppCompletionMethodDelegate); + }); + } + + private void KillAppCompleted(object operationState) + { + ApplicationCompletedEventArgs e = operationState as ApplicationCompletedEventArgs; + OnKillApplicationCompleted(e); + } + + protected void OnKillApplicationCompleted(ApplicationCompletedEventArgs e) + { + if (this.KillApplicationCompleted != null) + { + this.KillApplicationCompleted(this, e); + } + } + + private void KillAppCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + ApplicationCompletedEventArgs arg = new ApplicationCompletedEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); + asyncControlState.asyncOp.PostOperationCompleted(onKillAppCompletedDelegate, arg); + } + + private void KillAppWorker(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + try + { + mConnection.KillAllOfType(appId); + lock (mRunningApps.SyncRoot) + { + if (mRunningApps.Item != null) + { + for (int num = mRunningApps.Item.Count - 1; num >= 0; num--) + { + if (mRunningApps.Item[num].LaunchKey == appId) + { + mRunningApps.Item.RemoveAt(num); + } + } + } + } + } + catch (Exception ex2) + { + ex = ex2; + } + AsyncControlState state = new AsyncControlState(appId, ex, asyncOp); + completionMethodDelegate(state); + } + + public void KillApplicationAsync(Guid appId, object userState) + { + if (!IsConnected) + { + throw new InvalidOperationException("Cannot kill application while disconnected."); + } + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + ThreadPool.QueueUserWorkItem(delegate + { + KillAppWorker(appId, asyncOp, killAppCompletionMethodDelegate); + }); + } + + private void InstallProductInternalCompleted(object operationState) + { + InstallProductCompletedEventArgs e = operationState as InstallProductCompletedEventArgs; + OnInstallProductCompleted(e); + } + + private void InstallProductReportProgress(object state) + { + InstallProductProgressEventArgs e = state as InstallProductProgressEventArgs; + OnInstallProductProgress(e); + } + + protected void OnInstallProductCompleted(InstallProductCompletedEventArgs e) + { + if (this.InstallProductCompleted != null) + { + this.InstallProductCompleted(this, e); + } + } + + protected void OnInstallProductProgress(InstallProductProgressEventArgs e) + { + if (this.InstallProductProgress != null) + { + this.InstallProductProgress(this, e); + } + } + + private void InstallProductCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + InstallProductCompletedEventArgs arg = new InstallProductCompletedEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); + asyncControlState.asyncOp.PostOperationCompleted(installProductDelegates.OnCompleted, arg); + } + + private void InstallProductWorker(string filePath, SendOrPostCallback progressCallback, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + try + { + for (int i = 0; i < 3; i++) + { + if (!mConnection.IsOpen) + { + mConnection.Open(new IPEndPoint(mPod.IPAddress, 53290), mPod.Key); + } + Guid guid = mConnection.InstallProduct(filePath); + if (guid == Guid.Empty) + { + throw new Exception($"'{filePath}' is an invalid install file."); + } + OutOfBandProgress outOfBandProgress; + do + { + Thread.Sleep(250); + outOfBandProgress = mConnection.GetOutOfBandProgress(guid); + InstallProductProgressEventArgs arg = new InstallProductProgressEventArgs(outOfBandProgress.Status, outOfBandProgress.PercentComplete, asyncOp.UserSuppliedState); + asyncOp.Post(installProductDelegates.OnProgressReport, arg); + if (progressCallback != null) + { + asyncOp.Post(progressCallback, arg); + } + } + while (!outOfBandProgress.IsCompleted); + if (outOfBandProgress.PercentComplete == 99) + { + mAllApps.Expire(); + break; + } + } + } + catch (Exception ex2) + { + Program.LogException(ex2, "Installation Failed"); + ex = ex2; + } + AsyncControlState state = new AsyncControlState(filePath, ex, asyncOp); + completionMethodDelegate(state); + } + + public void InstallProductAsync(string uncPath, object userState) + { + InstallProductAsync(uncPath, userState, null); + } + + public void InstallProductAsync(string uncPath, object userState, Action progressCallback) + { + if (!IsConnected) + { + throw new InvalidOperationException("Cannot install application while disconnected."); + } + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + ThreadPool.QueueUserWorkItem(delegate + { + InstallProductWorker(uncPath, delegate(object e) + { + progressCallback((InstallProductProgressEventArgs)e); + }, asyncOp, installProductDelegates.CompletionMethod); + }); + } + + private void UninstallProductInternalCompleted(object operationState) + { + AsyncCompletedEventArgs e = operationState as AsyncCompletedEventArgs; + OnUninstallProductCompleted(e); + } + + protected void OnUninstallProductCompleted(AsyncCompletedEventArgs e) + { + if (this.UninstallProductCompleted != null) + { + this.UninstallProductCompleted(this, e); + } + } + + private void UninstallProductCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + AsyncCompletedEventArgs arg = new AsyncCompletedEventArgs(asyncControlState.ex, cancelled: false, asyncControlState.asyncOp.UserSuppliedState); + asyncControlState.asyncOp.PostOperationCompleted(uninstallProductDelegates.OnCompleted, arg); + } + + private void UninstallProductWorker(Guid launchKey, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + try + { + if (!mConnection.IsOpen) + { + mConnection.Open(new IPEndPoint(mPod.IPAddress, 53290), mPod.Key); + } + mConnection.UninstallApp(launchKey); + mAllApps.Expire(); + } + catch (Exception ex2) + { + Program.LogException(ex2, "Uninstallation Failed"); + ex = ex2; + } + AsyncControlState state = new AsyncControlState(ex, asyncOp); + completionMethodDelegate(state); + } + + public void UninstallProductAsync(Guid launchKey, object userState) + { + if (!IsConnected) + { + throw new InvalidOperationException("Cannot uninstall application while disconnected."); + } + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + ThreadPool.QueueUserWorkItem(delegate + { + UninstallProductWorker(launchKey, asyncOp, uninstallProductDelegates.CompletionMethod); + }); + } + + private void ServiceStatusGetCompletedInternal(object operationState) + { + ServiceStatusEventArgs e = operationState as ServiceStatusEventArgs; + OnServiceStatusGetCompleted(e); + } + + protected void OnServiceStatusGetCompleted(ServiceStatusEventArgs e) + { + if (this.ServiceStatusGetCompleted != null) + { + this.ServiceStatusGetCompleted(this, e); + } + } + + private void ServiceStatusGetCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + ServiceStatusEventArgs arg = new ServiceStatusEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); + asyncControlState.asyncOp.PostOperationCompleted(serviceGetDelegates.OnCompleted, arg); + } + + private void ServiceStatusWorker(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + ServiceControllerStatus state = ServiceControllerStatus.Stopped; + try + { + if (IPAddress.TryParse(machineName, out var address)) + { + IPHostEntry hostEntry = Dns.GetHostEntry(address); + machineName = hostEntry.HostName; + } + using ServiceController serviceController = new ServiceController("TeslaLauncherService", machineName); + state = serviceController.Status; + } + catch (Exception ex2) + { + ex = ex2; + } + AsyncControlState state2 = new AsyncControlState(state, ex, asyncOp); + completionMethodDelegate(state2); + } + + public void GetServiceStatusAsync(string machineName, object userState) + { + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + ThreadPool.QueueUserWorkItem(delegate + { + ServiceStatusWorker(machineName, asyncOp, serviceGetDelegates.CompletionMethod); + }); + } + + private void ServiceStartCompletedInternal(object operationState) + { + ServiceStatusEventArgs e = operationState as ServiceStatusEventArgs; + OnServiceStartCompleted(e); + } + + protected void OnServiceStartCompleted(ServiceStatusEventArgs e) + { + if (this.ServiceStartCompleted != null) + { + this.ServiceStartCompleted(this, e); + } + } + + private void ServiceStartCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + ServiceStatusEventArgs arg = new ServiceStatusEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); + asyncControlState.asyncOp.PostOperationCompleted(serviceGetDelegates.OnCompleted, arg); + } + + private void ServiceStartWorker(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + try + { + if (IPAddress.TryParse(machineName, out var address)) + { + IPHostEntry hostEntry = Dns.GetHostEntry(address); + machineName = hostEntry.HostName; + } + using ServiceController serviceController = new ServiceController("TeslaLauncherService", machineName); + serviceController.Start(); + serviceController.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30.0)); + } + catch (Exception ex2) + { + ex = ex2; + } + AsyncControlState state = new AsyncControlState(ex, asyncOp); + completionMethodDelegate(state); + } + + public void ServiceStartAsync(string machineName, object userState) + { + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + ThreadPool.QueueUserWorkItem(delegate + { + ServiceStartWorker(machineName, asyncOp, serviceStartDelegates.CompletionMethod); + }); + } + + private void ServiceStopCompletedInternal(object operationState) + { + ServiceStatusEventArgs e = operationState as ServiceStatusEventArgs; + OnServiceStopCompleted(e); + } + + protected void OnServiceStopCompleted(ServiceStatusEventArgs e) + { + if (this.ServiceStopCompleted != null) + { + this.ServiceStopCompleted(this, e); + } + } + + private void ServiceStopCompletionMethod(object completionState) + { + AsyncControlState asyncControlState = completionState as AsyncControlState; + ServiceStatusEventArgs arg = new ServiceStatusEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); + asyncControlState.asyncOp.PostOperationCompleted(serviceGetDelegates.OnCompleted, arg); + } + + private void ServiceStopWorker(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) + { + Exception ex = null; + try + { + if (IPAddress.TryParse(machineName, out var address)) + { + IPHostEntry hostEntry = Dns.GetHostEntry(address); + machineName = hostEntry.HostName; + } + using ServiceController serviceController = new ServiceController("TeslaLauncherService", machineName); + serviceController.Stop(); + serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30.0)); + } + catch (Exception ex2) + { + ex = ex2; + } + AsyncControlState state = new AsyncControlState(ex, asyncOp); + completionMethodDelegate(state); + } + + public void ServiceStopAsync(string machineName, object userState) + { + AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); + ThreadPool.QueueUserWorkItem(delegate + { + ServiceStopWorker(machineName, asyncOp, serviceStopDelegates.CompletionMethod); + }); + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + components = new Container(); + } +} diff --git a/Console/TeslaConsole/PodManager.cs b/Console/TeslaConsole/PodManager.cs new file mode 100644 index 0000000..986a3a3 --- /dev/null +++ b/Console/TeslaConsole/PodManager.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Tesla; + +namespace TeslaConsole; + +internal static class PodManager +{ + public delegate void PodReadyConfigHandler(string requestId, byte[] macAddress); + + private const int ConfigPort = 53291; + + private static PodConfigurationServer mServer; + + private static Dictionary mActivePods = new Dictionary(); + + public static List ActiveConfigRequests => new List(mActivePods.Keys); + + public static event PodReadyConfigHandler PodReadyForConfig; + + public static event PodReadyConfigHandler PodConfigured; + + public static void StartConfigurationServer() + { + mServer = new PodConfigurationServer(53291, 53292, PodConfigurationRequestHandler); + } + + private static void PodConfigurationRequestHandler(byte[] macAddress, string requestId) + { + lock (mActivePods) + { + if (mActivePods.ContainsKey(requestId)) + { + return; + } + mActivePods.Add(requestId, macAddress); + } + if (PodManager.PodReadyForConfig != null) + { + PodManager.PodReadyForConfig(requestId, macAddress); + } + } + + public static byte[] ConfigurePod(string requestId, string passphrase, Pod pod) + { + if (PodManager.PodConfigured != null) + { + PodManager.PodConfigured(requestId, pod.MacAddress); + } + byte[] result = null; + try + { + result = mServer.SendEncryptionKey(passphrase, pod.IPAddress, pod.Subnet, pod.Gateway, pod.DNS, pod.HostName, TimeSpan.FromSeconds(60.0)); + } + catch (TimeoutException) + { + } + mActivePods.Remove(requestId); + return result; + } + + public static byte[] GetMacAddressForRequest(string requestId) + { + if (mActivePods.ContainsKey(requestId)) + { + return mActivePods[requestId]; + } + return null; + } +} diff --git a/Console/TeslaConsole/PodState.cs b/Console/TeslaConsole/PodState.cs new file mode 100644 index 0000000..d2ccdd3 --- /dev/null +++ b/Console/TeslaConsole/PodState.cs @@ -0,0 +1,23 @@ +namespace TeslaConsole; + +public enum PodState +{ + Offline = -5, + Disconnected, + Connecting, + Critical, + Connected, + InitializingState, + WaitingForEgg, + LoadingMission, + WaitingForLaunch, + LaunchingMission, + RunningMission, + EndingMission, + StoppingMission, + SuspendingMission, + ResumingMission, + AbortingMission, + CreatingMission, + ApplicationStateCount +} diff --git a/Console/TeslaConsole/Program.cs b/Console/TeslaConsole/Program.cs new file mode 100644 index 0000000..6288286 --- /dev/null +++ b/Console/TeslaConsole/Program.cs @@ -0,0 +1,138 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.IO.IsolatedStorage; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace TeslaConsole; + +internal static class Program +{ + private static Mutex sInstanceMutex = null; + + private static readonly string sExceptionFile = Path.Combine(GetCommonAppDataDirectory(), "ExceptionLog.txt"); + + [STAThread] + private static void Main(string[] args) + { + if (args.Length >= 1 && args[0] == "/migrateIsoStore") + { + try + { + string path = Path.Combine(GetCommonAppDataDirectory(), "local.siteconfig"); + IsolatedStorageFile machineStoreForAssembly = IsolatedStorageFile.GetMachineStoreForAssembly(); + if (machineStoreForAssembly.GetFileNames("local.siteconfig").Length > 0) + { + using (IsolatedStorageFileStream isolatedStorageFileStream = new IsolatedStorageFileStream("local.siteconfig", FileMode.Open, FileAccess.Read, FileShare.Read, machineStoreForAssembly)) + { + using FileStream fileStream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read); + byte[] array = new byte[4096]; + int count; + while ((count = isolatedStorageFileStream.Read(array, 0, array.Length)) > 0) + { + fileStream.Write(array, 0, count); + } + isolatedStorageFileStream.Close(); + fileStream.Close(); + } + machineStoreForAssembly.DeleteFile("local.siteconfig"); + } + Environment.Exit(0); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + Environment.Exit(1); + } + } + sInstanceMutex = new Mutex(initiallyOwned: false, "TeslaConsole1Instance", out var createdNew); + if (!createdNew) + { + MessageBox.Show("Another instance of the Tesla Console is alread open.", "Tesla Console Open", MessageBoxButtons.OK); + return; + } + if (!Debugger.IsAttached) + { + Application.ThreadException += Application_ThreadException; + } + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(defaultValue: false); + Application.Run(new TeslaConsoleForm()); + } + + internal static string GetCommonAppDataDirectory() + { + string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tesla Console"); + if (!Directory.Exists(text)) + { + Directory.CreateDirectory(text); + } + return text; + } + + private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) + { + LogExceptionAndShowDialog(e.Exception, "An unhandled exception occurred, and the console must exit.", "Unhandled Exception"); + Environment.Exit(1); + } + + public static bool LogException(Exception ex, string generalMessage) + { + return LogString((generalMessage != null) ? generalMessage : "No general message available.", (ex != null) ? ex.ToString() : "The exception was null!"); + } + + public static bool LogMessage(string generalMessage) + { + return LogString(generalMessage, null); + } + + private static bool LogString(string generalString, string exceptionString) + { + try + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("**********"); + stringBuilder.AppendLine(DateTime.Now.ToString()); + if (!string.IsNullOrEmpty(generalString)) + { + stringBuilder.AppendLine(generalString); + } + if (!string.IsNullOrEmpty(exceptionString)) + { + stringBuilder.AppendLine(exceptionString); + } + lock (sExceptionFile) + { + string commonAppDataDirectory = GetCommonAppDataDirectory(); + if (!Directory.Exists(commonAppDataDirectory)) + { + Directory.CreateDirectory(commonAppDataDirectory); + } + File.AppendAllText(sExceptionFile, stringBuilder.ToString(), Encoding.UTF8); + } + return true; + } + catch (Exception) + { + return false; + } + } + + public static void LogExceptionAndShowDialog(Exception ex, string generalMessage, string messageBoxTitle) + { + if (LogException(ex, generalMessage)) + { + MessageBox.Show($"{generalMessage} The associated exception has been logged in \"{sExceptionFile}\". Please send this file to support@elsewhenstudios.com", messageBoxTitle, MessageBoxButtons.OK); + return; + } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine((generalMessage != null) ? generalMessage : "No general message available."); + stringBuilder.AppendLine(); + stringBuilder.AppendLine("The associated exception could not be logged. Please give the following information to support@elsewhenstudios.com"); + stringBuilder.AppendLine(); + stringBuilder.AppendLine((ex != null) ? ex.ToString() : "The exception was null!"); + MessageBox.Show(stringBuilder.ToString(), messageBoxTitle, MessageBoxButtons.OK); + } +} diff --git a/Console/TeslaConsole/RPStrings.cs b/Console/TeslaConsole/RPStrings.cs new file mode 100644 index 0000000..c6630d8 --- /dev/null +++ b/Console/TeslaConsole/RPStrings.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows.Forms; +using System.Xml; + +namespace TeslaConsole; + +internal class RPStrings +{ + private static bool sInitalized = false; + + private static List sFirstScore = new List(); + + private static List sFirstBoost = new List(); + + private static List sFirstDeath = new List(); + + private static List sFirstKill = new List(); + + private static List sShortestLap = new List(); + + private static List sLightDamage = new List(); + + private static List sModerateDamage = new List(); + + private static List sHeavyDamage = new List(); + + private static List sKill = new List(); + + private static List sSelfKill = new List(); + + private static List sScore = new List(); + + private static List sBoost = new List(); + + private static List sRecap = new List(); + + private static Random sRandom = new Random(); + + private static void Initalize() + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.Load(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "RedPlanet\\RPStrings.xml")); + if (xmlDocument.DocumentElement.Name != "RPStrings") + { + throw new XmlException("Document element was not an RPStrings node."); + } + foreach (XmlNode childNode in xmlDocument.DocumentElement.ChildNodes) + { + switch (childNode.Name) + { + case "FirstScore": + if (!sFirstScore.Contains(childNode.InnerText)) + { + sFirstScore.Add(childNode.InnerText); + } + break; + case "FirstBoost": + if (!sFirstBoost.Contains(childNode.InnerText)) + { + sFirstBoost.Add(childNode.InnerText); + } + break; + case "FirstDeath": + if (!sFirstDeath.Contains(childNode.InnerText)) + { + sFirstDeath.Add(childNode.InnerText); + } + break; + case "FirstKill": + if (!sFirstKill.Contains(childNode.InnerText)) + { + sFirstKill.Add(childNode.InnerText); + } + break; + case "ShortestLap": + if (!sShortestLap.Contains(childNode.InnerText)) + { + sShortestLap.Add(childNode.InnerText); + } + break; + case "LightDamage": + if (!sLightDamage.Contains(childNode.InnerText)) + { + sLightDamage.Add(childNode.InnerText); + } + break; + case "ModerateDamage": + if (!sModerateDamage.Contains(childNode.InnerText)) + { + sModerateDamage.Add(childNode.InnerText); + } + break; + case "HeavyDamage": + if (!sHeavyDamage.Contains(childNode.InnerText)) + { + sHeavyDamage.Add(childNode.InnerText); + } + break; + case "Kill": + if (!sKill.Contains(childNode.InnerText)) + { + sKill.Add(childNode.InnerText); + } + break; + case "SelfKill": + if (!sSelfKill.Contains(childNode.InnerText)) + { + sSelfKill.Add(childNode.InnerText); + } + break; + case "Score": + if (!sScore.Contains(childNode.InnerText)) + { + sScore.Add(childNode.InnerText); + } + break; + case "Boost": + if (!sBoost.Contains(childNode.InnerText)) + { + sBoost.Add(childNode.InnerText); + } + break; + case "Recap": + if (!sRecap.Contains(childNode.InnerText)) + { + sRecap.Add(childNode.InnerText); + } + break; + } + } + sInitalized = true; + } + + private static string GetRandomStringAndReplace(List list, TimeSpan time, string player, string vehicle, string damager) + { + if (!sInitalized) + { + Initalize(); + } + if (list.Count == 0) + { + return null; + } + return list[sRandom.Next(list.Count)].Replace("{time}", GetTimeString(time)).Replace("{player}", player).Replace("{vehicle}", vehicle) + .Replace("{damager}", damager); + } + + public static string GetTimeString(TimeSpan time) + { + int num = (int)(time.TotalSeconds + 0.5); + int num2 = num / 60; + return $"{num2:D2}:{num % 60:D2}"; + } + + public static string FirstScore(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sFirstScore, time, player, vehicle, damager); + } + + public static string FirstBoost(TimeSpan time, string player, string vehicle) + { + return GetRandomStringAndReplace(sFirstBoost, time, player, vehicle, ""); + } + + public static string FirstDeath(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sFirstDeath, time, player, vehicle, damager); + } + + public static string FirstKill(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sFirstKill, time, player, vehicle, damager); + } + + public static string ShortestLap(TimeSpan time, TimeSpan lapTime, string player, string vehicle) + { + return GetRandomStringAndReplace(sShortestLap, time, player, vehicle, "").Replace("{lap_time}", GetTimeString(lapTime)); + } + + public static string LightDamage(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sLightDamage, time, player, vehicle, damager); + } + + public static string ModerateDamage(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sModerateDamage, time, player, vehicle, damager); + } + + public static string HeavyDamage(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sHeavyDamage, time, player, vehicle, damager); + } + + public static string Kill(TimeSpan time, string player, string vehicle, string damager) + { + return GetRandomStringAndReplace(sKill, time, player, vehicle, damager); + } + + public static string SelfKill(TimeSpan time, string player, string vehicle) + { + return GetRandomStringAndReplace(sSelfKill, time, player, vehicle, ""); + } + + public static string Score(TimeSpan time, string player, string vehicle) + { + return GetRandomStringAndReplace(sScore, time, player, vehicle, ""); + } + + public static string Boost(TimeSpan time, string player, string vehicle) + { + return GetRandomStringAndReplace(sBoost, time, player, vehicle, ""); + } + + public static string Recap(string[] playerRanking) + { + StringBuilder stringBuilder = new StringBuilder(playerRanking[0]); + for (int i = 1; i < playerRanking.Length - 1; i++) + { + stringBuilder.AppendFormat(", {0}", playerRanking[i]); + } + return GetRandomStringAndReplace(sRecap, TimeSpan.Zero, "", "", "").Replace("{winners}", stringBuilder.ToString()).Replace("{looser}", (playerRanking.Length > 1) ? playerRanking[playerRanking.Length - 1] : "nobody"); + } +} diff --git a/Console/TeslaConsole/ServiceStatusEventArgs.cs b/Console/TeslaConsole/ServiceStatusEventArgs.cs new file mode 100644 index 0000000..540a255 --- /dev/null +++ b/Console/TeslaConsole/ServiceStatusEventArgs.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel; +using System.ServiceProcess; + +namespace TeslaConsole; + +public class ServiceStatusEventArgs : AsyncCompletedEventArgs +{ + private ServiceControllerStatus mStatus; + + public ServiceControllerStatus Status + { + get + { + RaiseExceptionIfNecessary(); + return mStatus; + } + } + + public ServiceStatusEventArgs(ServiceControllerStatus status, Exception e, bool canceled, object state) + : base(e, canceled, state) + { + mStatus = status; + } +} diff --git a/Console/TeslaConsole/SetupPod.cs b/Console/TeslaConsole/SetupPod.cs new file mode 100644 index 0000000..dd799ca --- /dev/null +++ b/Console/TeslaConsole/SetupPod.cs @@ -0,0 +1,676 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; +using System.Windows.Forms; + +namespace TeslaConsole; + +internal class SetupPod : Form +{ + private Pod mPod; + + private Pod[] mKnownPods; + + private IContainer components; + + private Label lblMacAddress; + + private TextBox txtMacAddress; + + private TextBox txtIPAddress; + + private Label lblIPAddress; + + private GroupBox gbNetwork; + + private TextBox txtSubnet; + + private Label lblSubnet; + + private TextBox txtDNS; + + private Label lblDNS; + + private Label lblGateway; + + private TextBox txtGateway; + + private TextBox txtPodId; + + private Label lblPodId; + + private TextBox txtPodName; + + private Label lblPodName; + + private Label lblPodType; + + private ComboBox cmbPodType; + + private Button cmdCancel; + + private Button cmdOK; + + private ErrorProvider errProvider; + + private TextBox txtPassphrase; + + private Label lblPassphrase; + + private ComboBox cmbSquad; + + private Label lblSquad; + + private Label lblHostName; + + private TextBox txtHostName; + + private TableLayoutPanel tblMain; + + private TableLayoutPanel tblButtons; + + private TableLayoutPanel tblNetwork; + + public Pod Pod + { + get + { + mPod.Name = txtPodName.Text; + mPod.HostType = ((HostTypeHelper)cmbPodType.SelectedItem).HostType; + mPod.IPAddress = IPAddress.Parse(txtIPAddress.Text); + mPod.Subnet = IPAddress.Parse(txtSubnet.Text); + if (string.IsNullOrEmpty(txtGateway.Text)) + { + mPod.Gateway = IPAddress.Any; + } + else + { + mPod.Gateway = IPAddress.Parse(txtGateway.Text); + } + if (string.IsNullOrEmpty(txtDNS.Text)) + { + mPod.DNS = IPAddress.Any; + } + else + { + mPod.DNS = IPAddress.Parse(txtDNS.Text); + } + mPod.HostName = txtHostName.Text; + return mPod; + } + private set + { + mPod = value; + txtPodName.Text = mPod.Name; + cmbPodType.SelectedItem = (HostTypeHelper)mPod.HostType; + txtPodId.Text = mPod.ID.ToString(); + txtMacAddress.Text = MacAddressToString(mPod.MacAddress); + txtIPAddress.Text = (mPod.IPAddress.Equals(IPAddress.Any) ? "" : mPod.IPAddress.ToString()); + txtSubnet.Text = (mPod.IPAddress.Equals(IPAddress.Any) ? "" : mPod.Subnet.ToString()); + txtGateway.Text = ((mPod.Gateway == IPAddress.Any) ? "" : mPod.Gateway.ToString()); + txtDNS.Text = ((mPod.DNS == IPAddress.Any) ? "" : mPod.DNS.ToString()); + txtHostName.Text = mPod.HostName; + } + } + + public Squad Squad => (Squad)cmbSquad.SelectedItem; + + public string Passphrase => txtPassphrase.Text; + + internal SetupPod(string requestId, Pod pod, List squads) + { + InitializeComponent(); + cmbPodType.Items.Add((HostTypeHelper)HostType.GameMachineHostType); + cmbPodType.Items.Add((HostTypeHelper)HostType.MissionReviewHostType); + Text = "Configure Pod - " + requestId; + Pod = pod; + cmbSquad.Items.Add(new Squad(Guid.Empty, "")); + List list = new List(); + foreach (Squad squad in squads) + { + cmbSquad.Items.Add(squad); + foreach (Pod pod2 in squad.Pods) + { + list.Add(pod2); + } + } + cmbSquad.SelectedIndex = ((cmbSquad.Items.Count > 1) ? 1 : 0); + mKnownPods = list.ToArray(); + } + + private string MacAddressToString(byte[] macAddress) + { + if (macAddress.Length < 1) + { + return string.Empty; + } + StringBuilder stringBuilder = new StringBuilder(macAddress.Length * 3 - 1); + stringBuilder.Append(macAddress[0].ToString("X2")); + for (int i = 1; i < macAddress.Length; i++) + { + stringBuilder.Append('-'); + stringBuilder.Append(macAddress[i].ToString("X2")); + } + return stringBuilder.ToString(); + } + + private void txtPassphrase_TextChanged(object sender, EventArgs e) + { + int selectionStart = txtPassphrase.SelectionStart; + int selectionLength = txtPassphrase.SelectionLength; + txtPassphrase.Text = txtPassphrase.Text.ToUpper(); + txtPassphrase.Select(selectionStart, selectionLength); + } + + private void txtPassphrase_Validating(object sender, CancelEventArgs e) + { + errProvider.SetError(txtPassphrase, ""); + if (txtPassphrase.Enabled && txtPassphrase.Text.Length != 5) + { + errProvider.SetError(txtPassphrase, "The passphrase is invalid."); + e.Cancel = true; + } + } + + private void txtPodName_Validating(object sender, CancelEventArgs e) + { + errProvider.SetError(txtPodName, ""); + if (string.IsNullOrEmpty(txtPodName.Text)) + { + errProvider.SetError(txtPodName, "Pod must be given a name."); + e.Cancel = true; + } + } + + private bool CheckIpTextBox(TextBox textbox, bool required, CancelEventArgs e, out IPAddress address) + { + if (string.IsNullOrEmpty(textbox.Text)) + { + if (required) + { + errProvider.SetError(textbox, "You must enter a valid address."); + e.Cancel = true; + } + else + { + errProvider.SetError(textbox, ""); + } + address = null; + return false; + } + if (!IPAddress.TryParse(textbox.Text, out address) || address.ToString() != textbox.Text || address.Equals(IPAddress.Any) || address.Equals(IPAddress.Broadcast)) + { + errProvider.SetError(textbox, "You must enter a valid address."); + e.Cancel = true; + address = null; + return false; + } + errProvider.SetError(textbox, ""); + return true; + } + + private void ValidateRequiredIP(object sender, CancelEventArgs e) + { + CheckIpTextBox((TextBox)sender, required: true, e, out var _); + } + + private void ValidateOptionalIP(object sender, CancelEventArgs e) + { + CheckIpTextBox((TextBox)sender, required: false, e, out var _); + } + + private void txtIPAddress_Validating(object sender, CancelEventArgs e) + { + if (!CheckIpTextBox((TextBox)sender, required: true, e, out var address)) + { + return; + } + Pod[] array = mKnownPods; + foreach (Pod pod in array) + { + if (pod.IPAddress.Equals(address)) + { + errProvider.SetError((TextBox)sender, "The specified address is already in use."); + e.Cancel = true; + break; + } + } + } + + private void txtHostName_Validating(object sender, CancelEventArgs e) + { + errProvider.SetError(txtHostName, ""); + if (string.IsNullOrEmpty(txtHostName.Text)) + { + errProvider.SetError(txtHostName, "Pod must be given a host name."); + e.Cancel = true; + return; + } + if (txtHostName.Text.Length > 63) + { + errProvider.SetError(txtHostName, "The host name may not be longer than 63 characters."); + e.Cancel = true; + return; + } + string text = txtHostName.Text; + foreach (char c in text) + { + if ((c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '-' && c != '_') + { + errProvider.SetError(txtHostName, "The host name may only contain ASCII letters and number or the dash and underscore characters."); + e.Cancel = true; + return; + } + } + Pod[] array = mKnownPods; + foreach (Pod pod in array) + { + if (pod.HostName.Equals(txtHostName.Text, StringComparison.OrdinalIgnoreCase)) + { + errProvider.SetError((TextBox)sender, "The specified host name is already in use."); + e.Cancel = true; + break; + } + } + } + + private void cmdOK_Click(object sender, EventArgs e) + { + if (!ValidateChildren()) + { + return; + } + IPAddress iPAddress = IPAddress.Parse(txtIPAddress.Text); + IPAddress obj = IPAddress.Parse(txtSubnet.Text); + if (iPAddress.AddressFamily == AddressFamily.InterNetwork) + { + uint num = BitConverter.ToUInt32(iPAddress.GetAddressBytes(), 0); + bool flag = false; + bool flag2 = false; + try + { + NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface networkInterface in allNetworkInterfaces) + { + try + { + foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses) + { + try + { + if (unicastAddress.Address.AddressFamily != AddressFamily.InterNetwork || unicastAddress.IPv4Mask == null) + { + continue; + } + uint num2 = BitConverter.ToUInt32(unicastAddress.Address.GetAddressBytes(), 0); + uint num3 = BitConverter.ToUInt32(unicastAddress.IPv4Mask.GetAddressBytes(), 0); + if ((num2 & num3) == (num & num3)) + { + flag = true; + if (unicastAddress.IPv4Mask.Equals(obj)) + { + flag2 = true; + } + } + } + catch (Exception) + { + } + } + } + catch (Exception) + { + } + } + } + catch (Exception) + { + } + if ((!flag && MessageBox.Show("The specified IP Address does not appear to be on any network directly attached to this console. Are you sure it is correct?", "IP Address Not Local", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) || (!flag2 && MessageBox.Show("The specified Subnet Mask does not match the subnet mask specified for this console. Are you sure it is correct?", "Subnet Mask Mismatch", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)) + { + return; + } + } + base.DialogResult = DialogResult.OK; + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.lblMacAddress = new System.Windows.Forms.Label(); + this.txtMacAddress = new System.Windows.Forms.TextBox(); + this.txtIPAddress = new System.Windows.Forms.TextBox(); + this.lblIPAddress = new System.Windows.Forms.Label(); + this.gbNetwork = new System.Windows.Forms.GroupBox(); + this.lblHostName = new System.Windows.Forms.Label(); + this.txtHostName = new System.Windows.Forms.TextBox(); + this.txtGateway = new System.Windows.Forms.TextBox(); + this.txtDNS = new System.Windows.Forms.TextBox(); + this.lblDNS = new System.Windows.Forms.Label(); + this.lblGateway = new System.Windows.Forms.Label(); + this.txtSubnet = new System.Windows.Forms.TextBox(); + this.lblSubnet = new System.Windows.Forms.Label(); + this.txtPodId = new System.Windows.Forms.TextBox(); + this.lblPodId = new System.Windows.Forms.Label(); + this.txtPodName = new System.Windows.Forms.TextBox(); + this.lblPodName = new System.Windows.Forms.Label(); + this.lblPodType = new System.Windows.Forms.Label(); + this.cmbPodType = new System.Windows.Forms.ComboBox(); + this.cmdCancel = new System.Windows.Forms.Button(); + this.cmdOK = new System.Windows.Forms.Button(); + this.errProvider = new System.Windows.Forms.ErrorProvider(this.components); + this.txtPassphrase = new System.Windows.Forms.TextBox(); + this.lblPassphrase = new System.Windows.Forms.Label(); + this.cmbSquad = new System.Windows.Forms.ComboBox(); + this.lblSquad = new System.Windows.Forms.Label(); + this.tblMain = new System.Windows.Forms.TableLayoutPanel(); + this.tblButtons = new System.Windows.Forms.TableLayoutPanel(); + this.tblNetwork = new System.Windows.Forms.TableLayoutPanel(); + this.gbNetwork.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)this.errProvider).BeginInit(); + this.tblMain.SuspendLayout(); + this.tblButtons.SuspendLayout(); + this.tblNetwork.SuspendLayout(); + base.SuspendLayout(); + this.lblMacAddress.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblMacAddress.AutoSize = true; + this.lblMacAddress.Location = new System.Drawing.Point(18, 6); + this.lblMacAddress.Name = "lblMacAddress"; + this.lblMacAddress.Size = new System.Drawing.Size(74, 13); + this.lblMacAddress.TabIndex = 0; + this.lblMacAddress.Text = "MAC Address:"; + this.txtMacAddress.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtMacAddress.Location = new System.Drawing.Point(98, 3); + this.txtMacAddress.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtMacAddress.Name = "txtMacAddress"; + this.txtMacAddress.ReadOnly = true; + this.txtMacAddress.Size = new System.Drawing.Size(154, 20); + this.txtMacAddress.TabIndex = 1; + this.txtMacAddress.TabStop = false; + this.txtIPAddress.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtIPAddress.Location = new System.Drawing.Point(98, 29); + this.txtIPAddress.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtIPAddress.Name = "txtIPAddress"; + this.txtIPAddress.Size = new System.Drawing.Size(154, 20); + this.txtIPAddress.TabIndex = 3; + this.txtIPAddress.Validating += new System.ComponentModel.CancelEventHandler(txtIPAddress_Validating); + this.lblIPAddress.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblIPAddress.AutoSize = true; + this.lblIPAddress.Location = new System.Drawing.Point(31, 32); + this.lblIPAddress.Name = "lblIPAddress"; + this.lblIPAddress.Size = new System.Drawing.Size(61, 13); + this.lblIPAddress.TabIndex = 2; + this.lblIPAddress.Text = "IP Address:"; + this.gbNetwork.AutoSize = true; + this.tblMain.SetColumnSpan(this.gbNetwork, 2); + this.gbNetwork.Controls.Add(this.tblNetwork); + this.gbNetwork.Dock = System.Windows.Forms.DockStyle.Fill; + this.gbNetwork.Location = new System.Drawing.Point(3, 135); + this.gbNetwork.Name = "gbNetwork"; + this.gbNetwork.Size = new System.Drawing.Size(288, 175); + this.gbNetwork.TabIndex = 11; + this.gbNetwork.TabStop = false; + this.gbNetwork.Text = "Network Setup"; + this.lblHostName.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblHostName.AutoSize = true; + this.lblHostName.Location = new System.Drawing.Point(29, 136); + this.lblHostName.Name = "lblHostName"; + this.lblHostName.Size = new System.Drawing.Size(63, 13); + this.lblHostName.TabIndex = 11; + this.lblHostName.Text = "Host Name:"; + this.txtHostName.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtHostName.Location = new System.Drawing.Point(98, 133); + this.txtHostName.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtHostName.Name = "txtHostName"; + this.txtHostName.Size = new System.Drawing.Size(154, 20); + this.txtHostName.TabIndex = 10; + this.txtHostName.Validating += new System.ComponentModel.CancelEventHandler(txtHostName_Validating); + this.txtGateway.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtGateway.Location = new System.Drawing.Point(98, 81); + this.txtGateway.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtGateway.Name = "txtGateway"; + this.txtGateway.Size = new System.Drawing.Size(154, 20); + this.txtGateway.TabIndex = 7; + this.txtGateway.Validating += new System.ComponentModel.CancelEventHandler(ValidateOptionalIP); + this.txtDNS.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtDNS.Location = new System.Drawing.Point(98, 107); + this.txtDNS.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtDNS.Name = "txtDNS"; + this.txtDNS.Size = new System.Drawing.Size(154, 20); + this.txtDNS.TabIndex = 9; + this.txtDNS.Validating += new System.ComponentModel.CancelEventHandler(ValidateOptionalIP); + this.lblDNS.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblDNS.AutoSize = true; + this.lblDNS.Location = new System.Drawing.Point(25, 110); + this.lblDNS.Name = "lblDNS"; + this.lblDNS.Size = new System.Drawing.Size(67, 13); + this.lblDNS.TabIndex = 8; + this.lblDNS.Text = "DNS Server:"; + this.lblGateway.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblGateway.AutoSize = true; + this.lblGateway.Location = new System.Drawing.Point(3, 84); + this.lblGateway.Name = "lblGateway"; + this.lblGateway.Size = new System.Drawing.Size(89, 13); + this.lblGateway.TabIndex = 6; + this.lblGateway.Text = "Default Gateway:"; + this.txtSubnet.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtSubnet.Location = new System.Drawing.Point(98, 55); + this.txtSubnet.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtSubnet.Name = "txtSubnet"; + this.txtSubnet.Size = new System.Drawing.Size(154, 20); + this.txtSubnet.TabIndex = 5; + this.txtSubnet.Validating += new System.ComponentModel.CancelEventHandler(ValidateRequiredIP); + this.lblSubnet.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblSubnet.AutoSize = true; + this.lblSubnet.Location = new System.Drawing.Point(19, 58); + this.lblSubnet.Name = "lblSubnet"; + this.lblSubnet.Size = new System.Drawing.Size(73, 13); + this.lblSubnet.TabIndex = 4; + this.lblSubnet.Text = "Subnet Mask:"; + this.txtPodId.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtPodId.Location = new System.Drawing.Point(74, 3); + this.txtPodId.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtPodId.Name = "txtPodId"; + this.txtPodId.ReadOnly = true; + this.txtPodId.Size = new System.Drawing.Size(190, 20); + this.txtPodId.TabIndex = 1; + this.txtPodId.TabStop = false; + this.txtPodId.Text = "{1AD57794-BD4B-4593-B1FC-B7C0F5DF360D}"; + this.lblPodId.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblPodId.AutoSize = true; + this.lblPodId.Location = new System.Drawing.Point(25, 6); + this.lblPodId.Name = "lblPodId"; + this.lblPodId.Size = new System.Drawing.Size(43, 13); + this.lblPodId.TabIndex = 0; + this.lblPodId.Text = "Pod ID:"; + this.txtPodName.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtPodName.Location = new System.Drawing.Point(74, 29); + this.txtPodName.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtPodName.Name = "txtPodName"; + this.txtPodName.Size = new System.Drawing.Size(190, 20); + this.txtPodName.TabIndex = 3; + this.txtPodName.Validating += new System.ComponentModel.CancelEventHandler(txtPodName_Validating); + this.lblPodName.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblPodName.AutoSize = true; + this.lblPodName.Location = new System.Drawing.Point(30, 32); + this.lblPodName.Name = "lblPodName"; + this.lblPodName.Size = new System.Drawing.Size(38, 13); + this.lblPodName.TabIndex = 2; + this.lblPodName.Text = "Name:"; + this.lblPodType.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblPodType.AutoSize = true; + this.lblPodType.Location = new System.Drawing.Point(34, 59); + this.lblPodType.Name = "lblPodType"; + this.lblPodType.Size = new System.Drawing.Size(34, 13); + this.lblPodType.TabIndex = 4; + this.lblPodType.Text = "Type:"; + this.cmbPodType.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.cmbPodType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbPodType.FormattingEnabled = true; + this.cmbPodType.Location = new System.Drawing.Point(74, 55); + this.cmbPodType.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.cmbPodType.Name = "cmbPodType"; + this.cmbPodType.Size = new System.Drawing.Size(190, 21); + this.cmbPodType.TabIndex = 5; + this.cmdCancel.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.cmdCancel.CausesValidation = false; + this.cmdCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cmdCancel.Location = new System.Drawing.Point(216, 3); + this.cmdCancel.Name = "cmdCancel"; + this.cmdCancel.Size = new System.Drawing.Size(75, 23); + this.cmdCancel.TabIndex = 13; + this.cmdCancel.Text = "Cancel"; + this.cmdCancel.UseVisualStyleBackColor = true; + this.cmdOK.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.cmdOK.Location = new System.Drawing.Point(135, 3); + this.cmdOK.Name = "cmdOK"; + this.cmdOK.Size = new System.Drawing.Size(75, 23); + this.cmdOK.TabIndex = 12; + this.cmdOK.Text = "OK"; + this.cmdOK.UseVisualStyleBackColor = true; + this.cmdOK.Click += new System.EventHandler(cmdOK_Click); + this.errProvider.BlinkStyle = System.Windows.Forms.ErrorBlinkStyle.NeverBlink; + this.errProvider.ContainerControl = this; + this.txtPassphrase.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtPassphrase.Location = new System.Drawing.Point(74, 109); + this.txtPassphrase.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.txtPassphrase.MaxLength = 5; + this.txtPassphrase.Name = "txtPassphrase"; + this.txtPassphrase.Size = new System.Drawing.Size(190, 20); + this.txtPassphrase.TabIndex = 9; + this.txtPassphrase.TextChanged += new System.EventHandler(txtPassphrase_TextChanged); + this.txtPassphrase.Validating += new System.ComponentModel.CancelEventHandler(txtPassphrase_Validating); + this.lblPassphrase.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblPassphrase.AutoSize = true; + this.lblPassphrase.Location = new System.Drawing.Point(3, 112); + this.lblPassphrase.Name = "lblPassphrase"; + this.lblPassphrase.Size = new System.Drawing.Size(65, 13); + this.lblPassphrase.TabIndex = 8; + this.lblPassphrase.Text = "Passphrase:"; + this.cmbSquad.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.cmbSquad.DisplayMember = "Name"; + this.cmbSquad.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbSquad.FormattingEnabled = true; + this.cmbSquad.Location = new System.Drawing.Point(74, 82); + this.cmbSquad.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3); + this.cmbSquad.Name = "cmbSquad"; + this.cmbSquad.Size = new System.Drawing.Size(190, 21); + this.cmbSquad.TabIndex = 7; + this.lblSquad.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.lblSquad.AutoSize = true; + this.lblSquad.Location = new System.Drawing.Point(27, 86); + this.lblSquad.Name = "lblSquad"; + this.lblSquad.Size = new System.Drawing.Size(41, 13); + this.lblSquad.TabIndex = 6; + this.lblSquad.Text = "Squad:"; + this.tblMain.ColumnCount = 2; + this.tblMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tblMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.tblMain.Controls.Add(this.lblPodId, 0, 0); + this.tblMain.Controls.Add(this.txtPodId, 1, 0); + this.tblMain.Controls.Add(this.lblPodName, 0, 1); + this.tblMain.Controls.Add(this.txtPodName, 1, 1); + this.tblMain.Controls.Add(this.lblPodType, 0, 2); + this.tblMain.Controls.Add(this.cmbPodType, 1, 2); + this.tblMain.Controls.Add(this.lblSquad, 0, 3); + this.tblMain.Controls.Add(this.cmbSquad, 1, 3); + this.tblMain.Controls.Add(this.lblPassphrase, 0, 4); + this.tblMain.Controls.Add(this.txtPassphrase, 1, 4); + this.tblMain.Controls.Add(this.gbNetwork, 0, 5); + this.tblMain.Controls.Add(this.tblButtons, 0, 7); + this.tblMain.Dock = System.Windows.Forms.DockStyle.Fill; + this.tblMain.Location = new System.Drawing.Point(0, 0); + this.tblMain.Name = "tblMain"; + this.tblMain.RowCount = 8; + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.tblMain.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblMain.Size = new System.Drawing.Size(294, 347); + this.tblMain.TabIndex = 14; + this.tblButtons.AutoSize = true; + this.tblButtons.ColumnCount = 2; + this.tblMain.SetColumnSpan(this.tblButtons, 2); + this.tblButtons.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.tblButtons.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tblButtons.Controls.Add(this.cmdOK, 0, 0); + this.tblButtons.Controls.Add(this.cmdCancel, 1, 0); + this.tblButtons.Dock = System.Windows.Forms.DockStyle.Fill; + this.tblButtons.Location = new System.Drawing.Point(0, 318); + this.tblButtons.Margin = new System.Windows.Forms.Padding(0); + this.tblButtons.Name = "tblButtons"; + this.tblButtons.RowCount = 1; + this.tblButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.tblButtons.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20f)); + this.tblButtons.Size = new System.Drawing.Size(294, 29); + this.tblButtons.TabIndex = 12; + this.tblNetwork.AutoSize = true; + this.tblNetwork.ColumnCount = 2; + this.tblNetwork.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tblNetwork.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100f)); + this.tblNetwork.Controls.Add(this.lblMacAddress, 0, 0); + this.tblNetwork.Controls.Add(this.txtMacAddress, 1, 0); + this.tblNetwork.Controls.Add(this.lblIPAddress, 0, 1); + this.tblNetwork.Controls.Add(this.txtIPAddress, 1, 1); + this.tblNetwork.Controls.Add(this.lblSubnet, 0, 2); + this.tblNetwork.Controls.Add(this.txtSubnet, 1, 2); + this.tblNetwork.Controls.Add(this.lblGateway, 0, 3); + this.tblNetwork.Controls.Add(this.txtGateway, 1, 3); + this.tblNetwork.Controls.Add(this.lblDNS, 0, 4); + this.tblNetwork.Controls.Add(this.txtDNS, 1, 4); + this.tblNetwork.Controls.Add(this.lblHostName, 0, 5); + this.tblNetwork.Controls.Add(this.txtHostName, 1, 5); + this.tblNetwork.Dock = System.Windows.Forms.DockStyle.Fill; + this.tblNetwork.Location = new System.Drawing.Point(3, 16); + this.tblNetwork.Margin = new System.Windows.Forms.Padding(0); + this.tblNetwork.Name = "tblNetwork"; + this.tblNetwork.RowCount = 6; + this.tblNetwork.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblNetwork.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblNetwork.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblNetwork.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblNetwork.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblNetwork.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tblNetwork.Size = new System.Drawing.Size(282, 156); + this.tblNetwork.TabIndex = 12; + base.AcceptButton = this.cmdOK; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoValidate = System.Windows.Forms.AutoValidate.EnableAllowFocusChange; + base.CancelButton = this.cmdCancel; + base.ClientSize = new System.Drawing.Size(294, 347); + base.Controls.Add(this.tblMain); + base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + base.Name = "SetupPod"; + base.ShowInTaskbar = false; + base.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Configure Pod"; + this.gbNetwork.ResumeLayout(false); + this.gbNetwork.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)this.errProvider).EndInit(); + this.tblMain.ResumeLayout(false); + this.tblMain.PerformLayout(); + this.tblButtons.ResumeLayout(false); + this.tblNetwork.ResumeLayout(false); + this.tblNetwork.PerformLayout(); + base.ResumeLayout(false); + } +} diff --git a/Console/TeslaConsole/ShutdownCompletedEventArgs.cs b/Console/TeslaConsole/ShutdownCompletedEventArgs.cs new file mode 100644 index 0000000..d9aed43 --- /dev/null +++ b/Console/TeslaConsole/ShutdownCompletedEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; + +namespace TeslaConsole; + +public class ShutdownCompletedEventArgs : AsyncCompletedEventArgs +{ + private bool mSuccessful; + + public bool WasSuccessful + { + get + { + RaiseExceptionIfNecessary(); + return mSuccessful; + } + } + + public ShutdownCompletedEventArgs(bool wasSuccessful, Exception e, bool canceled, object state) + : base(e, canceled, state) + { + mSuccessful = wasSuccessful; + } +} diff --git a/Console/TeslaConsole/Site.cs b/Console/TeslaConsole/Site.cs new file mode 100644 index 0000000..915432e --- /dev/null +++ b/Console/TeslaConsole/Site.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +namespace TeslaConsole; + +internal class Site +{ + private static Site mActiveConfig; + + private List mSquads = new List(); + + public static Site Active + { + get + { + if (mActiveConfig == null) + { + LoadFromStore(); + } + return mActiveConfig; + } + } + + public List Squads => mSquads; + + public static Site LoadFromStore() + { + mActiveConfig = new Site(); + mActiveConfig.Load(); + return mActiveConfig; + } + + public static Site LoadFromFile(string filename) + { + mActiveConfig = new Site(); + mActiveConfig.LoadFrom(filename); + return mActiveConfig; + } + + protected void Load() + { + BinaryFormatter binaryFormatter = new BinaryFormatter(); + string path = Path.Combine(Program.GetCommonAppDataDirectory(), "local.siteconfig"); + if (!File.Exists(path)) + { + return; + } + using FileStream input = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); + BinaryReader binaryReader = new BinaryReader(input); + int num = binaryReader.ReadInt32(); + for (int i = 0; i < num; i++) + { + Squad squad = (Squad)binaryFormatter.Deserialize(binaryReader.BaseStream); + mSquads.Add(squad); + int num2 = binaryReader.ReadInt32(); + for (int j = 0; j < num2; j++) + { + squad.Pods.Add((Pod)binaryFormatter.Deserialize(binaryReader.BaseStream)); + } + } + binaryReader.Close(); + } + + protected void LoadFrom(string filename) + { + BinaryReader binaryReader = new BinaryReader(File.OpenRead(filename)); + BinaryFormatter binaryFormatter = new BinaryFormatter(); + int num = binaryReader.ReadInt32(); + for (int i = 0; i < num; i++) + { + Squad squad = (Squad)binaryFormatter.Deserialize(binaryReader.BaseStream); + mSquads.Add(squad); + int num2 = binaryReader.ReadInt32(); + for (int j = 0; j < num2; j++) + { + squad.Pods.Add((Pod)binaryFormatter.Deserialize(binaryReader.BaseStream)); + } + } + binaryReader.Close(); + } + + public void Save() + { + BinaryFormatter binaryFormatter = new BinaryFormatter(); + string path = Path.Combine(Program.GetCommonAppDataDirectory(), "local.siteconfig"); + using (FileStream output = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) + { + BinaryWriter binaryWriter = new BinaryWriter(output); + binaryWriter.Write(mSquads.Count); + foreach (Squad mSquad in mSquads) + { + binaryFormatter.Serialize(binaryWriter.BaseStream, mSquad); + binaryWriter.Write(mSquad.Pods.Count); + foreach (Pod pod in mSquad.Pods) + { + binaryFormatter.Serialize(binaryWriter.BaseStream, pod); + } + } + binaryWriter.Close(); + } + mActiveConfig = this; + } + + public void SaveAs(string filename) + { + BinaryWriter binaryWriter = new BinaryWriter(File.OpenWrite(filename)); + BinaryFormatter binaryFormatter = new BinaryFormatter(); + binaryWriter.Write(mSquads.Count); + foreach (Squad mSquad in mSquads) + { + binaryFormatter.Serialize(binaryWriter.BaseStream, mSquad); + binaryWriter.Write(mSquad.Pods.Count); + foreach (Pod pod in mSquad.Pods) + { + binaryFormatter.Serialize(binaryWriter.BaseStream, pod); + } + } + binaryWriter.Close(); + } + + public Pod FindPod(byte[] macAddress) + { + foreach (Squad mSquad in mSquads) + { + foreach (Pod pod in mSquad.Pods) + { + if (pod.MacAddressEquals(macAddress)) + { + return pod; + } + } + } + return null; + } + + public Squad FindSquad(Guid id) + { + foreach (Squad mSquad in mSquads) + { + if (mSquad.Guid == id) + { + return mSquad; + } + } + return null; + } +} diff --git a/Console/TeslaConsole/SiteManagement.cs b/Console/TeslaConsole/SiteManagement.cs new file mode 100644 index 0000000..3033ef0 --- /dev/null +++ b/Console/TeslaConsole/SiteManagement.cs @@ -0,0 +1,1428 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Net; +using System.Net.NetworkInformation; +using System.Threading; +using System.Windows.Forms; +using Microsoft.VisualBasic; +using Tesla.Net; +using TeslaConsole.Properties; + +namespace TeslaConsole; + +public class SiteManagement : Form +{ + private class PodData + { + public Pod Pod; + + public PodInfo Conn; + + public string Squad; + + public PingReply PingReply; + + public int pings; + + public Size? Install_CustomResolution; + + public List Install_HostTypes = new List(); + + public bool OperationInProgress; + + public int PercentProgress = -1; + + public string Status = ""; + + public Exception Error; + + private SiteManagement Parent; + + private Thread PingThread; + + private bool Running = true; + + public PodData(Pod pod, string squad, SiteManagement parent) + { + Pod = pod; + Squad = squad; + if (pod.IsLocal) + { + Conn = new PodInfo(pod); + Conn.ConnectCompleted += parent.PodInfo_ConnectCompleted; + Conn.InstallProductCompleted += parent.PodInfo_InstallProductCompleted; + Conn.UninstallProductCompleted += parent.PodInfo_UninstallProductCompleted; + } + Parent = parent; + PingThread = new Thread(Ping); + PingThread.Name = pod.Name + " - Ping Thread"; + PingThread.IsBackground = true; + } + + public void StartPing() + { + PingThread.Start(); + } + + public void StopPing() + { + Running = false; + } + + private void Ping() + { + Ping ping = new Ping(); + while (Running) + { + if (!OperationInProgress) + { + PingReply = ping.Send(Pod.IPAddress, 5000); + pings++; + Parent.Pod_PingCompleted(this); + } + Thread.Sleep(1000); + } + } + } + + private enum PodInstallType + { + GameClient, + LiveCamera, + MissionReview + } + + private IContainer components; + + private DataGridView dgvPods; + + private ContextMenuStrip cmnuDataGrid; + + private ToolStripMenuItem installProductToolStripMenuItem; + + private ToolStripMenuItem registerProductToolStripMenuItem; + + private ToolStripMenuItem importSiteConfigurationToolStripMenuItem; + + private ToolStripMenuItem exportSiteConfigurationToolStripMenuItem; + + private OpenFileDialog dlgOpenFile; + + private SaveFileDialog dlgSaveFile; + + private OpenFileDialog dlgSelectPackage; + + private FlowLayoutPanel flpPodsToConfigure; + + private ToolStripMenuItem deletePodToolStripMenuItem; + + private ToolStripMenuItem resendPodConfigurationToolStripMenuItem; + + private DataGridViewCheckBoxColumn colIsLocal; + + private DataGridViewTextBoxColumn colPodName; + + private DataGridViewComboBoxColumn colSquad; + + private DataGridViewComboBoxColumn colType; + + private DataGridViewTextBoxColumn colMacAddress; + + private DataGridViewTextBoxColumn colIPAddress; + + private DataGridViewTextBoxColumn colSubnet; + + private DataGridViewTextBoxColumn colGateway; + + private DataGridViewTextBoxColumn colDNSServer; + + private DataGridViewTextBoxColumn colHostName; + + private DataGridViewTextBoxColumn colOperationProgress; + + private ToolStripMenuItem uninstallProductToolStripMenuItem; + + public static readonly Guid RPAppGuid = new Guid("7D241B1F-AB6D-4e08-9C20-12294E743D94"); + + public static readonly Guid RPMRAppGuid = new Guid("8F71D5C2-38E4-413c-8E22-88CAD08774D2"); + + public static readonly Guid RPLCAppGuid = new Guid("57A0B3C2-D5CF-46d6-ABED-A8F4A26AB086"); + + public static readonly Guid MW4AppGuid = new Guid("CC8500ED-A653-45a7-BEF8-C332D30371A6"); + + public static readonly Guid MW4LCAppGuid = new Guid("8EE93A6C-F16A-49be-B867-37FAE9087FFF"); + + private Site mSite; + + private List mPods = new List(); + + private PodData mPodInEdit; + + private int mRowInEdit = -1; + + private Dictionary mReconfiguringPods = new Dictionary(); + + private Dictionary mConfigureButtons = new Dictionary(); + + private Dictionary mConfigureLabels = new Dictionary(); + + private static bool mIsOpen = false; + + public static bool IsOpen => mIsOpen; + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle = new System.Windows.Forms.DataGridViewCellStyle(); + this.dgvPods = new System.Windows.Forms.DataGridView(); + this.colIsLocal = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.colPodName = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colSquad = new System.Windows.Forms.DataGridViewComboBoxColumn(); + this.colType = new System.Windows.Forms.DataGridViewComboBoxColumn(); + this.colMacAddress = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colIPAddress = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colSubnet = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colGateway = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colDNSServer = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colHostName = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colOperationProgress = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.cmnuDataGrid = new System.Windows.Forms.ContextMenuStrip(this.components); + this.deletePodToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.installProductToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.uninstallProductToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.resendPodConfigurationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.importSiteConfigurationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportSiteConfigurationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.dlgOpenFile = new System.Windows.Forms.OpenFileDialog(); + this.dlgSaveFile = new System.Windows.Forms.SaveFileDialog(); + this.dlgSelectPackage = new System.Windows.Forms.OpenFileDialog(); + this.flpPodsToConfigure = new System.Windows.Forms.FlowLayoutPanel(); + ((System.ComponentModel.ISupportInitialize)this.dgvPods).BeginInit(); + this.cmnuDataGrid.SuspendLayout(); + base.SuspendLayout(); + this.dgvPods.AllowUserToResizeRows = false; + dataGridViewCellStyle.BackColor = System.Drawing.Color.FromArgb(225, 225, 255); + this.dgvPods.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle; + this.dgvPods.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.dgvPods.BackgroundColor = System.Drawing.Color.White; + this.dgvPods.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.dgvPods.ClipboardCopyMode = System.Windows.Forms.DataGridViewClipboardCopyMode.Disable; + this.dgvPods.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgvPods.Columns.AddRange(this.colIsLocal, this.colPodName, this.colSquad, this.colType, this.colMacAddress, this.colIPAddress, this.colSubnet, this.colGateway, this.colDNSServer, this.colHostName, this.colOperationProgress); + this.dgvPods.ContextMenuStrip = this.cmnuDataGrid; + this.dgvPods.Location = new System.Drawing.Point(12, 12); + this.dgvPods.Name = "dgvPods"; + this.dgvPods.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dgvPods.Size = new System.Drawing.Size(1106, 395); + this.dgvPods.TabIndex = 0; + this.dgvPods.VirtualMode = true; + this.dgvPods.UserDeletingRow += new System.Windows.Forms.DataGridViewRowCancelEventHandler(dgvPods_UserDeletingRow); + this.dgvPods.CellMouseClick += new System.Windows.Forms.DataGridViewCellMouseEventHandler(dgvPods_CellMouseClick); + this.dgvPods.CancelRowEdit += new System.Windows.Forms.QuestionEventHandler(dgvPods_CancelRowEdit); + this.dgvPods.CellValueNeeded += new System.Windows.Forms.DataGridViewCellValueEventHandler(dgvPods_CellValueNeeded); + this.dgvPods.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(dgvPods_CellFormatting); + this.dgvPods.CellValidating += new System.Windows.Forms.DataGridViewCellValidatingEventHandler(dgvPods_CellValidating); + this.dgvPods.RowValidated += new System.Windows.Forms.DataGridViewCellEventHandler(dgvPods_RowValidated); + this.dgvPods.CellPainting += new System.Windows.Forms.DataGridViewCellPaintingEventHandler(dgvPods_CellPainting); + this.dgvPods.CellValuePushed += new System.Windows.Forms.DataGridViewCellValueEventHandler(dgvPods_CellValuePushed); + this.dgvPods.NewRowNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(dgvPods_NewRowNeeded); + this.colIsLocal.HeaderText = "Local"; + this.colIsLocal.MinimumWidth = 40; + this.colIsLocal.Name = "colIsLocal"; + this.colIsLocal.ReadOnly = true; + this.colIsLocal.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.colIsLocal.Width = 40; + this.colPodName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colPodName.HeaderText = "Pod"; + this.colPodName.MinimumWidth = 100; + this.colPodName.Name = "colPodName"; + this.colSquad.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colSquad.HeaderText = "Squad"; + this.colSquad.MinimumWidth = 75; + this.colSquad.Name = "colSquad"; + this.colSquad.Width = 75; + this.colType.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colType.HeaderText = "Type"; + this.colType.MinimumWidth = 100; + this.colType.Name = "colType"; + this.colMacAddress.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colMacAddress.HeaderText = "MAC Address"; + this.colMacAddress.MinimumWidth = 75; + this.colMacAddress.Name = "colMacAddress"; + this.colMacAddress.ReadOnly = true; + this.colMacAddress.Width = 88; + this.colIPAddress.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colIPAddress.HeaderText = "IP Address"; + this.colIPAddress.MinimumWidth = 75; + this.colIPAddress.Name = "colIPAddress"; + this.colIPAddress.ReadOnly = true; + this.colIPAddress.Width = 77; + this.colSubnet.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colSubnet.HeaderText = "Subnet Mask"; + this.colSubnet.MinimumWidth = 75; + this.colSubnet.Name = "colSubnet"; + this.colSubnet.ReadOnly = true; + this.colSubnet.Width = 88; + this.colGateway.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colGateway.HeaderText = "Default Gateway"; + this.colGateway.MinimumWidth = 75; + this.colGateway.Name = "colGateway"; + this.colGateway.ReadOnly = true; + this.colGateway.Width = 102; + this.colDNSServer.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colDNSServer.HeaderText = "DNS Server"; + this.colDNSServer.MinimumWidth = 75; + this.colDNSServer.Name = "colDNSServer"; + this.colDNSServer.ReadOnly = true; + this.colDNSServer.Width = 82; + this.colHostName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells; + this.colHostName.HeaderText = "Host Name"; + this.colHostName.MinimumWidth = 75; + this.colHostName.Name = "colHostName"; + this.colHostName.ReadOnly = true; + this.colHostName.Width = 79; + this.colOperationProgress.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.colOperationProgress.HeaderText = "Operation Progress"; + this.colOperationProgress.MinimumWidth = 150; + this.colOperationProgress.Name = "colOperationProgress"; + this.colOperationProgress.ReadOnly = true; + this.colOperationProgress.Resizable = System.Windows.Forms.DataGridViewTriState.True; + this.colOperationProgress.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + this.cmnuDataGrid.Items.AddRange(new System.Windows.Forms.ToolStripItem[6] { this.deletePodToolStripMenuItem, this.installProductToolStripMenuItem, this.uninstallProductToolStripMenuItem, this.resendPodConfigurationToolStripMenuItem, this.importSiteConfigurationToolStripMenuItem, this.exportSiteConfigurationToolStripMenuItem }); + this.cmnuDataGrid.Name = "cmnuDataGrid"; + this.cmnuDataGrid.Size = new System.Drawing.Size(219, 136); + this.cmnuDataGrid.Opening += new System.ComponentModel.CancelEventHandler(cmnuDataGrid_Opening); + this.deletePodToolStripMenuItem.Image = TeslaConsole.Properties.Resources.DeleteHS; + this.deletePodToolStripMenuItem.Name = "deletePodToolStripMenuItem"; + this.deletePodToolStripMenuItem.Size = new System.Drawing.Size(218, 22); + this.deletePodToolStripMenuItem.Text = "Delete"; + this.deletePodToolStripMenuItem.Click += new System.EventHandler(deletePodToolStripMenuItem_Click); + this.installProductToolStripMenuItem.Image = TeslaConsole.Properties.Resources.install; + this.installProductToolStripMenuItem.Name = "installProductToolStripMenuItem"; + this.installProductToolStripMenuItem.Size = new System.Drawing.Size(218, 22); + this.installProductToolStripMenuItem.Text = "Install Product"; + // Install Product children are built dynamically from AppRegistry (see BuildInstallProductMenu). + this.uninstallProductToolStripMenuItem.Name = "uninstallProductToolStripMenuItem"; + this.uninstallProductToolStripMenuItem.Size = new System.Drawing.Size(218, 22); + this.uninstallProductToolStripMenuItem.Text = "Uninstall Product"; + this.resendPodConfigurationToolStripMenuItem.Name = "resendPodConfigurationToolStripMenuItem"; + this.resendPodConfigurationToolStripMenuItem.Size = new System.Drawing.Size(218, 22); + this.resendPodConfigurationToolStripMenuItem.Text = "Reconfigure..."; + this.resendPodConfigurationToolStripMenuItem.Visible = false; + this.resendPodConfigurationToolStripMenuItem.Click += new System.EventHandler(resendPodConfigurationToolStripMenuItem_Click); + this.importSiteConfigurationToolStripMenuItem.Image = TeslaConsole.Properties.Resources.openHS; + this.importSiteConfigurationToolStripMenuItem.Name = "importSiteConfigurationToolStripMenuItem"; + this.importSiteConfigurationToolStripMenuItem.Size = new System.Drawing.Size(218, 22); + this.importSiteConfigurationToolStripMenuItem.Text = "Import Site Configuration..."; + this.importSiteConfigurationToolStripMenuItem.Click += new System.EventHandler(importSiteConfigurationToolStripMenuItem_Click); + this.exportSiteConfigurationToolStripMenuItem.Image = TeslaConsole.Properties.Resources.saveHS; + this.exportSiteConfigurationToolStripMenuItem.Name = "exportSiteConfigurationToolStripMenuItem"; + this.exportSiteConfigurationToolStripMenuItem.Size = new System.Drawing.Size(218, 22); + this.exportSiteConfigurationToolStripMenuItem.Text = "Export Site Configuration..."; + this.exportSiteConfigurationToolStripMenuItem.Click += new System.EventHandler(exportSiteConfigurationToolStripMenuItem_Click); + this.dlgOpenFile.DefaultExt = "siteconfig"; + this.dlgOpenFile.Filter = "Site Configuration Files (*.siteconfig)|*.siteconfig|All Files (*.*)|*.*"; + this.dlgOpenFile.Title = "Import Configuration"; + this.dlgSaveFile.DefaultExt = "siteconfig"; + this.dlgSaveFile.Filter = "Site Configuration Files (*.siteconfig)|*.siteconfig|All Files (*.*)|*.*"; + this.dlgSaveFile.Title = "Export Configuration"; + this.dlgSelectPackage.Filter = "VWE Install Archive (*.zip)|*.zip|All Files (*.*)|*.*"; + this.dlgSelectPackage.Title = "Select Package to Install"; + this.flpPodsToConfigure.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.flpPodsToConfigure.Location = new System.Drawing.Point(12, 413); + this.flpPodsToConfigure.Name = "flpPodsToConfigure"; + this.flpPodsToConfigure.Size = new System.Drawing.Size(1106, 27); + this.flpPodsToConfigure.TabIndex = 1; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(1130, 452); + base.Controls.Add(this.flpPodsToConfigure); + base.Controls.Add(this.dgvPods); + base.Name = "SiteManagement"; + base.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Site Management"; + base.Load += new System.EventHandler(SiteManagement_Load); + base.FormClosing += new System.Windows.Forms.FormClosingEventHandler(SiteManagement_FormClosing); + ((System.ComponentModel.ISupportInitialize)this.dgvPods).EndInit(); + this.cmnuDataGrid.ResumeLayout(false); + base.ResumeLayout(false); + } + + public SiteManagement() + { + InitializeComponent(); + LoadConfiguration(TeslaConsole.Site.Active); + BuildInstallProductMenu(); + foreach (string activeConfigRequest in PodManager.ActiveConfigRequests) + { + PodManager_PodReadyForConfig(activeConfigRequest, PodManager.GetMacAddressForRequest(activeConfigRequest)); + } + PodManager.PodReadyForConfig += PodManager_PodReadyForConfig; + PodManager.PodConfigured += PodManager_PodConfigured; + } + + protected override void OnClosing(CancelEventArgs e) + { + if (dgvPods.IsCurrentCellInEditMode) + { + MessageBox.Show("You can not close this window while a cell is being edited.", "Currently Editing", MessageBoxButtons.OK); + e.Cancel = true; + } + base.OnClosing(e); + } + + private void PodManager_PodReadyForConfig(string requestId, byte[] macAddress) + { + if (base.InvokeRequired) + { + Invoke(new PodManager.PodReadyConfigHandler(PodManager_PodReadyForConfig), requestId, macAddress); + return; + } + PodData podData = null; + foreach (PodData mPod in mPods) + { + if (mPod.Pod.MacAddressEquals(macAddress)) + { + podData = mPod; + break; + } + } + Button button = new Button(); + button.Image = Resources.PodOffline16; + if (podData == null) + { + button.Text = "Configure " + requestId; + button.Tag = new Tuple(requestId, macAddress); + } + else + { + button.Text = "Configure " + podData.Pod.Name; + button.Tag = new Tuple(requestId, podData); + } + button.ImageAlign = ContentAlignment.MiddleLeft; + button.TextAlign = ContentAlignment.MiddleLeft; + button.TextImageRelation = TextImageRelation.ImageBeforeText; + button.AutoSize = true; + button.AutoSizeMode = AutoSizeMode.GrowAndShrink; + button.Click += ConfigureButton_Click; + flpPodsToConfigure.Controls.Add(button); + } + + private void PodManager_PodConfigured(string requestId, byte[] macAddress) + { + if (mConfigureButtons.ContainsKey(requestId)) + { + Button value = mConfigureButtons[requestId]; + mConfigureButtons.Remove(requestId); + flpPodsToConfigure.Controls.Remove(value); + } + } + + private void ConfigureButton_Click(object sender, EventArgs e) + { + Button button = sender as Button; + if (button.Tag is Tuple) + { + Tuple tuple = button.Tag as Tuple; + string text = Interaction.InputBox("Enter passphrase for " + tuple.B.Pod.Name, "Configure " + tuple.B.Pod.Name); + if (!string.IsNullOrEmpty(text)) + { + Label label = new Label(); + label.Text = "Configuring " + tuple.B.Pod.Name + "..."; + label.AutoSize = false; + label.Size = new Size((int)((double)button.Width * 1.5), button.Height); + label.BorderStyle = BorderStyle.FixedSingle; + label.BackColor = Color.SkyBlue; + label.TextAlign = ContentAlignment.MiddleLeft; + flpPodsToConfigure.Controls.Remove(button); + mConfigureButtons.Remove(tuple.A); + flpPodsToConfigure.Controls.Add(label); + mConfigureLabels.Add(tuple.B.Pod.ID, label); + ConfigurePod(tuple.A, text.ToUpper(), tuple.B.Pod, tuple.B.Squad); + } + } + else + { + if (!(button.Tag is Tuple)) + { + return; + } + Tuple tuple2 = button.Tag as Tuple; + Pod pod = new Pod(); + pod.MacAddress = tuple2.B; + SetupPod setupPod; + string text2; + while (true) + { + setupPod = new SetupPod(tuple2.A, pod, TeslaConsole.Site.Active.Squads); + if (setupPod.ShowDialog(this) == DialogResult.OK) + { + Label label2 = new Label(); + label2.Text = "Configuring " + pod.Name + "..."; + label2.AutoSize = false; + label2.Size = new Size((int)((double)button.Width * 1.5), button.Height); + label2.BorderStyle = BorderStyle.FixedSingle; + label2.BackColor = Color.SlateBlue; + flpPodsToConfigure.Controls.Remove(button); + mConfigureButtons.Remove(tuple2.A); + flpPodsToConfigure.Controls.Add(label2); + mConfigureLabels.Add(pod.ID, label2); + text2 = setupPod.Squad.Name; + if (!(setupPod.Squad.Guid == Guid.Empty)) + { + break; + } + text2 = Interaction.InputBox("Enter the name of the new squad:", "New Squad"); + if (!string.IsNullOrEmpty(text2)) + { + Squad squad = new Squad(setupPod.Squad.Name); + TeslaConsole.Site.Active.Squads.Add(squad); + colSquad.Items.Add(squad.Name); + break; + } + } + } + ConfigurePod(tuple2.A, setupPod.Passphrase, setupPod.Pod, text2); + } + } + + private void ConfigurePod(string requestId, string passphrase, Pod pod, string squadName) + { + BackgroundWorker backgroundWorker = new BackgroundWorker(); + backgroundWorker.DoWork += ConfigureWorker_DoWork; + backgroundWorker.RunWorkerCompleted += ConfigureWorker_RunWorkerCompleted; + backgroundWorker.RunWorkerAsync(new Tuple(requestId, passphrase, pod, squadName)); + } + + private void ConfigureWorker_DoWork(object sender, DoWorkEventArgs e) + { + Tuple tuple = (Tuple)e.Argument; + tuple.C.Key = PodManager.ConfigurePod(tuple.A, tuple.B, tuple.C); + if (tuple.C.Key == null) + { + e.Result = new PodConfigurationException("Passphrase was incorrect.", tuple.C); + } + else + { + e.Result = new Tuple(tuple.C, tuple.D); + } + } + + private void ConfigureWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (e.Result is PodConfigurationException) + { + PodConfigurationException ex = e.Result as PodConfigurationException; + MessageBox.Show(this, "There was an error configuring " + ex.Pod.Name + ":" + Environment.NewLine + ex.Message, "Configure " + ex.Pod.Name, MessageBoxButtons.OK, MessageBoxIcon.Hand); + Label value = mConfigureLabels[ex.Pod.ID]; + flpPodsToConfigure.Controls.Remove(value); + mConfigureLabels.Remove(ex.Pod.ID); + } + else + { + if (!(e.Result is Tuple)) + { + return; + } + Tuple tuple = e.Result as Tuple; + MessageBox.Show(this, tuple.A.Name + " successfully configured!", "Configure " + tuple.A.Name, MessageBoxButtons.OK, MessageBoxIcon.Asterisk); + Label value2 = mConfigureLabels[tuple.A.ID]; + flpPodsToConfigure.Controls.Remove(value2); + mConfigureLabels.Remove(tuple.A.ID); + foreach (PodData mPod in mPods) + { + if (mPod.Pod.ID == tuple.A.ID) + { + return; + } + } + PodData podData = new PodData(tuple.A, tuple.B, this); + podData.StartPing(); + mPods.Add(podData); + List list = new List(mPods); + dgvPods.Rows.Clear(); + mPods.Clear(); + mPods = list; + dgvPods.RowCount = mPods.Count + 1; + } + } + + private void SiteManagement_Load(object sender, EventArgs e) + { + mIsOpen = true; + } + + private void SiteManagement_FormClosing(object sender, FormClosingEventArgs e) + { + foreach (PodData mPod in mPods) + { + if (e.CloseReason == CloseReason.UserClosing && mPod.OperationInProgress && mPod.Status.StartsWith("Installing")) + { + MessageBox.Show(this, "Site Management cannot be closed while installations are in progress.", "Site Management", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + e.Cancel = true; + return; + } + } + PodManager.PodReadyForConfig -= PodManager_PodReadyForConfig; + foreach (Squad squad in mSite.Squads) + { + squad.Pods.Clear(); + } + foreach (PodData mPod2 in mPods) + { + mPod2.StopPing(); + foreach (Squad squad2 in mSite.Squads) + { + if (squad2.Name == mPod2.Squad) + { + squad2.Pods.Add(mPod2.Pod); + } + } + } + mSite.Save(); + mIsOpen = false; + } + + private void dgvPods_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) + { + if (colOperationProgress.Index != e.ColumnIndex || e.RowIndex < 0 || e.RowIndex >= mPods.Count || (string)e.FormattedValue != "draw_progress") + { + return; + } + e.Handled = true; + e.PaintBackground(e.ClipBounds, cellsPaintSelectionBackground: false); + PodData podData = mPods[e.RowIndex]; + Rectangle rectangle = Rectangle.Inflate(e.CellBounds, -3, -3); + Rectangle rectangle2 = new Rectangle(rectangle.Location, rectangle.Size); + rectangle2.Inflate(-1, -1); + if (podData.PercentProgress >= 0) + { + rectangle2.Width = (int)Math.Round((double)podData.PercentProgress / 100.0 * (double)rectangle2.Width); + if (ProgressBarRenderer.IsSupported) + { + ProgressBarRenderer.DrawHorizontalBar(e.Graphics, rectangle); + ProgressBarRenderer.DrawHorizontalChunks(e.Graphics, rectangle2); + } + else + { + e.Graphics.FillRectangle(Brushes.LightGray, rectangle); + e.Graphics.FillRectangle(Brushes.Green, rectangle2); + e.Graphics.DrawRectangle(Pens.Black, rectangle); + } + } + StringFormat stringFormat = new StringFormat(StringFormatFlags.NoWrap); + stringFormat.Alignment = StringAlignment.Near; + stringFormat.LineAlignment = StringAlignment.Center; + e.Graphics.DrawString(podData.Status, Font, Brushes.Black, rectangle, stringFormat); + } + + private void dgvPods_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) + { + if (e.RowIndex < 0) + { + return; + } + if (colMacAddress.Index == e.ColumnIndex) + { + if (e.Value is byte[] array) + { + string[] array2 = new string[array.Length]; + for (int i = 0; i < array2.Length; i++) + { + array2[i] = array[i].ToString("x2"); + } + e.Value = string.Join("-", array2); + e.FormattingApplied = true; + } + } + else + { + if (colOperationProgress.Index != e.ColumnIndex) + { + return; + } + if (e.RowIndex < mPods.Count && mPods[e.RowIndex].Pod.IsLocal) + { + PodData podData = mPods[e.RowIndex]; + if (podData.Error != null) + { + e.Value = "Install Failed"; + e.CellStyle.BackColor = Color.Red; + e.CellStyle.ForeColor = Color.White; + } + else if (!podData.OperationInProgress) + { + if (podData.PingReply == null) + { + e.Value = "Idle. [pinging...]"; + } + else if (podData.PingReply.Status == IPStatus.Success) + { + if (!podData.Conn.IsConnected) + { + e.Value = "Could not connect to pod service."; + } + else + { + e.Value = $"Idle [{podData.PingReply.RoundtripTime} ms]"; + } + } + else + { + e.Value = $"Pod unreachable. Ping result: {podData.PingReply.Status}"; + } + } + else + { + e.Value = "draw_progress"; + } + } + else + { + e.Value = ""; + } + e.FormattingApplied = true; + } + } + + private void dgvPods_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) + { + if (e.ColumnIndex < 0 || e.RowIndex < 0 || e.RowIndex >= dgvPods.Rows.Count) + { + return; + } + DataGridViewRow dataGridViewRow = dgvPods.Rows[e.RowIndex]; + if (e.ColumnIndex >= dataGridViewRow.Cells.Count) + { + return; + } + DataGridViewCell dataGridViewCell = dataGridViewRow.Cells[e.ColumnIndex]; + if (e.ColumnIndex == colPodName.Index) + { + if (!dgvPods.Rows[e.RowIndex].IsNewRow && (e.FormattedValue == null || (string)e.FormattedValue == "")) + { + dataGridViewCell.ErrorText = "Pod name cannot be blank"; + } + else + { + dataGridViewCell.ErrorText = ""; + } + } + } + + private void dgvPods_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e) + { + if (e.Button == MouseButtons.Right && e.RowIndex >= 0 && e.RowIndex < dgvPods.Rows.Count && e.RowIndex != dgvPods.NewRowIndex) + { + if (dgvPods.SelectedRows.Count > 0) + { + dgvPods.SelectedRows[0].Selected = false; + } + dgvPods.Rows[e.RowIndex].Selected = true; + int num = ((e.ColumnIndex >= 0) ? dgvPods.GetColumnDisplayRectangle(e.ColumnIndex, cutOverflow: true).X : (num = e.Location.X)); + int num2 = dgvPods.GetRowDisplayRectangle(e.RowIndex, cutOverflow: true).Y; + cmnuDataGrid.Show(dgvPods, num + e.X, num2 + e.Y); + } + } + + private void dgvPods_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) + { + if (e.RowIndex != dgvPods.Rows.Count - 1) + { + PodData podData = ((e.RowIndex != mRowInEdit) ? mPods[e.RowIndex] : mPodInEdit); + if (e.ColumnIndex == colIsLocal.Index) + { + e.Value = podData.Pod.IsLocal; + } + else if (e.ColumnIndex == colPodName.Index) + { + e.Value = podData.Pod.Name; + } + else if (e.ColumnIndex == colSquad.Index) + { + e.Value = podData.Squad; + } + else if (e.ColumnIndex == colType.Index) + { + e.Value = (HostTypeHelper)podData.Pod.HostType; + } + else if (e.ColumnIndex == colMacAddress.Index) + { + e.Value = podData.Pod.MacAddress; + } + else if (e.ColumnIndex == colIPAddress.Index) + { + e.Value = podData.Pod.IPAddress; + dgvPods.Rows[e.RowIndex].Cells[e.ColumnIndex].ReadOnly = podData.Pod.IsLocal; + } + else if (e.ColumnIndex == colSubnet.Index) + { + e.Value = podData.Pod.Subnet; + } + else if (e.ColumnIndex == colGateway.Index) + { + e.Value = podData.Pod.Gateway; + } + else if (e.ColumnIndex == colDNSServer.Index) + { + e.Value = podData.Pod.DNS; + } + else if (e.ColumnIndex == colHostName.Index) + { + e.Value = podData.Pod.HostName; + } + } + } + + private void dgvPods_CellValuePushed(object sender, DataGridViewCellValueEventArgs e) + { + PodData podData = null; + if (e.RowIndex < mPods.Count) + { + if (mPodInEdit == null) + { + mPodInEdit = new PodData(mPods[e.RowIndex].Pod.Clone(), mPods[e.RowIndex].Squad, this); + } + podData = mPodInEdit; + mRowInEdit = e.RowIndex; + } + else + { + podData = mPodInEdit; + } + if (e.ColumnIndex == colPodName.Index) + { + podData.Pod.Name = (string)e.Value; + } + else if (e.ColumnIndex == colSquad.Index) + { + podData.Squad = (string)e.Value; + } + else if (e.ColumnIndex == colType.Index) + { + podData.Pod.HostType = ((e.Value is string) ? HostTypeHelper.Parse((string)e.Value).HostType : ((HostTypeHelper)e.Value).HostType); + } + else if (e.ColumnIndex == colIPAddress.Index) + { + if (IPAddress.TryParse((string)e.Value, out var address)) + { + podData.Pod.IPAddress = address; + } + else + { + MessageBox.Show("Address could not be parsed.", "Parsing Error", MessageBoxButtons.OK); + } + } + } + + private void dgvPods_NewRowNeeded(object sender, DataGridViewRowEventArgs e) + { + mPodInEdit = new PodData(new Pod(), (mSite.Squads.Count > 0) ? mSite.Squads[0].Name : "", this); + mRowInEdit = dgvPods.Rows.Count - 1; + e.Row.Cells[colIPAddress.Index].ReadOnly = false; + } + + private void dgvPods_RowValidated(object sender, DataGridViewCellEventArgs e) + { + if (e.RowIndex >= mPods.Count && e.RowIndex != dgvPods.Rows.Count - 1) + { + if (mPodInEdit.Pod.IsLocal) + { + mPodInEdit.StartPing(); + } + mPods.Add(mPodInEdit); + mPodInEdit = null; + mRowInEdit = -1; + } + else if (mPodInEdit != null && e.RowIndex < mPods.Count) + { + if (mPodInEdit.Pod.IsLocal) + { + mPodInEdit.StartPing(); + } + if (mPods[e.RowIndex] != null) + { + mPods[e.RowIndex].StopPing(); + } + mPods[e.RowIndex] = mPodInEdit; + mPodInEdit = null; + mRowInEdit = -1; + } + else if (dgvPods.ContainsFocus) + { + mPodInEdit = null; + mRowInEdit = -1; + } + } + + private void dgvPods_CancelRowEdit(object sender, QuestionEventArgs e) + { + if (mRowInEdit == dgvPods.Rows.Count - 2 && mRowInEdit == mPods.Count) + { + mPodInEdit = new PodData(new Pod(), mSite.Squads[0].Name, this); + return; + } + mPodInEdit = null; + mRowInEdit = -1; + } + + private void dgvPods_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e) + { + if (e.Row.Index < mPods.Count) + { + if (mPods[e.Row.Index].OperationInProgress) + { + e.Cancel = true; + return; + } + Pod pod = mPods[e.Row.Index].Pod; + if (MessageBox.Show(this, "Are you sure you want to delete " + pod.Name + "?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) + { + e.Cancel = true; + return; + } + mPods[e.Row.Index].StopPing(); + mPods.RemoveAt(e.Row.Index); + } + if (e.Row.Index == mRowInEdit) + { + mRowInEdit = -1; + mPodInEdit = null; + } + } + + private void cmnuDataGrid_Opening(object sender, CancelEventArgs e) + { + int num = 0; + int num2 = 0; + List list = null; + foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) + { + if (selectedRow.Index >= mPods.Count) + { + continue; + } + if (mPods[selectedRow.Index].Pod.IsLocal && !mPods[selectedRow.Index].OperationInProgress && mPods[selectedRow.Index].PingReply != null && mPods[selectedRow.Index].PingReply.Status == IPStatus.Success && mPods[selectedRow.Index].Conn.IsConnected) + { + num++; + } + if (!mPods[selectedRow.Index].OperationInProgress) + { + num2++; + } + if (list == null) + { + list = new List(); + if (mPods[selectedRow.Index].Pod.IsLocal) + { + LaunchData[] allApplications = mPods[selectedRow.Index].Conn.AllApplications; + if (allApplications != null) + { + list.AddRange(allApplications); + } + } + } + else if (mPods[selectedRow.Index].Pod.IsLocal) + { + for (int num3 = list.Count - 1; num3 >= 0; num3--) + { + bool flag = false; + LaunchData[] allApplications2 = mPods[selectedRow.Index].Conn.AllApplications; + for (int i = 0; i < allApplications2.Length; i++) + { + LaunchData launchData = allApplications2[i]; + if (list[num3].LaunchPair.LaunchKey == launchData.LaunchPair.LaunchKey) + { + flag = true; + break; + } + } + if (!flag) + { + list.RemoveAt(num3); + } + } + } + else + { + list.Clear(); + } + } + deletePodToolStripMenuItem.Enabled = num2 > 0; + installProductToolStripMenuItem.Enabled = num > 0; + if (registerProductToolStripMenuItem != null) + { + registerProductToolStripMenuItem.Enabled = num > 0; + } + resendPodConfigurationToolStripMenuItem.Enabled = num > 0; + uninstallProductToolStripMenuItem.Enabled = list != null && list.Count > 0; + uninstallProductToolStripMenuItem.DropDownItems.Clear(); + if (list == null) + { + return; + } + foreach (LaunchData item in list) + { + ToolStripMenuItem toolStripMenuItem = new ToolStripMenuItem(); + toolStripMenuItem.Text = item.LaunchPair.DisplayName; + toolStripMenuItem.Tag = item; + toolStripMenuItem.Click += UninstallProduct_Click; + uninstallProductToolStripMenuItem.DropDownItems.Add(toolStripMenuItem); + } + } + + /// + /// Rebuilds the product-related context menus from the data-driven catalog (): + /// • "Install Product" → transfer a package and register its launch entries + /// • "Register Product" → push launch entries to pods over the network, no file install + /// plus the "Add New Product..." management action. + /// + private void BuildInstallProductMenu() + { + // Lazily create the "Register Product" parent and place it after "Install Product". + if (registerProductToolStripMenuItem == null) + { + registerProductToolStripMenuItem = new ToolStripMenuItem("Register Product on Pods"); + int insertAt = cmnuDataGrid.Items.IndexOf(installProductToolStripMenuItem) + 1; + cmnuDataGrid.Items.Insert(insertAt, registerProductToolStripMenuItem); + } + + installProductToolStripMenuItem.DropDownItems.Clear(); + registerProductToolStripMenuItem.DropDownItems.Clear(); + foreach (ProductDefinition product in AppRegistry.Products) + { + ToolStripMenuItem installItem = new ToolStripMenuItem(product.MenuText) { Tag = product }; + installItem.Click += InstallProduct_Click; + installProductToolStripMenuItem.DropDownItems.Add(installItem); + + ToolStripMenuItem registerItem = new ToolStripMenuItem(product.Name) { Tag = product }; + registerItem.Click += RegisterProduct_Click; + registerProductToolStripMenuItem.DropDownItems.Add(registerItem); + } + + installProductToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator()); + ToolStripMenuItem addItem = new ToolStripMenuItem("Add New Product..."); + addItem.Click += AddProduct_Click; + installProductToolStripMenuItem.DropDownItems.Add(addItem); + } + + /// Defines a new catalog product, persists it, and optionally pushes it to the selected pods. + private void AddProduct_Click(object sender, EventArgs e) + { + using dlgAddProduct dialog = new dlgAddProduct(); + if (dialog.ShowDialog(this) != DialogResult.OK) + { + return; + } + AppRegistry.AddProduct(dialog.Product); + BuildInstallProductMenu(); + + if (!dialog.PushNow) + { + return; + } + if (dialog.PackagePath != null) + { + // Package supplied → full install (file transfer + entry registration on completion). + foreach (int podIndex in SelectedInstallablePodIndices()) + { + mPods[podIndex].Install_CustomResolution = null; + mPods[podIndex].Install_HostTypes = new List(); + InstallProduct(podIndex, dialog.Product.Id, dialog.PackagePath); + } + } + else + { + RegisterProductOnSelectedPods(dialog.Product); + } + } + + /// Pushes a catalog product's launch entries to selected connected pods without installing files. + private void RegisterProduct_Click(object sender, EventArgs e) + { + ProductDefinition product = (ProductDefinition)((ToolStripMenuItem)sender).Tag; + RegisterProductOnSelectedPods(product); + } + + private void RegisterProductOnSelectedPods(ProductDefinition product) + { + int pushed = 0; + int skipped = 0; + foreach (int podIndex in SelectedInstallablePodIndices()) + { + PodData pod = mPods[podIndex]; + if (!pod.Conn.IsConnected) + { + skipped++; + continue; + } + foreach (LaunchEntryDefinition entry in product.Entries) + { + pod.Conn.AddApp(entry.ToLaunchData(null)); + } + pod.Status = "Registered " + product.Name; + dgvPods.InvalidateCell(colOperationProgress.Index, podIndex); + pushed++; + } + if (skipped > 0) + { + MessageBox.Show(this, + $"Registered \"{product.Name}\" on {pushed} pod(s). {skipped} selected pod(s) were skipped because they are not connected.", + "Register Product", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + } + + /// Indices of selected rows that are local, idle pods eligible for install/registration. + private IEnumerable SelectedInstallablePodIndices() + { + foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) + { + if (selectedRow.Index < mPods.Count && mPods[selectedRow.Index].Pod.IsLocal && !mPods[selectedRow.Index].OperationInProgress) + { + yield return selectedRow.Index; + } + } + } + + /// Generic install handler: selects a package, optionally prompts for roles/resolution, installs to selected local pods. + private void InstallProduct_Click(object sender, EventArgs e) + { + ProductDefinition product = (ProductDefinition)((ToolStripMenuItem)sender).Tag; + dlgSelectPackage.Title = "Select " + product.Name + " Install Package"; + if (dlgSelectPackage.ShowDialog(this) != DialogResult.OK) + { + return; + } + Size? resolution = null; + List hostTypes = new List(); + if (product.HostTypeDialog) + { + using dlgOptionalStartParams startParams = new dlgOptionalStartParams(); + if (startParams.ShowDialog(this) != DialogResult.OK) + { + return; + } + resolution = startParams.Resolution; + if (startParams.IsGameClient) + { + hostTypes.Add(PodInstallType.GameClient); + } + if (startParams.IsLiveCam) + { + hostTypes.Add(PodInstallType.LiveCamera); + } + if (startParams.IsMissionReview) + { + hostTypes.Add(PodInstallType.MissionReview); + } + } + foreach (int podIndex in SelectedInstallablePodIndices()) + { + mPods[podIndex].Install_CustomResolution = resolution; + mPods[podIndex].Install_HostTypes = new List(hostTypes); + InstallProduct(podIndex, product.Id, dlgSelectPackage.FileName); + } + } + + private void Pod_PingCompleted(object sender, PingCompletedEventArgs e) + { + PodData podData = e.UserState as PodData; + podData.OperationInProgress = false; + podData.Error = e.Error; + podData.PingReply = e.Reply; + if (e.Reply.Status == IPStatus.Success) + { + podData.Conn.ConnectAsync(podData); + } + podData.StartPing(); + int num = mPods.IndexOf(podData); + if (num >= 0 && num < dgvPods.Rows.Count) + { + dgvPods.InvalidateRow(num); + } + } + + private void Pod_PingCompleted(PodData pod) + { + if (base.InvokeRequired) + { + BeginInvoke(new Action(Pod_PingCompleted), pod); + return; + } + if (!pod.Conn.IsConnecting && !pod.Conn.IsConnected && pod.PingReply != null && pod.PingReply.Status == IPStatus.Success) + { + pod.Conn.ConnectAsync(pod); + } + int num = mPods.IndexOf(pod); + if (num >= 0 && num < dgvPods.Rows.Count) + { + dgvPods.InvalidateRow(num); + } + } + + private void PodInfo_InstallProductProgress(InstallProductProgressEventArgs e) + { + Tuple tuple = e.UserState as Tuple; + tuple.A.PercentProgress = e.ProgressPercentage; + ProductDefinition product = AppRegistry.GetProduct(tuple.B); + tuple.A.Status = "Installing " + (product?.Name ?? "product") + ": " + e.Status; + dgvPods.InvalidateCell(colOperationProgress.Index, mPods.IndexOf(tuple.A)); + } + + private void PodInfo_InstallProductCompleted(object sender, InstallProductCompletedEventArgs e) + { + Tuple tuple = e.UserState as Tuple; + PodData a = tuple.A; + foreach (LaunchData appData in BuildLaunchData(tuple.B, a)) + { + a.Conn.AddApp(appData); + } + a.OperationInProgress = false; + a.Error = e.Error; + dgvPods.InvalidateCell(colOperationProgress.Index, mPods.IndexOf(a)); + } + + /// + /// Builds the launch entries a product registers on a pod, honoring (for host-type products) + /// the roles selected during install. Returns empty if the product is unknown. + /// + private static List BuildLaunchData(Guid productId, PodData pod) + { + List list = new List(); + ProductDefinition product = AppRegistry.GetProduct(productId); + if (product == null) + { + return list; + } + foreach (LaunchEntryDefinition entry in product.Entries) + { + if (product.HostTypeDialog && entry.HostType != AppHostType.None + && !pod.Install_HostTypes.Contains(ToInstallType(entry.HostType))) + { + continue; + } + list.Add(entry.ToLaunchData(pod.Install_CustomResolution)); + } + return list; + } + + private static PodInstallType ToInstallType(AppHostType hostType) + { + return hostType switch + { + AppHostType.LiveCamera => PodInstallType.LiveCamera, + AppHostType.MissionReview => PodInstallType.MissionReview, + _ => PodInstallType.GameClient, + }; + } + + private void PodInfo_UninstallProductCompleted(object sender, AsyncCompletedEventArgs e) + { + PodData podData = (PodData)e.UserState; + podData.OperationInProgress = false; + podData.Error = e.Error; + podData.Status = "Uninstall completed!"; + dgvPods.InvalidateCell(colOperationProgress.Index, mPods.IndexOf(podData)); + } + + private void PodInfo_ConnectCompleted(object sender, AsyncCompletedEventArgs e) + { + PodData podData = e.UserState as PodData; + if (e.Error != null) + { + podData.Error = e.Error; + if (podData.PingReply != null && podData.PingReply.Status == IPStatus.Success) + { + new System.Threading.Timer(delegate(object state) + { + PodData podData2 = (PodData)state; + if (!podData2.Conn.IsConnected && !podData2.Conn.IsConnecting) + { + ((PodData)state).Conn.ConnectAsync((PodData)state); + } + }, podData, 5000, -1); + } + } + else + { + podData.OperationInProgress = false; + } + int num = mPods.IndexOf(podData); + if (num >= 0 && num < dgvPods.Rows.Count) + { + dgvPods.InvalidateRow(num); + } + } + + private void UninstallProduct_Click(object sender, EventArgs e) + { + ToolStripMenuItem toolStripMenuItem = (ToolStripMenuItem)sender; + LaunchData launchData = (LaunchData)toolStripMenuItem.Tag; + foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) + { + if (selectedRow.Index < mPods.Count && mPods[selectedRow.Index].Pod.IsLocal && !mPods[selectedRow.Index].OperationInProgress) + { + UninstallProduct(selectedRow.Index, launchData.LaunchPair.LaunchKey); + } + } + } + + private void InstallProduct(int podIndex, Guid appId, string packageFilename) + { + PodInfo conn = mPods[podIndex].Conn; + mPods[podIndex].OperationInProgress = true; + mPods[podIndex].Error = null; + mPods[podIndex].PercentProgress = 0; + mPods[podIndex].Status = ""; + conn.InstallProductAsync(packageFilename, new Tuple(mPods[podIndex], appId), PodInfo_InstallProductProgress); + } + + private void UninstallProduct(int podIndex, Guid appId) + { + PodInfo conn = mPods[podIndex].Conn; + mPods[podIndex].OperationInProgress = true; + mPods[podIndex].Error = null; + mPods[podIndex].PercentProgress = 0; + mPods[podIndex].Status = "Uninstalling product(s)..."; + dgvPods.InvalidateCell(colOperationProgress.Index, podIndex); + conn.UninstallProductAsync(appId, mPods[podIndex]); + } + + private void LoadConfiguration(Site site) + { + mSite = site; + dgvPods.Rows.Clear(); + mPods.Clear(); + foreach (Squad squad in site.Squads) + { + colSquad.Items.Add(squad.Name); + foreach (Pod pod in squad.Pods) + { + PodData podData = new PodData(pod, squad.Name, this); + mPods.Add(podData); + if (pod.IsLocal) + { + podData.PercentProgress = -1; + podData.OperationInProgress = true; + podData.Status = "Pinging pod..."; + Ping ping = new Ping(); + ping.PingCompleted += Pod_PingCompleted; + ping.SendAsync(pod.IPAddress, 10000, podData); + } + } + } + colType.Items.Add((HostTypeHelper)HostType.GameMachineHostType); + colType.Items.Add((HostTypeHelper)HostType.MissionReviewHostType); + dgvPods.RowCount = mPods.Count + 1; + dgvPods.AutoResizeColumns(); + } + + private void importSiteConfigurationToolStripMenuItem_Click(object sender, EventArgs e) + { + if (MessageBox.Show(this, "Importing a site configuration will replace the current configuration. Are you sure you want to continue?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes && dlgOpenFile.ShowDialog(this) == DialogResult.OK) + { + LoadConfiguration(TeslaConsole.Site.LoadFromFile(dlgOpenFile.FileName)); + } + } + + private void exportSiteConfigurationToolStripMenuItem_Click(object sender, EventArgs e) + { + if (dlgSaveFile.ShowDialog(this) == DialogResult.OK) + { + mSite.SaveAs(dlgSaveFile.FileName); + } + } + + private void DeletePod(int rowIndex) + { + if (rowIndex < mPods.Count && !mPods[rowIndex].OperationInProgress) + { + dgvPods.Rows.RemoveAt(rowIndex); + mPods[rowIndex].StopPing(); + mPods.RemoveAt(rowIndex); + } + } + + private void deletePodToolStripMenuItem_Click(object sender, EventArgs e) + { + if (dgvPods.SelectedRows.Count > 1) + { + if (MessageBox.Show(this, "Are you sure you want to delete these pods?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) + { + return; + } + } + else if (dgvPods.CurrentRow.Index < mPods.Count && MessageBox.Show(this, "Are you sure you want to delete " + mPods[dgvPods.CurrentRow.Index].Pod.Name + "?", "Site Management", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) + { + return; + } + for (int num = dgvPods.SelectedRows.Count - 1; num >= 0; num--) + { + DeletePod(dgvPods.SelectedRows[num].Index); + } + } + + private void resendPodConfigurationToolStripMenuItem_Click(object sender, EventArgs e) + { + if (dgvPods.SelectedRows.Count == 1) + { + if (MessageBox.Show(this, "Reconfiguring this pod will remove it from your site, allowing you to reconfigure it. Are you sure you want to continue?", "Reconfigure", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) + { + return; + } + } + else if (MessageBox.Show(this, "Reconfiguring these pods will remove them from your site, allowing you to reconfigure them. Are you sure you want to continue?", "Reconfigure", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) + { + return; + } + List list = new List(); + foreach (DataGridViewRow selectedRow in dgvPods.SelectedRows) + { + if (selectedRow.Index >= mPods.Count) + { + continue; + } + PodData podData = mPods[selectedRow.Index]; + list.Add(podData); + DeletePod(selectedRow.Index); + foreach (Squad squad in mSite.Squads) + { + if (!(squad.Name == podData.Squad)) + { + continue; + } + for (int num = squad.Pods.Count - 1; num >= 0; num--) + { + if (squad.Pods[num].ID == podData.Pod.ID) + { + squad.Pods.RemoveAt(num); + } + } + } + } + mSite.Save(); + foreach (PodData item in list) + { + try + { + item.Conn.ClearStore(); + } + catch (ConnectionLostException) + { + MessageBox.Show($"The data store on {item.Pod.Name} could not be cleared because the connection has been lost. Please try again.", "Connection Lost"); + } + } + } +} diff --git a/Console/TeslaConsole/SitePanel.cs b/Console/TeslaConsole/SitePanel.cs new file mode 100644 index 0000000..a64bf36 --- /dev/null +++ b/Console/TeslaConsole/SitePanel.cs @@ -0,0 +1,1640 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Threading; +using System.Windows.Forms; +using Tesla.Net; +using TeslaConsole.Properties; +using WeifenLuo.WinFormsUI.Docking; + +namespace TeslaConsole; + +internal class SitePanel : DockContent +{ + private IContainer components; + + private NoDoubleClickTreeView tvSquadsNPods; + + private ToolStrip tsMain; + + private ToolStripButton tsbNewSquad; + + private BackgroundWorker bgwConfigurePod; + + private StatusStrip ssStatus; + + private ToolStripStatusLabel lblStatus; + + private ToolStripProgressBar pbarOpProgress; + + private Panel panel1; + + private PictureBox picPodIcon; + + private Label lblPodName; + + private Label lblPodStatus; + + private ImageList ilsBigIcons; + + private ToolTip ttTip; + + private PictureBox picRefresh; + + private ContextMenuStrip cmnuTree; + + private ToolStripMenuItem mRestartCheckedPodsIn; + + private ToolStripMenuItem mShutdownCheckedPodsIn; + + private ToolStripButton tsbManageSite; + + private System.Windows.Forms.Timer tmrPodsCheck; + + private ToolStripMenuItem mnuRemoveSquad; + + private SplitContainer splitContainer1; + + private BackgroundWorker bgwRefreshApps; + + private PictureBox picThrobber; + + private AppControlGrid grdApps; + + private ToolStripMenuItem mShutdownAllPodsIn; + + private ToolStripMenuItem mRestartAllPodsIn; + + private ToolStripDropDownButton mPowerDropDown; + + private ToolStripMenuItem mRestartAllPods; + + private ToolStripMenuItem mShutdownAllPods; + + private ToolStripSeparator mPowerSeparator; + + private ToolStripMenuItem mRestartAllCheckedPods; + + private ToolStripMenuItem mShutdownAllCheckedPods; + + private ToolStripMenuItem mnuVolume; + + private readonly Dictionary mOpenConfigAlerts = new Dictionary(); + + private readonly Dictionary> mSquadPodInfos = new Dictionary>(); + + private readonly Dictionary mAllPods = new Dictionary(); + + private readonly ToolStripMenuItem[] rVolumeItems; + + private Squad mActiveSquad; + + private PodInfo mActivePod; + + private BackgroundWorker mRunningRefreshWorker; + + private int mActiveLaunchKillRequests; + + private ManualResetEvent mNoRunningLaunchKillRequests = new ManualResetEvent(initialState: true); + + private PodInfo SelectedPod + { + get + { + if (tvSquadsNPods.SelectedNode != null && tvSquadsNPods.SelectedNode.Tag is PodInfo) + { + return (PodInfo)tvSquadsNPods.SelectedNode.Tag; + } + return null; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TeslaConsole.SitePanel)); + this.cmnuTree = new System.Windows.Forms.ContextMenuStrip(this.components); + this.mnuVolume = new System.Windows.Forms.ToolStripMenuItem(); + this.mRestartAllPodsIn = new System.Windows.Forms.ToolStripMenuItem(); + this.mShutdownAllPodsIn = new System.Windows.Forms.ToolStripMenuItem(); + this.mRestartCheckedPodsIn = new System.Windows.Forms.ToolStripMenuItem(); + this.mShutdownCheckedPodsIn = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuRemoveSquad = new System.Windows.Forms.ToolStripMenuItem(); + this.tsMain = new System.Windows.Forms.ToolStrip(); + this.tsbNewSquad = new System.Windows.Forms.ToolStripButton(); + this.tsbManageSite = new System.Windows.Forms.ToolStripButton(); + this.mPowerDropDown = new System.Windows.Forms.ToolStripDropDownButton(); + this.mRestartAllPods = new System.Windows.Forms.ToolStripMenuItem(); + this.mShutdownAllPods = new System.Windows.Forms.ToolStripMenuItem(); + this.mPowerSeparator = new System.Windows.Forms.ToolStripSeparator(); + this.mRestartAllCheckedPods = new System.Windows.Forms.ToolStripMenuItem(); + this.mShutdownAllCheckedPods = new System.Windows.Forms.ToolStripMenuItem(); + this.bgwConfigurePod = new System.ComponentModel.BackgroundWorker(); + this.ssStatus = new System.Windows.Forms.StatusStrip(); + this.lblStatus = new System.Windows.Forms.ToolStripStatusLabel(); + this.pbarOpProgress = new System.Windows.Forms.ToolStripProgressBar(); + this.panel1 = new System.Windows.Forms.Panel(); + this.picThrobber = new System.Windows.Forms.PictureBox(); + this.lblPodStatus = new System.Windows.Forms.Label(); + this.picPodIcon = new System.Windows.Forms.PictureBox(); + this.lblPodName = new System.Windows.Forms.Label(); + this.picRefresh = new System.Windows.Forms.PictureBox(); + this.grdApps = new TeslaConsole.AppControlGrid(); + this.ilsBigIcons = new System.Windows.Forms.ImageList(this.components); + this.ttTip = new System.Windows.Forms.ToolTip(this.components); + this.tmrPodsCheck = new System.Windows.Forms.Timer(this.components); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.tvSquadsNPods = new TeslaConsole.NoDoubleClickTreeView(); + this.bgwRefreshApps = new System.ComponentModel.BackgroundWorker(); + this.cmnuTree.SuspendLayout(); + this.tsMain.SuspendLayout(); + this.ssStatus.SuspendLayout(); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)this.picThrobber).BeginInit(); + ((System.ComponentModel.ISupportInitialize)this.picPodIcon).BeginInit(); + ((System.ComponentModel.ISupportInitialize)this.picRefresh).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + base.SuspendLayout(); + this.cmnuTree.Items.AddRange(new System.Windows.Forms.ToolStripItem[6] { this.mnuVolume, this.mRestartAllPodsIn, this.mShutdownAllPodsIn, this.mRestartCheckedPodsIn, this.mShutdownCheckedPodsIn, this.mnuRemoveSquad }); + this.cmnuTree.Name = "cmnuTree"; + this.cmnuTree.Size = new System.Drawing.Size(223, 158); + this.cmnuTree.Opening += new System.ComponentModel.CancelEventHandler(cmnuTree_Opening); + this.mnuVolume.Name = "mnuVolume"; + this.mnuVolume.Size = new System.Drawing.Size(222, 22); + this.mnuVolume.Text = "Volume"; + this.mnuVolume.DropDownOpening += new System.EventHandler(mnuVolume_DropDownOpening); + this.mRestartAllPodsIn.Name = "mRestartAllPodsIn"; + this.mRestartAllPodsIn.Size = new System.Drawing.Size(222, 22); + this.mRestartAllPodsIn.Text = "Restart All Pods in"; + this.mRestartAllPodsIn.Click += new System.EventHandler(mRestartAllPodsIn_Click); + this.mShutdownAllPodsIn.Name = "mShutdownAllPodsIn"; + this.mShutdownAllPodsIn.Size = new System.Drawing.Size(222, 22); + this.mShutdownAllPodsIn.Text = "Shutdown All Pods in"; + this.mShutdownAllPodsIn.Click += new System.EventHandler(mShutdownAllPodsIn_Click); + this.mRestartCheckedPodsIn.Name = "mRestartCheckedPodsIn"; + this.mRestartCheckedPodsIn.Size = new System.Drawing.Size(222, 22); + this.mRestartCheckedPodsIn.Text = "Restart Checked Pods in"; + this.mRestartCheckedPodsIn.Click += new System.EventHandler(mRestartCheckedPodsIn_Click); + this.mShutdownCheckedPodsIn.Name = "mShutdownCheckedPodsIn"; + this.mShutdownCheckedPodsIn.Size = new System.Drawing.Size(222, 22); + this.mShutdownCheckedPodsIn.Text = "Shutdown Checked Pods in "; + this.mShutdownCheckedPodsIn.Click += new System.EventHandler(mShutdownCheckedPodsIn_Click); + this.mnuRemoveSquad.Name = "mnuRemoveSquad"; + this.mnuRemoveSquad.Size = new System.Drawing.Size(222, 22); + this.mnuRemoveSquad.Text = "Remove Squad"; + this.mnuRemoveSquad.Click += new System.EventHandler(mnuRemoveSquad_Click); + this.tsMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[3] { this.tsbNewSquad, this.tsbManageSite, this.mPowerDropDown }); + this.tsMain.Location = new System.Drawing.Point(0, 0); + this.tsMain.Name = "tsMain"; + this.tsMain.Size = new System.Drawing.Size(344, 25); + this.tsMain.TabIndex = 1; + this.tsMain.Text = "toolStrip1"; + this.tsbNewSquad.Image = TeslaConsole.Properties.Resources.Add; + this.tsbNewSquad.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsbNewSquad.Name = "tsbNewSquad"; + this.tsbNewSquad.Size = new System.Drawing.Size(87, 22); + this.tsbNewSquad.Text = "New Squad"; + this.tsbNewSquad.Click += new System.EventHandler(tsbNewSquad_Click); + this.tsbManageSite.Image = TeslaConsole.Properties.Resources.PodOffline16; + this.tsbManageSite.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsbManageSite.Name = "tsbManageSite"; + this.tsbManageSite.Size = new System.Drawing.Size(92, 22); + this.tsbManageSite.Text = "Manage Site"; + this.tsbManageSite.Click += new System.EventHandler(tsbManageSite_Click); + this.mPowerDropDown.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[5] { this.mRestartAllPods, this.mShutdownAllPods, this.mPowerSeparator, this.mRestartAllCheckedPods, this.mShutdownAllCheckedPods }); + this.mPowerDropDown.Image = (System.Drawing.Image)resources.GetObject("mPowerDropDown.Image"); + this.mPowerDropDown.ImageTransparentColor = System.Drawing.Color.Magenta; + this.mPowerDropDown.Name = "mPowerDropDown"; + this.mPowerDropDown.Size = new System.Drawing.Size(69, 22); + this.mPowerDropDown.Text = "Power"; + this.mRestartAllPods.Name = "mRestartAllPods"; + this.mRestartAllPods.Size = new System.Drawing.Size(206, 22); + this.mRestartAllPods.Text = "Restart All Pods"; + this.mRestartAllPods.Click += new System.EventHandler(mRestartAllPods_Click); + this.mShutdownAllPods.Name = "mShutdownAllPods"; + this.mShutdownAllPods.Size = new System.Drawing.Size(206, 22); + this.mShutdownAllPods.Text = "Shutdown All Pods"; + this.mShutdownAllPods.Click += new System.EventHandler(mShutdownAllPods_Click); + this.mPowerSeparator.Name = "mPowerSeparator"; + this.mPowerSeparator.Size = new System.Drawing.Size(203, 6); + this.mRestartAllCheckedPods.Name = "mRestartAllCheckedPods"; + this.mRestartAllCheckedPods.Size = new System.Drawing.Size(206, 22); + this.mRestartAllCheckedPods.Text = "Restart Checked Pods"; + this.mRestartAllCheckedPods.Click += new System.EventHandler(mRestartAllCheckedPods_Click); + this.mShutdownAllCheckedPods.Name = "mShutdownAllCheckedPods"; + this.mShutdownAllCheckedPods.Size = new System.Drawing.Size(206, 22); + this.mShutdownAllCheckedPods.Text = "Shutdown Checked Pods"; + this.mShutdownAllCheckedPods.Click += new System.EventHandler(mShutdownAllCheckedPods_Click); + this.bgwConfigurePod.WorkerReportsProgress = true; + this.bgwConfigurePod.DoWork += new System.ComponentModel.DoWorkEventHandler(bgwConfigurePod_DoWork); + this.bgwConfigurePod.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bgwConfigurePod_RunWorkerCompleted); + this.bgwConfigurePod.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(bgwConfigurePod_ProgressChanged); + this.ssStatus.Items.AddRange(new System.Windows.Forms.ToolStripItem[2] { this.lblStatus, this.pbarOpProgress }); + this.ssStatus.Location = new System.Drawing.Point(0, 573); + this.ssStatus.Name = "ssStatus"; + this.ssStatus.Size = new System.Drawing.Size(344, 22); + this.ssStatus.TabIndex = 2; + this.ssStatus.Text = "statusStrip1"; + this.lblStatus.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.lblStatus.Name = "lblStatus"; + this.lblStatus.Size = new System.Drawing.Size(329, 17); + this.lblStatus.Spring = true; + this.lblStatus.Text = "Site Idle"; + this.lblStatus.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.pbarOpProgress.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.pbarOpProgress.Name = "pbarOpProgress"; + this.pbarOpProgress.Size = new System.Drawing.Size(100, 16); + this.pbarOpProgress.Visible = false; + this.panel1.BackColor = System.Drawing.Color.White; + this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.panel1.Controls.Add(this.picThrobber); + this.panel1.Controls.Add(this.lblPodStatus); + this.panel1.Controls.Add(this.picPodIcon); + this.panel1.Controls.Add(this.lblPodName); + this.panel1.Controls.Add(this.picRefresh); + this.panel1.Controls.Add(this.grdApps); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(344, 217); + this.panel1.TabIndex = 4; + this.picThrobber.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + this.picThrobber.Image = TeslaConsole.Properties.Resources.square_throbber; + this.picThrobber.Location = new System.Drawing.Point(293, 3); + this.picThrobber.Name = "picThrobber"; + this.picThrobber.Size = new System.Drawing.Size(47, 32); + this.picThrobber.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picThrobber.TabIndex = 9; + this.picThrobber.TabStop = false; + this.picThrobber.Visible = false; + this.lblPodStatus.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.lblPodStatus.AutoEllipsis = true; + this.lblPodStatus.Location = new System.Drawing.Point(41, 19); + this.lblPodStatus.Name = "lblPodStatus"; + this.lblPodStatus.Size = new System.Drawing.Size(246, 13); + this.lblPodStatus.TabIndex = 6; + this.lblPodStatus.Text = "Pod is Offline"; + this.picPodIcon.Location = new System.Drawing.Point(3, 3); + this.picPodIcon.Name = "picPodIcon"; + this.picPodIcon.Size = new System.Drawing.Size(32, 32); + this.picPodIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picPodIcon.TabIndex = 5; + this.picPodIcon.TabStop = false; + this.lblPodName.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.lblPodName.AutoEllipsis = true; + this.lblPodName.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75f, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 0); + this.lblPodName.Location = new System.Drawing.Point(41, 3); + this.lblPodName.Name = "lblPodName"; + this.lblPodName.Size = new System.Drawing.Size(246, 16); + this.lblPodName.TabIndex = 4; + this.lblPodName.Text = "Mistress Quickly"; + this.picRefresh.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; + this.picRefresh.Cursor = System.Windows.Forms.Cursors.Hand; + this.picRefresh.Image = (System.Drawing.Image)resources.GetObject("picRefresh.Image"); + this.picRefresh.Location = new System.Drawing.Point(308, 3); + this.picRefresh.Name = "picRefresh"; + this.picRefresh.Size = new System.Drawing.Size(32, 32); + this.picRefresh.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picRefresh.TabIndex = 8; + this.picRefresh.TabStop = false; + this.picRefresh.Click += new System.EventHandler(picRefresh_Click); + this.grdApps.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.grdApps.Location = new System.Drawing.Point(0, 41); + this.grdApps.Name = "grdApps"; + this.grdApps.ShowCounts = false; + this.grdApps.Size = new System.Drawing.Size(343, 172); + this.grdApps.TabIndex = 10; + this.grdApps.KillApp += new System.EventHandler(grdApps_KillApp); + this.grdApps.LaunchApp += new System.EventHandler(grdApps_LaunchApp); + this.ilsBigIcons.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit; + this.ilsBigIcons.ImageSize = new System.Drawing.Size(32, 32); + this.ilsBigIcons.TransparentColor = System.Drawing.Color.Transparent; + this.tmrPodsCheck.Enabled = true; + this.tmrPodsCheck.Interval = 500; + this.tmrPodsCheck.Tick += new System.EventHandler(tmrPodsCheck_Tick); + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 25); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.splitContainer1.Panel1.Controls.Add(this.tvSquadsNPods); + this.splitContainer1.Panel2.Controls.Add(this.panel1); + this.splitContainer1.Size = new System.Drawing.Size(344, 548); + this.splitContainer1.SplitterDistance = 327; + this.splitContainer1.TabIndex = 5; + this.tvSquadsNPods.AllowDrop = true; + this.tvSquadsNPods.CheckBoxes = true; + this.tvSquadsNPods.ContextMenuStrip = this.cmnuTree; + this.tvSquadsNPods.Dock = System.Windows.Forms.DockStyle.Fill; + this.tvSquadsNPods.LabelEdit = true; + this.tvSquadsNPods.Location = new System.Drawing.Point(0, 0); + this.tvSquadsNPods.Name = "tvSquadsNPods"; + this.tvSquadsNPods.Size = new System.Drawing.Size(344, 327); + this.tvSquadsNPods.TabIndex = 0; + this.tvSquadsNPods.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(tvSquadsNPods_AfterCheck); + this.tvSquadsNPods.AfterLabelEdit += new System.Windows.Forms.NodeLabelEditEventHandler(tvSquadsNPods_AfterLabelEdit); + this.tvSquadsNPods.DragDrop += new System.Windows.Forms.DragEventHandler(tvSquadsNPods_DragDrop); + this.tvSquadsNPods.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(tvSquadsNPods_AfterSelect); + this.tvSquadsNPods.DragEnter += new System.Windows.Forms.DragEventHandler(tvSquadsNPods_DragEnter); + this.tvSquadsNPods.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(tvSquadsNPods_NodeMouseClick); + this.tvSquadsNPods.BeforeCheck += new System.Windows.Forms.TreeViewCancelEventHandler(tvSquadsNPods_BeforeCheck); + this.tvSquadsNPods.KeyDown += new System.Windows.Forms.KeyEventHandler(tvSquadsNPods_KeyDown); + this.tvSquadsNPods.ItemDrag += new System.Windows.Forms.ItemDragEventHandler(tvSquadsNPods_ItemDrag); + this.tvSquadsNPods.DragOver += new System.Windows.Forms.DragEventHandler(tvSquadsNPods_DragOver); + this.bgwRefreshApps.WorkerSupportsCancellation = true; + this.bgwRefreshApps.DoWork += new System.ComponentModel.DoWorkEventHandler(bgwRefreshPodApps_DoWork); + this.bgwRefreshApps.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bgwRefreshApps_RunWorkerCompleted); + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(344, 595); + base.Controls.Add(this.splitContainer1); + base.Controls.Add(this.ssStatus); + base.Controls.Add(this.tsMain); + this.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25f, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0); + base.Name = "SitePanel"; + base.TabText = "PodControl"; + this.Text = "PodControl"; + base.Load += new System.EventHandler(SitePanel_Load); + base.FormClosing += new System.Windows.Forms.FormClosingEventHandler(SitePanel_FormClosing); + this.cmnuTree.ResumeLayout(false); + this.tsMain.ResumeLayout(false); + this.tsMain.PerformLayout(); + this.ssStatus.ResumeLayout(false); + this.ssStatus.PerformLayout(); + this.panel1.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)this.picThrobber).EndInit(); + ((System.ComponentModel.ISupportInitialize)this.picPodIcon).EndInit(); + ((System.ComponentModel.ISupportInitialize)this.picRefresh).EndInit(); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + this.splitContainer1.ResumeLayout(false); + base.ResumeLayout(false); + base.PerformLayout(); + } + + public SitePanel() + { + InitializeComponent(); + rVolumeItems = new ToolStripMenuItem[11]; + for (int i = 0; i <= 10; i++) + { + int vol = i * 10; + rVolumeItems[i] = new ToolStripMenuItem((vol > 0) ? vol.ToString() : "mute"); + rVolumeItems[i].Click += delegate + { + ConnectAndDo(EnumerateTreeTag(checkedOnly: false), delegate(PodInfo pi) + { + pi.Volume = (float)vol / 100f; + }, "Setting Volume"); + }; + } + } + + private void FillSite() + { + tvSquadsNPods.BeginUpdate(); + tvSquadsNPods.Nodes.Clear(); + mSquadPodInfos.Clear(); + foreach (Squad squad in TeslaConsole.Site.Active.Squads) + { + AddSquad(squad); + } + tvSquadsNPods.ExpandAll(); + tvSquadsNPods.EndUpdate(); + foreach (TreeNode node in tvSquadsNPods.Nodes) + { + foreach (TreeNode node2 in node.Nodes) + { + if (((PodInfo)node2.Tag).Pod.Online) + { + node2.Checked = true; + } + } + } + } + + private TreeNode AddSquad(Squad squad) + { + TreeNode treeNode = new TreeNode(squad.Name); + treeNode.Tag = squad; + treeNode.Checked = squad.Online; + if (!squad.IsLocal) + { + SetNodeFont(treeNode, underline: false); + } + mSquadPodInfos.Add(squad.Guid, new List()); + foreach (Pod pod in squad.Pods) + { + AddPod(new PodInfo(pod, register: false), treeNode); + } + tvSquadsNPods.Nodes.Add(treeNode); + return treeNode; + } + + private TreeNode NewSquad() + { + int num = 1; + Squad squad = new Squad(); + squad.Name = "New Squad"; + for (int i = 0; i < TeslaConsole.Site.Active.Squads.Count; i++) + { + if (string.Compare(TeslaConsole.Site.Active.Squads[i].Name, squad.Name, ignoreCase: true) == 0) + { + squad.Name = "New Squad " + num++; + i = -1; + } + } + TeslaConsole.Site.Active.Squads.Add(squad); + return AddSquad(squad); + } + + private TreeNode AddPod(PodInfo pi, TreeNode squadNode) + { + Squad squad = squadNode.Tag as Squad; + mSquadPodInfos[squad.Guid].Add(pi); + pi.ConnectCompleted += Pod_ConnectCompleted; + pi.LaunchApplicationCompleted += Pod_LaunchApplicationCompleted; + pi.KillApplicationCompleted += Pod_KillApplicationCompleted; + TreeNode treeNode = new TreeNode(pi.Pod.Name); + treeNode.Tag = pi; + string text2 = (treeNode.ImageKey = (treeNode.SelectedImageKey = Resources.PodOfflineImageKey)); + if (!pi.Pod.IsLocal) + { + SetNodeFont(treeNode, underline: false); + } + squadNode.Nodes.Add(treeNode); + mAllPods[pi.Pod.ID] = pi; + return treeNode; + } + + private void SetNodeFont(TreeNode node, bool underline) + { + bool flag = true; + if (node.Tag is Squad) + { + flag = ((Squad)node.Tag).IsLocal; + } + else if (node.Tag is PodInfo) + { + flag = ((PodInfo)node.Tag).Pod.IsLocal; + } + if (underline) + { + if (flag) + { + node.NodeFont = new Font(tvSquadsNPods.Font, FontStyle.Underline); + } + else + { + node.NodeFont = new Font(tvSquadsNPods.Font, FontStyle.Italic | FontStyle.Underline); + } + } + else if (flag) + { + node.NodeFont = null; + } + else + { + node.NodeFont = new Font(tvSquadsNPods.Font, FontStyle.Italic); + } + node.ForeColor = (flag ? Color.Black : Color.Gray); + } + + private void FillInfoPanel(PodInfo pod, string imageKey) + { + if (pod == null) + { + picPodIcon.Image = null; + lblPodName.Text = ""; + lblPodStatus.Text = ""; + } + else + { + picPodIcon.Image = ilsBigIcons.Images[imageKey]; + lblPodName.Text = pod.Pod.Name; + picRefresh.Enabled = false; + if (pod.Pod.IsLocal) + { + ttTip.SetToolTip(lblPodStatus, ""); + if (pod.IsConnected) + { + lblPodStatus.Text = "Console is connected to pod"; + lblPodStatus.ForeColor = Color.Green; + picRefresh.Enabled = true; + } + else if (pod.LastConnectionException != null) + { + lblPodStatus.Text = "Connect error: " + pod.LastConnectionException.Message; + ttTip.SetToolTip(lblPodStatus, lblPodStatus.Text); + lblPodStatus.ForeColor = Color.Red; + } + else if (pod.IsConnecting) + { + lblPodStatus.Text = "Connecting..."; + ttTip.SetToolTip(lblPodStatus, lblPodStatus.Text); + lblPodStatus.ForeColor = Color.Black; + } + else + { + lblPodStatus.Text = "Not connected"; + lblPodStatus.ForeColor = Color.DarkGray; + } + } + else + { + lblPodStatus.Text = "Pod is not a local pod."; + lblPodStatus.ForeColor = Color.DarkGray; + } + } + RefreshAppList(pod, forceRefresh: false); + } + + private void FillInfoPanel(Squad squad) + { + if (squad == null || !mSquadPodInfos.ContainsKey(squad.Guid)) + { + picPodIcon.Image = null; + lblPodName.Text = ""; + lblPodStatus.Text = ""; + } + else + { + picPodIcon.Image = Resources.PodOffline32; + lblPodName.Text = squad.Name; + ttTip.SetToolTip(lblPodStatus, ""); + int num = 0; + int num2 = 0; + foreach (PodInfo item in mSquadPodInfos[squad.Guid]) + { + if (item.Pod.IsLocal) + { + if (item.Pod.Online) + { + num++; + } + if (item.IsConnected) + { + num2++; + } + } + } + lblPodStatus.Text = $"{squad.Pods.Count} pods, {num} online, {num2} connected"; + lblPodStatus.ForeColor = Color.Black; + } + RefreshAppList(squad, forceRefresh: false); + } + + private void RefreshAppList(PodInfo pod, bool forceRefresh) + { + mActiveSquad = null; + mActivePod = null; + grdApps.Clear(); + lblStatus.Text = "Refreshing application list..."; + lblStatus.ForeColor = Color.Black; + if (pod == null) + { + picRefresh.Visible = false; + return; + } + picRefresh.Show(); + picThrobber.Show(); + grdApps.ShowCounts = false; + mActivePod = pod; + mRunningRefreshWorker = new BackgroundWorker(); + mRunningRefreshWorker.DoWork += bgwRefreshPodApps_DoWork; + mRunningRefreshWorker.RunWorkerCompleted += bgwRefreshApps_RunWorkerCompleted; + mRunningRefreshWorker.RunWorkerAsync(Tuple.Create(pod, forceRefresh)); + } + + private void RefreshAppList(Squad squad, bool forceRefresh) + { + if (mActiveSquad == null || !(mActiveSquad.Guid == squad.Guid) || mRunningRefreshWorker == null) + { + mActiveSquad = null; + mActivePod = null; + grdApps.Clear(); + lblStatus.Text = "Refreshing application list..."; + lblStatus.ForeColor = Color.Black; + if (squad == null || !mSquadPodInfos.ContainsKey(squad.Guid)) + { + picRefresh.Visible = false; + return; + } + picRefresh.Show(); + picThrobber.Show(); + grdApps.ShowCounts = true; + mActiveSquad = squad; + mRunningRefreshWorker = new BackgroundWorker(); + mRunningRefreshWorker.DoWork += bgwRefreshSquadApps_DoWork; + mRunningRefreshWorker.RunWorkerCompleted += bgwRefreshApps_RunWorkerCompleted; + mRunningRefreshWorker.RunWorkerAsync(Tuple.Create(squad, forceRefresh)); + } + } + + private void IncrementActiveLaunchKillRequests() + { + if (mActiveLaunchKillRequests == 0) + { + mNoRunningLaunchKillRequests.Reset(); + } + mActiveLaunchKillRequests++; + } + + private void DecrementActiveLaunchKillRequests() + { + mActiveLaunchKillRequests--; + if (mActiveLaunchKillRequests == 0) + { + mNoRunningLaunchKillRequests.Set(); + } + } + + private void SitePanel_Load(object sender, EventArgs e) + { + tvSquadsNPods.ImageList = new ImageList(); + tvSquadsNPods.ImageList.Images.Add(Resources.PodOfflineImageKey, ImageCache.PodOffline16); + tvSquadsNPods.ImageList.Images.Add(Resources.PodOnlineImageKey, ImageCache.PodOnline16); + tvSquadsNPods.ImageList.Images.Add(Resources.PodBadImageKey, ImageCache.PodBad16); + tvSquadsNPods.ImageList.Images.Add(Resources.PodQuestionImageKey, ImageCache.PodQuestion16); + tvSquadsNPods.ImageList.Images.Add(Resources.PodGoImageKey, ImageCache.PodGo16); + ilsBigIcons.Images.Add(Resources.PodOfflineImageKey, Resources.PodOffline32); + ilsBigIcons.Images.Add(Resources.PodOnlineImageKey, Resources.PodOnline32); + ilsBigIcons.Images.Add(Resources.PodBadImageKey, Resources.PodBad32); + ilsBigIcons.Images.Add(Resources.PodQuestionImageKey, Resources.PodQuestion32); + ilsBigIcons.Images.Add(Resources.PodGoImageKey, Resources.PodGo32); + FillSite(); + PodManager.PodReadyForConfig += PodManager_PodReadyForConfig; + PodManager.PodConfigured += PodManager_PodConfigured; + } + + private void SitePanel_FormClosing(object sender, FormClosingEventArgs e) + { + tmrPodsCheck.Stop(); + PodManager.PodReadyForConfig -= PodManager_PodReadyForConfig; + PodManager.PodConfigured -= PodManager_PodConfigured; + foreach (TreeNode node in tvSquadsNPods.Nodes) + { + foreach (TreeNode node2 in node.Nodes) + { + PodInfo podInfo = (PodInfo)node2.Tag; + podInfo.ConnectCompleted -= Pod_ConnectCompleted; + podInfo.Disconnect(); + } + } + TeslaConsole.Site.Active.Save(); + } + + private void tsbNewSquad_Click(object sender, EventArgs e) + { + NewSquad().BeginEdit(); + } + + private void tsbManageSite_Click(object sender, EventArgs e) + { + foreach (NotificationAlert value in mOpenConfigAlerts.Values) + { + value.Close(); + } + mOpenConfigAlerts.Clear(); + using (SiteManagement siteManagement = new SiteManagement()) + { + siteManagement.ShowDialog(this); + } + FillSite(); + } + + private void tvSquadsNPods_BeforeCheck(object sender, TreeViewCancelEventArgs e) + { + if (e.Node.Tag is Squad) + { + e.Cancel = !((Squad)e.Node.Tag).IsLocal; + } + else if (e.Node.Tag is PodInfo) + { + e.Cancel = !((PodInfo)e.Node.Tag).Pod.IsLocal; + } + } + + private void tvSquadsNPods_AfterCheck(object sender, TreeViewEventArgs e) + { + if (e.Node.Tag is Squad) + { + (e.Node.Tag as Squad).Online = e.Node.Checked; + foreach (TreeNode node in e.Node.Nodes) + { + if (node.Checked != e.Node.Checked) + { + node.Checked = e.Node.Checked; + } + } + if (e.Node.IsSelected) + { + FillInfoPanel((Squad)e.Node.Tag); + } + } + else + { + if (!(e.Node.Tag is PodInfo)) + { + return; + } + PodInfo podInfo = (PodInfo)e.Node.Tag; + if (e.Node.Checked) + { + podInfo.ConnectAsync(e.Node); + string text2 = (e.Node.ImageKey = (e.Node.SelectedImageKey = Resources.PodQuestionImageKey)); + } + else + { + podInfo.Disconnect(); + string text4 = (e.Node.ImageKey = (e.Node.SelectedImageKey = Resources.PodOfflineImageKey)); + if (e.Node.IsSelected) + { + FillInfoPanel(podInfo, e.Node.ImageKey); + } + } + podInfo.Pod.Online = e.Node.Checked; + if (mActiveSquad != null) + { + FillInfoPanel(mActiveSquad); + } + int num = 0; + foreach (TreeNode node2 in e.Node.Parent.Nodes) + { + if (node2.Checked) + { + num++; + } + } + if (num == e.Node.Parent.Nodes.Count) + { + e.Node.Parent.Checked = true; + } + else if (num == 0) + { + e.Node.Parent.Checked = false; + } + } + } + + private void tvSquadsNPods_AfterLabelEdit(object sender, NodeLabelEditEventArgs e) + { + if (e.Label == null) + { + return; + } + if (e.Node.Tag is Squad) + { + foreach (Squad squad in TeslaConsole.Site.Active.Squads) + { + if (string.Compare(squad.Name, e.Label, ignoreCase: true) == 0) + { + MessageBox.Show(this, "A squad already exists with the name \"" + e.Label + "\". Squad names must be unique.", "Rename Squad", MessageBoxButtons.OK, MessageBoxIcon.Hand); + e.CancelEdit = true; + return; + } + } + ((Squad)e.Node.Tag).Name = e.Label; + } + else if (e.Node.Tag is PodInfo) + { + foreach (Squad squad2 in TeslaConsole.Site.Active.Squads) + { + foreach (Pod pod in squad2.Pods) + { + if (string.Compare(pod.Name, e.Label, ignoreCase: true) == 0) + { + MessageBox.Show(this, "A pod already exists with the name \"" + e.Label + "\". Pod names must be unique.", "Rename Pod", MessageBoxButtons.OK, MessageBoxIcon.Hand); + e.CancelEdit = true; + return; + } + } + } + ((PodInfo)e.Node.Tag).Pod.Name = e.Label; + } + TeslaConsole.Site.Active.Save(); + } + + private void tvSquadsNPods_AfterSelect(object sender, TreeViewEventArgs e) + { + grdApps.Clear(); + if (e.Node == null) + { + FillInfoPanel(null, ""); + } + else if (e.Node.Tag is Squad) + { + FillInfoPanel((Squad)e.Node.Tag); + } + else if (e.Node.Tag is PodInfo) + { + FillInfoPanel((PodInfo)e.Node.Tag, e.Node.ImageKey); + } + } + + private void tvSquadsNPods_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) + { + if (e.Button == MouseButtons.Right) + { + tvSquadsNPods.SelectedNode = e.Node; + } + } + + private void PodManager_PodReadyForConfig(string requestId, byte[] macAddress) + { + if (base.InvokeRequired) + { + Invoke(new PodManager.PodReadyConfigHandler(PodManager_PodReadyForConfig), requestId, macAddress); + } + else if (!SiteManagement.IsOpen) + { + NotificationAlert notificationAlert = new NotificationAlert(); + notificationAlert.RightButtonText = "Configure"; + notificationAlert.RightButtonDialogResult = DialogResult.OK; + notificationAlert.AlertButtonClick += ConfigAlert_AlertButtonClick; + notificationAlert.FormClosed += ConfigAlert_FormClosed; + notificationAlert.Tag = new Tuple(requestId, macAddress); + mOpenConfigAlerts.Add(requestId, notificationAlert); + Pod pod = TeslaConsole.Site.Active.FindPod(macAddress); + if (pod != null) + { + notificationAlert.Title = "Configuration - " + pod.Name; + notificationAlert.Text = pod.Name + " is ready to be configured." + Environment.NewLine + "Enter passphrase:"; + notificationAlert.ShowInput = true; + } + else + { + notificationAlert.Title = "Configuration - " + requestId; + notificationAlert.Text = "An unrecognized pod on your site is ready to be configured."; + } + notificationAlert.Show(); + } + } + + private void PodManager_PodConfigured(string requestId, byte[] macAddress) + { + NotificationAlert alert = null; + if (!mOpenConfigAlerts.TryGetValue(requestId, out alert)) + { + return; + } + mOpenConfigAlerts.Remove(requestId); + if (alert.InvokeRequired) + { + alert.Invoke((VoidDelegate)delegate + { + alert.Close(); + }); + } + else + { + alert.Close(); + } + } + + private void ConfigAlert_AlertButtonClick(object sender, AlertButtonClickEventArgs e) + { + NotificationAlert notificationAlert = sender as NotificationAlert; + if (e.DialogResult == DialogResult.OK) + { + Tuple tuple = (Tuple)notificationAlert.Tag; + mOpenConfigAlerts.Remove(tuple.A); + notificationAlert.Close(); + if (e.Input == null) + { + Pod pod = new Pod(); + pod.MacAddress = tuple.B; + SetupPod setupPod = new SetupPod(tuple.A, pod, TeslaConsole.Site.Active.Squads); + if (setupPod.ShowDialog(this) == DialogResult.OK) + { + ConfigurePod(tuple.A, setupPod.Passphrase, setupPod.Pod); + TreeNode treeNode = null; + bool flag = false; + if (setupPod.Squad.Guid == Guid.Empty) + { + treeNode = NewSquad(); + flag = true; + } + else + { + foreach (TreeNode node in tvSquadsNPods.Nodes) + { + if (((Squad)node.Tag).Guid == setupPod.Squad.Guid) + { + treeNode = node; + break; + } + } + } + ((Squad)treeNode.Tag).Pods.Add(setupPod.Pod); + treeNode.Expand(); + tvSquadsNPods.SelectedNode = AddPod(new PodInfo(setupPod.Pod, register: false), treeNode); + tvSquadsNPods.SelectedNode.EnsureVisible(); + if (flag) + { + treeNode.BeginEdit(); + } + } + } + else + { + Pod pod2 = TeslaConsole.Site.Active.FindPod(tuple.B); + ConfigurePod(tuple.A, e.Input.ToUpper(), pod2); + } + } + notificationAlert.Dispose(); + } + + private void ConfigAlert_FormClosed(object sender, FormClosedEventArgs e) + { + NotificationAlert notificationAlert = sender as NotificationAlert; + mOpenConfigAlerts.Remove(((Tuple)notificationAlert.Tag).A); + } + + private void ConfigurePod(string requestId, string passphrase, Pod pod) + { + lblStatus.Text = "Configuring " + pod.Name + "..."; + pbarOpProgress.Visible = true; + pbarOpProgress.Value = 0; + tsbManageSite.Enabled = false; + tsbManageSite.ToolTipText = "Configuring Pod..."; + ssStatus.Refresh(); + bgwConfigurePod.RunWorkerAsync(new Tuple(requestId, passphrase, pod)); + } + + private void bgwConfigurePod_DoWork(object sender, DoWorkEventArgs e) + { + Tuple tuple = (Tuple)e.Argument; + tuple.C.Key = PodManager.ConfigurePod(tuple.A, tuple.B, tuple.C); + if (tuple.C.Key == null) + { + e.Result = new PodConfigurationException(tuple.C.Name + " timed out configuring.", tuple.C); + return; + } + bgwConfigurePod.ReportProgress(50, "Saving site information..."); + TeslaConsole.Site.Active.Save(); + e.Result = tuple.C.Name + " configured."; + } + + private void bgwConfigurePod_ProgressChanged(object sender, ProgressChangedEventArgs e) + { + pbarOpProgress.Value = e.ProgressPercentage; + lblStatus.Text = (string)e.UserState; + } + + private void bgwConfigurePod_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (e.Result is PodConfigurationException) + { + lblStatus.Text = ((PodConfigurationException)e.Result).Message; + } + else + { + lblStatus.Text = (string)e.Result; + } + pbarOpProgress.Visible = false; + tsbManageSite.Enabled = true; + tsbManageSite.ToolTipText = "Manage Site"; + } + + private void bgwRefreshPodApps_DoWork(object sender, DoWorkEventArgs e) + { + PodInfo a2 = ((Tuple)e.Argument).A; + bool b = ((Tuple)e.Argument).B; + if (a2 == null) + { + return; + } + mNoRunningLaunchKillRequests.WaitOne(TimeSpan.FromMinutes(1.0)); + if (!a2.IsConnected) + { + return; + } + if (b) + { + a2.Refresh(); + } + List list = new List(); + LaunchData[] allApplications = a2.AllApplications; + LaunchData app; + for (int i = 0; i < allApplications.Length; i++) + { + app = allApplications[i]; + list.Add(new PodAppInstance + { + LaunchPair = app.LaunchPair, + PodId = a2.Pod.ID, + IsRunning = Array.Exists(a2.RunningApplications, (LaunchedAppData a) => app.LaunchPair.LaunchKey == a.LaunchKey) + }); + } + e.Result = list; + } + + private void bgwRefreshSquadApps_DoWork(object sender, DoWorkEventArgs e) + { + Squad a2 = ((Tuple)e.Argument).A; + bool b = ((Tuple)e.Argument).B; + if (a2 == null) + { + return; + } + List list = mSquadPodInfos[a2.Guid]; + if (list.Count <= 0) + { + return; + } + mNoRunningLaunchKillRequests.WaitOne(TimeSpan.FromMinutes(1.0)); + List list2 = new List(); + foreach (PodInfo item in list) + { + if (!item.Pod.Online || !item.IsConnected) + { + continue; + } + if (b) + { + item.Refresh(); + } + LaunchData[] allApplications = item.AllApplications; + LaunchData app; + for (int i = 0; i < allApplications.Length; i++) + { + app = allApplications[i]; + list2.Add(new PodAppInstance + { + LaunchPair = app.LaunchPair, + PodId = item.Pod.ID, + IsRunning = Array.Exists(item.RunningApplications, (LaunchedAppData a) => app.LaunchPair.LaunchKey == a.LaunchKey) + }); + } + } + e.Result = list2; + } + + private void bgwRefreshApps_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + BackgroundWorker backgroundWorker = (BackgroundWorker)sender; + if (backgroundWorker == mRunningRefreshWorker) + { + picThrobber.Visible = false; + if (e.Error != null) + { + lblStatus.Text = "Failed to retrieve list"; + lblStatus.ForeColor = Color.Red; + } + else + { + if (e.Result != null) + { + List list = (List)e.Result; + grdApps.SetData(list.FindAll((PodAppInstance app) => mAllPods[app.PodId].IsConnected)); + } + lblStatus.Text = "Site Idle"; + } + mRunningRefreshWorker = null; + } + backgroundWorker.Dispose(); + } + + private void grdApps_LaunchApp(object sender, AppEventArgs e) + { + if (mActivePod != null) + { + mActivePod.LaunchApplicationAsync(e.ApplicationId, null); + IncrementActiveLaunchKillRequests(); + } + else + { + if (mActiveSquad == null || !mSquadPodInfos.ContainsKey(mActiveSquad.Guid)) + { + return; + } + foreach (PodInfo item in mSquadPodInfos[mActiveSquad.Guid]) + { + if (item.IsConnected && e.PodIds.Contains(item.Pod.ID)) + { + item.LaunchApplicationAsync(e.ApplicationId, null); + IncrementActiveLaunchKillRequests(); + } + } + } + } + + private void grdApps_KillApp(object sender, AppEventArgs e) + { + if (mActivePod != null) + { + if (mActivePod.IsConnected) + { + mActivePod.KillApplicationAsync(e.ApplicationId, null); + IncrementActiveLaunchKillRequests(); + } + else + { + mActivePod.Refresh(); + } + } + else + { + if (mActiveSquad == null || !mSquadPodInfos.ContainsKey(mActiveSquad.Guid)) + { + return; + } + foreach (PodInfo item in mSquadPodInfos[mActiveSquad.Guid]) + { + if (item.IsConnected && e.PodIds.Contains(item.Pod.ID)) + { + item.KillApplicationAsync(e.ApplicationId, null); + IncrementActiveLaunchKillRequests(); + } + } + } + } + + private void Pod_ConnectCompleted(object sender, AsyncCompletedEventArgs e) + { + PodInfo podInfo = sender as PodInfo; + TreeNode treeNode = e.UserState as TreeNode; + if (!treeNode.Checked) + { + return; + } + if (e.Error == null) + { + string text2 = (treeNode.ImageKey = (treeNode.SelectedImageKey = Resources.PodOnlineImageKey)); + } + else + { + string text4 = (treeNode.ImageKey = (treeNode.SelectedImageKey = Resources.PodBadImageKey)); + } + if (tvSquadsNPods.SelectedNode == null) + { + return; + } + if (tvSquadsNPods.SelectedNode.Tag is Squad) + { + FillInfoPanel((Squad)tvSquadsNPods.SelectedNode.Tag); + } + else if (tvSquadsNPods.SelectedNode.Tag is PodInfo) + { + PodInfo podInfo2 = (PodInfo)tvSquadsNPods.SelectedNode.Tag; + if (podInfo2.Pod.ID == podInfo.Pod.ID) + { + FillInfoPanel(podInfo, treeNode.ImageKey); + } + } + } + + private void Pod_LaunchApplicationCompleted(object sender, ApplicationCompletedEventArgs e) + { + PodLaunchedOrKilled((PodInfo)sender, e.ApplicationId, wasKilled: false, e.Error != null); + } + + private void Pod_KillApplicationCompleted(object sender, ApplicationCompletedEventArgs e) + { + PodLaunchedOrKilled((PodInfo)sender, e.ApplicationId, wasKilled: true, e.Error != null); + } + + private void PodLaunchedOrKilled(PodInfo pod, Guid appId, bool wasKilled, bool failed) + { + DecrementActiveLaunchKillRequests(); + if ((mActivePod == null || !(pod.Pod.ID == mActivePod.Pod.ID)) && (mActiveSquad == null || !mActiveSquad.Pods.Contains(pod.Pod))) + { + return; + } + if (wasKilled) + { + if (failed) + { + grdApps.StopAppFailed(appId, pod.Pod.Name); + } + else + { + grdApps.StopApp(pod.Pod.ID, appId); + } + } + else if (failed) + { + grdApps.StartAppFailed(appId, pod.Pod.Name); + } + else + { + grdApps.StartApp(pod.Pod.ID, appId); + } + } + + private void picRefresh_Click(object sender, EventArgs e) + { + if (mActivePod != null) + { + RefreshAppList(mActivePod, forceRefresh: true); + } + else if (mActiveSquad != null) + { + RefreshAppList(mActiveSquad, forceRefresh: true); + } + } + + private void cmnuTree_Opening(object sender, CancelEventArgs e) + { + cmnuTree.Items.Clear(); + cmnuTree.Items.Add(mnuVolume); + cmnuTree.Items.Add(new ToolStripSeparator()); + TreeNode selectedNode = tvSquadsNPods.SelectedNode; + if (selectedNode == null) + { + return; + } + if (selectedNode.Tag is Squad) + { + Squad squad = (Squad)selectedNode.Tag; + cmnuTree.Tag = squad; + mRestartAllPodsIn.Text = "Restart All Pods in " + squad.Name; + mShutdownAllPodsIn.Text = "Shutdown All Pods in " + squad.Name; + mRestartCheckedPodsIn.Text = "Restart Checked Pods in " + squad.Name; + mShutdownCheckedPodsIn.Text = "Shutdown Checked Pods in " + squad.Name; + cmnuTree.Items.Add(mRestartAllPodsIn); + cmnuTree.Items.Add(mShutdownAllPodsIn); + cmnuTree.Items.Add(mRestartCheckedPodsIn); + cmnuTree.Items.Add(mShutdownCheckedPodsIn); + cmnuTree.Items.Add(new ToolStripSeparator()); + cmnuTree.Items.Add(mnuRemoveSquad); + } + else + { + if (!(selectedNode.Tag is PodInfo)) + { + return; + } + PodInfo podInfo = (PodInfo)selectedNode.Tag; + cmnuTree.Tag = podInfo; + mRestartAllPodsIn.Text = "Restart " + podInfo.Pod.Name; + mShutdownAllPodsIn.Text = "Shutdown " + podInfo.Pod.Name; + cmnuTree.Items.Add(mRestartAllPodsIn); + cmnuTree.Items.Add(mShutdownAllPodsIn); + if (!podInfo.IsConnected) + { + return; + } + LaunchData[] allApplications = podInfo.AllApplications; + if (allApplications.Length > 0) + { + cmnuTree.Items.Add(new ToolStripSeparator()); + LaunchData[] array = allApplications; + for (int i = 0; i < array.Length; i++) + { + LaunchData launchData = array[i]; + bool flag = podInfo.IsAppRunning(launchData.LaunchPair.LaunchKey); + cmnuTree.Items.Add((flag ? "Stop " : "Start ") + launchData.LaunchPair.DisplayName, flag ? Resources.Stop : Resources.Play, cmnuApplicationItem_Click).Tag = new Tuple(flag, launchData.LaunchPair); + } + } + } + } + + private void mRestartAllPods_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateSite(checkedOnly: false), delegate(PodInfo pi) + { + pi.Shutdown(restart: true); + }, "Restarting All Pods"); + } + + private void mShutdownAllPods_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateSite(checkedOnly: false), delegate(PodInfo pi) + { + pi.Shutdown(restart: false); + }, "Shutting Down Pods"); + } + + private void mRestartAllCheckedPods_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateSite(checkedOnly: true), delegate(PodInfo pi) + { + pi.Shutdown(restart: true); + }, "Restarting Pods"); + } + + private void mShutdownAllCheckedPods_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateSite(checkedOnly: true), delegate(PodInfo pi) + { + pi.Shutdown(restart: false); + }, "Shutting Down Pods"); + } + + private void mRestartAllPodsIn_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateTreeTag(checkedOnly: false), delegate(PodInfo pi) + { + pi.Shutdown(restart: true); + }, "Restarting Pods"); + } + + private void mShutdownAllPodsIn_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateTreeTag(checkedOnly: false), delegate(PodInfo pi) + { + pi.Shutdown(restart: false); + }, "Shutting Down Pods"); + } + + private void mRestartCheckedPodsIn_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateTreeTag(checkedOnly: true), delegate(PodInfo pi) + { + pi.Shutdown(restart: true); + }, "Restarting Pods"); + } + + private void mShutdownCheckedPodsIn_Click(object sender, EventArgs e) + { + ConnectAndDo(EnumerateTreeTag(checkedOnly: true), delegate(PodInfo pi) + { + pi.Shutdown(restart: false); + }, "Shutting Down Pods"); + } + + private IEnumerable EnumerateSite(bool checkedOnly) + { + foreach (Squad squad in TeslaConsole.Site.Active.Squads) + { + foreach (PodInfo item in EnumerateSquad(squad, checkedOnly)) + { + yield return item; + } + } + } + + private IEnumerable EnumerateTreeTag(bool checkedOnly) + { + if (cmnuTree.Tag is Squad) + { + foreach (PodInfo item in EnumerateSquad((Squad)cmnuTree.Tag, checkedOnly)) + { + yield return item; + } + yield break; + } + if (cmnuTree.Tag is PodInfo) + { + yield return (PodInfo)cmnuTree.Tag; + } + } + + private IEnumerable EnumerateSquad(Squad squad, bool checkedOnly) + { + if (!mSquadPodInfos.TryGetValue(squad.Guid, out var podInfos)) + { + yield break; + } + foreach (PodInfo podInfo in podInfos) + { + if (!checkedOnly || podInfo.Pod.Online) + { + yield return podInfo; + } + } + } + + private void ConnectAndDo(IEnumerable podInfos, Action action, string title) + { + List list = new List(); + foreach (PodInfo podInfo in podInfos) + { + if (MakeChecked(podInfo, tvSquadsNPods.Nodes)) + { + list.Add(podInfo); + } + } + PodActionDialog.Show(list, action, title); + } + + private bool MakeChecked(PodInfo podInfo, TreeNodeCollection nodes) + { + foreach (TreeNode node in nodes) + { + if (object.ReferenceEquals(node.Tag, podInfo)) + { + if (!node.Checked) + { + node.Checked = true; + } + return true; + } + if (MakeChecked(podInfo, node.Nodes)) + { + return true; + } + } + return false; + } + + private void GetConnectedPodNodes(TreeNodeCollection nodes, List connectedPods) + { + foreach (TreeNode node in nodes) + { + if (node.Tag is Squad) + { + GetConnectedPodNodes(node.Nodes, connectedPods); + continue; + } + PodInfo podInfo = (PodInfo)node.Tag; + if (podInfo.IsConnected) + { + connectedPods.Add(node); + } + } + } + + private void mnuRemoveSquad_Click(object sender, EventArgs e) + { + if (tvSquadsNPods.SelectedNode != null) + { + Squad squad = (Squad)tvSquadsNPods.SelectedNode.Tag; + if (squad.Pods.Count == 0 || MessageBox.Show(this, "Removing this squad will remove the pods it contains as well. Are you sure you want to continue?", "Remove Squad", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) + { + tvSquadsNPods.SelectedNode.Remove(); + TeslaConsole.Site.Active.Squads.Remove(squad); + mSquadPodInfos.Remove(squad.Guid); + } + } + } + + private void cmnuApplicationItem_Click(object sender, EventArgs e) + { + ToolStripMenuItem toolStripMenuItem = sender as ToolStripMenuItem; + PodInfo podInfo = cmnuTree.Tag as PodInfo; + Tuple tuple = (Tuple)toolStripMenuItem.Tag; + if (tuple.A) + { + podInfo.KillApplicationAsync(tuple.B.LaunchKey, null); + toolStripMenuItem.Text = "Start " + tuple.B.DisplayName; + toolStripMenuItem.Image = Resources.Play; + return; + } + podInfo.LaunchApplicationAsync(tuple.B.LaunchKey, null); + toolStripMenuItem.Text = "Stop " + tuple.B.DisplayName; + toolStripMenuItem.Image = Resources.Stop; + if (tuple.B.LaunchKey == SiteManagement.RPAppGuid || tuple.B.LaunchKey == SiteManagement.MW4AppGuid) + { + podInfo.Pod.HostType = HostType.GameMachineHostType; + } + else if (tuple.B.LaunchKey == SiteManagement.RPLCAppGuid || tuple.B.LaunchKey == SiteManagement.RPMRAppGuid || tuple.B.LaunchKey == SiteManagement.MW4LCAppGuid) + { + podInfo.Pod.HostType = HostType.MissionReviewHostType; + } + } + + private void mnuVolume_DropDownOpening(object sender, EventArgs e) + { + PodInfo podInfo = tvSquadsNPods.SelectedNode.Tag as PodInfo; + mnuVolume.DropDownItems.Clear(); + if (podInfo == null) + { + mnuVolume.DropDownItems.AddRange(rVolumeItems); + return; + } + float volume = podInfo.Volume; + if (float.IsNaN(volume)) + { + mnuVolume.DropDownItems.Add("There was a problem accessing the pod's volume."); + return; + } + int num = (int)(Math.Round((double)volume * 10.0, 0) * 10.0); + for (int i = 0; i < rVolumeItems.Length; i++) + { + mnuVolume.DropDownItems.Add(rVolumeItems[i]); + ((ToolStripMenuItem)mnuVolume.DropDownItems[i]).Checked = i + 1 == num / 10; + } + } + + private void tmrPodsCheck_Tick(object sender, EventArgs e) + { + foreach (TreeNode node in tvSquadsNPods.Nodes) + { + foreach (TreeNode node2 in node.Nodes) + { + PodInfo podInfo = node2.Tag as PodInfo; + if (node2.Checked && !podInfo.IsConnecting && !podInfo.IsConnected) + { + podInfo.ConnectAsync(node2); + } + } + } + } + + private void tvSquadsNPods_ItemDrag(object sender, ItemDragEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + tvSquadsNPods.SelectedNode = null; + tvSquadsNPods.Tag = null; + DoDragDrop(e.Item, DragDropEffects.Move); + } + } + + private void tvSquadsNPods_DragEnter(object sender, DragEventArgs e) + { + e.Effect = e.AllowedEffect; + } + + private void tvSquadsNPods_DragOver(object sender, DragEventArgs e) + { + Point pt = tvSquadsNPods.PointToClient(new Point(e.X, e.Y)); + TreeNode treeNode = e.Data.GetData(typeof(TreeNode)) as TreeNode; + TreeNode treeNode2 = tvSquadsNPods.Tag as TreeNode; + TreeNode nodeAt = tvSquadsNPods.GetNodeAt(pt); + e.Effect = DragDropEffects.None; + if (treeNode.Tag is PodInfo) + { + if (nodeAt != null) + { + e.Effect = e.AllowedEffect; + } + } + else if (treeNode.Tag is Squad && nodeAt != null && nodeAt.Tag is Squad) + { + e.Effect = e.AllowedEffect; + } + if (treeNode2 != nodeAt) + { + if (treeNode2 != null) + { + SetNodeFont(treeNode2, underline: false); + } + if (nodeAt != null) + { + SetNodeFont(nodeAt, underline: true); + } + tvSquadsNPods.Tag = nodeAt; + } + } + + private void tvSquadsNPods_DragDrop(object sender, DragEventArgs e) + { + Point pt = tvSquadsNPods.PointToClient(new Point(e.X, e.Y)); + TreeNode nodeAt = tvSquadsNPods.GetNodeAt(pt); + TreeNode treeNode = (TreeNode)e.Data.GetData(typeof(TreeNode)); + if (!treeNode.Equals(nodeAt) && e.Effect == DragDropEffects.Move) + { + if (treeNode.Tag is Squad) + { + Squad item = (Squad)treeNode.Tag; + Squad item2 = (Squad)nodeAt.Tag; + TeslaConsole.Site.Active.Squads.Remove(item); + treeNode.Remove(); + TeslaConsole.Site.Active.Squads.Insert(TeslaConsole.Site.Active.Squads.IndexOf(item2) + 1, item); + tvSquadsNPods.Nodes.Insert(tvSquadsNPods.Nodes.IndexOf(nodeAt) + 1, treeNode); + } + else if (treeNode.Tag is PodInfo) + { + Pod pod = ((PodInfo)treeNode.Tag).Pod; + ((Squad)treeNode.Parent.Tag).Pods.Remove(pod); + mSquadPodInfos[((Squad)treeNode.Parent.Tag).Guid].Remove((PodInfo)treeNode.Tag); + SetNodeFont(treeNode.Parent, underline: false); + treeNode.Remove(); + if (nodeAt.Parent == null) + { + nodeAt.Nodes.Insert(0, treeNode); + ((Squad)nodeAt.Tag).Pods.Insert(0, pod); + mSquadPodInfos[((Squad)nodeAt.Tag).Guid].Add((PodInfo)treeNode.Tag); + SetNodeFont(nodeAt, underline: false); + } + else + { + Pod pod2 = ((PodInfo)nodeAt.Tag).Pod; + Squad squad = (Squad)nodeAt.Parent.Tag; + nodeAt.Parent.Nodes.Insert(nodeAt.Parent.Nodes.IndexOf(nodeAt) + 1, treeNode); + squad.Pods.Insert(squad.Pods.IndexOf(pod2) + 1, pod); + mSquadPodInfos[squad.Guid].Add((PodInfo)treeNode.Tag); + SetNodeFont(nodeAt.Parent, underline: false); + } + } + tvSquadsNPods.SelectedNode = treeNode; + } + if (tvSquadsNPods.Tag != null) + { + ((TreeNode)tvSquadsNPods.Tag).NodeFont = null; + } + } + + private void tvSquadsNPods_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.F2 && tvSquadsNPods.SelectedNode != null) + { + tvSquadsNPods.SelectedNode.BeginEdit(); + } + } +} diff --git a/Console/TeslaConsole/Squad.cs b/Console/TeslaConsole/Squad.cs new file mode 100644 index 0000000..45aab55 --- /dev/null +++ b/Console/TeslaConsole/Squad.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace TeslaConsole; + +[Serializable] +internal class Squad +{ + [NonSerialized] + private EventHandler mOnOnlineChanged; + + private Guid mGuid = Guid.NewGuid(); + + private string mName = ""; + + private bool mOnline; + + [NonSerialized] + private List mPods = new List(); + + public Guid Guid => mGuid; + + public string Name + { + get + { + return mName; + } + set + { + mName = value; + } + } + + public bool IsLocal + { + get + { + int num = 0; + foreach (Pod pod in Pods) + { + if (pod.IsLocal) + { + num++; + } + } + return num > 0; + } + } + + public bool Online + { + get + { + return mOnline; + } + set + { + if (value == mOnline) + { + return; + } + mOnline = value; + if (mOnOnlineChanged != null) + { + mOnOnlineChanged(this, EventArgs.Empty); + } + foreach (Pod mPod in mPods) + { + mPod.Online = value; + } + } + } + + public List Pods + { + get + { + if (mPods == null) + { + mPods = new List(); + } + return mPods; + } + } + + public List OnlinePods + { + get + { + List list = new List(); + foreach (Pod mPod in mPods) + { + if (mPod.Online) + { + list.Add(mPod); + } + } + return list; + } + } + + public event EventHandler OnOnlineChanged + { + [MethodImpl(MethodImplOptions.Synchronized)] + add + { + mOnOnlineChanged = (EventHandler)Delegate.Combine(mOnOnlineChanged, value); + } + [MethodImpl(MethodImplOptions.Synchronized)] + remove + { + mOnOnlineChanged = (EventHandler)Delegate.Remove(mOnOnlineChanged, value); + } + } + + public Squad() + : this(Guid.NewGuid(), "") + { + } + + public Squad(string name) + : this(Guid.NewGuid(), name) + { + } + + public Squad(Guid guid, string name) + { + mGuid = guid; + mName = name; + } + + public Squad Clone() + { + Squad squad = new Squad(mGuid, mName); + squad.mOnline = mOnline; + squad.mPods.AddRange(mPods); + return squad; + } +} diff --git a/Console/TeslaConsole/TeslaConsoleForm.cs b/Console/TeslaConsole/TeslaConsoleForm.cs new file mode 100644 index 0000000..69d73b9 --- /dev/null +++ b/Console/TeslaConsole/TeslaConsoleForm.cs @@ -0,0 +1,602 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Printing; +using System.Windows.Forms; +using Munga.Net; +using TeslaConsole.RedPlanet; +using WeifenLuo.WinFormsUI.Docking; + +namespace TeslaConsole; + +public class TeslaConsoleForm : Form +{ + private SitePanel mSitePanel = new SitePanel(); + + private IContainer components; + + private DockPanel mMainDockPanel; + + private Timer mRPNetworkScanTimer; + + private MenuStrip mMenu; + + private ToolStripMenuItem mFileMenu; + + private ToolStripMenuItem mShowSitePanelMenuItem; + + private ToolStripSeparator mSitePanelSeparator; + + private ToolStripMenuItem mPlasmaEditor; + + private ToolStripMenuItem mExit; + + private ToolStripMenuItem mGamesMenu; + + private ToolStripMenuItem mRpDeathRace; + + private ToolStripMenuItem mRpMartianFootball; + + private ToolStripMenuItem mPlasmaFontTool; + + private ToolStripMenuItem mRpMissionPrintPreview; + + private PrintPreviewDialog mRPPrintPreviewDialog; + + private ToolStripMenuItem mPrintRpMission; + + private ToolStripSeparator mSeperator1; + + private PrintDialog mRPPrintDialog; + + private ToolStripMenuItem mPlasmaBitmapDecoder; + + private ToolStripSeparator mSeperator2; + + private OpenFileDialog mRPMissionOpenDialog; + + private ToolStripMenuItem aboutToolStripMenuItem; + + private ToolStripMenuItem aboutToolStripMenuItem1; + + private ToolStripMenuItem settingsToolStripMenuItem; + + private ToolStripMenuItem redPlanetDefaultsToolStripMenuItem; + + private ToolStripMenuItem importRedPlanetDefaultsToolStripMenuItem; + + private ToolStripMenuItem exportRedPlanetDefaultsToolStripMenuItem; + + private OpenFileDialog mRPDefaultsOpenDialog; + + private SaveFileDialog mRPDefaultsSaveDialog; + + private ToolStripSeparator toolStripSeparator1; + + private ToolStripMenuItem enableCustomBitmapsToolStripMenuItem; + + private ToolStripMenuItem mExceptionsMenu; + + private ToolStripMenuItem mThrowHandledExceptionMenuItem; + + private ToolStripMenuItem mThrownUnHandledExceptionMenuItem; + + public TeslaConsoleForm() + { + InitializeComponent(); + mRpMissionPrintPreview.Visible = false; + mSeperator1.Visible = false; + mExceptionsMenu.Visible = false; + enableCustomBitmapsToolStripMenuItem.Checked = PlasmaBitmaps.EnableCustomBitmaps; + mRPMissionOpenDialog.InitialDirectory = RPMissionResults.GetRPMissionsDirectory(); + TeslaConsole.Site.LoadFromStore(); + PodManager.StartConfigurationServer(); + } + + protected override void OnShown(EventArgs e) + { + base.OnShown(e); + ShowSitePanel(); + } + + private void mShowSitePanelMenuItem_Click(object sender, EventArgs e) + { + ShowSitePanel(); + } + + private void ShowSitePanel() + { + if (mSitePanel == null || mSitePanel.IsDisposed) + { + mSitePanel = new SitePanel(); + } + mSitePanel.Show(mMainDockPanel, DockState.DockRight); + mSitePanel.Activate(); + } + + private void NetworkScan(object sender, EventArgs e) + { + foreach (Squad squad in TeslaConsole.Site.Active.Squads) + { + foreach (Pod pod in squad.Pods) + { + switch (pod.MungaGame.State) + { + case MungaConnectionState.Connected: + pod.MungaGame.QueryStateIfNeeded(); + if (!pod.MungaGame.IsOwned) + { + while (pod.MungaGame.Receive() != null) + { + } + } + if (!pod.MungaGame.AppID.HasValue || !pod.MungaGame.AppState.HasValue) + { + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Waiting For State Update..."); + break; + } + if (pod.MungaGame.AppID.Value != 0) + { + pod.MungaGame.SetStatus(ImageCache.PodBad16, "Pod Not Running RP"); + break; + } + switch (pod.MungaGame.AppState.Value) + { + case ApplicationState.InitializingState: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "RP Initalizing"); + break; + case ApplicationState.WaitingForEgg: + pod.MungaGame.SetStatus(ImageCache.PodOnline16, "Waiting For A Game"); + break; + case ApplicationState.LoadingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Loading Mission"); + break; + case ApplicationState.WaitingForLaunch: + pod.MungaGame.SetStatus(ImageCache.PodGo16, "Waiting For Launch"); + break; + case ApplicationState.LaunchingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Launching Mission"); + break; + case ApplicationState.RunningMission: + pod.MungaGame.SetStatus(ImageCache.PodRun16, "Mission Running"); + break; + case ApplicationState.EndingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Ending Mission"); + break; + case ApplicationState.StoppingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Stopping Mission"); + break; + case ApplicationState.SuspendingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Suspending Mission"); + break; + case ApplicationState.ResumingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Resuming Mission"); + break; + case ApplicationState.AbortingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Aborting Mission"); + break; + case ApplicationState.CreatingMission: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Creating Mission"); + break; + default: + pod.MungaGame.SetStatus(ImageCache.PodBad16, "Pod State Unknown"); + break; + } + break; + case MungaConnectionState.Connecting: + pod.MungaGame.SetStatus(ImageCache.PodQuestion16, "Connecting..."); + break; + default: + if (pod.MungaGame.Requested) + { + pod.MungaGame.SetStatus(ImageCache.PodBad16, "Not Connected"); + } + else + { + pod.MungaGame.SetStatus(ImageCache.PodOffline16, ""); + } + break; + } + } + } + } + + private void redPlanetDeathRaceToolStripMenuItem_Click(object sender, EventArgs e) + { + CreateNewRPGame(footballMode: false); + } + + private void redPlanetMartianFootballToolStripMenuItem_Click(object sender, EventArgs e) + { + CreateNewRPGame(footballMode: true); + } + + private void CreateNewRPGame(bool footballMode) + { + RPGame rPGame = new RPGame(TeslaConsole.Site.Active, footballMode); + rPGame.MdiParent = this; + rPGame.Show(mMainDockPanel, DockState.Document); + } + + private void plasmaEditorToolStripMenuItem_Click(object sender, EventArgs e) + { + new PlasmaEditor().Show(); + } + + private void plasmaFontToolToolStripMenuItem_Click(object sender, EventArgs e) + { + new PlasmaFontTool().Show(); + } + + private void plasmaBitmapDecoderToolStripMenuItem_Click(object sender, EventArgs e) + { + new PlasmaBitmapDecoder().Show(); + } + + private PrintDocument GetMissionPrintDocument() + { + if (mRPMissionOpenDialog.ShowDialog() == DialogResult.OK) + { + try + { + RPMissionResults rPMissionResults = RPMissionResults.Load(mRPMissionOpenDialog.FileName); + return rPMissionResults.GetPrintDocument(); + } + catch (Exception ex) + { + MessageBox.Show("The selected document could not be loaded because it appears to be corrupt.", "Error Loading Mission Data", MessageBoxButtons.OK); + Program.LogException(ex, $"Error Loading RP Mission Results (\"{mRPMissionOpenDialog.FileName}\")."); + } + } + return null; + } + + private void printPreviewToolStripMenuItem_Click(object sender, EventArgs e) + { + PrintDocument missionPrintDocument = GetMissionPrintDocument(); + if (missionPrintDocument != null) + { + if (mRPPrintPreviewDialog.IsDisposed) + { + mRPPrintPreviewDialog = new PrintPreviewDialog(); + } + mRPPrintPreviewDialog.Document = missionPrintDocument; + mRPPrintPreviewDialog.Show(); + mRPPrintPreviewDialog.DesktopBounds = Screen.PrimaryScreen.WorkingArea; + } + } + + private void printToolStripMenuItem_Click(object sender, EventArgs e) + { + PrintDocument missionPrintDocument = GetMissionPrintDocument(); + if (missionPrintDocument != null) + { + mRPPrintDialog.Document = missionPrintDocument; + if (mRPPrintDialog.ShowDialog() == DialogResult.OK) + { + mRPPrintDialog.Document.Print(); + } + } + } + + private void TeslaConsoleForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (mSitePanel != null && !mSitePanel.IsDisposed) + { + mSitePanel.Close(); + } + } + + private void aboutToolStripMenuItem1_Click(object sender, EventArgs e) + { + using abtTeslaConsole abtTeslaConsole2 = new abtTeslaConsole(); + abtTeslaConsole2.ShowDialog(); + } + + private void changeRedPlanetDefaultsToolStripMenuItem_Click(object sender, EventArgs e) + { + RPDefaultsDialog.ShowSingleton(); + } + + private void importRedPlanetDefaultsToolStripMenuItem_Click(object sender, EventArgs e) + { + if (mRPDefaultsOpenDialog.ShowDialog() == DialogResult.OK) + { + try + { + RPDefaults.Import(mRPDefaultsOpenDialog.FileName); + } + catch (Exception) + { + MessageBox.Show("This Red Planet defaults file appears to be corrupt.", "Error Loading Red Planet Defaults!", MessageBoxButtons.OK); + return; + } + RPDefaults.Save(); + } + } + + private void exportRedPlanetDefaultsToolStripMenuItem_Click(object sender, EventArgs e) + { + if (mRPDefaultsSaveDialog.ShowDialog() == DialogResult.OK) + { + try + { + RPDefaults.Export(mRPDefaultsSaveDialog.FileName); + } + catch (Exception) + { + MessageBox.Show("The Red Planet defaults file could not be exported.", "Error Exporting Red Planet Defaults!", MessageBoxButtons.OK); + } + } + } + + private void enableCustomBitmapsToolStripMenuItem_Click(object sender, EventArgs e) + { + enableCustomBitmapsToolStripMenuItem.Checked = (PlasmaBitmaps.EnableCustomBitmaps = !PlasmaBitmaps.EnableCustomBitmaps); + } + + private void mThrowHandledExceptionMenuItem_Click(object sender, EventArgs e) + { + try + { + mThrownUnHandledExceptionMenuItem_Click(sender, e); + } + catch (Exception ex) + { + Program.LogExceptionAndShowDialog(ex, "The user pressed the exception generating button.", "An Exception Occured."); + } + } + + private void mThrownUnHandledExceptionMenuItem_Click(object sender, EventArgs e) + { + throw new Exception("User Generated Exception"); + } + + private void mExit_Click(object sender, EventArgs e) + { + Close(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + WeifenLuo.WinFormsUI.Docking.DockPanelSkin dockPanelSkin = new WeifenLuo.WinFormsUI.Docking.DockPanelSkin(); + WeifenLuo.WinFormsUI.Docking.AutoHideStripSkin autoHideStripSkin = new WeifenLuo.WinFormsUI.Docking.AutoHideStripSkin(); + WeifenLuo.WinFormsUI.Docking.DockPanelGradient dockPanelGradient = new WeifenLuo.WinFormsUI.Docking.DockPanelGradient(); + WeifenLuo.WinFormsUI.Docking.TabGradient tabGradient = new WeifenLuo.WinFormsUI.Docking.TabGradient(); + WeifenLuo.WinFormsUI.Docking.DockPaneStripSkin dockPaneStripSkin = new WeifenLuo.WinFormsUI.Docking.DockPaneStripSkin(); + WeifenLuo.WinFormsUI.Docking.DockPaneStripGradient dockPaneStripGradient = new WeifenLuo.WinFormsUI.Docking.DockPaneStripGradient(); + WeifenLuo.WinFormsUI.Docking.TabGradient tabGradient2 = new WeifenLuo.WinFormsUI.Docking.TabGradient(); + WeifenLuo.WinFormsUI.Docking.DockPanelGradient dockPanelGradient2 = new WeifenLuo.WinFormsUI.Docking.DockPanelGradient(); + WeifenLuo.WinFormsUI.Docking.TabGradient tabGradient3 = new WeifenLuo.WinFormsUI.Docking.TabGradient(); + WeifenLuo.WinFormsUI.Docking.DockPaneStripToolWindowGradient dockPaneStripToolWindowGradient = new WeifenLuo.WinFormsUI.Docking.DockPaneStripToolWindowGradient(); + WeifenLuo.WinFormsUI.Docking.TabGradient tabGradient4 = new WeifenLuo.WinFormsUI.Docking.TabGradient(); + WeifenLuo.WinFormsUI.Docking.TabGradient tabGradient5 = new WeifenLuo.WinFormsUI.Docking.TabGradient(); + WeifenLuo.WinFormsUI.Docking.DockPanelGradient dockPanelGradient3 = new WeifenLuo.WinFormsUI.Docking.DockPanelGradient(); + WeifenLuo.WinFormsUI.Docking.TabGradient tabGradient6 = new WeifenLuo.WinFormsUI.Docking.TabGradient(); + WeifenLuo.WinFormsUI.Docking.TabGradient tabGradient7 = new WeifenLuo.WinFormsUI.Docking.TabGradient(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TeslaConsole.TeslaConsoleForm)); + this.mMainDockPanel = new WeifenLuo.WinFormsUI.Docking.DockPanel(); + this.mRPNetworkScanTimer = new System.Windows.Forms.Timer(this.components); + this.mMenu = new System.Windows.Forms.MenuStrip(); + this.mFileMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.mShowSitePanelMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mSitePanelSeparator = new System.Windows.Forms.ToolStripSeparator(); + this.mRpMissionPrintPreview = new System.Windows.Forms.ToolStripMenuItem(); + this.mPrintRpMission = new System.Windows.Forms.ToolStripMenuItem(); + this.mSeperator1 = new System.Windows.Forms.ToolStripSeparator(); + this.mPlasmaEditor = new System.Windows.Forms.ToolStripMenuItem(); + this.mPlasmaFontTool = new System.Windows.Forms.ToolStripMenuItem(); + this.mPlasmaBitmapDecoder = new System.Windows.Forms.ToolStripMenuItem(); + this.mSeperator2 = new System.Windows.Forms.ToolStripSeparator(); + this.mExit = new System.Windows.Forms.ToolStripMenuItem(); + this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.redPlanetDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.importRedPlanetDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportRedPlanetDefaultsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.enableCustomBitmapsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mGamesMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.mRpDeathRace = new System.Windows.Forms.ToolStripMenuItem(); + this.mRpMartianFootball = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.mExceptionsMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.mThrowHandledExceptionMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mThrownUnHandledExceptionMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mRPPrintPreviewDialog = new System.Windows.Forms.PrintPreviewDialog(); + this.mRPPrintDialog = new System.Windows.Forms.PrintDialog(); + this.mRPMissionOpenDialog = new System.Windows.Forms.OpenFileDialog(); + this.mRPDefaultsOpenDialog = new System.Windows.Forms.OpenFileDialog(); + this.mRPDefaultsSaveDialog = new System.Windows.Forms.SaveFileDialog(); + this.mMenu.SuspendLayout(); + base.SuspendLayout(); + this.mMainDockPanel.ActiveAutoHideContent = null; + this.mMainDockPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.mMainDockPanel.DockBackColor = System.Drawing.SystemColors.Control; + this.mMainDockPanel.Location = new System.Drawing.Point(0, 24); + this.mMainDockPanel.Name = "mMainDockPanel"; + this.mMainDockPanel.Size = new System.Drawing.Size(1168, 567); + dockPanelGradient.EndColor = System.Drawing.SystemColors.ControlLight; + dockPanelGradient.StartColor = System.Drawing.SystemColors.ControlLight; + autoHideStripSkin.DockStripGradient = dockPanelGradient; + tabGradient.EndColor = System.Drawing.SystemColors.Control; + tabGradient.StartColor = System.Drawing.SystemColors.Control; + tabGradient.TextColor = System.Drawing.SystemColors.ControlDarkDark; + autoHideStripSkin.TabGradient = tabGradient; + dockPanelSkin.AutoHideStripSkin = autoHideStripSkin; + tabGradient2.EndColor = System.Drawing.SystemColors.ControlLightLight; + tabGradient2.StartColor = System.Drawing.SystemColors.ControlLightLight; + tabGradient2.TextColor = System.Drawing.SystemColors.ControlText; + dockPaneStripGradient.ActiveTabGradient = tabGradient2; + dockPanelGradient2.EndColor = System.Drawing.SystemColors.Control; + dockPanelGradient2.StartColor = System.Drawing.SystemColors.Control; + dockPaneStripGradient.DockStripGradient = dockPanelGradient2; + tabGradient3.EndColor = System.Drawing.SystemColors.ControlLight; + tabGradient3.StartColor = System.Drawing.SystemColors.ControlLight; + tabGradient3.TextColor = System.Drawing.SystemColors.ControlText; + dockPaneStripGradient.InactiveTabGradient = tabGradient3; + dockPaneStripSkin.DocumentGradient = dockPaneStripGradient; + tabGradient4.EndColor = System.Drawing.SystemColors.ActiveCaption; + tabGradient4.LinearGradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical; + tabGradient4.StartColor = System.Drawing.SystemColors.GradientActiveCaption; + tabGradient4.TextColor = System.Drawing.SystemColors.ActiveCaptionText; + dockPaneStripToolWindowGradient.ActiveCaptionGradient = tabGradient4; + tabGradient5.EndColor = System.Drawing.SystemColors.Control; + tabGradient5.StartColor = System.Drawing.SystemColors.Control; + tabGradient5.TextColor = System.Drawing.SystemColors.ControlText; + dockPaneStripToolWindowGradient.ActiveTabGradient = tabGradient5; + dockPanelGradient3.EndColor = System.Drawing.SystemColors.ControlLight; + dockPanelGradient3.StartColor = System.Drawing.SystemColors.ControlLight; + dockPaneStripToolWindowGradient.DockStripGradient = dockPanelGradient3; + tabGradient6.EndColor = System.Drawing.SystemColors.GradientInactiveCaption; + tabGradient6.LinearGradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical; + tabGradient6.StartColor = System.Drawing.SystemColors.GradientInactiveCaption; + tabGradient6.TextColor = System.Drawing.SystemColors.ControlText; + dockPaneStripToolWindowGradient.InactiveCaptionGradient = tabGradient6; + tabGradient7.EndColor = System.Drawing.Color.Transparent; + tabGradient7.StartColor = System.Drawing.Color.Transparent; + tabGradient7.TextColor = System.Drawing.SystemColors.ControlDarkDark; + dockPaneStripToolWindowGradient.InactiveTabGradient = tabGradient7; + dockPaneStripSkin.ToolWindowGradient = dockPaneStripToolWindowGradient; + dockPanelSkin.DockPaneStripSkin = dockPaneStripSkin; + this.mMainDockPanel.Skin = dockPanelSkin; + this.mMainDockPanel.TabIndex = 0; + this.mRPNetworkScanTimer.Enabled = true; + this.mRPNetworkScanTimer.Tick += new System.EventHandler(NetworkScan); + this.mMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[5] { this.mFileMenu, this.settingsToolStripMenuItem, this.mGamesMenu, this.aboutToolStripMenuItem, this.mExceptionsMenu }); + this.mMenu.Location = new System.Drawing.Point(0, 0); + this.mMenu.Name = "mMenu"; + this.mMenu.Size = new System.Drawing.Size(1168, 24); + this.mMenu.TabIndex = 3; + this.mMenu.Text = "menuStrip1"; + this.mFileMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[10] { this.mShowSitePanelMenuItem, this.mSitePanelSeparator, this.mRpMissionPrintPreview, this.mPrintRpMission, this.mSeperator1, this.mPlasmaEditor, this.mPlasmaFontTool, this.mPlasmaBitmapDecoder, this.mSeperator2, this.mExit }); + this.mFileMenu.Name = "mFileMenu"; + this.mFileMenu.Size = new System.Drawing.Size(37, 20); + this.mFileMenu.Text = "File"; + this.mShowSitePanelMenuItem.Name = "mShowSitePanelMenuItem"; + this.mShowSitePanelMenuItem.Size = new System.Drawing.Size(204, 22); + this.mShowSitePanelMenuItem.Text = "Pod Control Panel"; + this.mShowSitePanelMenuItem.Click += new System.EventHandler(mShowSitePanelMenuItem_Click); + this.mSitePanelSeparator.Name = "mSitePanelSeparator"; + this.mSitePanelSeparator.Size = new System.Drawing.Size(201, 6); + this.mRpMissionPrintPreview.Name = "mRpMissionPrintPreview"; + this.mRpMissionPrintPreview.Size = new System.Drawing.Size(204, 22); + this.mRpMissionPrintPreview.Text = "RP Mission Print Preview"; + this.mRpMissionPrintPreview.Click += new System.EventHandler(printPreviewToolStripMenuItem_Click); + this.mPrintRpMission.Name = "mPrintRpMission"; + this.mPrintRpMission.Size = new System.Drawing.Size(204, 22); + this.mPrintRpMission.Text = "Print RP Mission"; + this.mPrintRpMission.Click += new System.EventHandler(printToolStripMenuItem_Click); + this.mSeperator1.Name = "mSeperator1"; + this.mSeperator1.Size = new System.Drawing.Size(201, 6); + this.mPlasmaEditor.Name = "mPlasmaEditor"; + this.mPlasmaEditor.Size = new System.Drawing.Size(204, 22); + this.mPlasmaEditor.Text = "Plasma Editor"; + this.mPlasmaEditor.Click += new System.EventHandler(plasmaEditorToolStripMenuItem_Click); + this.mPlasmaFontTool.Name = "mPlasmaFontTool"; + this.mPlasmaFontTool.Size = new System.Drawing.Size(204, 22); + this.mPlasmaFontTool.Text = "Plasma Font Tool"; + this.mPlasmaFontTool.Click += new System.EventHandler(plasmaFontToolToolStripMenuItem_Click); + this.mPlasmaBitmapDecoder.Name = "mPlasmaBitmapDecoder"; + this.mPlasmaBitmapDecoder.Size = new System.Drawing.Size(204, 22); + this.mPlasmaBitmapDecoder.Text = "Plasma Bitmap Decoder"; + this.mPlasmaBitmapDecoder.Click += new System.EventHandler(plasmaBitmapDecoderToolStripMenuItem_Click); + this.mSeperator2.Name = "mSeperator2"; + this.mSeperator2.Size = new System.Drawing.Size(201, 6); + this.mExit.Name = "mExit"; + this.mExit.Size = new System.Drawing.Size(204, 22); + this.mExit.Text = "Exit"; + this.mExit.Click += new System.EventHandler(mExit_Click); + this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[5] { this.redPlanetDefaultsToolStripMenuItem, this.importRedPlanetDefaultsToolStripMenuItem, this.exportRedPlanetDefaultsToolStripMenuItem, this.toolStripSeparator1, this.enableCustomBitmapsToolStripMenuItem }); + this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; + this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); + this.settingsToolStripMenuItem.Text = "Settings"; + this.redPlanetDefaultsToolStripMenuItem.Name = "redPlanetDefaultsToolStripMenuItem"; + this.redPlanetDefaultsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.redPlanetDefaultsToolStripMenuItem.Text = "&Change Red Planet Defaults"; + this.redPlanetDefaultsToolStripMenuItem.Click += new System.EventHandler(changeRedPlanetDefaultsToolStripMenuItem_Click); + this.importRedPlanetDefaultsToolStripMenuItem.Name = "importRedPlanetDefaultsToolStripMenuItem"; + this.importRedPlanetDefaultsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.importRedPlanetDefaultsToolStripMenuItem.Text = "&Import Red Planet Defaults"; + this.importRedPlanetDefaultsToolStripMenuItem.Click += new System.EventHandler(importRedPlanetDefaultsToolStripMenuItem_Click); + this.exportRedPlanetDefaultsToolStripMenuItem.Name = "exportRedPlanetDefaultsToolStripMenuItem"; + this.exportRedPlanetDefaultsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.exportRedPlanetDefaultsToolStripMenuItem.Text = "&Export Red Planet Defaults"; + this.exportRedPlanetDefaultsToolStripMenuItem.Click += new System.EventHandler(exportRedPlanetDefaultsToolStripMenuItem_Click); + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(217, 6); + this.enableCustomBitmapsToolStripMenuItem.Name = "enableCustomBitmapsToolStripMenuItem"; + this.enableCustomBitmapsToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.enableCustomBitmapsToolStripMenuItem.Text = "Enable Custom Bitmaps"; + this.enableCustomBitmapsToolStripMenuItem.Click += new System.EventHandler(enableCustomBitmapsToolStripMenuItem_Click); + this.mGamesMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[2] { this.mRpDeathRace, this.mRpMartianFootball }); + this.mGamesMenu.Name = "mGamesMenu"; + this.mGamesMenu.Size = new System.Drawing.Size(55, 20); + this.mGamesMenu.Text = "Games"; + this.mRpDeathRace.Name = "mRpDeathRace"; + this.mRpDeathRace.Size = new System.Drawing.Size(223, 22); + this.mRpDeathRace.Text = "Red Planet: Death Race"; + this.mRpDeathRace.Click += new System.EventHandler(redPlanetDeathRaceToolStripMenuItem_Click); + this.mRpMartianFootball.Name = "mRpMartianFootball"; + this.mRpMartianFootball.Size = new System.Drawing.Size(223, 22); + this.mRpMartianFootball.Text = "Red Planet: Martian Football"; + this.mRpMartianFootball.Click += new System.EventHandler(redPlanetMartianFootballToolStripMenuItem_Click); + this.aboutToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[1] { this.aboutToolStripMenuItem1 }); + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(52, 20); + this.aboutToolStripMenuItem.Text = "About"; + this.aboutToolStripMenuItem1.Name = "aboutToolStripMenuItem1"; + this.aboutToolStripMenuItem1.Size = new System.Drawing.Size(107, 22); + this.aboutToolStripMenuItem1.Text = "About"; + this.aboutToolStripMenuItem1.Click += new System.EventHandler(aboutToolStripMenuItem1_Click); + this.mExceptionsMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[2] { this.mThrowHandledExceptionMenuItem, this.mThrownUnHandledExceptionMenuItem }); + this.mExceptionsMenu.Name = "mExceptionsMenu"; + this.mExceptionsMenu.Size = new System.Drawing.Size(75, 20); + this.mExceptionsMenu.Text = "Exceptions"; + this.mThrowHandledExceptionMenuItem.Name = "mThrowHandledExceptionMenuItem"; + this.mThrowHandledExceptionMenuItem.Size = new System.Drawing.Size(237, 22); + this.mThrowHandledExceptionMenuItem.Text = "Throw Handled Exception"; + this.mThrowHandledExceptionMenuItem.Click += new System.EventHandler(mThrowHandledExceptionMenuItem_Click); + this.mThrownUnHandledExceptionMenuItem.Name = "mThrownUnHandledExceptionMenuItem"; + this.mThrownUnHandledExceptionMenuItem.Size = new System.Drawing.Size(237, 22); + this.mThrownUnHandledExceptionMenuItem.Text = "Thrown Un-Handled Exception"; + this.mThrownUnHandledExceptionMenuItem.Click += new System.EventHandler(mThrownUnHandledExceptionMenuItem_Click); + this.mRPPrintPreviewDialog.AutoScrollMargin = new System.Drawing.Size(0, 0); + this.mRPPrintPreviewDialog.AutoScrollMinSize = new System.Drawing.Size(0, 0); + this.mRPPrintPreviewDialog.ClientSize = new System.Drawing.Size(400, 300); + this.mRPPrintPreviewDialog.Enabled = true; + this.mRPPrintPreviewDialog.Icon = (System.Drawing.Icon)resources.GetObject("mRPPrintPreviewDialog.Icon"); + this.mRPPrintPreviewDialog.Name = "printPreviewDialog1"; + this.mRPPrintPreviewDialog.Visible = false; + this.mRPPrintDialog.UseEXDialog = true; + this.mRPMissionOpenDialog.Filter = "RP Mission (*.rpm)|*.rpm"; + this.mRPDefaultsOpenDialog.Filter = "RP Defaults (*.rpd)|*.rpd"; + this.mRPDefaultsSaveDialog.Filter = "RP Defaults (*.rpd)|*.rpd"; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(1168, 591); + base.Controls.Add(this.mMainDockPanel); + base.Controls.Add(this.mMenu); + base.Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon"); + base.IsMdiContainer = true; + base.MainMenuStrip = this.mMenu; + base.Name = "TeslaConsoleForm"; + this.Text = "Tesla Console"; + base.FormClosing += new System.Windows.Forms.FormClosingEventHandler(TeslaConsoleForm_FormClosing); + this.mMenu.ResumeLayout(false); + this.mMenu.PerformLayout(); + base.ResumeLayout(false); + base.PerformLayout(); + } +} diff --git a/Console/TeslaConsole/Tuple.cs b/Console/TeslaConsole/Tuple.cs new file mode 100644 index 0000000..7614c7e --- /dev/null +++ b/Console/TeslaConsole/Tuple.cs @@ -0,0 +1,172 @@ +namespace TeslaConsole; + +public static class Tuple +{ + public static Tuple Create(T1 a, T2 b) + { + return new Tuple(a, b); + } + + public static Tuple Create(T1 a, T2 b, T3 c) + { + return new Tuple(a, b, c); + } + + public static Tuple Create(T1 a, T2 b, T3 c, T4 d) + { + return new Tuple(a, b, c, d); + } +} +public class Tuple +{ + private T1 mA; + + private T2 mB; + + public T1 A + { + get + { + return mA; + } + set + { + mA = value; + } + } + + public T2 B + { + get + { + return mB; + } + set + { + mB = value; + } + } + + public Tuple(T1 a, T2 b) + { + mA = a; + mB = b; + } +} +public class Tuple +{ + private T1 mA; + + private T2 mB; + + private T3 mC; + + public T1 A + { + get + { + return mA; + } + set + { + mA = value; + } + } + + public T2 B + { + get + { + return mB; + } + set + { + mB = value; + } + } + + public T3 C + { + get + { + return mC; + } + set + { + mC = value; + } + } + + public Tuple(T1 a, T2 b, T3 c) + { + mA = a; + mB = b; + mC = c; + } +} +public class Tuple +{ + private T1 mA; + + private T2 mB; + + private T3 mC; + + private T4 mD; + + public T1 A + { + get + { + return mA; + } + set + { + mA = value; + } + } + + public T2 B + { + get + { + return mB; + } + set + { + mB = value; + } + } + + public T3 C + { + get + { + return mC; + } + set + { + mC = value; + } + } + + public T4 D + { + get + { + return mD; + } + set + { + mD = value; + } + } + + public Tuple(T1 a, T2 b, T3 c, T4 d) + { + mA = a; + mB = b; + mC = c; + mD = d; + } +} diff --git a/Console/TeslaConsole/VoidDelegate.cs b/Console/TeslaConsole/VoidDelegate.cs new file mode 100644 index 0000000..e0e7c23 --- /dev/null +++ b/Console/TeslaConsole/VoidDelegate.cs @@ -0,0 +1,3 @@ +namespace TeslaConsole; + +public delegate void VoidDelegate(); diff --git a/Console/TeslaConsole/abtTeslaConsole.cs b/Console/TeslaConsole/abtTeslaConsole.cs new file mode 100644 index 0000000..69e0a97 --- /dev/null +++ b/Console/TeslaConsole/abtTeslaConsole.cs @@ -0,0 +1,214 @@ +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Reflection; +using System.Windows.Forms; +using TeslaConsole.Properties; + +namespace TeslaConsole; + +internal class abtTeslaConsole : Form +{ + private IContainer components; + + private Label labelProductName; + + private Label labelVersion; + + private Label labelCopyright; + + private Label labelCompanyName; + + private Button okButton; + + private PictureBox picLogo; + + private TextBox textBoxDescription; + + public string AssemblyTitle + { + get + { + object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), inherit: false); + if (customAttributes.Length > 0) + { + AssemblyTitleAttribute assemblyTitleAttribute = (AssemblyTitleAttribute)customAttributes[0]; + if (assemblyTitleAttribute.Title != "") + { + return assemblyTitleAttribute.Title; + } + } + return Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); + } + } + + public string AssemblyVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + public string AssemblyDescription + { + get + { + object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), inherit: false); + if (customAttributes.Length == 0) + { + return ""; + } + return ((AssemblyDescriptionAttribute)customAttributes[0]).Description; + } + } + + public string AssemblyProduct + { + get + { + object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), inherit: false); + if (customAttributes.Length == 0) + { + return ""; + } + return ((AssemblyProductAttribute)customAttributes[0]).Product; + } + } + + public string AssemblyCopyright + { + get + { + object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), inherit: false); + if (customAttributes.Length == 0) + { + return ""; + } + return ((AssemblyCopyrightAttribute)customAttributes[0]).Copyright; + } + } + + public string AssemblyCompany + { + get + { + object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), inherit: false); + if (customAttributes.Length == 0) + { + return ""; + } + return ((AssemblyCompanyAttribute)customAttributes[0]).Company; + } + } + + public abtTeslaConsole() + { + InitializeComponent(); + Text = $"About {AssemblyTitle}"; + labelProductName.Text = AssemblyProduct; + labelVersion.Text = $"Version {AssemblyVersion}"; + labelCopyright.Text = AssemblyCopyright; + labelCompanyName.Text = AssemblyCompany; + textBoxDescription.Text = AssemblyDescription; + picLogo.Image = new Icon(Resources.swirl, 64, 64).ToBitmap(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.labelProductName = new System.Windows.Forms.Label(); + this.labelVersion = new System.Windows.Forms.Label(); + this.labelCopyright = new System.Windows.Forms.Label(); + this.labelCompanyName = new System.Windows.Forms.Label(); + this.okButton = new System.Windows.Forms.Button(); + this.picLogo = new System.Windows.Forms.PictureBox(); + this.textBoxDescription = new System.Windows.Forms.TextBox(); + ((System.ComponentModel.ISupportInitialize)this.picLogo).BeginInit(); + base.SuspendLayout(); + this.labelProductName.AutoSize = true; + this.labelProductName.Location = new System.Drawing.Point(85, 12); + this.labelProductName.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); + this.labelProductName.MaximumSize = new System.Drawing.Size(0, 17); + this.labelProductName.Name = "labelProductName"; + this.labelProductName.Size = new System.Drawing.Size(74, 13); + this.labelProductName.TabIndex = 19; + this.labelProductName.Text = "Tesla Console"; + this.labelProductName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.labelVersion.AutoSize = true; + this.labelVersion.Location = new System.Drawing.Point(85, 31); + this.labelVersion.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); + this.labelVersion.MaximumSize = new System.Drawing.Size(0, 17); + this.labelVersion.Name = "labelVersion"; + this.labelVersion.Size = new System.Drawing.Size(22, 13); + this.labelVersion.TabIndex = 0; + this.labelVersion.Text = "1.0"; + this.labelVersion.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.labelCopyright.AutoSize = true; + this.labelCopyright.Location = new System.Drawing.Point(85, 50); + this.labelCopyright.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); + this.labelCopyright.MaximumSize = new System.Drawing.Size(0, 17); + this.labelCopyright.Name = "labelCopyright"; + this.labelCopyright.Size = new System.Drawing.Size(90, 13); + this.labelCopyright.TabIndex = 21; + this.labelCopyright.Text = "Copyright © 2009"; + this.labelCopyright.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.labelCompanyName.AutoSize = true; + this.labelCompanyName.Location = new System.Drawing.Point(85, 69); + this.labelCompanyName.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); + this.labelCompanyName.MaximumSize = new System.Drawing.Size(0, 17); + this.labelCompanyName.Name = "labelCompanyName"; + this.labelCompanyName.Size = new System.Drawing.Size(135, 13); + this.labelCompanyName.TabIndex = 22; + this.labelCompanyName.Text = "Virtual World Entertainment"; + this.labelCompanyName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.okButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.okButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.okButton.Location = new System.Drawing.Point(358, 234); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 24; + this.okButton.Text = "&OK"; + this.picLogo.Location = new System.Drawing.Point(12, 12); + this.picLogo.Name = "picLogo"; + this.picLogo.Size = new System.Drawing.Size(64, 70); + this.picLogo.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picLogo.TabIndex = 1; + this.picLogo.TabStop = false; + this.textBoxDescription.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.textBoxDescription.Location = new System.Drawing.Point(12, 88); + this.textBoxDescription.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); + this.textBoxDescription.Multiline = true; + this.textBoxDescription.Name = "textBoxDescription"; + this.textBoxDescription.ReadOnly = true; + this.textBoxDescription.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.textBoxDescription.Size = new System.Drawing.Size(421, 140); + this.textBoxDescription.TabIndex = 23; + this.textBoxDescription.TabStop = false; + base.AcceptButton = this.okButton; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(445, 269); + base.Controls.Add(this.textBoxDescription); + base.Controls.Add(this.labelCompanyName); + base.Controls.Add(this.okButton); + base.Controls.Add(this.labelCopyright); + base.Controls.Add(this.labelVersion); + base.Controls.Add(this.labelProductName); + base.Controls.Add(this.picLogo); + base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + base.MaximizeBox = false; + base.MinimizeBox = false; + base.Name = "abtTeslaConsole"; + base.Padding = new System.Windows.Forms.Padding(9); + base.ShowIcon = false; + base.ShowInTaskbar = false; + base.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "About"; + ((System.ComponentModel.ISupportInitialize)this.picLogo).EndInit(); + base.ResumeLayout(false); + base.PerformLayout(); + } +} diff --git a/Console/TeslaConsole/dlgAddProduct.cs b/Console/TeslaConsole/dlgAddProduct.cs new file mode 100644 index 0000000..b602143 --- /dev/null +++ b/Console/TeslaConsole/dlgAddProduct.cs @@ -0,0 +1,260 @@ +using System; +using System.Drawing; +using System.IO; +using System.Windows.Forms; + +namespace TeslaConsole; + +/// +/// Defines a new catalog product (a single launch entry) and, optionally, an install +/// package to deploy. The validated result is exposed as ; the +/// caller persists it to the catalog and (optionally) pushes it to selected pods. +/// +internal class dlgAddProduct : Form +{ + private TextBox txtName; + + private TextBox txtInstallDir; + + private TextBox txtExe; + + private TextBox txtArgs; + + private TextBox txtPackage; + + private CheckBox chkAutoRestart; + + private CheckBox chkPushNow; + + private Button cmdOK; + + private Button cmdCancel; + + /// The product the operator defined. Valid only after the dialog returns . + public ProductDefinition Product { get; private set; } + + /// Optional install package (zip) to deploy with the entry; null/empty for entry-only. + public string PackagePath => string.IsNullOrWhiteSpace(txtPackage.Text) ? null : txtPackage.Text.Trim(); + + /// Whether to push the new product to the currently selected pods after adding it. + public bool PushNow => chkPushNow.Checked; + + public dlgAddProduct() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.txtName = new TextBox(); + this.txtInstallDir = new TextBox(); + this.txtExe = new TextBox(); + this.txtArgs = new TextBox(); + this.txtPackage = new TextBox(); + this.chkAutoRestart = new CheckBox(); + this.chkPushNow = new CheckBox(); + this.cmdOK = new Button(); + this.cmdCancel = new Button(); + base.SuspendLayout(); + + int labelX = 12; + int fieldX = 130; + int fieldW = 300; + int browseX = fieldX + fieldW + 6; + int y = 15; + int rowH = 28; + + AddLabel("Product name:", labelX, y); + this.txtName.SetBounds(fieldX, y - 3, fieldW, 20); + y += rowH; + + AddLabel("Install directory:", labelX, y); + this.txtInstallDir.SetBounds(fieldX, y - 3, fieldW, 20); + AddBrowseButton(browseX, y - 4, BrowseInstallDir); + y += rowH; + + AddLabel("Executable:", labelX, y); + this.txtExe.SetBounds(fieldX, y - 3, fieldW, 20); + AddBrowseButton(browseX, y - 4, BrowseExe); + y += rowH; + + AddLabel("Arguments:", labelX, y); + this.txtArgs.SetBounds(fieldX, y - 3, fieldW, 20); + y += rowH; + + AddLabel("Install package (optional):", labelX, y); + this.txtPackage.SetBounds(fieldX, y - 3, fieldW, 20); + AddBrowseButton(browseX, y - 4, BrowsePackage); + y += rowH; + + this.chkAutoRestart.SetBounds(fieldX, y, 200, 20); + this.chkAutoRestart.Text = "Auto-restart if it exits"; + this.chkAutoRestart.Checked = true; + y += 24; + + this.chkPushNow.SetBounds(fieldX, y, 280, 20); + this.chkPushNow.Text = "Push to selected pods now"; + this.chkPushNow.Checked = true; + y += 36; + + this.cmdOK.SetBounds(browseX - 169, y, 80, 24); + this.cmdOK.Text = "OK"; + this.cmdOK.Click += cmdOK_Click; + + this.cmdCancel.SetBounds(browseX - 83, y, 80, 24); + this.cmdCancel.Text = "Cancel"; + this.cmdCancel.DialogResult = DialogResult.Cancel; + + base.AcceptButton = this.cmdOK; + base.CancelButton = this.cmdCancel; + base.FormBorderStyle = FormBorderStyle.FixedDialog; + base.MaximizeBox = false; + base.MinimizeBox = false; + base.StartPosition = FormStartPosition.CenterParent; + base.ClientSize = new Size(browseX + 40, y + 36); + base.Controls.Add(this.txtName); + base.Controls.Add(this.txtInstallDir); + base.Controls.Add(this.txtExe); + base.Controls.Add(this.txtArgs); + base.Controls.Add(this.txtPackage); + base.Controls.Add(this.chkAutoRestart); + base.Controls.Add(this.chkPushNow); + base.Controls.Add(this.cmdOK); + base.Controls.Add(this.cmdCancel); + base.Name = "dlgAddProduct"; + this.Text = "Add Product"; + base.ResumeLayout(false); + base.PerformLayout(); + } + + private void AddLabel(string text, int x, int y) + { + Label label = new Label + { + Text = text, + AutoSize = true + }; + label.SetBounds(x, y, 110, 16); + base.Controls.Add(label); + } + + private void AddBrowseButton(int x, int y, EventHandler onClick) + { + Button button = new Button + { + Text = "..." + }; + button.SetBounds(x, y, 30, 22); + button.Click += onClick; + base.Controls.Add(button); + } + + private void BrowseInstallDir(object sender, EventArgs e) + { + using FolderBrowserDialog dialog = new FolderBrowserDialog(); + if (!string.IsNullOrWhiteSpace(txtInstallDir.Text)) + { + dialog.SelectedPath = txtInstallDir.Text; + } + if (dialog.ShowDialog(this) == DialogResult.OK) + { + txtInstallDir.Text = dialog.SelectedPath; + } + } + + private void BrowseExe(object sender, EventArgs e) + { + using OpenFileDialog dialog = new OpenFileDialog + { + Filter = "Executables (*.exe)|*.exe|All Files (*.*)|*.*", + Title = "Select Executable" + }; + if (dialog.ShowDialog(this) == DialogResult.OK) + { + txtExe.Text = dialog.FileName; + } + } + + private void BrowsePackage(object sender, EventArgs e) + { + using OpenFileDialog dialog = new OpenFileDialog + { + Filter = "VWE Install Archive (*.zip)|*.zip|All Files (*.*)|*.*", + Title = "Select Install Package" + }; + if (dialog.ShowDialog(this) == DialogResult.OK) + { + txtPackage.Text = dialog.FileName; + } + } + + private void cmdOK_Click(object sender, EventArgs e) + { + string name = txtName.Text.Trim(); + string installDir = txtInstallDir.Text.Trim(); + string exe = txtExe.Text.Trim(); + + if (name.Length == 0) + { + Warn("Please enter a product name."); + return; + } + if (exe.Length == 0) + { + Warn("Please specify the executable path."); + return; + } + if (installDir.Length == 0) + { + Warn("Please specify the install directory."); + return; + } + + string fullExe, fullDir; + try + { + fullExe = Path.GetFullPath(exe); + fullDir = Path.GetFullPath(installDir); + } + catch (Exception) + { + Warn("The executable or install directory path is not valid."); + return; + } + + // Invariant: the exe must live under the install directory the package extracts to. + string dirWithSep = fullDir.TrimEnd('\\', '/') + "\\"; + if (!fullExe.StartsWith(dirWithSep, StringComparison.OrdinalIgnoreCase)) + { + Warn("The executable must be located inside the install directory:\n" + fullDir); + return; + } + + Guid id = Guid.NewGuid(); + Product = new ProductDefinition + { + Id = id, + Name = name, + MenuText = name + "...", + HostTypeDialog = false + }; + Product.Entries.Add(new LaunchEntryDefinition + { + LaunchKey = id, + DisplayName = name, + Exe = fullExe, + Args = txtArgs.Text.Trim(), + WorkingDirectory = fullDir, + AutoRestart = chkAutoRestart.Checked, + HostType = AppHostType.None + }); + + base.DialogResult = DialogResult.OK; + Close(); + } + + private void Warn(string message) + { + MessageBox.Show(this, message, "Add Product", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } +} diff --git a/Console/TeslaConsole/dlgOptionalStartParams.cs b/Console/TeslaConsole/dlgOptionalStartParams.cs new file mode 100644 index 0000000..284b69b --- /dev/null +++ b/Console/TeslaConsole/dlgOptionalStartParams.cs @@ -0,0 +1,266 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Text.RegularExpressions; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class dlgOptionalStartParams : Form +{ + private IContainer components; + + private Label label1; + + private TableLayoutPanel tlpResolution; + + private Label label2; + + private CheckBox chkCustomResolution; + + private TextBox txtResX; + + private TextBox txtResY; + + private Label label3; + + private Label label4; + + private CheckBox chkLiveCamera; + + private Button cmdOK; + + private CheckBox chkGameClient; + + private CheckBox chkMissionReview; + + private Label label5; + + public Size? Resolution + { + get + { + if (chkCustomResolution.Checked && txtResX.Text != "" && txtResY.Text != "") + { + return new Size(int.Parse(txtResX.Text), int.Parse(txtResY.Text)); + } + return null; + } + set + { + chkCustomResolution.Checked = value.HasValue; + if (value.HasValue) + { + txtResX.Text = value.Value.Width.ToString(); + txtResY.Text = value.Value.Height.ToString(); + } + } + } + + public bool IsGameClient + { + get + { + return chkGameClient.Checked; + } + set + { + chkGameClient.Checked = value; + } + } + + public bool IsLiveCam + { + get + { + return chkLiveCamera.Checked; + } + set + { + chkLiveCamera.Checked = value; + } + } + + public bool IsMissionReview + { + get + { + return chkMissionReview.Checked; + } + set + { + chkMissionReview.Checked = value; + } + } + + public dlgOptionalStartParams() + { + InitializeComponent(); + } + + private void chkCustomResolution_CheckedChanged(object sender, EventArgs e) + { + tlpResolution.Enabled = chkCustomResolution.Checked; + } + + private void txtRes_TextChanged(object sender, EventArgs e) + { + TextBox textBox = (TextBox)sender; + int selectionStart = textBox.SelectionStart; + int selectionLength = textBox.SelectionLength; + textBox.Text = Regex.Replace(textBox.Text, "[^0-9]", ""); + textBox.Select(selectionStart, selectionLength); + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.tlpResolution = new System.Windows.Forms.TableLayoutPanel(); + this.txtResX = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.txtResY = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.chkCustomResolution = new System.Windows.Forms.CheckBox(); + this.chkLiveCamera = new System.Windows.Forms.CheckBox(); + this.cmdOK = new System.Windows.Forms.Button(); + this.chkGameClient = new System.Windows.Forms.CheckBox(); + this.chkMissionReview = new System.Windows.Forms.CheckBox(); + this.label5 = new System.Windows.Forms.Label(); + this.tlpResolution.SuspendLayout(); + base.SuspendLayout(); + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(195, 32); + this.label1.TabIndex = 0; + this.label1.Text = "Select options you would like sent to Red Planet when it starts."; + this.tlpResolution.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.tlpResolution.ColumnCount = 3; + this.tlpResolution.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50f)); + this.tlpResolution.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpResolution.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50f)); + this.tlpResolution.Controls.Add(this.txtResX, 0, 1); + this.tlpResolution.Controls.Add(this.label2, 0, 0); + this.tlpResolution.Controls.Add(this.txtResY, 2, 1); + this.tlpResolution.Controls.Add(this.label3, 1, 1); + this.tlpResolution.Controls.Add(this.label4, 2, 0); + this.tlpResolution.Enabled = false; + this.tlpResolution.Location = new System.Drawing.Point(27, 67); + this.tlpResolution.Name = "tlpResolution"; + this.tlpResolution.RowCount = 2; + this.tlpResolution.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpResolution.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpResolution.Size = new System.Drawing.Size(180, 42); + this.tlpResolution.TabIndex = 1; + this.txtResX.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtResX.Location = new System.Drawing.Point(3, 17); + this.txtResX.MaxLength = 5; + this.txtResX.Name = "txtResX"; + this.txtResX.Size = new System.Drawing.Size(75, 20); + this.txtResX.TabIndex = 3; + this.txtResX.TextChanged += new System.EventHandler(txtRes_TextChanged); + this.label2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(35, 13); + this.label2.TabIndex = 3; + this.label2.Text = "Width"; + this.txtResY.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.txtResY.Location = new System.Drawing.Point(102, 17); + this.txtResY.MaxLength = 5; + this.txtResY.Name = "txtResY"; + this.txtResY.Size = new System.Drawing.Size(75, 20); + this.txtResY.TabIndex = 5; + this.txtResY.TextChanged += new System.EventHandler(txtRes_TextChanged); + this.label3.Anchor = System.Windows.Forms.AnchorStyles.None; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(84, 21); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(12, 13); + this.label3.TabIndex = 6; + this.label3.Text = "x"; + this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.label4.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left; + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(102, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(38, 13); + this.label4.TabIndex = 7; + this.label4.Text = "Height"; + this.chkCustomResolution.AutoSize = true; + this.chkCustomResolution.Location = new System.Drawing.Point(12, 44); + this.chkCustomResolution.Name = "chkCustomResolution"; + this.chkCustomResolution.Size = new System.Drawing.Size(114, 17); + this.chkCustomResolution.TabIndex = 2; + this.chkCustomResolution.Text = "Custom Resolution"; + this.chkCustomResolution.UseVisualStyleBackColor = true; + this.chkCustomResolution.CheckedChanged += new System.EventHandler(chkCustomResolution_CheckedChanged); + this.chkLiveCamera.AutoSize = true; + this.chkLiveCamera.Location = new System.Drawing.Point(12, 185); + this.chkLiveCamera.Name = "chkLiveCamera"; + this.chkLiveCamera.Size = new System.Drawing.Size(85, 17); + this.chkLiveCamera.TabIndex = 3; + this.chkLiveCamera.Text = "Live Camera"; + this.chkLiveCamera.UseVisualStyleBackColor = true; + this.cmdOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + this.cmdOK.DialogResult = System.Windows.Forms.DialogResult.OK; + this.cmdOK.Location = new System.Drawing.Point(132, 244); + this.cmdOK.Name = "cmdOK"; + this.cmdOK.Size = new System.Drawing.Size(75, 23); + this.cmdOK.TabIndex = 4; + this.cmdOK.Text = "OK"; + this.cmdOK.UseVisualStyleBackColor = true; + this.chkGameClient.AutoSize = true; + this.chkGameClient.Location = new System.Drawing.Point(12, 162); + this.chkGameClient.Name = "chkGameClient"; + this.chkGameClient.Size = new System.Drawing.Size(83, 17); + this.chkGameClient.TabIndex = 5; + this.chkGameClient.Text = "Game Client"; + this.chkGameClient.UseVisualStyleBackColor = true; + this.chkMissionReview.AutoSize = true; + this.chkMissionReview.Location = new System.Drawing.Point(12, 208); + this.chkMissionReview.Name = "chkMissionReview"; + this.chkMissionReview.Size = new System.Drawing.Size(100, 17); + this.chkMissionReview.TabIndex = 6; + this.chkMissionReview.Text = "Mission Review"; + this.chkMissionReview.UseVisualStyleBackColor = true; + this.label5.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.label5.Location = new System.Drawing.Point(9, 127); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(195, 32); + this.label5.TabIndex = 7; + this.label5.Text = "Select which roles you would like this machine to fill."; + base.AcceptButton = this.cmdOK; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(219, 279); + base.Controls.Add(this.label5); + base.Controls.Add(this.chkMissionReview); + base.Controls.Add(this.chkGameClient); + base.Controls.Add(this.cmdOK); + base.Controls.Add(this.chkLiveCamera); + base.Controls.Add(this.label1); + base.Controls.Add(this.chkCustomResolution); + base.Controls.Add(this.tlpResolution); + base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + base.MaximizeBox = false; + base.MinimizeBox = false; + base.Name = "dlgOptionalStartParams"; + this.Text = "Select Optional Parameters"; + this.tlpResolution.ResumeLayout(false); + this.tlpResolution.PerformLayout(); + base.ResumeLayout(false); + base.PerformLayout(); + } +} diff --git a/Console/TeslaConsole/dlgProgress.cs b/Console/TeslaConsole/dlgProgress.cs new file mode 100644 index 0000000..4e3a0ef --- /dev/null +++ b/Console/TeslaConsole/dlgProgress.cs @@ -0,0 +1,84 @@ +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace TeslaConsole; + +public class dlgProgress : Form +{ + private IContainer components; + + private Label lblStatus; + + private ProgressBar pbarProgress; + + public int ProgressPercentage + { + get + { + return pbarProgress.Value; + } + set + { + pbarProgress.Value = value; + } + } + + public string Status + { + get + { + return lblStatus.Text; + } + set + { + lblStatus.Text = value; + } + } + + public dlgProgress() + { + InitializeComponent(); + } + + protected override void Dispose(bool disposing) + { + if (disposing && components != null) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.lblStatus = new System.Windows.Forms.Label(); + this.pbarProgress = new System.Windows.Forms.ProgressBar(); + base.SuspendLayout(); + this.lblStatus.AutoSize = true; + this.lblStatus.Location = new System.Drawing.Point(12, 9); + this.lblStatus.Name = "lblStatus"; + this.lblStatus.Size = new System.Drawing.Size(96, 13); + this.lblStatus.TabIndex = 0; + this.lblStatus.Text = "Status goes here..."; + this.pbarProgress.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + this.pbarProgress.Location = new System.Drawing.Point(15, 25); + this.pbarProgress.Name = "pbarProgress"; + this.pbarProgress.Size = new System.Drawing.Size(364, 23); + this.pbarProgress.TabIndex = 1; + base.AutoScaleDimensions = new System.Drawing.SizeF(6f, 13f); + base.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + base.ClientSize = new System.Drawing.Size(391, 57); + base.Controls.Add(this.pbarProgress); + base.Controls.Add(this.lblStatus); + base.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + base.MaximizeBox = false; + base.MinimizeBox = false; + base.Name = "dlgProgress"; + base.ShowInTaskbar = false; + base.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Progress"; + base.ResumeLayout(false); + base.PerformLayout(); + } +} diff --git a/Console/app.ico b/Console/app.ico new file mode 100644 index 0000000..5a2e676 Binary files /dev/null and b/Console/app.ico differ diff --git a/Console/images/pod art/Age of Discovery.bmp b/Console/images/pod art/Age of Discovery.bmp new file mode 100644 index 0000000..f7040a8 Binary files /dev/null and b/Console/images/pod art/Age of Discovery.bmp differ diff --git a/Console/images/pod art/Calamity Jane.bmp b/Console/images/pod art/Calamity Jane.bmp new file mode 100644 index 0000000..22e8791 Binary files /dev/null and b/Console/images/pod art/Calamity Jane.bmp differ diff --git a/Console/images/pod art/Call of the Wild.bmp b/Console/images/pod art/Call of the Wild.bmp new file mode 100644 index 0000000..0657dd8 Binary files /dev/null and b/Console/images/pod art/Call of the Wild.bmp differ diff --git a/Console/images/pod art/Carpe Diem.bmp b/Console/images/pod art/Carpe Diem.bmp new file mode 100644 index 0000000..b3f5794 Binary files /dev/null and b/Console/images/pod art/Carpe Diem.bmp differ diff --git a/Console/images/pod art/Divine Wind.bmp b/Console/images/pod art/Divine Wind.bmp new file mode 100644 index 0000000..9abd85c Binary files /dev/null and b/Console/images/pod art/Divine Wind.bmp differ diff --git a/Console/images/pod art/Fly Girl.bmp b/Console/images/pod art/Fly Girl.bmp new file mode 100644 index 0000000..b374ab9 Binary files /dev/null and b/Console/images/pod art/Fly Girl.bmp differ diff --git a/Console/images/pod art/Flying Blind.bmp b/Console/images/pod art/Flying Blind.bmp new file mode 100644 index 0000000..d734396 Binary files /dev/null and b/Console/images/pod art/Flying Blind.bmp differ diff --git a/Console/images/pod art/Flying Dutchman.bmp b/Console/images/pod art/Flying Dutchman.bmp new file mode 100644 index 0000000..126faaa Binary files /dev/null and b/Console/images/pod art/Flying Dutchman.bmp differ diff --git a/Console/images/pod art/Frequent Flyer.bmp b/Console/images/pod art/Frequent Flyer.bmp new file mode 100644 index 0000000..d573185 Binary files /dev/null and b/Console/images/pod art/Frequent Flyer.bmp differ diff --git a/Console/images/pod art/Gypsy.bmp b/Console/images/pod art/Gypsy.bmp new file mode 100644 index 0000000..751156c Binary files /dev/null and b/Console/images/pod art/Gypsy.bmp differ diff --git a/Console/images/pod art/Icarus.bmp b/Console/images/pod art/Icarus.bmp new file mode 100644 index 0000000..5ba6e03 Binary files /dev/null and b/Console/images/pod art/Icarus.bmp differ diff --git a/Console/images/pod art/Man O'War.bmp b/Console/images/pod art/Man O'War.bmp new file mode 100644 index 0000000..0c101b5 Binary files /dev/null and b/Console/images/pod art/Man O'War.bmp differ diff --git a/Console/images/pod art/Martian Belle.bmp b/Console/images/pod art/Martian Belle.bmp new file mode 100644 index 0000000..cea51e4 Binary files /dev/null and b/Console/images/pod art/Martian Belle.bmp differ diff --git a/Console/images/pod art/Medicine Man.bmp b/Console/images/pod art/Medicine Man.bmp new file mode 100644 index 0000000..0727d6d Binary files /dev/null and b/Console/images/pod art/Medicine Man.bmp differ diff --git a/Console/images/pod art/Mistress Quickly.bmp b/Console/images/pod art/Mistress Quickly.bmp new file mode 100644 index 0000000..585452b Binary files /dev/null and b/Console/images/pod art/Mistress Quickly.bmp differ diff --git a/Console/images/pod art/Nomad.bmp b/Console/images/pod art/Nomad.bmp new file mode 100644 index 0000000..e365e7a Binary files /dev/null and b/Console/images/pod art/Nomad.bmp differ diff --git a/Console/images/pod art/Pegasus.bmp b/Console/images/pod art/Pegasus.bmp new file mode 100644 index 0000000..3c5e74f Binary files /dev/null and b/Console/images/pod art/Pegasus.bmp differ diff --git a/Console/images/pod art/Privateer.bmp b/Console/images/pod art/Privateer.bmp new file mode 100644 index 0000000..da9ed68 Binary files /dev/null and b/Console/images/pod art/Privateer.bmp differ diff --git a/Console/images/pod art/Puck.bmp b/Console/images/pod art/Puck.bmp new file mode 100644 index 0000000..f778c99 Binary files /dev/null and b/Console/images/pod art/Puck.bmp differ diff --git a/Console/images/pod art/Rapid Transit.bmp b/Console/images/pod art/Rapid Transit.bmp new file mode 100644 index 0000000..7dbe2ea Binary files /dev/null and b/Console/images/pod art/Rapid Transit.bmp differ diff --git a/Console/images/pod art/Sarcophagus.bmp b/Console/images/pod art/Sarcophagus.bmp new file mode 100644 index 0000000..8789824 Binary files /dev/null and b/Console/images/pod art/Sarcophagus.bmp differ diff --git a/Console/images/pod art/Snake Eyes.bmp b/Console/images/pod art/Snake Eyes.bmp new file mode 100644 index 0000000..41f0ddd Binary files /dev/null and b/Console/images/pod art/Snake Eyes.bmp differ diff --git a/Console/images/pod art/Unto the Breech.bmp b/Console/images/pod art/Unto the Breech.bmp new file mode 100644 index 0000000..ebac63c Binary files /dev/null and b/Console/images/pod art/Unto the Breech.bmp differ diff --git a/Console/images/pod art/Valkyrie.bmp b/Console/images/pod art/Valkyrie.bmp new file mode 100644 index 0000000..0669155 Binary files /dev/null and b/Console/images/pod art/Valkyrie.bmp differ diff --git a/Console/images/red planet maps/Berserker's Bahnzai.bmp b/Console/images/red planet maps/Berserker's Bahnzai.bmp new file mode 100644 index 0000000..a11b14f Binary files /dev/null and b/Console/images/red planet maps/Berserker's Bahnzai.bmp differ diff --git a/Console/images/red planet maps/Blade's Edge.bmp b/Console/images/red planet maps/Blade's Edge.bmp new file mode 100644 index 0000000..8ebf986 Binary files /dev/null and b/Console/images/red planet maps/Blade's Edge.bmp differ diff --git a/Console/images/red planet maps/Brewer's Bane.bmp b/Console/images/red planet maps/Brewer's Bane.bmp new file mode 100644 index 0000000..92ddf6f Binary files /dev/null and b/Console/images/red planet maps/Brewer's Bane.bmp differ diff --git a/Console/images/red planet maps/Burnt Cookies.bmp b/Console/images/red planet maps/Burnt Cookies.bmp new file mode 100644 index 0000000..62a8b9b Binary files /dev/null and b/Console/images/red planet maps/Burnt Cookies.bmp differ diff --git a/Console/images/red planet maps/Firestorm's Fury.bmp b/Console/images/red planet maps/Firestorm's Fury.bmp new file mode 100644 index 0000000..3060b80 Binary files /dev/null and b/Console/images/red planet maps/Firestorm's Fury.bmp differ diff --git a/Console/images/red planet maps/Lyz's Lane.bmp b/Console/images/red planet maps/Lyz's Lane.bmp new file mode 100644 index 0000000..25a0792 Binary files /dev/null and b/Console/images/red planet maps/Lyz's Lane.bmp differ diff --git a/Console/images/red planet maps/Pain God's Passage.bmp b/Console/images/red planet maps/Pain God's Passage.bmp new file mode 100644 index 0000000..303f416 Binary files /dev/null and b/Console/images/red planet maps/Pain God's Passage.bmp differ diff --git a/Console/images/red planet maps/Wiseguy's Wake.bmp b/Console/images/red planet maps/Wiseguy's Wake.bmp new file mode 100644 index 0000000..b590d67 Binary files /dev/null and b/Console/images/red planet maps/Wiseguy's Wake.bmp differ diff --git a/Console/images/red planet maps/Yip's Yahoorama.bmp b/Console/images/red planet maps/Yip's Yahoorama.bmp new file mode 100644 index 0000000..8eef30b Binary files /dev/null and b/Console/images/red planet maps/Yip's Yahoorama.bmp differ diff --git a/Console/images/red planet vehicles/Armadillo.bmp b/Console/images/red planet vehicles/Armadillo.bmp new file mode 100644 index 0000000..5ea7a9e Binary files /dev/null and b/Console/images/red planet vehicles/Armadillo.bmp differ diff --git a/Console/images/red planet vehicles/Bug.bmp b/Console/images/red planet vehicles/Bug.bmp new file mode 100644 index 0000000..02c1e40 Binary files /dev/null and b/Console/images/red planet vehicles/Bug.bmp differ diff --git a/Console/images/red planet vehicles/Bull.bmp b/Console/images/red planet vehicles/Bull.bmp new file mode 100644 index 0000000..9d5a9f4 Binary files /dev/null and b/Console/images/red planet vehicles/Bull.bmp differ diff --git a/Console/images/red planet vehicles/Mule.bmp b/Console/images/red planet vehicles/Mule.bmp new file mode 100644 index 0000000..63aaca1 Binary files /dev/null and b/Console/images/red planet vehicles/Mule.bmp differ diff --git a/Console/images/red planet vehicles/Police.bmp b/Console/images/red planet vehicles/Police.bmp new file mode 100644 index 0000000..dafb0f7 Binary files /dev/null and b/Console/images/red planet vehicles/Police.bmp differ diff --git a/Console/images/red planet vehicles/Quark.bmp b/Console/images/red planet vehicles/Quark.bmp new file mode 100644 index 0000000..8d22067 Binary files /dev/null and b/Console/images/red planet vehicles/Quark.bmp differ diff --git a/Console/images/red planet vehicles/Roadblock.bmp b/Console/images/red planet vehicles/Roadblock.bmp new file mode 100644 index 0000000..207d1dd Binary files /dev/null and b/Console/images/red planet vehicles/Roadblock.bmp differ diff --git a/Console/images/red planet vehicles/Skeeter.bmp b/Console/images/red planet vehicles/Skeeter.bmp new file mode 100644 index 0000000..73bfc19 Binary files /dev/null and b/Console/images/red planet vehicles/Skeeter.bmp differ diff --git a/Console/images/red planet vehicles/Vole.bmp b/Console/images/red planet vehicles/Vole.bmp new file mode 100644 index 0000000..3c01bd1 Binary files /dev/null and b/Console/images/red planet vehicles/Vole.bmp differ diff --git a/Console/installer_banner.bmp b/Console/installer_banner.bmp new file mode 100644 index 0000000..efeedd4 Binary files /dev/null and b/Console/installer_banner.bmp differ diff --git a/Console/lib/BitmapLibrary.dll b/Console/lib/BitmapLibrary.dll new file mode 100644 index 0000000..0e079f5 Binary files /dev/null and b/Console/lib/BitmapLibrary.dll differ diff --git a/Console/lib/Munga Net.dll b/Console/lib/Munga Net.dll new file mode 100644 index 0000000..0a281f5 Binary files /dev/null and b/Console/lib/Munga Net.dll differ diff --git a/Console/lib/TeslaConsoleLaunchLib.dll b/Console/lib/TeslaConsoleLaunchLib.dll new file mode 100644 index 0000000..25e63aa Binary files /dev/null and b/Console/lib/TeslaConsoleLaunchLib.dll differ diff --git a/Console/lib/TeslaSecureConfiguration.dll b/Console/lib/TeslaSecureConfiguration.dll new file mode 100644 index 0000000..f1ee032 Binary files /dev/null and b/Console/lib/TeslaSecureConfiguration.dll differ diff --git a/Console/lib/WeifenLuo.WinFormsUI.Docking.dll b/Console/lib/WeifenLuo.WinFormsUI.Docking.dll new file mode 100644 index 0000000..c232381 Binary files /dev/null and b/Console/lib/WeifenLuo.WinFormsUI.Docking.dll differ diff --git a/Console/lib/WeifenLuo.txt b/Console/lib/WeifenLuo.txt new file mode 100644 index 0000000..96f3fa5 --- /dev/null +++ b/Console/lib/WeifenLuo.txt @@ -0,0 +1,9 @@ +The MIT License + +Copyright (c) 2007 Weifen Luo (email: weifenluo@yahoo.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Console/original/TeslaConsole.InstallState b/Console/original/TeslaConsole.InstallState new file mode 100644 index 0000000..7e57f5f --- /dev/null +++ b/Console/original/TeslaConsole.InstallState @@ -0,0 +1,43 @@ + + + +0.72 +2 + + +11 + + + + +_reserved_nestedSavedStates +_reserved_lastInstallerAttempted + + + +0 + + + + + +0.72 +2 + + +11 + + + + + + + + + +-1 + + + + + diff --git a/Console/original/TeslaConsole.exe b/Console/original/TeslaConsole.exe new file mode 100644 index 0000000..8323479 Binary files /dev/null and b/Console/original/TeslaConsole.exe differ diff --git a/Console/tests/TeslaConsole.DiffTests/AssemblyPaths.cs b/Console/tests/TeslaConsole.DiffTests/AssemblyPaths.cs new file mode 100644 index 0000000..08b3467 --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/AssemblyPaths.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +namespace TeslaConsole.DiffTests +{ + /// + /// Locates the two assemblies under comparison: + /// * Original - original/TeslaConsole.exe (the lost-source reference baseline) + /// * Recovered - bin/Release/net48/TeslaConsole.exe (freshly built reconstruction) + /// + public static class AssemblyPaths + { + public static string RepoRoot { get; } = FindRepoRoot(); + + public static string OriginalExe { get; } = + Path.Combine(RepoRoot, "original", "TeslaConsole.exe"); + + // The reconstruction's build output. Release is what README documents; fall + // back to Debug so the suite still runs from either configuration. + public static string RecoveredExe { get; } = FindRecoveredExe(); + + // The product catalog copied next to the recovered exe. + public static string RecoveredCatalog { get; } = + Path.Combine(Path.GetDirectoryName(RecoveredExe), "RedPlanet", "Apps.xml"); + + private static string FindRepoRoot() + { + var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); + while (dir != null) + { + if (File.Exists(Path.Combine(dir.FullName, "TeslaConsole.csproj"))) + return dir.FullName; + dir = dir.Parent; + } + throw new InvalidOperationException( + "Could not locate repo root (TeslaConsole.csproj) above " + + AppDomain.CurrentDomain.BaseDirectory); + } + + private static string FindRecoveredExe() + { + string release = Path.Combine(RepoRoot, "bin", "Release", "net48", "TeslaConsole.exe"); + string debug = Path.Combine(RepoRoot, "bin", "Debug", "net48", "TeslaConsole.exe"); + + // Test whichever build is freshest, so a stale config never silently wins. + string best = null; + DateTime bestTime = DateTime.MinValue; + foreach (var candidate in new[] { release, debug }) + { + if (!File.Exists(candidate)) continue; + var t = File.GetLastWriteTimeUtc(candidate); + if (t >= bestTime) { bestTime = t; best = candidate; } + } + // Fall back to the Release path; callers assert existence with a clear message. + return best ?? release; + } + } +} diff --git a/Console/tests/TeslaConsole.DiffTests/BehavioralEquivalenceTests.cs b/Console/tests/TeslaConsole.DiffTests/BehavioralEquivalenceTests.cs new file mode 100644 index 0000000..9b083d3 --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/BehavioralEquivalenceTests.cs @@ -0,0 +1,135 @@ +using System; +using Xunit; + +namespace TeslaConsole.DiffTests +{ + /// + /// For every input below, the original and the recovered build must return the + /// exact same result. Each [Theory] case runs the same method in both assemblies + /// (in their own AppDomains) and asserts byte-for-byte string equality. + /// + public class BehavioralEquivalenceTests : IClassFixture + { + private readonly DifferentialFixture _fx; + + public BehavioralEquivalenceTests(DifferentialFixture fx) => _fx = fx; + + private void AssertSame(string caseName, params string[] args) + { + var (original, recovered) = _fx.RunBoth(caseName, args); + Assert.Equal(original, recovered); + // Guard against both silently throwing the same way for a case that should succeed. + Assert.False(original == null, + $"{caseName}({string.Join(",", args)}) returned null from both assemblies"); + } + + [Fact] + public void Harness_Distinguishes_Different_Outputs() + { + // Negative control: a passing suite must be capable of seeing a difference. + // Same method, different inputs, must yield different results in each assembly. + string ninetySec = (90L * TimeSpan.TicksPerSecond).ToString(); + var zero = _fx.Original.Run("GetTimeString", new[] { "0" }); + var ninety = _fx.Original.Run("GetTimeString", new[] { ninetySec }); + Assert.NotEqual(zero, ninety); + + var rZero = _fx.Recovered.Run("GetTimeString", new[] { "0" }); + var rNinety = _fx.Recovered.Run("GetTimeString", new[] { ninetySec }); + Assert.NotEqual(rZero, rNinety); + } + + [Fact] + public void Loaded_Assemblies_Share_Identity() + { + // Both must really be TeslaConsole 4.11.3.37076 — guards against testing the wrong files. + Assert.Equal(_fx.Original.AssemblyFullName, _fx.Recovered.AssemblyFullName); + Assert.Contains("TeslaConsole", _fx.Original.AssemblyFullName); + } + + // ---- RPStrings.GetTimeString: mm:ss formatting with 0.5s rounding ---- + [Theory] + [InlineData(0L)] // 0s -> 00:00 + [InlineData(4_000_000L)] // 0.4s -> rounds down + [InlineData(5_000_000L)] // 0.5s -> rounds up to 1s + [InlineData(6_000_000L)] // 0.6s + [InlineData(595_000_000L)] // 59.5s -> rolls to 01:00 + [InlineData(600_000_000L)] // 60s -> 01:00 + [InlineData(900_000_000L)] // 90s -> 01:30 + [InlineData(5_990_000_000L)] // 599s + [InlineData(6_000_000_000L)] // 600s -> 10:00 + [InlineData(36_000_000_000L)] // 3600s -> 60:00 + [InlineData(-50_000_000L)] // negative + [InlineData(864_000_000_000L)] // 1 day + public void GetTimeString_Matches(long ticks) + => AssertSame("GetTimeString", ticks.ToString()); + + // ---- HostTypeHelper.Parse(...).ToString() including invalid input ---- + [Theory] + [InlineData("Game Machine")] + [InlineData("Mission Review")] + [InlineData("Console")] + [InlineData("GameMachineHostType")] + [InlineData("MissionReviewHostType")] + [InlineData("ConsoleHostType")] + [InlineData("console")] // case-sensitive: should throw in both + [InlineData("Nonsense")] // invalid: ArgumentException in both + [InlineData("")] // empty + public void HostTypeParse_Matches(string input) + => AssertSame("HostTypeParse", input); + + // ---- PlasmaBitmaps.ConvertBitmap: 1bpp packing of a known pixel pattern ---- + [Theory] + [InlineData(8, 8, 0)] + [InlineData(8, 8, 1)] + [InlineData(8, 8, 2)] + [InlineData(16, 16, 0)] + [InlineData(16, 16, 3)] + [InlineData(32, 8, 5)] + [InlineData(7, 8, 0)] // width not multiple of 8 -> throws in both + [InlineData(8, 7, 0)] // height not multiple of 8 -> throws in both + public void ConvertBitmap_Matches(int width, int height, int seed) + => AssertSame("ConvertBitmap", width.ToString(), height.ToString(), seed.ToString()); + + // ---- PlasmaBitmaps.GenerateString: full GDI text -> 1bpp pipeline ---- + [Theory] + [InlineData(128, 32, "Microsoft Sans Serif", "RED")] + [InlineData(64, 16, "Microsoft Sans Serif", "RED")] + [InlineData(128, 32, "Microsoft Sans Serif", "1")] + [InlineData(128, 32, "Microsoft Sans Serif", "ABCDEFGH")] + [InlineData(128, 32, "Microsoft Sans Serif", "")] + [InlineData(128, 32, "Arial", "GO")] + public void GenerateString_Matches(int width, int height, string font, string text) + => AssertSame("GenerateString", width.ToString(), height.ToString(), font, text); + + // ---- RedPlanet RPMap / RPVehicle XML parsing ---- + [Theory] + [InlineData("")] + [InlineData("")] // no image attr + [InlineData("")] // empty values + public void RPMapParse_Matches(string xml) + => AssertSame("RPMapParse", xml); + + [Theory] + [InlineData("")] + [InlineData("")] + public void RPVehicleParse_Matches(string xml) + => AssertSame("RPVehicleParse", xml); + + // ---- SiteManagement well-known application GUIDs (constants) ---- + [Theory] + [InlineData("RPAppGuid")] + [InlineData("RPMRAppGuid")] + [InlineData("RPLCAppGuid")] + [InlineData("MW4AppGuid")] + [InlineData("MW4LCAppGuid")] + public void SiteManagementGuid_Matches(string fieldName) + => AssertSame("SiteManagementGuid", fieldName); + + // ---- Tuple.Create generic factory ---- + [Theory] + [InlineData("1", "alpha")] + [InlineData("-7", "")] + public void TupleCreate_Matches(string a, string b) + => AssertSame("TupleCreate", a, b); + } +} diff --git a/Console/tests/TeslaConsole.DiffTests/CatalogTests.cs b/Console/tests/TeslaConsole.DiffTests/CatalogTests.cs new file mode 100644 index 0000000..fdd4ad7 --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/CatalogTests.cs @@ -0,0 +1,77 @@ +using System.IO; +using Xunit; + +namespace TeslaConsole.DiffTests +{ + /// + /// Characterization tests for the data-driven product catalog (AppRegistry + RedPlanet\Apps.xml). + /// These assert the catalog reproduces the EXACT LaunchData the previously-hardcoded + /// SiteManagement code produced, so the refactor is behavior-preserving. Catalog logic is new + /// (absent from the original exe), so these run against the recovered build only. + /// + public class CatalogTests : IClassFixture + { + private readonly DifferentialFixture _fx; + private readonly string _catalog; + + public CatalogTests(DifferentialFixture fx) + { + _fx = fx; + _catalog = AssemblyPaths.RecoveredCatalog; + Assert.True(File.Exists(_catalog), "Catalog not found next to the build: " + _catalog); + } + + private string Entry(string launchKey, string w = "", string h = "") + => _fx.Recovered.Run("CatalogEntry", new[] { _catalog, launchKey, w, h }); + + [Fact] + public void Catalog_Has_Three_Products_And_Five_Entries() + => Assert.Equal("products=3;entries=5", + _fx.Recovered.Run("CatalogSummary", new[] { _catalog })); + + // Each expected string is "DisplayName|LaunchKey|Exe|Args|WorkingDirectory|AutoRestart" + // and matches exactly what the old hardcoded PodInfo_InstallProductCompleted emitted. + + [Fact] + public void RedPlanet_GameClient_Matches_Original() + => Assert.Equal( + @"Red Planet 4.11|7d241b1f-ab6d-4e08-9c20-12294e743d94|C:\Games\RP411\rpl4opt.exe|-net 1501|C:\Games\RP411|True", + Entry("7D241B1F-AB6D-4e08-9C20-12294E743D94")); + + [Fact] + public void RedPlanet_GameClient_With_Resolution_Matches_Original() + => Assert.Equal( + @"Red Planet 4.11|7d241b1f-ab6d-4e08-9c20-12294e743d94|C:\Games\RP411\rpl4opt.exe|-net 1501 -res 1024 768|C:\Games\RP411|True", + Entry("7D241B1F-AB6D-4e08-9C20-12294E743D94", "1024", "768")); + + [Fact] + public void RedPlanet_LiveCamera_Matches_Original() + => Assert.Equal( + @"Red Planet 4.11 LC|57a0b3c2-d5cf-46d6-abed-a8f4a26ab086|C:\Games\RP411\rpl4opt.exe|-net 1501 -lc|C:\Games\RP411|True", + Entry("57A0B3C2-D5CF-46d6-ABED-A8F4A26AB086")); + + [Fact] + public void RedPlanet_LiveCamera_With_Resolution_Matches_Original() + => Assert.Equal( + @"Red Planet 4.11 LC|57a0b3c2-d5cf-46d6-abed-a8f4a26ab086|C:\Games\RP411\rpl4opt.exe|-net 1501 -res 1024 768 -lc|C:\Games\RP411|True", + Entry("57A0B3C2-D5CF-46d6-ABED-A8F4A26AB086", "1024", "768")); + + [Fact] + public void RedPlanet_MissionReview_Matches_Original() + => Assert.Equal( + @"Red Planet 4.11 MR|8f71d5c2-38e4-413c-8e22-88cad08774d2|C:\Games\RP411\rpl4opt.exe|-net 1501 -mr|C:\Games\RP411|True", + Entry("8F71D5C2-38E4-413c-8E22-88CAD08774D2")); + + [Fact] + public void Firestorm_Matches_Original() + => Assert.Equal( + @"BattleTech Firestorm|cc8500ed-a653-45a7-bef8-c332d30371a6|C:\Games\MW4\launcher.exe||C:\Games\MW4|True", + Entry("CC8500ED-A653-45a7-BEF8-C332D30371A6")); + + [Fact] + public void FirestormLC_Matches_Original() + => Assert.Equal( + @"BattleTech Firestorm LC|8ee93a6c-f16a-49be-b867-37fae9087fff|C:\Games\MW4\launcher.exe||C:\Games\MW4|True", + Entry("8EE93A6C-F16A-49be-B867-37FAE9087FFF")); + } +} diff --git a/Console/tests/TeslaConsole.DiffTests/DifferentialFixture.cs b/Console/tests/TeslaConsole.DiffTests/DifferentialFixture.cs new file mode 100644 index 0000000..2e4b0b4 --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/DifferentialFixture.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using System.Reflection; +using Xunit; + +namespace TeslaConsole.DiffTests +{ + /// + /// Shared per-test-class fixture that spins up one child AppDomain per assembly + /// (original + recovered), each rooted at that assembly's own directory so the CLR + /// probes for any dependencies next to it. The Invoker proxy in each domain loads + /// and exercises that assembly. + /// + public sealed class DifferentialFixture : IDisposable + { + private readonly AppDomain _originalDomain; + private readonly AppDomain _recoveredDomain; + + public Invoker Original { get; } + public Invoker Recovered { get; } + + public DifferentialFixture() + { + Assert.True(File.Exists(AssemblyPaths.OriginalExe), + "Original baseline not found: " + AssemblyPaths.OriginalExe); + Assert.True(File.Exists(AssemblyPaths.RecoveredExe), + "Recovered build not found (build TeslaConsole.csproj -c Release first): " + + AssemblyPaths.RecoveredExe); + + _originalDomain = CreateDomain("Original", AssemblyPaths.OriginalExe); + _recoveredDomain = CreateDomain("Recovered", AssemblyPaths.RecoveredExe); + + Original = CreateInvoker(_originalDomain, AssemblyPaths.OriginalExe); + Recovered = CreateInvoker(_recoveredDomain, AssemblyPaths.RecoveredExe); + } + + private static AppDomain CreateDomain(string name, string exePath) + { + var setup = new AppDomainSetup + { + ApplicationBase = Path.GetDirectoryName(exePath), + }; + return AppDomain.CreateDomain("TeslaDiff_" + name, null, setup); + } + + private static Invoker CreateInvoker(AppDomain domain, string exePath) + { + // Probe directory = recovered output, which ships every dependency DLL. + string probeDir = Path.GetDirectoryName(AssemblyPaths.RecoveredExe); + return (Invoker)domain.CreateInstanceFromAndUnwrap( + typeof(Invoker).Assembly.Location, + typeof(Invoker).FullName, + false, + BindingFlags.Default, + null, + new object[] { exePath, probeDir }, + null, + null); + } + + /// Run a case in both domains and return (original, recovered). + public (string Original, string Recovered) RunBoth(string caseName, params string[] args) + { + return (Original.Run(caseName, args), Recovered.Run(caseName, args)); + } + + public void Dispose() + { + if (_originalDomain != null) AppDomain.Unload(_originalDomain); + if (_recoveredDomain != null) AppDomain.Unload(_recoveredDomain); + } + } +} diff --git a/Console/tests/TeslaConsole.DiffTests/Invoker.cs b/Console/tests/TeslaConsole.DiffTests/Invoker.cs new file mode 100644 index 0000000..d789aaa --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/Invoker.cs @@ -0,0 +1,369 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Xml; + +namespace TeslaConsole.DiffTests +{ + /// + /// Lives inside a child AppDomain whose base directory is the directory of one + /// TeslaConsole.exe. It loads that exe and invokes deterministic, dependency-free + /// methods by reflection, returning a normalized string so results marshal cleanly + /// across the AppDomain boundary. + /// + /// Only methods whose code path touches nothing but the framework + the exe itself + /// are exercised, so the original (which ships without its proprietary dependency + /// DLLs) loads and runs them fine. + /// + public sealed class Invoker : MarshalByRefObject + { + private readonly Assembly _asm; + private readonly string _probeDir; + + public Invoker(string exePath, string probeDir) + { + _probeDir = probeDir; + // The original ships without its proprietary dependency DLLs; resolve any + // referenced assembly out of the recovered build's output directory so both + // assemblies expose the same metadata. + AppDomain.CurrentDomain.AssemblyResolve += ResolveFromProbeDir; + _asm = Assembly.LoadFrom(exePath); + } + + private Assembly ResolveFromProbeDir(object sender, ResolveEventArgs args) + { + var name = new AssemblyName(args.Name); + if (string.Equals(name.Name, "TeslaConsole", StringComparison.OrdinalIgnoreCase)) + return _asm; // self-references bind to the assembly under test + if (string.IsNullOrEmpty(_probeDir)) return null; + foreach (var ext in new[] { ".dll", ".exe" }) + { + string candidate = Path.Combine(_probeDir, name.Name + ext); + if (File.Exists(candidate)) + return Assembly.LoadFrom(candidate); + } + return null; + } + + /// Full identity of the loaded assembly (for sanity assertions). + public string AssemblyFullName => _asm.FullName; + + // ---- Structural (public API surface) enumeration, run inside this domain ---- + + public string[] GetPublicTypeNames() + { + return SafeGetTypes() + .Where(t => (t.IsPublic || t.IsNestedPublic) && !IsCompilerGenerated(t)) + .Select(t => t.FullName) + .Distinct() + .ToArray(); + } + + public string[] GetPublicMemberSignatures() + { + var set = new HashSet(); + const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | + BindingFlags.Static | BindingFlags.DeclaredOnly; + foreach (var t in SafeGetTypes()) + { + if (!(t.IsPublic || t.IsNestedPublic) || IsCompilerGenerated(t)) + continue; + foreach (var m in t.GetMembers(flags)) + { + if (m.Name.IndexOf('<') >= 0 || IsCompilerGenerated(m)) + continue; + // Skip property/event accessor methods: the property/event itself is + // compared via its own entry, and Roslyn vs. the original compiler mark + // these accessors' [CompilerGenerated] differently. Real operators + // (op_*) are kept. + if (IsAccessorMethod(m)) + continue; + try { set.Add(t.FullName + " :: " + DescribeMember(m)); } + catch { /* unresolved member metadata; skip symmetrically */ } + } + } + return set.ToArray(); + } + + private IEnumerable SafeGetTypes() + { + try { return _asm.GetTypes(); } + catch (ReflectionTypeLoadException ex) { return ex.Types.Where(t => t != null); } + } + + private static string DescribeMember(MemberInfo m) + { + switch (m) + { + case MethodInfo mi: + return "M " + mi.ReturnType.FullName + " " + mi.Name + + "(" + string.Join(",", mi.GetParameters().Select(p => p.ParameterType.FullName)) + ")"; + case ConstructorInfo ci: + return "C .ctor(" + + string.Join(",", ci.GetParameters().Select(p => p.ParameterType.FullName)) + ")"; + case PropertyInfo pi: + return "P " + pi.PropertyType.FullName + " " + pi.Name; + case FieldInfo fi: + return "F " + fi.FieldType.FullName + " " + fi.Name; + case EventInfo ei: + return "E " + ei.EventHandlerType.FullName + " " + ei.Name; + case Type nt: + return "T " + nt.FullName; + default: + return m.MemberType + " " + m.Name; + } + } + + private static bool IsAccessorMethod(MemberInfo m) + { + if (!(m is MethodInfo mi) || !mi.IsSpecialName) return false; + string n = mi.Name; + return n.StartsWith("get_") || n.StartsWith("set_") || + n.StartsWith("add_") || n.StartsWith("remove_"); + } + + private static bool IsCompilerGenerated(MemberInfo m) + { + try { return m.IsDefined(typeof(CompilerGeneratedAttribute), false); } + catch { return false; } + } + + /// + /// Dispatch a named case. Returns the method's normalized result, or + /// "EXC:<ExceptionTypeName>" when the underlying call throws — so exception + /// behavior is compared too. + /// + public string Run(string caseName, string[] args) + { + try + { + return Dispatch(caseName, args); + } + catch (TargetInvocationException tie) + { + return "EXC:" + (tie.InnerException ?? tie).GetType().Name; + } + catch (Exception ex) + { + return "EXC:" + ex.GetType().Name; + } + } + + private string Dispatch(string caseName, string[] args) + { + switch (caseName) + { + case "GetTimeString": + return RPStringsGetTimeString(long.Parse(args[0], CultureInfo.InvariantCulture)); + + case "HostTypeParse": + return HostTypeParse(args[0]); + + case "ConvertBitmap": + return ConvertBitmap( + int.Parse(args[0], CultureInfo.InvariantCulture), + int.Parse(args[1], CultureInfo.InvariantCulture), + int.Parse(args[2], CultureInfo.InvariantCulture)); + + case "GenerateString": + return GenerateString( + int.Parse(args[0], CultureInfo.InvariantCulture), + int.Parse(args[1], CultureInfo.InvariantCulture), + args[2], args[3]); + + case "RPMapParse": + return ParseRedPlanetNode("TeslaConsole.RedPlanet.RPMap", args[0]); + + case "RPVehicleParse": + return ParseRedPlanetNode("TeslaConsole.RedPlanet.RPVehicle", args[0]); + + case "SiteManagementGuid": + return SiteManagementGuid(args[0]); + + case "TupleCreate": + return TupleCreate(args[0], args[1]); + + case "CatalogSummary": + return CatalogSummary(args[0]); + + case "CatalogEntry": + return CatalogEntry(args[0], args[1], args[2], args[3]); + + default: + throw new ArgumentException("Unknown case: " + caseName); + } + } + + private Type T(string fullName) + { + return _asm.GetType(fullName, throwOnError: true); + } + + private string RPStringsGetTimeString(long ticks) + { + var m = T("TeslaConsole.RPStrings").GetMethod( + "GetTimeString", BindingFlags.Public | BindingFlags.Static); + return (string)m.Invoke(null, new object[] { TimeSpan.FromTicks(ticks) }); + } + + private string HostTypeParse(string input) + { + var helperType = T("TeslaConsole.HostTypeHelper"); + var parse = helperType.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static); + object helper = parse.Invoke(null, new object[] { input }); + return helper.ToString(); + } + + private string ConvertBitmap(int width, int height, int seed) + { + using (var bmp = MakePatternBitmap(width, height, seed)) + { + var m = T("TeslaConsole.PlasmaBitmaps").GetMethod( + "ConvertBitmap", BindingFlags.Public | BindingFlags.Static); + return (string)m.Invoke(null, new object[] { bmp }); + } + } + + private string GenerateString(int width, int height, string font, string text) + { + var m = T("TeslaConsole.PlasmaBitmaps").GetMethod( + "GenerateString", + BindingFlags.Public | BindingFlags.Static, + null, + new[] { typeof(int), typeof(int), typeof(string), typeof(string) }, + null); + return (string)m.Invoke(null, new object[] { width, height, font, text }); + } + + private string ParseRedPlanetNode(string typeName, string xml) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + XmlNode node = doc.DocumentElement; + + var type = T(typeName); + var ctor = type.GetConstructor( + BindingFlags.NonPublic | BindingFlags.Instance, + null, new[] { typeof(XmlNode) }, null); + object instance = ctor.Invoke(new object[] { node }); + + string key = (string)type.GetProperty("Key").GetValue(instance, null); + string name = (string)type.GetProperty("Name").GetValue(instance, null); + string toStr = instance.ToString(); + return key + "|" + name + "|" + toStr; + } + + private string SiteManagementGuid(string fieldName) + { + var f = T("TeslaConsole.SiteManagement").GetField( + fieldName, BindingFlags.Public | BindingFlags.Static); + return ((Guid)f.GetValue(null)).ToString("D"); + } + + private string TupleCreate(string a, string b) + { + // Tuple.Create(int.Parse(a), b) -> "A|B" + var tupleType = T("TeslaConsole.Tuple"); + MethodInfo create = null; + foreach (var m in tupleType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (m.Name == "Create" && m.IsGenericMethodDefinition && + m.GetGenericArguments().Length == 2 && m.GetParameters().Length == 2) + { + create = m; + break; + } + } + var closed = create.MakeGenericMethod(typeof(int), typeof(string)); + object tuple = closed.Invoke(null, new object[] { int.Parse(a, CultureInfo.InvariantCulture), b }); + + Type tt = tuple.GetType(); + object av = tt.GetProperty("A").GetValue(tuple, null); + object bv = tt.GetProperty("B").GetValue(tuple, null); + return Convert.ToString(av, CultureInfo.InvariantCulture) + "|" + + Convert.ToString(bv, CultureInfo.InvariantCulture); + } + + private System.Collections.IEnumerable LoadCatalog(string path) + { + var reg = T("TeslaConsole.AppRegistry"); + reg.GetMethod("LoadFromFile", BindingFlags.Public | BindingFlags.Static) + .Invoke(null, new object[] { path }); + return (System.Collections.IEnumerable)reg.GetProperty("Products").GetValue(null); + } + + private string CatalogSummary(string path) + { + int products = 0, entries = 0; + foreach (var product in LoadCatalog(path)) + { + products++; + var list = (System.Collections.IEnumerable)product.GetType().GetField("Entries").GetValue(product); + foreach (var _ in list) entries++; + } + return $"products={products};entries={entries}"; + } + + /// + /// Finds the launch entry with the given key, builds its LaunchData with the optional + /// resolution, and returns "DisplayName|LaunchKey|Exe|Args|WorkingDirectory|AutoRestart". + /// + private string CatalogEntry(string path, string launchKey, string resW, string resH) + { + Guid key = Guid.Parse(launchKey); + object entry = null; + foreach (var product in LoadCatalog(path)) + { + var entries = (System.Collections.IEnumerable)product.GetType().GetField("Entries").GetValue(product); + foreach (var en in entries) + { + if ((Guid)en.GetType().GetField("LaunchKey").GetValue(en) == key) { entry = en; break; } + } + if (entry != null) break; + } + if (entry == null) return "NOTFOUND"; + + object resolution = null; + if (resW.Length > 0 && resH.Length > 0) + resolution = new Size(int.Parse(resW, CultureInfo.InvariantCulture), + int.Parse(resH, CultureInfo.InvariantCulture)); + + object ld = entry.GetType().GetMethod("ToLaunchData").Invoke(entry, new object[] { resolution }); + Type t = ld.GetType(); + object pair = t.GetField("LaunchPair").GetValue(ld); + string disp = (string)pair.GetType().GetField("DisplayName").GetValue(pair); + Guid lkey = (Guid)pair.GetType().GetField("LaunchKey").GetValue(pair); + string exe = (string)t.GetField("ExeFile").GetValue(ld); + string margs = (string)t.GetField("Arguments").GetValue(ld); + string wd = (string)t.GetField("WorkingDirectory").GetValue(ld); + bool ar = (bool)t.GetField("AutoRestart").GetValue(ld); + return $"{disp}|{lkey:D}|{exe}|{margs}|{wd}|{ar}"; + } + + /// + /// Deterministic black/white bitmap whose pixels depend only on (x, y, seed), + /// so both assemblies receive identical input pixels to pack. + /// + private static Bitmap MakePatternBitmap(int width, int height, int seed) + { + var bmp = new Bitmap(width, height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + bool on = ((x * 31 + y * 17 + seed * 7) & 3) == 0; + bmp.SetPixel(x, y, on ? Color.White : Color.Black); + } + } + return bmp; + } + + // Keep the proxy alive for the life of the AppDomain. + public override object InitializeLifetimeService() => null; + } +} diff --git a/Console/tests/TeslaConsole.DiffTests/PublicApiSurfaceTests.cs b/Console/tests/TeslaConsole.DiffTests/PublicApiSurfaceTests.cs new file mode 100644 index 0000000..803f5bf --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/PublicApiSurfaceTests.cs @@ -0,0 +1,56 @@ +using System.Linq; +using Xunit; + +namespace TeslaConsole.DiffTests +{ + /// + /// Structural equivalence: every public type and public member exposed by the + /// original TeslaConsole.exe must also be exposed, with the same signature, by the + /// recovered build. This is a strong "same program" contract that catches any + /// dropped, renamed, or re-signatured member introduced during reconstruction. + /// + /// The enumeration runs inside each assembly's own AppDomain (see DifferentialFixture) + /// because the two files share identical assembly identity and cannot coexist in one + /// domain. Compiler-generated members are excluded — the README notes those legitimately + /// differ between a decompilation and the lost original sources. + /// + public class PublicApiSurfaceTests : IClassFixture + { + private readonly DifferentialFixture _fx; + + public PublicApiSurfaceTests(DifferentialFixture fx) => _fx = fx; + + [Fact] + public void Recovered_Exposes_All_Original_Public_Types() + { + var original = _fx.Original.GetPublicTypeNames(); + var recovered = _fx.Recovered.GetPublicTypeNames(); + + // Sanity: the original actually yielded a non-trivial surface to compare. + Assert.True(original.Length > 20, + "Only " + original.Length + " public types enumerated from the original; " + + "dependency resolution likely failed."); + + var missing = original.Except(recovered).OrderBy(s => s).ToArray(); + Assert.True(missing.Length == 0, + "Public types present in the original but missing from the recovered build:\n " + + string.Join("\n ", missing)); + } + + [Fact] + public void Recovered_Exposes_All_Original_Public_Members() + { + var original = _fx.Original.GetPublicMemberSignatures(); + var recovered = _fx.Recovered.GetPublicMemberSignatures(); + + Assert.True(original.Length > 100, + "Only " + original.Length + " public members enumerated from the original; " + + "dependency resolution likely failed."); + + var missing = original.Except(recovered).OrderBy(s => s).ToArray(); + Assert.True(missing.Length == 0, + missing.Length + " public member(s) present in the original but missing from the recovered build:\n " + + string.Join("\n ", missing.Take(80))); + } + } +} diff --git a/Console/tests/TeslaConsole.DiffTests/README.md b/Console/tests/TeslaConsole.DiffTests/README.md new file mode 100644 index 0000000..6dc11db --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/README.md @@ -0,0 +1,57 @@ +# TeslaConsole.DiffTests — differential equivalence suite + +Verifies that the **reconstructed** `TeslaConsole.exe` (built from the decompiled +source in this repo) behaves identically to the **original** reference binary in +[`original/TeslaConsole.exe`](../../original/TeslaConsole.exe). + +## How it works + +Both files carry the *exact same* assembly identity +(`TeslaConsole, Version=4.11.3.37076`), so the .NET loader will not hold both in +one AppDomain. The suite therefore loads each assembly into its **own child +AppDomain** (`DifferentialFixture`) and drives it through a `MarshalByRefObject` +proxy (`Invoker`). This is why the project targets **net48** — AppDomains are a +.NET Framework feature. + +Each child domain is given a probe directory (the recovered build's output, which +ships every dependency DLL) so the original — which is distributed without its +proprietary dependencies — still resolves its references for metadata inspection. + +### What is compared + +1. **Public API surface** (`PublicApiSurfaceTests`) + Every public type and public member (signature-for-signature) exposed by the + original must also be exposed by the recovered build. Compiler-generated members + and property/event accessor methods are excluded — the README at the repo root + notes those legitimately differ between a decompilation and the lost sources. + +2. **Behavioral output** (`BehavioralEquivalenceTests`) + The same deterministic, dependency-free methods are invoked in *both* assemblies + over a battery of inputs and the results must match byte-for-byte: + - `RPStrings.GetTimeString` (mm:ss formatting + 0.5 s rounding) + - `HostTypeHelper.Parse(...).ToString()` (incl. invalid-input exceptions) + - `PlasmaBitmaps.ConvertBitmap` (1-bpp packing of a known pixel pattern) + - `PlasmaBitmaps.GenerateString` (full GDI text → 1-bpp plasma pipeline) + - `RPMap` / `RPVehicle` XML parsing + - `SiteManagement` well-known application GUID constants + - `Tuple.Create<,>` generic factory + + A negative-control test (`Harness_Distinguishes_Different_Outputs`) proves the + harness can actually see a difference, so a green run is never vacuous. + +## Running + +``` +dotnet test tests/TeslaConsole.DiffTests/TeslaConsole.DiffTests.csproj +``` + +A project reference builds the reconstruction first, and the suite always tests +the most recently built `bin/{Debug,Release}/net48/TeslaConsole.exe`. + +## Scope / limitations + +This compares **deterministic logic**. It deliberately does not drive the WinForms +UI, the pod networking, secure-configuration, or hardware-facing code — those +require the live console, its pods, and the proprietary services, and are not +reproducible in a unit test. The API-surface test still asserts those types exist +with matching signatures even though their behavior isn't exercised. diff --git a/Console/tests/TeslaConsole.DiffTests/TeslaConsole.DiffTests.csproj b/Console/tests/TeslaConsole.DiffTests/TeslaConsole.DiffTests.csproj new file mode 100644 index 0000000..0fca65f --- /dev/null +++ b/Console/tests/TeslaConsole.DiffTests/TeslaConsole.DiffTests.csproj @@ -0,0 +1,46 @@ + + + + + net48 + latest + disable + disable + false + TeslaConsole.DiffTests + TeslaConsole.DiffTests + + + + + + + + + + + + + + + + + + + false + false + + + + diff --git a/Launcher/.gitattributes b/Launcher/.gitattributes new file mode 100644 index 0000000..a413a72 --- /dev/null +++ b/Launcher/.gitattributes @@ -0,0 +1,21 @@ +# Normalize line endings: detect text automatically, keep CRLF for Windows tooling +* text=auto + +# Source and project files are CRLF (Windows / Visual Studio project) +*.cs text eol=crlf +*.csproj text eol=crlf +*.sln text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf +*.xml text eol=crlf +*.json text eol=crlf +*.md text eol=crlf + +# Binary assets — never normalize, never diff as text +*.dll binary +*.exe binary +*.ico binary +*.bmp binary +*.png binary +*.jpg binary +*.InstallState binary diff --git a/Launcher/.gitignore b/Launcher/.gitignore new file mode 100644 index 0000000..7675e13 --- /dev/null +++ b/Launcher/.gitignore @@ -0,0 +1,28 @@ +# ── Build output ───────────────────────────────────────────────────────────── +# dotnet build/publish intermediates +[Bb]in/ +[Oo]bj/ + +# build.bat staging + final output (see build.bat: BUILD_DIR / *_STAGE) +/_stage_svc/ +/_stage_agt/ +/TeslaLauncher/ + +# ── Runtime / installer state ──────────────────────────────────────────────── +# Generated by installutil / the service at runtime, not source +*.InstallState +*.log + +# ── Visual Studio / Rider / VS Code ────────────────────────────────────────── +.vs/ +.vscode/ +.idea/ +*.user +*.suo +*.userosscache +*.sln.docstates + +# ── OS cruft ───────────────────────────────────────────────────────────────── +Thumbs.db +desktop.ini +.DS_Store diff --git a/Launcher/LaunchModels_Shared.cs b/Launcher/LaunchModels_Shared.cs new file mode 100644 index 0000000..e3372d4 --- /dev/null +++ b/Launcher/LaunchModels_Shared.cs @@ -0,0 +1,120 @@ +// ============================================================================= +// TeslaLauncher — Shared Models +// ============================================================================= +// Wire types (Tesla.Net): BinaryFormatter-compatible replicas of the original +// structs from TeslaConsoleLaunchLib.dll. Field names, types, and order must +// match exactly for serialization compatibility. +// +// IPC types (Tesla.Launcher.Shared): JSON messages between Service and Agent +// over Named Pipe. These never touch the Console TCP connection. +// ============================================================================= + +using System; +using System.Reflection; +using System.Runtime.Serialization; + +namespace Tesla.Net +{ + /// RPC command from TeslaConsole. Function is a MethodBase of ILauncherService. + [Serializable] + public struct InvokeCommand + { + public MethodBase Function; + public object[] Parameters; + } + + /// RPC result returned to TeslaConsole. + [Serializable] + public struct InvokeResult + { + public object Result; + public Exception Exception; + public TimeSpan CallDuration; + } + + /// App identifier + display name pair. + [Serializable] + public struct LaunchPair + { + public Guid LaunchKey; + public string DisplayName; + } + + /// Describes one launchable simulation application. + [Serializable] + public struct LaunchData + { + public LaunchPair LaunchPair; + public string WorkingDirectory; + public string ExeFile; + public string Arguments; + public bool AutoRestart; + } + + /// Tracks a currently running simulation process. + [Serializable] + public struct LaunchedAppData + { + public int ProcessId; + public Guid LaunchKey; + } + + /// Complete pod state snapshot for FullUpdate RPC. + [Serializable] + public struct FullUpdateData + { + public LaunchData[] InstalledApps; + public LaunchedAppData[] LaunchedApps; + public float VolumeLevel; + } + + /// Progress of an out-of-band product installation. + [Serializable] + public struct OutOfBandProgress + { + public int PercentComplete; + public string Status; + public bool IsCompleted; + } + + /// Interface for BinaryFormatter MethodBase resolution. Signatures must match the original exactly. + public interface ILauncherService + { + float VolumeLevel { get; set; } + void ClearStore(); + DateTime Ping(DateTime now); + Guid InitiateInstallProduct(); + OutOfBandProgress GetOutOfBandProgress(Guid invokeCallId); + int LaunchApp(Guid launchKey); + LaunchPair[] GetLaunchableApps(); + void KillApp(Guid launchKey, int processId); + void KillAllOfType(Guid launchKey); + void KillAllApps(); + void Shutdown(bool restart); + LaunchedAppData[] GetLaunchedApps(); + LaunchData[] GetInstalledApps(); + void RemoveApp(Guid index); + void InstallApp(LaunchData data); + void UninstallApp(Guid launchKey); + FullUpdateData FullUpdate(); + } +} + +namespace Tesla.Launcher.Shared +{ + /// Command forwarded from the Windows Service to the Userspace Agent. + public sealed class IpcMessage + { + public string Command { get; set; } + public string LaunchKey { get; set; } + public string PayloadJson { get; set; } + } + + /// Response from the Userspace Agent back to the Windows Service. + public sealed class IpcResponse + { + public bool Success { get; set; } + public string Message { get; set; } + public object Data { get; set; } + } +} diff --git a/Launcher/README.md b/Launcher/README.md new file mode 100644 index 0000000..74bb481 --- /dev/null +++ b/Launcher/README.md @@ -0,0 +1,110 @@ +# TeslaLauncher + +.NET 6 rewrite of the original Elsewhen Studios LLC software (Windows 2000 / .NET Framework 2.0). + +## Architecture + +TeslaLauncher has three components that work together: + +### TeslaLauncherService (Windows Service, Session 0) +- Runs at boot before any user logs in +- Listens on **TCP 53290** for OFB-encrypted BinaryFormatter RPC from TeslaConsole +- Forwards commands to the Agent via Named Pipe (`TeslaLauncherIPC`) +- Handles first-boot network configuration (SecureConfig) +- Handles game file transfers from the Console (InstallProduct) + +### TeslaLauncherAgent (WinForms tray app, user session) +- Runs in the logged-in user's desktop session +- Executes commands that require desktop access: launching/killing apps, volume control +- Manages `LaunchApps.xml` (installed games registry) +- On first boot, displays SecureConfig Request ID and Passphrase + +### SecureConfig (first-boot protocol) +- Assigns a temporary IP and broadcasts a UDP beacon so the Console can discover the pod +- Operator reads the Passphrase off the pod screen and enters it into the Console +- Console sends AES-encrypted network configuration (IP, mask, gateway, DNS, hostname) +- TCP handshake establishes an OFB-encrypted session with RSA key exchange +- Session key is saved for all subsequent Console connections + +## Communication Flow + +``` +TeslaConsole ──TCP 53290 (OFB + BinaryFormatter)──> TeslaLauncherService + │ + Named Pipe (JSON) + │ + v + TeslaLauncherAgent +``` + +## Files + +| File | Description | +|------|-------------| +| `TeslaLauncherService.cs` | Windows Service implementation | +| `TeslaLauncherService.csproj` | Service project (net6.0-windows, x86, self-contained) | +| `TeslaLauncherAgent.cs` | Userspace Agent implementation | +| `TeslaLauncherAgent.csproj` | Agent project (WinForms, net6.0-windows, x86) | +| `LaunchModels_Shared.cs` | Wire types (Tesla.Net) and IPC types (Tesla.Launcher.Shared) | +| `SecureConfig.cs` | First-boot secure configuration protocol | +| `build.bat` | Builds both components | +| `install.bat` | Installs on a cockpit PC (run as Administrator) | + +## Building + +Requirements: +- .NET 6 SDK +- Internet access for NuGet restore + +``` +build.bat :: build both components +build.bat /service :: build Service only +build.bat /agent :: build Agent only +``` + +Output goes to `TeslaLauncher\` with `Service\` and `Agent\` subdirectories. + +## Installation + +1. Copy the `TeslaLauncher\` folder to each cockpit PC +2. Run `TeslaLauncher\install.bat` as Administrator + +The installer: +- Registers the Service (delayed auto-start) +- Configures the Agent for auto-login startup +- Installs OpenAL and DirectX runtimes +- Enables SMB1 file sharing +- Creates `C:\Games` with appropriate permissions +- Resets network adapters to DHCP for SecureConfig + +## First Boot + +1. Cockpit boots with DHCP (unconfigured state) +2. Service runs SecureConfig: broadcasts beacon, displays codes on screen +3. Console operator sees the pod's Request ID and enters the Passphrase +4. Console sends encrypted network configuration +5. Pod applies the configuration and is ready for normal operation + +## Normal Operation + +The Console connects to each configured pod on TCP 53290 and can: +- Install/uninstall simulation games +- Launch/kill applications +- Get/set volume level +- Query pod status (FullUpdate) +- Shutdown or reboot the pod + +## Key Paths + +| Path | Purpose | +|------|---------| +| `C:\ProgramData\TeslaLauncher\TeslaKeyStore.key` | Session key (32 bytes) | +| `C:\ProgramData\TeslaLauncher\LaunchApps.xml` | Installed games registry | +| `C:\ProgramData\TeslaLauncher\configuring.json` | Transient: SecureConfig codes for Agent display | +| `C:\Games\` | Game installation directory | + +## Wire Protocol + +The Console uses BinaryFormatter over OFB-encrypted TCP streams. All types in the `Tesla.Net` namespace are exact replicas of the original structs from `TeslaConsoleLaunchLib.dll` to maintain serialization compatibility. + +The Service-to-Agent IPC uses length-prefixed JSON over a Named Pipe, with flat types that avoid the nested struct layout of the wire format. diff --git a/Launcher/SecureConfig.cs b/Launcher/SecureConfig.cs new file mode 100644 index 0000000..b5d1718 --- /dev/null +++ b/Launcher/SecureConfig.cs @@ -0,0 +1,1200 @@ +// ============================================================================= +// TeslaLauncher — Secure Pod Configuration +// ============================================================================= +// Implements the Tesla secure pod configuration protocol (reverse-engineered +// from TeslaSecureConfiguration.dll, Elsewhen Studios LLC). +// +// Protocol summary: +// 1. Assign temp IP, generate RequestId + Passphrase +// 2. Broadcast UDP RQST beacon on port 53291 +// 3. Display codes on screen and COM2 plasma display +// 4. Console sends AES-encrypted network config via UDP RPLY on port 53292 +// 5. Apply static IP, DNS, hostname via netsh +// 6. TCP handshake on port 53292: OFB crypto negotiation + RSA key exchange +// 7. Save 32-byte session key to TeslaKeyStore.key +// ============================================================================= + +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Net.NetworkInformation; +using System.Text; +using System.Security.Cryptography; +using System.Threading; +using System.Diagnostics; +using Microsoft.Win32; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +#if WINFORMS +using System.Windows.Forms; +using System.Drawing; +using System.Runtime.InteropServices; +#endif + +namespace TeslaSecureConfig +{ + // ---- Wire protocol constants ---- + internal static class Proto + { + public const string RQST = "RQST"; + public const string RPLY = "RPLY"; + public const string CONF = "CONF"; + public const string DONE = "DONE"; + + public const int UdpBroadcastPort = 53291; // Console listens here + public const int UdpReplyPort = 53292; // Cockpit listens here for Console RPLY (UDP broadcast) + public const int UdpBeaconIntervalMs= 2000; + + public const string TempIp = "172.16.0.100"; + public const string TempBcast = "172.31.255.255"; // directed broadcast for 172.16.0.0/12 + public const string TempMask = "255.240.0.0"; + + public const int PassphraseLength = 5; + public const int RequestIdLength = 3; + public const string Alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + + public const int RsaKeySize = 2048; + public const int Pbkdf2Iterations = 1000; + public const int AesKeyBytes = 32; // 256-bit AES + + // Static salt extracted from TeslaSecureConfiguration.dll FieldRVA + public static readonly byte[] PassphraseSalt = new byte[] + { + 0x17, 0xab, 0x51, 0xd9, 0xec, 0xd1, 0xd4, 0x74, + 0xa9, 0x09, 0x4a, 0x34, 0x27, 0xfb, 0x1f, 0xf2, + 0xde, 0xc4, 0xf9, 0xf1, 0xa6, 0xd8, 0x9e, 0xda, + 0x15, 0x11, 0x47, 0x65, 0x32, 0xe7, 0xe7, 0xef + }; + + public const string ComPort = "COM2"; + public const int ComBaud = 9600; + public const string PlasmaFont = "Verdana"; + + public const string RegComputerName = @"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName"; + public const string RegTcpipParams = @"SYSTEM\CurrentControlSet\services\Tcpip\Parameters"; + } + + // ---- Network configuration returned from Console ---- + public class PodNetworkConfig + { + public IPAddress Address { get; set; } + public IPAddress Mask { get; set; } + public IPAddress Gateway { get; set; } + public IPAddress Dns { get; set; } + public string HostName { get; set; } + } + +#if WINFORMS + // ---- Passcode display form ---- + // Request ID: sent in beacon, Console shows it to identify this cockpit. + // Passphrase: displayed HERE only. Operator reads it and types it into the + // Console. Console returns it inside the encrypted CONF to prove + // it is configuring the correct pod. + internal class PasscodeDisplayForm : Form + { + public PasscodeDisplayForm(string requestId, string passphrase) + { + SuspendLayout(); + + Text = "Your Configuration Passcode"; + FormBorderStyle = FormBorderStyle.FixedDialog; + StartPosition = FormStartPosition.CenterScreen; + ClientSize = new Size(480, 220); + MaximizeBox = false; + MinimizeBox = false; + + var boldFont = new Font("Arial Narrow", 24f, FontStyle.Bold, GraphicsUnit.Point); + var labelFont = new Font("Arial Narrow", 14f, FontStyle.Regular, GraphicsUnit.Point); + var noteFont = new Font("Arial Narrow", 10f, FontStyle.Italic, GraphicsUnit.Point); + + // Request ID row + var lblRqstTitle = new Label { Text = "Request ID:", AutoSize = true, + Location = new Point(20, 24), Font = labelFont }; + var lblRqstVal = new Label { Text = requestId, AutoSize = true, + Location = new Point(200, 20), Font = boldFont }; + + // Passphrase row + var lblPassTitle = new Label { Text = "Passphrase:", AutoSize = true, + Location = new Point(20, 100), Font = labelFont }; + var lblPassVal = new Label { Text = passphrase, AutoSize = true, + Location = new Point(200, 96), Font = boldFont, + ForeColor = System.Drawing.Color.DarkRed }; + + // Instruction note + var lblNote = new Label + { + Text = "Console will show the Request ID above.\r\n" + + "Read the Passphrase (red) to the operator,\r\n" + + "who types it into the Console to authorise this pod.", + AutoSize = true, + Location = new Point(20, 158), + Font = noteFont, + ForeColor = System.Drawing.Color.Gray + }; + + Controls.AddRange(new Control[] + { lblRqstTitle, lblRqstVal, lblPassTitle, lblPassVal, lblNote }); + + ResumeLayout(false); + PerformLayout(); + } + } + +#endif + // ---- PlasmaIO wrapper (mirrors PlasmaIO.dll PlasmaDisplay API) ---- + internal sealed class PlasmaWriter : IDisposable + { + private System.IO.Ports.SerialPort _port; + private bool _disposed; + + public PlasmaWriter(string comPort, int baud = 9600) + { + try + { + _port = new System.IO.Ports.SerialPort(comPort, baud, + System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One); + _port.Open(); + ClearAll(); + } + catch (Exception ex) + { + // Plasma display is optional - log but continue + System.Diagnostics.Debug.WriteLine($"PlasmaIO init failed: {ex.Message}"); + _port = null; + } + } + + // ClearAll: send ESC J (clear display) as per Plasma protocol + public void ClearAll() + { + if (_port == null || !_port.IsOpen) return; + _port.BaseStream.WriteByte(0x1B); // ESC + _port.BaseStream.WriteByte(0x4A); // J (clear screen) + _port.BaseStream.Flush(); + } + + public void WriteLine(string format, params object[] args) + { + if (_port == null || !_port.IsOpen) return; + var text = string.Format(format, args) + "\r\n"; + var bytes = Encoding.ASCII.GetBytes(text); + _port.BaseStream.Write(bytes, 0, bytes.Length); + _port.BaseStream.Flush(); + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + try { _port?.Close(); } catch { } + } + } + } + + // ---- Crypto helpers ---- + internal static class CryptoHelper + { + /// Derive AES key from passphrase using PBKDF2 with the hard-coded salt + public static byte[] DeriveKey(string passphrase) + { + var pbkdf2 = new Rfc2898DeriveBytes(passphrase, + Proto.PassphraseSalt, Proto.Pbkdf2Iterations); + return pbkdf2.GetBytes(Proto.AesKeyBytes); + } + + /// Encrypt data with AES-256 (CBC mode) + public static byte[] Encrypt(byte[] data, byte[] key) + { + using var aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Key = key; + aes.GenerateIV(); + var iv = aes.IV; + using var enc = aes.CreateEncryptor(); + using var ms = new MemoryStream(); + using var cs = new CryptoStream(ms, enc, CryptoStreamMode.Write); + cs.Write(data, 0, data.Length); + cs.FlushFinalBlock(); + var cipher = ms.ToArray(); + // Prepend IV + var result = new byte[iv.Length + cipher.Length]; + Buffer.BlockCopy(iv, 0, result, 0, iv.Length); + Buffer.BlockCopy(cipher, 0, result, iv.Length, cipher.Length); + return result; + } + + /// Decrypt data with AES-256 (CBC mode), IV prepended + public static byte[] Decrypt(byte[] data, byte[] key) + { + using var aes = Aes.Create(); + aes.Mode = CipherMode.CBC; + aes.Key = key; + var iv = new byte[aes.BlockSize / 8]; + Buffer.BlockCopy(data, 0, iv, 0, iv.Length); + aes.IV = iv; + using var dec = aes.CreateDecryptor(); + using var ms = new MemoryStream(); + using var cs = new CryptoStream(ms, dec, CryptoStreamMode.Write); + cs.Write(data, iv.Length, data.Length - iv.Length); + cs.FlushFinalBlock(); + return ms.ToArray(); + } + } + + // ── OFB Duplex Stream ────────────────────────────────────────────────── + // + // The original OFBCryptoStream wraps Rijndael-ECB (CipherMode=2) and implements OFB + // (Output Feedback) mode manually: keystream is generated by repeatedly encrypting the + // feedback register with the ECB block cipher, then XORing data with the keystream. + // + // For a full-duplex TCP connection, two independent keystream generators are maintained: + // Write direction (pod→console): seeded from the pod's generated IV + // Read direction (console→pod): seeded from the console's sent IV + // + // Both directions use the same PBKDF2-derived AES key. + internal sealed class OFBDuplexStream : Stream + { + private readonly NetworkStream _inner; + + // Write-direction (pod→console) OFB state + private readonly Aes _writeAes; + private readonly ICryptoTransform _writeXform; + private readonly byte[] _writeFb = new byte[16]; // feedback register (starts as IV) + private readonly byte[] _writeKs = new byte[16]; // current keystream block + private int _wPos = 16; // 16 = "generate new block on next byte" + + // Read-direction (console→pod) OFB state + private readonly Aes _readAes; + private readonly ICryptoTransform _readXform; + private readonly byte[] _readFb = new byte[16]; + private readonly byte[] _readKs = new byte[16]; + private int _rPos = 16; + + /// Underlying NetworkStream (not owned — caller disposes). + /// 32-byte PBKDF2 key derived from the passphrase. + /// 16-byte IV for pod→console keystream (pod-generated). + /// 16-byte IV for console→pod keystream (console-generated). + public OFBDuplexStream(NetworkStream inner, byte[] key, byte[] writeIv, byte[] readIv) + { + _inner = inner; + Buffer.BlockCopy(writeIv, 0, _writeFb, 0, 16); + Buffer.BlockCopy(readIv, 0, _readFb, 0, 16); + + _writeAes = Aes.Create(); + _writeAes.Mode = CipherMode.ECB; + _writeAes.Padding = PaddingMode.None; + _writeAes.Key = key; + _writeXform = _writeAes.CreateEncryptor(); + + _readAes = Aes.Create(); + _readAes.Mode = CipherMode.ECB; + _readAes.Padding = PaddingMode.None; + _readAes.Key = key; + _readXform = _readAes.CreateEncryptor(); + } + + private byte NextWriteByte() + { + if (_wPos == 16) + { + _writeXform.TransformBlock(_writeFb, 0, 16, _writeKs, 0); + Buffer.BlockCopy(_writeKs, 0, _writeFb, 0, 16); + _wPos = 0; + } + return _writeKs[_wPos++]; + } + + private byte NextReadByte() + { + if (_rPos == 16) + { + _readXform.TransformBlock(_readFb, 0, 16, _readKs, 0); + Buffer.BlockCopy(_readKs, 0, _readFb, 0, 16); + _rPos = 0; + } + return _readKs[_rPos++]; + } + + public override int Read(byte[] buffer, int offset, int count) + { + int n = _inner.Read(buffer, offset, count); + for (int i = 0; i < n; i++) + buffer[offset + i] ^= NextReadByte(); + return n; + } + + public override void Write(byte[] buffer, int offset, int count) + { + var enc = new byte[count]; + for (int i = 0; i < count; i++) + enc[i] = (byte)(buffer[offset + i] ^ NextWriteByte()); + _inner.Write(enc, 0, count); + } + + public override void Flush() => _inner.Flush(); + + public override bool CanRead => true; + public override bool CanWrite => true; + public override bool CanSeek => false; + + public override long Length => throw new NotSupportedException(); + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + + // Delegate timeout properties to the inner NetworkStream. + public override bool CanTimeout => true; + public override int ReadTimeout { get => _inner.ReadTimeout; set => _inner.ReadTimeout = value; } + public override int WriteTimeout { get => _inner.WriteTimeout; set => _inner.WriteTimeout = value; } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _writeXform?.Dispose(); + _readXform?.Dispose(); + _writeAes?.Dispose(); + _readAes?.Dispose(); + // _inner is not owned — do not dispose + } + base.Dispose(disposing); + } + } + + // ---- Main secure configuration orchestrator ---- + public sealed class PodSecureConfigurator : IDisposable + { + private int _adapterIndex; // IPv4 interface index — used with netsh index=N + private string _adapterId; // Adapter GUID — used for registry static-IP persistence + private string _adapterName; // Display name e.g. "Ethernet" — used for ipconfig /release + private readonly Action _log; + private IPAddress _consoleAddress; // Console sender IP from RPLY — used to re-resolve adapter + + private string _requestId; + private string _passphrase; + + /// + /// After the RSA key exchange, the 32-byte session key is stored here. + /// The Service saves it to TeslaKeyStore.key and uses it for NegotiateCryptoStreams + /// on the management port (53290) for all subsequent Console connections. + /// + public byte[] SessionKey { get; private set; } + + private System.Net.Sockets.Socket _replySocket; + private Thread _beaconThread; + private Thread _replyThread; + private volatile bool _running; + private ManualResetEventSlim _configReceived = new ManualResetEventSlim(false); + private PodNetworkConfig _receivedConfig; + +#if WINFORMS + private PasscodeDisplayForm _displayForm; +#endif + private PlasmaWriter _plasma; + + public PodSecureConfigurator(string adapterName = null, Action logger = null) // adapterName = display name hint or null for auto-detect + { + if (adapterName != null) + { + _adapterIndex = FindAdapterIndex(adapterName); + _adapterId = FindAdapterId(adapterName); + _adapterName = adapterName; + } + else + { + (_adapterIndex, _adapterId, _adapterName) = FindFirstEthernetAdapter(); + } + _log = logger ?? (s => Debug.WriteLine(s)); + } + + // --- Entry point --- + public PodNetworkConfig Configure(int timeoutMs = 300_000) + { + Log("Beginning configuration."); + + // Delete any stale configuring.json from a previous run so + // the Agent doesn't display old codes before we generate new ones. + try + { + var staleFile = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TeslaLauncher", "configuring.json"); + if (File.Exists(staleFile)) File.Delete(staleFile); + } + catch { } + + // Step 1: Generate identifiers + _requestId = GenerateRandomString(Proto.RequestIdLength); + _passphrase = GenerateRandomString(Proto.PassphraseLength); + Log($"Waiting for configuration. Request ID: {_requestId} Passphrase: {_passphrase} (passphrase is NOT transmitted — operator reads it off screen)"); + + // Write RequestId/Passphrase to a shared file so the Agent + // (running in the user session) can display them on screen. + try + { + var cfgDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TeslaLauncher"); + Directory.CreateDirectory(cfgDir); + File.WriteAllText(Path.Combine(cfgDir, "configuring.json"), + $"{{\"RequestId\":\"{_requestId}\",\"Passphrase\":\"{_passphrase}\"}}"); + } + catch { /* best-effort — plasma/log still have the values */ } + + // Step 2: Assign temporary IP + Log($"Configuring temporary IP address on \"{_adapterName}\"."); + if (!ConfigureTempIp(_adapterName)) + { + Log("Could not initalize temporary IP address."); + return null; + } + Log("Temp IP configured."); + + // Step 3: Show display (WinForms + Plasma) + ShowPasscodeDisplay(); + + // Step 4: Start UDP beacon. + // Bind to the temp IP so broadcasts go out through the correct + // Ethernet adapter. netsh returns before the kernel has finished + // assigning the address, so retry for up to 5 seconds. + _running = true; + + // No longer waiting for the temp IP to propagate before broadcasting. + // BeaconLoop uses per-interface sockets bound to each adapter's actual current + // IP so Windows routes each broadcast out the correct physical NIC. + // The temp IP assignment still runs (source=static disables DHCP for TCP), + // but the beacon works immediately from whatever address the adapter has. + Log("Starting beacon."); + + _beaconThread = new Thread(BeaconLoop) { Name = "BeaconThread", IsBackground = true }; + _beaconThread.Start(); + + // Step 5: Start UDP listener on port 53292 for RPLY from Console. + // The Console broadcasts "RPLY" + AES-encrypted CONF as a 68-byte UDP datagram + // to 255.255.255.255:53292. We receive it, derive the AES key from our locally- + // generated passphrase (operator typed it into the Console), decrypt and apply. + try + { + _replySocket = new System.Net.Sockets.Socket( + System.Net.Sockets.AddressFamily.InterNetwork, + System.Net.Sockets.SocketType.Dgram, + System.Net.Sockets.ProtocolType.Udp); + _replySocket.SetSocketOption( + System.Net.Sockets.SocketOptionLevel.Socket, + System.Net.Sockets.SocketOptionName.ReuseAddress, true); + _replySocket.EnableBroadcast = true; + _replySocket.Bind(new IPEndPoint(IPAddress.Any, Proto.UdpReplyPort)); + } + catch (SocketException ex) + { + Log($"Could not bind UDP reply socket on port {Proto.UdpReplyPort}: {ex.Message}"); + _running = false; + return null; + } + _replyThread = new Thread(UdpReplyLoop) { Name = "UdpReplyThread", IsBackground = true }; + _replyThread.Start(); + + Log("Broadcasting... waiting for packets."); + + // Wait for config + bool received = _configReceived.Wait(timeoutMs); + _running = false; + + // Hide display +#if WINFORMS + try { _displayForm?.Invoke(new Action(() => _displayForm.Close())); } catch { } +#endif + _plasma?.Dispose(); + + if (!received || _receivedConfig == null) + { + Log("Timed out waiting for configuration."); + return null; + } + + Log("Packets received."); + + // Step 6: Open TCP listener on 0.0.0.0:53292 BEFORE changing the IP. + // + // After sending RPLY the Console immediately ARPs for the NEW IP it put + // in the payload (not the beacon source IP). It then connects TCP to that + // new IP:53292 to confirm the cockpit applied the config. + // + // Binding the TCP listener to 0.0.0.0 first means it is already waiting + // when the IP changes. Once netsh applies the new address and the Console's + // ARP gets a reply, it connects TCP — and the listener accepts it. + Log("Confirming configuration to Console."); + try { _replySocket?.Close(); _replySocket = null; } catch { } + + // Re-resolve the adapter using the TARGET IP from the RPLY config. + // + // The Console may route its RPLY broadcast through a secondary NIC that is + // NOT on the pod's network (e.g. a 192.168.1.x NIC instead of the 10.0.x + // NIC that actually connects to the pods). Using the Console's sender IP + // (_consoleAddress) for adapter selection therefore fails whenever the Console + // has multiple NICs. + // + // The target IP in the RPLY config is always on the pod's network — the + // Console operator assigns it from the same IP range. We find the local NIC + // whose current IP shares the same subnet as the target: that is the correct + // NIC to reconfigure. + if (_receivedConfig != null) + ResolveAdapterForTargetIp(_receivedConfig.Address, _receivedConfig.Mask); + + var tcpConf = new System.Net.Sockets.TcpListener(IPAddress.Any, Proto.UdpReplyPort); + tcpConf.Start(); + + // Step 7: Apply network configuration (IP changes here). + // The listener is already bound so it will accept the Console's connection + // once the new IP is live and the ARP resolves. + Log($"Attempting to configure final IP."); + if (!ApplyNetworkConfig(_adapterId, _adapterName, _receivedConfig)) + { + Log("Could not initalize final IP address."); + tcpConf.Stop(); + return null; + } + + Log("Setting host name."); + SetHostName(_receivedConfig.HostName); + + // Step 8: Accept Console connection and send DONE. + SendTcpConfirmation(tcpConf); + + Log("...Configured"); + + // Clean up the shared file so the Agent doesn't show stale data + try + { + var cfgFile = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TeslaLauncher", "configuring.json"); + if (File.Exists(cfgFile)) File.Delete(cfgFile); + } + catch { } + + return _receivedConfig; + } + + // --- UDP beacon loop --- + // On Windows, a socket bound to 0.0.0.0 sending to 255.255.255.255 + // is routed through whichever interface has the lowest metric — + // which is often a virtual or loopback adapter, silently discarding + // the packet before it reaches the physical wire. + // + // Fix: enumerate every live Ethernet interface, bind a separate socket + // to each one's actual current IP, and send to that subnet's directed + // broadcast address. This forces each datagram out the correct NIC. + private void BeaconLoop() + { + byte[] payload = BuildBeaconPayload(); + + // On boot, the service may start before the NIC driver has fully initialised. + // Wait up to 60 s for at least one UP Ethernet interface with a real, *bindable* IPv4 + // address. Checking OperationalStatus+UnicastAddresses is not sufficient — after + // "netsh interface ip set address ... static", the new IP can appear in the interface + // list for several seconds before the kernel routing table commits it, causing socket + // Bind() to fail with WSAEADDRNOTAVAIL. We probe-bind a throwaway UDP socket to + // confirm the address is actually usable before leaving the wait loop. + int waitSecs = 0; + while (_running) + { + bool ready = false; + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.NetworkInterfaceType == NetworkInterfaceType.Loopback) continue; + if (nic.OperationalStatus != OperationalStatus.Up) continue; + foreach (var ua in nic.GetIPProperties().UnicastAddresses) + { + if (ua.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue; + if (IPAddress.IsLoopback(ua.Address)) continue; + // Verify the address is actually bindable, not just listed + try + { + using var probe = new System.Net.Sockets.Socket( + System.Net.Sockets.AddressFamily.InterNetwork, + System.Net.Sockets.SocketType.Dgram, + System.Net.Sockets.ProtocolType.Udp); + probe.Bind(new IPEndPoint(ua.Address, 0)); + ready = true; + } + catch { /* not committed by kernel yet — keep waiting */ } + if (ready) break; + } + if (ready) break; + } + if (ready) break; + if (waitSecs == 0) Log("Waiting for network interface to obtain a bindable IP address..."); + waitSecs++; + if (waitSecs >= 60) { Log("Warning: no interface had a bindable IP after 60 s — broadcasting anyway."); break; } + Thread.Sleep(1000); + } + + while (_running) + { + SendBeaconOnAllInterfaces(payload); + Thread.Sleep(Proto.UdpBeaconIntervalMs); + } + } + + private byte[] BuildBeaconPayload() + { + // RQST beacon — 13 bytes: + // [0:4] "RQST" + // [4:10] 6-byte raw MAC of the physical Ethernet adapter + // [10:13] RequestId (3 ASCII chars) + byte[] macBytes = GetAdapterMacBytes(); + using var ms = new MemoryStream(13); + using var w = new BinaryWriter(ms, Encoding.ASCII); + w.Write(Encoding.ASCII.GetBytes(Proto.RQST)); + w.Write(macBytes); + w.Write(Encoding.ASCII.GetBytes(_requestId.PadRight(Proto.RequestIdLength))); + return ms.ToArray(); + } + + private void SendBeaconOnAllInterfaces(byte[] payload) + { + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.NetworkInterfaceType == NetworkInterfaceType.Loopback) continue; + if (nic.OperationalStatus != OperationalStatus.Up) continue; + + var ipProps = nic.GetIPProperties(); + foreach (var ua in ipProps.UnicastAddresses) + { + if (ua.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue; + if (IPAddress.IsLoopback(ua.Address)) continue; + + // Send to the limited broadcast address (255.255.255.255). + // + // The Console can be on any IP subnet — in production it is on + // 10.0.0.x while the pod temp IP is 172.16.0.x. A directed + // subnet broadcast (e.g. 172.31.255.255) never crosses the subnet + // boundary so the Console would not receive it. + // Bind to this NIC's IP so Windows routes via the physical adapter + try + { + using var sock = new System.Net.Sockets.Socket( + System.Net.Sockets.AddressFamily.InterNetwork, + System.Net.Sockets.SocketType.Dgram, + System.Net.Sockets.ProtocolType.Udp); + sock.EnableBroadcast = true; + sock.Bind(new IPEndPoint(ua.Address, 0)); + sock.SendTo(payload, new IPEndPoint(IPAddress.Broadcast, Proto.UdpBroadcastPort)); + } + catch (Exception ex) + { + Log($"Beacon send error on {ua.Address}: {ex.Message}"); + } + } + } + } + + /// + /// Returns the 6-byte raw MAC of the adapter holding the temp IP. + /// Falls back to the first operational Ethernet adapter. + /// + private static byte[] GetAdapterMacBytes() + { + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue; + if (nic.OperationalStatus != OperationalStatus.Up) continue; + + foreach (var ua in nic.GetIPProperties().UnicastAddresses) + { + if (ua.Address.ToString() == Proto.TempIp) + return nic.GetPhysicalAddress().GetAddressBytes(); + } + } + + // Fallback: first operational Ethernet adapter + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue; + if (nic.OperationalStatus != OperationalStatus.Up) continue; + + var mac = nic.GetPhysicalAddress().GetAddressBytes(); + if (mac.Length == 6) return mac; + } + + return new byte[6]; // all-zero fallback + } + + // --- UDP RPLY listener --- + // Listens for AES-encrypted RPLY packets from the Console on port 53292. + // Key = PBKDF2(passphrase, salt, 1000, 32). Format: "RPLY"(4) + IV(16) + ciphertext(48). + private void UdpReplyLoop() + { + Log("Waiting for console to connect."); + var buf = new byte[4096]; + _replySocket.ReceiveTimeout = 2000; + + while (_running) + { + try + { + EndPoint remoteEp = new IPEndPoint(IPAddress.Any, 0); + int len = _replySocket.ReceiveFrom(buf, ref remoteEp); + if (len < 4) continue; + + string tag = Encoding.ASCII.GetString(buf, 0, 4); + if (tag != Proto.RPLY) continue; + + // Payload after "RPLY" tag is the AES-encrypted CONF block + int cipherLen = len - 4; + byte[] cipherText = new byte[cipherLen]; + Buffer.BlockCopy(buf, 4, cipherText, 0, cipherLen); + + // Derive AES key from our passphrase (operator typed this into Console) + byte[] aesKey = CryptoHelper.DeriveKey(_passphrase); + byte[] confData; + try { confData = CryptoHelper.Decrypt(cipherText, aesKey); } + catch { Log("RPLY decrypt failed — AES error, likely wrong passphrase key."); continue; } + + var config = ParseConf(confData); + if (config == null) { Log("RPLY parsed but config invalid — skipping."); continue; } + + Log($"CONF accepted: IP {config.Address}, Mask {config.Mask}, " + + $"GW {config.Gateway}, DNS {config.Dns}, Host {config.HostName}"); + + _receivedConfig = config; + if (remoteEp is IPEndPoint rip && !IPAddress.Any.Equals(rip.Address)) + _consoleAddress = rip.Address; + _configReceived.Set(); + return; + } + catch (SocketException) { /* timeout — loop */ } + catch (ObjectDisposedException) { break; } + catch (Exception ex) { Log($"RPLY receive error: {ex.Message}"); } + } + } + + // --- TCP session: OFB negotiation + RSA key exchange --- + // Uses Socket.Poll() because ReceiveTimeout doesn't affect AcceptTcpClient in .NET 6. + private void SendTcpConfirmation(System.Net.Sockets.TcpListener tcpConf) + { + try + { + const int TimeoutMs = 60_000; // 60 s — Console retries at ~0.5 s intervals + + Log("TCP port 53292 open — waiting for Console to connect..."); + + bool ready = tcpConf.Server.Poll(TimeoutMs * 1000, System.Net.Sockets.SelectMode.SelectRead); + if (!ready) { Log("TCP confirmation timed out."); return; } + + System.Net.Sockets.TcpClient client; + try { client = tcpConf.AcceptTcpClient(); } + catch (SocketException) { Log("TCP accept failed."); return; } + + using (client) + { + var netStream = client.GetStream(); + netStream.WriteTimeout = 10_000; + netStream.ReadTimeout = 30_000; + + Log("Console connection received. Negotiating..."); + + // ── 1. IV exchange ────────────────────────────────────── + var consoleIv = new byte[16]; + TcpReadExact(netStream, consoleIv); + + var podIv = new byte[16]; + RandomNumberGenerator.Fill(podIv); + netStream.Write(podIv, 0, 16); + netStream.Flush(); + + // ── 2. OFB session setup ──────────────────────────────── + // Each direction keyed from the OTHER side's IV + var aesKey = CryptoHelper.DeriveKey(_passphrase); + + using var ofb = new OFBDuplexStream(netStream, aesKey, + writeIv: consoleIv, // pod→console direction + readIv: podIv); // console→pod direction + ofb.WriteTimeout = 10_000; + ofb.ReadTimeout = 30_000; + + // ── 3. CONF handshake ─────────────────────────────────── + // Both sides write "CONF" over OFB and verify the reply + // to confirm matching PBKDF2 keys. + var confMsg = Encoding.UTF8.GetBytes("CONF"); + ofb.Write(confMsg, 0, confMsg.Length); + ofb.Flush(); + + var confReply = new byte[4]; + TcpReadExact(ofb, confReply); + if (!confReply.AsSpan().SequenceEqual(confMsg)) + { + Log($"CONF mismatch: got [{BitConverter.ToString(confReply)}], expected CONF — key/IV mismatch."); + return; + } + Log("Secure connection to console negotiated."); + + // ── 4. RSA key exchange ───────────────────────────────── + using var rsa = RSA.Create(Proto.RsaKeySize); + + Log("Sending console final key."); + using (var bw = new BinaryWriter(ofb, Encoding.UTF8, leaveOpen: true)) + { + bw.Write(rsa.ToXmlString(false)); // public key only + bw.Flush(); + } + + Log("Receiving console key."); + byte[] sessionKey; + using (var br = new BinaryReader(ofb, Encoding.UTF8, leaveOpen: true)) + { + int encLen = br.ReadInt32(); + byte[] enc = br.ReadBytes(encLen); + sessionKey = rsa.Decrypt(enc, RSAEncryptionPadding.Pkcs1); + } + Log($"Console key received ({sessionKey.Length} bytes)."); + + // Store session key — used for OFB on the management port (53290) + SessionKey = sessionKey; + + // Persist in original KeyStore format: [1-byte length] + [key bytes] + var keyDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TeslaLauncher"); + Directory.CreateDirectory(keyDir); + var keyPath = Path.Combine(keyDir, "TeslaKeyStore.key"); + using (var fs = File.Open(keyPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + fs.WriteByte((byte)sessionKey.Length); + fs.Write(sessionKey, 0, sessionKey.Length); + } + Log($"Session key saved ({sessionKey.Length} bytes)."); + + Log("Console confirmed — configuration complete."); + } + } + catch (Exception ex) { Log($"TCP confirmation error (non-fatal): {ex.Message}"); } + finally { try { tcpConf?.Stop(); } catch { } } + } + + /// Reads exactly buf.Length bytes from stream; throws IOException on EOF. + private static void TcpReadExact(Stream stream, byte[] buf) + { + int off = 0; + while (off < buf.Length) + { + int n = stream.Read(buf, off, buf.Length - off); + if (n == 0) throw new IOException("TCP connection closed before all bytes received."); + off += n; + } + } + + // Parse decrypted CONF payload: IP(4) + Mask(4) + GW(4) + DNS(4) + Hostname(ASCII) + private PodNetworkConfig ParseConf(byte[] data) + { + try + { + if (data.Length < 16) return null; + + var addr = new IPAddress(new byte[] { data[0], data[1], data[2], data[3] }); + var mask = new IPAddress(new byte[] { data[4], data[5], data[6], data[7] }); + var gw = new IPAddress(new byte[] { data[8], data[9], data[10], data[11] }); + var dns = new IPAddress(new byte[] { data[12], data[13], data[14], data[15] }); + + // Sanity-check: first octet of IP must be 1–223 (unicast range). + // If decryption produced garbage (wrong passphrase), this will fail silently — + // the cockpit just keeps broadcasting and waiting for the correct RPLY. + if (data[0] == 0 || data[0] >= 224) + { + Log($"RPLY: decryption produced non-unicast IP first-octet ({data[0]}) — ignoring (wrong passphrase in Console?)."); + return null; + } + + // Hostname is the printable ASCII content of the remaining bytes, + // terminated at the first non-printable character. + var hostName = string.Empty; + for (int i = 16; i < data.Length; i++) + { + byte b = data[i]; + if (b < 0x20 || b > 0x7e) break; + hostName += (char)b; + } + + Log($"CONF decrypted: IP={addr} Mask={mask} GW={gw} DNS={dns} Host={hostName}"); + return new PodNetworkConfig { Address=addr, Mask=mask, Gateway=gw, Dns=dns, HostName=hostName }; + } + catch (Exception ex) { Log($"ParseConf error: {ex.Message}"); return null; } + } + + // --- Show passcode display --- + private void ShowPasscodeDisplay() + { + _plasma = new PlasmaWriter(Proto.ComPort, Proto.ComBaud); + _plasma.ClearAll(); + _plasma.WriteLine("Request ID: {0}", _requestId); + _plasma.WriteLine("Passphrase: {0}", _passphrase); + +#if WINFORMS + // WinForms dialog — only shown when running as the Agent (user session) + var t = new Thread(() => + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + _displayForm = new PasscodeDisplayForm(_requestId, _passphrase); + _displayForm.ShowDialog(); + }) + { IsBackground = true }; + t.SetApartmentState(ApartmentState.STA); + t.Start(); +#endif + } + + // --- Temp IP via netsh --- + // + // Uses the old-style "interface ip set address" command (not "interface ipv4") + // because the old form directly calls SetAdapterIPAddress() and immediately + // updates the running TCP/IP stack. The "interface ipv4" variant only updates + // the persistent config and requires a network adapter restart to take effect. + // + // No gateway for the temp IP — it is only needed to receive the RPLY broadcast + // on the same subnet; no routing is required at this stage. + private static bool ConfigureTempIp(string adapterName) + { + return RunNetsh( + $"interface ip set address \"{adapterName}\" " + + $"static {Proto.TempIp} {Proto.TempMask}"); + } + + // --- Final IP via netsh + registry --- + // + // Uses the old-style "interface ip set address" command to immediately update + // both the running stack and the persistent config. The registry writes provide + // belt-and-suspenders persistence in case the DHCP client service ever re-asserts + // on the next boot. + private static bool ApplyNetworkConfig(string adapterId, string adapterName, PodNetworkConfig cfg) + { + // Set static IP + mask + gateway. The trailing "1" is the gateway metric. + bool ok = RunNetsh( + $"interface ip set address \"{adapterName}\" " + + $"static {cfg.Address} {cfg.Mask} {cfg.Gateway} 1"); + if (!ok) return false; + + // Set primary DNS. "register=primary" ensures the host registers its DNS record. + ok = RunNetsh( + $"interface ip set dns \"{adapterName}\" " + + $"static {cfg.Dns} primary"); + if (!ok) return false; + + // Belt-and-suspenders: write directly to the Tcpip registry key so the + // static config is guaranteed to survive a reboot. + PersistStaticIpRegistry(adapterId, cfg); + return true; + } + + private static void PersistStaticIpRegistry(string adapterId, PodNetworkConfig cfg) + { + string keyPath = $@"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{adapterId}"; + try + { + using var key = Registry.LocalMachine.OpenSubKey(keyPath, writable: true); + if (key == null) { Log2($"Registry key not found: {keyPath}"); return; } + + key.SetValue("EnableDHCP", 0, RegistryValueKind.DWord); + key.SetValue("IPAddress", new[] { cfg.Address.ToString() }, RegistryValueKind.MultiString); + key.SetValue("SubnetMask", new[] { cfg.Mask.ToString() }, RegistryValueKind.MultiString); + key.SetValue("DefaultGateway", new[] { cfg.Gateway.ToString() }, RegistryValueKind.MultiString); + key.SetValue("GatewayMetric", new[] { "0" }, RegistryValueKind.MultiString); + key.SetValue("NameServer", cfg.Dns.ToString(), RegistryValueKind.String); + + // Remove cached DHCP lease data that could re-enable DHCP on boot + foreach (var val in new[] { + "DhcpIPAddress", "DhcpSubnetMask", "DhcpDefaultGateway", + "DhcpNameServer", "DhcpServer", "Lease", + "LeaseObtainedTime", "LeaseTerminatesTime" }) + { + try { key.DeleteValue(val); } catch { } + } + Log2("Static IP configuration persisted to registry."); + } + catch (Exception ex) { Log2($"Registry static-IP persist failed: {ex.Message}"); } + } + + private static bool RunNetsh(string args) + { + var psi = new ProcessStartInfo("netsh", args) + { + UseShellExecute = false, + CreateNoWindow = true + }; + var p = Process.Start(psi); + p.WaitForExit(); + return p.ExitCode == 0; + } + + // --- Set hostname in registry --- + private static void SetHostName(string hostName) + { + try + { + using var key1 = Registry.LocalMachine.OpenSubKey(Proto.RegComputerName, true); + key1?.SetValue("ComputerName", hostName, RegistryValueKind.String); + + using var key2 = Registry.LocalMachine.OpenSubKey(Proto.RegTcpipParams, true); + key2?.SetValue("NV Hostname", hostName, RegistryValueKind.String); + + Log2($"Host set to: {hostName}"); + } + catch (Exception ex) { Log2($"Could not set host name: {ex.Message}"); } + } + + private static void Log2(string s) => Debug.WriteLine(s); + + // --- Virtual adapter filter --- + // Hyper-V, VirtualBox, VMware and similar virtualization layers expose + // Ethernet-type adapters that are DHCP-enabled by default. Without this + // filter, FindFirstEthernetAdapter() and IsMachineConfigured() both pick + // up the virtual adapter (often the lowest-index one) instead of the + // physical NIC, causing netsh to configure the wrong interface. + private static bool IsVirtualAdapter(NetworkInterface nic) + { + var desc = nic.Description ?? ""; + var name = nic.Name ?? ""; + // Common substrings found in virtual/software adapter descriptions. + // Checked case-insensitively to cover all Windows localizations. + string[] virtualMarkers = { + "Virtual", "Hyper-V", "VMware", "VirtualBox", + "Loopback", "Tunnel", "Miniport", "Wi-Fi Direct", + "Bluetooth", "WAN Miniport", "Microsoft Kernel Debug" + }; + foreach (var m in virtualMarkers) + if (desc.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0 || + name.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0) + return true; + // Physical NICs always have a 6-byte MAC. Virtual adapters sometimes + // use locally-administered addresses (bit 1 of first octet set). + var mac = nic.GetPhysicalAddress().GetAddressBytes(); + if (mac.Length == 6 && (mac[0] & 0x02) != 0) return true; // locally administered + return false; + } + + // --- Re-resolve adapter using the target IP from the RPLY config --- + // Using the Console's sender IP is unreliable when the Console has multiple NICs: + // Windows may route the RPLY broadcast through a NIC on a completely different + // subnet (e.g. 192.168.1.x instead of the 10.0.x NIC that faces the pods). + // The TARGET IP assigned in the RPLY payload is always on the pod's network — + // the Console operator picks it from the same range. So we find the local NIC + // whose current address is on the same subnet as the target: that is the correct + // NIC to reconfigure, regardless of which Console NIC happened to send the RPLY. + private void ResolveAdapterForTargetIp(IPAddress targetIp, IPAddress targetMask) + { + byte[] tBytes = targetIp.GetAddressBytes(); + byte[] mBytes = targetMask?.GetAddressBytes(); + if (mBytes == null || mBytes.Length != 4) mBytes = new byte[] { 255, 255, 255, 0 }; + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.OperationalStatus != OperationalStatus.Up) continue; + if (nic.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue; + if (IsVirtualAdapter(nic)) continue; + foreach (var ua in nic.GetIPProperties().UnicastAddresses) + { + if (ua.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue; + if (IPAddress.IsLoopback(ua.Address)) continue; + byte[] aBytes = ua.Address.GetAddressBytes(); + bool same = true; + for (int i = 0; i < 4; i++) + if ((aBytes[i] & mBytes[i]) != (tBytes[i] & mBytes[i])) { same = false; break; } + if (!same) continue; + try + { + int idx = nic.GetIPProperties().GetIPv4Properties().Index; + Log($"Adapter resolved to [{idx}] {nic.Name} (same subnet as target {targetIp})"); + _adapterIndex = idx; + _adapterId = nic.Id; + _adapterName = nic.Name; + return; + } + catch (NetworkInformationException) { } + } + } + Log($"Warning: target IP {targetIp} not on any local subnet — keeping [{_adapterIndex}] {_adapterName}"); + } + + // Returns (IPv4 interface index, adapter GUID). + // Index is used with `netsh interface ipv4 ... interface=N` (avoids name-quoting issues). + // GUID is used for direct registry writes to persist static IP across reboots. + private static (int index, string id, string name) FindFirstEthernetAdapter() + { + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.OperationalStatus != OperationalStatus.Up) continue; + if (nic.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue; + if (IsVirtualAdapter(nic)) continue; + // Skip interfaces with no IPv4 unicast address (NIC Up but no IP yet) + bool hasIp = false; + foreach (var ua in nic.GetIPProperties().UnicastAddresses) + if (ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork + && !IPAddress.IsLoopback(ua.Address)) + { hasIp = true; break; } + if (!hasIp) continue; + try + { + int idx = nic.GetIPProperties().GetIPv4Properties().Index; + Log2($"Selected physical adapter: [{idx}] {nic.Name} / {nic.Description}"); + return (idx, nic.Id, nic.Name); + } + catch (NetworkInformationException) { } + } + throw new InvalidOperationException("Could not find a physical Ethernet adapter with an IPv4 address."); + } + + private static string FindAdapterId(string displayName) + { + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (string.Equals(nic.Name, displayName, StringComparison.OrdinalIgnoreCase) || + string.Equals(nic.Description, displayName, StringComparison.OrdinalIgnoreCase)) + return nic.Id; + } + return FindFirstEthernetAdapter().id; // .name ignored in fallback + } + + // Find index for a named adapter (fallback when caller passes a display name) + private static int FindAdapterIndex(string displayName) + { + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (string.Equals(nic.Name, displayName, StringComparison.OrdinalIgnoreCase) || + string.Equals(nic.Description, displayName, StringComparison.OrdinalIgnoreCase)) + { + try { return nic.GetIPProperties().GetIPv4Properties().Index; } + catch (NetworkInformationException) { } + } + } + // Fall back to auto-detect if name not matched + return FindFirstEthernetAdapter().index; // .name ignored in fallback + } + + // --- Random string generator --- + // Alphabet length is 32 (2^5), byte range is 256 (2^8) — no modulo bias. + private static string GenerateRandomString(int length) + { + var buf = new byte[length]; + RandomNumberGenerator.Fill(buf); + var sb = new System.Text.StringBuilder(length); + for (int i = 0; i < length; i++) + sb.Append(Proto.Alphabet[buf[i] % Proto.Alphabet.Length]); + return sb.ToString(); + } + + private void Log(string msg) => _log?.Invoke(msg); + + public void Dispose() + { + _running = false; + try { _replySocket?.Close(); } catch { } + _plasma?.Dispose(); + } + } +} diff --git a/Launcher/TeslaLauncher.sln b/Launcher/TeslaLauncher.sln new file mode 100644 index 0000000..0575f46 --- /dev/null +++ b/Launcher/TeslaLauncher.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeslaLauncherAgent", "TeslaLauncherAgent.csproj", "{96766D0D-C3A1-A4C4-E93D-B963FBAC4C56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeslaLauncherService", "TeslaLauncherService.csproj", "{37D7E6C3-8DFD-CCB8-AFDF-18F2C0A48862}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {96766D0D-C3A1-A4C4-E93D-B963FBAC4C56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96766D0D-C3A1-A4C4-E93D-B963FBAC4C56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96766D0D-C3A1-A4C4-E93D-B963FBAC4C56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96766D0D-C3A1-A4C4-E93D-B963FBAC4C56}.Release|Any CPU.Build.0 = Release|Any CPU + {37D7E6C3-8DFD-CCB8-AFDF-18F2C0A48862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37D7E6C3-8DFD-CCB8-AFDF-18F2C0A48862}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37D7E6C3-8DFD-CCB8-AFDF-18F2C0A48862}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37D7E6C3-8DFD-CCB8-AFDF-18F2C0A48862}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4780968B-FFD6-47C9-946F-BE2874EAA95B} + EndGlobalSection +EndGlobal diff --git a/Launcher/TeslaLauncherAgent.cs b/Launcher/TeslaLauncherAgent.cs new file mode 100644 index 0000000..4bfd73d --- /dev/null +++ b/Launcher/TeslaLauncherAgent.cs @@ -0,0 +1,941 @@ +// ============================================================================= +// TeslaLauncher — Userspace Agent +// ============================================================================= +// Runs as a WinForms tray application in the logged-in user's desktop session. +// On first boot (unconfigured machine), displays a splash with the SecureConfig +// Request ID and Passphrase so the operator can read them to the console. +// On subsequent boots, listens on a Named Pipe for commands from +// TeslaLauncherService and manages simulation applications. +// ============================================================================= + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.IO.IsolatedStorage; +using System.IO.Pipes; +using System.Linq; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Tesla.Launcher.Shared; + +namespace Tesla.Launcher.Agent +{ + public class AgentApplication : Form + { + // ── Configuration ───────────────────────────────────────────────────── + private const string PIPE_NAME = "TeslaLauncherIPC"; + private const string CONFIG_FILE = @"C:\ProgramData\TeslaLauncher\LaunchApps.xml"; + private const string GAMES_DIR = @"C:\Games"; + // ───────────────────────────────────────────────────────────────────── + + private NotifyIcon _trayIcon; + private ContextMenuStrip _trayMenu; + + private List _launchApps = new(); + private readonly Dictionary> _runningProcesses = new(); + private readonly object _processLock = new(); + private readonly List _watcherThreads = new(); + private bool _stopping = false; + + private readonly Dictionary _installProgress = new(); + private readonly object _installLock = new(); + + private Thread _pipeThread; + private CancellationTokenSource _cts = new(); + + // ── Entry point ─────────────────────────────────────────────────────── + + [STAThread] + public static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + // First-boot check: if this machine is unconfigured (DHCP adapter), + // show a waiting splash. The Service (Session 0) runs the full + // SecureConfig protocol and applies the static IP. + // When the splash detects configuration is complete it closes itself, + // and we fall through to start the normal Agent with the pipe server. + if (!IsMachineConfigured()) + { + ShowConfiguringWait(); // blocks until config detected or form closed + + // If still not configured (user closed the splash manually), exit. + if (!IsMachineConfigured()) + return; + } + + Application.Run(new AgentApplication()); + } + + // ── Secure Configuration ────────────────────────────────────────────── + + /// + /// Returns true when the cockpit has already been configured — + /// detected by the Ethernet adapter having a static IP assignment. + /// SecureConfiguration writes the permanent address via netsh with + /// no DHCP, so a static adapter means configuration is complete. + /// A DHCP adapter (or no adapter) means we are on a fresh machine + /// and need to run the SecureConfiguration protocol. + /// + private static bool IsMachineConfigured() + { + foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue; + if (nic.OperationalStatus != OperationalStatus.Up) continue; + if (IsVirtualAdapter(nic)) continue; + + try + { + var ipv4 = nic.GetIPProperties().GetIPv4Properties(); + // IsDhcpEnabled == false → static IP → already configured + if (!ipv4.IsDhcpEnabled) + return true; + } + catch (NetworkInformationException) + { + // GetIPv4Properties() throws if IPv4 is not configured at all; + // treat that the same as DHCP (i.e. not yet configured). + } + } + return false; // all Ethernet adapters are DHCP or absent → needs configuration + } + + private static bool IsVirtualAdapter(NetworkInterface nic) + { + var desc = nic.Description ?? ""; + var name = nic.Name ?? ""; + string[] markers = { + "Virtual", "Hyper-V", "VMware", "VirtualBox", + "Loopback", "Tunnel", "Miniport", "Wi-Fi Direct", + "Bluetooth", "WAN Miniport", "Microsoft Kernel Debug" + }; + foreach (var m in markers) + if (desc.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0 || + name.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0) + return true; + var mac = nic.GetPhysicalAddress().GetAddressBytes(); + if (mac.Length == 6 && (mac[0] & 0x02) != 0) return true; // locally-administered MAC + return false; + } + + private static void ShowConfiguringWait() + { + // The Service handles SecureConfig in Session 0. The Agent shows + // a splash with the RequestId and Passphrase for the operator. + using var form = new Form + { + Text = "Tesla Cockpit Configuration", + FormBorderStyle = FormBorderStyle.FixedDialog, + StartPosition = FormStartPosition.CenterScreen, + ClientSize = new System.Drawing.Size(480, 260), + MaximizeBox = false, + MinimizeBox = false, + TopMost = true, + }; + + var lblStatus = new Label + { + Text = "This cockpit is being configured.\r\n" + + "Configuration will complete automatically.", + AutoSize = false, + Dock = DockStyle.Top, + Height = 50, + TextAlign = System.Drawing.ContentAlignment.MiddleCenter, + Font = new System.Drawing.Font("Arial", 10f), + Padding = new Padding(12, 8, 12, 0), + }; + form.Controls.Add(lblStatus); + + var lblRequestIdTitle = new Label + { + Text = "Request ID:", + AutoSize = false, + TextAlign = System.Drawing.ContentAlignment.MiddleCenter, + Font = new System.Drawing.Font("Arial", 11f), + Top = 60, + Left = 0, + Width = 480, + Height = 25, + }; + form.Controls.Add(lblRequestIdTitle); + + var lblRequestId = new Label + { + Text = "waiting...", + AutoSize = false, + TextAlign = System.Drawing.ContentAlignment.MiddleCenter, + Font = new System.Drawing.Font("Consolas", 28f, System.Drawing.FontStyle.Bold), + ForeColor = System.Drawing.Color.DarkBlue, + Top = 85, + Left = 0, + Width = 480, + Height = 45, + }; + form.Controls.Add(lblRequestId); + + var lblPassphraseTitle = new Label + { + Text = "Passphrase:", + AutoSize = false, + TextAlign = System.Drawing.ContentAlignment.MiddleCenter, + Font = new System.Drawing.Font("Arial", 11f), + Top = 140, + Left = 0, + Width = 480, + Height = 25, + }; + form.Controls.Add(lblPassphraseTitle); + + var lblPassphrase = new Label + { + Text = "waiting...", + AutoSize = false, + TextAlign = System.Drawing.ContentAlignment.MiddleCenter, + Font = new System.Drawing.Font("Consolas", 28f, System.Drawing.FontStyle.Bold), + ForeColor = System.Drawing.Color.DarkRed, + Top = 165, + Left = 0, + Width = 480, + Height = 45, + }; + form.Controls.Add(lblPassphrase); + + var lblHint = new Label + { + Text = "Read the Passphrase to the console operator.", + AutoSize = false, + TextAlign = System.Drawing.ContentAlignment.MiddleCenter, + Font = new System.Drawing.Font("Arial", 9f, System.Drawing.FontStyle.Italic), + ForeColor = System.Drawing.Color.Gray, + Top = 220, + Left = 0, + Width = 480, + Height = 25, + }; + form.Controls.Add(lblHint); + + string lastJson = null; + bool codesLoaded = false; + var cfgFile = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TeslaLauncher", "configuring.json"); + + // Poll every 2 s — read codes from the Service's shared file. + // The file lifecycle: + // 1. Service deletes stale file on startup + // 2. Service writes fresh file with new codes + // 3. Agent reads and displays codes + // 4. Service deletes file when config is complete → splash closes + var timer = new System.Windows.Forms.Timer { Interval = 2000 }; + timer.Tick += (s, e) => + { + if (File.Exists(cfgFile)) + { + try + { + var json = File.ReadAllText(cfgFile); + if (json != lastJson) + { + lastJson = json; + var doc = System.Text.Json.JsonDocument.Parse(json); + var root = doc.RootElement; + if (root.TryGetProperty("RequestId", out var rid)) + lblRequestId.Text = rid.GetString(); + if (root.TryGetProperty("Passphrase", out var pp)) + lblPassphrase.Text = pp.GetString(); + codesLoaded = true; + } + } + catch { /* file may be mid-write, retry next tick */ } + } + else if (codesLoaded) + { + // Service deleted the file → configuration complete. + timer.Stop(); + form.Close(); + } + }; + timer.Start(); + + Application.Run(form); + } + + // ── Form / tray setup ───────────────────────────────────────────────── + + public AgentApplication() + { + ShowInTaskbar = false; + WindowState = FormWindowState.Minimized; + Opacity = 0; + + BuildTrayIcon(); + LoadLaunchApps(); + StartPipeServer(); + } + + private void BuildTrayIcon() + { + _trayMenu = new ContextMenuStrip(); + _trayMenu.Items.Add("Tesla Launcher Agent", null, null).Enabled = false; + _trayMenu.Items.Add(new ToolStripSeparator()); + _trayMenu.Items.Add("Reload Config", null, (s, e) => LoadLaunchApps()); + _trayMenu.Items.Add("Kill All Apps", null, (s, e) => CmdKillAllApps()); + _trayMenu.Items.Add(new ToolStripSeparator()); + _trayMenu.Items.Add("Exit", null, (s, e) => ExitAgent()); + + _trayIcon = new NotifyIcon + { + Text = "Tesla Launcher Agent", + Icon = SystemIcons.Application, + ContextMenuStrip = _trayMenu, + Visible = true + }; + } + + private void ExitAgent() + { + _stopping = true; + _cts.Cancel(); + _trayIcon.Visible = false; + Application.Exit(); + } + + // ── LaunchApps.xml ──────────────────────────────────────────────────── + + private void LoadLaunchApps() + { + try + { + if (!File.Exists(CONFIG_FILE)) + { + _launchApps = new List(); + SetTrayStatus("Config not found"); + return; + } + + var xml = new System.Xml.Serialization.XmlSerializer(typeof(List)); + using var reader = File.OpenRead(CONFIG_FILE); + _launchApps = (List)xml.Deserialize(reader) + ?? new List(); + + SetTrayStatus($"{_launchApps.Count} apps configured"); + } + catch (Exception ex) + { + SetTrayStatus($"Config error: {ex.Message}"); + } + } + + private void SaveLaunchApps() + { + var xml = new System.Xml.Serialization.XmlSerializer(typeof(List)); + Directory.CreateDirectory(Path.GetDirectoryName(CONFIG_FILE)!); + using var writer = File.CreateText(CONFIG_FILE); + xml.Serialize(writer, _launchApps); + } + + private void SetTrayStatus(string status) + { + if (_trayIcon != null) _trayIcon.Text = $"Tesla Launcher: {status}"; + } + + // ── Named Pipe Server ───────────────────────────────────────────────── + + private void StartPipeServer() + { + _pipeThread = new Thread(PipeServerLoop) + { + IsBackground = true, + Name = "TeslaLauncherPipeServer" + }; + _pipeThread.Start(); + } + + private void PipeServerLoop() + { + var ct = _cts.Token; + + while (!ct.IsCancellationRequested) + { + try + { + using var server = new NamedPipeServerStream( + PIPE_NAME, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous); + + server.WaitForConnectionAsync(ct).GetAwaiter().GetResult(); + HandlePipeRequest(server); + } + catch (OperationCanceledException) { break; } + catch (Exception) + { + if (!ct.IsCancellationRequested) Thread.Sleep(500); + } + } + } + + private void HandlePipeRequest(NamedPipeServerStream pipe) + { + IpcResponse response; + try + { + var lenBuf = new byte[4]; + ReadExact(pipe, lenBuf); + var reqBuf = new byte[BitConverter.ToInt32(lenBuf, 0)]; + ReadExact(pipe, reqBuf); + + var msg = JsonSerializer.Deserialize( + Encoding.UTF8.GetString(reqBuf)); + + response = ExecuteCommand(msg); + } + catch (Exception ex) + { + response = new IpcResponse { Success = false, Message = ex.Message }; + } + + var resBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(response)); + pipe.Write(BitConverter.GetBytes(resBytes.Length)); + pipe.Write(resBytes); + pipe.Flush(); + } + + private static void ReadExact(Stream stream, byte[] buf) + { + var off = 0; + while (off < buf.Length) + { + var n = stream.Read(buf, off, buf.Length - off); + if (n == 0) throw new IOException("Pipe closed."); + off += n; + } + } + + // ── Command Dispatcher ──────────────────────────────────────────────── + + private IpcResponse ExecuteCommand(IpcMessage msg) + { + if (msg == null) return Fail("Null message received."); + + try + { + object result = msg.Command switch + { + "PING" => (object)null, + "CLEARSTORE" => CmdClearStore(), + "LAUNCHAPP" => CmdLaunchApp(msg), + "KILLAPP" => CmdKillApp(msg), + "KILLALLOFTYPE" => CmdKillAllOfType(msg), + "KILLALLAPPS" => CmdKillAllApps(), + "GETLAUNCHEDAPPS" => CmdGetLaunchedApps(), + "GETLAUNCHABLEAPPS" => CmdGetLaunchableApps(), + "GETINSTALLEDAPPS" => CmdGetLaunchableApps(), + "REMOVEAPP" => CmdRemoveApp(msg), + "INSTALLAPP" => CmdInstallApp(msg), + "UNINSTALLAPP" => CmdUninstallApp(msg), + "GET_VOLUMELEVEL" => CmdGetVolumeLevel(), + "SET_VOLUMELEVEL" => CmdSetVolumeLevel(msg), + "FULLUPDATE" => CmdFullUpdate(), + "INITIATEINSTALLPRODUCT" => CmdInitiateInstallProduct(msg), + "GETOUTOFBANDPROGRESS" => CmdGetOutOfBandProgress(msg), + "SHUTDOWN" => CmdShutdown(msg), + _ => throw new Exception($"Unknown command: {msg.Command}") + }; + + return Ok(result); + } + catch (Exception ex) + { + return Fail(ex.Message); + } + } + + // ── Command Implementations ─────────────────────────────────────────── + + private object CmdClearStore() + { + var store = IsolatedStorageFile.GetMachineStoreForAssembly(); + foreach (var file in store.GetFileNames("*")) + store.DeleteFile(file); + LoadLaunchApps(); + return null; + } + + private object CmdLaunchApp(IpcMessage msg) + { + var key = msg.LaunchKey + ?? throw new Exception("LaunchApp requires a LaunchKey"); + + var app = FindApp(key) + ?? throw new Exception($"No app configured for key '{key}'"); + + // A launch entry can be registered (pushed from the Console) before its game + // files are installed. Fail cleanly with an actionable message rather than + // surfacing a raw Win32 "file not found" from Process.Start. + if (string.IsNullOrWhiteSpace(app.ExeFile) || !File.Exists(app.ExeFile)) + throw new Exception( + $"Cannot launch '{app.DisplayName ?? key}': executable not found at " + + $"'{app.ExeFile}'. The product may be registered but not yet installed on this pod."); + + var psi = new ProcessStartInfo + { + FileName = app.ExeFile, + Arguments = app.Arguments ?? "", + WorkingDirectory = app.WorkingDirectory ?? Path.GetDirectoryName(app.ExeFile), + UseShellExecute = false + }; + + var process = Process.Start(psi) + ?? throw new Exception($"Process.Start returned null for '{app.ExeFile}'"); + + lock (_processLock) + { + if (!_runningProcesses.ContainsKey(key)) + _runningProcesses[key] = new List(); + _runningProcesses[key].Add(process); + } + + if (app.AutoRestart) StartWatcher(app, process); + + SetTrayStatus($"Running: {app.DisplayName ?? key}"); + return new LaunchedAppData { LaunchKey = key, ProcessId = process.Id }; + } + + private object CmdKillApp(IpcMessage msg) + { + var key = msg.LaunchKey; + int? pid = null; + + if (msg.PayloadJson != null) + { + var parms = JsonSerializer.Deserialize(msg.PayloadJson); + if (parms?.Length > 1 && parms[1] is JsonElement je) + pid = je.ValueKind == JsonValueKind.Number ? je.GetInt32() : (int?)null; + } + + lock (_processLock) + { + if (!_runningProcesses.TryGetValue(key ?? "", out var procs)) return null; + + var toKill = pid.HasValue + ? procs.Where(p => p.Id == pid.Value).ToList() + : procs.ToList(); + + foreach (var p in toKill) + { + try { if (!p.HasExited) p.Kill(); } catch { } + procs.Remove(p); + } + + if (procs.Count == 0) _runningProcesses.Remove(key ?? ""); + } + + return null; + } + + private object CmdKillAllOfType(IpcMessage msg) + { + var key = msg.LaunchKey ?? throw new Exception("KillAllOfType requires LaunchKey"); + + lock (_processLock) + { + if (_runningProcesses.TryGetValue(key, out var procs)) + { + foreach (var p in procs) + try { if (!p.HasExited) p.Kill(); } catch { } + _runningProcesses.Remove(key); + } + } + + return null; + } + + private object CmdKillAllApps() + { + lock (_processLock) + { + foreach (var kvp in _runningProcesses) + foreach (var p in kvp.Value) + try { if (!p.HasExited) p.Kill(); } catch { } + _runningProcesses.Clear(); + } + + SetTrayStatus("All apps stopped"); + return null; + } + + private object CmdGetLaunchedApps() + { + var result = new List(); + + lock (_processLock) + { + foreach (var kvp in _runningProcesses) + { + kvp.Value.RemoveAll(p => p.HasExited); + + foreach (var p in kvp.Value) + result.Add(new LaunchedAppData + { + LaunchKey = kvp.Key, + ProcessId = p.Id + }); + } + + foreach (var key in _runningProcesses.Keys + .Where(k => _runningProcesses[k].Count == 0).ToList()) + _runningProcesses.Remove(key); + } + + return result.ToArray(); + } + + private object CmdGetLaunchableApps() => _launchApps.ToArray(); + + private object CmdRemoveApp(IpcMessage msg) + { + var key = msg.LaunchKey ?? throw new Exception("RemoveApp requires LaunchKey"); + _launchApps.RemoveAll(a => a.LaunchKey == key); + SaveLaunchApps(); + return null; + } + + private object CmdInstallApp(IpcMessage msg) + { + if (msg.PayloadJson == null) + throw new Exception("InstallApp requires PayloadJson"); + + var parms = JsonSerializer.Deserialize(msg.PayloadJson); + if (parms == null || parms.Length == 0) + throw new Exception("InstallApp: no parameters"); + + var appData = JsonSerializer.Deserialize(parms[0].GetRawText()) + ?? throw new Exception("InstallApp: could not deserialize LaunchData"); + + var existing = _launchApps.FindIndex(a => a.LaunchKey == appData.LaunchKey); + if (existing >= 0) _launchApps[existing] = appData; + else _launchApps.Add(appData); + + SaveLaunchApps(); + return null; + } + + private object CmdUninstallApp(IpcMessage msg) + { + var key = msg.LaunchKey ?? throw new Exception("UninstallApp requires LaunchKey"); + CmdKillAllOfType(msg); + _launchApps.RemoveAll(a => a.LaunchKey == key); + SaveLaunchApps(); + return null; + } + + private object CmdGetVolumeLevel() + { + return GetMasterVolume(); + } + + private object CmdSetVolumeLevel(IpcMessage msg) + { + if (msg.PayloadJson == null) + throw new Exception("set_VolumeLevel requires parameters"); + + var parms = JsonSerializer.Deserialize(msg.PayloadJson); + if (parms == null || parms.Length == 0) + throw new Exception("Invalid volume value"); + var scalar = parms[0].ValueKind == JsonValueKind.Number + ? (float)parms[0].GetDouble() + : throw new Exception("Invalid volume value"); + SetMasterVolume(Math.Clamp(scalar, 0f, 1f)); + return null; + } + + private object CmdFullUpdate() => + new FullUpdateData + { + InstalledApps = _launchApps.ToArray(), + LaunchedApps = (LaunchedAppData[])CmdGetLaunchedApps(), + VolumeLevel = GetMasterVolume() + }; + + private object CmdInitiateInstallProduct(IpcMessage msg) + { + // ILauncherService.InitiateInstallProduct() takes no parameters. + // It just returns a tracking Guid. The actual game registration + // happens via InstallApp(LaunchData) in a separate RPC call. + var callId = Guid.NewGuid().ToString("N"); + var progress = new OutOfBandProgress + { + PercentComplete = 100, + Status = "Complete", + IsCompleted = true + }; + + lock (_installLock) _installProgress[callId] = progress; + + return callId; + } + + private object CmdGetOutOfBandProgress(IpcMessage msg) + { + var callId = msg.PayloadJson != null + ? JsonSerializer.Deserialize(msg.PayloadJson)?[0].GetString() + : null; + + if (callId == null) + throw new Exception("GetOutOfBandProgress requires a callId"); + + lock (_installLock) + { + if (_installProgress.TryGetValue(callId, out var prog)) return prog; + } + + return new OutOfBandProgress + { + PercentComplete = 0, + Status = "Unknown call ID", + IsCompleted = true + }; + } + + private object CmdShutdown(IpcMessage msg) + { + bool doRestart = false; + if (msg.PayloadJson != null) + { + try + { + var parms = JsonSerializer.Deserialize(msg.PayloadJson); + if (parms?.Length > 0 && parms[0].ValueKind == JsonValueKind.True) + doRestart = true; + } + catch { /* default to shutdown */ } + } + + CmdKillAllApps(); + var flag = doRestart ? "/r" : "/s"; + new Thread(() => + { + Thread.Sleep(2000); + Process.Start("shutdown", $"{flag} /t 0"); + }) { IsBackground = true }.Start(); + return null; + } + + // ── Helpers ─────────────────────────────────────────────────────────── + + private LaunchData FindApp(string launchKey) => + _launchApps.FirstOrDefault(a => + string.Equals(a.LaunchKey, launchKey, StringComparison.OrdinalIgnoreCase)); + + private void StartWatcher(LaunchData app, Process process) + { + new Thread(() => + { + process.WaitForExit(); + if (_stopping) return; + + bool stillTracked; + lock (_processLock) + { + stillTracked = _runningProcesses.TryGetValue( + app.LaunchKey, out var procs) && procs.Contains(process); + } + + if (!stillTracked) return; + + Thread.Sleep(2000); + try { CmdLaunchApp(new IpcMessage { LaunchKey = app.LaunchKey }); } + catch { } + }) + { IsBackground = true, Name = $"Watcher-{app.LaunchKey}" }.Start(); + } + + // ── Volume Control ──────────────────────────────────────────────────── + // The Console stores/retrieves volume as a float (0.0–1.0 scalar). + // We cache the exact value the Console sent so get returns the same + // value without CoreAudio roundtrip quantization error. + + private float _cachedVolumeScalar = 1.0f; + + private float GetMasterVolume() + { + return _cachedVolumeScalar; + } + + private void SetMasterVolume(float scalar) + { + _cachedVolumeScalar = scalar; + + // Try nircmd.exe first (legacy compatibility) + var nircmd = Path.Combine(GAMES_DIR, "nircmd.exe"); + if (File.Exists(nircmd)) + { + Process.Start(nircmd, $"setsysvolume {(int)(scalar * 65535)}")?.Dispose(); + return; + } + + // Windows Core Audio API fallback (Vista+, no external dependencies) + CoreAudio.SetMasterScalar(scalar); + } + + // ── Response helpers ────────────────────────────────────────────────── + + private static IpcResponse Ok(object data = null) => new() { Success = true, Data = data }; + private static IpcResponse Fail(string msg) => new() { Success = false, Message = msg }; + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + Visible = false; + } + + protected override void Dispose(bool disposing) + { + if (disposing) { _trayIcon?.Dispose(); _trayMenu?.Dispose(); } + base.Dispose(disposing); + } + } + + // ── Agent-local data types ──────────────────────────────────────────────── + // These are used internally by the Agent (XML config, process management). + // They are NOT the BinaryFormatter wire types (Tesla.Net namespace). + + /// + /// Agent-internal representation of a launchable app. + /// Loaded from LaunchApps.xml via XmlSerializer. + /// + [Serializable] + public class LaunchData + { + public string LaunchKey { get; set; } + public string DisplayName { get; set; } + public string WorkingDirectory { get; set; } + public string ExeFile { get; set; } + public string Arguments { get; set; } + public bool AutoRestart { get; set; } + } + + /// + /// Agent-internal tracking of a running process. + /// Returned via JSON IPC (string LaunchKey, not Guid). + /// + [Serializable] + public class LaunchedAppData + { + public string LaunchKey { get; set; } + public int ProcessId { get; set; } + } + + /// + /// Agent-internal full state snapshot. + /// Returned via JSON IPC for the FullUpdate command. + /// + [Serializable] + public class FullUpdateData + { + public LaunchData[] InstalledApps { get; set; } + public LaunchedAppData[] LaunchedApps { get; set; } + public float VolumeLevel { get; set; } + } + + [Serializable] + public class OutOfBandProgress + { + public int PercentComplete { get; set; } + public string Status { get; set; } + public bool IsCompleted { get; set; } + } + + // ── Windows Core Audio API — minimal COM interop (Vista+) ───────────────── + // No external dependencies. Vtable slot order matches the SDK headers exactly. + // Methods we don't call are declared as void stubs to preserve vtable offsets. + + internal static class CoreAudio + { + internal static float GetMasterScalar() + { + var ep = GetEndpointVolume(); + try { ep.GetMasterVolumeLevelScalar(out float v); return v; } + finally { ReleaseAll(ep); } + } + + internal static void SetMasterScalar(float scalar) + { + var ep = GetEndpointVolume(); + try { var ctx = Guid.Empty; ep.SetMasterVolumeLevelScalar(scalar, ref ctx); } + finally { ReleaseAll(ep); } + } + + private static IAudioEndpointVolume GetEndpointVolume() + { + var enumerator = (IMMDeviceEnumerator)new MMAudioEnumeratorComClass(); + try + { + enumerator.GetDefaultAudioEndpoint(0 /*eRender*/, 1 /*eMultimedia*/, out var device); + try + { + var iid = typeof(IAudioEndpointVolume).GUID; + device.Activate(ref iid, 23 /*CLSCTX_ALL*/, IntPtr.Zero, out var obj); + return (IAudioEndpointVolume)obj; + } + finally { Marshal.ReleaseComObject(device); } + } + finally { Marshal.ReleaseComObject(enumerator); } + } + + private static void ReleaseAll(object obj) + { + if (obj != null) Marshal.ReleaseComObject(obj); + } + } + + [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] + internal class MMAudioEnumeratorComClass { } + + [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IMMDeviceEnumerator + { + void _unused_EnumAudioEndpoints(); // slot 0 — not used + [PreserveSig] + int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint); + } + + [Guid("D666063F-1587-4E43-81F1-B948E807363F"), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IMMDevice + { + [PreserveSig] + int Activate(ref Guid iid, int clsCtx, IntPtr pActivationParams, + [MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer); + } + + // Vtable order (after IUnknown): RegisterControlChangeNotify(0), + // UnregisterControlChangeNotify(1), GetChannelCount(2), + // SetMasterVolumeLevel(3), SetMasterVolumeLevelScalar(4), + // GetMasterVolumeLevel(5), GetMasterVolumeLevelScalar(6) + [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IAudioEndpointVolume + { + void _unused_RegisterControlChangeNotify(); // slot 0 + void _unused_UnregisterControlChangeNotify(); // slot 1 + void _unused_GetChannelCount(); // slot 2 + [PreserveSig] int SetMasterVolumeLevel(float levelDB, ref Guid ctx); + [PreserveSig] int SetMasterVolumeLevelScalar(float level, ref Guid ctx); + [PreserveSig] int GetMasterVolumeLevel(out float levelDB); + [PreserveSig] int GetMasterVolumeLevelScalar(out float level); + } +} diff --git a/Launcher/TeslaLauncherAgent.csproj b/Launcher/TeslaLauncherAgent.csproj new file mode 100644 index 0000000..59a88b8 --- /dev/null +++ b/Launcher/TeslaLauncherAgent.csproj @@ -0,0 +1,33 @@ + + + + net6.0-windows + WinExe + true + disable + disable + + true + 0.1.0 + app.ico + TeslaLauncherAgent + Tesla.Launcher.Agent + x86 + win-x86 + true + true + Tesla.Launcher.Agent.AgentApplication + + + + + + + + + + + + + + diff --git a/Launcher/TeslaLauncherService.cs b/Launcher/TeslaLauncherService.cs new file mode 100644 index 0000000..f3d9d20 --- /dev/null +++ b/Launcher/TeslaLauncherService.cs @@ -0,0 +1,1103 @@ +// ============================================================================= +// TeslaLauncher — Windows Service (Session 0) +// ============================================================================= +// Replaces TeslaLauncherService.exe (Elsewhen Studios LLC, .NET Framework 2.0). +// Runs in Session 0, auto-starts at boot before user login. +// Listens on TCP 53290 for OFB-encrypted BinaryFormatter RPC from TeslaConsole. +// Forwards commands to TeslaLauncherAgent (user session) via Named Pipe. +// +// On first boot (DHCP detected), runs SecureConfig to receive network +// configuration from the Console and establish the session key. +// ============================================================================= + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.IO.Pipes; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using TeslaSecureConfig; +using Tesla.Launcher.Shared; +using Tesla.Net; + +#pragma warning disable SYSLIB0011 // BinaryFormatter is required for Console wire compatibility + +namespace Tesla.Launcher.Service +{ + public class TeslaLauncherService : BackgroundService + { + // --------------------------------------------------------------- + // CONFIGURATION + // --------------------------------------------------------------- + + /// + /// TCP port the Console connects to for RPC commands. + /// Original: ManagePort = 53290 (confirmed from decompiled TeslaLauncherService.exe). + /// Each connection is wrapped in OFB encryption via NegotiateCryptoStreams + /// using the session key from SecureConfig (TeslaKeyStore.key). + /// + private const int CONSOLE_PORT = 53290; + + /// + /// Named pipe connecting this service to TeslaLauncherAgent.exe. + /// Must match PIPE_NAME in TeslaLauncherAgent.cs. + /// + private const string PIPE_NAME = "TeslaLauncherIPC"; + + /// + /// Seconds to wait for the Agent to connect on the Named Pipe. + /// If the Agent isn't running (user not yet logged in), we return an error. + /// + private const int AGENT_CONNECT_TIMEOUT_MS = 3000; + + // --------------------------------------------------------------- + + /// Path to session key file (original KeyStore format). + private static readonly string KEY_FILE = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TeslaLauncher", "TeslaKeyStore.key"); + + private readonly ILogger _logger; + private TcpListener _listener; + private readonly List _clientTasks = new(); + private readonly object _taskLock = new(); + private byte[] _sessionKey; + + // Install product — file transfer progress tracking. + // InitiateInstallProduct creates a Guid; the Console then streams the + // game archive on the SAME OFB connection. GetOutOfBandProgress polls + // this dictionary from a separate connection. + private readonly Dictionary _installProgress = new(); + private readonly object _installLock = new(); + + public TeslaLauncherService(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation( + "Tesla Launcher Service starting on port {Port}", CONSOLE_PORT); + + // Enable BinaryFormatter at runtime. Must also be set in .csproj. + // Both switches are required on .NET 6+. + AppContext.SetSwitch( + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); + + // ── Boot diagnostic — always written so every reboot is traceable ── + // Logs adapter state to podconf.log before IsMachineConfigured() decides, + // visible even if SecureConfig does not run. + var logPath = Path.Combine(AppContext.BaseDirectory, "podconf.log"); + void StartupLog(string msg) => File.AppendAllText(logPath, + $"[{DateTime.Now:HH:mm:ss}] {msg}{Environment.NewLine}"); + StartupLog("=== Service starting ==="); + foreach (var nic in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Ethernet) continue; + if (nic.OperationalStatus != System.Net.NetworkInformation.OperationalStatus.Up) continue; + try + { + var ipv4 = nic.GetIPProperties().GetIPv4Properties(); + StartupLog($" NIC [{nic.Name}] DHCP={ipv4.IsDhcpEnabled} Virtual={IsVirtualAdapter(nic)}"); + } + catch { StartupLog($" NIC [{nic.Name}] (no IPv4 props)"); } + } + + // ── First-boot secure configuration ────────────────────────────── + // If the Ethernet adapter is still on DHCP the cockpit has not yet + // been configured. Run PodSecureConfigurator here in Session 0: + // • broadcasts the RQST beacon (MAC + RequestId) + // • displays passphrase on COM2 / PlasmaIO + // • receives UDP RPLY with AES-encrypted network configuration + // • applies netsh IP settings and hostname + // • completes TCP handshake with Console + RSA key exchange + // • saves 32-byte session key to TeslaKeyStore.key + // After SecureConfig, fall through to the normal command listener — + // the original Service does the same (ExecutionMode after Configure). + if (!IsMachineConfigured()) + { + StartupLog("IsMachineConfigured=false → running SecureConfig"); + _logger.LogInformation("Machine is unconfigured (DHCP). Running SecureConfiguration."); + await Task.Run(() => RunSecureConfiguration(stoppingToken), stoppingToken); + // Load key that was saved during SecureConfig + _sessionKey = LoadSessionKey(); + if (_sessionKey == null) + { + _logger.LogWarning("SecureConfig completed but no session key was saved. Cannot start command listener."); + return; + } + StartupLog("SecureConfig complete → starting command listener on port 53290"); + } + else + { + StartupLog("IsMachineConfigured=true → loading session key"); + _sessionKey = LoadSessionKey(); + if (_sessionKey == null) + { + _logger.LogWarning("Machine is configured but TeslaKeyStore.key not found. Reboot with DHCP to re-run SecureConfig."); + StartupLog("ERROR: TeslaKeyStore.key not found — cannot start command listener"); + return; + } + StartupLog($"Session key loaded ({_sessionKey.Length} bytes) → starting normal service"); + } + _logger.LogInformation("Starting command listener on port {Port}.", CONSOLE_PORT); + + try + { + _listener = new TcpListener(IPAddress.Any, CONSOLE_PORT); + _listener.Start(); + _logger.LogInformation("Listening. Waiting for Console connections..."); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + var client = await _listener.AcceptTcpClientAsync(stoppingToken); + _logger.LogInformation( + "Console connected from {EP}", client.Client.RemoteEndPoint); + + // Handle each Console connection on its own thread. + // BinaryFormatter.Deserialize is synchronous, so we + // offload to a thread-pool thread via Task.Run. + var task = Task.Run( + () => HandleConsoleClient(client, stoppingToken), + stoppingToken); + + lock (_taskLock) + { + _clientTasks.Add(task); + _clientTasks.RemoveAll(t => t.IsCompleted); + } + } + catch (OperationCanceledException) { break; } + catch (Exception ex) + { + _logger.LogError(ex, "Error accepting Console connection"); + } + } + } + finally + { + _listener?.Stop(); + _logger.LogInformation("Tesla Launcher Service stopped."); + } + } + + // ── Console Connection Handler ─────────────────────────────────── + + private async Task HandleConsoleClient(TcpClient tcpClient, CancellationToken ct) + { + using var client = tcpClient; + using var netStream = client.GetStream(); + netStream.WriteTimeout = 10_000; + netStream.ReadTimeout = 30_000; + + // ── OFB crypto negotiation ──────────────────────────────────── + // Both sides exchange 16-byte IVs, then verify "CONF" handshake. + + // Read Console's IV + var consoleIv = new byte[16]; + try { ReadExactSync(netStream, consoleIv); } + catch (Exception ex) + { + _logger.LogWarning("Failed reading Console IV: {Msg}", ex.Message); + return; + } + + // Generate and send our IV + var podIv = new byte[16]; + RandomNumberGenerator.Fill(podIv); + netStream.Write(podIv, 0, 16); + netStream.Flush(); + + // Create OFB duplex stream — lives for the entire session + using var ofb = new OFBDuplexStream(netStream, _sessionKey, + writeIv: consoleIv, // pod→console (other's IV) + readIv: podIv); // console→pod (own IV) + + // CONF handshake — verify both sides have the same session key + var confMsg = Encoding.UTF8.GetBytes("CONF"); + ofb.Write(confMsg, 0, confMsg.Length); + ofb.Flush(); + + var confReply = new byte[4]; + try { ReadExactSync(ofb, confReply); } + catch (Exception ex) + { + _logger.LogWarning("CONF read failed: {Msg}", ex.Message); + return; + } + if (!confReply.AsSpan().SequenceEqual(confMsg)) + { + _logger.LogWarning("CONF mismatch — session key mismatch, dropping connection."); + return; + } + + Stream rpcStream = ofb; + + var formatter = CreateFormatter(); + _logger.LogInformation("Console session started."); + + try + { + while (!ct.IsCancellationRequested && client.Connected) + { + InvokeCommand command; + try + { + MethodInfoProxy.LastResolvedName = null; + var obj = formatter.Deserialize(rpcStream); + command = (InvokeCommand)obj; + } + catch (IOException) + { + _logger.LogInformation("Console disconnected (EOF)."); + break; + } + catch (SerializationException ex) + when (ex.Message.Contains("End of Stream")) + { + _logger.LogInformation("Console disconnected (end of stream)."); + break; + } + catch (SerializationException ex) + when (ex.InnerException is IOException) + { + // Socket timeout or reset — Console went idle or disconnected + _logger.LogInformation("Console disconnected (I/O: {Msg}).", + ex.InnerException.Message); + break; + } + catch (Exception ex) + { + _logger.LogWarning("Deserialization error ({Type}): {Msg}", + ex.GetType().Name, ex.Message); + File.AppendAllText( + Path.Combine(AppContext.BaseDirectory, "podconf.log"), + $"[{DateTime.Now:HH:mm:ss}] DESER ERROR: {ex}{Environment.NewLine}"); + break; + } + + // Fall back to ThreadStatic name if IObjectReference fixup + // didn't update the boxed struct field. + var funcName = command.Function?.Name + ?? MethodInfoProxy.LastResolvedName ?? "???"; + _logger.LogInformation( + "Command: {Func}({Args})", funcName, + command.Parameters != null + ? string.Join(", ", command.Parameters) : ""); + + var start = DateTime.UtcNow; + var result = new InvokeResult(); + var diagLog = Path.Combine(AppContext.BaseDirectory, "podconf.log"); + + try + { + result.Result = await DispatchCommandAsync( + funcName, command.Parameters, ct); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error executing {Func}", funcName); + result.Exception = new Exception(ex.Message); + File.AppendAllText(diagLog, + $"[{DateTime.Now:HH:mm:ss}] CMD {funcName} ERROR: {ex.Message}{Environment.NewLine}"); + } + + result.CallDuration = DateTime.UtcNow - start; + if (result.Exception == null) + { + File.AppendAllText(diagLog, + $"[{DateTime.Now:HH:mm:ss}] CMD {funcName} OK ({result.CallDuration.TotalMilliseconds:F0}ms)" + + $"{(result.Result != null ? $" → {result.Result}" : "")}{Environment.NewLine}"); + } + + try + { + formatter.Serialize(rpcStream, result); + rpcStream.Flush(); + } + catch (IOException) + { + _logger.LogInformation("Console disconnected while writing response."); + break; + } + catch (Exception serEx) + { + // Result type not BinaryFormatter-serializable — send error instead + _logger.LogWarning("Serialize failed for {Func}: {Msg}", + funcName, serEx.Message); + var fallback = new InvokeResult + { + Exception = new Exception($"Pod error: {serEx.Message}"), + CallDuration = result.CallDuration + }; + try + { + formatter.Serialize(rpcStream, fallback); + rpcStream.Flush(); + } + catch { break; } + } + + // ── File receive after InitiateInstallProduct ──────────── + // The Console streams the game archive on this same OFB + // connection immediately after receiving the Guid response: + // 8-byte Int64 file size + raw file bytes + // Then closes the connection (FIN). + if (funcName == "InitiateInstallProduct" + && result.Exception == null + && result.Result is Guid installGuid) + { + ReceiveInstallFile(rpcStream, installGuid, diagLog); + break; // connection done after file transfer + } + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled error in Console handler"); + var diagLog = Path.Combine(AppContext.BaseDirectory, "podconf.log"); + File.AppendAllText(diagLog, + $"[{DateTime.Now:HH:mm:ss}] HANDLER ERROR: {ex.GetType().Name}: {ex.Message}{Environment.NewLine}" + + $"[{DateTime.Now:HH:mm:ss}] Inner: {ex.InnerException?.Message}{Environment.NewLine}"); + } + + _logger.LogInformation("Console session ended."); + } + + // ── Command Dispatch ───────────────────────────────────────────── + + private async Task DispatchCommandAsync( + string functionName, object[] parameters, CancellationToken ct) + { + switch (functionName) + { + case "Ping": + // Echo back the DateTime parameter (Console measures round-trip) + return parameters?.Length > 0 ? parameters[0] : DateTime.Now; + + case "GetLaunchableApps": + { + var json = await ForwardToAgentJsonAsync(functionName, parameters, ct); + var wire = FlatArrayToWire(json); + var pairs = new LaunchPair[wire.Length]; + for (int i = 0; i < wire.Length; i++) + pairs[i] = wire[i].LaunchPair; + return pairs; + } + + case "GetInstalledApps": + { + var json = await ForwardToAgentJsonAsync(functionName, parameters, ct); + return FlatArrayToWire(json); + } + + case "GetLaunchedApps": + { + var json = await ForwardToAgentJsonAsync(functionName, parameters, ct); + return FlatLaunchedArrayToWire(json); + } + + case "FullUpdate": + { + var json = await ForwardToAgentJsonAsync(functionName, parameters, ct); + if (json == null) return new FullUpdateData(); + var flat = JsonSerializer.Deserialize(json); + if (flat == null) return new FullUpdateData(); + var result2 = new FullUpdateData + { + InstalledApps = flat.InstalledApps != null + ? Array.ConvertAll(flat.InstalledApps, FlatToWire) + : Array.Empty(), + LaunchedApps = flat.LaunchedApps != null + ? Array.ConvertAll(flat.LaunchedApps, FlatLaunchedToWire) + : Array.Empty(), + VolumeLevel = flat.VolumeLevel + }; + return result2; + } + + case "GetOutOfBandProgress": + { + // Handled directly in the Service — progress is tracked + // during file receive on the InitiateInstallProduct connection. + var diagPath = Path.Combine(AppContext.BaseDirectory, "podconf.log"); + if (parameters?.Length > 0) + { + var p0 = parameters[0]; + var p0Type = p0?.GetType().FullName ?? "null"; + Guid progressId; + bool guidMatch = false; + + if (p0 is Guid g) + { + progressId = g; + guidMatch = true; + } + else if (p0 is string s && Guid.TryParse(s, out var gs)) + { + progressId = gs; + guidMatch = true; + } + else + { + progressId = Guid.Empty; + } + + if (guidMatch) + { + lock (_installLock) + { + if (_installProgress.TryGetValue(progressId, out var prog)) + { + // Log first poll and when status changes + _logger.LogDebug( + "GetOutOfBandProgress({Id}): {Pct}% {Status} done={Done}", + progressId, prog.PercentComplete, prog.Status, prog.IsCompleted); + return prog; + } + } + File.AppendAllText(diagPath, + $"[{DateTime.Now:HH:mm:ss}] OOBP: Guid {progressId} NOT FOUND in _installProgress (type={p0Type}, keys={string.Join(",", GetProgressKeys())}){Environment.NewLine}"); + } + else + { + File.AppendAllText(diagPath, + $"[{DateTime.Now:HH:mm:ss}] OOBP: param[0] type={p0Type} value={p0} — NOT a Guid{Environment.NewLine}"); + } + } + else + { + File.AppendAllText(diagPath, + $"[{DateTime.Now:HH:mm:ss}] OOBP: NO parameters{Environment.NewLine}"); + } + return new OutOfBandProgress + { + PercentComplete = 0, + Status = "Unknown call ID", + IsCompleted = true + }; + } + + case "LaunchApp": + { + var json = await ForwardToAgentJsonAsync(functionName, parameters, ct); + if (json == null) return 0; + var doc = JsonDocument.Parse(json); + if (doc.RootElement.TryGetProperty("ProcessId", out var pid)) + return pid.GetInt32(); + return 0; + } + + case "get_VolumeLevel": + { + var json = await ForwardToAgentJsonAsync(functionName, parameters, ct); + return json != null ? JsonSerializer.Deserialize(json) : 1.0f; + } + + case "InitiateInstallProduct": + { + var callId = Guid.NewGuid(); + lock (_installLock) + { + _installProgress[callId] = new OutOfBandProgress + { + PercentComplete = 0, + Status = "Waiting for file...", + IsCompleted = false + }; + } + return callId; + } + + case "KillApp": + case "KillAllOfType": + case "KillAllApps": + case "Shutdown": + case "ClearStore": + case "RemoveApp": + case "UninstallApp": + case "InstallApp": + { + // Flatten nested LaunchPair for Agent's flat JSON format + if (parameters?.Length > 0 && parameters[0] is LaunchData ld) + { + var flat = new + { + LaunchKey = ld.LaunchPair.LaunchKey.ToString(), + DisplayName = ld.LaunchPair.DisplayName, + WorkingDirectory = ld.WorkingDirectory, + ExeFile = ld.ExeFile, + Arguments = ld.Arguments, + AutoRestart = ld.AutoRestart + }; + parameters = new object[] { flat }; + } + await ForwardToAgentJsonAsync(functionName, parameters, ct); + return null; + } + + case "set_VolumeLevel": + await ForwardToAgentJsonAsync(functionName, parameters, ct); + return null; + + default: + _logger.LogWarning("Unknown command: {Func}", functionName); + await ForwardToAgentJsonAsync(functionName, parameters, ct); + return null; + } + } + + // ── Install Product File Receive ───────────────────────────────── + + private const string GAMES_DIR = @"C:\Games"; + + private void ReceiveInstallFile(Stream stream, Guid callId, string logPath) + { + void Log(string msg) => File.AppendAllText(logPath, + $"[{DateTime.Now:HH:mm:ss}] {msg}{Environment.NewLine}"); + + string tempZip = null; + + try + { + // ── Phase 1: Receive file ──────────────────────────────── + var sizeBuf = new byte[8]; + ReadExactSync(stream, sizeBuf); + long fileSize = BitConverter.ToInt64(sizeBuf, 0); + + Log($"INSTALL {callId:N}: receiving {fileSize:N0} bytes..."); + UpdateProgress(callId, 0, "Receiving file..."); + + var dataDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "TeslaLauncher"); + Directory.CreateDirectory(dataDir); + + tempZip = Path.Combine(dataDir, $"install_{callId:N}.zip"); + + using (var fs = File.Create(tempZip)) + { + var buffer = new byte[65536]; + long received = 0; + + while (received < fileSize) + { + var toRead = (int)Math.Min(buffer.Length, fileSize - received); + var n = stream.Read(buffer, 0, toRead); + if (n == 0) throw new IOException("Connection closed during file transfer."); + fs.Write(buffer, 0, n); + received += n; + + // Report receive progress as 0–50% + int pct = (int)(received * 50 / fileSize); + UpdateProgress(callId, pct, "Receiving file..."); + } + } + + Log($"INSTALL {callId:N}: file received ({fileSize:N0} bytes)"); + + // ── Phase 2: Extract to C:\Games\ ──────────────────────── + UpdateProgress(callId, 50, "Extracting..."); + Directory.CreateDirectory(GAMES_DIR); + + using (var zip = ZipFile.OpenRead(tempZip)) + { + int total = zip.Entries.Count; + int done = 0; + + foreach (var entry in zip.Entries) + { + var destPath = Path.GetFullPath( + Path.Combine(GAMES_DIR, entry.FullName)); + + // Zip-slip protection + if (!destPath.StartsWith(GAMES_DIR, StringComparison.OrdinalIgnoreCase)) + continue; + + if (string.IsNullOrEmpty(entry.Name)) + { + // Directory entry + Directory.CreateDirectory(destPath); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(destPath)); + entry.ExtractToFile(destPath, overwrite: true); + } + + done++; + // Report extract progress as 50–95% + int pct = 50 + (done * 45 / Math.Max(total, 1)); + UpdateProgress(callId, pct, "Extracting..."); + } + } + + Log($"INSTALL {callId:N}: extracted to {GAMES_DIR}"); + + // ── Phase 3: Run postinstall.bat if present ────────────── + var postInstall = Path.Combine(GAMES_DIR, "postinstall.bat"); + if (File.Exists(postInstall)) + { + UpdateProgress(callId, 96, "Running postinstall..."); + Log($"INSTALL {callId:N}: running postinstall.bat"); + + try + { + using var proc = System.Diagnostics.Process.Start( + new System.Diagnostics.ProcessStartInfo + { + FileName = postInstall, + WorkingDirectory = GAMES_DIR, + UseShellExecute = false, + CreateNoWindow = true + }); + proc?.WaitForExit(60_000); // 60 s timeout + } + catch (Exception ex) + { + Log($"INSTALL {callId:N}: postinstall.bat error: {ex.Message}"); + } + + try { File.Delete(postInstall); } catch { } + Log($"INSTALL {callId:N}: postinstall.bat completed and removed"); + } + + // ── Done ───────────────────────────────────────────────── + // Console checks PercentComplete == 99 to break out of its + // 3-iteration install loop (InstallProductWorker in PodInfo). + // Using 100 causes the Console to retry all 3 times. + UpdateProgress(callId, 99, "Complete", isCompleted: true); + Log($"INSTALL {callId:N}: install complete"); + } + catch (Exception ex) + { + UpdateProgress(callId, 0, $"Failed: {ex.Message}", isCompleted: true); + Log($"INSTALL {callId:N}: FAILED: {ex.Message}"); + _logger.LogError(ex, "Install {Id} failed", callId); + } + finally + { + // Clean up temp zip + try { if (tempZip != null) File.Delete(tempZip); } catch { } + } + } + + private string[] GetProgressKeys() + { + lock (_installLock) + { + return Array.ConvertAll( + new List(_installProgress.Keys).ToArray(), + g => g.ToString()); + } + } + + // ── Flat ↔ Wire Conversion ──────────────────────────────────────── + + private class FlatLaunchData + { + public string LaunchKey { get; set; } + public string DisplayName { get; set; } + public string WorkingDirectory { get; set; } + public string ExeFile { get; set; } + public string Arguments { get; set; } + public bool AutoRestart { get; set; } + } + + private class FlatLaunchedAppData + { + public string LaunchKey { get; set; } + public int ProcessId { get; set; } + } + + private class FlatFullUpdateData + { + public FlatLaunchData[] InstalledApps { get; set; } + public FlatLaunchedAppData[] LaunchedApps { get; set; } + public float VolumeLevel { get; set; } + } + + private static LaunchData FlatToWire(FlatLaunchData f) + { + return new LaunchData + { + LaunchPair = new LaunchPair + { + LaunchKey = Guid.TryParse(f.LaunchKey, out var g) ? g : Guid.Empty, + DisplayName = f.DisplayName + }, + WorkingDirectory = f.WorkingDirectory, + ExeFile = f.ExeFile, + Arguments = f.Arguments, + AutoRestart = f.AutoRestart + }; + } + + private static LaunchedAppData FlatLaunchedToWire(FlatLaunchedAppData f) + { + return new LaunchedAppData + { + LaunchKey = Guid.TryParse(f.LaunchKey, out var g) ? g : Guid.Empty, + ProcessId = f.ProcessId + }; + } + + private static LaunchedAppData[] FlatLaunchedArrayToWire(string json) + { + if (json == null) return Array.Empty(); + var flat = JsonSerializer.Deserialize(json); + if (flat == null) return Array.Empty(); + return Array.ConvertAll(flat, FlatLaunchedToWire); + } + + private static LaunchData[] FlatArrayToWire(string json) + { + if (json == null) return Array.Empty(); + var flat = JsonSerializer.Deserialize(json); + if (flat == null) return Array.Empty(); + var result = new LaunchData[flat.Length]; + for (int i = 0; i < flat.Length; i++) + result[i] = FlatToWire(flat[i]); + return result; + } + + private void UpdateProgress(Guid callId, int pct, string status, bool isCompleted = false) + { + lock (_installLock) + { + _installProgress[callId] = new OutOfBandProgress + { + PercentComplete = pct, + Status = status, + IsCompleted = isCompleted + }; + } + } + + // ── Secure Configuration ───────────────────────────────────────── + + private static bool IsVirtualAdapter(System.Net.NetworkInformation.NetworkInterface nic) + { + var desc = nic.Description ?? ""; var name = nic.Name ?? ""; + string[] markers = { "Virtual", "Hyper-V", "VMware", "VirtualBox", + "Loopback", "Tunnel", "Miniport", "Wi-Fi Direct", "Bluetooth" }; + foreach (var m in markers) + if (desc.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0 || + name.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0) return true; + var mac = nic.GetPhysicalAddress().GetAddressBytes(); + if (mac.Length == 6 && (mac[0] & 0x02) != 0) return true; + return false; + } + + private static bool IsMachineConfigured() + { + foreach (var nic in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()) + { + if (nic.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Ethernet) continue; + if (nic.OperationalStatus != System.Net.NetworkInformation.OperationalStatus.Up) continue; + if (IsVirtualAdapter(nic)) continue; + try + { + var ipv4 = nic.GetIPProperties().GetIPv4Properties(); + if (!ipv4.IsDhcpEnabled) return true; // static = configured + } + catch (System.Net.NetworkInformation.NetworkInformationException) { } + } + return false; + } + + private void RunSecureConfiguration(CancellationToken ct) + { + var logPath = Path.Combine(AppContext.BaseDirectory, "podconf.log"); + void Log(string msg) => File.AppendAllText(logPath, + $"[{DateTime.Now:HH:mm:ss}] {msg}{Environment.NewLine}"); + + try + { + using var configurator = new PodSecureConfigurator( + adapterName: null, + logger: msg => { Log(msg); _logger.LogInformation(msg); }); + + // 10-minute timeout — rebroadcasts automatically every 2 s + var config = configurator.Configure(timeoutMs: 600_000); + + if (config == null) + { + _logger.LogWarning( + "SecureConfiguration timed out. Machine will need a reboot to retry."); + return; + } + + _logger.LogInformation("SecureConfiguration complete."); + } + catch (Exception ex) + { + _logger.LogError(ex, "SecureConfiguration failed"); + } + } + + // ── Session Key ────────────────────────────────────────────────── + + private static byte[] LoadSessionKey() + { + if (!File.Exists(KEY_FILE)) return null; + using var fs = File.Open(KEY_FILE, FileMode.Open, FileAccess.Read, FileShare.Read); + var len = fs.ReadByte(); + if (len <= 0) return null; + var key = new byte[len]; + int off = 0; + while (off < key.Length) + { + int n = fs.Read(key, off, key.Length - off); + if (n == 0) break; + off += n; + } + return off == key.Length ? key : null; + } + + /// Reads exactly buf.Length bytes from stream; throws IOException on EOF. + private static void ReadExactSync(Stream stream, byte[] buf) + { + int off = 0; + while (off < buf.Length) + { + int n = stream.Read(buf, off, buf.Length - off); + if (n == 0) throw new IOException("Connection closed before all bytes received."); + off += n; + } + } + + // ── BinaryFormatter Setup ──────────────────────────────────────── + + private static BinaryFormatter CreateFormatter() + { + return new BinaryFormatter + { + Binder = new TeslaSerializationBinder() + }; + } + + // ── Named Pipe IPC ─────────────────────────────────────────────── + private async Task ForwardToAgentJsonAsync( + string functionName, object[] parameters, CancellationToken ct) + { + string launchKey = null; + if (parameters?.Length > 0 && parameters[0] is Guid guid) + launchKey = guid.ToString(); + + var msg = new IpcMessage + { + Command = functionName.ToUpperInvariant(), + LaunchKey = launchKey, + PayloadJson = parameters?.Length > 0 + ? JsonSerializer.Serialize(parameters) : null + }; + + using var pipe = new NamedPipeClientStream( + ".", PIPE_NAME, PipeDirection.InOut, PipeOptions.Asynchronous); + + try + { + await pipe.ConnectAsync(AGENT_CONNECT_TIMEOUT_MS, ct); + } + catch (TimeoutException) + { + _logger.LogWarning( + "Agent pipe timeout — is TeslaLauncherAgent.exe running?"); + throw new Exception( + "Cockpit agent is not available. " + + "Ensure TeslaLauncherAgent.exe is running in the user session."); + } + + // Send IpcMessage + var reqBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg)); + await pipe.WriteAsync(BitConverter.GetBytes(reqBytes.Length), ct); + await pipe.WriteAsync(reqBytes, ct); + await pipe.FlushAsync(ct); + + // Read IpcResponse + var lenBuf = new byte[4]; + await ReadExactAsync(pipe, lenBuf, ct); + var resBuf = new byte[BitConverter.ToInt32(lenBuf, 0)]; + await ReadExactAsync(pipe, resBuf, ct); + + var response = JsonSerializer.Deserialize( + Encoding.UTF8.GetString(resBuf)); + + if (response == null) + throw new Exception("Null response from Agent."); + + if (!response.Success) + throw new Exception(response.Message ?? "Agent returned failure."); + + if (response.Data is System.Text.Json.JsonElement je) + return je.GetRawText(); + return response.Data?.ToString(); + } + + private static async Task ReadExactAsync(Stream stream, byte[] buf, CancellationToken ct) + { + var off = 0; + while (off < buf.Length) + { + var n = await stream.ReadAsync(buf, off, buf.Length - off, ct); + if (n == 0) throw new IOException("Pipe closed before all bytes were read."); + off += n; + } + } + } + + // ── Serialization Binder ───────────────────────────────────────────── + // BindToType: resolves Console-side type names to local Tesla.Net types. + // BindToName: writes "TeslaConsoleLaunchLib" as assembly for outgoing types. + + internal sealed class TeslaSerializationBinder : SerializationBinder + { + private static readonly Assembly TeslaAssembly = + typeof(ILauncherService).Assembly; + + public override Type BindToType(string assemblyName, string typeName) + { + // Intercept MemberInfoSerializationHolder — handles cross-runtime + // MethodBase deserialization (.NET Framework 2.0 → .NET 6). + if (typeName == "System.Reflection.MemberInfoSerializationHolder") + return typeof(MethodInfoProxy); + + // Try to resolve Tesla.Net types from our assembly + var type = TeslaAssembly.GetType(typeName); + if (type != null) return type; + + // Return null → default resolution (triggers AssemblyResolve if needed) + return null; + } + + public override void BindToName( + Type serializedType, out string assemblyName, out string typeName) + { + if (serializedType.Namespace == "Tesla.Net") + { + // Console expects types from TeslaConsoleLaunchLib + assemblyName = "TeslaConsoleLaunchLib, Version=4.11.4.0, Culture=neutral, PublicKeyToken=null"; + typeName = serializedType.FullName; + } + else + { + assemblyName = null; + typeName = null; + } + } + } + + // ── MethodInfo Proxy ───────────────────────────────────────────────── + // Replaces MemberInfoSerializationHolder during deserialization. + // Resolves serialized MethodBase references to ILauncherService methods. + + [Serializable] + internal sealed class MethodInfoProxy : ISerializable, IObjectReference + { + /// + /// Thread-static capture of the last deserialized method name. + /// IObjectReference fixup may fail to update fields inside boxed + /// value types (InvokeCommand is a struct), leaving Function null. + /// The handler reads this field as a fallback for the method name. + /// + [ThreadStatic] + internal static string LastResolvedName; + + private readonly string _memberName; + + private MethodInfoProxy(SerializationInfo info, StreamingContext ctx) + { + _memberName = info.GetString("Name"); + LastResolvedName = _memberName; + } + + public object GetRealObject(StreamingContext context) + { + // Look up the method on our ILauncherService interface. + // This handles both regular methods (Ping, LaunchApp, etc.) + // and property accessors (get_VolumeLevel, set_VolumeLevel). + var type = typeof(ILauncherService); + var method = type.GetMethod(_memberName, + BindingFlags.Public | BindingFlags.Instance); + if (method != null) return method; + + // Property accessors: get_X / set_X + if (_memberName.StartsWith("get_") || _memberName.StartsWith("set_")) + { + var propName = _memberName.Substring(4); + var prop = type.GetProperty(propName, + BindingFlags.Public | BindingFlags.Instance); + if (prop != null) + { + var accessor = _memberName.StartsWith("get_") + ? prop.GetMethod : prop.SetMethod; + if (accessor != null) return accessor; + } + } + + throw new SerializationException( + $"Cannot resolve method '{_memberName}' on ILauncherService"); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + throw new NotSupportedException("MethodInfoProxy is read-only."); + } + } + + // ── Entry Point ────────────────────────────────────────────────────── + + public class Program + { + public static void Main(string[] args) + { + AppContext.SetSwitch( + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); + + // Redirect TeslaConsoleLaunchLib assembly references to our assembly + AppDomain.CurrentDomain.AssemblyResolve += (sender, resolveArgs) => + { + var name = new AssemblyName(resolveArgs.Name); + if (string.Equals(name.Name, "TeslaConsoleLaunchLib", + StringComparison.OrdinalIgnoreCase)) + return typeof(ILauncherService).Assembly; + return null; + }; + + Host.CreateDefaultBuilder(args) + .UseWindowsService(options => + { + options.ServiceName = "Tesla Application Launcher"; + }) + .ConfigureServices(services => + { + services.AddHostedService(); + }) + .ConfigureLogging(logging => + { + logging.AddEventLog(settings => + { + settings.SourceName = "Tesla Application Launcher"; + }); + }) + .Build() + .Run(); + } + } +} diff --git a/Launcher/TeslaLauncherService.csproj b/Launcher/TeslaLauncherService.csproj new file mode 100644 index 0000000..36d5819 --- /dev/null +++ b/Launcher/TeslaLauncherService.csproj @@ -0,0 +1,35 @@ + + + + net6.0-windows + disable + disable + + true + true + 0.1.0 + app.ico + TeslaLauncherService + Tesla.Launcher.Service + x86 + win-x86 + true + true + true + + + + + + + + + + + + + + + + + diff --git a/Launcher/app.ico b/Launcher/app.ico new file mode 100644 index 0000000..d58dbf6 Binary files /dev/null and b/Launcher/app.ico differ diff --git a/Launcher/assets/MEMORY.md b/Launcher/assets/MEMORY.md new file mode 100644 index 0000000..df40326 --- /dev/null +++ b/Launcher/assets/MEMORY.md @@ -0,0 +1,115 @@ +# TeslaLauncher Project Memory + +## Project Overview +Simulation cockpit launcher system (BattleTech/laser-tag venue). +Original: Windows 2000 app by Elsewhen Studios LLC. +Current: Modern .NET 6 rewrite to work around Session 0 isolation (Vista+). + +## Architecture: 3 components +1. **TeslaLauncherService** (Session 0 Windows Service) - TCP 53290, handles Console commands, forwards via Named Pipe +2. **TeslaLauncherAgent** (WinForms tray, user desktop session) - Named Pipe server, launches/kills apps +3. **SecureConfig** (PodSecureConfigurator) - First-boot network configuration protocol + +## Key Files +- `TeslaLauncherService.cs` + `.csproj` - Windows Service (net6.0-windows, x86, self-contained) +- `TeslaLauncherAgent.cs` + `.csproj` - Userspace Agent (WinForms, net6.0-windows, x86) +- `LaunchModels_Shared.cs` - BinaryFormatter wire types + JSON IPC types +- `SecureConfig.cs` - First-boot UDP beacon + AES configuration protocol +- `LaunchApps.xml` - Game configs (Red Planet 4.11, BattleTech Firestorm at C:\Games\) +- `build.bat` / `install.bat` - Build and install scripts + +## Assets (original binaries) +- `assets/Tesla Application Launcher/` — original pod-side binaries (TeslaLauncherService.exe, TeslaSecureConfiguration.dll, etc.) +- `assets/Tesla Console/` — original console-side binaries (TeslaConsole.exe, TeslaSecureConfiguration.dll, etc.) +- `assets/configoflc.pcapng` — pcap of a SUCCESSFUL pod configuration (used to confirm protocol) +- `assets/pcap.pcapng` — pcap of a FAILING configuration (root cause: Console RPLY sent from wrong NIC) + +## SecureConfig Protocol — CONFIRMED from binary (TeslaSecureConfiguration.dll) +All values below are verified from the original DLL. + +### UDP Config Phase +- Port 53291: pod broadcasts RQST, Console listens +- Port 53292: Console broadcasts RPLY (UDP), pod listens +- RQST format: `RQST`(4 plaintext) + MAC(6) + RequestId(3) = 13 bytes PLAINTEXT +- RPLY format: `RPLY`(4 plaintext) + AES-256-CBC ciphertext(64 bytes) = 68 bytes + - CBC ciphertext = IV(16) + PKCS7-padded plaintext(48) + - AES key = PBKDF2(passphrase, salt, 1000 iter, 32 bytes) + - Salt (32 bytes): 0x17,0xab,0x51,0xd9,0xec,0xd1,0xd4,0x74,0xa9,0x09,0x4a,0x34,0x27,0xfb,0x1f,0xf2,0xde,0xc4,0xf9,0xf1,0xa6,0xd8,0x9e,0xda,0x15,0x11,0x47,0x65,0x32,0xe7,0xe7,0xef +- Passphrase alphabet: "23456789ABCDEFGHJKLMNPQRSTUVWXYZ" (32 chars) +- RequestId length: 3 chars; Passphrase length: 5 chars +- Temp IP: RANDOM in 172.16.0.0/12 range (byte[0]=172, byte[1]=0x1X random, bytes 2-3 random) + - Our fixed 172.16.0.100 is fine (Console doesn't connect back to temp IP) +- Temp mask: 255.240.0.0 + +### TCP Session Phase (port 53292) — CONFIRMED from DLL decompilation (ilspycmd) +- Console connects TCP to pod's NEW IP:53292 AFTER UDP config +- Both sides call the SAME `NegotiateCryptoStreams` static method: +1. Both write own 16-byte IV (raw), read other's 16-byte IV +2. OFB setup: outStream keyed from OTHER side's IV, inStream keyed from OWN IV + - Pod writes with consoleIv, reads with podIv + - Console writes with podIv, reads with consoleIv +3. Both write raw "CONF" (4 bytes: 0x43,0x4F,0x4E,0x46) over OFB + Both read 4 bytes and verify == "CONF" → key verification handshake + If mismatch → returns false → caller retries with new TCP connection +- After NegotiateCryptoStreams returns true: +4. Pod → Console: BinaryWriter.Write(string) — RSA-2048 public key XML (~417 bytes) +5. Console reads RSA XML, generates AES-256 session key, RSA-encrypts it +6. Console → Pod: BinaryWriter.Write(int len) + BinaryWriter.Write(byte[] ciphertext) + (260 bytes = 4-byte int32 (256) + 256-byte RSA PKCS#1 v1.5 ciphertext) +7. Pod: ReadInt32() + ReadBytes(len) + RSA.Decrypt(enc, Pkcs1) → session key +- Key derivation: PBKDF2(passphrase, salt, 1000 iter, 32 bytes) — same key both directions +- OFBCryptoStream: unidirectional, constructor calls NextBuffer() immediately (IV→KS block 0) + +### Registry / netsh +- Hostname: SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName +- netsh: `interface ip set address "{adapter}" static {ip} {mask} {gw}` with ` 1` metric +- DNS: `interface ip set dns "{adapter}" static {dns}` + +## BinaryFormatter Cross-Runtime Findings (.NET FW 2.0 → .NET 6) +- **MemberInfoSerializationHolder field names differ**: .NET FW 2.0 uses "Name", .NET 6 uses "MemberName" +- **MethodInfoProxy** intercepts via binder redirect + ISerializable, reads "Name" field +- **IObjectReference fixup fails for struct fields**: InvokeCommand is a struct; fixup doesn't update Function field in boxed value type. Fixed with [ThreadStatic] `LastResolvedName` fallback. +- **Console checks ICMP reachability** before connecting TCP 53290 — Windows Firewall must allow ICMPv4 type 8 +- **BindToName** must write "TeslaConsoleLaunchLib" as assembly for Tesla.Net types in serialized responses +- **JsonElement not [Serializable]**: Agent IPC returns JsonElement via System.Text.Json; must convert to proper Tesla.Net types before BinaryFormatter serialization + +## Known Bugs Found During Scan +1. ~~**CONFIG_FILE path mismatch**~~: Fixed +2. **IsMachineConfigured in Agent** doesn't filter virtual adapters (Service version does) - may false-trigger on Hyper-V machines +3. **`new Random()` for passphrase/RequestId generation** - should use RandomNumberGenerator (security) +4. **Named pipe has no PipeSecurity ACL** - any local process can connect and issue commands +5. **Volume control silently fails** if nircmd.exe not present - no fallback +6. ~~**`_clientTasks` list**~~: Fixed with _taskLock + +## Wire Protocol +- Console → Service: TCP 53290, OFB-encrypted (NegotiateCryptoStreams + session key), BinaryFormatter RPC +- Service → Agent: JSON over Named Pipe "TeslaLauncherIPC" with 4-byte length prefix +- SecureConfig: UDP 53291 (RQST beacon), UDP 53292 (RPLY), TCP 53292 (OFB + RSA key exchange) + +### Management Port (53290) — confirmed from decompiled TeslaLauncherService.exe +- Original: `ManagePort = 53290`, `ConfigPort = 53291` +- Every Console connection: NegotiateCryptoStreams(sessionKey) → BinaryFormatter RPC over OFB +- Session key: 32-byte AES key from RSA exchange during SecureConfig, saved to TeslaKeyStore.key +- Key file: `C:\ProgramData\TeslaLauncher\TeslaKeyStore.key` (format: 1-byte length + key bytes) +- Original Console: `PodManagerConnection.Open()` → `NegotiateCryptoStreams()` → `BinaryFormatter.Serialize/Deserialize` + +## Install Product Protocol (confirmed from old pod pcap) +- Console uses TWO concurrent TCP 53290 connections: + - **Polling connection**: GetInstalledApps, then GetOutOfBandProgress polling (~4/sec), then InstallApp + - **Install connection**: InitiateInstallProduct → Guid response, then 8-byte Int64 LE file size + raw file bytes, then FIN +- Service handles InitiateInstallProduct and GetOutOfBandProgress directly (not forwarded to Agent) +- Progress tracking: receive (0-50%), extract to C:\Games (50-95%), postinstall.bat (96%), complete (100%) +- Zip-slip protection on extraction; postinstall.bat executed with 60s timeout then deleted +- **FIXED**: Console sent file 3x because `InstallProductWorker` loops `for (i=0; i<3; i++)` and only breaks on `PercentComplete == 99`. We were returning 100 → now returns 99. + +## Fixes Applied (session 2) +- **Agent splash-to-normal transition**: form.Close() in timer + Main() falls through to AgentApplication +- **Guid format mismatch**: string-first deserialization with TryParse (moot after moving to Service) +- **InitiateInstallProduct**: handled directly in Service, returns Guid, triggers ReceiveInstallFile +- **LaunchApps.xml ACL**: icacls in install.bat grants Users modify access +- **build.bat /q flag**: suppresses pause for non-interactive terminals +- **install.bat**: creates C:\Games with Users modify ACL + +## User Preferences +- User writes in informal English, technical background +- Prefers building via build.bat (not direct dotnet publish) diff --git a/Launcher/assets/Tesla Application Launcher/ICSharpCode.SharpZipLib.dll b/Launcher/assets/Tesla Application Launcher/ICSharpCode.SharpZipLib.dll new file mode 100644 index 0000000..e829ebf Binary files /dev/null and b/Launcher/assets/Tesla Application Launcher/ICSharpCode.SharpZipLib.dll differ diff --git a/Launcher/assets/Tesla Application Launcher/PlasmaIO.dll b/Launcher/assets/Tesla Application Launcher/PlasmaIO.dll new file mode 100644 index 0000000..5ac0e0d Binary files /dev/null and b/Launcher/assets/Tesla Application Launcher/PlasmaIO.dll differ diff --git a/Launcher/assets/Tesla Application Launcher/RemoveIdentity.bat b/Launcher/assets/Tesla Application Launcher/RemoveIdentity.bat new file mode 100644 index 0000000..5270356 --- /dev/null +++ b/Launcher/assets/Tesla Application Launcher/RemoveIdentity.bat @@ -0,0 +1,3 @@ +net stop "Tesla Application Launcher" +TeslaLauncherService.exe /clearstore +net start "Tesla Application Launcher" \ No newline at end of file diff --git a/Launcher/assets/Tesla Application Launcher/TeslaConsoleLaunchLib.dll b/Launcher/assets/Tesla Application Launcher/TeslaConsoleLaunchLib.dll new file mode 100644 index 0000000..25e63aa Binary files /dev/null and b/Launcher/assets/Tesla Application Launcher/TeslaConsoleLaunchLib.dll differ diff --git a/Launcher/assets/Tesla Application Launcher/TeslaLauncherService.exe b/Launcher/assets/Tesla Application Launcher/TeslaLauncherService.exe new file mode 100644 index 0000000..76179e3 Binary files /dev/null and b/Launcher/assets/Tesla Application Launcher/TeslaLauncherService.exe differ diff --git a/Launcher/assets/Tesla Application Launcher/TeslaPasscodeDisplay.exe b/Launcher/assets/Tesla Application Launcher/TeslaPasscodeDisplay.exe new file mode 100644 index 0000000..51bdda2 Binary files /dev/null and b/Launcher/assets/Tesla Application Launcher/TeslaPasscodeDisplay.exe differ diff --git a/Launcher/assets/Tesla Application Launcher/TeslaSecureConfiguration.dll b/Launcher/assets/Tesla Application Launcher/TeslaSecureConfiguration.dll new file mode 100644 index 0000000..f1ee032 Binary files /dev/null and b/Launcher/assets/Tesla Application Launcher/TeslaSecureConfiguration.dll differ diff --git a/Launcher/assets/Tesla Application Launcher/installer_banner.bmp b/Launcher/assets/Tesla Application Launcher/installer_banner.bmp new file mode 100644 index 0000000..efeedd4 Binary files /dev/null and b/Launcher/assets/Tesla Application Launcher/installer_banner.bmp differ diff --git a/Launcher/assets/Tesla Console/BitmapLibrary.dll b/Launcher/assets/Tesla Console/BitmapLibrary.dll new file mode 100644 index 0000000..0e079f5 Binary files /dev/null and b/Launcher/assets/Tesla Console/BitmapLibrary.dll differ diff --git a/Launcher/assets/Tesla Console/Munga Net.dll b/Launcher/assets/Tesla Console/Munga Net.dll new file mode 100644 index 0000000..0a281f5 Binary files /dev/null and b/Launcher/assets/Tesla Console/Munga Net.dll differ diff --git a/Launcher/assets/Tesla Console/RedPlanet/RPConfig.xml b/Launcher/assets/Tesla Console/RedPlanet/RPConfig.xml new file mode 100644 index 0000000..90963c0 --- /dev/null +++ b/Launcher/assets/Tesla Console/RedPlanet/RPConfig.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Launcher/assets/Tesla Console/RedPlanet/RPStrings.xml b/Launcher/assets/Tesla Console/RedPlanet/RPStrings.xml new file mode 100644 index 0000000..5fbf8e4 --- /dev/null +++ b/Launcher/assets/Tesla Console/RedPlanet/RPStrings.xml @@ -0,0 +1,49 @@ + + + {time} - First into the score zone is {player}! + + {player} gets off to a roaring start as the first booster of the mission is ignited. + + It is a sorry state of affairs when {player} is the first to trash their {vehicle}. + + High glory goes to {player} as the first kill of the mission is delivered unto {damager}! + + The crowd is on their feet as {player} brings in the shortest lap of {lap_time} of the mission! + + {time} - Sparks fly as {damager} lightly damages {player}'s vehicle. + {time} - {damager} implements a light impact against {player}. + {damager} provides a light spanking to {player}'s vehicle. + {player} laughs defiantly as {damager} can only deliver light damage to {player}'s vehicle. + + {time} - Sparks fly as {damager} moderately damages {player}'s vehicle. + {player} still has a chance as {damager} delivers a moderate blow to {player}'s vehicle. + {damager} provides a moderate spanking to {player}'s vehicle. + {time} - {damager} implements a moderate impact against {player}. + + {time} - Sparks fly as {damager} heavily damages {player}'s vehicle. + {player} lets out a sob of disgust as {damager} nearly dismembers {player}'s vehicle. + {damager} provides a heavy handed spanking to {player}'s vehicle. + {time} - {damager} implements a heavy impact against {player}. + + {time} - {damager} delivers righteous kill to {player}. + {damager} rules as sheer death panic is brought down upon {player}. + {time} - {damager} delivers peace, love, rest to {player}. + The burning wreckage of {player}'s vehicle twists in the Martian wind as {damager} smiles joyously. + + Tragedy strikes {player}'s {vehicle} as the clumsy pilot ejects to safety. + {time} - Smoke roils from the {vehicle} as {player} learns a hard piloting lesson. + {player} destroys the {vehicle} and bails out. + {time} - {player} takes a risk which ends with the {vehicle} in flames. + + {time} - {player}'s ears are still ringing from the score zone buzzer. + {time} - {player} maneuvers into the score zone. + {time} - {player} scores! + {time} - {player} adds another notch, entering the score zone. + + {time} - A righteous booster is deployed by {player}. + {player}'s {vehicle} shows awesome booster power. + {time} - {player} is not babysitting on a booster. + {time} - {player}'s {vehicle} is cruising on a full booster thrust. + + ...and at the end of the mission it's: {winners} followed by {looser}. + \ No newline at end of file diff --git a/Launcher/assets/Tesla Console/TeslaConsole.exe b/Launcher/assets/Tesla Console/TeslaConsole.exe new file mode 100644 index 0000000..8323479 Binary files /dev/null and b/Launcher/assets/Tesla Console/TeslaConsole.exe differ diff --git a/Launcher/assets/Tesla Console/TeslaConsoleLaunchLib.dll b/Launcher/assets/Tesla Console/TeslaConsoleLaunchLib.dll new file mode 100644 index 0000000..25e63aa Binary files /dev/null and b/Launcher/assets/Tesla Console/TeslaConsoleLaunchLib.dll differ diff --git a/Launcher/assets/Tesla Console/TeslaSecureConfiguration.dll b/Launcher/assets/Tesla Console/TeslaSecureConfiguration.dll new file mode 100644 index 0000000..f1ee032 Binary files /dev/null and b/Launcher/assets/Tesla Console/TeslaSecureConfiguration.dll differ diff --git a/Launcher/assets/Tesla Console/WeifenLuo.WinFormsUI.Docking.dll b/Launcher/assets/Tesla Console/WeifenLuo.WinFormsUI.Docking.dll new file mode 100644 index 0000000..c232381 Binary files /dev/null and b/Launcher/assets/Tesla Console/WeifenLuo.WinFormsUI.Docking.dll differ diff --git a/Launcher/assets/Tesla Console/WeifenLuo.txt b/Launcher/assets/Tesla Console/WeifenLuo.txt new file mode 100644 index 0000000..96f3fa5 --- /dev/null +++ b/Launcher/assets/Tesla Console/WeifenLuo.txt @@ -0,0 +1,9 @@ +The MIT License + +Copyright (c) 2007 Weifen Luo (email: weifenluo@yahoo.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Launcher/assets/Tesla Console/images/pod art/Age of Discovery.bmp b/Launcher/assets/Tesla Console/images/pod art/Age of Discovery.bmp new file mode 100644 index 0000000..f7040a8 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Age of Discovery.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Calamity Jane.bmp b/Launcher/assets/Tesla Console/images/pod art/Calamity Jane.bmp new file mode 100644 index 0000000..22e8791 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Calamity Jane.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Call of the Wild.bmp b/Launcher/assets/Tesla Console/images/pod art/Call of the Wild.bmp new file mode 100644 index 0000000..0657dd8 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Call of the Wild.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Carpe Diem.bmp b/Launcher/assets/Tesla Console/images/pod art/Carpe Diem.bmp new file mode 100644 index 0000000..b3f5794 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Carpe Diem.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Divine Wind.bmp b/Launcher/assets/Tesla Console/images/pod art/Divine Wind.bmp new file mode 100644 index 0000000..9abd85c Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Divine Wind.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Fly Girl.bmp b/Launcher/assets/Tesla Console/images/pod art/Fly Girl.bmp new file mode 100644 index 0000000..b374ab9 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Fly Girl.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Flying Blind.bmp b/Launcher/assets/Tesla Console/images/pod art/Flying Blind.bmp new file mode 100644 index 0000000..d734396 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Flying Blind.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Flying Dutchman.bmp b/Launcher/assets/Tesla Console/images/pod art/Flying Dutchman.bmp new file mode 100644 index 0000000..126faaa Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Flying Dutchman.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Frequent Flyer.bmp b/Launcher/assets/Tesla Console/images/pod art/Frequent Flyer.bmp new file mode 100644 index 0000000..d573185 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Frequent Flyer.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Gypsy.bmp b/Launcher/assets/Tesla Console/images/pod art/Gypsy.bmp new file mode 100644 index 0000000..751156c Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Gypsy.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Icarus.bmp b/Launcher/assets/Tesla Console/images/pod art/Icarus.bmp new file mode 100644 index 0000000..5ba6e03 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Icarus.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Man O'War.bmp b/Launcher/assets/Tesla Console/images/pod art/Man O'War.bmp new file mode 100644 index 0000000..0c101b5 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Man O'War.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Martian Belle.bmp b/Launcher/assets/Tesla Console/images/pod art/Martian Belle.bmp new file mode 100644 index 0000000..cea51e4 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Martian Belle.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Medicine Man.bmp b/Launcher/assets/Tesla Console/images/pod art/Medicine Man.bmp new file mode 100644 index 0000000..0727d6d Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Medicine Man.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Mistress Quickly.bmp b/Launcher/assets/Tesla Console/images/pod art/Mistress Quickly.bmp new file mode 100644 index 0000000..585452b Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Mistress Quickly.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Nomad.bmp b/Launcher/assets/Tesla Console/images/pod art/Nomad.bmp new file mode 100644 index 0000000..e365e7a Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Nomad.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Pegasus.bmp b/Launcher/assets/Tesla Console/images/pod art/Pegasus.bmp new file mode 100644 index 0000000..3c5e74f Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Pegasus.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Privateer.bmp b/Launcher/assets/Tesla Console/images/pod art/Privateer.bmp new file mode 100644 index 0000000..da9ed68 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Privateer.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Puck.bmp b/Launcher/assets/Tesla Console/images/pod art/Puck.bmp new file mode 100644 index 0000000..f778c99 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Puck.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Rapid Transit.bmp b/Launcher/assets/Tesla Console/images/pod art/Rapid Transit.bmp new file mode 100644 index 0000000..7dbe2ea Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Rapid Transit.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Sarcophagus.bmp b/Launcher/assets/Tesla Console/images/pod art/Sarcophagus.bmp new file mode 100644 index 0000000..8789824 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Sarcophagus.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Snake Eyes.bmp b/Launcher/assets/Tesla Console/images/pod art/Snake Eyes.bmp new file mode 100644 index 0000000..41f0ddd Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Snake Eyes.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Unto the Breech.bmp b/Launcher/assets/Tesla Console/images/pod art/Unto the Breech.bmp new file mode 100644 index 0000000..ebac63c Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Unto the Breech.bmp differ diff --git a/Launcher/assets/Tesla Console/images/pod art/Valkyrie.bmp b/Launcher/assets/Tesla Console/images/pod art/Valkyrie.bmp new file mode 100644 index 0000000..0669155 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/pod art/Valkyrie.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Berserker's Bahnzai.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Berserker's Bahnzai.bmp new file mode 100644 index 0000000..a11b14f Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Berserker's Bahnzai.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Blade's Edge.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Blade's Edge.bmp new file mode 100644 index 0000000..8ebf986 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Blade's Edge.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Brewer's Bane.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Brewer's Bane.bmp new file mode 100644 index 0000000..92ddf6f Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Brewer's Bane.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Burnt Cookies.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Burnt Cookies.bmp new file mode 100644 index 0000000..62a8b9b Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Burnt Cookies.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Firestorm's Fury.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Firestorm's Fury.bmp new file mode 100644 index 0000000..3060b80 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Firestorm's Fury.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Lyz's Lane.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Lyz's Lane.bmp new file mode 100644 index 0000000..25a0792 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Lyz's Lane.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Pain God's Passage.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Pain God's Passage.bmp new file mode 100644 index 0000000..303f416 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Pain God's Passage.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Wiseguy's Wake.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Wiseguy's Wake.bmp new file mode 100644 index 0000000..b590d67 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Wiseguy's Wake.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet maps/Yip's Yahoorama.bmp b/Launcher/assets/Tesla Console/images/red planet maps/Yip's Yahoorama.bmp new file mode 100644 index 0000000..8eef30b Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet maps/Yip's Yahoorama.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Armadillo.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Armadillo.bmp new file mode 100644 index 0000000..5ea7a9e Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Armadillo.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Bug.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Bug.bmp new file mode 100644 index 0000000..02c1e40 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Bug.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Bull.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Bull.bmp new file mode 100644 index 0000000..9d5a9f4 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Bull.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Mule.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Mule.bmp new file mode 100644 index 0000000..63aaca1 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Mule.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Police.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Police.bmp new file mode 100644 index 0000000..dafb0f7 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Police.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Quark.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Quark.bmp new file mode 100644 index 0000000..8d22067 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Quark.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Roadblock.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Roadblock.bmp new file mode 100644 index 0000000..207d1dd Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Roadblock.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Skeeter.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Skeeter.bmp new file mode 100644 index 0000000..73bfc19 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Skeeter.bmp differ diff --git a/Launcher/assets/Tesla Console/images/red planet vehicles/Vole.bmp b/Launcher/assets/Tesla Console/images/red planet vehicles/Vole.bmp new file mode 100644 index 0000000..3c01bd1 Binary files /dev/null and b/Launcher/assets/Tesla Console/images/red planet vehicles/Vole.bmp differ diff --git a/Launcher/assets/Tesla Console/installer_banner.bmp b/Launcher/assets/Tesla Console/installer_banner.bmp new file mode 100644 index 0000000..efeedd4 Binary files /dev/null and b/Launcher/assets/Tesla Console/installer_banner.bmp differ diff --git a/Launcher/assets/oalinst.exe b/Launcher/assets/oalinst.exe new file mode 100644 index 0000000..0b61822 Binary files /dev/null and b/Launcher/assets/oalinst.exe differ diff --git a/Launcher/build.bat b/Launcher/build.bat new file mode 100644 index 0000000..089bf85 --- /dev/null +++ b/Launcher/build.bat @@ -0,0 +1,189 @@ +@echo off +:: ============================================================================= +:: TeslaLauncher — Unified Build Script +:: ============================================================================= +:: Builds TeslaLauncherService.exe and TeslaLauncherAgent.exe (with integrated +:: TeslaSecureConfiguration protocol) from source. +:: +:: Requirements: +:: .NET 6 SDK https://dotnet.microsoft.com/download/dotnet/6.0 +:: Internet access for NuGet restore (System.IO.Ports 7.0.0) +:: +:: Usage: +:: build.bat — build both components +:: build.bat /service — build Service only +:: build.bat /agent — build Agent only +:: +:: Output: +:: TeslaLauncher\ +:: Service\ +:: TeslaLauncherService.exe +:: Agent\ +:: TeslaLauncherAgent.exe +:: install.bat +:: ============================================================================= + +setlocal enabledelayedexpansion + +set ROOT=%~dp0 +set BUILD_DIR=%ROOT%TeslaLauncher +set SVC_STAGE=%ROOT%_stage_svc +set AGT_STAGE=%ROOT%_stage_agt + +:: ── Parse arguments ─────────────────────────────────────────────────────────── +set BUILD_SERVICE=1 +set BUILD_AGENT=1 +set QUIET=0 + +for %%a in (%*) do ( + if /i "%%~a"=="/service" set BUILD_AGENT=0 + if /i "%%~a"=="/agent" set BUILD_SERVICE=0 + if /i "%%~a"=="/q" set QUIET=1 +) + +echo. +echo ============================================================ +echo Tesla Launcher v0.1 — Unified Build +echo Output : %BUILD_DIR% +echo ============================================================ +echo. + +:: ── Verify .NET SDK ─────────────────────────────────────────────────────────── +where dotnet >nul 2>&1 +if errorlevel 1 ( + echo ERROR: dotnet not found. + echo Install the .NET 6 SDK from: + echo https://dotnet.microsoft.com/download/dotnet/6.0 + if "%QUIET%"=="0" pause + exit /b 1 +) +for /f "tokens=*" %%v in ('dotnet --version 2^>^&1') do set SDK_VER=%%v +echo SDK : %SDK_VER% +echo. + +:: ── Verify source files ─────────────────────────────────────────────────────── +call :check_file "TeslaLauncherService.cs" || goto :err_missing +call :check_file "TeslaLauncherService.csproj" || goto :err_missing +call :check_file "TeslaLauncherAgent.cs" || goto :err_missing +call :check_file "TeslaLauncherAgent.csproj" || goto :err_missing +call :check_file "LaunchModels_Shared.cs" || goto :err_missing +call :check_file "SecureConfig.cs" || goto :err_missing + +:: ── Clean and prepare build output ─────────────────────────────────────────── +if exist "%BUILD_DIR%\Service" rmdir /s /q "%BUILD_DIR%\Service" +if exist "%BUILD_DIR%\Agent" rmdir /s /q "%BUILD_DIR%\Agent" + +:: ── Build Service ───────────────────────────────────────────────────────────── +if %BUILD_SERVICE%==0 goto :skip_service + +echo [1/2] Building TeslaLauncherService... +echo. + +if exist "%SVC_STAGE%" rmdir /s /q "%SVC_STAGE%" +mkdir "%SVC_STAGE%" + +copy /y "%ROOT%TeslaLauncherService.cs" "%SVC_STAGE%\" >nul +copy /y "%ROOT%TeslaLauncherService.csproj" "%SVC_STAGE%\" >nul +copy /y "%ROOT%LaunchModels_Shared.cs" "%SVC_STAGE%\" >nul +copy /y "%ROOT%SecureConfig.cs" "%SVC_STAGE%\" >nul +copy /y "%ROOT%app.ico" "%SVC_STAGE%\" >nul + +dotnet publish "%SVC_STAGE%\TeslaLauncherService.csproj" ^ + -c Release ^ + -r win-x86 ^ + --self-contained true ^ + -p:PublishSingleFile=true ^ + -o "%BUILD_DIR%\Service" + +if errorlevel 1 ( + echo. + echo ERROR: Service build failed. + rmdir /s /q "%SVC_STAGE%" 2>nul + if "%QUIET%"=="0" pause + exit /b 1 +) + +rmdir /s /q "%SVC_STAGE%" +echo. +echo Service : %BUILD_DIR%\Service\TeslaLauncherService.exe +echo. + +:skip_service + +:: ── Build Agent ─────────────────────────────────────────────────────────────── +if %BUILD_AGENT%==0 goto :skip_agent + +if %BUILD_SERVICE%==1 (echo [2/2] Building TeslaLauncherAgent...) else (echo [1/1] Building TeslaLauncherAgent...) +echo. + +if exist "%AGT_STAGE%" rmdir /s /q "%AGT_STAGE%" +mkdir "%AGT_STAGE%" + +copy /y "%ROOT%TeslaLauncherAgent.cs" "%AGT_STAGE%\" >nul +copy /y "%ROOT%TeslaLauncherAgent.csproj" "%AGT_STAGE%\" >nul +copy /y "%ROOT%LaunchModels_Shared.cs" "%AGT_STAGE%\" >nul +copy /y "%ROOT%SecureConfig.cs" "%AGT_STAGE%\" >nul +copy /y "%ROOT%app.ico" "%AGT_STAGE%\" >nul + +dotnet publish "%AGT_STAGE%\TeslaLauncherAgent.csproj" ^ + -c Release ^ + -r win-x86 ^ + --self-contained true ^ + -p:PublishSingleFile=true ^ + "-p:DefineConstants=WINFORMS" ^ + -o "%BUILD_DIR%\Agent" + +if errorlevel 1 ( + echo. + echo ERROR: Agent build failed. + rmdir /s /q "%AGT_STAGE%" 2>nul + if "%QUIET%"=="0" pause + exit /b 1 +) + +rmdir /s /q "%AGT_STAGE%" +echo. +echo Agent : %BUILD_DIR%\Agent\TeslaLauncherAgent.exe +echo. + +:skip_agent + +:: ── Copy shared assets into build output ───────────────────────────────────── +if exist "%ROOT%install.bat" copy /y "%ROOT%install.bat" "%BUILD_DIR%\" >nul +if exist "%ROOT%assets\oalinst.exe" copy /y "%ROOT%assets\oalinst.exe" "%BUILD_DIR%\" >nul +if exist "%ROOT%assets\dx9201006\" xcopy /y /s /i /q "%ROOT%assets\dx9201006" "%BUILD_DIR%\dx9201006" >nul + +:: ── Summary ─────────────────────────────────────────────────────────────────── +echo ============================================================ +echo Build complete +echo ============================================================ +echo. +if %BUILD_SERVICE%==1 echo Service : %BUILD_DIR%\Service\TeslaLauncherService.exe +if %BUILD_AGENT%==1 echo Agent : %BUILD_DIR%\Agent\TeslaLauncherAgent.exe +echo. +echo Next steps: +echo 1. Copy the TeslaLauncher\ folder to each cockpit PC +echo 2. Run TeslaLauncher\install.bat as Administrator +echo. +if "%QUIET%"=="0" pause +exit /b 0 + +:: ── Helpers ─────────────────────────────────────────────────────────────────── +:check_file +if not exist "%ROOT%%~1" ( + echo ERROR: Required source file not found: %~1 + exit /b 1 +) +exit /b 0 + +:err_missing +echo. +echo Make sure all source files are in the same folder as build.bat: +echo TeslaLauncherService.cs / .csproj +echo TeslaLauncherAgent.cs / .csproj +echo LaunchModels_Shared.cs +echo SecureConfig.cs +echo install.bat +echo. +if "%QUIET%"=="0" pause +exit /b 1 diff --git a/Launcher/install.bat b/Launcher/install.bat new file mode 100644 index 0000000..f3ccfc3 --- /dev/null +++ b/Launcher/install.bat @@ -0,0 +1,273 @@ +@echo off +:: ============================================================================= +:: TeslaLauncher Modern — Installation Script +:: ============================================================================= +:: Run as Administrator on each cockpit PC. +:: Must be run from the TeslaLauncher\ folder produced by build.bat: +:: +:: TeslaLauncher\ +:: install.bat <- this file +:: Service\ +:: TeslaLauncherService.exe +:: Agent\ +:: TeslaLauncherAgent.exe +:: oalinst.exe +:: dx9201006\ +:: DXSETUP.exe +:: ============================================================================= + +setlocal enabledelayedexpansion + +echo ============================================================ +echo Tesla Launcher v0.1 - Installation +echo ============================================================ +echo. + +:: ── Paths ───────────────────────────────────────────────────────────────────── +set ROOT=%~dp0 +set SERVICE_SRC=%ROOT%Service +set AGENT_SRC=%ROOT%Agent +set INSTALL_DIR=C:\Program Files\TeslaLauncher +set DATA_DIR=C:\ProgramData\TeslaLauncher +set SERVICE_EXE=%INSTALL_DIR%\TeslaLauncherService.exe +set AGENT_EXE=%INSTALL_DIR%\TeslaLauncherAgent.exe +set SERVICE_NAME=Tesla Application Launcher +set SERVICE_DISPLAY=Tesla Application Launcher +set CONSOLE_PORT=53290 + +:: ── Admin check ─────────────────────────────────────────────────────────────── +net session >nul 2>&1 +if %errorlevel% neq 0 ( + echo ERROR: This script must be run as Administrator. + pause & exit /b 1 +) + +:: ── Verify source files ─────────────────────────────────────────────────────── +if not exist "%SERVICE_SRC%\TeslaLauncherService.exe" ( + echo ERROR: TeslaLauncherService.exe not found in %SERVICE_SRC% + echo Run build.bat first, then run install.bat from the TeslaLauncher\ folder. + pause & exit /b 1 +) +if not exist "%AGENT_SRC%\TeslaLauncherAgent.exe" ( + echo ERROR: TeslaLauncherAgent.exe not found in %AGENT_SRC% + echo Run build.bat first, then run install.bat from the TeslaLauncher\ folder. + pause & exit /b 1 +) + +:: ── Detect and clean up existing installation ───────────────────────────────── +set EXISTING=0 +if exist "%SERVICE_EXE%" set EXISTING=1 +if exist "%AGENT_EXE%" set EXISTING=1 + +if "%EXISTING%"=="1" ( + echo Existing installation detected. Cleaning up... + echo. + + :: Stop and kill the Agent process + taskkill /F /IM TeslaLauncherAgent.exe >nul 2>&1 + echo Agent process stopped. + + :: Stop and remove the service, wait for it to fully stop + sc stop "%SERVICE_NAME%" >nul 2>&1 + sc stop "Tesla Application Launcher" >nul 2>&1 + timeout /t 3 /nobreak >nul + sc delete "%SERVICE_NAME%" >nul 2>&1 + sc delete "Tesla Application Launcher" >nul 2>&1 + timeout /t 2 /nobreak >nul + echo Service stopped and removed. + + :: Delete old executables, logs, and session key so SecureConfig re-runs + del /F /Q "%SERVICE_EXE%" >nul 2>&1 + del /F /Q "%AGENT_EXE%" >nul 2>&1 + del /F /Q "%INSTALL_DIR%\podconf.log" >nul 2>&1 + del /F /Q "%DATA_DIR%\TeslaKeyStore.key" >nul 2>&1 + del /F /Q "%DATA_DIR%\LaunchApps.xml" >nul 2>&1 + del /F /Q "%DATA_DIR%\configuring.json" >nul 2>&1 + echo Old executables, logs, session key, and app config deleted. + + :: Reset all physical Ethernet adapters to DHCP so the SecureConfig + :: protocol triggers correctly on the next boot. + :: We must REMOVE the static IP address entry first, then enable DHCP. + :: Set-NetIPInterface -Dhcp Enabled alone only flips the DHCP flag but + :: leaves the static address in the registry, so IsMachineConfigured() + :: still sees a non-DHCP adapter on the next boot and skips SecureConfig. + echo Resetting network adapters to DHCP... + powershell -NoProfile -Command "Get-NetAdapter -Physical | Where-Object {$_.Status -eq 'Up'} | ForEach-Object { $n=$_.Name; Get-NetIPAddress -InterfaceAlias $n -AddressFamily IPv4 -EA 0 | Where-Object {$_.PrefixOrigin -ne 'WellKnown' -and $_.PrefixOrigin -ne 'Dhcp'} | Remove-NetIPAddress -Confirm:$false -EA 0; Set-NetIPInterface -InterfaceAlias $n -Dhcp Enabled -EA 0 }; Get-NetAdapter -Physical | Where-Object {$_.Status -eq 'Up'} | Set-DnsClientServerAddress -ResetServerAddresses" >nul 2>&1 + echo Network adapters reset to DHCP. + echo. +) else ( + :: No existing install — still remove any stale service registration + sc stop "%SERVICE_NAME%" >nul 2>&1 + sc stop "Tesla Application Launcher" >nul 2>&1 + sc delete "%SERVICE_NAME%" >nul 2>&1 + sc delete "Tesla Application Launcher" >nul 2>&1 + timeout /t 2 /nobreak >nul +) + +:: ── STEP 1: Create directories ──────────────────────────────────────────────── +echo [1/8] Creating directories... +if not exist "%INSTALL_DIR%" mkdir "%INSTALL_DIR%" +if not exist "%DATA_DIR%" mkdir "%DATA_DIR%" +if not exist "C:\Games" mkdir "C:\Games" + +:: Grant all users modify access to the data and games directories so +:: the Service and Agent can write files (LaunchApps.xml, game installs). +icacls "%DATA_DIR%" /grant *S-1-5-32-545:(OI)(CI)M /T >nul 2>&1 +icacls "C:\Games" /grant *S-1-5-32-545:(OI)(CI)M /T >nul 2>&1 +echo %INSTALL_DIR% +echo %DATA_DIR% (Users: modify access) +echo C:\Games (Users: modify access) + +:: ── STEP 2: Copy files ──────────────────────────────────────────────────────── +echo [2/8] Copying files... +copy /Y "%SERVICE_SRC%\TeslaLauncherService.exe" "%INSTALL_DIR%\" >nul +copy /Y "%AGENT_SRC%\TeslaLauncherAgent.exe" "%INSTALL_DIR%\" >nul +copy /Y "%AGENT_SRC%\*.dll" "%INSTALL_DIR%\" >nul 2>&1 + +:: Install DirectX June 2010 runtime (required by games) +if exist "%ROOT%\dx9201006\DXSETUP.exe" ( + echo Installing DirectX runtime... + "%ROOT%\dx9201006\DXSETUP.exe" /silent + echo DirectX installed. +) + +:: Install OpenAL runtime (required by game audio) +if exist "%ROOT%oalinst.exe" ( + echo Installing OpenAL runtime... + "%ROOT%oalinst.exe" /s + echo OpenAL installed. +) + +:: Enable SMB1 client (required by game networking) +echo Enabling SMB1 file sharing... +dism /Online /Enable-Feature /FeatureName:SMB1Protocol /All /NoRestart >nul 2>&1 +echo SMB1 enabled (reboot required to take effect). + +:: Enable DirectPlay (required by older games) +echo Enabling DirectPlay... +dism /Online /Enable-Feature /FeatureName:DirectPlay /All /NoRestart >nul 2>&1 +echo DirectPlay enabled. +echo Done. + +:: ── STEP 3: Install Windows Service ────────────────────────────────────────── +echo [3/8] Installing Windows Service... +sc create "%SERVICE_NAME%" ^ + binPath= "\"%SERVICE_EXE%\"" ^ + DisplayName= "%SERVICE_DISPLAY%" ^ + start= delayed-auto ^ + obj= LocalSystem ^ + type= own + +if %errorlevel% neq 0 ( + echo ERROR: Failed to register Windows Service. + pause & exit /b 1 +) + +sc description "%SERVICE_NAME%" ^ + "Tesla Application Launcher - accepts TeslaConsole connections and controls simulation software" +sc failure "%SERVICE_NAME%" ^ + reset= 86400 actions= restart/5000/restart/10000/restart/30000 + +:: Delay auto-start by 20 seconds to let the network stack settle +reg add "HKLM\SYSTEM\CurrentControlSet\Services\Tesla Application Launcher" ^ + /v AutoStartDelay /t REG_DWORD /d 20000 /f >nul + +:: Set the global delayed-auto-start delay to 20 seconds (default is 120s) +reg add "HKLM\SYSTEM\CurrentControlSet\Control" ^ + /v AutoStartDelay /t REG_DWORD /d 20000 /f >nul + +echo Service registered: "%SERVICE_NAME%" (20s per-service delay, 20s global delay) + +:: ── STEP 4: Disable Windows Firewall (closed network) ──────────────────────── +echo. +echo [4/8] Disabling Windows Firewall (closed network)... +:: Disable firewall on all profiles — pods run on a closed network +netsh advfirewall set allprofiles state off +echo Windows Firewall disabled on all profiles. + +:: ── STEP 5: Power settings (max performance, no sleep) ────────────────────── +echo. +echo [5/8] Configuring power settings... + +:: Activate Ultimate Performance plan (hidden by default, GUID is well-known) +powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61 >nul 2>&1 +for /f "tokens=4" %%g in ('powercfg -list ^| findstr /i "Ultimate"') do ( + powercfg -setactive %%g +) +echo Ultimate Performance power plan activated. + +:: Disable all sleep/hibernate/standby timeouts (AC power) +powercfg -change -standby-timeout-ac 0 +powercfg -change -hibernate-timeout-ac 0 +powercfg -change -monitor-timeout-ac 0 +powercfg -change -disk-timeout-ac 0 +powercfg -h off >nul 2>&1 +echo Sleep, hibernate, monitor timeout, and disk timeout disabled. + +:: ── STEP 6: Disable notifications ─────────────────────────────────────────── +echo. +echo [6/8] Disabling notifications... + +:: Disable Action Center / notification center +reg add "HKCU\SOFTWARE\Policies\Microsoft\Windows\Explorer" ^ + /v DisableNotificationCenter /t REG_DWORD /d 1 /f >nul +:: Disable toast notifications +reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\PushNotifications" ^ + /v ToastEnabled /t REG_DWORD /d 0 /f >nul +:: Disable Windows tips and suggestions +reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" ^ + /v SubscribedContent-338389Enabled /t REG_DWORD /d 0 /f >nul +reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" ^ + /v SubscribedContent-310093Enabled /t REG_DWORD /d 0 /f >nul +:: Disable "Get tips, tricks, and suggestions" +reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" ^ + /v SoftLandingEnabled /t REG_DWORD /d 0 /f >nul +echo Notifications and tips disabled. + +:: ── STEP 7: Disable UAC ───────────────────────────────────────────────────── +echo. +echo [7/8] Disabling UAC... +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" ^ + /v EnableLUA /t REG_DWORD /d 0 /f >nul +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" ^ + /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f >nul +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" ^ + /v PromptOnSecureDesktop /t REG_DWORD /d 0 /f >nul +echo UAC disabled (takes effect after reboot). + +:: ── STEP 8: Configure Agent auto-start ─────────────────────────────────────── +echo. +echo [8/8] Configuring Agent auto-start... + +set AUTORUN_KEY=HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run +reg add "%AUTORUN_KEY%" /v "TeslaLauncherAgent" /t REG_SZ ^ + /d "\"%AGENT_EXE%\"" /f >nul + +echo Added TeslaLauncherAgent to HKLM\...\Run. +echo. +echo IMPORTANT: This cockpit PC must be configured for auto-login. +echo If not already set up, run netplwiz and uncheck +echo "Users must enter a user name and password", or set: +echo HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon +echo AutoAdminLogon = 1 +echo DefaultUserName = ^ +echo DefaultPassword = ^ + +:: ── Done ────────────────────────────────────────────────────────────────────── +echo. +echo ============================================================ +echo Installation Complete! +echo ============================================================ +echo. +echo Installed to : %INSTALL_DIR% +echo Config dir : %DATA_DIR% +echo Firewall : disabled (closed network) +echo. +echo Rebooting in 10 seconds... +echo The Service and Agent will start automatically in the correct order. +echo On first boot with DHCP, SecureConfig runs automatically — +echo watch plasma or on main screen for the Request ID and Passphrase. +echo. +echo After reboot: use Console to install apps. +echo. +shutdown /r /t 10 diff --git a/README.md b/README.md new file mode 100644 index 0000000..1718664 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# TeslaSuite + +The Tesla cockpit-pod software, in one repository: + +| Folder | What it is | Target | +|--------|------------|--------| +| [`Console/`](Console/) | **TeslaConsole** — the operator console (WinForms) that configures and drives the pods. A faithful decompiled reconstruction of the original `TeslaConsole.exe`, with a differential test suite that pins it to the original baseline. | .NET Framework 4.8 | +| [`Launcher/`](Launcher/) | **TeslaLauncher** — the pod-side Service (Session 0 RPC listener) + Agent (user-session app launcher). A clean .NET 6 rewrite of the original launcher. | .NET 6 (windows, x86) | + +The console and the launcher talk over TCP 53290 using an OFB-encrypted +BinaryFormatter RPC. They share a wire contract (`Tesla.Net` types + +`ILauncherService`/`IPodManagerConnection` signatures) that **must stay byte-for-byte +compatible**. + +> ⚠️ **Known duplication (intentional, for now):** that shared contract currently +> lives in two places — the console consumes it as the compiled +> `Console/lib/TeslaConsoleLaunchLib.dll`, and the launcher hand-replicates the same +> types in `Launcher/LaunchModels_Shared.cs`. They must match exactly. Consolidating +> this into a single multi-targeted `Tesla.Contract` source project is the main +> follow-up this monorepo is meant to enable. + +## Building + +Each side has its own toolchain; the combined solution builds both: + +``` +dotnet build TeslaSuite.sln -c Release +``` + +Or individually: + +``` +dotnet build Console/TeslaConsole.csproj -c Release # the console +dotnet test Console/tests/TeslaConsole.DiffTests # differential + catalog tests +dotnet build Launcher/TeslaLauncher.sln -c Release # service + agent +``` + +The launcher also has its original `Launcher/build.bat` / `Launcher/install.bat` +for producing and deploying the self-contained pod build. + +## Layout notes + +- `Console/original/TeslaConsole.exe` is the reference baseline the differential + tests compare against — keep it. +- `Console/lib/*.dll` are the proprietary binary dependencies the console still + references (`TeslaConsoleLaunchLib`, `TeslaSecureConfiguration`, `Munga Net`, + `BitmapLibrary`, `WeifenLuo` docking). +- `Console/RedPlanet/Apps.xml` is the data-driven product catalog (see the console's + Site Management → Add Product / Register Product on Pods). diff --git a/TeslaSuite.sln b/TeslaSuite.sln new file mode 100644 index 0000000..6e6b8fc --- /dev/null +++ b/TeslaSuite.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeslaConsole", "Console\TeslaConsole.csproj", "{27285664-95C3-49FB-95BA-A34721060BF1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Console", "Console", "{91E92ADD-9F67-41E4-B43C-CCB7B2D95F15}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{27C769F3-F07F-456E-ACB7-F4A4A21AB6D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeslaConsole.DiffTests", "Console\tests\TeslaConsole.DiffTests\TeslaConsole.DiffTests.csproj", "{467AA87A-FBD4-45D7-B8F8-9336C95884D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeslaLauncherService", "Launcher\TeslaLauncherService.csproj", "{910D4404-B3A2-4217-B61C-D43E7F22CDAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeslaLauncherAgent", "Launcher\TeslaLauncherAgent.csproj", "{916DCDA5-3379-4383-96F0-AC7B26FAF64E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {27285664-95C3-49FB-95BA-A34721060BF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27285664-95C3-49FB-95BA-A34721060BF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27285664-95C3-49FB-95BA-A34721060BF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27285664-95C3-49FB-95BA-A34721060BF1}.Release|Any CPU.Build.0 = Release|Any CPU + {467AA87A-FBD4-45D7-B8F8-9336C95884D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {467AA87A-FBD4-45D7-B8F8-9336C95884D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {467AA87A-FBD4-45D7-B8F8-9336C95884D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {467AA87A-FBD4-45D7-B8F8-9336C95884D2}.Release|Any CPU.Build.0 = Release|Any CPU + {910D4404-B3A2-4217-B61C-D43E7F22CDAA}.Debug|Any CPU.ActiveCfg = Debug|x86 + {910D4404-B3A2-4217-B61C-D43E7F22CDAA}.Debug|Any CPU.Build.0 = Debug|x86 + {910D4404-B3A2-4217-B61C-D43E7F22CDAA}.Release|Any CPU.ActiveCfg = Release|x86 + {910D4404-B3A2-4217-B61C-D43E7F22CDAA}.Release|Any CPU.Build.0 = Release|x86 + {916DCDA5-3379-4383-96F0-AC7B26FAF64E}.Debug|Any CPU.ActiveCfg = Debug|x86 + {916DCDA5-3379-4383-96F0-AC7B26FAF64E}.Debug|Any CPU.Build.0 = Debug|x86 + {916DCDA5-3379-4383-96F0-AC7B26FAF64E}.Release|Any CPU.ActiveCfg = Release|x86 + {916DCDA5-3379-4383-96F0-AC7B26FAF64E}.Release|Any CPU.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {27C769F3-F07F-456E-ACB7-F4A4A21AB6D1} = {91E92ADD-9F67-41E4-B43C-CCB7B2D95F15} + {467AA87A-FBD4-45D7-B8F8-9336C95884D2} = {27C769F3-F07F-456E-ACB7-F4A4A21AB6D1} + EndGlobalSection +EndGlobal