using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.IO.Compression; using System.Linq; using Microsoft.Win32; using System.Reflection; using System.Security.Cryptography; using System.Threading.Tasks; using System.Windows.Forms; namespace PokerGameSetup { static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new InstallerForm()); } } sealed class InstallerForm : Form { const string Marker = "<>"; const string ExpectedPayloadSha256 = "D44B44805C80AA6107F2F0226D0B98F0C7B085031911CB97FB89729F9B2510E6"; readonly Color bg = Color.FromArgb(15, 12, 10); readonly Color panel = Color.FromArgb(28, 22, 18); readonly Color gold = Color.FromArgb(236, 186, 75); readonly Color red = Color.FromArgb(164, 26, 32); readonly Color green = Color.FromArgb(29, 135, 64); readonly Color text = Color.FromArgb(245, 236, 205); CasinoArtPanel art; Panel page; Button backButton; Button nextButton; Button cancelButton; int step; TextBox folderBox; CasinoProgress progress; Label progressText; CheckBox launchCheck; string installedPath; bool installing; public InstallerForm() { Text = "Assembler Casino Setup"; try { Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); } catch { } StartPosition = FormStartPosition.CenterScreen; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; ClientSize = new Size(820, 500); BackColor = bg; Font = new Font("Segoe UI", 10f); art = new CasinoArtPanel { Dock = DockStyle.Left, Width = 260 }; Controls.Add(art); page = new Panel { Left = 280, Top = 28, Width = 500, Height = 365, BackColor = panel }; page.Paint += (s, e) => { using (var pen = new Pen(Color.FromArgb(88, gold), 2)) e.Graphics.DrawRectangle(pen, 0, 0, page.Width - 1, page.Height - 1); }; Controls.Add(page); backButton = MakeButton("Назад"); backButton.Left = 406; backButton.Top = 428; backButton.Click += (s, e) => ShowStep(step - 1); Controls.Add(backButton); nextButton = MakeButton("Далее"); nextButton.Left = 532; nextButton.Top = 428; nextButton.Click += NextClicked; Controls.Add(nextButton); cancelButton = MakeButton("Отмена"); cancelButton.Left = 658; cancelButton.Top = 428; cancelButton.Click += (s, e) => { if (!installing) Close(); }; Controls.Add(cancelButton); ShowStep(0); } Button MakeButton(string caption) { var b = new Button { Text = caption, Width = 108, Height = 38, FlatStyle = FlatStyle.Flat, BackColor = Color.FromArgb(62, 43, 20), ForeColor = text, Font = new Font("Segoe UI", 10f, FontStyle.Bold), Cursor = Cursors.Hand }; b.FlatAppearance.BorderColor = gold; b.FlatAppearance.MouseOverBackColor = Color.FromArgb(94, 61, 24); b.FlatAppearance.MouseDownBackColor = Color.FromArgb(120, 71, 22); return b; } Label MakeLabel(string caption, int x, int y, int w, int h, float size, FontStyle style) { var l = new Label { Text = caption, Left = x, Top = y, Width = w, Height = h, ForeColor = text, BackColor = Color.Transparent, Font = new Font("Segoe UI", size, style) }; page.Controls.Add(l); return l; } void ShowStep(int value) { step = Math.Max(0, Math.Min(3, value)); page.Controls.Clear(); art.Stage = step; art.Invalidate(); backButton.Enabled = step > 0 && !installing; nextButton.Enabled = !installing; cancelButton.Enabled = !installing; nextButton.Text = step == 3 ? "Готово" : "Далее"; if (step == 0) WelcomePage(); if (step == 1) FolderPage(); if (step == 2) InstallPage(); if (step == 3) FinishPage(); } void WelcomePage() { MakeLabel("Добро пожаловать", 32, 32, 430, 42, 22f, FontStyle.Bold); MakeLabel("Установщик Assembler Casino", 34, 82, 420, 28, 13f, FontStyle.Regular); MakeLabel("Внутри уже лежит вся игра: exe, арты, звуки, анимации, музыка башни и служебные файлы.", 34, 136, 420, 72, 10.5f, FontStyle.Regular); MakeLabel("Сейчас выберем папку, красиво распакуем казино и положим рядом нормальный exe для запуска.", 34, 220, 420, 60, 10.5f, FontStyle.Regular); MakeLabel("Нажми «Далее», если готов открыть двери.", 34, 315, 420, 26, 10.5f, FontStyle.Bold); } void FolderPage() { MakeLabel("Куда установить игру?", 32, 32, 430, 42, 22f, FontStyle.Bold); MakeLabel("Можно выбрать любую папку. По умолчанию игра ставится на рабочий стол.", 34, 86, 420, 50, 10.5f, FontStyle.Regular); folderBox = new TextBox { Left = 34, Top = 160, Width = 330, Height = 28, Text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "покер"), BackColor = Color.FromArgb(9, 9, 9), ForeColor = text, BorderStyle = BorderStyle.FixedSingle }; page.Controls.Add(folderBox); var browse = MakeButton("Обзор"); browse.Left = 374; browse.Top = 154; browse.Width = 86; browse.Parent = page; browse.Click += (s, e) => { using (var dialog = new FolderBrowserDialog()) { dialog.Description = "Выбери папку для Assembler Casino"; dialog.SelectedPath = Directory.Exists(folderBox.Text) ? folderBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); if (dialog.ShowDialog(this) == DialogResult.OK) folderBox.Text = Path.Combine(dialog.SelectedPath, "покер"); } }; MakeLabel("Сохранения игроков будут храниться отдельно в AppData, поэтому переустановка игру не обнулит.", 34, 230, 420, 70, 10.5f, FontStyle.Regular); } void InstallPage() { MakeLabel("Установка", 32, 32, 430, 42, 22f, FontStyle.Bold); MakeLabel("Казино распаковывается. Фишки на стол, карты в колоду, барабаны на место.", 34, 86, 420, 52, 10.5f, FontStyle.Regular); progressText = MakeLabel("Ожидание старта...", 34, 166, 420, 28, 10.5f, FontStyle.Bold); progress = new CasinoProgress { Left = 34, Top = 208, Width = 420, Height = 34 }; page.Controls.Add(progress); BeginInstall(); } void FinishPage() { MakeLabel("Готово", 32, 32, 430, 42, 22f, FontStyle.Bold); MakeLabel("Assembler Casino установлено. Можно запускать и проверять, как оно выглядит уже на другом компьютере.", 34, 86, 420, 70, 10.5f, FontStyle.Regular); MakeLabel("Удачи за столами. И да, казино все еще подозрительно смотрит на чит-коды.", 34, 176, 420, 48, 10.5f, FontStyle.Regular); launchCheck = new CheckBox { Text = "Запустить игру после закрытия установщика", Left = 34, Top = 275, Width = 390, Height = 28, Checked = true, BackColor = Color.Transparent, ForeColor = text }; page.Controls.Add(launchCheck); cancelButton.Text = "Закрыть"; } async void BeginInstall() { installing = true; backButton.Enabled = nextButton.Enabled = cancelButton.Enabled = false; try { var target = folderBox != null && !string.IsNullOrWhiteSpace(folderBox.Text) ? folderBox.Text.Trim() : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "покер"); await Task.Run(() => ExtractPayload(target)); installedPath = target; installing = false; ShowStep(3); } catch (Exception ex) { installing = false; backButton.Enabled = nextButton.Enabled = cancelButton.Enabled = true; MessageBox.Show(this, ex.Message, "Ошибка установки", MessageBoxButtons.OK, MessageBoxIcon.Error); ShowStep(1); } } void ExtractPayload(string target) { Directory.CreateDirectory(target); Directory.CreateDirectory(Path.Combine(target, "runtime")); var exe = Assembly.GetExecutingAssembly().Location; var marker = System.Text.Encoding.UTF8.GetBytes(Marker); SetProgress(0, "Поиск архива игры..."); long payloadStart = -1; using (var fs = File.OpenRead(exe)) { payloadStart = FindMarkerInStream(fs, marker); } if (payloadStart < 0) throw new InvalidOperationException("В установщике не найден архив игры."); var tempZip = Path.Combine(Path.GetTempPath(), "assembler_casino_payload_" + Guid.NewGuid().ToString("N") + ".zip"); try { SetProgress(5, "Проверка целостности архива..."); using (var fs = File.OpenRead(exe)) { fs.Position = payloadStart; using (var outFs = File.Create(tempZip, 65536)) using (var sha = SHA256.Create()) { var buf = new byte[65536]; int r; while ((r = fs.Read(buf, 0, buf.Length)) > 0) { sha.TransformBlock(buf, 0, r, null, 0); outFs.Write(buf, 0, r); } sha.TransformFinalBlock(buf, 0, 0); var actual = BitConverter.ToString(sha.Hash).Replace("-", ""); if (!string.IsNullOrWhiteSpace(ExpectedPayloadSha256) && ExpectedPayloadSha256 != "TO_BE_FILLED_BY_RELEASE_BUILD" && !actual.Equals(ExpectedPayloadSha256, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Архив игры внутри установщика был изменен. Установка остановлена."); } } } using (var zip = ZipFile.OpenRead(tempZip)) { var files = zip.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList(); for (int i = 0; i < files.Count; i++) { var entry = files[i]; var full = Path.GetFullPath(Path.Combine(target, entry.FullName)); if (!full.StartsWith(Path.GetFullPath(target), StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("Архив содержит некорректный путь."); Directory.CreateDirectory(Path.GetDirectoryName(full)); entry.ExtractToFile(full, true); int percent = (int)((i + 1) * 100.0 / Math.Max(1, files.Count)); SetProgress(percent, "Устанавливаем: " + entry.FullName.Replace('/', '\\')); } } var oldBat = Path.Combine(target, "Assembler Casino.bat"); try { if (File.Exists(oldBat)) File.Delete(oldBat); } catch { } CreateDesktopShortcut(target); RegisterUninstaller(target); SetProgress(100, "Казино установлено."); } finally { try { if (File.Exists(tempZip)) File.Delete(tempZip); } catch { } } } void CreateDesktopShortcut(string target) { try { var shellType = Type.GetTypeFromProgID("WScript.Shell"); if (shellType == null) return; dynamic shell = Activator.CreateInstance(shellType); var desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); var linkPath = Path.Combine(desktop, "Assembler Casino.lnk"); dynamic shortcut = shell.CreateShortcut(linkPath); var launcher = Path.Combine(target, "Assembler Casino.exe"); var icon = Path.Combine(target, "assets", "icons", "casino.ico"); shortcut.TargetPath = launcher; shortcut.WorkingDirectory = target; shortcut.IconLocation = File.Exists(icon) ? icon : launcher + ",0"; shortcut.Description = "Assembler Casino"; shortcut.Save(); } catch { } } void RegisterUninstaller(string target) { try { using (var key = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\AssemblerCasino")) { if (key == null) return; var launcher = Path.Combine(target, "Assembler Casino.exe"); var uninstaller = Path.Combine(target, "Uninstall Assembler Casino.exe"); var icon = Path.Combine(target, "assets", "icons", "casino.ico"); key.SetValue("DisplayName", "Assembler Casino"); key.SetValue("DisplayVersion", "1.0"); key.SetValue("Publisher", "Assembler Casino"); key.SetValue("InstallLocation", target); key.SetValue("DisplayIcon", File.Exists(icon) ? icon : launcher); key.SetValue("UninstallString", "\"" + uninstaller + "\""); key.SetValue("NoModify", 1, RegistryValueKind.DWord); key.SetValue("NoRepair", 1, RegistryValueKind.DWord); } } catch { } } long FindMarkerInStream(Stream stream, byte[] marker) { var buffer = new byte[1024 * 1024]; long streamPos = 0; int bytesRead; int overlap = marker.Length - 1; int carry = 0; while ((bytesRead = stream.Read(buffer, carry, buffer.Length - carry)) > 0) { int total = carry + bytesRead; int idx = FindMarker(buffer, marker, total); if (idx >= 0) { return streamPos + idx + marker.Length; } if (total >= overlap) { Buffer.BlockCopy(buffer, total - overlap, buffer, 0, overlap); carry = overlap; streamPos += (total - overlap); } else { carry = total; } } return -1; } int FindMarker(byte[] bytes, byte[] marker) { return FindMarker(bytes, marker, bytes.Length); } int FindMarker(byte[] bytes, byte[] marker, int length) { for (int i = 0; i <= length - marker.Length; i++) { if (bytes[i] != marker[0]) continue; bool ok = true; for (int j = 1; j < marker.Length; j++) { if (bytes[i + j] != marker[j]) { ok = false; break; } } if (ok) return i; } return -1; } void SetProgress(int value, string caption) { if (InvokeRequired) { BeginInvoke(new Action(SetProgress), value, caption); return; } if (progress != null) progress.Value = value; if (progressText != null) progressText.Text = caption; } void NextClicked(object sender, EventArgs e) { if (step == 1) { if (folderBox == null || string.IsNullOrWhiteSpace(folderBox.Text)) { MessageBox.Show(this, "Выбери папку установки.", "Assembler Casino", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } } if (step == 2) return; if (step == 3) { if (launchCheck != null && launchCheck.Checked) { var root = !string.IsNullOrWhiteSpace(installedPath) ? installedPath : folderBox.Text.Trim(); var path = Path.Combine(root, "Assembler Casino.exe"); if (File.Exists(path)) Process.Start(path); } Close(); return; } ShowStep(step + 1); } } sealed class CasinoProgress : Control { int value; public int Value { get { return value; } set { this.value = Math.Max(0, Math.Min(100, value)); Invalidate(); } } public CasinoProgress() { DoubleBuffered = true; } protected override void OnPaint(PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; var r = ClientRectangle; r.Width -= 1; r.Height -= 1; using (var b = new SolidBrush(Color.FromArgb(8, 8, 8))) e.Graphics.FillRectangle(b, r); using (var p = new Pen(Color.FromArgb(236, 186, 75), 2)) e.Graphics.DrawRectangle(p, r); var fill = new Rectangle(4, 4, (int)((Width - 9) * value / 100.0), Height - 9); if (fill.Width > 0 && fill.Height > 0) { using (var brush = new LinearGradientBrush(fill, Color.FromArgb(30, 130, 60), Color.FromArgb(236, 186, 75), 0f)) e.Graphics.FillRectangle(brush, fill); } var caption = value + "%"; using (var font = new Font("Consolas", 10f, FontStyle.Bold)) using (var brush = new SolidBrush(Color.White)) { var sz = e.Graphics.MeasureString(caption, font); e.Graphics.DrawString(caption, font, brush, (Width - sz.Width) / 2, (Height - sz.Height) / 2); } } } sealed class CasinoArtPanel : Control { public int Stage { get; set; } readonly Color gold = Color.FromArgb(236, 186, 75); readonly Color red = Color.FromArgb(166, 24, 31); readonly Color green = Color.FromArgb(20, 112, 54); public CasinoArtPanel() { DoubleBuffered = true; } protected override void OnPaint(PaintEventArgs e) { var g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; using (var br = new LinearGradientBrush(ClientRectangle, Color.FromArgb(34, 12, 14), Color.FromArgb(7, 55, 30), 90f)) g.FillRectangle(br, ClientRectangle); DrawTitle(g); DrawCards(g, 28, 96); DrawRoulette(g, 132, 235); DrawChips(g, 48, 345); DrawStageDots(g); using (var pen = new Pen(Color.FromArgb(120, gold), 2)) g.DrawLine(pen, Width - 1, 0, Width - 1, Height); } void DrawTitle(Graphics g) { using (var f1 = new Font("Georgia", 20f, FontStyle.Bold)) using (var f2 = new Font("Segoe UI", 9f, FontStyle.Bold)) using (var b1 = new SolidBrush(gold)) using (var b2 = new SolidBrush(Color.FromArgb(235, 235, 220))) { g.DrawString("ASSEMBLER", f1, b1, 24, 24); g.DrawString("CASINO SETUP", f2, b2, 62, 58); } } void DrawCards(Graphics g, int x, int y) { DrawCard(g, x + 28, y + 8, -10, red, "A", "♥"); DrawCard(g, x + 76, y, 8, Color.Black, "K", "♠"); DrawCard(g, x + 124, y + 14, 14, red, "7", "♦"); } void DrawCard(Graphics g, int x, int y, float angle, Color suitColor, string rank, string suit) { var state = g.Save(); g.TranslateTransform(x + 30, y + 42); g.RotateTransform(angle); var r = new Rectangle(-30, -42, 60, 84); using (var path = RoundRect(r, 6)) using (var br = new SolidBrush(Color.FromArgb(245, 241, 225))) using (var pen = new Pen(gold, 2)) { g.FillPath(br, path); g.DrawPath(pen, path); } using (var f = new Font("Georgia", 17f, FontStyle.Bold)) using (var b = new SolidBrush(suitColor)) { g.DrawString(rank, f, b, -20, -32); g.DrawString(suit, f, b, -10, 4); } g.Restore(state); } void DrawRoulette(Graphics g, int x, int y) { var outer = new Rectangle(x - 68, y - 68, 136, 136); using (var br = new SolidBrush(Color.FromArgb(96, 48, 22))) g.FillEllipse(br, outer); using (var p = new Pen(gold, 5)) g.DrawEllipse(p, outer); for (int i = 0; i < 12; i++) { var a = i * 30.0 * Math.PI / 180.0; var c = (i % 3 == 0) ? green : (i % 2 == 0 ? red : Color.FromArgb(12, 12, 12)); using (var p = new Pen(c, 8)) g.DrawLine(p, x, y, x + (float)Math.Cos(a) * 58, y + (float)Math.Sin(a) * 58); } using (var br = new SolidBrush(Color.FromArgb(30, 120, 50))) g.FillEllipse(br, x - 36, y - 36, 72, 72); using (var p = new Pen(Color.FromArgb(255, 230, 150), 3)) g.DrawEllipse(p, x - 36, y - 36, 72, 72); using (var br = new SolidBrush(Color.White)) g.FillEllipse(br, x + 18, y - 55, 12, 12); using (var p = new Pen(Color.FromArgb(255, 230, 150), 4)) { g.DrawLine(p, x, y, x + 44, y - 18); g.DrawLine(p, x, y, x - 36, y + 26); g.DrawLine(p, x, y, x + 8, y + 48); } } void DrawChips(Graphics g, int x, int y) { DrawChip(g, x, y, red, "$"); DrawChip(g, x + 48, y + 28, green, "21"); DrawChip(g, x + 106, y + 2, Color.FromArgb(25, 70, 160), "VIP"); } void DrawChip(Graphics g, int x, int y, Color c, string label) { using (var br = new SolidBrush(c)) g.FillEllipse(br, x, y, 54, 54); using (var p = new Pen(Color.White, 4)) g.DrawEllipse(p, x + 5, y + 5, 44, 44); using (var p = new Pen(gold, 2)) g.DrawEllipse(p, x + 1, y + 1, 52, 52); using (var f = new Font("Segoe UI", 9f, FontStyle.Bold)) using (var b = new SolidBrush(Color.White)) { var sz = g.MeasureString(label, f); g.DrawString(label, f, b, x + 27 - sz.Width / 2, y + 27 - sz.Height / 2); } } void DrawStageDots(Graphics g) { int y = Height - 44; for (int i = 0; i < 4; i++) { var c = i <= Stage ? gold : Color.FromArgb(90, 80, 62); using (var br = new SolidBrush(c)) g.FillEllipse(br, 78 + i * 28, y, 12, 12); } } GraphicsPath RoundRect(Rectangle r, int radius) { int d = radius * 2; var path = new GraphicsPath(); path.AddArc(r.X, r.Y, d, d, 180, 90); path.AddArc(r.Right - d, r.Y, d, d, 270, 90); path.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90); path.AddArc(r.X, r.Bottom - d, d, d, 90, 90); path.CloseFigure(); return path; } } }