|
07.08.2010, 18:16
|
#1
|
|
|
|
Главнокомандующий
|
Регистрация: 20.01.2010
Сообщений: 1,539
Популярность: 22780
Золото Zhyk.Ru: 600
Сказал(а) спасибо: 43
Поблагодарили 1,717 раз(а) в 538 сообщениях
|
Автообновление. C# Version
> Автообновление <
В данной статье речь пойдет о том как сделать автообновление продукта и собственно создание самого ланчера/апдейтера.
Хочу сразу отменить, что в данном руководстве речь пойдет о создании примитивного апдейтера, который качает только 1 файл.
В последнее время обострилась тема с информерами для разных игр, а по скольку их пишут начинающие программисты, ПО представляет собой 1 файл, он же ехе. Автообновление, о котором я расскажу как раз им поможет
Конечный материал данной статьи (скрин):
Теория: - Как устроен механизм автообновления:
- Программа, которая выполняет фукнцию автообновления делает запрос на сервер, к некому файлу, в котором содержится информация, например о версии файла на сервере.
- После получения версии файла с сервера, программа сверяет версию нашего локального файла и версию сервера
- Если версия сервера выше, то мы качаем обновление.
- Если версия сервера равна или ниже, то мы ничего не делаем.
- После всех манипуляций, у нас запускается главный файл программы(не автообновления)
- Для реализации мы будем использовать следующие "манипуляции":
- Чтение/Запись XML файлов
- Чтение файлов с сервера (как будто браузер)
- Скачивание файлов с сервера
- Запуск другого процесса
Практика: - Для реализации у меня получился следующий набор классов:
- Core.cs
Содержит вспомогальный статичный класс Tools
- Core.Settings.cs
Реализация чтения/записи XML конфига
- Core.AutoUpdater.cs
Реализация автообновления
- Класс Core.cs
Код:
using System.Windows.Forms;
namespace AutoUpdater
{
public static class Tools
{
public static DialogResult InfoBowShow(string text)
{
return MessageBox.Show(text, "Информация", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
- Класс Core.Settings.cs
Код:
using System;
using System.IO;
using System.Xml;
namespace AutoUpdater
{
public class Settings
{
// Путь к файлу с настройками
private String configFile = "updater.xml";
// Свойства для передачи данных из файла настроек
public String UpdateServerVersionsUrl { get; set; }
public String UpdateServerMainExeUrl { get; set; }
public String LocalMainExe { get; set; }
public Int32 Version { get; set; }
public Settings() { Version = 0; }
#region :: Methods ::
public void Save()
{
try
{
XmlDocument xdoc = new XmlDocument();
{
xdoc.AppendChild(xdoc.CreateNode(XmlNodeType.XmlDeclaration, "", ""));
XmlNode conf = xdoc.CreateElement("ConfigurationFile");
{
XmlNode lNode = xdoc.CreateElement("UpdateServerVersionsUrl");
XmlNode lNodeText = xdoc.CreateTextNode(UpdateServerVersionsUrl);
lNode.AppendChild(lNodeText);
conf.AppendChild(lNode);
XmlNode pNode = xdoc.CreateElement("UpdateServerMainExeUrl");
XmlNode pNodeText = xdoc.CreateTextNode(UpdateServerMainExeUrl);
pNode.AppendChild(pNodeText);
conf.AppendChild(pNode);
XmlNode mNode = xdoc.CreateElement("LocalMainExe");
XmlNode mNodeText = xdoc.CreateTextNode(LocalMainExe);
mNode.AppendChild(mNodeText);
conf.AppendChild(mNode);
XmlNode vNode = xdoc.CreateElement("Version");
XmlNode vNodeText = xdoc.CreateTextNode(Version.ToString());
vNode.AppendChild(vNodeText);
conf.AppendChild(vNode);
}
xdoc.AppendChild(conf);
}
xdoc.Save(configFile);
}
catch { }
}
public void Load()
{
if (File.Exists(configFile))
{
try
{
XmlDocument xdoc = new XmlDocument();
xdoc.Load(configFile);
XmlNode reader = xdoc.SelectSingleNode("ConfigurationFile/UpdateServerVersionsUrl");
if (reader != null)
UpdateServerVersionsUrl = reader.InnerText;
reader = xdoc.SelectSingleNode("ConfigurationFile/UpdateServerMainExeUrl");
if (reader != null)
UpdateServerMainExeUrl = reader.InnerText;
reader = xdoc.SelectSingleNode("ConfigurationFile/LocalMainExe");
if (reader != null)
LocalMainExe = reader.InnerText;
reader = xdoc.SelectSingleNode("ConfigurationFile/Version");
if (reader != null)
{
int tmpInt = 0;
if (Int32.TryParse(reader.InnerText, out tmpInt)) Version = Int32.Parse(reader.InnerText);
}
}
catch { }
}
}
#endregion
}
}
- Класс Core.AutoUpdater.cs
Код:
using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Diagnostics;
namespace AutoUpdater
{
public class Core
{
// Класс-структура, который мы будем передавать как аргумент в событие,
// которое вызывается после проверки версии сервера
public class AutoUpdateEventArg
{
// Флаг отражающий успех или провал попытки получить версию сервера
public Boolean UpdateSuccess { get; set; }
// Флаг отражающий нужно ли нам обновление
public Boolean NeedToUpdate { get; set; }
public AutoUpdateEventArg(bool UpdateSuccess, bool NeedToUpdate)
{
this.UpdateSuccess = UpdateSuccess;
this.NeedToUpdate = NeedToUpdate;
}
}
// Создание нового делегата
public delegate void AutoUpdateCheckDelegate(AutoUpdateEventArg arg);
// Создание события на базе делегата
public event AutoUpdateCheckDelegate CheckForUpdatesCompleted;
// Метод вызова события внутри класса инициатора
private void OnCheckForUpdatesCompleted(AutoUpdateEventArg arg)
{
if (CheckForUpdatesCompleted != null) CheckForUpdatesCompleted(arg);
}
// Объект класса настроек
private Settings settings;
// Объект класса WebClient
private WebClient downloader;
// Свойства
public WebClient Downloader { get { return downloader; } }
public Int32 CurrentVersion { get; set; }
public Int32 ServerVersion { get; set; }
public Core()
{
downloader = new WebClient();
settings = new Settings();
// Загружаем файл настроек
settings.Load();
// Устанавилваем начальные значения версий
this.ServerVersion = 0;
this.CurrentVersion = settings.Version;
}
// Метод, для проверки на сколько успешно загрузились настройки
public bool CheckSettings()
{
// Если хотябы одно из 3х свойств настроек пустое
if (String.IsNullOrEmpty(settings.LocalMainExe) ||
String.IsNullOrEmpty(settings.UpdateServerMainExeUrl) ||
String.IsNullOrEmpty(settings.UpdateServerVersionsUrl))
{
// возвращаем false;
return false;
}
else
return true;
}
// Метод для запуска главного файла (не автообновления)
public void RunMainExe()
{
try
{
using (Process p = new Process() { StartInfo = new ProcessStartInfo(settings.LocalMainExe) })
{
p.Start();
System.Windows.Forms.Application.Exit();
}
}
catch
{
Tools.InfoBowShow("Ошибка при запуске главного приложения!");
System.Windows.Forms.Application.Exit();
}
}
// Метод проверки наличиия обновления на сервере
public void CheckForUpdates()
{
// Создаем объект класса Thread и инициализируем его
Thread checkerThread = new Thread(new ThreadStart(delegate
{
try
{
// Веб запрос к нашему серверу по URL с файлом хранящим версию на сервере
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(settings.UpdateServerVersionsUrl);
// Ответ сервера
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
// Используем чтение потока данных
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
// Пытаемся получить версию сервера, приобразуя в Int32 из строки
// строка это содержимое файла версий с сервера
ServerVersion = Int32.Parse(reader.ReadToEnd());
// Если версия сервера выше, то мы сообщаем о завершении проверки и передаем
// флаги, что проверка прошла успешно и надо обновиться
if (ServerVersion > CurrentVersion) OnCheckForUpdatesCompleted(new AutoUpdateEventArg(true, true));
// В противном случаи говорим, что проверка прошла успешно, но обновления не надо
else OnCheckForUpdatesCompleted(new AutoUpdateEventArg(true, false));
}
}
catch
{
// Если возникает ошибка, то говорим, что проверка не удалась и обновления не нужно
OnCheckForUpdatesCompleted(new AutoUpdateEventArg(false, false));
}
}));
// Запускаем наш поток, чтобы он выполнил код выше
checkerThread.Start();
}
// Метод для скачивания обновленной версии файла с сервера
public void DownloadUpdates()
{
// Запускаем асинхронное скачивание файла
// Указываем URL - что качать и путь куда сохранить
downloader.DownloadFileAsync(new Uri(settings.UpdateServerMainExeUrl), settings.LocalMainExe);
// Добавляем обработчик события
downloader.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(downloader_DownloadFileCompleted);
}
// Если скачивание обновленного файла прошло успешно, что сохраняем в настройки версию с сервера
private void downloader_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
settings.Version = ServerVersion;
settings.Save();
}
}
}
Самые важные части кода закомментирвоаны для упрощения разбора вами.
Настройка автообновления:
|
Цитата: |
|
|
|
|
Сообщение от Клиентский файл - updater.xml |
|
|
|
|
|
|
|
<?xml version="1.0"?>
<ConfigurationFile>
<UpdateServerVersionsUrl>http://localhost/autoupdater/versions.file</UpdateServerVersionsUrl>
<UpdateServerMainExeUrl>http://localhost/autoupdater/mainFile.exe</UpdateServerMainExeUrl>
<LocalMainExe>mainFile.exe</LocalMainExe>
<Version>1</Version>
</ConfigurationFile>
|
|
|
|
|
|
- Разъяснения:
Ключ: UpdateServerVersionsUrl - путь на сервере к файлу с версиями
Ключ: UpdateServerMainExeUrl - путь к файлу, который будет скачан с сервера
Ключ: LocalMainExe - главный файл, который запускается из автообновления и куда сохранится скачанный файл.
Ключ: Version - текущая версия локального главного файла.
|
Цитата: |
|
|
|
|
Сообщение от Серверный файл - versions.file |
|
|
|
|
|
|
|
1
|
|
|
|
|
|
Примечание: - В данной реализации, серверный файл должен иметь всего 1 строку, и в этой строке должны быть только цифра.
- Чтобы произошло обновление, версия файла на сервере должна быть выше, чем в клиенте.
Во вложении находится готовый проект на Visual Studio 2010.
________________
Fireball - Быстрое снятие и загрузка скриншотов на хостинг.
|
|
|
15 пользователя(ей) сказали cпасибо:
|
.AsTex. (18.08.2010), Грант97 (31.01.2015), Amba (08.08.2010), Dunя (07.08.2010), крайслер (12.08.2013), Leo_ня (07.08.2010), Moisei (07.08.2010), OrBiT_DaRk (19.08.2012), PrOveN (24.01.2012), RemoteAccess (06.11.2012), Sinyss (17.02.2012), the-boxi (12.07.2012), warl0ck (10.08.2010), XRASER (12.10.2012), xSkyDev (06.03.2013) |
08.08.2010, 14:57
|
#2
|
|
|
|
Пехотинец
|
Регистрация: 22.05.2009
Сообщений: 80
Популярность: 104
Сказал(а) спасибо: 28
Поблагодарили 46 раз(а) в 31 сообщениях
|
Re: Автообновление. C# Version.
Сейчас попробовал на c++ проверку версии и загрузка по списку, кода вышло не много только не знаю как реализовать загрузку файлов.
На форме 3 memo 2 label и 1 button
Код:
Memo1->Lines->Add(IdHTTP1->Get("адрес/server.txt")); //загрузка файла версии
Memo2->Lines->LoadFromFile("client.txt");
Label1->Caption=Memo1->Lines->GetText(); //версия сервера (для оформления)
Label2->Caption=Memo2->Lines->GetText(); //версия клиента (для оформления)
if (Label1->Caption!=Label2->Caption) { //если разные то загрузка списка файлов для скачивания
Memo3->Lines->Add(IdHTTP1->Get("адресс/updatelist.txt"));
double b;
double n;
String l;
double i;
b = Memo3->Lines->Count; //количество файлов
for (i = 0; i <= b; i++) {
n++; //номер файла р
l = Memo3->Lines->Strings[n]; //загрузка адресса файла
IdHTTP1->Get(l);
}
}
В цикле загрузка файлов каждый круг цикла добавляет номер строки (ссылки на файл) на единицу. Вопрос как заставить качать, видел код через инди на делфи на си не вышло сделать такого
|
|
|
01.08.2012, 23:48
|
#3
|
|
|
|
Разведчик
|
Регистрация: 28.12.2010
Сообщений: 48
Популярность: 202
Сказал(а) спасибо: 21
Поблагодарили 21 раз(а) в 17 сообщениях
|
Re: Автообновление. C# Version
Не бог программирования, но все же думаю мой вариант полегче
Код:
using System.Net;
using System.IO;
using System.Diagnostics;
Код:
private void button1_Click(object sender, EventArgs e)
{
if (File.Exists("ver.txt"))
{
}
else
{
File.AppendAllText("ver.txt", "0");
}
WebClient vers = new WebClient();
string vers1 = vers.DownloadString("https://dl.dropbox.com/u/33968340/Astrel/ver.txt");
string verc1 = File.ReadAllText("ver.txt");
if (verc1 != vers1)
{
MessageBox.Show("Найдено обновление");
vers.DownloadFile("https://dl.dropbox.com/u/33968340/Astrel/Test.txt", "Test.txt");
string[] files = File.ReadAllLines("Test.txt");
foreach (string i in files)
{
vers.DownloadFile("https://dl.dropbox.com/u/33968340/Astrel/" + i, i);
}
vers.DownloadFile("https://dl.dropbox.com/u/33968340/Astrel/ver.txt", "ver.txt");
File.Delete("Test.txt");
// Process.Start("name.exe");
}
else
{
MessageBox.Show("Обновлений нет");
// Process.Start("name.exe");
}
}
Последний раз редактировалось xSkyDev; 01.08.2012 в 23:50.
|
|
|
2 пользователя(ей) сказали cпасибо:
|
|
02.08.2012, 13:23
|
#4
|
|
|
|
Рыцарь-защитник
|
Регистрация: 28.08.2009
Сообщений: 603
Популярность: 19129
Золото Zhyk.Ru: 500
Сказал(а) спасибо: 84
Поблагодарили 602 раз(а) в 321 сообщениях
|
Re: Автообновление. C# Version
Он полегче, но хуже по оформлению + твоя программа будет виснуть при загрузке.
________________
We are Ducks. We are birds. We like bread. We cryack. Cryack.
|
|
|
02.08.2012, 15:00
|
#5
|
|
|
|
Разведчик
|
Регистрация: 28.12.2010
Сообщений: 48
Популярность: 202
Сказал(а) спасибо: 21
Поблагодарили 21 раз(а) в 17 сообщениях
|
Re: Автообновление. C# Version
Последний раз редактировалось xSkyDev; 07.10.2012 в 01:28.
|
|
|
08.03.2013, 18:47
|
#6
|
|
|
|
Разведчик
|
Регистрация: 08.03.2013
Сообщений: 3
Популярность: 10
Сказал(а) спасибо: 0
Поблагодарили 0 раз(а) в 0 сообщениях
|
Re: Автообновление. C# Version
а группу файлов как обновить?
1й - раз выкачиваем все.
2, 3, n+1 - только те файлы которые были изменены на сервере
и поддержку не только http как можно сделать? ftp с авторизацией tsl например?
________________
Инженер не смотрит порно. Он ведет расчет бабы на усталость © Федор Сумкин
|
|
|
08.03.2013, 19:28
|
#7
|
|
|
|
Старший сержант
|
Регистрация: 04.02.2011
Сообщений: 198
Популярность: 12244
Сказал(а) спасибо: 453
Поблагодарили 435 раз(а) в 242 сообщениях
|
Re: Автообновление. C# Version
________________
-Отложи на послезавтра то что можешь сделать сегодня, и тогда у тебя появятся два свободных дня!
|
|
|
08.03.2013, 19:33
|
#8
|
|
|
|
Разведчик
|
Регистрация: 08.03.2013
Сообщений: 3
Популярность: 10
Сказал(а) спасибо: 0
Поблагодарили 0 раз(а) в 0 сообщениях
|
Re: Автообновление. C# Version
|
Цитата: |
|
|
|
|
|
|
|
|
if (_Ftp.FileExists("/Folder/File.dat")) //проверим и если существует
_Ftp.GetFile("/Folder/File.dat", false);//скачаем |
|
|
|
|
|
а мне не конечный файл нужен, а целая папка.
с этим как быть?
а торент протокол как у апдейтеров у wow и wot прикрутить можно?
________________
Инженер не смотрит порно. Он ведет расчет бабы на усталость © Федор Сумкин
Последний раз редактировалось an.p.den; 08.03.2013 в 19:35.
|
|
|
08.03.2013, 19:46
|
#9
|
|
|
|
Старший сержант
|
Регистрация: 04.02.2011
Сообщений: 198
Популярность: 12244
Сказал(а) спасибо: 453
Поблагодарили 435 раз(а) в 242 сообщениях
|
Re: Автообновление. C# Version
________________
-Отложи на послезавтра то что можешь сделать сегодня, и тогда у тебя появятся два свободных дня!
Последний раз редактировалось крайслер; 08.03.2013 в 19:54.
|
|
|
08.03.2013, 19:53
|
#10
|
|
|
|
Разведчик
|
Регистрация: 08.03.2013
Сообщений: 3
Популярность: 10
Сказал(а) спасибо: 0
Поблагодарили 0 раз(а) в 0 сообщениях
|
Re: Автообновление. C# Version
|
Цитата: |
|
|
|
|
|
|
|
|
public const string ListDirectoryDetails |
|
|
|
|
|
но:
|
Цитата: |
|
|
|
|
|
|
|
|
Платформы
Windows 8, Windows Server 2012, Windows 7, Windows Vista с пакетом обновления 2 (SP2), Windows Server 2008 (роль основных серверных компонентов не поддерживается), Windows Server 2008 R2 (роль основных серверных компонентов поддерживается в пакете обновления 1 (SP1) или выше; системы на базе Itanium не поддерживаются) |
|
|
|
|
|
xp тут нет
Добавлено через 32 минуты
вру. читает.
тока блин... засада...
если брать ListDirectoryDetails - то выводится полная инфа: права, размер, дата название файла или папки
если брать ListDirectory - выводится все в кучу.
брать проверять каждое название на папку? типа если вошел - папка - если файл. то будет реально за*б...
или парсером пройтись по выводу ListDirectoryDetails
и получить два списка, 1й - где размер и имя файла и/или директории(без размера естесственно), 2й - просто название файлов с директорией.
по первому сравнивать текущие. по второму скачивать.
Добавлено через 36 минут
а как расположить все эти списки в памяти, а не в ini, txt и прочих файлах?
чтобы все в памяти делало, а файло было только одно - с настройками
________________
Инженер не смотрит порно. Он ведет расчет бабы на усталость © Федор Сумкин
Последний раз редактировалось an.p.den; 08.03.2013 в 20:30.
Причина: Добавлено сообщение
|
|
|
09.03.2013, 12:17
|
#11
|
|
|
|
Сержант
|
Регистрация: 06.11.2011
Сообщений: 105
Популярность: 873
Золото Zhyk.Ru: 14
Сказал(а) спасибо: 95
Поблагодарили 107 раз(а) в 70 сообщениях
|
Re: Автообновление. C# Version
Код:
public void selfUpdate()
{
try
{
WebClient client = new WebClient();
string actual = client.DownloadString(Settings.launchFilesUrl + "/version.txt");
string current = Application.ProductVersion;
if (!File.Exists("update.exe"))
{
Uri ui = new Uri(Settings.launchFilesUrl + "/update.exe");
client.DownloadFile(ui, launchPath + "\\update.exe");
}
if (!actual.Equals(current))
{
DialogResult dial = MessageBox.Show("Доступна новая версия \nлаунчера, обновить?", "Автоматическое обновление", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);
if (dial == DialogResult.Yes)
{
Process.Start("update.exe");
Environment.Exit(0);
}
client.Dispose();
}
}
catch { }
}
|
|
|
01.04.2013, 18:18
|
#12
|
|
|
|
Разведчик
|
Регистрация: 01.05.2012
Сообщений: 2
Популярность: 10
Сказал(а) спасибо: 0
Поблагодарили 0 раз(а) в 0 сообщениях
|
Re: Автообновление. C# Version
Мда..Вы хоть скрины заливайте на долгие файло изображение
У меня проблемма, я вот скачал исходник создал файлы залил их и мне пишет что не получается обновиться, скажите надо править ссылки в самой проге если да то где?!
|
|
|
20.04.2013, 22:26
|
#13
|
|
|
|
Сержант
|
Регистрация: 12.09.2010
Сообщений: 165
Популярность: 375
Сказал(а) спасибо: 70
Поблагодарили 39 раз(а) в 25 сообщениях
|
Re: Автообновление. C# Version
razor9113, попробуй сделать так
Код:
try
{
//код программы
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
И смотри ошибку.
|
|
|
21.04.2013, 03:30
|
#14
|
|
|
|
Разведчик
|
Регистрация: 28.12.2010
Сообщений: 48
Популярность: 202
Сказал(а) спасибо: 21
Поблагодарили 21 раз(а) в 17 сообщениях
|
Re: Автообновление. C# Version
|
Цитата: |
|
|
|
|
|
|
|
|
|
а мне не конечный файл нужен, а целая папка.
с этим как быть?
|
|
|
|
|
|
У себя реализовал примерно так :
принцип : - Получаем список файлов на веб сервере (парсинг html)
- Используя FileInfo сравниваем размер и название файлов
- Если файл не соответствует файлу на веб сервере, удаляем, скачиваем новый
- Если файла не существует, скачиваем файл с веб сервера (catch)
Код:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
namespace GetAllFiles
{
class Program
{
static void Main(string[] args)
{
WebClient wb = new WebClient();
string url = "http://files.skydev.su/zh/";
string html = wb.DownloadString(url);
Regex rg = new Regex("<a href=\".*?\"");
int i = 0;
int z = 0;
string[] file_net = new string[rg.Matches(html).Count - 5]; /// - 5 headers
////////////////////////
/// List of Web Files //
foreach(Match m in rg.Matches(html))
{
if (i < 5)
{
i++; /// Skiping headers 5
}
else
{
file_net[z] = m.ToString().Replace("\"", "").Replace("<a href=",""); /// getting files
Console.WriteLine(file_net[z]);
z++;
}
}
//////////////////////////
/// List of Local files///
/// //////////////////////
string[] files_local = Directory.GetFiles(Directory.GetCurrentDirectory());
//////////////////////
/// Checking files ///
/// //////////////////
foreach (string fil_net in file_net)
{
////////////////////////////
////Get Size of Web File ///
////////////////////////////
WebRequest req = HttpWebRequest.Create(url + fil_net);
req.Method = "HEAD";
int ContentLength;
using (System.Net.WebResponse resp = req.GetResponse())
{
if (int.TryParse(resp.Headers.Get("Content-Length"), out ContentLength))
{
}
}
///////////////////
//// Diff files ///
///////////////////
Console.WriteLine(ContentLength);
try
{
FileInfo f_info = new FileInfo(fil_net);
if (ContentLength != f_info.Length) //// File exists
{
Console.WriteLine("Найдено обновление для файла : " + f_info.Name);
f_info.Delete();
wb.DownloadFile(url + fil_net, fil_net);
}
Console.WriteLine(f_info.Length);
}
catch //// File exists = false
{
Console.WriteLine("Downloading " + fil_net);
wb.DownloadFile(url + fil_net,fil_net);
}
}
Console.ReadKey();
}
}
}
Последний раз редактировалось xSkyDev; 21.04.2013 в 17:17.
|
|
|
26.04.2016, 21:55
|
#15
|
|
|
|
Разведчик
|
Регистрация: 02.04.2012
Сообщений: 5
Популярность: 584
Сказал(а) спасибо: 0
Поблагодарили 1 раз в 1 сообщении
|
Re: Автообновление. C# Version
Пожалуйста, обновите скрины...
|
|
|
Ваши права в разделе
|
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения
HTML код Выкл.
|
|
|
Заявление об ответственности / Список мошенников
Часовой пояс GMT +4, время: 09:34.
|
|