From 9aa059996cb35c04e5bef16529ab30de1d7853d5 Mon Sep 17 00:00:00 2001 From: 1415ddfer <1783488228@qq.com> Date: Thu, 7 Mar 2024 21:04:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.1 | 0 AboutDialog/About1.xaml | 29 +++ AboutDialog/About1.xaml.cs | 28 ++ App.xaml | 9 + App.xaml.cs | 25 ++ Defender/EngineCacheSha.cs | 35 +++ MainWindow.xaml | 38 +++ MainWindow.xaml.cs | 158 +++++++++++ Manager/ADManager.cs | 7 + Manager/AccountManager.cs | 156 +++++++++++ Manager/DataStream.cs | 89 +++++++ Manager/DownloadManager.cs | 374 +++++++++++++++++++++++++++ Manager/EngineManager.cs | 246 ++++++++++++++++++ Manager/HashHelper.cs | 201 ++++++++++++++ Manager/LoginManager.cs | 146 +++++++++++ Manager/SpaceEncoder.cs | 77 ++++++ Manager/UpDateManager.cs | 129 +++++++++ Manager/VersionManager.cs | 8 + Manager/WebApiManager.cs | 159 ++++++++++++ ServicesStaticInfo.cs | 9 + StaticHandleProtol.cs | 27 ++ Zerolauncher.csproj | 57 ++++ Zerolauncher.sln | 25 ++ controls/AirButton.cs | 68 +++++ controls/AirButton.xaml | 20 ++ controls/AirTextBox.xaml | 3 + controls/MemberControl.xaml | 14 + controls/MemberControl.xaml.cs | 114 ++++++++ controls/TextBoxRule.cs | 167 ++++++++++++ dialog/BaseDialog.xaml | 35 +++ dialog/BaseDialog.xaml.cs | 40 +++ dialog/DialogHelper.cs | 128 +++++++++ dialog/DownloadControl.xaml | 14 + dialog/DownloadControl.xaml.cs | 104 ++++++++ dialog/EditMember.xaml | 122 +++++++++ dialog/EditMember.xaml.cs | 80 ++++++ dialog/EditTeamControl.xaml | 49 ++++ dialog/EditTeamControl.xaml.cs | 45 ++++ dialog/UseAccDataTextAdd.xaml | 18 ++ dialog/UseAccDataTextAdd.xaml.cs | 70 +++++ res/1.png | Bin 0 -> 735508 bytes res/111.png | Bin 0 -> 1889 bytes res/112.png | Bin 0 -> 1975 bytes res/2.png | Bin 0 -> 210476 bytes res/21.png | Bin 0 -> 488 bytes res/22.png | Bin 0 -> 7845 bytes res/24.png | Bin 0 -> 566 bytes res/25.png | Bin 0 -> 1738 bytes res/319145.jpg | Bin 0 -> 218581 bytes res/319163.jpg | Bin 0 -> 485272 bytes res/39376.jpg | Bin 0 -> 29190 bytes res/39389.jpg | Bin 0 -> 30753 bytes res/4.png | Bin 0 -> 599 bytes res/5.png | Bin 0 -> 971 bytes res/592342.jpg | Bin 0 -> 597138 bytes res/592343.jpg | Bin 0 -> 658297 bytes res/61.png | Bin 0 -> 1071956 bytes res/88657.jpg | Bin 0 -> 430473 bytes res/RulePic/0.png | Bin 0 -> 76540 bytes res/RulePic/1.png | Bin 0 -> 51059 bytes res/RulePic/2.png | Bin 0 -> 51042 bytes res/RulePic/3.png | Bin 0 -> 51417 bytes res/RulePic/4.png | Bin 0 -> 51523 bytes res/about1.png | Bin 0 -> 924578 bytes res/btn_close1.png | Bin 0 -> 1596 bytes res/btn_mini1.png | Bin 0 -> 1837 bytes res/config.png | Bin 0 -> 2332 bytes res/defaultUserData.json | 45 ++++ res/del.png | Bin 0 -> 2994 bytes res/player_ico.png | Bin 0 -> 9896 bytes res/player_ico1.png | Bin 0 -> 17614 bytes res/title.ico | Bin 0 -> 276710 bytes res/wp12470772-ddtank-wallpapers.jpg | Bin 0 -> 199219 bytes res/wp12470850-ddtank-wallpapers.jpg | Bin 0 -> 221703 bytes 74 files changed, 3168 insertions(+) create mode 100644 1.1 create mode 100644 AboutDialog/About1.xaml create mode 100644 AboutDialog/About1.xaml.cs create mode 100644 App.xaml create mode 100644 App.xaml.cs create mode 100644 Defender/EngineCacheSha.cs create mode 100644 MainWindow.xaml create mode 100644 MainWindow.xaml.cs create mode 100644 Manager/ADManager.cs create mode 100644 Manager/AccountManager.cs create mode 100644 Manager/DataStream.cs create mode 100644 Manager/DownloadManager.cs create mode 100644 Manager/EngineManager.cs create mode 100644 Manager/HashHelper.cs create mode 100644 Manager/LoginManager.cs create mode 100644 Manager/SpaceEncoder.cs create mode 100644 Manager/UpDateManager.cs create mode 100644 Manager/VersionManager.cs create mode 100644 Manager/WebApiManager.cs create mode 100644 ServicesStaticInfo.cs create mode 100644 StaticHandleProtol.cs create mode 100644 Zerolauncher.csproj create mode 100644 Zerolauncher.sln create mode 100644 controls/AirButton.cs create mode 100644 controls/AirButton.xaml create mode 100644 controls/AirTextBox.xaml create mode 100644 controls/MemberControl.xaml create mode 100644 controls/MemberControl.xaml.cs create mode 100644 controls/TextBoxRule.cs create mode 100644 dialog/BaseDialog.xaml create mode 100644 dialog/BaseDialog.xaml.cs create mode 100644 dialog/DialogHelper.cs create mode 100644 dialog/DownloadControl.xaml create mode 100644 dialog/DownloadControl.xaml.cs create mode 100644 dialog/EditMember.xaml create mode 100644 dialog/EditMember.xaml.cs create mode 100644 dialog/EditTeamControl.xaml create mode 100644 dialog/EditTeamControl.xaml.cs create mode 100644 dialog/UseAccDataTextAdd.xaml create mode 100644 dialog/UseAccDataTextAdd.xaml.cs create mode 100644 res/1.png create mode 100644 res/111.png create mode 100644 res/112.png create mode 100644 res/2.png create mode 100644 res/21.png create mode 100644 res/22.png create mode 100644 res/24.png create mode 100644 res/25.png create mode 100644 res/319145.jpg create mode 100644 res/319163.jpg create mode 100644 res/39376.jpg create mode 100644 res/39389.jpg create mode 100644 res/4.png create mode 100644 res/5.png create mode 100644 res/592342.jpg create mode 100644 res/592343.jpg create mode 100644 res/61.png create mode 100644 res/88657.jpg create mode 100644 res/RulePic/0.png create mode 100644 res/RulePic/1.png create mode 100644 res/RulePic/2.png create mode 100644 res/RulePic/3.png create mode 100644 res/RulePic/4.png create mode 100644 res/about1.png create mode 100644 res/btn_close1.png create mode 100644 res/btn_mini1.png create mode 100644 res/config.png create mode 100644 res/defaultUserData.json create mode 100644 res/del.png create mode 100644 res/player_ico.png create mode 100644 res/player_ico1.png create mode 100644 res/title.ico create mode 100644 res/wp12470772-ddtank-wallpapers.jpg create mode 100644 res/wp12470850-ddtank-wallpapers.jpg diff --git a/1.1 b/1.1 new file mode 100644 index 0000000..e69de29 diff --git a/AboutDialog/About1.xaml b/AboutDialog/About1.xaml new file mode 100644 index 0000000..8149ee6 --- /dev/null +++ b/AboutDialog/About1.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + My GitHub + + + + + ddfgame@foxmail.com + + + + + + diff --git a/AboutDialog/About1.xaml.cs b/AboutDialog/About1.xaml.cs new file mode 100644 index 0000000..c959bd0 --- /dev/null +++ b/AboutDialog/About1.xaml.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using System.Windows; +using System.Windows.Documents; + +namespace Zerolauncher.AboutDialog +{ + /// + /// About1.xaml 的交互逻辑 + /// + public partial class About1 : Window + { + public About1() + { + InitializeComponent(); + } + + private void Hyperlink_Click(object sender, RoutedEventArgs e) + { + Process.Start(new ProcessStartInfo("http://github.com/1415ddfer/ZeroHelper") { UseShellExecute = true }); + } + + private void Hyperlink_Click_1(object sender, RoutedEventArgs e) + { + MessageBox.Show("已拷贝到粘贴板", "提示"); + Clipboard.SetDataObject("ddfgame@foxmail.com", true); + } + } +} diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..3605fff --- /dev/null +++ b/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..3c27778 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,25 @@ +using Lierda.WPFHelper; +using System.Windows; +using Zerolauncher.Manager; + +namespace Zerolauncher +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + LierdaCracker cracker = new LierdaCracker(); + + protected override void OnStartup(StartupEventArgs e) + { + cracker.Cracker(100);//垃圾回收间隔时间 + base.OnStartup(e); + _ = UpDateManager.TakeQMessage(); + DataStream.Load(); + _ = WebApiManager.StartListener(); + AccountManager.initLoadData(); + } + } + +} diff --git a/Defender/EngineCacheSha.cs b/Defender/EngineCacheSha.cs new file mode 100644 index 0000000..4c3aa44 --- /dev/null +++ b/Defender/EngineCacheSha.cs @@ -0,0 +1,35 @@ + +using System.Text; + +namespace Zerolauncher.Defender +{ + class EngineCacheSha + { + const string key = "mysecretkey"; + static string? sha; + public static int errorCode = 0; + + public static void Put(string eSha) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < eSha.Length; i++) + { + sb.Append((char)(eSha[i] ^ key[i % key.Length])); + } + sha = sb.ToString(); + } + + public static string? Get() + { + if (sha == null) { return null; } + var sb = new StringBuilder(); + for (int i = 0; i < sha.Length; i++) + { + sb.Append((char)(sha[i] ^ key[i % key.Length])); + } + return sb.ToString(); + + } + + } +} diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..ebe2f11 --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs new file mode 100644 index 0000000..19d7137 --- /dev/null +++ b/MainWindow.xaml.cs @@ -0,0 +1,158 @@ +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Zerolauncher.AboutDialog; +using Zerolauncher.controls; +using Zerolauncher.dialog; +using Zerolauncher.Manager; + +namespace Zerolauncher +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public static MainWindow Instance { get; private set; } + + private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + //实现窗口的拖动 + DragMove(); + } + + public MainWindow() + { + InitializeComponent(); + Instance = this; + + memberBG.ContextMenu = CreateContextMenu(); + int i = 0; + foreach (var account in AccountManager.accountsList) + { + var member = new MemberControl(); + member.memberId = i++; + member.text.Content = ServicesStaticInfo.ServicesShortName[account.providerId] + "-" + account.nickName; + mLayout.Children.Add(member); + } + } + + public void ReloadBtn() + { + //foreach (var btn in mLayout.Children.) + mLayout.Children.Clear(); + int i = 0; + foreach (var account in AccountManager.accountsList) + { + var member = new MemberControl(); + member.memberId = i++; + member.text.Content = ServicesStaticInfo.ServicesShortName[account.providerId] + "-" + account.nickName; + mLayout.Children.Add(member); + } + } + + private ContextMenu CreateContextMenu() + { + ContextMenu contextMenu = new ContextMenu(); + MenuItem addMemberItem = new MenuItem { Header = "添加账号" }; + addMemberItem.Click += addMemberItem_Click; + contextMenu.Items.Add(addMemberItem); + + MenuItem addMembersItem = new MenuItem { Header = "导入账号" }; + contextMenu.Items.Add(addMembersItem); + addMembersItem.Click += addMembersItem_Click; + + MenuItem addTeamItem = new MenuItem { Header = "新增队伍" }; + contextMenu.Items.Add(addTeamItem); + addTeamItem.Click += addTeamItem_Click; + + MenuItem editTeamItem = new MenuItem { Header = "编辑队伍" }; + contextMenu.Items.Add(editTeamItem); + editTeamItem.Click += editTeamItem_Click; + + MenuItem changeTeamItem = new MenuItem { Header = "切换队伍" }; + contextMenu.Items.Add(changeTeamItem); + changeTeamItem.MouseEnter += (sender, args) => + { + // 清空ContextMenu的items + changeTeamItem.Items.Clear(); + + // 假设你有一个字符串数组 + string[] items = TeamManager.GetAllTeamName(); + + // 遍历字符串数组,为每个元素创建一个MenuItem + for (int i = 0; i < items.Length; i++) + { + MenuItem menuItem = new MenuItem { Header = items[i] }; + menuItem.Tag = i; + menuItem.Click += (sender, args) => + { + // 输出被点击的item的index + MenuItem menuItem = sender as MenuItem; + int i = (int)menuItem.Tag; + Trace.WriteLine("Clicked item index: " + i); + AccountManager.reloadData(i); + ReloadBtn(); + }; + changeTeamItem.Items.Add(menuItem); + } + }; + + return contextMenu; + } + + private void addMemberItem_Click(object sender, RoutedEventArgs e) + { + EditMemberDialog.CreateDailog(this, null); + } + + private void addMembersItem_Click(object sender, RoutedEventArgs e) + { + AddMemebersDialog.CreateDailog(this); + } + + private void addTeamItem_Click(object sender, RoutedEventArgs e) + { + EditTeamDialog.CreateDailog(this); + } + + private void editTeamItem_Click(object sender, RoutedEventArgs e) + { + EditTeamDialog.CreateDailog(this, AccountManager.teamName); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + // 创建新的按钮 + Button newButton = new Button(); + newButton.Content = "新按钮"; + + // 将新按钮添加到布局中 + // 假设你的布局是一个名为"myLayout"的StackPanel + mLayout.Children.Add(newButton); + } + + private void Button_Click_1(object sender, RoutedEventArgs e) + { + LoginManager.LoginTest(); + } + + private void Button_Click_2(object sender, RoutedEventArgs e) + { + LoginManager.GameTest(); + } + + private void Button_Click_3(object sender, RoutedEventArgs e) + { + WebApiManager.StopListener(); + Close(); + } + + private void AirButton_Click(object sender, RoutedEventArgs e) + { + var ab = new About1(); + ab.ShowDialog(); + } + } +} \ No newline at end of file diff --git a/Manager/ADManager.cs b/Manager/ADManager.cs new file mode 100644 index 0000000..45592db --- /dev/null +++ b/Manager/ADManager.cs @@ -0,0 +1,7 @@ +namespace Zerolauncher.Manager +{ + internal class ADManager + { + + } +} diff --git a/Manager/AccountManager.cs b/Manager/AccountManager.cs new file mode 100644 index 0000000..c4fe811 --- /dev/null +++ b/Manager/AccountManager.cs @@ -0,0 +1,156 @@ + +namespace Zerolauncher.Manager +{ + internal class AccountManager + { + static int teamIndex=0; + + public static string teamName = ""; + + public static List accountsList = new List(); + + public static void initLoadData() + { + teamIndex = DataStream.dataStream.teamIndex; + var group = DataStream.dataStream.Groups[teamIndex]; + teamName = group.Name; + foreach (var acc in group.Accounts) + { + accountsList.Add(acc); + } + } + + public static void reloadData(int index = -1) + { + if (index < 0) index = teamIndex; + var group = DataStream.dataStream.Groups[index]; + if (teamIndex!=index) + { + teamIndex = index; + teamName = group.Name; + DataStream.dataStream.teamIndex = index; + DataStream.write(); + } + accountsList.Clear(); + foreach (var acc in group.Accounts) + { + accountsList.Add(acc); + } + } + + public static void saveEdit() + { + DataStream.write(); + MainWindow.Instance.ReloadBtn(); + } + + public static bool AddAccount(Account account) + { + if (TeamManager.Nick2Acc(account.nickName) != null) + { + return false; + } + var group = DataStream.dataStream.Groups[teamIndex]; + group.Accounts.Add(account); + + accountsList.Add(account); + DataStream.write(); + MainWindow.Instance.ReloadBtn(); + return true; + } + + public static bool AddAccounts(Account account) + { + if (TeamManager.Nick2Acc(account.nickName) != null) + { + return false; + } + var group = DataStream.dataStream.Groups[teamIndex]; + group.Accounts.Add(account); + + accountsList.Add(account); + //DataStream.write(); 不写 + //MainWindow.Instance.ReloadBtn(); 不更新 + return true; + } + + public static void MoveAccount(int memberId, int newIndex) + { + var group1 = DataStream.dataStream.Groups[newIndex]; + group1.Accounts.Add(accountsList[memberId]); + var group = DataStream.dataStream.Groups[teamIndex]; + group.Accounts.RemoveAt(memberId); + DataStream.write(); + MainWindow.Instance.ReloadBtn(); + } + + public static void DeleteAccount(int index) { + var group = DataStream.dataStream.Groups[teamIndex]; + group.Accounts.RemoveAt(index); + DataStream.write(); + reloadData(); + MainWindow.Instance.ReloadBtn(); + } + + public static void editTeamName(string teamName0) + { + teamName = teamName0; + var group = DataStream.dataStream.Groups[teamIndex]; + group.Name = teamName; + DataStream.write(); + } + + public static bool DeleteTeam() + { + if (DataStream.dataStream.Groups.Count == 1) + { + return false; + } + DataStream.dataStream.Groups.RemoveAt(teamIndex); + DataStream.write(); + reloadData(0); + MainWindow.Instance.ReloadBtn(); + return true; + } + + } + + internal class TeamManager + { + public static string[] GetAllTeamName() + { + string[] names = new string[DataStream.dataStream.Groups.Count]; + int i = 0; + foreach (var group in DataStream.dataStream.Groups) + { + names[i++] = group.Name; + } + return names; + } + + public static void addTeam(string teamName) + { + var group = new Group { Name = teamName, Accounts = new List { } }; + DataStream.dataStream.Groups.Add(group); + DataStream.write(); + } + + //运行时才能决定是否执行内联 + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static Account? Nick2Acc(string nickName) + { + // find and return first take + foreach (var group in DataStream.dataStream.Groups) + { + foreach (var acc in group.Accounts) + { + if (acc.nickName.Equals(nickName)) return acc; + } + } + return null; + + } + + } + +} diff --git a/Manager/DataStream.cs b/Manager/DataStream.cs new file mode 100644 index 0000000..3a8d2f0 --- /dev/null +++ b/Manager/DataStream.cs @@ -0,0 +1,89 @@ +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Text; +using Newtonsoft.Json; + +namespace Zerolauncher.Manager +{ + class DataStream + { + private const string path = "user.data"; + public static Data dataStream; + + public static void Load() + { + if (File.Exists(path)) + { + dataStream = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Decompress(File.ReadAllBytes(path)))); + } + else + { + dataStream = JsonConvert.DeserializeObject( + "{\"teamIndex\":0,\"Groups\":[{\"Name\":\"队伍1\", \"Accounts\": [{\"providerId\":0,\"userName\":\"test\",\"userPWD\":\"test\",\"nickName\":\"测试账号\",\"serverId\":\"1\"}]}]}"); + } + + } + + public static void write() + { + string? data = JsonConvert.SerializeObject(dataStream); + Trace.WriteLine(data); + if (data == null) return; + File.WriteAllBytes(path, CompressBytes(Encoding.UTF8.GetBytes(data))); + } + + //压缩字节 + //1.创建压缩的数据流 + //2.设定compressStream为存放被压缩的文件流,并设定为压缩模式 + //3.将需要压缩的字节写到被压缩的文件流 + public static byte[] CompressBytes(byte[] bytes) + { + using (MemoryStream compressStream = new MemoryStream()) + { + using (var zipStream = new GZipStream(compressStream, CompressionMode.Compress)) + zipStream.Write(bytes, 0, bytes.Length); + return compressStream.ToArray(); + } + } + + //解压缩字节 + //1.创建被压缩的数据流 + //2.创建zipStream对象,并传入解压的文件流 + //3.创建目标流 + //4.zipStream拷贝到目标流 + //5.返回目标流输出字节 + public static byte[] Decompress(byte[] bytes) + { + using (var compressStream = new MemoryStream(bytes)) + { + using (var zipStream = new GZipStream(compressStream, CompressionMode.Decompress)) + { + using (var resultStream = new MemoryStream()) + { + zipStream.CopyTo(resultStream); + return resultStream.ToArray(); + } + } + } + } + } + + public class Account + { + public int providerId; + public string userName, userPWD, nickName, serverId; + } + + class Group + { + public string Name; + public List Accounts; + } + + struct Data + { + public int teamIndex; + public List Groups; + } +} \ No newline at end of file diff --git a/Manager/DownloadManager.cs b/Manager/DownloadManager.cs new file mode 100644 index 0000000..b2ce01d --- /dev/null +++ b/Manager/DownloadManager.cs @@ -0,0 +1,374 @@ +using System.Net; +using System.IO; + +namespace Zerolauncher.Manager +{ + + public class DownloadManager + { + /// + /// 主线程 + /// + private SynchronizationContext _mainThreadSynContext; + + /// + /// 下载网址 + /// + public string Url; + + public event Action OnError; + + private static object errorlock = new object(); + + /// + /// 主要用于关闭线程 + /// + private bool _isDownload = false; + public DownloadManager(string url) + { + // 主线程赋值 + _mainThreadSynContext = SynchronizationContext.Current; + // 突破Http协议的并发连接数限制 + ServicePointManager.DefaultConnectionLimit = 512; + + Url = url; + } + /// + /// 查询文件大小 + /// + /// + public long GetFileSize() + { + HttpWebRequest request; + HttpWebResponse response; + try + { + request = (HttpWebRequest)WebRequest.CreateHttp(new Uri(Url)); + request.Method = "HEAD"; + response = (HttpWebResponse)request.GetResponse(); + // 获得文件长度 + long contentLength = response.ContentLength; + + response.Close(); + request.Abort(); + + return contentLength; + } + catch (Exception ex) + { + onError(ex); + // throw; + return -1; + } + } + /// + /// 异步查询文件大小 + /// + /// + public void GetFileSizeAsyn(Action onTrigger = null) + { + ThreadStart threadStart = new ThreadStart(() => + { + PostMainThreadAction(onTrigger, GetFileSize()); + }); + Thread thread = new Thread(threadStart); + thread.Start(); + } + /// + /// 多线程下载文件至本地 + /// + /// 线程总数 + /// 保存文件路径 + /// 下载过程回调(已下载文件大小、总文件大小) + /// 下载完毕回调(下载文件数据) + public void DownloadToFile(int threadCount, string filePath, Action onDownloading = null, Action onTrigger = null) + { + _isDownload = true; + + long csize = 0; //已下载大小 + int ocnt = 0; //完成线程数 + + + // 下载逻辑 + GetFileSizeAsyn((size) => + { + if (size == -1) return; + // 准备工作 + var tempFilePaths = new string[threadCount]; + var tempFileFileStreams = new FileStream[threadCount]; + var dirPath = Path.GetDirectoryName(filePath); + var fileName = Path.GetFileName(filePath); + // 下载根目录不存在则创建 + if (!Directory.Exists(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + // 查看下载临时文件是否可以继续断点续传 + var fileInfos = new DirectoryInfo(dirPath).GetFiles(fileName + "*.temp"); + if (fileInfos.Length != threadCount) + { + // 下载临时文件数量不相同,则清理 + foreach (var info in fileInfos) + { + info.Delete(); + } + } + // 创建下载临时文件,并创建文件流 + for (int i = 0; i < threadCount; i++) + { + tempFilePaths[i] = filePath + i + ".temp"; + if (!File.Exists(tempFilePaths[i])) + { + File.Create(tempFilePaths[i]).Dispose(); + } + tempFileFileStreams[i] = File.OpenWrite(tempFilePaths[i]); + tempFileFileStreams[i].Seek(tempFileFileStreams[i].Length, System.IO.SeekOrigin.Current); + + csize += tempFileFileStreams[i].Length; + } + // 单线程下载过程回调函数 + Action t_onDownloading = (index, rsize, rbytes, data) => + { + csize += rsize; + tempFileFileStreams[index].Write(rbytes, 0, (int)rsize); + PostMainThreadAction(onDownloading, csize, size); + }; + // 单线程下载完毕回调函数 + Action t_onTrigger = (index, data) => + { + // 关闭文件流 + tempFileFileStreams[index].Close(); + ocnt++; + if (ocnt >= threadCount) + { + // 将临时文件转为下载文件 + if (!File.Exists(filePath)) + { + File.Create(filePath).Dispose(); + } + else + { + File.WriteAllBytes(filePath, new byte[] { }); + } + FileStream fs = File.OpenWrite(filePath); + fs.Seek(fs.Length, System.IO.SeekOrigin.Current); + foreach (var tempPath in tempFilePaths) + { + var tempData = File.ReadAllBytes(tempPath); + fs.Write(tempData, 0, tempData.Length); + File.Delete(tempPath); + } + fs.Close(); + PostMainThreadAction(onTrigger, File.ReadAllBytes(filePath)); + } + }; + // 分割文件尺寸,多线程下载 + long[] sizes = SplitFileSize(size, threadCount); + for (int i = 0; i < sizes.Length; i = i + 2) + { + long from = sizes[i]; + long to = sizes[i + 1]; + + // 断点续传 + from += tempFileFileStreams[i / 2].Length; + if (from >= to) + { + t_onTrigger(i / 2, null); + continue; + } + + _threadDownload(i / 2, from, to, t_onDownloading, t_onTrigger); + } + }); + } + /// + /// 多线程下载文件至内存 + /// + /// 线程总数 + /// 下载过程回调(已下载文件大小、总文件大小) + /// 下载完毕回调(下载文件数据) + public void DownloadToMemory(int threadCount, Action onDownloading = null, Action onTrigger = null) + { + _isDownload = true; + + long csize = 0; // 已下载大小 + int ocnt = 0; // 完成线程数 + byte[] cdata; // 已下载数据 + // 下载逻辑 + GetFileSizeAsyn((size) => + { + cdata = new byte[size]; + // 单线程下载过程回调函数 + Action t_onDownloading = (index, rsize, rbytes, data) => + { + csize += rsize; + PostMainThreadAction(onDownloading, csize, size); + }; + // 单线程下载完毕回调函数 + Action t_onTrigger = (index, data) => + { + long dIndex = (long)Math.Ceiling((double)(size * index / threadCount)); + Array.Copy(data, 0, cdata, dIndex, data.Length); + + ocnt++; + if (ocnt >= threadCount) + { + PostMainThreadAction(onTrigger, cdata); + } + }; + // 分割文件尺寸,多线程下载 + long[] sizes = SplitFileSize(size, threadCount); + for (int i = 0; i < sizes.Length; i = i + 2) + { + long from = sizes[i]; + long to = sizes[i + 1]; + _threadDownload(i / 2, from, to, t_onDownloading, t_onTrigger); + } + }); + } + /// + /// 单线程下载 + /// + /// 线程ID + /// 下载起始位置 + /// 下载结束位置 + /// 下载过程回调(线程ID、单次下载数据大小、单次下载数据缓存区、已下载文件数据) + /// 下载完毕回调(线程ID、下载文件数据) + private void _threadDownload(int index, long from, long to, Action onDownloading = null, Action onTrigger = null) + { + Thread thread = new Thread(new ThreadStart(() => + { + try + { + var request = (HttpWebRequest)WebRequest.Create(new Uri(Url)); + request.AddRange(from, to); + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + Stream ns = response.GetResponseStream(); + + byte[] rbytes = new byte[8 * 1024]; + int rSize = 0; + MemoryStream ms = new MemoryStream(); + while (true) + { + if (!_isDownload) return; + rSize = ns.Read(rbytes, 0, rbytes.Length); + if (rSize <= 0) break; + ms.Write(rbytes, 0, rSize); + if (onDownloading != null) onDownloading(index, rSize, rbytes, ms.ToArray()); + } + + ns.Close(); + response.Close(); + request.Abort(); + + if (ms.Length == (to - from) + 1) + { + if (onTrigger != null) onTrigger(index, ms.ToArray()); + } + else + { + lock (errorlock) + { + if (_isDownload) + { + onError(new Exception("文件大小校验不通过")); + } + } + } + + } + catch (Exception ex) + { + onError(ex); + } + + + })); + thread.Start(); + } + + public void Close() + { + _isDownload = false; + } + + /// + /// 分割文件大小 + /// + /// + private long[] SplitFileSize(long size, int count) + { + long[] result = new long[count * 2]; + for (int i = 0; i < count; i++) + { + long from = (long)Math.Ceiling((double)(size * i / count)); + long to = (long)Math.Ceiling((double)(size * (i + 1) / count)) - 1; + result[i * 2] = from; + result[i * 2 + 1] = to; + } + + return result; + } + + private void onError(Exception ex) + { + Close(); + PostMainThreadAction(OnError, ex); + } + + /// + /// 通知主线程回调 + /// + private void PostMainThreadAction(Action action) + { + _mainThreadSynContext.Post(new SendOrPostCallback((o) => + { + Action e = (Action)o.GetType().GetProperty("action").GetValue(o); + if (e != null) e(); + }), new { action = action }); + } + private void PostMainThreadAction(Action action, T arg1) + { + _mainThreadSynContext.Post(new SendOrPostCallback((o) => + { + Action e = (Action)o.GetType().GetProperty("action").GetValue(o); + T t1 = (T)o.GetType().GetProperty("arg1").GetValue(o); + if (e != null) e(t1); + }), new { action = action, arg1 = arg1 }); + } + public void PostMainThreadAction(Action action, T1 arg1, T2 arg2) + { + _mainThreadSynContext.Post(new SendOrPostCallback((o) => + { + Action e = (Action)o.GetType().GetProperty("action").GetValue(o); + T1 t1 = (T1)o.GetType().GetProperty("arg1").GetValue(o); + T2 t2 = (T2)o.GetType().GetProperty("arg2").GetValue(o); + if (e != null) e(t1, t2); + }), new { action = action, arg1 = arg1, arg2 = arg2 }); + } + public void PostMainThreadAction(Action action, T1 arg1, T2 arg2, T3 arg3) + { + _mainThreadSynContext.Post(new SendOrPostCallback((o) => + { + Action e = (Action)o.GetType().GetProperty("action").GetValue(o); + T1 t1 = (T1)o.GetType().GetProperty("arg1").GetValue(o); + T2 t2 = (T2)o.GetType().GetProperty("arg2").GetValue(o); + T3 t3 = (T3)o.GetType().GetProperty("arg3").GetValue(o); + if (e != null) e(t1, t2, t3); + }), new { action = action, arg1 = arg1, arg2 = arg2, arg3 = arg3 }); + } + public void PostMainThreadAction(Action action, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + _mainThreadSynContext.Post(new SendOrPostCallback((o) => + { + Action e = (Action)o.GetType().GetProperty("action").GetValue(o); + T1 t1 = (T1)o.GetType().GetProperty("arg1").GetValue(o); + T2 t2 = (T2)o.GetType().GetProperty("arg2").GetValue(o); + T3 t3 = (T3)o.GetType().GetProperty("arg3").GetValue(o); + T4 t4 = (T4)o.GetType().GetProperty("arg4").GetValue(o); + if (e != null) e(t1, t2, t3, t4); + }), new { action = action, arg1 = arg1, arg2 = arg2, arg3 = arg3, arg4 = arg4 }); + } + } +} diff --git a/Manager/EngineManager.cs b/Manager/EngineManager.cs new file mode 100644 index 0000000..8d3a909 --- /dev/null +++ b/Manager/EngineManager.cs @@ -0,0 +1,246 @@ +using System.Diagnostics; +using System.IO; +using System.Security.Cryptography; +using System.Windows; +using Zerolauncher.Defender; + +namespace Zerolauncher.Manager +{ + internal class EngineManager + { + private static Dictionary mGame = new Dictionary(); + + //运行时才能决定是否执行内联 + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static string AccToKey(Account account) + { + return string.Format("{0}{1}{2}", account.providerId, account.serverId, account.userName); + } + + public static bool CreateGame(Account account) + { + var key = AccToKey(account); + if (mGame.ContainsKey(key)) { return false; } + if (EngineCacheSha.errorCode != 0) { + switch (EngineCacheSha.errorCode) + { + case 1: + MessageBox.Show("发生网络错误==EMS。\n 请检查网络", "错误", MessageBoxButton.OKCancel, MessageBoxImage.Error); + break; + case 2: + MessageBox.Show("发生游戏服务错误==EMS。\n 请联系管理员", "错误", MessageBoxButton.OKCancel, MessageBoxImage.Error); + break; + default: + MessageBox.Show("发生未知错误==EMS。\n 请联系管理员", "错误", MessageBoxButton.OKCancel, MessageBoxImage.Error); + break; + } + return false; + } + try + { + mGame[key] = new SingleGame(account); + }catch (Exception _ex) + { + MessageBox.Show("发生错误!\n如重复发生此错误,\n请重新下载登陆器文件或联系管理员。", "错误", MessageBoxButton.OKCancel, MessageBoxImage.Error); + return false; + } + return true; + } + + public static bool CreateGame(int memberId) + { + var account = AccountManager.accountsList[memberId]; + var key = AccToKey(account); + if (mGame.ContainsKey(key)) { return false; } + mGame[key] = new SingleGame(account); + return true; + } + + public static int CheckGameState(Account account) + { + var key = AccToKey(account); + if (mGame.ContainsKey(key)) { return mGame[key].mHandle; } + return -1; + } + + public static short TurnGameSizeMini(Account account) + { + var key = AccToKey(account); + if (mGame.ContainsKey(key)) + { + var game = mGame[key]; + if (game.mHandle != 0) + { + game.Send(StaticHandleS.MiniSize); + return 2; + } + return 1; + } + return 0; + } + + public static short TurnGameSizeNormal(Account account) + { + var key = AccToKey(account); + if (mGame.ContainsKey(key)) + { + var game = mGame[key]; + if (game.mHandle != 0) + { + game.Send(StaticHandleS.NormalSize); + return 2; + } + return 1; + } + return 0; + } + + public static bool ExitGame(Account account) + { + var key = AccToKey(account); + if (!mGame.ContainsKey(key)) { return false; } + mGame[key].Send(StaticHandleS.CloseGame); + return true; + } + + public static void OnGameExit(Account account) + { + mGame.Remove(AccToKey(account)); + } + + public static bool CheckEmpy() + { + return mGame.Count == 0; + } + } + + class SingleGame + { + Process process; + + string? restartUrl; + + public int mHandle; + + public Account account; + + public SingleGame(Account acc) + { + restartUrl = null; + this.account = acc; + mHandle = 0; + process = EngineShell.CheckEngineSafe(); + process.OutputDataReceived += Handle; + process.Exited += Process_Exited; + process.Start(); + process.BeginOutputReadLine(); // 开始异步读取 + } + + private void CreateProcess() + { + process = EngineShell.CheckEngineSafe(); + process.OutputDataReceived += Handle; + process.Exited += Process_Exited; + process.Start(); + process.BeginOutputReadLine(); // 开始异步读取 + } + + private void Process_Exited(object? sender, EventArgs e) + { + Trace.WriteLine( + $"Exit time : {process.ExitTime}\n" + + $"Exit code : {process.ExitCode}\n" + + $"Elapsed time : {Math.Round((process.ExitTime - process.StartTime).TotalMilliseconds)}"); + Trace.WriteLine($"进程已退出:{account.nickName}"); + if ( restartUrl == null ) + { + EngineManager.OnGameExit(account); + return; + } + CreateProcess(); + } + + private void Handle(object sender, DataReceivedEventArgs e) + { + var lines = e.Data == null? [""] : e.Data.Split(" "); + switch (lines[0]) + { + case StaticHandleC.StartDone: + if(restartUrl == null) + { + Send($"{StaticHandleS.ShowWindow} {ServicesStaticInfo.ServicesName[account.providerId]}-{account.nickName}"); + _ = LoginManager.DoLogin(this); + } + else + { + Send($"{StaticHandleS.ShowWindow} {ServicesStaticInfo.ServicesName[account.providerId]}-{account.nickName}"); + Send($"{StaticHandleS.GameSa} {restartUrl}"); + restartUrl = null; + } + break; + case StaticHandleC.GameDone: + mHandle = int.Parse(lines[1]); + break; + case StaticHandleC.BrowserDone: + restartUrl = lines[1]; + Send(StaticHandleS.CloseGame); + break; + } + } + + public bool Send(string msg) + { + if(process.HasExited) { return false; } + process.StandardInput.WriteLine(msg); + return true; + } + + } + + public class FileReadException : Exception + { + public FileReadException(string message) : base(message) + { + } + } + + class EngineShell + { + static bool is_check = false; + const string engine_file = @"ZeroEngine.exe"; + public static Process CheckEngineSafe() + { + if (is_check && EngineManager.CheckEmpy()) + { + string? now_bit; + using (SHA256 sha256 = SHA256.Create()) + { + using (FileStream fileStream = File.OpenRead(engine_file)) + { + byte[] hashBytes = sha256.ComputeHash(fileStream); + now_bit = BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } + if (EngineCacheSha.Get() != now_bit) + { + throw new FileReadException("无法读取文件内容"); + } + } + is_check = true; + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = engine_file, + Arguments = StaticHandleA.GameMode, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardInput = true, + RedirectStandardOutput = true + }, + EnableRaisingEvents = true + }; + return process; + } + } +} diff --git a/Manager/HashHelper.cs b/Manager/HashHelper.cs new file mode 100644 index 0000000..717c39e --- /dev/null +++ b/Manager/HashHelper.cs @@ -0,0 +1,201 @@ +using System.Text; + +namespace Zerolauncher.Manager +{ + /// + /// 提供用于计算指定文件哈希值的方法 + /// 例如计算文件的MD5值: + /// + /// String hashMd5=HashHelper.ComputeMD5("MyFile.txt"); + /// + /// + /// 例如计算文件的CRC32值: + /// + /// String hashCrc32 = HashHelper.ComputeCRC32("MyFile.txt"); + /// + /// + /// 例如计算文件的SHA1值: + /// + /// String hashSha1 =HashHelper.ComputeSHA1("MyFile.txt"); + /// + /// + /// + public sealed class HashHelper + { + /// + /// 计算指定文件的MD5值 + /// + /// 指定文件的完全限定名称 + /// 返回值的字符串形式 + public static String ComputeMD5(String fileName) + { + String hashMD5 = String.Empty; + //检查文件是否存在,如果文件存在则进行计算,否则返回空值 + if (System.IO.File.Exists(fileName)) + { + using (System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + { + //计算文件的MD5值 + System.Security.Cryptography.MD5 calculator = System.Security.Cryptography.MD5.Create(); + Byte[] buffer = calculator.ComputeHash(fs); + calculator.Clear(); + //将字节数组转换成十六进制的字符串形式 + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < buffer.Length; i++) + { + stringBuilder.Append(buffer[i].ToString("x2")); + } + hashMD5 = stringBuilder.ToString(); + }//关闭文件流 + }//结束计算 + return hashMD5; + }//ComputeMD5 + /// + /// 计算指定文件的CRC32值 + /// + /// 指定文件的完全限定名称 + /// 返回值的字符串形式 + public static String ComputeCRC32(String fileName) + { + String hashCRC32 = String.Empty; + //检查文件是否存在,如果文件存在则进行计算,否则返回空值 + if (System.IO.File.Exists(fileName)) + { + using (System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + { + //计算文件的CSC32值 + Crc32 calculator = new Crc32(); + Byte[] buffer = calculator.ComputeHash(fs); + calculator.Clear(); + //将字节数组转换成十六进制的字符串形式 + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < buffer.Length; i++) + { + stringBuilder.Append(buffer[i].ToString("x2")); + } + hashCRC32 = stringBuilder.ToString(); + }//关闭文件流 + } + return hashCRC32; + }//ComputeCRC32 + /// + /// 计算指定文件的SHA1值 + /// + /// 指定文件的完全限定名称 + /// 返回值的字符串形式 + public static String ComputeSHA1(String fileName) + { + String hashSHA1 = String.Empty; + //检查文件是否存在,如果文件存在则进行计算,否则返回空值 + if (System.IO.File.Exists(fileName)) + { + using (System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + { + //计算文件的SHA1值 + System.Security.Cryptography.SHA1 calculator = System.Security.Cryptography.SHA1.Create(); + Byte[] buffer = calculator.ComputeHash(fs); + calculator.Clear(); + //将字节数组转换成十六进制的字符串形式 + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < buffer.Length; i++) + { + stringBuilder.Append(buffer[i].ToString("x2")); + } + hashSHA1 = stringBuilder.ToString(); + }//关闭文件流 + } + return hashSHA1; + }//ComputeSHA1 + }//end class: HashHelper + /// + /// 提供 CRC32 算法的实现 + /// + public class Crc32 : System.Security.Cryptography.HashAlgorithm + { + public const UInt32 DefaultPolynomial = 0xedb88320; + public const UInt32 DefaultSeed = 0xffffffff; + private UInt32 hash; + private UInt32 seed; + private UInt32[] table; + private static UInt32[] defaultTable; + public Crc32() + { + table = InitializeTable(DefaultPolynomial); + seed = DefaultSeed; + Initialize(); + } + public Crc32(UInt32 polynomial, UInt32 seed) + { + table = InitializeTable(polynomial); + this.seed = seed; + Initialize(); + } + public override void Initialize() + { + hash = seed; + } + protected override void HashCore(byte[] buffer, int start, int length) + { + hash = CalculateHash(table, hash, buffer, start, length); + } + protected override byte[] HashFinal() + { + byte[] hashBuffer = UInt32ToBigEndianBytes(~hash); + this.HashValue = hashBuffer; + return hashBuffer; + } + public static UInt32 Compute(byte[] buffer) + { + return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer, 0, buffer.Length); + } + public static UInt32 Compute(UInt32 seed, byte[] buffer) + { + return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer, 0, buffer.Length); + } + public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer) + { + return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); + } + private static UInt32[] InitializeTable(UInt32 polynomial) + { + if (polynomial == DefaultPolynomial && defaultTable != null) + { + return defaultTable; + } + UInt32[] createTable = new UInt32[256]; + for (int i = 0; i < 256; i++) + { + UInt32 entry = (UInt32)i; + for (int j = 0; j < 8; j++) + { + if ((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; + else + entry = entry >> 1; + } + createTable[i] = entry; + } + if (polynomial == DefaultPolynomial) + { + defaultTable = createTable; + } + return createTable; + } + private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, byte[] buffer, int start, int size) + { + UInt32 crc = seed; + for (int i = start; i < size; i++) + { + unchecked + { + crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff]; + } + } + return crc; + } + private byte[] UInt32ToBigEndianBytes(UInt32 x) + { + return new byte[] { (byte)((x >> 24) & 0xff), (byte)((x >> 16) & 0xff), (byte)((x >> 8) & 0xff), (byte)(x & 0xff) }; + } + }//end class: Crc32 +} diff --git a/Manager/LoginManager.cs b/Manager/LoginManager.cs new file mode 100644 index 0000000..d38be5e --- /dev/null +++ b/Manager/LoginManager.cs @@ -0,0 +1,146 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Http; + +namespace Zerolauncher.Manager +{ + internal class LoginManager + { + //private static readonly HttpClient client = new HttpClient(); + + public static async Task SendPostRequest() + { + var client = new HttpClient(); + client.DefaultRequestVersion = HttpVersion.Version20; + client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + var values = new Dictionary + { + { "thing1", "hello" }, + { "thing2", "world" } + }; + var content = new FormUrlEncodedContent(values); + var response = await client.PostAsync("http://www.example.com/recepticle.aspx", content); + var responseString = await response.Content.ReadAsStringAsync(); + + Console.WriteLine(responseString); + } + + public static async Task LoginTest() + { + var client = new HttpClient(); + client.DefaultRequestVersion = HttpVersion.Version20; + client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + var values = new Dictionary + { + { "thing1", "hello" }, + { "thing2", "world" } + }; + var content = new FormUrlEncodedContent(values); + var response = await client.PostAsync("http://www.example.com/recepticle.aspx", content); + var responseString = await response.Content.ReadAsStringAsync(); + + Console.WriteLine(responseString); + } + + public static void GameTest() + { + //Account account = new Account + //{ + // userName = "b1783488228", + // userPWD = "565656", + // serverId = "179" + //}; + //SingleGame game = new SingleGame(account); + } + + public static async Task DoLogin(SingleGame game) + { + var client = new HttpClient(); + string? need_web = null; + client.Timeout = TimeSpan.FromSeconds(10); + switch (game.account.providerId) + { + case 0: + { + var values = new Dictionary + { + { "loginFrom", "uframe" }, + { "postLoginHandler", "default" }, + { "layoutSelfAdapting", "true" }, + { "externalLogin", "qq" }, + { "displayMode", "popup" }, + { "layout", "vertical" }, + { "appId", "www_home" }, + { "mainDivId", "popup_login_div" }, + { "includeFcmInfo", "false" }, + { "userNameLabel", "4399用户名" }, + { "userNameTip", "请输入4399用户名" }, + { "welcomeTip", "欢迎回到4399" }, + { "username", game.account.userName }, + { "password", game.account.userPWD } + }; + + var content = new FormUrlEncodedContent(values); + HttpResponseMessage? response = null; + try + { + response = await client.PostAsync("http://ptlogin.4399.com/ptlogin/login.do?v=1", content); + } + catch (Exception ex) + { + game.Send($"{StaticHandleS.HintText} 网络发生错误,类型:{ex.GetType().Name},消息:{ex.Message}"); + need_web = $"http://web.4399.com/stat/togame.php?target=ddt&server_id=S{game.account.serverId}"; + break; + } + var responseString = await response.Content.ReadAsStringAsync(); + // eventHandles.__errorCallback('密码错误'); + if (responseString.Contains("eventHandles.__errorCallback")) + { + game.Send($"{StaticHandleS.HintText} 登录异常:{responseString.Split("eventHandles.__errorCallback('")[1].Split("'")[0]}"); + need_web = $"http://web.4399.com/stat/togame.php?target=ddt&server_id=S{game.account.serverId}"; + break; + } + if(!game.Send($"{StaticHandleS.HintText} 登录成功,开始解析iframe")) return; + } + { + HttpResponseMessage? response = null; + try + { + response = await client.PostAsync("http://web.4399.com/stat/togame.php?target=ddt&server_id=S" + game.account.serverId, null); + } + catch (Exception ex) + { + game.Send($"{StaticHandleS.HintText} 网络发生错误,类型:{ex.GetType().Name},消息:{ex.Message}"); + need_web = $"http://web.4399.com/stat/togame.php?target=ddt&server_id=S{game.account.serverId}"; + break; + } + var responseString = await response.Content.ReadAsStringAsync(); + if (!responseString.Contains("name=\"game_box\"")) + { + game.Send($"{StaticHandleS.HintText} 解析异常:服务器返回数据不正确"); + need_web = $"http://web.4399.com/stat/togame.php?target=ddt&server_id=S{game.account.serverId}"; + break; + } + responseString = responseString.Split("name=\"game_box\"")[1].Split("src=\"")[1].Split("\"")[0]; + response = await client.PostAsync(responseString, null); + responseString = await response.Content.ReadAsStringAsync(); + responseString = "http://" + response.RequestMessage.RequestUri.ToString().Split("/")[2] + "/" + responseString.Split("movie\" value='")[1].Split("'")[0]; + Trace.WriteLine(responseString); + game.Send($"{StaticHandleS.GameSa} {responseString}"); + } + break; + default: + game.Send($"{StaticHandleS.HintText} 错误。未适配的运营商:{game.account.serverId}"); + break; + } + client.Dispose(); + if (need_web == null) return; + for (int i = 6; i > 0; i--) + { + await Task.Delay(1000); + if (!game.Send($"{StaticHandleS.HintText} 自动登录失败,将在{i}后启用网页登录,关闭窗口取消")) { return; } + } + game.Send($"{StaticHandleS.UseBrowser} {need_web}"); + } + } +} diff --git a/Manager/SpaceEncoder.cs b/Manager/SpaceEncoder.cs new file mode 100644 index 0000000..50138a2 --- /dev/null +++ b/Manager/SpaceEncoder.cs @@ -0,0 +1,77 @@ +using System.Text; + +namespace Zerolauncher.Manager +{ + class SpaceEncoder + { + const int mini_index = 48; + const int max_index = 70; + const int sec_index = 60; + static readonly List map = [ + (char)8192, + (char)8194, + (char)8196, + (char)8197, + (char)8198, + (char)8199, + (char)8200, + (char)8201, + (char)8202, + (char)8239, + (char)8287, + (char)32 + ]; + + public static string? Encode(string hex) + { + StringBuilder sb = new StringBuilder(); + foreach (char c in hex) + { + if (mini_index > c && max_index < c) + { + return null; + } + if (c < sec_index) + { + sb.Append(map[c - mini_index]); + } + else + { + sb.Append(map[11]); + sb.Append(map[c - sec_index]); + } + } + return sb.ToString(); + } + + public static string? Decode(string hex, out int counter) + { + StringBuilder sb = new StringBuilder(); + bool double_char = false; + counter = 0; + foreach (char c in hex) + { + counter++; + if (!map.Contains(c)) return null; + if (c != map[11]) + { + int j = map.FindIndex(item => item.Equals(c)); + if (double_char) + { + int k = j + sec_index; + if (k > max_index) return null; + sb.Append((char)k); + double_char = false; + } + else sb.Append((char)(j + mini_index)); + } + else + { + if (double_char) return null; + double_char = true; + } + } + return sb.ToString(); + } + } +} diff --git a/Manager/UpDateManager.cs b/Manager/UpDateManager.cs new file mode 100644 index 0000000..d76cad0 --- /dev/null +++ b/Manager/UpDateManager.cs @@ -0,0 +1,129 @@ +using System.IO; +using System.Net; +using System.Net.Http; +using Zerolauncher.Defender; + +namespace Zerolauncher.Manager +{ + + class UpDateManager + { + + public static async Task TakeQMessage() + { + var client = new HttpClient(); + client.DefaultRequestVersion = HttpVersion.Version20; + client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (iPad; U; CPU OS 6_0 like Mac OS X; zh-CN; iPad2)"); + HttpResponseMessage response; + try + { + client.Timeout = TimeSpan.FromMinutes(3); + response = await client.GetAsync($"https://sharechain.qq.com/13111bbd6ffbffa3057878431bef103e"); + }catch (Exception _ex) + { + EngineCacheSha.errorCode = 1; + return; + } + var responseString = await response.Content.ReadAsStringAsync(); + responseString = responseString.Split("
")[1].Split("")[0].Replace("
", "").Replace("\n", "").Replace("\t", "").Replace("\r", "").Replace("'", ""); + var arr = responseString.Split("
").Last(); + + if (arr.Length < 90) + { + EngineCacheSha.errorCode = 2; + return; + } + int count; + var sha = SpaceEncoder.Decode(arr[..86], out count); + if (sha == null) + { + //Console.WriteLine($"faile: {count}"); + EngineCacheSha.errorCode = 2; + return; + } + EngineCacheSha.Put(sha); + } + + public async Task GetTask() + { + var client = new HttpClient(); + client.DefaultRequestVersion = HttpVersion.Version20; + client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (iPad; U; CPU OS 6_0 like Mac OS X; zh-CN; iPad2)"); + var response = await client.GetAsync("https://sharechain.qq.com/13111bbd6ffbffa3057878431bef103e"); + var responseString = await response.Content.ReadAsStringAsync(); + if (responseString.Contains("
")) + { + responseString = responseString.Split("
")[1].Split("")[0].Replace("
", "").Replace("\n", "").Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("'", ""); + Console.WriteLine(responseString.Replace("
", "\n")); + var arr = responseString.Split("
"); + int main_version, engine_version, sa_version; + if (int.TryParse(arr[0], out main_version) && int.TryParse(arr[1], out engine_version)) + { + + } + } + // todo open faile update dialog + } + + public static void DownLoad(string Url, string FileName) + { + bool Value = false; + WebResponse response = null; + Stream stream = null; + + try + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url); + + response = request.GetResponse(); + stream = response.GetResponseStream(); + + if (!response.ContentType.ToLower().StartsWith("text/")) + { + Value = SaveBinaryFile(response, FileName); + + } + + } + catch (Exception err) + { + string aa = err.ToString(); + } + + } + + private static bool SaveBinaryFile(WebResponse response, string FileName) + { + bool Value = true; + byte[] buffer = new byte[1024]; + + try + { + if (File.Exists(FileName)) + File.Delete(FileName); + Stream outStream = File.Create(FileName); + Stream inStream = response.GetResponseStream(); + + int l; + do + { + l = inStream.Read(buffer, 0, buffer.Length); + if (l > 0) + outStream.Write(buffer, 0, l); + } + while (l > 0); + + outStream.Close(); + inStream.Close(); + } + catch + { + Value = false; + } + return Value; + } + + } +} diff --git a/Manager/VersionManager.cs b/Manager/VersionManager.cs new file mode 100644 index 0000000..cfe7e64 --- /dev/null +++ b/Manager/VersionManager.cs @@ -0,0 +1,8 @@ +namespace Zerolauncher.Manager +{ + class VersionManager + { + public static int GetMainVer() { return 10001; } + + } +} diff --git a/Manager/WebApiManager.cs b/Manager/WebApiManager.cs new file mode 100644 index 0000000..c4b9703 --- /dev/null +++ b/Manager/WebApiManager.cs @@ -0,0 +1,159 @@ +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +namespace Zerolauncher.Manager +{ + internal class WebApiManager + { + static HttpListener listener = new HttpListener(); + static CancellationTokenSource cts = new CancellationTokenSource(); + public static async Task StartListener() + { + listener.Prefixes.Add("http://127.0.0.1:7233/"); + listener.Start(); + Trace.WriteLine("Listening..."); + + while (!cts.Token.IsCancellationRequested) + { + HttpListenerContext context = await listener.GetContextAsync(); + HttpListenerRequest request = context.Request; + HttpListenerResponse response = context.Response; + var raw = request.RawUrl; + ResponesBody? rb = null; + if (raw != null && raw.Contains("?")) + { + var lines = raw.Split("?"); + var args = request.QueryString; + var nick = args["name"]; + if (nick != null) + { + var acc = TeamManager.Nick2Acc(nick); + if (acc != null) + { + switch (lines[0]) + { + case "/CloseGame": + { + if (EngineManager.ExitGame(acc)) + { + rb = new ResponesBody(1, 0, $"Account [{nick}] successful to close."); + } + else + { + rb = new ResponesBody($"Account [{nick}] is unluacher."); + break; + } + } + break; + case "/ToNormalSize": + { + switch (EngineManager.TurnGameSizeNormal(acc)) + { + case 2: + rb = new ResponesBody(1, 0, $"Account [{nick}] now is normal size."); + break; + case 1: + rb = new ResponesBody(0, 0, $"Account [{nick}] is loading, please wait."); + break; + case 0: + rb = new ResponesBody($"Account [{nick}] is unluacher."); + break; + } + } + break; + case "/ToMiniSize": + { + switch (EngineManager.TurnGameSizeMini(acc)) + { + case 2: + rb = new ResponesBody(1, 0, $"Account [{nick}] now is mini size."); + break; + case 1: + rb = new ResponesBody(0, 0, $"Account [{nick}] is loading, please wait."); + break; + case 0: + rb = new ResponesBody($"Account [{nick}] is unluacher."); + break; + } + } + break; + case "/LoginGame": + { + if (EngineManager.CreateGame(acc)) + { + rb = new ResponesBody(0, 0, $"Account [{nick}] successful to start."); + } + else + { + int hwnd = EngineManager.CheckGameState(acc); + if (hwnd <= 0) + { + rb = new ResponesBody(0, 0, $"Account [{nick}] is loading, please wait."); + } + else + { + rb = new ResponesBody(1, hwnd, $"Account [{nick}] now is running."); + } + } + } + break; + default: + rb = new ResponesBody($"commad [{lines[0]}] undefind."); + break; + } + } + else + { + rb = new ResponesBody($"Account [{nick}] undefind."); + } + } + else + { + rb = new ResponesBody("args is missing."); + } + + } + if (rb == null) + { + rb = new ResponesBody(); + } + byte[] buffer = Encoding.UTF8.GetBytes(rb.Encode()); + + response.ContentLength64 = buffer.Length; + Stream output = response.OutputStream; + await output.WriteAsync(buffer, 0, buffer.Length); + output.Close(); + } + listener.Stop(); + } + + public static void StopListener() { cts.Cancel(); } + } + + class ResponesBody + { + public int status; + public int hwnd; + public string message; + + public ResponesBody(string message) + { + this.status = -1; + this.hwnd = 0; + this.message = message; + } + + public ResponesBody(int status=-1, int hwnd=0, string message= "Hello, World!") + { + this.status = status; + this.hwnd = hwnd; + this.message = message; + } + + public string Encode() + { + return $"{{\"state\": {status}, \"hwnd\": {hwnd}, \"message\": \"{message}\"}}"; + } + } +} diff --git a/ServicesStaticInfo.cs b/ServicesStaticInfo.cs new file mode 100644 index 0000000..e02a536 --- /dev/null +++ b/ServicesStaticInfo.cs @@ -0,0 +1,9 @@ +namespace Zerolauncher +{ + class ServicesStaticInfo + { + public static string[] ServicesName = ["4399大区", "7k7k大区", "第七大道"]; + public static string[] ServicesShortName = ["43", "7k", "7d"]; + + } +} diff --git a/StaticHandleProtol.cs b/StaticHandleProtol.cs new file mode 100644 index 0000000..bd7f213 --- /dev/null +++ b/StaticHandleProtol.cs @@ -0,0 +1,27 @@ +namespace Zerolauncher +{ + class StaticHandleS + { + public const string ShowWindow = "a0"; + public const string HintText = "b0"; + public const string MiniSize = "c0"; + public const string NormalSize = "d0"; + public const string UseBrowser = "e0"; + public const string GameSa = "f0"; + public const string CloseGame = "g0"; + } + + class StaticHandleC + { + public const string StartDone = "0a"; + public const string BrowserDone = "0b"; + public const string StartGame = "0c"; + public const string GameDone = "0d"; + } + + class StaticHandleA + { + public const string GameMode = "0a"; + public const string UpdateMode = "0b"; + } +} diff --git a/Zerolauncher.csproj b/Zerolauncher.csproj new file mode 100644 index 0000000..ef6538f --- /dev/null +++ b/Zerolauncher.csproj @@ -0,0 +1,57 @@ + + + + WinExe + net8.0-windows + enable + enable + true + res\title.ico + true + true + + + + full + + + + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Zerolauncher.sln b/Zerolauncher.sln new file mode 100644 index 0000000..60ff396 --- /dev/null +++ b/Zerolauncher.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34408.163 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zerolauncher", "Zerolauncher.csproj", "{C6930B95-44F3-4B41-90A6-20BA34560BF7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C6930B95-44F3-4B41-90A6-20BA34560BF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6930B95-44F3-4B41-90A6-20BA34560BF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6930B95-44F3-4B41-90A6-20BA34560BF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6930B95-44F3-4B41-90A6-20BA34560BF7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {89B100BA-83CE-4B02-8537-22B5A520DFCE} + EndGlobalSection +EndGlobal diff --git a/controls/AirButton.cs b/controls/AirButton.cs new file mode 100644 index 0000000..2e5f917 --- /dev/null +++ b/controls/AirButton.cs @@ -0,0 +1,68 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Zerolauncher.controls +{ + /// + /// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。 + /// + /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。 + /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 + /// 元素中: + /// + /// xmlns:MyNamespace="clr-namespace:Zerolauncher.controls" + /// + /// + /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。 + /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 + /// 元素中: + /// + /// xmlns:MyNamespace="clr-namespace:Zerolauncher.controls;assembly=Zerolauncher.controls" + /// + /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用, + /// 并重新生成以避免编译错误: + /// + /// 在解决方案资源管理器中右击目标项目,然后依次单击 + /// “添加引用”->“项目”->[浏览查找并选择此项目] + /// + /// + /// 步骤 2) + /// 继续操作并在 XAML 文件中使用控件。 + /// + /// + /// + /// + + public class AirButton : Button + { + static AirButton() + { + //DefaultStyleKeyProperty.OverrideMetadata(typeof(AirButton), new FrameworkPropertyMetadata(typeof(AirButton))); + } + + protected override void OnMouseEnter(MouseEventArgs e) + { + // 不执行任何操作 + } + + protected override void OnMouseLeave(MouseEventArgs e) + { + // 不执行任何操作 + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + this.RenderTransform = new TranslateTransform(3, 3); + } + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseUp(e); + this.RenderTransform = new TranslateTransform(0, 0); + } + } + +} diff --git a/controls/AirButton.xaml b/controls/AirButton.xaml new file mode 100644 index 0000000..793541d --- /dev/null +++ b/controls/AirButton.xaml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/controls/AirTextBox.xaml b/controls/AirTextBox.xaml new file mode 100644 index 0000000..42f1cd1 --- /dev/null +++ b/controls/AirTextBox.xaml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/controls/MemberControl.xaml b/controls/MemberControl.xaml new file mode 100644 index 0000000..31a05be --- /dev/null +++ b/controls/MemberControl.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/controls/MemberControl.xaml.cs b/controls/MemberControl.xaml.cs new file mode 100644 index 0000000..53d7141 --- /dev/null +++ b/controls/MemberControl.xaml.cs @@ -0,0 +1,114 @@ +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Zerolauncher.dialog; +using Zerolauncher.Manager; + +namespace Zerolauncher.controls +{ + /// + /// MemberControl.xaml 的交互逻辑 + /// + public partial class MemberControl : UserControl + { + public int memberId { get; set; } + + private bool isMouseDown; + + + public MemberControl() + { + InitializeComponent(); + isMouseDown = false; + // 鼠标按下事件 + MouseDown += (s, e) => + { + RenderTransform = new TranslateTransform(3, 3); + isMouseDown = true; + }; + + // 鼠标松开事件 + MouseUp += (s, e) => + { + if (!isMouseDown) return; + RenderTransform = new TranslateTransform(0, 0); + if (e.ChangedButton == MouseButton.Right) return; + if (!EngineManager.CreateGame(memberId)) MessageBox.Show("账号已启动!请勿重复启动", "提示"); + + }; + + // 鼠标离开事件 + MouseLeave += (s, e) => + { + if (Mouse.LeftButton == MouseButtonState.Pressed) + { + RenderTransform = new TranslateTransform(0, 0); + isMouseDown = false; + } + }; + + // 鼠标捕获丢失事件 + LostMouseCapture += (s, e) => + { + RenderTransform = new TranslateTransform(0, 0); + isMouseDown = false; + }; + ContextMenu = CreateContextMenu(); + } + + private ContextMenu CreateContextMenu() + { + ContextMenu contextMenu = new ContextMenu(); + + MenuItem editItem = new MenuItem { Header = "编辑" }; + editItem.Click += editItem_Click; + contextMenu.Items.Add(editItem); + + MenuItem delItem = new MenuItem { Header = "删除" }; + delItem.Click += delItem_Click; + contextMenu.Items.Add(delItem); + + MenuItem changeTeamItem = new MenuItem { Header = "移动到" }; + contextMenu.Items.Add(changeTeamItem); + changeTeamItem.MouseEnter += (sender, args) => + { + // 清空ContextMenu的items + changeTeamItem.Items.Clear(); + + // 假设你有一个字符串数组 + string[] items = TeamManager.GetAllTeamName(); + + // 遍历字符串数组,为每个元素创建一个MenuItem + for (int i = 0; i < items.Length; i++) + { + MenuItem menuItem = new MenuItem { Header = items[i] }; + menuItem.Tag = i; + menuItem.Click += (sender, args) => + { + // 输出被点击的item的index + MenuItem menuItem = sender as MenuItem; + int i = (int)menuItem.Tag; + Trace.WriteLine("Clicked item index: " + i); + AccountManager.MoveAccount(memberId, i); + }; + changeTeamItem.Items.Add(menuItem); + } + }; + + return contextMenu; + } + + private void delItem_Click(object sender, RoutedEventArgs e) + { + AccountManager.DeleteAccount(memberId); + } + + private void editItem_Click(object sender, RoutedEventArgs e) + { + Trace.WriteLine("edit member " + memberId.ToString()); + EditMemberDialog.CreateDailog(MainWindow.Instance, memberId); + } + } +} diff --git a/controls/TextBoxRule.cs b/controls/TextBoxRule.cs new file mode 100644 index 0000000..bd48f8a --- /dev/null +++ b/controls/TextBoxRule.cs @@ -0,0 +1,167 @@ +using System.Globalization; +using System.Text.RegularExpressions; + +using System.Windows.Controls; + +/// +/// 不能为空验证 +/// +public class NotNullValidationRule : ValidationRule +{ + public NotNullValidationRule() + { + ValidatesOnTargetUpdated = true; + } + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + if (string.IsNullOrEmpty(value as string) || string.IsNullOrWhiteSpace(value as string)) + { + return new ValidationResult(false, "不能为空!"); + } + return ValidationResult.ValidResult; + } +} + +/// +/// 自定义正则表达式验证 +/// +/* + 一、校验数字的表达式 + 1. 数字:^[0-9]*$ + 2. n位的数字:^\d{n}$ + 3. 至少n位的数字:^\d{n,}$ + 4. m-n位的数字:^\d{m,n}$ + 5. 零和非零开头的数字:^(0|[1-9][0-9]*)$ + 6. 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$ + 7. 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$ + 8. 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$ + 9. 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$ + 10. 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$ + 11. 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$ + 12. 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$ + 13. 非负整数:^\d+$ 或 ^[1-9]\d*|0$ + 14. 非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$ + 15. 非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$ + 16. 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$ + 17. 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$ + 18. 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$ + 19. 浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$ + 二、校验字符的表达式 + 1. 汉字:^[\u4e00-\u9fa5]{0,}$ + 2. 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$ + 3. 长度为3-20的所有字符:^.{3,20}$ + 4. 由26个英文字母组成的字符串:^[A-Za-z]+$ + 5. 由26个大写英文字母组成的字符串:^[A-Z]+$ + 6. 由26个小写英文字母组成的字符串:^[a-z]+$ + 7. 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$ + 8. 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$ + 9. 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$ + 10. 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$ + 11. 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+ 12 禁止输入含有~的字符:[^~\x22]+ + 三、特殊需求表达式 + 1. Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ + 2. 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.? + 3. InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$ + 4. 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$ + 5. 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$ + 6. 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7} + 7. 身份证号(15位、18位数字):^\d{15}|\d{18}$ + 8. 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$ + 9. 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$ + 10. 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$ + 11. 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$ + 12. 日期格式:^\d{4}-\d{1,2}-\d{1,2} + 13. 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$ + 14. 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$ + 15. 钱的输入格式: + 16. 1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$ + 17. 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$ + 18. 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$ + 19. 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$ + 20. 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$ + 21. 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$ + 22. 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$ + 23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$ + 24. 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里 + 25. xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$ + 26. 中文字符的正则表达式:[\u4e00-\u9fa5] + 27. 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)) + 28. 空白行的正则表达式:\n\s*\r (可以用来删除空白行) + 29. HTML标记的正则表达式:<(\S*?)[^>]*>.*?|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力) + 30. 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式) + 31. 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始) + 32. 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字) + 33. IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用) + 34. IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)) +*/ +public class CustomRegularValidationRule : ValidationRule +{ + public CustomRegularValidationRule() + { + ValidatesOnTargetUpdated = true; + } + public string RegularString { get; set; } // 正则表达式规则 + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + string str = value as string; + if (!string.IsNullOrWhiteSpace(str)) + { + // 检查输入的字符串是否符合当前正则规范 + if (!Regex.IsMatch(str, RegularString)) + { + return new ValidationResult(false, "内容格式不正确"); + } + } + return ValidationResult.ValidResult; + } +} + +/// +/// 长度限制 +/// +class LengthLimitValidationRule : ValidationRule +{ + public LengthLimitValidationRule() + { + ValidatesOnTargetUpdated = true; + } + public double Minimum { get; set; } + public double Maximum { get; set; } + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + if (value == null) return ValidationResult.ValidResult; + double number = value.ToString().Length; + if (number > Maximum || number < Minimum) + { + return new ValidationResult(false, string.Format("值长度限制在 {0} 到 {1}", Minimum, Maximum)); + } + return ValidationResult.ValidResult; + } +} + +/// +/// ip规则验证 +/// +public class IPAddressRule : ValidationRule +{ + public IPAddressRule() + { + ValidatesOnTargetUpdated = true; + } + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + string IPAddress = value as string; + + if (!string.IsNullOrWhiteSpace(IPAddress)) + { + string IPAddressFormartRegex = @"^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$"; + + // 检查输入的字符串是否符合IP地址格式 + if (!Regex.IsMatch(IPAddress, IPAddressFormartRegex)) + { + return new ValidationResult(false, "IP地址格式不正确"); + } + } + return ValidationResult.ValidResult; + } +} diff --git a/dialog/BaseDialog.xaml b/dialog/BaseDialog.xaml new file mode 100644 index 0000000..af618a6 --- /dev/null +++ b/dialog/BaseDialog.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dialog/BaseDialog.xaml.cs b/dialog/BaseDialog.xaml.cs new file mode 100644 index 0000000..65e09fc --- /dev/null +++ b/dialog/BaseDialog.xaml.cs @@ -0,0 +1,40 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Zerolauncher.dialog +{ + /// + /// BaseDialog.xaml 的交互逻辑 + /// + public partial class BaseDialog : Window + { + + //Storyboard storyboard; + + public BaseDialog(UserControl control) + { + InitializeComponent(); + //storyboard = FindResource("BlinkAnimation") as Storyboard; + mControl.Children.Add(control); + } + + //protected override void OnDeactivated(EventArgs e) + //{ + // base.OnDeactivated(e); + // // 当窗口失去焦点时,开始播放闪烁动画 + // storyboard.Begin(this); + //} + + private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + // 实现窗口的拖动 + DragMove(); + } + + private void AirButton_Click(object sender, RoutedEventArgs e) + { + Close(); + } + } +} diff --git a/dialog/DialogHelper.cs b/dialog/DialogHelper.cs new file mode 100644 index 0000000..1e01054 --- /dev/null +++ b/dialog/DialogHelper.cs @@ -0,0 +1,128 @@ +using System.Windows; + +namespace Zerolauncher.dialog +{ + class EditMemberDialog + { + static BaseDialog? baseDialog; + static EditMember? editControl; + + public static int? member; + + public static void CreateDailog(Window p, int? memberId) + { + if (editControl != null || baseDialog != null) + { + return; + } + member = memberId; + editControl = new EditMember(); + baseDialog = new BaseDialog(editControl); + baseDialog.ShowDialog(); + baseDialog = null; + editControl = null; + member = null; + } + + public static void Close() + { + if (baseDialog != null) + { + baseDialog.Close(); + } + } + + } + + class EditTeamDialog + { + static BaseDialog? baseDialog; + static EditTeamControl? editControl; + + + public static bool CreateDailog(Window p, string? oldTeamName=null) + { + if (editControl != null || baseDialog != null) + { + return false; + } + if (oldTeamName != null) + { + editControl = new EditTeamControl(true); + editControl.edit_name.Text = oldTeamName; + } + else + { + editControl = new EditTeamControl(false); + editControl.btn_del.Visibility = Visibility.Hidden; + } + baseDialog = new BaseDialog(editControl); + baseDialog.ShowDialog(); + baseDialog = null; + editControl = null; + return true; + } + + public static void Close() + { + if (baseDialog != null) + { + baseDialog.Close(); + } + } + } + + class AddMemebersDialog + { + static BaseDialog? baseDialog; + static UseAccDataTextAdd? editControl; + + public static void CreateDailog(Window p) + { + if (editControl != null || baseDialog != null) + { + return; + } + editControl = new UseAccDataTextAdd(); + baseDialog = new BaseDialog(editControl); + baseDialog.ShowDialog(); + baseDialog = null; + editControl = null; + } + + public static void Close() + { + if (baseDialog != null) + { + baseDialog.Close(); + } + } + } + + class UpdateDialog + { + static BaseDialog? baseDialog; + static DownloadControl? editControl; + + public static void CreateDailog(Window p) + { + if (editControl != null || baseDialog != null) + { + return; + } + editControl = new DownloadControl(); + baseDialog = new BaseDialog(editControl); + baseDialog.ShowDialog(); + baseDialog = null; + editControl = null; + } + + public static void Close() + { + if (baseDialog != null) + { + baseDialog.Close(); + } + } + } +} diff --git a/dialog/DownloadControl.xaml b/dialog/DownloadControl.xaml new file mode 100644 index 0000000..22a801a --- /dev/null +++ b/dialog/DownloadControl.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/dialog/DownloadControl.xaml.cs b/dialog/DownloadControl.xaml.cs new file mode 100644 index 0000000..86c2a7d --- /dev/null +++ b/dialog/DownloadControl.xaml.cs @@ -0,0 +1,104 @@ +using System.IO; +using System.Net.Http; +using System.Windows.Controls; + +namespace Zerolauncher.dialog +{ + /// + /// DownloadControl.xaml 的交互逻辑 + /// + public partial class DownloadControl : UserControl + { + const string cache_dir = "./.cache/auto.cache"; + + public DownloadControl() + { + InitializeComponent(); + } + + private void OnError(Exception ex) + { + // UnityEngine.Debug.Log("捕获异常 >>> " + ex); + } + + public async Task Download(string fileUrl) + { + using (HttpClient client = new HttpClient()) + { + // 获取文件大小 + HttpResponseMessage responseHead = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, fileUrl)); + long? contentLength = responseHead.Content.Headers.ContentLength; + Console.WriteLine($"文件大小:{contentLength} 字节"); + + // 计算块大小 + int blockSize = 1024 * 1024; // 1MB + int blockCount = (int)Math.Ceiling((double)contentLength / blockSize); + + // 创建SemaphoreSlim以限制同时进行的下载任务的数量 + SemaphoreSlim semaphore = new SemaphoreSlim(4); // 最多允许4个并发下载任务 + + using (FileStream fileStream = new FileStream(cache_dir, FileMode.Create, FileAccess.Write, FileShare.None, blockSize, true)) + { + // 创建任务列表 + Task[] tasks = new Task[blockCount]; + + for (int i = 0; i < blockCount; i++) + { + int blockNumber = i; + tasks[i] = Task.Run(async () => + { + await semaphore.WaitAsync(); // 等待获取许可 + + try + { + // 计算当前块的范围 + var start = blockNumber * blockSize; + var end = (blockNumber == blockCount - 1) ? contentLength - 1 : start + blockSize - 1; + + // 设置请求头,请求指定范围的数据 + var request = new HttpRequestMessage { RequestUri = new Uri(fileUrl), Method = HttpMethod.Get }; + request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(start, end); + + // 下载当前块 + using (HttpResponseMessage response = await client.SendAsync(request)) + { + response.EnsureSuccessStatusCode(); + byte[] buffer = await response.Content.ReadAsByteArrayAsync(); + await fileStream.WriteAsync(buffer, 0, buffer.Length); + } + + // 显示下载进度 + double percent = (double)(blockNumber + 1) / blockCount * 100; + Console.WriteLine($"已下载:{percent:F2}%"); + } + finally + { + semaphore.Release(); // 释放许可 + } + }); + } + + // 等待所有任务完成 + await Task.WhenAll(tasks); + } + } + + Console.WriteLine("下载完成!"); + } + + private void OnUpdate(long size, long count) + { + + } + + private void OnDone(byte[] data) + { + + } + + private void OnDestroy() + { + } + + } +} diff --git a/dialog/EditMember.xaml b/dialog/EditMember.xaml new file mode 100644 index 0000000..fb0086e --- /dev/null +++ b/dialog/EditMember.xaml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dialog/EditMember.xaml.cs b/dialog/EditMember.xaml.cs new file mode 100644 index 0000000..42a6eeb --- /dev/null +++ b/dialog/EditMember.xaml.cs @@ -0,0 +1,80 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Zerolauncher.Manager; + +namespace Zerolauncher.dialog +{ + /// + /// EditMember.xaml 的交互逻辑 + /// + public partial class EditMember : UserControl + { + + private Dictionary _services; + + public EditMember() + { + _services = new Dictionary(); + int i = 0; + foreach (string serviceName in ServicesStaticInfo.ServicesName) + { + _services[i++] = serviceName; + } + InitializeComponent(); + cb_pid.ItemsSource = _services; + if(EditMemberDialog.member != null) + { + int index = (int)EditMemberDialog.member; + var acc = AccountManager.accountsList[index]; + cb_pid.SelectedIndex = acc.providerId; + edit_sid.Text = acc.serverId; + edit_acc.Text = acc.userName; + edit_pwd.Password = acc.userPWD; + edit_nick.Text = acc.nickName; + } + } + + private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e) + { + PasswordBox passwordBox = sender as PasswordBox; + VisualBrush helpBrush = passwordBox.Resources["HelpBrush"] as VisualBrush; + if (passwordBox.Password.Length > 0) + { + passwordBox.Background = new SolidColorBrush(Color.FromRgb(255, 255, 255)); + } + else + { + passwordBox.Background = helpBrush; + } + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (string.IsNullOrEmpty(cb_pid.Text) || string.IsNullOrEmpty(edit_sid.Text) || string.IsNullOrEmpty(edit_acc.Text) || string.IsNullOrEmpty(edit_pwd.Password) || string.IsNullOrEmpty(edit_nick.Text)) + { + MessageBox.Show("所有选项均不能为空!", "提示"); + return; + } + if (EditMemberDialog.member == null) + { + AccountManager.AddAccount(new Account { providerId = cb_pid.SelectedIndex, serverId = edit_sid.Text, userName = edit_acc.Text, userPWD = edit_pwd.Password, nickName = edit_nick.Text }); + + EditMemberDialog.Close(); + return; + } + int index = (int)EditMemberDialog.member; + var acc = AccountManager.accountsList[index]; + acc.providerId = cb_pid.SelectedIndex; + acc.serverId = edit_sid.Text; + acc.userName = edit_acc.Text; + acc.userPWD = edit_pwd.Password; + acc.nickName = edit_nick.Text; + AccountManager.saveEdit(); + EditMemberDialog.Close(); + return; + } + + } +} + diff --git a/dialog/EditTeamControl.xaml b/dialog/EditTeamControl.xaml new file mode 100644 index 0000000..bbbcd4a --- /dev/null +++ b/dialog/EditTeamControl.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dialog/EditTeamControl.xaml.cs b/dialog/EditTeamControl.xaml.cs new file mode 100644 index 0000000..a3befa8 --- /dev/null +++ b/dialog/EditTeamControl.xaml.cs @@ -0,0 +1,45 @@ +using System.Windows; +using System.Windows.Controls; +using Zerolauncher.Manager; + +namespace Zerolauncher.dialog +{ + /// + /// EditTeamControl.xaml 的交互逻辑 + /// + public partial class EditTeamControl : UserControl + { + + bool isEditModle; + + public EditTeamControl(bool isEditModle) + { + InitializeComponent(); + this.isEditModle = isEditModle; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if(string.IsNullOrEmpty(edit_name.Text)) + { + MessageBox.Show("昵称不能为空!", "提示"); + return; + } + if (isEditModle) AccountManager.editTeamName(edit_name.Text); else TeamManager.addTeam(edit_name.Text); + EditTeamDialog.Close(); + } + + private void Button_Click_1(object sender, RoutedEventArgs e) + { + MessageBoxResult result = MessageBox.Show("即将放生此队伍以及队伍里所有的账号\n将会为你带来114点功德\n您确定要执行此操作吗?", "提示", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); + if (result == MessageBoxResult.OK) + { + if (!AccountManager.DeleteTeam()) + { + MessageBox.Show("少爷这已经是最后一个队伍了!\n请新建一个队伍后再放生这个吧~", "提示"); + } + } + + } + } +} diff --git a/dialog/UseAccDataTextAdd.xaml b/dialog/UseAccDataTextAdd.xaml new file mode 100644 index 0000000..02010b3 --- /dev/null +++ b/dialog/UseAccDataTextAdd.xaml @@ -0,0 +1,18 @@ + + + + +