[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs     diff=csharp\n\n# Standard to msysgit\n*.doc\t diff=astextplain\n*.DOC\t diff=astextplain\n*.docx diff=astextplain\n*.DOCX diff=astextplain\n*.dot  diff=astextplain\n*.DOT  diff=astextplain\n*.pdf  diff=astextplain\n*.PDF\t diff=astextplain\n*.rtf\t diff=astextplain\n*.RTF\t diff=astextplain\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n# build/\nbld/\n[Bb]in/\n[Oo]bj/\n\n# Visual Studo 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding addin-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings \n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Windows Azure Build Output\ncsx/\n*.build.csdef\n\n# Windows Store app package directory\nAppPackages/\n\n# Others\n*.[Cc]ache\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\nnode_modules/\nbower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Ignore WcR2 temp directory\nOldVer/\n!References/x86/\n!References/x64/\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"CharaSimResource\"]\n\tpath = CharaSimResource\n\turl = https://github.com/Kagamia/CharaSimResource.git\n"
  },
  {
    "path": "Build/Common.props",
    "content": "<Project>\n  <!-- common c# language build config -->\n  <PropertyGroup>\n    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>\n    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n    <LangVersion>latest</LangVersion>\n    <Nullable>disable</Nullable>\n  </PropertyGroup>\n\n  <!-- disable code analysis -->\n  <PropertyGroup>\n    <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>\n    <RunAnalyzersDuringLiveAnalysis>false</RunAnalyzersDuringLiveAnalysis>\n    <RunAnalyzers>false</RunAnalyzers>\n  </PropertyGroup>\n  \n  <!-- net6 -->\n  <PropertyGroup Condition=\"$([System.Text.RegularExpressions.Regex]::IsMatch($(TargetFramework), '^net8'))\">\n    <ImplicitUsings>disable</ImplicitUsings>\n    <NoWarn>CA1416</NoWarn>\n    <DotnetEdition>core</DotnetEdition>\n    <MonogameFrameworkVersion>3.8.2.1105</MonogameFrameworkVersion>\n    <SystemDrawingCommonVersion>8.0.11</SystemDrawingCommonVersion>\n  </PropertyGroup>\n  \n  <!-- net6 -->\n  <PropertyGroup Condition=\"$([System.Text.RegularExpressions.Regex]::IsMatch($(TargetFramework), '^net6'))\">\n    <ImplicitUsings>disable</ImplicitUsings>\n    <NoWarn>CA1416</NoWarn>\n    <DotnetEdition>core</DotnetEdition>\n    <MonogameFrameworkVersion>3.8.1.303</MonogameFrameworkVersion>\n    <SystemDrawingCommonVersion>6.0.0</SystemDrawingCommonVersion>\n  </PropertyGroup>\n  \n  <!-- net framework -->\n  <PropertyGroup Condition=\"$([System.Text.RegularExpressions.Regex]::IsMatch($(TargetFramework), '^net4'))\">\n    <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>\n    <DotnetEdition>framework</DotnetEdition>\n    <MonogameFrameworkVersion>3.8.0.1641</MonogameFrameworkVersion>\n  </PropertyGroup>\n\n  <!-- common -->\n  <PropertyGroup>\n    <SharpDXVersion>4.2.0</SharpDXVersion>\n    <SystemResourcesExtensionsVersion>8.0.0</SystemResourcesExtensionsVersion>\n  </PropertyGroup>\n\n  <!-- plugin specified property -->\n  <PropertyGroup Condition=\"$(WcR2Plugin) == 'true' and $(DotnetEdition) == 'core'\">\n    <EnableDynamicLoading>true</EnableDynamicLoading>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "Build/WcR2Plugin.targets",
    "content": "<Project>\n  <PropertyGroup>\n    <MainProgramOutputDir Condition=\"'$(MainProgramOutputDir)' == ''\">$(SolutionDir)WzComparerR2\\bin\\$(Configuration)\\$(TargetFramework)</MainProgramOutputDir>\n    <PluginOutputDir Condition=\"'$(PluginOutputDir)' == ''\">$(MainProgramOutputDir)\\Plugin\\$(MSBuildProjectName)</PluginOutputDir>\n    <NativeLibOutputDir Condition=\"'$(NativeLibOutputDir)' == ''\">$(MainProgramOutputDir)\\Lib</NativeLibOutputDir>\n  </PropertyGroup>\n\n  <Target Name=\"PostBuild\" AfterTargets=\"PostBuildEvent\" Condition=\"$([MSBuild]::IsOSPlatform('Windows'))\">\n    <Exec Command=\"xcopy &quot;$(TargetDir)*.dll&quot; &quot;$(PluginOutputDir)&quot; /I /Y /F\" />\n    <Exec Condition=\"$(DotnetEdition) == 'core'\" Command=\"xcopy &quot;$(TargetDir)*.deps.json&quot; &quot;$(PluginOutputDir)&quot; /I /Y /F\" />\n    <Exec Condition=\"$(DotnetEdition) == 'core'\" Command=\"xcopy &quot;$(TargetDir)*.runtimeconfig.json&quot; &quot;$(PluginOutputDir)&quot; /I /Y /F\" />\n    <Exec Condition=\"Exists('$(TargetDir)x86')\" Command=\"xcopy &quot;$(TargetDir)x86\\*.dll&quot; &quot;$(NativeLibOutputDir)\\x86&quot; /I /Y /F\" />\n    <Exec Condition=\"Exists('$(TargetDir)x64')\" Command=\"xcopy &quot;$(TargetDir)x64\\*.dll&quot; &quot;$(NativeLibOutputDir)\\x64&quot; /I /Y /F\" />\n    <Exec Condition=\"Exists('$(TargetDir)ARM64')\" Command=\"xcopy &quot;$(TargetDir)ARM64\\*.dll&quot; &quot;$(NativeLibOutputDir)\\ARM64&quot; /I /Y /F\" />\n  </Target>\n</Project>"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n \nCopyright © 2015 Kagamia Studio\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "*<s>使用前先大喊 niconiconi! poi! duang!以减少bug发生率</s>*  \n\n[![Build Status](https://dev.azure.com/kagamiastudio/WzComparerR2/_apis/build/status/Kagamia.WzComparerR2?branchName=master)](https://dev.azure.com/kagamiastudio/WzComparerR2/_build/latest?definitionId=4&branchName=master)\n\n# Maintenance Status\n\n⚠️ The WzComparerR2 project is now in deep maintenance status. This means that only critical bugs or wz file format breaking changes are being considered for inclusion by owner. Expect slow replies to issues.\n\n# WzComparerR2\n这是一个用C# latest/.Net4.62+.Net8组装的冒险岛提取器...  \n包含了一些奇怪的机能比如stringWZ搜索 客户端对比 装备模拟 地图模拟等等..  \n\ntips: WcR2将尽力维持每周更新，Releases里**不会**提供最稳定版下载，最新版会通过azure-pipeline自动发布。  \nlinks: [\\[更新日志\\]](https://github.com/Kagamia/WzComparerR2/tree/master/UpdateLogs)  [\\[版本计划\\]](https://github.com/Kagamia/WzComparerR2/wiki/Roadmap)  [\\[最新版下载\\]](https://github.com/Kagamia/WzComparerR2/releases/tag/ci-build)\n\n# Modules\n- **WzComparerR2** 主程序\n- **WzComparerR2.Common** 一些通用类\n- **WzComparerR2.PluginBase** 插件管理器\n- **WzComparerR2.WzLib** wz文件读取相关\n- **CharaSimResource** 用于装备模拟的资源文件\n- **WzComparerR2.LuaConsole** (可选插件)Lua控制台\n- **WzComparerR2.MapRender** (可选插件)地图仿真器\n- **WzComparerR2.Avatar** (可选插件)纸娃娃\n- **WzComparerR2.Network** (可选插件)在线聊天室\n\n# Prerequisite\n- **2.x**: Win7sp1+/.net4.6.2+/dx11.0\n- **1.x**: WinXp+/.net2.0+/dx9.0\n\n# Installation\n```sh\ngit clone --recurse-submodules -j8 git://github.com/Kagamia/WzComparerR2.git\n```\nClone repository with submodules.\n\n# Compile\n- vs2022 or higher/.net 8 SDK\n\n# Credits and Acknowledgement\n- **Fiel** ([Southperry](http://www.southperry.net))  wz文件读取代码改造自WzExtract 以及WzPatcher\n- **Index** ([Exrpg](http://bbs.exrpg.com/space-uid-137285.html)) MapRender的原始代码 以及libgif\n- **Deneo** For .ms file format and video format\n- [DotNetBar](http://www.devcomponents.com/)\n- [SharpDX](https://github.com/sharpdx/SharpDX) & [Monogame](https://github.com/MonoGame/MonoGame)\n- [BassLibrary](http://www.un4seen.com/)\n- [IMEHelper](https://github.com/JLChnToZ/IMEHelper)\n- [Spine-Runtime](https://github.com/EsotericSoftware/spine-runtimes)\n- [EmptyKeysUI](https://github.com/EmptyKeys)\n- [libvpx](https://www.webmproject.org/code/) & [libyuv](https://chromium.googlesource.com/libyuv/libyuv/) for video decoding\n- [VC-LTL5](https://github.com/Chuyu-Team/VC-LTL5) for native library build\n- All testers from CMST tester group.\n"
  },
  {
    "path": "UpdateLogs/dev.md",
    "content": "﻿## 2018.7.5\n\n### 共通\n- 添加了一个api以利于插件输出error.log。\n- 更新了buildin gif encoder以修复某些场合导出错误的bug。\n\n### WzLib\n- 修复了上次修复Uol链接推断的bug。\n- 修复了上次新增支持读取独立的img文件的bug。\n\n### CharaSim\n- 修复某些动作下发型渲染错误的bug。\n- 支持了宠物套装的渲染，添加宠物过期时间。\n- 支持显示GMS按时间奖励的套装效果。\n\n### MapRender\n- 调整了粒子系统渲染效果。\n- Worldmap支持了KMST1070的QuestLimit分阶段渲染机制。\n\n\n## 2018.6.20\n\n### 共通\n- 支持了按照imgID进行排序。\n- 配置文件中默认开启wz自动排序和自动加载扩展wz。\n- File-Option里添加了一些新的配置项。\n\n### WzLib\n- 修复了wz类型推断的bug。\n- 修复Uol链接推断的bug。\n- 支持读取独立的img文件。\n- 支持跳过img校验和检测，以识别老旧版本的客户端。\n\n### CharaSim\n- 称号支持模拟包含任务数量宏字符串。\n- 添加Ark特殊装备类型的模拟。\n- 添加KMST1069的新属性nbdR识别。\n\n### MapRender\n- 修复了阿斯旺地图特有的wz怪物声明的识别。\n\n\n## 2018.4.25\n\n### WzLib\n- 重新设计数据结构，以减少内存占用。\n\n### CharaSim\n- 支持套装显示点装图标。\n- 支持徽章的tag显示。\n- 龙神的v5技能模拟可以超过等级上限了。\n- 重新设计StringLinker数据结构，以减少内存占用。\n\n### MapRender\n- 修复了MapRender窗口反复开关引发的的内存泄露。\n\n\n## 2018.4.13\n\n### 共通\n- 修复了BGR565格式纹理对于win8前系统的支持。\n\n### MapRender\n- 屏蔽了因输入框失去焦点导致的按键处理异常的错误。\n\n### Patcher\n- 添加了CMS的补丁地址。\n\n\n## 2018.4.10\n\n### MapRender\n- 修复下拉菜单点击无效的bug。\n- 支持更多交互命令。\n\n\n## 2018.4.9\n\n### MapRender\n- 支持隐藏NPC和怪物名字。\n- 更新了MessageBox样式。\n- 修复了UIChatBox渲染模糊和操作上的bug。\n\n\n## 2018.4.8\n\n### WzLib\n- 修复了format517图片解码错误的bug。\n\n### MapRender\n- 添加UIMessageBox用于各种提示信息。\n- 添加UIChatBox，并支持了输入法，用于交互和显示提示。\n- 临时修复粒子系统的渲染错误。\n- UIWorldmap支持右键返回。\n- 修复了back图层的渲染错误，支持blend模式。\n- UI布局略微调整。\n\n\n## 2018.3.23\n\n### CharaSim\n- 支持了KMST1066版本拆分的03xx.img物品识别。\n\n\n## 2018.3.22\n\n### Wz提取\n- 修复了一个可能导致Gif/Apng导出有锯齿的bug。\n\n### WzLib\n- 修复了GetValue()导致搜索效率低下的bug。\n- 调整string.intern过滤条件，减少字符串池常驻内存占用。\n\n### MapRender\n- UIWorldMap支持更丰富的tooltip信息，并且支持点击传送了。\n- 修复UI绘图资源的潜在内存泄露。\n\n"
  },
  {
    "path": "UpdateLogs/v2.0.1.md",
    "content": "﻿WzComparerR2   2.0.1\n==================================\n##共通：\n- 程序已升级至C#6.0/.net4.0编译\n- 程序已支持anyCPU编译 wcR2.exe(32位) wcR2.anyCPU.exe(32/64位自适应)\n- 配置文件系统完全重制 旧有文件废弃并不继承 新的配置文件为setting.config\n- 图片浏览模块完全重制 使用Monogame 3.4/DX11作为绘图引擎 \n- 图片保存模块完全重制 原因参考上一条\n- 为了支持上述环境 程序最低运行环境为win7以上/显示卡支持dx11以上\n- 目前没有计划重新支持xp/GL环境 但是理论上是可以支持的 您可以自行编译试试:)\n\n##基础提取：\n- 支持dxt5图片格式\n- 支持CMST115版本后的套装属性合并的显示方式\n- 支持KMST1033版本后的图片链接方式\n- 支持Spine动画预览 方式为选择.altas节点 点击ExtractGif按钮\n\n##UI变化：\n- HandleUol按钮移动到wz内容浏览的右键菜单中\n- Gif保存添加了更多选项 包括图片背景和文件命名方式\n- 图片的拖拽保存需要按住ctrl才会执行了 普通的拖拽只是移动而已:)\n\n##CharaSim：\n- 爆破手职业装备支持\n- 勋章预览支持\n- 怪物卡片 NPC卡片功能已内置\n- 技能等级上限提升至100\n- 属性表达式计算器完全重制 如果有bug请报告\n- 添加了更多的显示控制项 自行感受\n\n##MapRender：\n- 插件已废弃 准备重制\n\n##MonsterCard：\n- 插件已废弃 部分功能合并到主程序\n"
  },
  {
    "path": "UpdateLogs/v2.0.9.md",
    "content": "﻿## 2017.7.6\n\n### MapRender\n- 更新至正式版本。\n\n\n## 2017.6.25\n\n### SoundPlayer\n- 重新支持Bass插件，把插件放置在Lib/x86和Lib/x64中生效。\n\n### MapRender2\n- 更新了一个测试版本。\n\n\n## 2017.6.18\n\n### Avatar\n- 修复了一个因主程序更新无法初始化的bug。\n\n\n## 2017.6.11\n\n### WzComparer\n- Options实装，添加了一些全局设置，以解决GMS无法正确解析字符串的bug。\n\n### Patcher\n- 修复了多开时config文件保存不正确的bug\n\n\n## 2017.6.2\n\n### Comparer\n- 使用大小写敏感的方式寻找link，大幅度优化对比速度。\n- 修复了一个对比过程中手动回收img可能导致错误的bug。\n- 稍微调整了输出文件样式，默认隐藏掉因开启ResolvePngLink无变动项的img对比结果。\n\n\n## 2017.5.12\n\n### Comparer\n- 修复了在补丁对比过程中ResolvePngLink无法正常工作的bug。\n\n\n## 2017.4.25\n\n### CharaSim\n- 装备模拟：能正确显示多行的套装效果了。\n\n\n## 2017.3.20\n\n### Comparer\n- 修复了开启ResolvePngLink会出现OutOfMemoryException的bug，现在在对比中打开的image会自动回收了。\n\n### WzLib\n- 优化了忽略大小写的wz节点搜索方法。\n\n### CharaSim\n- 装备模拟：能量源重新正确的识别了。\n\n\n## 2017.3.15\n\n### CharaSim\n- 装备模拟：支持识别更多特性，支持多行装备特性的排版。\n- 修复上版本装备加载异常的bug。\n- 默认字体调整回“宋体”。\n\n\n## 2017.3.14\n积累更新\n\n### WzComparer\n- 优化了wz节点搜索效率，不再突然卡死了。\n- 关于里面可以看到插件的文件版本了。\n\n### WzLib\n- 添加了自定义Encoding的支持，用于特定场合下解决ansi字符串解析的bug。\n\n### CharaSim\n- 更新支持同步至最新CMST。\n- 装备模拟：支持显示装备属性与标准属性差异，支持限时属性的模拟。\n- 技能模拟：链接属性时不再识别数字开头的属性名称，以修复某些场合的解析bug。\n\n### Comparer\n- 支持对于Link的图片智能链接对比，以减少输出对比报告的文件大小。\n\n\n## 2017.1.11\n积累更新\n\n### 共通\n- 绘图引擎更新，大部分场合使用Bgra32替代Color作为后备绘图缓冲区像素格式，以提高文件保存与加载效率。\n\n### CharaSim\n- 迎接第5次转职，更新技能/道具/装备的仿真效果至最新CMST。\n- 为了支持CharaSim其他语言版本开发，更新了支持非等宽字体和word-wrap的排版算法。\n- 默认字体由宋体改为新宋体。\n- 添加了一个韩文字体供测试。\n- 装备模拟：移除魔法防御力/命中/回避，调整关于暴击伤害的显示，添加支持戒指特殊潜能显示。\n- 道具模拟：添加道具等级显示，支持道具限时的显示。\n- 略微调整文字坐标以适配最新游戏效果。\n\n### Issue讨论中未更新内容\n- #20：Ansi编码导致某些字符串显示错误问题（已确认，待解决）\n- #19：装备特殊潜能等级的显示问题（CMS未确认）\n- #15：技能模拟中宏变量引用无视大小写导致链接错误问题（GMS已确认，未更新）\n- #14：套装模拟中文字范围溢出错误（已确认，未更新）\n\n\n## 2016.10.27\n\n### WzLib\n- 修复了因CMS加密方式变更 导致Lua无法正常提取的bug\n\n\n## 2016.10.18\n\n### WzLib\n- img导出xml移至WzLib作为扩展函数出现\n- 修复了xml导出时对于SoundType.Binary无法正常导出的bug\n\n### LuaConsole\n- 实现lua文件载入和保存按钮功能\n- 添加了一个使用脚本批量导出的example\n\n\n## 2016.10.08\n\n### 共通\n- 添加了可以对img导出为xml的功能\n\n\n## 2016.09.26\n\n### CharaSim\n- 修复Item无法识别link没有图标的bug\n\n\n## 2016.09.16\n\n### 共通\n- wzlib读取结构变更 Wz_Image.Node.Value不再指向Wz_Image自身的引用\n- 修复因上一条导致的若干运行效果不正确的场合\n\n### WzCompare\n- 支持了CMS/TMS等wz合并对比时同名节点冲突的场合，目前可以分别对比\n\n### CharaSim\n- 修复恶魔盾牌MP/DF显示bug\n\n### LuaConsole\n- 添加了可以获取全局插件环境和事件的接口 因此在Lua中可以获取当前选中的Wz节点了\n\n\n## 2016.08.11\n\n### 共通\n- 整理Lua的提取方式 直接绑定在顶层节点上 可能会影响对比\n\n### Avatar\n- 修复了纸娃娃无法识别Link的Bug\n\n\n## 2016.08.07\n\n### 共通\n- 临时支持了CMST117新增的Lua节点特殊格式\n\n\n##2016.07.31\n\n### 共通\n- 修复了JMSwz无法解析的bug\n- 加入了一个设置项 允许wz打开时自动排序 这个设置项可通过手动编辑setting文件配置\n- wz显示的部分代码整理\n- 动画部分代码整理 以兼容新的MapRender \n\n### CharaSim\n- 支持显示KMST新增属性incARC\n\n### Patcher\n- 支持了新的64位长度标记的Patch.exe文件格式\n\n\n## 2016.07.11\n\n### 共通\n- 修复了IndexGifEncoder编码纯色帧导致gif文件损坏的bug\n- 修复了MonoGame多Device共存可能出现的bug\n\n### CharaSim\n- Eval计算支持了KMST新的log公式，说不定未来会用到\n- 修复了属性Eval计算时因公式出现空白符解析失败的bug\n\n### MapRender\n- Xna引擎升级至MonoGame，功能部分恢复到之前的版本，并未支持KMST的压缩版客户端资源链接方式\n- 暂时移除没用的输入法和聊天模块\n\n"
  },
  {
    "path": "UpdateLogs/v2.1.1.md",
    "content": "﻿## 2018.3.15\n久违的积累更新\n\n### 共通\n- 支持了Apng格式动画导出\n\n### WzLib\n- 支持自动检测扩展wz(如map2, map001)的设置\n\n### CharaSim\n- 对于V5技能模拟，提高等级上限至100。\n- 支持新职业装备，更新模拟效果至最新的CMST。\n- 对于装备附带技能，可以在模拟中显示技能说明了。\n- 支持互斥装备的显示说明。（ExclusiveEquip）\n- 修复了bodyattack默认值的错误。\n- 改进怪物卡数值的显示格式。\n\n### Comparer\n- 修复了在应用补丁中同类型wz对比时，输出文件覆盖的bug。\n\n### Avatar\n- 支持arc职业耳朵的模拟。\n\n### MapRender\n- 支持了D2D渲染引擎的支持，可用于文字和简单图元渲染。\n- 支持了粒子特效渲染。\n- 大幅度优化运行效率，内存占用与GC。\n- 修复了潜在的内存泄露。\n"
  },
  {
    "path": "UpdateLogs/v2.1.md",
    "content": "﻿## 2017.10.10\n积累更新\n\n### Network\n- \n\n### Patcher\n- 追加了全世界冒险岛补丁下载地址。\n- 修复了ftp开头的地址无法探测文件更新时间的bug。\n\n### WzLib\n- 兼容2G以上的wz文件。\n\n### CharaSim\n- 更新至CMST最新版。\n\n\n## 2017.8.8\n\n### Avatar\n- 修复骑宠相关的大量bug，实验性支持flip，removeBody等特性。\n- 修复了z层排序异常的错误。\n- 重构缓冲算法，不再因为总图像范围过大无法渲染了。\n\n\n## 2017.8.5\n\n### CharaSim\n- 支持套装属性的扩展显示和默认装备名称显示。\n- 对于封印解除属性显示时自动合并同类属性。\n\n### Avatar\n- 支持骑宠的强制身体动作字段。\n- 加载骑宠时将自动设置为关联的身体动作和表情。\n- 优化了预渲染效率。\n\n\n## 2017.8.2\n\n### 共通\n- 支持了渲染Spine骨骼动画的新特性。\n\n### MapRender\n- 新增了一个Option界面和配置文件，TopBar回归。\n\n### CharaSim\n- 忽略属性表达式中的'%'，以强行屏蔽一个bug。\n- 修复了宏字符串中'#'会丢失字符的bug。\n\n### Avatar\n- 支持了选择ear图层类型，以支持KMST新职业。\n\n\n## 2017.7.23\n\n### 共通\n- GearGraphics.TextRenderer重构，排版与渲染分离，支持Monogame。\n\n### MapRender\n- 完整支持了UIWorldMap。\n\n\n## 2017.7.20\n\n### 基础\n- 修复了技能编号8001xxxx无法自动链接的bug。\n\n\n## 2017.7.8\n积累更新\n\n### 共通\n- 因为发现了一个特殊的wz结构，调整了全部解决uol引用的代码。这影响到了全部动画加载相关的模块。\n\n### WzLib\n- 添加了可以支持自动挂接扩展wz文件(mob2, map2, etc.)的选项。\n\n### MapRender\n- 修复了一些可能造成内存泄漏的bug（但并无卵用）。"
  },
  {
    "path": "WzComparerR2/AnimateEncoderFactory.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing WzComparerR2.Config;\nusing WzComparerR2.Encoders;\n\nnamespace WzComparerR2\n{\n    public static class AnimateEncoderFactory\n    {\n        static AnimateEncoderFactory()\n        {\n            registeredEncoders = new Dictionary<int, IAnimateEncoderProvider>();\n            RegisterEncoders();\n        }\n\n        private static Dictionary<int, IAnimateEncoderProvider> registeredEncoders;\n\n        private static void RegisterEncoders()\n        {\n            registeredEncoders.Add(0, new AnimateEncoderProvider<BuildInGifEncoder>\n            {\n                ID = 0,\n                Name = nameof(BuildInGifEncoder),\n                CreateEncoderCallback = () => new BuildInGifEncoder(),\n            });\n\n            registeredEncoders.Add(1, new AnimateEncoderProvider<IndexGifEncoder>\n            {\n                ID = 1,\n                Name = nameof(IndexGifEncoder),\n                CreateEncoderCallback = () => new IndexGifEncoder(),\n            });\n\n            registeredEncoders.Add(2, new AnimateEncoderProvider<BuildInApngEncoder>\n            {\n                ID = 2,\n                Name = nameof(BuildInApngEncoder),\n                CreateEncoderCallback = () => new BuildInApngEncoder(),\n                ConfigureEncoderCallback = (encoder, config) =>\n                {\n                    encoder.OptimizeEnabled = config.PaletteOptimized;\n                }\n            });\n\n            registeredEncoders.Add(3, new AnimateEncoderProvider<FFmpegEncoder>\n            {\n                ID = 3,\n                Name = nameof(FFmpegEncoder),\n                CreateEncoderCallback = () => new FFmpegEncoder(),\n                ConfigureEncoderCallback = (encoder, config) =>\n                {\n                    encoder.FFmpegBinPath = config.FFmpegBinPath;\n                    encoder.FFmpegArgumentFormat = config.FFmpegArgument;\n                    encoder.OutputFileExtension = config.FFmpegOutputFileExtension;\n                }\n            });\n        }\n\n        public static GifEncoder CreateEncoder(ImageHandlerConfig config)\n        {\n            return CreateEncoder(config.GifEncoder, config);\n        }\n\n        public static GifEncoder CreateEncoder(int id, ImageHandlerConfig config)\n        {\n            if (!registeredEncoders.TryGetValue(id, out var provider))\n            {\n                throw new Exception($\"Encoder ID {id} has not registered\");\n            }\n\n            var encoder = provider.CreateEncoder();\n            provider.ConfigureEncoder(encoder, config);\n            return encoder;\n        }\n\n        public interface IAnimateEncoderProvider\n        {\n            GifEncoder CreateEncoder();\n            void ConfigureEncoder(GifEncoder encoder, ImageHandlerConfig config);\n        }\n\n        public class AnimateEncoderProvider<T> : IAnimateEncoderProvider where T : GifEncoder\n        {\n            public int ID { get; set; }\n            public string Name { get; set; }\n            public Func<T> CreateEncoderCallback { get; set; }\n            public Action<T, ImageHandlerConfig> ConfigureEncoderCallback { get; set; }\n\n            public GifEncoder CreateEncoder()\n            {\n                if (this.CreateEncoderCallback == null)\n                {\n                    throw new ArgumentNullException(nameof(CreateEncoderCallback));\n                }\n\n                return this.CreateEncoderCallback();\n            }\n\n            public void ConfigureEncoder(GifEncoder encoder, ImageHandlerConfig config)\n            {\n                if (this.ConfigureEncoderCallback != null)\n                {\n                    this.ConfigureEncoderCallback((T)encoder, config);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSim/CharaEquip.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class CharaEquip\n    {\n        public CharaEquip()\n        {\n            int slotsCount = 66;\n            gearSlots = new Gear[slotsCount];\n            cashGearSlots = new Gear[slotsCount];\n        }\n\n        public const int RingCount = 6;\n        public const int PendantCount = 2;\n\n        private Gear[] gearSlots;\n        private Gear[] cashGearSlots;\n\n\n        public Gear[] GearSlots\n        {\n            get { return gearSlots; }\n        }\n\n        public Gear[] CashGearSlots\n        {\n            get { return cashGearSlots; }\n        }\n\n        public int GetGearSlot(GearType type, int index)\n        {\n            switch (type)\n            {\n                //line 0\n                case GearType.badge: return 0;\n                case GearType.cap: return 1;\n                case GearType.ring:\n                    switch (index)\n                    {\n                        case 0: return 8;\n                        case 1: return 9;\n                        case 2: return 23;\n                        case 3: return 24;\n                        case 4: return 2;\n                        case 5: return 7;\n                        default: return -1;\n                    }\n                case GearType.android: return 3;\n                case GearType.machineHeart: return 4;\n                //line 1\n                case GearType.medal: return 5;\n                case GearType.faceAccessory: return 6;\n                //line 2\n                case GearType.pocket: return 10;\n                case GearType.eyeAccessory: return 11;\n                case GearType.pendant:\n                    switch (index)\n                    {\n                        case 0: return 17;\n                        case 1: return 12;\n                        default: return -1;\n                    }\n                case GearType.earrings: return 13;\n                case GearType.shoulderPad: return 14;\n                //line 3\n                case GearType.cape: return 15;\n                case GearType.coat:\n                case GearType.longcoat: return 16;\n                default:\n                    if (Gear.IsLeftWeapon(type) || Gear.IsDoubleHandWeapon(type))\n                        return 18;\n                    else if (Gear.IsSubWeapon(type))\n                        return 19;\n                    else\n                        return -1;\n                //line 4\n                case GearType.glove: return 20;\n                case GearType.pants: return 21;\n                case GearType.belt: return 22;\n                //line 5\n                case GearType.shoes: return 27;\n                //dragon\n                case GearType.dragonMask: return 35;\n                case GearType.dragonPendant: return 36;\n                case GearType.dragonWings: return 37;\n                case GearType.dragonTail: return 38;\n                //machine\n                case GearType.machineTransistors: return 39;\n                case GearType.machineEngine: return 40;\n                case GearType.machineBody: return 41;\n                case GearType.machineArms: return 42;\n                case GearType.machineLegs: return 43;\n                //totem\n                case GearType.totem:\n                    switch (index)\n                    {\n                        case 0: return 44;\n                        case 1: return 45;\n                        case 2: return 46;\n                        default: return -1;\n                    }\n            }\n        }\n\n        public IEnumerable<Gear> GearsEquiped\n        {\n            get\n            {\n                foreach (Gear gear in gearSlots)\n                {\n                    if (gear != null)\n                        yield return gear;\n                }\n                foreach (Gear gear in cashGearSlots)\n                {\n                    if (gear != null)\n                        yield return gear;\n                }\n            }\n        }\n\n        public bool AddGear(Gear gear, out Gear[] removedGears)\n        {\n            if (gear == null)\n            {\n                removedGears = new Gear[0];\n                return false;\n            }\n            int emptyIdx = GetEmptySlotIndex(gear.type, gear.Cash);\n            return AddGear(gear, emptyIdx, out removedGears);\n        }\n\n        public bool AddGear(Gear gear, int index, out Gear[] removedGears)\n        {\n            if (gear == null)\n            {\n                removedGears = new Gear[0];\n                return false;\n            }\n            int slotIdx = GetGearSlot(gear.type, index);\n            if (slotIdx == -1)\n            {\n                removedGears = new Gear[0];\n                return false;\n            }\n            List<Gear> removedGearList = new List<Gear>();\n\n            Gear[] slotList = gear.Cash ? cashGearSlots : gearSlots;\n            if (slotList[slotIdx] != null) //移除同槽\n            {\n                removedGearList.Add(slotList[slotIdx]);\n            }\n            slotList[slotIdx] = gear; //装备上\n            Gear preRemove = getPreRemoveGears(gear.type, gear.Cash);\n            if (preRemove != null) //移除冲突装备\n            {\n                removedGearList.Add(preRemove);\n            }\n\n            removedGears = removedGearList.ToArray();\n            return true;\n        }\n\n        public int GetEmptySlotIndex(GearType gearType, bool cash)\n        {\n            Gear[] slotList = cash ? cashGearSlots : gearSlots;\n            int max;\n            switch (gearType)\n            {\n                case GearType.ring: max = RingCount; break;\n                case GearType.pendant: max = PendantCount; break;\n                default: return 0;\n            }\n            for (int i = 0; i < max; i++)\n            {\n                if (slotList[GetGearSlot(gearType, i)] == null)\n                    return i;\n            }\n            return 0;\n        }\n\n        private Gear getPreRemoveGears(GearType newGearType, bool cash)\n        {\n            Gear[] slotList = cash ? cashGearSlots : gearSlots;\n\n            if (Gear.IsDoubleHandWeapon(newGearType)) //双手 移除副手\n            {\n                Gear gear = slotList[GetGearSlot(GearType.shield, 0)];\n                if (gear != null)\n                    return gear;\n            }\n            else if (Gear.IsSubWeapon(newGearType)) //副手 移除双手\n            {\n                Gear gear = slotList[GetGearSlot(GearType.ohSword, 0)];\n                if (gear != null\n                    && (Gear.IsDoubleHandWeapon(gear.type)\n                    || (newGearType != GearType.magicArrow && gear.type == GearType.dualBow))) //非魔法箭 移除双弓的主手\n                    return gear;\n            }\n            else if (newGearType == GearType.dualBow) //双弩 移除非魔法箭的副手\n            {\n                Gear gear = slotList[GetGearSlot(GearType.magicArrow, 0)];\n                if (gear != null && gear.type != GearType.magicArrow)\n                {\n                    return gear;\n                }\n            }\n            else if (newGearType == GearType.pants) //下装 移除套服\n            {\n                Gear gear = slotList[GetGearSlot(GearType.longcoat, 0)];\n                if (gear != null && gear.type == GearType.longcoat)\n                {\n                    return gear;\n                }\n            }\n            else if (newGearType == GearType.pants) //套服 移除下装\n            {\n                Gear gear = slotList[GetGearSlot(GearType.pants, 0)];\n                if (gear != null && gear.type == GearType.pants)\n                {\n                    return gear;\n                }\n            }\n\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSim/CharaProp.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class CharaProp\n    {\n        public CharaProp()\n        {\n        }\n\n        public CharaProp(int totalMax)\n        {\n            this.totalMax = totalMax;\n        }\n\n        private int baseVal; //基础值\n        private int gearAdd; //装备附加值\n        private int buffAdd; //技能buff增加值\n        private int eBuffAdd; //技能增加的enhance值\n        private int rate; //装备潜能百分比\n        private int aBuffRate; //主动buff百分比 如骰子\n        private int pBuffRate; //被动buff百分比 如盾防精通\n        private int totalMax;\n        private bool smart; //当前的技能buff增加值是否为smart\n        \n        public int BaseVal\n        {\n            get { return baseVal; }\n            set { baseVal = value; }\n        }\n        \n        public int GearAdd\n        {\n            get { return gearAdd; }\n            set { gearAdd = value; }\n        }\n        \n        public int BuffAdd\n        {\n            get { return buffAdd; }\n            set { buffAdd = value; }\n        }\n        \n        public int EBuffAdd\n        {\n            get { return eBuffAdd; }\n            set { eBuffAdd = value; }\n        }\n        \n        public int Rate\n        {\n            get { return rate; }\n            set { rate = value; }\n        }\n\n        public int ABuffRate\n        {\n            get { return aBuffRate; }\n            set { aBuffRate = value; }\n        }\n        \n        public int PBuffRate\n        {\n            get { return pBuffRate; }\n            set { pBuffRate = value; }\n        }\n        \n        public bool Smart\n        {\n            get { return smart; }\n            set { smart = value; }\n        }\n\n        public int TotalMax\n        {\n            get { return totalMax; }\n            set { totalMax = value; }\n        }\n\n        public int GetSum()\n        {\n            int origSum = (baseVal + gearAdd + buffAdd + eBuffAdd) * (100 + rate + aBuffRate + pBuffRate) / 100;\n            return this.totalMax > 0 ? Math.Min(this.totalMax, origSum) : origSum;\n        }\n\n        public int GetGearReqSum()\n        {\n            int origSum = (baseVal + gearAdd + buffAdd) * (100 + rate + aBuffRate + pBuffRate) / 100;\n            return this.totalMax > 0 ? Math.Min(this.totalMax, origSum) : origSum;\n        }\n\n        public void ResetAdd()\n        {\n            gearAdd = 0;\n            eBuffAdd = 0;\n            buffAdd = 0;\n            rate = 0;\n            aBuffRate = 0;\n            pBuffRate = 0;\n            smart = false;\n        }\n\n        public void ResetAll()\n        {\n            baseVal = 0;\n            ResetAdd();\n        }\n\n        public override string ToString()\n        {\n            int sum = GetSum();\n            return baseVal == sum ? baseVal.ToString() :\n                string.Format(\"{0} ({1}+{2})\", sum, baseVal, sum - baseVal);\n        }\n\n        public string ToStringDetail(out int red)\n        {\n            int sum = GetSum();\n            int baseSum = (baseVal + gearAdd) +\n                (baseVal + gearAdd + buffAdd + eBuffAdd) * (rate + aBuffRate) / 100;\n            if (buffAdd == 0 && eBuffAdd == 0 && pBuffRate == 0 && baseSum <= sum)\n            {\n                red = Math.Sign(aBuffRate);\n                return baseSum.ToString();\n            }\n\n            red = Math.Sign(sum - baseSum);\n            return (sum == baseSum) ? sum.ToString() :\n                string.Format(\"{0} ({1}{2}{3})\", sum, baseSum, (sum - baseSum >= 0) ? \"+\" : \"-\", sum - baseSum);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSim/CharaSimLoader.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.PluginBase;\n\nnamespace WzComparerR2.CharaSim\n{\n    public static class CharaSimLoader\n    {\n        static CharaSimLoader()\n        {\n            LoadedSetItems = new Dictionary<int, SetItem>();\n            LoadedExclusiveEquips = new Dictionary<int, ExclusiveEquip>();\n        }\n\n        public static Dictionary<int, SetItem> LoadedSetItems { get; private set; }\n        public static Dictionary<int, ExclusiveEquip> LoadedExclusiveEquips { get; private set; }\n\n        public static void LoadSetItemsIfEmpty()\n        {\n            if (LoadedSetItems.Count == 0)\n            {\n                LoadSetItems();\n            }\n        }\n\n        public static void LoadSetItems()\n        {\n            //搜索setItemInfo.img\n            Wz_Node etcWz = PluginManager.FindWz(Wz_Type.Etc);\n            if (etcWz == null)\n                return;\n            Wz_Node setItemNode = etcWz.FindNodeByPath(\"SetItemInfo.img\", true);\n            if (setItemNode == null)\n                return;\n\n            //搜索ItemOption.img\n            Wz_Node itemWz = PluginManager.FindWz(Wz_Type.Item);\n            if (itemWz == null)\n                return;\n            Wz_Node optionNode = itemWz.FindNodeByPath(\"ItemOption.img\", true);\n            if (optionNode == null)\n                return;\n\n            LoadedSetItems.Clear();\n            foreach (Wz_Node node in setItemNode.Nodes)\n            {\n                int setItemIndex;\n                if (Int32.TryParse(node.Text, out setItemIndex))\n                {\n                    SetItem setItem = SetItem.CreateFromNode(node, optionNode);\n                    if (setItem != null)\n                        LoadedSetItems[setItemIndex] = setItem;\n                }\n            }\n        }\n\n        public static void LoadExclusiveEquipsIfEmpty()\n        {\n            if (LoadedExclusiveEquips.Count == 0)\n            {\n                LoadExclusiveEquips();\n            }\n        }\n\n        public static void LoadExclusiveEquips()\n        {\n            Wz_Node exclusiveNode = PluginManager.FindWz(\"Etc/ExclusiveEquip.img\");\n            if (exclusiveNode == null)\n                return;\n\n            LoadedExclusiveEquips.Clear();\n            foreach (Wz_Node node in exclusiveNode.Nodes)\n            {\n                int exclusiveEquipIndex;\n                if (Int32.TryParse(node.Text, out exclusiveEquipIndex))\n                {\n                    ExclusiveEquip exclusiveEquip = ExclusiveEquip.CreateFromNode(node);\n                    if (exclusiveEquip != null)\n                        LoadedExclusiveEquips[exclusiveEquipIndex] = exclusiveEquip;\n                }\n            }\n        }\n\n        public static void ClearAll()\n        {\n            LoadedSetItems.Clear();\n            LoadedExclusiveEquips.Clear();\n        }\n\n        public static int GetActionDelay(string actionName)\n        {\n            if (string.IsNullOrEmpty(actionName))\n            {\n                return 0;\n            }\n            Wz_Node actionNode = PluginManager.FindWz(\"Character/00002000.img/\" + actionName);\n            if (actionNode == null)\n            {\n                return 0;\n            }\n\n            int delay = 0;\n            foreach (Wz_Node frameNode in actionNode.Nodes)\n            {\n                Wz_Node delayNode = frameNode.Nodes[\"delay\"];\n                if (delayNode != null)\n                {\n                    delay += Math.Abs(delayNode.GetValue<int>());\n                }\n            }\n\n            return delay;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSim/Character.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Character\n    {\n        public Character()\n        {\n            this.status = new CharacterStatus();\n            this.status.Job = 0;\n            this.status.Level = 1;\n            this.status.MaxHP.BaseVal = 50;\n            this.status.HP = 50;\n            this.status.MaxMP.BaseVal = 10;\n            this.status.MP = 10;\n            this.status.Strength.BaseVal = 12;\n            this.status.Dexterity.BaseVal = 5;\n            this.status.Intelligence.BaseVal = 4;\n            this.status.Luck.BaseVal = 4;\n\n            this.status.CriticalRate.BaseVal = 5;\n            this.status.MoveSpeed.BaseVal = 100;\n            this.status.Jump.BaseVal = 100;\n            this.status.CriticalDamageMax.BaseVal = 150;\n            this.status.CriticalDamageMin.BaseVal = 120;\n\n            this.itemSlots = new ItemBase[5][];\n            for (int i = 0; i < this.itemSlots.Length; i++)\n            {\n                this.itemSlots[i] = new ItemBase[96];\n            }\n\n            this.equip = new CharaEquip();\n        }\n\n        private static FormulaVersion version;\n\n        /// <summary>\n        /// 获取或设置角色属性计算的公式版本。\n        /// </summary>\n        public static FormulaVersion Version\n        {\n            get { return Character.version; }\n            set { Character.version = value; }\n        }\n\n        private string name;\n        private string guild;\n        private CharacterStatus status;\n        private ItemBase[][] itemSlots;\n        private CharaEquip equip;\n\n        public string Name\n        {\n            get { return name; }\n            set { name = value; }\n        }\n\n        public string Guild\n        {\n            get { return guild; }\n            set { guild = value; }\n        }\n\n        public CharacterStatus Status\n        {\n            get { return status; }\n        }\n\n        public ItemBase[][] ItemSlots\n        {\n            get { return itemSlots; }\n        }\n        public CharaEquip Equip\n        {\n            get { return equip; }\n            set { equip = value; }\n        }\n\n        public void UpdateProps()\n        {\n            status.Strength.ResetAdd();\n            status.Dexterity.ResetAdd();\n            status.Intelligence.ResetAdd();\n            status.Luck.ResetAdd();\n            status.MaxHP.ResetAdd();\n            status.MaxMP.ResetAdd();\n\n            status.PADamage.ResetAll();\n            status.MADamage.ResetAll();\n            status.PDDamage.ResetAll();\n            status.MDDamage.ResetAll();\n            status.PAccurate.ResetAll();\n            status.MAccurate.ResetAll();\n            status.PEvasion.ResetAll();\n            status.MEvasion.ResetAll();\n\n            status.MoveSpeed.ResetAdd();\n            status.Jump.ResetAdd();\n            status.CriticalRate.ResetAdd();\n            status.CriticalDamageMax.ResetAdd();\n            status.CriticalDamageMin.ResetAdd();\n            status.DamageRate.ResetAll();\n\n            //foreach (Buff buff in buffs)\n            //{\n            //    foreach (KeyValuePair<GearPropType, int> prop in buff.props)\n            //    {\n            //        AddBuffProp(prop.Key, prop.Value, buff.Type == BuffType.passiveSkill);\n            //    }\n            //}\n\n            foreach (Gear gear in equip.GearsEquiped)\n            {\n                if (gear.State == GearState.enable)\n                {\n                    foreach (KeyValuePair<GearPropType, int> prop in gear.Props)\n                    {\n                        addProp(prop.Key, prop.Value);\n                    }\n                    foreach (Potential potential in gear.Options)\n                    {\n                        if (potential != null)\n                        {\n                            foreach (KeyValuePair<GearPropType, int> prop in potential.props)\n                            {\n                                addProp(prop.Key, prop.Value);\n                            }\n                        }\n                    }\n                    foreach (Addition addition in gear.Additions)\n                    {\n                        foreach (KeyValuePair<GearPropType, int> prop in getAdditionProps(addition))\n                        {\n                            addProp(prop.Key, prop.Value);\n                        }\n                    }\n                }\n            }\n\n            checkSetItemEnabled();\n            foreach (SetItem setItem in CharaSimLoader.LoadedSetItems.Values)\n            {\n                foreach (SetItemEffect effect in setItem.Effects.Values)\n                {\n                    if (effect.Enabled)\n                    {\n                        foreach (KeyValuePair<GearPropType, object> prop in effect.Props)\n                        {\n                            if (prop.Key == GearPropType.Option)\n                            {\n                                List<Potential> potens = prop.Value as List<Potential>;\n                                foreach (Potential p in potens)\n                                {\n                                    foreach (KeyValuePair<GearPropType, int> pprop in p.props)\n                                    {\n                                        addProp(pprop.Key, pprop.Value);\n                                    }\n                                }\n                            }\n                            else\n                            {\n                                addProp(prop.Key, Convert.ToInt32(prop.Value));\n                            }\n                        }\n                    }\n                }\n            }\n\n            int[] sum = new int[4] { status.Strength.GetSum(),\n                status.Dexterity.GetSum(),\n                status.Intelligence.GetSum(),\n                status.Luck.GetSum() };\n            if (version == FormulaVersion.Bigbang)\n            {\n                status.PDDamage.BaseVal = (int)Math.Floor(sum[0] * 1.2 + sum[1] * 0.5 + sum[2] * 0.4 + sum[3] * 0.5);\n                status.MDDamage.BaseVal = (int)Math.Floor(sum[0] * 0.4 + sum[1] * 0.5 + sum[2] * 1.2 + sum[3] * 0.5);\n                status.PAccurate.BaseVal = (int)Math.Floor(sum[1] * 1.2 + sum[3] * 1.0);\n                status.MAccurate.BaseVal = (int)Math.Floor(sum[2] * 1.2 + sum[3] * 1.0);\n                status.PEvasion.BaseVal = sum[1] * 1 + sum[3] * 2;\n                status.MEvasion.BaseVal = sum[2] * 1 + sum[3] * 2;\n            }\n            else if (version == FormulaVersion.Chaos)\n            {\n                status.PDDamage.BaseVal = (int)Math.Floor(sum[0] * 1.5 + sum[1] * 0.4 + sum[2] * 0 + sum[3] * 0.4);\n                status.MDDamage.BaseVal = (int)Math.Floor(sum[0] * 0 + sum[1] * 0.4 + sum[2] * 1.5 + sum[3] * 0.4);\n                status.PAccurate.BaseVal = (int)Math.Floor(sum[0] * 0.4 + sum[1] * 1.6 + sum[2] * 0 + sum[3] * 0.8);\n                status.MAccurate.BaseVal = (int)Math.Floor(sum[0] * 0 + sum[1] * 0.4 + sum[2] * 1.6 + sum[3] * 0.8);\n                status.PEvasion.BaseVal = (int)Math.Floor(sum[0] * 0.2 + sum[1] * 0.6 + sum[2] * 0 + sum[3] * 1.4);\n                status.MEvasion.BaseVal = (int)Math.Floor(sum[0] * 0 + sum[1] * 0.2 + sum[2] * 0.6 + sum[3] * 1.4);\n            }\n        }\n\n        private void addProp(GearPropType type, int value)\n        {\n            switch (type)\n            {\n                case GearPropType.incSTR: status.Strength.GearAdd += value; break;\n                case GearPropType.incSTRr: status.Strength.Rate += value; break;\n                case GearPropType.incDEX: status.Dexterity.GearAdd += value; break;\n                case GearPropType.incDEXr: status.Dexterity.Rate += value; break;\n                case GearPropType.incINT: status.Intelligence.GearAdd += value; break;\n                case GearPropType.incINTr: status.Intelligence.Rate += value; break;\n                case GearPropType.incLUK: status.Luck.GearAdd += value; break;\n                case GearPropType.incLUKr: status.Luck.Rate += value; break;\n                case GearPropType.incAllStat:\n                    status.Strength.GearAdd += value;\n                    status.Dexterity.GearAdd += value;\n                    status.Intelligence.GearAdd += value;\n                    status.Luck.GearAdd += value;\n                    break;\n                case GearPropType.incPAD: status.PADamage.GearAdd += value; break;\n                case GearPropType.incPADr: status.PADamage.Rate += value; break;\n                case GearPropType.incPDD: status.PDDamage.GearAdd += value; break;\n                case GearPropType.incPDDr: status.PDDamage.Rate += value; break;\n                case GearPropType.incMAD: status.MADamage.GearAdd += value; break;\n                case GearPropType.incMADr: status.MADamage.Rate += value; break;\n                case GearPropType.incMDD: status.MDDamage.GearAdd += value; break;\n                case GearPropType.incMDDr: status.MDDamage.Rate += value; break;\n\n                case GearPropType.incACC: status.PAccurate.GearAdd += value; status.MAccurate.GearAdd += value; break;\n                case GearPropType.incACCr: status.PAccurate.Rate += value; status.MAccurate.Rate += value; break;\n                case GearPropType.incEVA: status.PEvasion.GearAdd += value; status.MEvasion.GearAdd += value; break;\n                case GearPropType.incEVAr: status.PEvasion.Rate += value; status.MEvasion.Rate += value; break;\n                case GearPropType.incCr: status.CriticalRate.GearAdd += value; break;\n\n                case GearPropType.incMHP: status.MaxHP.GearAdd += value; break;\n                case GearPropType.incMHPr: status.MaxHP.Rate += value; break;\n                case GearPropType.incMMP: status.MaxMP.GearAdd += value; break;\n                case GearPropType.incMMPr: status.MaxMP.Rate += value; break;\n                case GearPropType.incSpeed: status.MoveSpeed.GearAdd += value; break;\n                case GearPropType.incJump: status.Jump.GearAdd += value; break;\n\n                case GearPropType.incCriticaldamageMax: status.CriticalDamageMax.GearAdd += value; break;\n                case GearPropType.incCriticaldamageMin: status.CriticalDamageMin.GearAdd += value; break;\n            }\n        }\n\n        private Dictionary<GearPropType, int> getAdditionProps(Addition addition)\n        {\n            Dictionary<GearPropType, int> props = new Dictionary<GearPropType, int>();\n            if (addition != null &&\n                (addition.Type == AdditionType.critical || addition.Type == AdditionType.statinc))\n            {\n                bool con = false;\n                switch (addition.ConType)\n                {\n                    case GearPropType.reqLevel:\n                        con = (this.status.Level >= addition.ConValue[0]);\n                        break;\n                    case GearPropType.reqJob:\n                        foreach (int val in addition.ConValue)\n                        {\n                            con |= this.status.Job == val;\n                        }\n                        break;\n                    case GearPropType.reqCraft:\n                    default:\n                        con = true;\n                        break;\n                }\n                if (con)\n                {\n                    string strcr; int cr;\n                    if (addition.Props.TryGetValue(\"prob\", out strcr) && Int32.TryParse(strcr, out cr))\n                        props.Add(GearPropType.incCr, cr);\n\n                    if (addition.Type == AdditionType.statinc)\n                    {\n                        foreach (var kv in addition.Props)\n                        {\n                            try\n                            {\n                                GearPropType propType = (GearPropType)Enum.Parse(typeof(GearPropType), kv.Key);\n                                if ((int)propType > 0 && (int)propType < 100)\n                                    props.Add(propType, Convert.ToInt32(kv.Value));\n                            }\n                            catch\n                            {\n                            }\n                        }\n                    }\n                }\n            }\n            return props;\n        }\n\n        private void checkSetItemEnabled()\n        {\n            //重置所有setItem\n            List<int> idList = new List<int>();\n            foreach (SetItem setItem in CharaSimLoader.LoadedSetItems.Values)\n            {\n                foreach (KeyValuePair<int, SetItemIDPart> idPart in setItem.ItemIDs.Parts)\n                {\n                    idList.AddRange(idPart.Value.ItemIDs.Keys);\n                    foreach (int id in idList)\n                    {\n                        idPart.Value.ItemIDs[id] = false;\n                    }\n                    idList.Clear();\n                }\n                setItem.currentCount = 0;\n            }\n            //验证有效装备\n            int setItemID;\n            foreach (Gear gear in equip.GearsEquiped)\n            {\n                if (gear.State == GearState.enable\n                    && gear.Props.TryGetValue(GearPropType.setItemID, out setItemID))\n                {\n                    CharaSimLoader.LoadedSetItems[setItemID].ItemIDs[gear.ItemID] = true;\n                    CharaSimLoader.LoadedSetItems[setItemID].currentCount++;\n                }\n            }\n            //验证所有setItem\n            foreach (SetItem setItem in CharaSimLoader.LoadedSetItems.Values)\n            {\n                foreach (KeyValuePair<int, SetItemEffect> effect in setItem.Effects)\n                {\n                    effect.Value.Enabled = (setItem.currentCount >= effect.Key);\n                }\n            }\n        }\n\n        public void ChangeGear(Gear newGear)\n        {\n            int emptyIdx = this.equip.GetEmptySlotIndex(newGear.type, newGear.Cash);\n            ChangeGear(newGear, emptyIdx);\n        }\n\n        public void ChangeGear(Gear newGear, int index)\n        {\n            ItemBase[] itemTab = this.itemSlots[0];\n            int newGearIndex = Array.IndexOf<ItemBase>(itemTab, newGear);\n            if (newGearIndex < 0 || newGear.State != GearState.itemList)\n            {\n                throw new InvalidOperationException(\"未知错误：装备不在背包。\");\n            }\n\n            int onlyEquip;\n            if (newGear.Props.TryGetValue(GearPropType.onlyEquip, out onlyEquip) && onlyEquip > 0)\n            {\n                foreach (Gear gear in this.equip.GearsEquiped)\n                {\n                    if (gear.ItemID == newGear.ItemID)\n                    {\n                        throw new InvalidOperationException(\"该道具只能同时装备一个。\");\n                    }\n                }\n            }\n\n            string errorString;\n            if (!checkGearReq(newGear, out errorString))\n            {\n                throw new InvalidOperationException(errorString);\n            }\n\n            Gear[] removedGear;\n            if (!this.equip.AddGear(newGear, out removedGear))\n            {\n                throw new InvalidOperationException(\"未知错误：添加装备失败。\");\n            }\n\n            CheckGearEnabled();\n\n            if (newGear.State == GearState.enable)\n            {\n                Queue<int> emptyItemSlot = new Queue<int>();\n                emptyItemSlot.Enqueue(newGearIndex);\n\n                if (removedGear.Length > 1) //检查剩余背包大小\n                {\n                    for (int i = 0; i < itemTab.Length; i++)\n                    {\n                        if (itemTab[i] == null)\n                        {\n                            emptyItemSlot.Enqueue(i);\n                        }\n                    }\n                }\n                if (emptyItemSlot.Count >= removedGear.Length)\n                {\n                    for (int i = 0; i < removedGear.Length; i++)\n                    {\n                        Gear gear = removedGear[i];\n                        gear.State = GearState.itemList;\n                        itemTab[emptyItemSlot.Dequeue()] = gear;\n                    }\n                    return; //函数出口\n                }\n                else\n                {\n                    errorString = \"背包已满。\";\n                }\n            }\n            else\n            {\n                errorString = \"能力值不足，无法装备道具。\";\n            }\n\n            //还原装备\n            foreach (Gear gear in removedGear)\n            {\n                Gear[] arg;\n                this.equip.AddGear(gear, index, out arg); //可以证明直接输入index是可以还原的。\n            }\n            newGear.State = GearState.itemList;\n            throw new InvalidOperationException(errorString);\n        }\n\n        private bool checkGearReq(Gear gear, out string errorMessage)\n        {\n            if (Gear.IsMechanicGear(gear.type) && status.Job / 100 != 35)\n            {\n                errorMessage = \"只有机械师才能装备。\";\n                return false;\n            }\n            if (Gear.IsDragonGear(gear.type) && status.Job / 100 != 22)\n            {\n                errorMessage = \"只有龙神才能装备。\";\n                return false;\n            }\n            if (gear.type == GearType.katara && status.Job / 10 != 43)\n            {\n                errorMessage = \"只有暗影双刀才能装备。\";\n                return false;\n            }\n            if (gear.type == GearType.shield &&\n                (status.Job / 10 == 43 || status.Job / 100 == 23 || status.Job / 100 == 31))\n            {\n                errorMessage = \"该职业无法装备盾牌。\";\n                return false;\n            }\n            if (gear.type == GearType.magicArrow && status.Job / 100 != 23)\n            {\n                errorMessage = \"只有双弩精灵职业才能装备。\";\n                return false;\n            }\n            if (gear.type == GearType.demonShield && status.Job / 100 != 31)\n            {\n                errorMessage = \"只有恶魔猎手职业才能装备。\";\n                return false;\n            }\n            if (!checkGearPropReq(gear))\n            {\n                errorMessage = \"能力值不足，无法装备道具。\";\n                return false;\n            }\n            errorMessage = null;\n            return true;\n        }\n\n        private bool checkGearPropReq(Gear gear)\n        {\n            return checkGearPropReq(gear.Props, GearPropType.reqSTR, status.Strength.GetGearReqSum())\n                && checkGearPropReq(gear.Props, GearPropType.reqDEX, status.Dexterity.GetGearReqSum())\n                && checkGearPropReq(gear.Props, GearPropType.reqINT, status.Intelligence.GetGearReqSum())\n                && checkGearPropReq(gear.Props, GearPropType.reqLUK, status.Luck.GetGearReqSum())\n                && checkGearPropReq(gear.Props, GearPropType.reqLevel, status.Level)\n                && checkGearPropReq(gear.Props, GearPropType.reqPOP, status.Pop)\n                && checkGearJobReq(gear.Props, gear.type);\n        }\n\n        private bool checkGearPropReq(Dictionary<GearPropType, int> props, GearPropType prop, int value)\n        {\n            int v;\n            if (!props.TryGetValue(prop, out v) || value >= v)\n            {\n                return true;\n            }\n            return false;\n        }\n\n        private bool checkGearJobReq(Dictionary<GearPropType, int> props, GearType type)\n        {\n            int reqJob;\n            props.TryGetValue(GearPropType.reqJob, out reqJob);\n            int jobClass = status.Job % 1000 / 100;\n            if (reqJob == 0) //全职\n                return true;\n            if (reqJob == -1) //新手\n                return jobClass == 0;\n            return (reqJob & (1 << (jobClass - 1))) != 0;\n        }\n\n        /// <summary>\n        /// 检查指定的职业ID是否归属于标准职业。\n        /// </summary>\n        /// <param name=\"jobID\">要检查的职业ID。</param>\n        /// <param name=\"baseJob\">标准职业代码。0-新手 1-战士 2-法师 3-弓手 4-飞侠 5-海盗</param>\n        /// <returns></returns>\n        public static bool CheckJobReq(int jobID, int baseJob)\n        {\n            switch (jobID / 100)\n            {\n                case 27: return baseJob == 2; //夜光\n                case 36: return baseJob == 4 || baseJob == 5; //煎饼\n                default:\n                    return jobID / 100 % 10 == baseJob;\n            }\n        }\n\n        public bool CheckGearEnabled()\n        {\n            List<Gear> gearsEquip = new List<Gear>(this.equip.GearsEquiped);\n            List<GearState> oldStates = new List<GearState>(gearsEquip.Count);\n            foreach (Gear gear in gearsEquip)\n            {\n                oldStates.Add(gear.State);\n                gear.State = GearState.enable;\n            }\n\n            while (true)\n            {\n                bool reset = false;\n                //逐个装备判定装备要求\n                foreach (Gear gear in gearsEquip)\n                {\n                    if (gear.State == GearState.enable)\n                    {\n                        gear.State = GearState.disable;\n                        UpdateProps();\n\n                        //判定装备要求\n                        if (!checkGearPropReq(gear))\n                        {\n                            reset = true; //如果不符合 无效化装备 进行下一轮判断\n                            break;\n                        }\n\n                        //恢复有效性\n                        gear.State = GearState.enable;\n                    }\n                }\n                if (!reset) //如果本轮判断没变化则停止 可以证明是不会进入死循环的\n                    break;\n            }\n\n            for (int i = 0; i < gearsEquip.Count; i++)\n            {\n                if (gearsEquip[i].State != oldStates[i]) //装备状态变化\n                {\n                    return true;\n                }\n            }\n\n            return false;\n        }\n\n        public void CalcAttack(out double max, out double min)\n        {\n            int sign;\n            CalcAttack(out max, out min, out sign);\n        }\n\n        public void CalcAttack(out double max, out double min, out int sign)\n        {\n            max = CalcAttack(status.Strength.GetSum(),\n                status.Dexterity.GetSum(),\n                status.Intelligence.GetSum(),\n                status.Luck.GetSum(),\n                status.PADamage.GetSum(),\n                status.MADamage.GetSum(),\n                GearType.totem,\n                version);\n            min = max * status.Mastery.GetSum() / 100; \n            sign = 0;\n        }\n\n        public static double CalcAttack(int str, int dex, int inte, int luk, int pad, int mad,\n            GearType WeaponType, FormulaVersion version)\n        {\n            switch (WeaponType)\n            {\n                case GearType.ohSword:\n                case GearType.ohAxe:\n                case GearType.ohBlunt:\n                    return (str * 4 + dex) * 1.2 * pad * 0.01;\n                case GearType.dagger:\n                    return (str + dex + luk * 4) * 1.3 * pad * 0.01;\n                case GearType.cane:\n                    return (dex + luk * 4) * 1.3 * pad * 0.01;\n                case GearType.wand:\n                case GearType.staff:\n                    return (inte * 4 + luk) * 1.0 * mad * 0.01;\n                case GearType.barehand:\n                    return (str * 4 + dex) * 1.43 * 1 * 0.01;\n                case GearType.thSword:\n                case GearType.thAxe:\n                case GearType.thBlunt:\n                    if (version == FormulaVersion.Bigbang)\n                        return (str * 4 + dex) * 1.32 * pad * 0.01;\n                    else if (version == FormulaVersion.Chaos)\n                        return (str * 4 + dex) * 1.34 * pad * 0.01;\n                    break;\n                case GearType.spear:\n                case GearType.polearm:\n                    return (str * 4 + dex) * 1.49 * pad * 0.01;\n                case GearType.bow:\n                    if (version == FormulaVersion.Bigbang)\n                        return (dex * 4 + str) * 1.2 * pad * 0.01;\n                    else if (version == FormulaVersion.Chaos)\n                        return (dex * 4 + str) * 1.3 * pad * 0.01;\n                    break;\n\n                case GearType.crossbow:\n                    return (dex * 4 + str) * 1.35 * pad * 0.01;\n                case GearType.throwingGlove:\n                    return (dex + luk * 4) * 1.75 * pad * 0.01;\n                case GearType.knuckle:\n                    return (str * 4 + dex) * 1.7 * pad * 0.01;\n                case GearType.gun:\n                    return (dex * 4 + str) * 1.5 * pad * 0.01;\n                case GearType.dualBow:\n                    return (dex * 4 + str) * 1.3 * pad * 0.01;\n                case GearType.handCannon:\n                    return (str * 4 + dex) * 1.5 * pad * 0.01;\n            }\n            return 0;\n        }\n\n        private static int[] _exptnl = new int[]\n        {\n            15,34,57,92,\n            135,372,560,840,\n            1242,19136,479143,10063200\n        };\n\n        public static int ExpToNextLevel(int level)\n        {\n            int exp;\n            if (level < 1 || level > 200)\n                return -1;\n            if (level < 10)\n                return _exptnl[level - 1];\n            if (level >= 10 && level <= 14)\n                return _exptnl[8];\n            if (level >= 15 && level <= 29)\n            {\n                exp = _exptnl[8]; //level 10\n                while (level > 14)\n                {\n                    exp = (int)Math.Round(exp * 1.2, MidpointRounding.AwayFromZero);\n                    level -= 1;\n                }\n                return exp;\n            }\n            if (level >= 30 && level <= 34)\n            {\n                return _exptnl[9];//level 30\n            }\n            if (level >= 35 && level <= 39)\n            {\n                exp = ExpToNextLevel(34);\n                while (level > 34)\n                {\n                    exp = (int)Math.Round(exp * 1.2, MidpointRounding.AwayFromZero);\n                    level -= 1;\n                }\n                return exp;\n            }\n            if (level >= 40 && level <= 69)\n            {\n                exp = ExpToNextLevel(39);\n                while (level > 39)\n                {\n                    exp = (int)Math.Round(exp * 1.08, MidpointRounding.AwayFromZero);\n                    level -= 1;\n                }\n                return exp;\n            }\n            if (level >= 70 && level <= 74)\n            {\n                return _exptnl[10];//level 70\n            }\n            if (level >= 75 && level <= 119)\n            {\n                exp = ExpToNextLevel(74);\n                while (level > 74)\n                {\n                    exp = (int)Math.Round(exp * 1.07, MidpointRounding.AwayFromZero);\n                    level -= 1;\n                }\n                return exp;\n            }\n            if (level >= 120 && level <= 124)\n            {\n                return _exptnl[11];//level 120\n            }\n            if (level >= 125 && level <= 159)\n            {\n                exp = _exptnl[11];\n                while (level > 124)\n                {\n                    exp = (int)Math.Round(exp * 1.07, MidpointRounding.AwayFromZero);\n                    level -= 1;\n                }\n                return exp;\n            }\n            if (level >= 160 && level <= 199)\n            {\n                exp = ExpToNextLevel(159);\n                while (level > 159)\n                {\n                    exp = (int)Math.Round(exp * 1.06, MidpointRounding.AwayFromZero);\n                    level -= 1;\n                }\n                return exp;\n            }\n            return -1;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSim/CharacterStatus.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Reflection;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class CharacterStatus\n    {\n        public CharacterStatus()\n        {\n            this.maxHP = new CharaProp(99999);\n            this.maxMP = new CharaProp(99999);\n            this.pdd = new CharaProp(9999);\n            this.mdd = new CharaProp(9999);\n            this.pAcc = new CharaProp(9999);\n            this.mAcc = new CharaProp(9999);\n            this.pEva = new CharaProp(9999);\n            this.mEva = new CharaProp(9999);\n\n            FieldInfo[] fields = this.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic);\n            foreach (FieldInfo f in fields)\n            {\n                if (f.FieldType == typeof(CharaProp) && f.GetValue(this) == null)\n                {\n                    f.SetValue(this, new CharaProp());\n                }\n            }\n        }\n\n        private int job;\n        private int level;\n        private int hp;\n        private int mp;\n        private int exp;\n        private int ap;\n        private int pop;\n\n        private CharaProp maxHP;\n        private CharaProp maxMP;\n        private CharaProp str = null;\n        private CharaProp dex = null;\n        private CharaProp inte = null;\n        private CharaProp luk = null;\n\n        private CharaProp pad = null;\n        private CharaProp mad = null;\n        private CharaProp pdd;\n        private CharaProp mdd;\n        private CharaProp pAcc;\n        private CharaProp mAcc;\n        private CharaProp pEva;\n        private CharaProp mEva;\n        private CharaProp crit = null;\n        private CharaProp move = null;\n        private CharaProp jump = null;\n        private CharaProp critDamMax = null;\n        private CharaProp critDamMin = null;\n        private CharaProp mastery = null;\n        private CharaProp damR = null;\n        private CharaProp bossDamR = null;\n\n        #region 基础属性\n        /// <summary>\n        /// 获取或设置角色的职业代码。\n        /// </summary>\n        public int Job\n        {\n            get { return job; }\n            set { job = value; }\n        }\n        \n        /// <summary>\n        /// 获取或设置角色的等级。\n        /// </summary>\n        public int Level\n        {\n            get { return level; }\n            set { level = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置角色的当前HP。\n        /// </summary>\n        public int HP\n        {\n            get { hp = Math.Max(0, Math.Min(maxHP.GetSum(), hp)); return hp; }\n            set { value = Math.Max(0, Math.Min(maxHP.GetSum(), value)); hp = value; }\n        }\n\n        /// <summary>\n        /// 获取角色的HP上限。\n        /// </summary>\n        public CharaProp MaxHP\n        {\n            get { return maxHP; }\n        }\n        \n        /// <summary>\n        /// 获取或设置角色的当前MP。\n        /// </summary>\n        public int MP\n        {\n            get { mp = Math.Max(0, Math.Min(maxMP.GetSum(), mp)); return mp; }\n            set { value = Math.Max(0, Math.Min(maxMP.GetSum(), value)); mp = value; }\n        }\n\n        /// <summary>\n        /// 获取角色的MP上限。\n        /// </summary>\n        public CharaProp MaxMP\n        {\n            get { return maxMP; }\n        }\n        \n        /// <summary>\n        /// 获取或设置角色的当前经验值。\n        /// </summary>\n        public int Exp\n        {\n            get { exp = (Exptnl == -1) ? -1 : Math.Max(0, Math.Min(Exptnl - 1, exp)); return exp; }\n            set { value = (Exptnl == -1) ? -1 : Math.Max(0, Math.Min(Exptnl - 1, value)); exp = value; }\n        }\n\n        /// <summary>\n        /// 获取角色当前升级经验值。\n        /// </summary>\n        public int Exptnl\n        {\n            get { return Character.ExpToNextLevel(this.level); }\n        }\n\n        /// <summary>\n        /// 获取或设置角色的人气度。\n        /// </summary>\n        public int Pop\n        {\n            get { return pop; }\n            set { pop = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置角色的可分配AP。\n        /// </summary>\n        public int Ap\n        {\n            get { return ap; }\n            set { if (value >= 0)ap = value; }\n        }\n\n        /// <summary>\n        /// 获取角色的力量值。\n        /// </summary>\n        public CharaProp Strength\n        {\n            get { return str; }\n        }\n        \n        /// <summary>\n        /// 获取角色的敏捷值。\n        /// </summary>\n        public CharaProp Dexterity\n        {\n            get { return dex; }\n        }\n        \n        /// <summary>\n        /// 获取角色的智力值。\n        /// </summary>\n        public CharaProp Intelligence\n        {\n            get { return inte; }\n        }\n\n        /// <summary>\n        /// 获取角色的运气值。\n        /// </summary>\n        public CharaProp Luck\n        {\n            get { return luk; }\n        }\n        #endregion\n\n        #region 扩展属性\n        /// <summary>\n        /// 获取角色的攻击力，这是一个隐藏属性。\n        /// </summary>\n        public CharaProp PADamage\n        {\n            get { return pad; }\n        }\n        \n        /// <summary>\n        /// 获取角色的魔法攻击力，这是一个隐藏属性。\n        /// </summary>\n        public CharaProp MADamage\n        {\n            get { return mad; }\n        }\n        \n        /// <summary>\n        /// 获取角色的物理防御力。\n        /// </summary>\n        public CharaProp PDDamage\n        {\n            get { return pdd; }\n        }\n\n        /// <summary>\n        /// 获取角色的魔法防御力。\n        /// </summary>\n        public CharaProp MDDamage\n        {\n            get { return mdd; }\n        }\n       \n        /// <summary>\n        /// 获取角色的物理命中率。\n        /// </summary>\n        public CharaProp PAccurate\n        {\n            get { return pAcc; }\n        }\n        \n        /// <summary>\n        /// 获取角色的魔法命中率。\n        /// </summary>\n        public CharaProp MAccurate\n        {\n            get { return mAcc; }\n        }\n        \n        /// <summary>\n        /// 获取角色的物理回避率。\n        /// </summary>\n        public CharaProp PEvasion\n        {\n            get { return pEva; }\n        }\n        \n        /// <summary>\n        /// 获取角色的魔法回避率。\n        /// </summary>\n        public CharaProp MEvasion\n        {\n            get { return mEva; }\n        }\n        \n        /// <summary>\n        /// 获取角色的暴击率，这是一个百分比属性。\n        /// </summary>\n        public CharaProp CriticalRate\n        {\n            get { return crit; }\n        }\n        \n        /// <summary>\n        /// 获取角色的移动速度，这是一个百分比属性。\n        /// </summary>\n        public CharaProp MoveSpeed\n        {\n            get { return move; }\n        }\n        \n        /// <summary>\n        /// 获取角色的跳跃力，这是一个百分比属性。\n        /// </summary>\n        public CharaProp Jump\n        {\n            get { return jump; }\n        }\n        \n        /// <summary>\n        /// 获取角色的暴击最大伤害，这是一个隐藏的百分比属性。\n        /// </summary>\n        public CharaProp CriticalDamageMax\n        {\n            get { return critDamMax; }\n        }\n        \n        /// <summary>\n        /// 获取角色的暴击最小伤害，这是一个隐藏的百分比属性。\n        /// </summary>\n        public CharaProp CriticalDamageMin\n        {\n            get { return critDamMin; }\n        }\n        \n        /// <summary>\n        /// 获取角色的攻击熟练度，这是一个隐藏的百分比属性。\n        /// </summary>\n        public CharaProp Mastery\n        {\n            get { return mastery; }\n        }\n        \n        /// <summary>\n        /// 获取角色的攻击力百分比加成，这是一个隐藏的百分比属性。\n        /// </summary>\n        public CharaProp DamageRate\n        {\n            get { return damR; }\n        }\n\n        /// <summary>\n        /// 获取角色的BOSS攻击力百分比加成，这是一个隐藏的百分比属性。\n        /// </summary>\n        public CharaProp BossDamageRate\n        {\n            get { return bossDamR; }\n        }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/AControl.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Windows.Forms;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public abstract class AControl\n    {\n        public AControl()\n        {\n        }\n\n        private Point location;\n        private Size size;\n        private bool visible;\n\n        public Point Location\n        {\n            get { return location; }\n            set { location = value; }\n        }\n\n        public Size Size\n        {\n            get { return size; }\n            set { size = value; }\n        }\n\n        public bool Visible\n        {\n            get { return visible; }\n            set { visible = value; }\n        }\n\n        public Rectangle Rectangle\n        {\n            get { return new Rectangle(this.location, this.size); }\n        }\n\n        public abstract void Draw(Graphics g);\n\n        public virtual void OnMouseClick(MouseEventArgs e)\n        {\n            if (IsMouseContains(e.Location))\n            {\n                if (this.MouseClick != null)\n                    this.MouseClick(this, e);\n            }\n        }\n\n        public virtual void OnMouseDown(MouseEventArgs e)\n        {\n        }\n\n        public virtual void OnMouseUp(MouseEventArgs e)\n        {\n        }\n\n        public virtual void OnMouseMove(MouseEventArgs e)\n        {\n        }\n\n        public virtual void OnMouseWheel(MouseEventArgs e)\n        {\n        }\n\n        protected virtual bool IsMouseContains(Point mouseLocation)\n        {\n            return this.visible && this.Rectangle.Contains(mouseLocation);\n        }\n\n        protected MouseEventArgs ToChildEventargs(MouseEventArgs e)\n        {\n            return new MouseEventArgs(e.Button, e.Clicks, e.X - this.Location.X, e.Y - this.Location.Y, e.Delta);\n        }\n\n        public event MouseEventHandler MouseClick;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/ACtrlButton.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Windows.Forms;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class ACtrlButton : AControl\n    {\n        public ACtrlButton()\n        {\n            this.Visible = true;\n        }\n\n        private BitmapOrigin normal;\n        private BitmapOrigin pressed;\n        private BitmapOrigin mouseOver;\n        private BitmapOrigin disabled;\n\n        private ButtonState state;\n\n        public BitmapOrigin Normal\n        {\n            get { return normal; }\n            set { normal = value; }\n        }\n\n        public BitmapOrigin Pressed\n        {\n            get { return pressed; }\n            set { pressed = value; }\n        }\n\n        public BitmapOrigin MouseOver\n        {\n            get { return mouseOver; }\n            set { mouseOver = value; }\n        }\n\n        public BitmapOrigin Disabled\n        {\n            get { return disabled; }\n            set { disabled = value; }\n        }\n\n        public ButtonState State\n        {\n            get { return state; }\n            set\n            {\n                if (state != value)\n                {\n                    state = value;\n                    OnButtonStateChanged();\n                }\n            }\n        }\n\n        public BitmapOrigin CurrentBitmap\n        {\n            get\n            {\n                switch (this.state)\n                {\n                    default:\n                    case ButtonState.Normal: return this.normal;\n                    case ButtonState.Pressed: return this.pressed;\n                    case ButtonState.MouseOver: return this.mouseOver;\n                    case ButtonState.Disabled: return this.disabled;\n                }\n            }\n        }\n\n        /// <summary>\n        /// 应用当前的ButtonState和Location，绘制对应的图像。\n        /// </summary>\n        /// <param Name=\"g\">要绘制的绘图表面。</param>\n        public override void Draw(Graphics g)\n        {\n            this.Draw(g, new Point(0, 0));\n        }\n\n        /// <summary>\n        /// 应用当前的ButtonState、Location以及给定的坐标偏移，绘制对应的图像。\n        /// </summary>\n        /// <param Name=\"g\">要绘制的绘图表面。</param>\n        /// <param Name=\"Offset\">表示对于绘图原点的坐标偏移。</param>\n        public void Draw(Graphics g, Point offset)\n        {\n            if (g == null || !this.Visible)\n                return;\n            BitmapOrigin bmp = this.CurrentBitmap;\n            if (bmp.Bitmap != null)\n                g.DrawImage(bmp.Bitmap, bmp.OpOrigin.X + this.Location.X + offset.X, bmp.OpOrigin.Y + this.Location.Y + offset.Y);\n        }\n\n        public override void OnMouseMove(MouseEventArgs e)\n        {\n            if (this.IsMouseContains(e.Location))\n            {\n                if (this.State == ButtonState.Normal)\n                {\n                    this.State = ButtonState.MouseOver;\n                }\n            }\n            else if (this.state != ButtonState.Disabled)\n            {\n                this.State = ButtonState.Normal;\n            }\n\n            base.OnMouseMove(e);\n        }\n\n        public override void OnMouseDown(MouseEventArgs e)\n        {\n            if (this.IsMouseContains(e.Location))\n            {\n                if (this.State == ButtonState.Normal || this.State == ButtonState.MouseOver)\n                {\n                    this.State = ButtonState.Pressed;\n                }\n            }\n            base.OnMouseDown(e);\n        }\n\n        public override void OnMouseUp(MouseEventArgs e)\n        {\n            if (this.IsMouseContains(e.Location))\n            {\n                if (this.State == ButtonState.Pressed)\n                {\n                    this.State = ButtonState.MouseOver;\n                }\n            }\n\n            base.OnMouseUp(e);\n        }\n\n        protected virtual void OnButtonStateChanged()\n        {\n            if (this.ButtonStateChanged != null)\n            {\n                this.ButtonStateChanged(this, EventArgs.Empty);\n            }\n        }\n\n        public override void OnMouseClick(MouseEventArgs e)\n        {\n            if (this.state != ButtonState.Disabled)\n            {\n                base.OnMouseClick(e);\n            }\n        }\n\n        public event EventHandler ButtonStateChanged;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/ACtrlVScroll.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Windows.Forms;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class ACtrlVScroll : AControl\n    {\n        public ACtrlVScroll()\n        {\n            this.btnPrev = new ACtrlButton();\n            this.btnNext = new ACtrlButton();\n            this.btnThumb = new ACtrlButton();\n            this.picBase = new ACtrlButton();\n            this.btnPrev.ButtonStateChanged += new EventHandler(childBtn_ButtonStateChanged);\n            this.btnPrev.MouseClick += new MouseEventHandler(btnPrev_MouseClick);\n            this.btnNext.ButtonStateChanged += new EventHandler(childBtn_ButtonStateChanged);\n            this.btnNext.MouseClick += new MouseEventHandler(btnNext_MouseClick);\n            this.btnThumb.ButtonStateChanged += new EventHandler(childBtn_ButtonStateChanged);\n            this.picBase.ButtonStateChanged += new EventHandler(childBtn_ButtonStateChanged);\n        }\n\n        ACtrlButton btnPrev;\n        ACtrlButton btnNext;\n        ACtrlButton btnThumb;\n        ACtrlButton picBase;\n        int minimum;\n        int maximum;\n        int value;\n        bool isScrolling;\n\n        public int Minimum\n        {\n            get { return minimum; }\n            set\n            {\n                if (this.minimum != value)\n                {\n                    value = Math.Min(value, maximum);\n                    minimum = value;\n                    this.Value = this.Value;\n                    this.OnValueChanged();\n                }\n            }\n        }\n\n        public int Maximum\n        {\n            get { return maximum; }\n            set\n            {\n                if (this.maximum != value)\n                {\n                    value = Math.Max(minimum, value);\n                    maximum = value;\n                    this.Value = this.Value;\n                    this.OnValueChanged();\n                }\n            }\n        }\n\n        public int Value\n        {\n            get\n            {\n                return this.value;\n            }\n            set\n            {\n                value = Math.Min(Math.Max(this.minimum, value), this.maximum);\n                if (this.value != value)\n                {\n                    this.value = value;\n                    this.OnValueChanged();\n                }\n            }\n        }\n\n        public ACtrlButton BtnPrev\n        {\n            get { return btnPrev; }\n        }\n\n        public ACtrlButton BtnNext\n        {\n            get { return btnNext; }\n        }\n\n        public ACtrlButton BtnThumb\n        {\n            get { return btnThumb; }\n        }\n\n        public ACtrlButton PicBase\n        {\n            get { return picBase; }\n        }\n\n        public bool Enabled\n        {\n            get { return !(this.minimum == this.maximum); }\n        }\n\n        public override void OnMouseMove(MouseEventArgs e)\n        {\n            if (!this.Enabled)\n            {\n                setAllState(ButtonState.Disabled);\n                return;\n            }\n\n            if (IsMouseContains(e.Location))\n            {\n                MouseEventArgs e2 = ToChildEventargs(e);\n\n                foreach (ACtrlButton btn in this.buttons)\n                {\n                    btn.OnMouseMove(e2);\n                }\n                if (this.isScrolling)\n                {\n                    this.scrolling(e.Location);\n                }\n            }\n            else\n            {\n                setAllState(ButtonState.Normal);\n            }\n            \n            base.OnMouseMove(e);\n        }\n\n        public override void OnMouseDown(MouseEventArgs e)\n        {\n            if (!this.Enabled)\n            {\n                setAllState(ButtonState.Disabled);\n                return;\n            }\n\n            if (IsMouseContains(e.Location))\n            {\n                this.isScrolling = true;\n                MouseEventArgs e2 = ToChildEventargs(e);\n\n                foreach (ACtrlButton btn in this.buttons)\n                {\n                    btn.OnMouseDown(e2);\n                }\n            }\n\n            base.OnMouseDown(e);\n        }\n\n        public override void OnMouseUp(MouseEventArgs e)\n        {\n            if (!this.Enabled)\n            {\n                setAllState(ButtonState.Disabled);\n                return;\n            }\n\n            if (IsMouseContains(e.Location))\n            {\n                MouseEventArgs e2 = ToChildEventargs(e);\n\n                foreach (ACtrlButton btn in this.buttons)\n                {\n                    btn.OnMouseUp(e2);\n                }\n\n            }\n            this.isScrolling = false;\n            base.OnMouseUp(e);\n        }\n\n        public override void OnMouseClick(MouseEventArgs e)\n        {\n            if (!this.Enabled)\n            {\n                setAllState(ButtonState.Disabled);\n                return;\n            }\n\n            if (IsMouseContains(e.Location))\n            {\n                MouseEventArgs e2 = ToChildEventargs(e);\n\n                foreach (ACtrlButton btn in this.buttons)\n                {\n                    btn.OnMouseClick(e2);\n                }\n            }\n\n            base.OnMouseClick(e);\n        }\n\n        public override void OnMouseWheel(MouseEventArgs e)\n        {\n            if (!this.Enabled)\n            {\n                setAllState(ButtonState.Disabled);\n                return;\n            }\n            if (this.IsMouseContains(e.Location))\n            {\n                if (e.Delta > 0)\n                {\n                    this.Value -= 1;\n                }\n                else if (e.Delta < 0)\n                {\n                    this.Value += 1;\n                }\n            }\n\n            base.OnMouseWheel(e);\n        }\n\n        private void btnPrev_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.Value -= 1;\n        }\n\n        private void btnNext_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.Value += 1;\n        }\n\n        private void setAllState(ButtonState buttonState)\n        {\n            btnPrev.State = buttonState;\n            btnNext.State = buttonState;\n            btnThumb.State = buttonState;\n            picBase.State = buttonState;\n        }\n\n        private IEnumerable<ACtrlButton> buttons\n        {\n            get\n            {\n                yield return btnPrev;\n                yield return btnNext;\n                yield return btnThumb;\n            }\n        }\n\n        public override void Draw(Graphics g)\n        {\n            if (g == null || !this.Visible)\n                return;\n            if (picBase != null)\n            {\n                BitmapOrigin curBmp = picBase.CurrentBitmap;\n                g.SetClip(this.Rectangle);\n                for (int h = 0; h < Size.Height; h += curBmp.Bitmap.Size.Height)\n                {\n                    g.DrawImage(curBmp.Bitmap, Location.X, Location.Y + h);\n                }\n                g.ResetClip();\n            }\n            if (btnPrev != null)\n            {\n                btnPrev.Draw(g, this.Location);\n            }\n            if (btnNext != null)\n            {\n                btnNext.Draw(g, this.Location);\n            }\n            if (btnThumb != null)\n            {\n                btnThumb.Location = new Point(0, calcThumbLocationY());\n                btnThumb.Draw(g, this.Location);\n            }\n        }\n\n        private int calcThumbLocationY()\n        {\n            if (this.minimum == this.maximum)\n                return 0;\n            int totalHeight = this.Size.Height - this.btnPrev.Size.Height - this.btnNext.Size.Height - this.btnThumb.Size.Height;\n            int thumbY = totalHeight * this.value / (this.maximum - this.minimum);\n            return thumbY + this.btnPrev.Size.Height;\n        }\n\n        private void childBtn_ButtonStateChanged(object sender, EventArgs e)\n        {\n            this.OnChildButtonStateChanged();\n        }\n\n        private void scrolling(Point mouseLocation)\n        {\n            if (this.minimum == this.maximum)\n                return;\n            Point origin = new Point(this.Location.X, this.Location.Y + this.btnPrev.Size.Height + this.btnThumb.Size.Height / 2);\n            Size size = new Size(this.Size.Width, this.Size.Height - this.btnPrev.Size.Height - this.btnNext.Size.Height - this.btnThumb.Size.Height);\n            Rectangle scrollingRect = new Rectangle(origin, size);\n            this.Value = (int)Math.Round(1.0 * (this.maximum - this.minimum) * (mouseLocation.Y - scrollingRect.Y) / scrollingRect.Height);\n        }\n\n        protected virtual void OnValueChanged()\n        {\n            if (this.ValueChanged != null)\n            {\n                this.ValueChanged(this, EventArgs.Empty);\n            }\n        }\n\n        protected virtual void OnChildButtonStateChanged()\n        {\n            if (this.ChildButtonStateChanged != null)\n            {\n                this.ChildButtonStateChanged(this, EventArgs.Empty);\n            }\n        }\n\n        public event EventHandler ValueChanged;\n        public event EventHandler ChildButtonStateChanged;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/AfrmEquip.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\nusing System.Windows.Forms;\nusing System.Drawing;\nusing System.Text;\nusing CharaSimResource;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Common;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class AfrmEquip : AlphaForm\n    {\n        public AfrmEquip()\n        {\n            sec = new int[5];\n            for (int i = 0; i < sec.Length; i++)\n                sec[i] = 1 << i;\n\n            initCtrl();\n            this.AllowDrop = true;\n            this.TotemVisible = true;\n        }\n\n        private BitVector32 partVisible;\n        private int[] sec;\n\n        private Point baseOffset;\n        private Point newLocation;\n        private bool waitForRefresh;\n        private Character character;\n\n        private ACtrlButton btnPet;\n        private ACtrlButton btnDragon;\n        private ACtrlButton btnMechanic;\n        private ACtrlButton btnAndroid;\n        private ACtrlButton btnClose;\n\n        public Character Character\n        {\n            get { return character; }\n            set { character = value; }\n        }\n\n        public bool PetVisible\n        {\n            get { return partVisible[sec[0]]; }\n            private set { partVisible[sec[0]] = value; }\n        }\n\n        public bool DragonVisible\n        {\n            get { return partVisible[sec[1]]; }\n            private set\n            {\n                partVisible[sec[1]] = value;\n                if (value)\n                {\n                    partVisible[sec[2]] = false;\n                    partVisible[sec[3]] = false;\n                }\n            }\n        }\n\n        public bool MechanicVisible\n        {\n            get { return partVisible[sec[2]]; }\n            private set\n            {\n                partVisible[sec[2]] = value;\n                if (value)\n                {\n                    partVisible[sec[1]] = false;\n                    partVisible[sec[3]] = false;\n                }\n            }\n        }\n\n        public bool AndroidVisible\n        {\n            get { return partVisible[sec[3]]; }\n            private set\n            {\n                partVisible[sec[3]] = value;\n                if (value)\n                {\n                    partVisible[sec[1]] = false;\n                    partVisible[sec[2]] = false;\n                }\n            }\n        }\n\n        public bool TotemVisible\n        {\n            get { return partVisible[sec[4]]; }\n            private set { partVisible[sec[4]] = value; }\n        }\n\n        private Rectangle DragonRect\n        {\n            get\n            {\n                return new Rectangle(\n                    new Point(baseOffset.X - Resource.Equip_dragon_backgrnd.Width, baseOffset.Y),\n                    Resource.Equip_dragon_backgrnd.Size);\n            }\n        }\n\n        private Rectangle MechanicRect\n        {\n            get\n            {\n                return new Rectangle(\n                    new Point(baseOffset.X - Resource.Equip_mechanic_backgrnd.Width, baseOffset.Y),\n                    Resource.Equip_mechanic_backgrnd.Size);\n            }\n        }\n\n        private Rectangle AndroidRect\n        {\n            get\n            {\n                return new Rectangle(\n                    new Point(baseOffset.X - Resource.Equip_Android_backgrnd.Width, baseOffset.Y),\n                    Resource.Equip_Android_backgrnd.Size);\n            }\n        }\n\n        private Rectangle PetRect\n        {\n            get\n            {\n                return new Rectangle(\n                    new Point(baseOffset.X + Resource.Equip_character_backgrnd.Width,\n                        baseOffset.Y + Resource.Equip_character_backgrnd.Height - Resource.Equip_pet_backgrnd.Height),\n                    Resource.Equip_pet_backgrnd.Size);\n            }\n        }\n\n        private Rectangle TotemRect\n        {\n            get\n            {\n                return new Rectangle(\n                    new Point(baseOffset.X - Resource.Equip_totem_backgrnd.Width,\n                        baseOffset.Y + Resource.Equip_character_backgrnd.Height - Resource.Equip_totem_backgrnd.Height),\n                    Resource.Equip_pet_backgrnd.Size);\n            }\n        }\n\n        private void initCtrl()\n        {\n            this.btnPet = new ACtrlButton();\n            this.btnPet.Normal = new BitmapOrigin(Resource.Equip_character_BtPet_normal_0);\n            this.btnPet.Pressed = new BitmapOrigin(Resource.Equip_character_BtPet_pressed_0);\n            this.btnPet.MouseOver = new BitmapOrigin(Resource.Equip_character_BtPet_mouseOver_0);\n            this.btnPet.Disabled = new BitmapOrigin(Resource.Equip_character_BtPet_disabled_0);\n            this.btnPet.Location = new Point(139, 264);\n            this.btnPet.Size = new Size(36, 17);\n            this.btnPet.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnPet.MouseClick += new System.Windows.Forms.MouseEventHandler(btnPet_MouseClick);\n\n            this.btnDragon = new ACtrlButton();\n            this.btnDragon.Normal = new BitmapOrigin(Resource.Equip_character_BtDragon_normal_0);\n            this.btnDragon.Pressed = new BitmapOrigin(Resource.Equip_character_BtDragon_pressed_0);\n            this.btnDragon.MouseOver = new BitmapOrigin(Resource.Equip_character_BtDragon_mouseOver_0);\n            this.btnDragon.Disabled = new BitmapOrigin(Resource.Equip_character_BtDragon_disabled_0);\n            this.btnDragon.Location = new Point(10, 264);\n            this.btnDragon.Size = new Size(43, 17);\n            this.btnDragon.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnDragon.MouseClick += new MouseEventHandler(btnDragon_MouseClick);\n\n            this.btnMechanic = new ACtrlButton();\n            this.btnMechanic.Normal = new BitmapOrigin(Resource.Equip_character_BtMechanic_normal_0);\n            this.btnMechanic.Pressed = new BitmapOrigin(Resource.Equip_character_BtMechanic_pressed_0);\n            this.btnMechanic.MouseOver = new BitmapOrigin(Resource.Equip_character_BtMechanic_mouseOver_0);\n            this.btnMechanic.Disabled = new BitmapOrigin(Resource.Equip_character_BtMechanic_disabled_0);\n            this.btnMechanic.Location = new Point(10, 264);\n            this.btnMechanic.Size = new Size(43, 17);\n            this.btnMechanic.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnMechanic.MouseClick += new MouseEventHandler(btnMechanic_MouseClick);\n\n            this.btnAndroid = new ACtrlButton();\n            this.btnAndroid.Normal = new BitmapOrigin(Resource.Equip_character_BtAndroid_normal_0);\n            this.btnAndroid.Pressed = new BitmapOrigin(Resource.Equip_character_BtAndroid_pressed_0);\n            this.btnAndroid.MouseOver = new BitmapOrigin(Resource.Equip_character_BtAndroid_mouseOver_0);\n            this.btnAndroid.Disabled = new BitmapOrigin(Resource.Equip_character_BtAndroid_disabled_0);\n            this.btnAndroid.Location = new Point(65, 266);\n            this.btnAndroid.Size = new Size(25, 12);\n            this.btnAndroid.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnAndroid.MouseClick += new MouseEventHandler(btnAndroid_MouseClick);\n\n            this.btnClose = new ACtrlButton();\n            this.btnClose.Normal = new BitmapOrigin(Resource.BtClose3_normal_0);\n            this.btnClose.Pressed = new BitmapOrigin(Resource.BtClose3_pressed_0);\n            this.btnClose.MouseOver = new BitmapOrigin(Resource.BtClose3_mouseOver_0);\n            this.btnClose.Disabled = new BitmapOrigin(Resource.BtClose3_disabled_0);\n            this.btnClose.Location = new Point(162, 6);\n            this.btnClose.Size = new Size(13, 13);\n            this.btnClose.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnClose.MouseClick += new MouseEventHandler(btnClose_MouseClick);\n        }\n\n        public override void Refresh()\n        {\n            this.preRender();\n            this.SetBitmap(this.Bitmap);\n            this.CaptionRectangle = new Rectangle(this.baseOffset, new Size(Resource.Equip_character_backgrnd.Width, 24));\n            this.Location = newLocation;\n            base.Refresh();\n        }\n\n        protected override bool captionHitTest(Point point)\n        {\n            Rectangle rect = this.btnClose.Rectangle;\n            rect.Offset(this.baseOffset);\n            if (rect.Contains(point))\n                return false;\n            return base.captionHitTest(point);\n        }\n\n        private void preRender()\n        {\n            if (Bitmap != null)\n                Bitmap.Dispose();\n\n            //处理按钮可见\n            setControlState();\n\n            //计算图像大小\n            Point baseOffsetnew = calcRenderBaseOffset();\n            Size size = Resource.Equip_character_backgrnd.Size;\n            size.Width += baseOffsetnew.X;\n            if (this.PetVisible)\n                size.Width += Resource.Equip_pet_backgrnd.Width;\n\n            //处理偏移\n            this.newLocation = new Point(this.Location.X + this.baseOffset.X - baseOffsetnew.X,\n                this.Location.Y + this.baseOffset.Y - baseOffsetnew.Y);\n            this.baseOffset = baseOffsetnew;\n\n            //绘制图像\n            Bitmap bitmap = new Bitmap(size.Width, size.Height);\n            Graphics g = Graphics.FromImage(bitmap);\n\n            renderBase(g);\n            if (this.DragonVisible) renderDragon(g);\n            else if (this.MechanicVisible) renderMechanic(g);\n            else if (this.AndroidVisible) renderAndroid(g);\n            if (this.PetVisible) renderPet(g);\n            if (this.TotemVisible) renderTotem(g);\n\n            g.Dispose();\n            this.Bitmap = bitmap;\n        }\n\n        private Point calcRenderBaseOffset()\n        {\n            if (this.DragonVisible)\n                return new Point(Resource.Equip_dragon_backgrnd.Width, 0);\n            else if (this.MechanicVisible)\n                return new Point(Resource.Equip_mechanic_backgrnd.Width, 0);\n            else if (this.AndroidVisible)\n                return new Point(Resource.Equip_Android_backgrnd.Width, 0);\n            else\n                return new Point(Resource.Equip_totem_backgrnd.Width, 0);\n        }\n\n        private void setControlState()\n        {\n            if (this.character == null)\n            {\n                this.btnDragon.Visible = false;\n                this.btnMechanic.Visible = false;\n                this.DragonVisible = false;\n                this.MechanicVisible = false;\n            }\n            else\n            {\n                if (this.character.Status.Job / 100 == 22) //龙神\n                {\n                    this.btnDragon.Visible = true;\n                }\n                else\n                {\n                    this.btnDragon.Visible = false;\n                    this.DragonVisible = false;\n                }\n\n                if (this.character.Status.Job / 100 == 35) //机械\n                {\n                    this.btnMechanic.Visible = true;\n                }\n                else\n                {\n                    this.btnMechanic.Visible = false;\n                    this.MechanicVisible = false;\n                }\n            }\n        }\n\n        private void renderBase(Graphics g)\n        {\n            g.TranslateTransform(baseOffset.X, baseOffset.Y);\n            g.DrawImage(Resource.Equip_character_backgrnd, 0, 0);\n            g.DrawImage(Resource.Equip_character_backgrnd2, 6, 22);\n            g.DrawImage(Resource.Equip_character_backgrnd3, 10, 27);\n            g.DrawImage(Resource.Equip_character_cashPendant, 76, 93);\n            g.DrawImage(Resource.Equip_character_charmPocket, 10, 93);\n            if (this.character != null\n                && (this.character.Status.Job / 100 == 23 || this.character.Status.Job == 2002))\n            {\n                g.DrawImage(Resource.Equip_character_magicArrow, 142, 126);\n            }\n\n            foreach (AControl aCtrl in this.aControls)\n            {\n                aCtrl.Draw(g);\n            }\n\n            if (this.character != null)\n            {\n                for (int i = 0; i < 30; i++)\n                {\n                    Gear gear = this.character.Equip.GearSlots[i];\n                    if (gear != null)\n                    {\n                        int dx = 10 + i % 5 * 33, dy = 27 + i / 5 * 33;\n                        drawGearIcon(gear, g, dx, dy);\n                    }\n                }\n            }\n            g.ResetTransform();\n        }\n\n        private void renderDragon(Graphics g)\n        {\n            Rectangle rect = this.DragonRect;\n            g.TranslateTransform(rect.X, rect.Y);\n            g.DrawImage(Resource.Equip_dragon_backgrnd, 0, 0);\n            g.DrawImage(Resource.Equip_dragon_backgrnd2, 6, 22);\n            g.DrawImage(Resource.Equip_dragon_backgrnd3, 10, 29);\n\n            if (this.character != null)\n            {\n                for (int i = 35; i < 39; i++)\n                {\n                    Gear gear = this.character.Equip.GearSlots[i];\n                    if (gear != null)\n                    {\n                        int dx = 10 + (i - 35) * 33, dy = 22 + (((i - 1) % 2) + 1) * 33;\n                        drawGearIcon(gear, g, dx, dy);\n                    }\n                }\n            }\n            g.ResetTransform();\n        }\n\n        private void renderMechanic(Graphics g)\n        {\n            Rectangle rect = this.MechanicRect;\n            g.TranslateTransform(rect.X, rect.Y);\n            g.DrawImage(Resource.Equip_mechanic_backgrnd, 0, 0);\n            g.DrawImage(Resource.Equip_mechanic_backgrnd2, 6, 22);\n            g.DrawImage(Resource.Equip_mechanic_backgrnd3, 12, 35);\n\n            if (this.character != null)\n            {\n                int dx, dy;\n                for (int i = 39; i < 44; i++)\n                {\n                    Gear gear = this.character.Equip.GearSlots[i];\n                    if (gear != null)\n                    {\n                        switch(i)\n                        {\n                            case 39: dx = 1; dy = 1; break;\n                            case 40: dx = 1; dy = 2; break;\n                            case 41: dx = 2; dy = 2; break;\n                            case 42: dx = 0; dy = 3; break;\n                            case 43: dx = 1; dy = 3; break;\n                            default: continue;\n                        }\n                        dx = 10 + dx * 33;\n                        dy = 22 + dy * 33;\n                        drawGearIcon(gear, g, dx, dy);\n                    }\n                }\n            }\n            g.ResetTransform();\n        }\n\n        private void renderPet(Graphics g)\n        {\n            Rectangle rect = this.PetRect;\n            g.TranslateTransform(rect.X, rect.Y);\n            g.DrawImage(Resource.Equip_pet_backgrnd, 0, 0);\n            g.DrawImage(Resource.Equip_pet_backgrnd2, 6, 21);\n            g.DrawImage(Resource.Equip_pet_backgrnd3, 11, 27);\n            g.ResetTransform();\n        }\n\n        private void renderAndroid(Graphics g)\n        {\n            Rectangle rect = this.AndroidRect;\n            g.TranslateTransform(rect.X, rect.Y);\n            g.DrawImage(Resource.Equip_Android_backgrnd, 0, 0);\n            g.DrawImage(Resource.Equip_Android_backgrnd2, 6, 24);\n            g.DrawImage(Resource.Equip_Android_backgrnd3, 12, 28);\n            g.ResetTransform();\n        }\n\n        private void renderTotem(Graphics g)\n        {\n            Rectangle rect = this.TotemRect;\n            g.TranslateTransform(rect.X, rect.Y);\n            g.DrawImage(Resource.Equip_totem_backgrnd, 0, 0);\n            g.ResetTransform();\n        }\n\n        private void drawGearIcon(Gear gear, Graphics g, int x, int y)\n        {\n            if (gear == null || g == null)\n                return;\n            if (gear.State == GearState.disable)\n                g.DrawImage(Resource.Equip_character_disabled, x, y);\n            Pen pen = GearGraphics.GetGearItemBorderPen(gear.Grade);\n            if (pen != null)\n            {\n                Point[] path = GearGraphics.GetIconBorderPath(x, y);\n                g.DrawLines(pen, path);\n            }\n            g.DrawImage(gear.Icon.Bitmap,\n                x - gear.Icon.Origin.X,\n                y + 32 - gear.Icon.Origin.Y);\n        }\n\n        private IEnumerable<AControl> aControls\n        {\n            get\n            {\n                yield return btnDragon;\n                yield return btnMechanic;\n                yield return btnPet;\n                yield return btnClose;\n                yield return btnAndroid;\n            }\n        }\n\n        private void aCtrl_RefreshCall(object sender, EventArgs e)\n        {\n            this.waitForRefresh = true;\n        }\n\n        private void btnPet_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.PetVisible = !this.PetVisible;\n            this.waitForRefresh = true;\n        }\n\n        private void btnDragon_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.DragonVisible = !this.DragonVisible;\n            this.waitForRefresh = true;\n        }\n\n        private void btnMechanic_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.MechanicVisible = !this.MechanicVisible;\n            this.waitForRefresh = true;\n        }\n\n        private void btnAndroid_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.AndroidVisible = !this.AndroidVisible;\n            this.waitForRefresh = true;\n        }\n\n        private void btnClose_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.Visible = false;\n        }\n\n        protected override void OnMouseMove(MouseEventArgs e)\n        {\n            MouseEventArgs childArgs = new MouseEventArgs(e.Button, e.Clicks, e.X - baseOffset.X, e.Y - baseOffset.Y, e.Delta);\n\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseMove(childArgs);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseMove(e);\n        }\n\n        protected override void OnMouseDown(MouseEventArgs e)\n        {\n            MouseEventArgs childArgs = new MouseEventArgs(e.Button, e.Clicks, e.X - baseOffset.X, e.Y - baseOffset.Y, e.Delta);\n\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseDown(childArgs);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseDown(e);\n        }\n\n        protected override void OnMouseUp(MouseEventArgs e)\n        {\n            MouseEventArgs childArgs = new MouseEventArgs(e.Button, e.Clicks, e.X - baseOffset.X, e.Y - baseOffset.Y, e.Delta);\n\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseUp(childArgs);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseUp(e);\n        }\n\n        protected override void OnMouseClick(MouseEventArgs e)\n        {\n            MouseEventArgs childArgs = new MouseEventArgs(e.Button, e.Clicks, e.X - baseOffset.X, e.Y - baseOffset.Y, e.Delta);\n\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseClick(childArgs);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseClick(e);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/AfrmItem.cs",
    "content": "﻿using System;\nusing System.Drawing;\nusing System.Collections.Generic;\nusing System.Windows.Forms;\nusing System.Text;\nusing CharaSimResource;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Common;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class AfrmItem : AlphaForm\n    {\n        public AfrmItem()\n        {\n            this.AllowDrop = true;\n            initCtrl();\n        }\n\n        private ItemTab[] itemTabs;\n        private bool fullMode;\n        private bool selectedIndexChanging;\n        private ACtrlVScroll vScroll;\n        private Character character;\n\n        private ACtrlButton btnFull;\n        private ACtrlButton btnSmall;\n        private ACtrlButton btnCoin;\n        private ACtrlButton btnGather;\n        private ACtrlButton btnSort;\n        private ACtrlButton btnClose;\n        private bool waitForRefresh;\n\n        public event ItemMouseEventHandler ItemMouseDown;\n        public event ItemMouseEventHandler ItemMouseUp;\n        public event ItemMouseEventHandler ItemMouseClick;\n        public event ItemMouseEventHandler ItemMouseMove;\n        public event EventHandler ItemMouseLeave;\n\n        public Character Character\n        {\n            get { return character; }\n            set\n            {\n                character = value;\n\n                for (int i = 0; i < this.itemTabs.Length; i++)\n                {\n                    if (this.character == null || !this.itemTabs[i].SetItemSource(character.ItemSlots[i]))\n                    {\n                        this.itemTabs[i].ClearItems();\n                    }\n                }\n            }\n        }\n\n        private void initCtrl()\n        {\n            this.itemTabs = new ItemTab[5];\n            for (int i = 0; i < itemTabs.Length; i++)\n            {\n                this.itemTabs[i] = new ItemTab(this);\n                this.itemTabs[i].TabEnabled = new BitmapOrigin((Bitmap)Resource.ResourceManager.GetObject(\"Item_Tab_enabled_\" + i),\n                   -9 - 31 * i, -26);\n                this.itemTabs[i].TabDisabled = new BitmapOrigin((Bitmap)Resource.ResourceManager.GetObject(\"Item_Tab_disabled_\" + i),\n                    -9 - 31 * i, -26);\n            }\n            this.itemTabs[0].Selected = true;\n\n            this.vScroll = new ACtrlVScroll();\n\n            this.vScroll.PicBase.Normal = new BitmapOrigin(Resource.VScr9_enabled_base);\n            this.vScroll.PicBase.Disabled = new BitmapOrigin(Resource.VScr9_disabled_base);\n\n            this.vScroll.BtnPrev.Normal = new BitmapOrigin(Resource.VScr9_enabled_prev0);\n            this.vScroll.BtnPrev.Pressed = new BitmapOrigin(Resource.VScr9_enabled_prev1);\n            this.vScroll.BtnPrev.MouseOver = new BitmapOrigin(Resource.VScr9_enabled_prev2);\n            this.vScroll.BtnPrev.Disabled = new BitmapOrigin(Resource.VScr9_disabled_prev);\n            this.vScroll.BtnPrev.Size = this.vScroll.BtnPrev.Normal.Bitmap.Size;\n            this.vScroll.BtnPrev.Location = new Point(0, 0);\n\n            this.vScroll.BtnNext.Normal = new BitmapOrigin(Resource.VScr9_enabled_next0);\n            this.vScroll.BtnNext.Pressed = new BitmapOrigin(Resource.VScr9_enabled_next1);\n            this.vScroll.BtnNext.MouseOver = new BitmapOrigin(Resource.VScr9_enabled_next2);\n            this.vScroll.BtnNext.Disabled = new BitmapOrigin(Resource.VScr9_disabled_next);\n            this.vScroll.BtnNext.Size = this.vScroll.BtnNext.Normal.Bitmap.Size;\n            this.vScroll.BtnNext.Location = new Point(0, 195);\n\n            this.vScroll.BtnThumb.Normal = new BitmapOrigin(Resource.VScr9_enabled_thumb0);\n            this.vScroll.BtnThumb.Pressed = new BitmapOrigin(Resource.VScr9_enabled_thumb1);\n            this.vScroll.BtnThumb.MouseOver = new BitmapOrigin(Resource.VScr9_enabled_thumb2);\n            this.vScroll.BtnThumb.Size = this.vScroll.BtnThumb.Normal.Bitmap.Size;\n\n            this.vScroll.Location = new Point(152, 51);\n            this.vScroll.Size = new Size(11, 207);\n            this.vScroll.ValueChanged += new EventHandler(vScroll_ValueChanged);\n            this.vScroll.ChildButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n\n            this.btnFull = new ACtrlButton();\n            this.btnFull.Normal = new BitmapOrigin(Resource.Item_BtFull_normal_0);\n            this.btnFull.Pressed = new BitmapOrigin(Resource.Item_BtFull_pressed_0);\n            this.btnFull.MouseOver = new BitmapOrigin(Resource.Item_BtFull_mouseOver_0);\n            this.btnFull.Disabled = new BitmapOrigin(Resource.Item_BtFull_disabled_0);\n            this.btnFull.Location = new Point(147, 267);\n            this.btnFull.Size = new Size(16, 16);\n            this.btnFull.MouseClick += new MouseEventHandler(btnFull_MouseClick);\n            this.btnFull.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n\n            this.btnSmall = new ACtrlButton();\n            this.btnSmall.Normal = new BitmapOrigin(Resource.Item_BtSmall_normal_0);\n            this.btnSmall.Pressed = new BitmapOrigin(Resource.Item_BtSmall_pressed_0);\n            this.btnSmall.MouseOver = new BitmapOrigin(Resource.Item_BtSmall_mouseOver_0);\n            this.btnSmall.Disabled = new BitmapOrigin(Resource.Item_BtSmall_disabled_0);\n            this.btnSmall.Location = new Point(147, 267);\n            this.btnSmall.Size = new Size(16, 16);\n            this.btnSmall.MouseClick += new MouseEventHandler(btnSmall_MouseClick);\n            this.btnSmall.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n\n            this.btnCoin = new ACtrlButton();\n            this.btnCoin.Normal = new BitmapOrigin(Resource.Item_BtCoin_normal_0);\n            this.btnCoin.Pressed = new BitmapOrigin(Resource.Item_BtCoin_pressed_0);\n            this.btnCoin.MouseOver = new BitmapOrigin(Resource.Item_BtCoin_mouseOver_0);\n            this.btnCoin.Disabled = new BitmapOrigin(Resource.Item_BtCoin_disabled_0);\n            this.btnCoin.Location = new Point(9, 267);\n            this.btnCoin.Size = new Size(40, 16);\n            this.btnCoin.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n\n            this.btnGather = new ACtrlButton();\n            this.btnGather.Normal = new BitmapOrigin(Resource.Item_BtGather_normal_0);\n            this.btnGather.Pressed = new BitmapOrigin(Resource.Item_BtGather_pressed_0);\n            this.btnGather.MouseOver = new BitmapOrigin(Resource.Item_BtGather_mouseOver_0);\n            this.btnGather.Disabled = new BitmapOrigin(Resource.Item_BtGather_disabled_0);\n            this.btnGather.Location = new Point(129, 267);\n            this.btnGather.Size = new Size(16, 16);\n            this.btnGather.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnGather.MouseClick += new MouseEventHandler(btnGather_MouseClick);\n\n            this.btnSort = new ACtrlButton();\n            this.btnSort.Normal = new BitmapOrigin(Resource.Item_BtSort_normal_0);\n            this.btnSort.Pressed = new BitmapOrigin(Resource.Item_BtSort_pressed_0);\n            this.btnSort.MouseOver = new BitmapOrigin(Resource.Item_BtSort_mouseOver_0);\n            this.btnSort.Disabled = new BitmapOrigin(Resource.Item_BtSort_disabled_0);\n            this.btnSort.Location = new Point(129, 267);\n            this.btnSort.Size = new Size(16, 16);\n            this.btnSort.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnSort.MouseClick += new MouseEventHandler(btnSort_MouseClick);\n\n            this.btnClose = new ACtrlButton();\n            this.btnClose.Normal = new BitmapOrigin(Resource.BtClose3_normal_0);\n            this.btnClose.Pressed = new BitmapOrigin(Resource.BtClose3_pressed_0);\n            this.btnClose.MouseOver = new BitmapOrigin(Resource.BtClose3_mouseOver_0);\n            this.btnClose.Disabled = new BitmapOrigin(Resource.BtClose3_disabled_0);\n            this.btnClose.Location = new Point(150, 6);\n            this.btnClose.Size = new Size(13, 13);\n            this.btnClose.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnClose.MouseClick += new MouseEventHandler(btnClose_MouseClick);\n        }\n\n        public override void Refresh()\n        {\n            this.preRender();\n            this.SetBitmap(this.Bitmap);\n            this.CaptionRectangle = new Rectangle(0, 0, this.Bitmap.Width, 24);\n            base.Refresh();\n        }\n\n        protected override bool captionHitTest(Point point)\n        {\n            if (this.btnClose.Rectangle.Contains(point))\n                return false;\n            return base.captionHitTest(point);\n        }\n\n        private void preRender()\n        {\n            if (Bitmap != null)\n                Bitmap.Dispose();\n\n            if (this.btnGather.Visible)\n            {\n                this.btnSort.Visible = false;\n            }\n            else\n            {\n                this.btnSort.Visible = true;\n            }\n\n            if (this.fullMode)\n            {\n                this.btnFull.Visible = false;\n                this.btnSmall.Visible = true;\n                this.vScroll.Visible = false;\n                this.btnClose.Location = new Point(574, 6);\n                renderFull();\n            }\n            else\n            {\n                this.btnFull.Visible = true;\n                this.btnSmall.Visible = false;\n                this.vScroll.Visible = true;\n                this.vScroll.Maximum = this.SelectedTab.ScrollMaxValue - 6;\n                this.vScroll.Value = this.SelectedTab.ScrollValue;\n                this.btnClose.Location = new Point(150, 6);\n                renderSmall();\n            }\n        }\n\n        private void renderSmall()\n        {\n            this.Bitmap = new Bitmap(Resource.Item_backgrnd);\n            Graphics g = Graphics.FromImage(this.Bitmap);\n            g.DrawImage(Resource.Item_backgrnd2, 6, 23);\n            renderTabs(g);\n            g.DrawImage(Resource.Item_backgrnd3, 7, 45);\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.Draw(g);\n            }\n\n            ItemBase[] itemArray = this.SelectedTab.Items;\n            int idxOffset = 4 * this.SelectedTab.ScrollValue;\n            for (int i = 0; i < 24; i++)\n            {\n                Point origin = getItemIconOrigin(i);\n                origin.Offset(0, 32);\n                renderItemBase(g, itemArray[i + idxOffset], origin);\n            }\n\n            g.Dispose();\n        }\n\n        private void renderFull()\n        {\n            this.Bitmap = new Bitmap(Resource.Item_FullBackgrnd);\n            Graphics g = Graphics.FromImage(this.Bitmap);\n            g.DrawImage(Resource.Item_FullBackgrnd2, 6, 23);\n            renderTabs(g);\n            g.DrawImage(Resource.Item_FullBackgrnd3, 10, 51);\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.Draw(g);\n            }\n\n            ItemBase[] itemArray = this.SelectedTab.Items;\n            for (int i = 0; i < itemArray.Length; i++)\n            {\n                int idx = i % 24, group = i / 24;\n                Point origin = getItemIconOrigin(i);\n                origin.Offset(0, 32);\n                renderItemBase(g, itemArray[i], origin);\n            }\n\n            g.Dispose();\n        }\n\n        private void renderTabs(Graphics g)\n        {\n            for (int i = 0; i < this.itemTabs.Length; i++)\n            {\n                if (this.itemTabs[i].Selected)\n                {\n                    Point pos = this.itemTabs[i].TabEnabled.OpOrigin;\n                    // if (this.fullMode)\n                    pos.Offset(0, -2);\n                    g.DrawImage(this.itemTabs[i].TabEnabled.Bitmap, pos);\n                }\n                else\n                {\n                    g.DrawImage(this.itemTabs[i].TabDisabled.Bitmap, this.itemTabs[i].TabEnabled.OpOrigin);\n                }\n            }\n        }\n\n        private void renderItemBase(Graphics g, ItemBase itemBase, Point origin)\n        {\n            if (itemBase is Gear)\n                renderGear(g, itemBase as Gear, origin);\n            else if (itemBase is Item)\n                renderItem(g, itemBase as Item, origin);\n        }\n\n        private void renderGear(Graphics g, Gear gear, Point origin)\n        {\n            if (g == null || gear == null)\n                return;\n            Pen pen = GearGraphics.GetGearItemBorderPen(gear.Grade);\n            if (pen != null)\n            {\n                Point[] path = GearGraphics.GetIconBorderPath(origin.X, origin.Y - 32);\n                g.DrawLines(pen, path);\n            }\n            g.DrawImage(Resource.Item_shadow, origin.X + 3, origin.Y - 6);\n            if (gear.IconRaw.Bitmap != null)\n            {\n                g.DrawImage(gear.IconRaw.Bitmap, origin.X - gear.IconRaw.Origin.X, origin.Y - gear.IconRaw.Origin.Y);\n            }\n            if (gear.Cash)\n            {\n                /*\n                int value;\n                if (gear.Props.TryGetValue(GearPropType.royalSpecial, out value) && value > 0)\n                    g.DrawImage(Resource.CashItem_label_0, origin.X + 20, origin.Y - 12);\n                else if (gear.Props.TryGetValue(GearPropType.masterSpecial, out value) && value > 0)\n                    g.DrawImage(Resource.CashItem_label_3, origin.X + 20, origin.Y - 12);\n                else\n                */\n                g.DrawImage(Resource.CashItem_0, origin.X + 20, origin.Y - 12);\n            }\n        }\n\n        private void renderItem(Graphics g, Item item, Point origin)\n        {\n            if (g == null || item == null)\n                return;\n            g.DrawImage(Resource.Item_shadow, origin.X + 3, origin.Y - 6);\n            if (item.IconRaw.Bitmap != null)\n            {\n                g.DrawImage(item.IconRaw.Bitmap, origin.X - item.IconRaw.Origin.X, origin.Y - item.IconRaw.Origin.Y);\n            }\n            if (item.Cash)\n            {\n                if (item.Props.TryGetValue(ItemPropType.wonderGrade, out long value) && value > 0)\n                {\n                    Image label = Resource.ResourceManager.GetObject(\"CashItem_label_\" + (value + 3)) as Image;\n                    if (label != null)\n                    {\n                        g.DrawImage(new Bitmap(label), origin.X + 20, origin.Y - 12);\n                    }\n                }\n                else\n                {\n                    g.DrawImage(Resource.CashItem_0, origin.X + 20, origin.Y - 12);\n                }\n            }\n        }\n\n        private Point getItemIconOrigin(int index)\n        {\n            int idx = index % 24, group = index / 24;\n            Point p = new Point((idx % 4 + group * 4) * 36, idx / 4 * 35);\n            p.Offset(10, 51);\n            return p;\n        }\n\n        public int GetSlotIndexByPoint(Point point)\n        {\n            Point p = point;\n            p.Offset(-10, -51);\n            if (p.X < 0 || p.Y < 0)\n                return -1;\n            int x = p.X / 36, y = p.Y / 35;\n            if (y >= 6 || (fullMode ? x >= 24 : x >= 4))\n                return -1;\n            int idx = y * 4 + x % 4 + x / 4 * 24;\n            if (new Rectangle(getItemIconOrigin(idx), new Size(33, 33)).Contains(point))\n                return idx;\n            else\n                return -1;\n        }\n\n        public int GetItemIndexByPoint(Point point)\n        {\n            int slotIdx = GetSlotIndexByPoint(point);\n            if (slotIdx != -1 && !this.fullMode)\n            {\n                slotIdx += 4 * this.SelectedTab.ScrollValue;\n            }\n            return slotIdx;\n        }\n\n        public ItemBase GetItemByPoint(Point point)\n        {\n            int itemIdx = GetItemIndexByPoint(point);\n            if (itemIdx > -1 && itemIdx < this.SelectedTab.Items.Length)\n                return this.SelectedTab.Items[itemIdx];\n            else\n                return null;\n        }\n\n        #region 重写和响应事件\n        protected override void OnMouseMove(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseMove(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseMove(e);\n\n            ItemBase item = GetItemByPoint(e.Location);\n            if (item != null)\n                this.OnItemMouseMove(new ItemMouseEventArgs(e, item));\n            else\n                this.OnItemMouseLeave(EventArgs.Empty);\n        }\n\n        protected override void OnMouseDown(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseDown(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n            \n            base.OnMouseDown(e);\n\n            ItemBase item = GetItemByPoint(e.Location);\n            if (item != null)\n                this.OnItemMouseDown(new ItemMouseEventArgs(e, item));\n        }\n\n        protected override void OnMouseUp(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseUp(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseUp(e);\n\n            ItemBase item = GetItemByPoint(e.Location);\n            if (item != null)\n                this.OnItemMouseUp(new ItemMouseEventArgs(e, item));\n        }\n\n        protected override void OnMouseClick(MouseEventArgs e)\n        {\n            //处理选择选项卡\n            tab_OnMouseClick(e);\n\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseClick(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseClick(e);\n\n            ItemBase item = GetItemByPoint(e.Location);\n            if (item != null)\n                this.OnItemMouseClick(new ItemMouseEventArgs(e, item));\n        }\n\n        protected override void OnMouseWheel(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseWheel(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseWheel(e);\n        }\n\n        protected virtual void OnItemMouseDown(ItemMouseEventArgs e)\n        {\n            if (this.ItemMouseDown != null)\n                this.ItemMouseDown(this, e);\n        }\n\n        protected virtual void OnItemMouseUp(ItemMouseEventArgs e)\n        {\n            if (this.ItemMouseUp != null)\n                this.ItemMouseUp(this, e);\n        }\n\n        protected virtual void OnItemMouseClick(ItemMouseEventArgs e)\n        {\n            if (this.ItemMouseClick != null)\n                this.ItemMouseClick(this, e);\n        }\n\n        protected virtual void OnItemMouseMove(ItemMouseEventArgs e)\n        {\n            if (this.ItemMouseMove != null)\n                this.ItemMouseMove(this, e);\n        }\n\n        protected virtual void OnItemMouseLeave(EventArgs e)\n        {\n            if (this.ItemMouseLeave != null)\n                this.ItemMouseLeave(this, e);\n        }\n        #endregion\n\n        private void tab_OnMouseClick(MouseEventArgs e)\n        {\n            if (e.Button == MouseButtons.Left)\n            {\n                for (int i = 0; i < this.itemTabs.Length; i++)\n                {\n                    Rectangle rect;\n                    if (this.itemTabs[i].Selected)\n                    {\n                        rect = this.itemTabs[i].TabEnabled.Rectangle;\n                    }\n                    else\n                    {\n                        rect = this.itemTabs[i].TabDisabled.Rectangle;\n                    }\n                    if (rect.Contains(e.Location))\n                    {\n                        if (this.SelectedIndex != i)\n                        {\n                            this.SelectedIndex = i;\n                            this.btnGather.Visible = true; //切换tab时重置btnGather状态\n                            this.waitForRefresh = true;\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n\n        private void btnSmall_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.fullMode = false;\n            this.waitForRefresh = true;\n        }\n\n        private void btnFull_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.fullMode = true;\n            this.waitForRefresh = true;\n        }\n\n        private void vScroll_ValueChanged(object sender, EventArgs e)\n        {\n            this.SelectedTab.ScrollValue = this.vScroll.Value;\n            this.waitForRefresh = true;\n        }\n\n        private void btnGather_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.btnGather.Visible = !this.btnGather.Visible;\n            this.gather();\n            this.waitForRefresh = true;\n        }\n\n        private void btnSort_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.btnGather.Visible = !this.btnGather.Visible;\n            this.sort();\n            this.waitForRefresh = true;\n        }\n\n        private void btnClose_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.Visible = false;\n        }\n\n        private void aCtrl_RefreshCall(object sender, EventArgs e)\n        {\n            this.waitForRefresh = true;\n        }\n\n        private void gather()\n        {\n            ItemBase[] itemArray = this.SelectedTab.Items;\n            Queue<int> nullQueue = new Queue<int>();\n            for (int i = 0; i < itemArray.Length; i++)\n            {\n                if (itemArray[i] == null)\n                {\n                    nullQueue.Enqueue(i);\n                }\n                else if (nullQueue.Count > 0)\n                {\n                    int nullIdx = nullQueue.Dequeue();\n                    itemArray[nullIdx] = itemArray[i];\n                    itemArray[i] = null;\n                    nullQueue.Enqueue(i);\n                }\n            }\n        }\n\n        private void sort()\n        {\n            ItemBase[] itemArray = this.SelectedTab.Items;\n            Array.Sort<ItemBase>(itemArray, (a, b) =>\n            {\n                if (a == null) return 1;\n                if (b == null) return -1;\n                return a.ItemID - b.ItemID;\n            });\n        }\n\n        private IEnumerable<AControl> aControls\n        {\n            get\n            {\n                yield return this.vScroll;\n                yield return this.btnFull;\n                yield return this.btnSmall;\n                yield return this.btnCoin;\n                yield return this.btnGather;\n                yield return this.btnSort;\n                yield return this.btnClose;\n            }\n        }\n\n        public ItemTab[] ItemTabs\n        {\n            get { return this.itemTabs; }\n        }\n\n        /// <summary>\n        /// 获取或设置一个bool值，它表示是否当前背包显示为大背包模式。\n        /// </summary>\n        public bool FullMode\n        {\n            get { return fullMode; }\n            set { fullMode = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置正在选中的背包选项卡的索引。\n        /// </summary>\n        public int SelectedIndex\n        {\n            get\n            {\n                for (int i = 0; i < this.itemTabs.Length; i++)\n                {\n                    if (this.itemTabs[i].Selected)\n                        return i;\n                }\n                this.itemTabs[0].Selected = true;\n                return 0;\n            }\n            set\n            {\n                value = Math.Min(Math.Max(value, 0), this.itemTabs.Length - 1);\n                this.selectedIndexChanging = true;\n                for (int i = 0; i < this.itemTabs.Length; i++)\n                {\n                    this.itemTabs[i].Selected = (i == value);\n                }\n                this.selectedIndexChanging = false;\n            }\n        }\n\n        public bool AddItem(ItemBase item)\n        {\n            if (item == null)\n                return false;\n\n            int idx;\n            switch (item.Type)\n            {\n                case ItemBaseType.Equip: idx = 0; break;\n                case ItemBaseType.Consume: idx = 1; break;\n                case ItemBaseType.Install: idx = 3; break;\n                case ItemBaseType.Etc: idx = 2; break;\n                case ItemBaseType.Cash: idx = 4; break;\n                default: return false;\n            }\n\n            ItemBase[] itemArray = this.itemTabs[idx].Items;\n\n            for (int i = 0; i < itemArray.Length; i++)\n            {\n                if (itemArray[i] == null)\n                {\n                    itemArray[i] = (ItemBase)item.Clone();\n                    this.SelectedIndex = idx;\n                    this.itemTabs[idx].ScrollValue = (i - 20) / 4;\n                    this.Refresh();\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        public void RemoveItem(int index)\n        {\n            ItemBase[] itemArray = this.SelectedTab.Items;\n            if (index >= 0 && index < itemArray.Length && itemArray[index] != null)\n            {\n                itemArray[index] = null;\n                this.Refresh();\n            }\n        }\n\n        /// <summary>\n        /// 获取或设置正在选中的背包选项卡。\n        /// </summary>\n        public ItemTab SelectedTab\n        {\n            get { return this.itemTabs[this.SelectedIndex]; }\n            set { this.SelectedIndex = Array.IndexOf(this.itemTabs, value); }\n        }\n\n        public class ItemTab\n        {\n            public ItemTab(AfrmItem owner)\n            {\n                this.owner = owner;\n                this.items = new ItemBase[ItemCount];\n            }\n\n            public const int ItemCount = 96;\n            private AfrmItem owner;\n            private ItemBase[] items;\n            private bool selected;\n            private BitmapOrigin tabEnabled;\n            private BitmapOrigin tabDisabled;\n            private int scrollValue;\n\n            public ItemBase[] Items\n            {\n                get { return this.items; }\n            }\n\n            public bool Selected\n            {\n                get { return selected; }\n                set\n                {\n                    if (selected != value)\n                    {\n                        if (!this.owner.selectedIndexChanging)\n                            this.owner.SelectedIndex = Array.IndexOf(this.owner.itemTabs, this);\n                        this.selected = value;\n                    }\n                }\n            }\n\n            public bool SetItemSource(ItemBase[] items)\n            {\n                if (items == null || items.Length != ItemCount)\n                {\n                    return false;\n                }\n                else\n                {\n                    this.items = items;\n                    return true;\n                }\n            }\n\n            public void ClearItems()\n            {\n                for (int i = 0; i < this.items.Length; i++)\n                {\n                    this.items[i] = null;\n                }\n            }\n\n            public BitmapOrigin TabEnabled\n            {\n                get { return tabEnabled; }\n                set { tabEnabled = value; }\n            }\n\n            public BitmapOrigin TabDisabled\n            {\n                get { return tabDisabled; }\n                set { tabDisabled = value; }\n            }\n\n            public int ScrollMaxValue\n            {\n                get { return this.items.Length / 4; }\n            }\n\n            public int ScrollValue\n            {\n                get { return scrollValue; }\n                set\n                {\n                    value = Math.Min(Math.Max(0, value), ScrollMaxValue);\n                    scrollValue = value;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/AfrmStat.cs",
    "content": "﻿using System;\nusing System.Drawing;\nusing System.Collections.Generic;\nusing System.Windows.Forms;\nusing System.Text;\nusing CharaSimResource;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Common;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class AfrmStat : AlphaForm\n    {\n        public AfrmStat()\n        {\n            initCtrl();\n        }\n\n        private bool fullMode;\n        private Character character;\n\n        private ACtrlButton btnHPUp;\n        private ACtrlButton btnMPUp;\n        private ACtrlButton btnStrUp;\n        private ACtrlButton btnDexUp;\n        private ACtrlButton btnIntUp;\n        private ACtrlButton btnLukUp;\n        private ACtrlButton btnAuto;\n        private ACtrlButton btnClose;\n        private ACtrlButton btnDetailOpen;\n        private ACtrlButton btnDetailClose;\n        private bool waitForRefresh;\n\n        public Character Character\n        {\n            get { return character; }\n            set { character = value; }\n        }\n\n        private void initCtrl()\n        {\n            this.btnHPUp = new ACtrlButton();\n            this.btnHPUp.Location = new Point(167, 157);\n\n            this.btnMPUp = new ACtrlButton();\n            this.btnMPUp.Location = new Point(167, 175);\n\n            this.btnStrUp = new ACtrlButton();\n            this.btnStrUp.Location = new Point(167, 262);\n\n            this.btnDexUp = new ACtrlButton();\n            this.btnDexUp.Location = new Point(167, 280);\n\n            this.btnIntUp = new ACtrlButton();\n            this.btnIntUp.Location = new Point(167, 298);\n\n            this.btnLukUp = new ACtrlButton();\n            this.btnLukUp.Location = new Point(167, 316);\n\n            ACtrlButton[] addBtnList = new ACtrlButton[] { btnHPUp, btnMPUp, btnStrUp, btnDexUp, btnIntUp, btnLukUp };\n            for (int i = 0; i < addBtnList.Length; i++)\n            {\n                addBtnList[i].Normal = new BitmapOrigin(Resource.Stat_main_BtUp_normal_0);\n                addBtnList[i].MouseOver = new BitmapOrigin(Resource.Stat_main_BtUp_mouseOver_0);\n                addBtnList[i].Pressed = new BitmapOrigin(Resource.Stat_main_BtUp_pressed_0);\n                addBtnList[i].Disabled = new BitmapOrigin(Resource.Stat_main_BtUp_disabled_0);\n                addBtnList[i].Size = new Size(12, 12);\n                addBtnList[i].ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            }\n\n            this.btnClose = new ACtrlButton();\n            this.btnClose.Normal = new BitmapOrigin(Resource.BtClose3_normal_0);\n            this.btnClose.Pressed = new BitmapOrigin(Resource.BtClose3_pressed_0);\n            this.btnClose.MouseOver = new BitmapOrigin(Resource.BtClose3_mouseOver_0);\n            this.btnClose.Disabled = new BitmapOrigin(Resource.BtClose3_disabled_0);\n            this.btnClose.Location = new Point(170, 6);\n            this.btnClose.Size = new Size(13, 13);\n            this.btnClose.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnClose.MouseClick += new MouseEventHandler(btnClose_MouseClick);\n\n            this.btnDetailOpen = new ACtrlButton();\n            this.btnDetailOpen.Normal = new BitmapOrigin(Resource.Stat_main_BtDetailOpen_normal_0);\n            this.btnDetailOpen.Pressed = new BitmapOrigin(Resource.Stat_main_BtDetailOpen_pressed_0);\n            this.btnDetailOpen.MouseOver = new BitmapOrigin(Resource.Stat_main_BtDetailOpen_mouseOver_0);\n            this.btnDetailOpen.Disabled = new BitmapOrigin(Resource.Stat_main_BtDetailOpen_disabled_0);\n            this.btnDetailOpen.Location = new Point(112,344);\n            this.btnDetailOpen.Size = new Size(68, 16);\n            this.btnDetailOpen.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnDetailOpen.MouseClick += new MouseEventHandler(btnDetailOpen_MouseClick);\n\n            this.btnDetailClose = new ACtrlButton();\n            this.btnDetailClose.Normal = new BitmapOrigin(Resource.Stat_main_BtDetailClose_normal_0);\n            this.btnDetailClose.Pressed = new BitmapOrigin(Resource.Stat_main_BtDetailClose_pressed_0);\n            this.btnDetailClose.MouseOver = new BitmapOrigin(Resource.Stat_main_BtDetailClose_mouseOver_0);\n            this.btnDetailClose.Disabled = new BitmapOrigin(Resource.Stat_main_BtDetailClose_disabled_0);\n            this.btnDetailClose.Location = new Point(112, 344);\n            this.btnDetailClose.Size = new Size(68, 16);\n            this.btnDetailClose.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n            this.btnDetailClose.MouseClick += new MouseEventHandler(btnDetailClose_MouseClick);\n\n            this.btnAuto = new ACtrlButton();\n            this.btnAuto.Normal = new BitmapOrigin(Resource.Stat_main_BtAuto_normal_3);\n            this.btnAuto.Pressed = new BitmapOrigin(Resource.Stat_main_BtAuto_pressed_0);\n            this.btnAuto.MouseOver = new BitmapOrigin(Resource.Stat_main_BtAuto_mouseOver_0);\n            this.btnAuto.Disabled = new BitmapOrigin(Resource.Stat_main_BtAuto_disabled_0);\n            this.btnAuto.Location = new Point(108, 217);\n            this.btnAuto.Size = new Size(67, 34);\n            this.btnAuto.ButtonStateChanged += new EventHandler(aCtrl_RefreshCall);\n        }\n\n        private IEnumerable<AControl> aControls\n        {\n            get\n            {\n                yield return btnHPUp;\n                yield return btnMPUp;\n                yield return btnStrUp;\n                yield return btnDexUp;\n                yield return btnIntUp;\n                yield return btnLukUp;\n                yield return btnAuto;\n                yield return btnClose;\n                yield return btnDetailOpen;\n                yield return btnDetailClose;\n            }\n        }\n\n        public override void Refresh()\n        {\n            this.preRender();\n            this.SetBitmap(this.Bitmap);\n            this.CaptionRectangle = new Rectangle(0, 0, this.Bitmap.Width, 24);\n            base.Refresh();\n        }\n\n        protected override bool captionHitTest(Point point)\n        {\n            if (this.btnClose.Rectangle.Contains(point))\n                return false;\n            return base.captionHitTest(point);\n        }\n\n        private void preRender()\n        {\n            if (Bitmap != null)\n                Bitmap.Dispose();\n\n            setControlState();\n\n            Size size = Resource.Stat_main_backgrnd.Size;\n            if (fullMode)\n                size = new Size(size.Width + Resource.Stat_detail_backgrnd.Width, size.Height);\n\n            //绘制背景\n            Bitmap stat = new Bitmap(size.Width, size.Height);\n            Graphics g = Graphics.FromImage(stat);\n            renderBase(g);\n            if (fullMode)\n                renderDetail(g);\n\n            //绘制按钮\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.Draw(g);\n            }\n\n            g.Dispose();\n            this.Bitmap = stat;\n        }\n\n        private void setControlState()\n        {\n            if (this.fullMode)\n            {\n                this.btnDetailOpen.Visible = true;\n                this.btnDetailClose.Visible = false;\n            }\n            else\n            {\n                this.btnDetailOpen.Visible = false;\n                this.btnDetailClose.Visible = true; \n            }\n\n            if (this.character != null)\n            {\n                CharacterStatus charStat = this.character.Status;\n                setButtonEnabled(this.btnHPUp, charStat.Ap > 0 && charStat.MaxHP.BaseVal < charStat.MaxHP.TotalMax);\n                setButtonEnabled(this.btnMPUp, charStat.Ap > 0 && charStat.MaxMP.BaseVal < charStat.MaxMP.TotalMax);\n                setButtonEnabled(this.btnStrUp, charStat.Ap > 0 && charStat.Strength.BaseVal <= 999);\n                setButtonEnabled(this.btnDexUp, charStat.Ap > 0 && charStat.Dexterity.BaseVal <= 999);\n                setButtonEnabled(this.btnIntUp, charStat.Ap > 0 && charStat.Intelligence.BaseVal <= 999);\n                setButtonEnabled(this.btnLukUp, charStat.Ap > 0 && charStat.Luck.BaseVal <= 999);\n                setButtonEnabled(this.btnAuto, charStat.Ap > 0);\n            }\n            else\n            {\n                foreach (AControl ctrl in this.aControls)\n                {\n                    setButtonEnabled(ctrl as ACtrlButton, true);\n                }\n            }\n        }\n\n        private void setButtonEnabled(ACtrlButton button, bool enabled)\n        {\n            if (button == null)\n                return;\n            if (enabled)\n            {\n                if (button.State == ButtonState.Disabled)\n                {\n                    button.State = ButtonState.Normal;\n                }\n            }\n            else\n            {\n                if (button.State != ButtonState.Disabled)\n                {\n                    button.State = ButtonState.Disabled;\n                }\n            }\n        }\n\n        private void renderBase(Graphics g)\n        {\n            g.DrawImage(Resource.Stat_main_backgrnd, 0, 0);\n            g.DrawImage(Resource.Stat_main_backgrnd2, 6, 22);\n            g.DrawImage(Resource.Stat_main_backgrnd3, 7, 211);\n\n            if (this.character != null)\n            {\n                CharacterStatus charStat = this.character.Status;\n                //绘制自动分配\n               // g.DrawImage(charStat.Ap > 0 ? Resource.Stat_main_BtAuto_normal_3 : Resource.Stat_main_BtAuto_disabled_0, 94, 180);\n                switch (charStat.Job / 100 % 10)//绘制角色属性灰色背景\n                {\n                    case 0:\n                    case 1:\n                    case 3:\n                    case 5:\n                        g.DrawImage(Resource.Stat_main_Disabled_INT, 11, 296);\n                        g.DrawImage(Resource.Stat_main_Disabled_LUK, 11, 314);\n                        break;\n                    case 2:\n                        g.DrawImage(Resource.Stat_main_Disabled_STR, 11, 260);\n                        g.DrawImage(Resource.Stat_main_Disabled_DEX, 11, 278);\n                        break;\n                    case 4:\n                        g.DrawImage(Resource.Stat_main_Disabled_STR, 11, 260);\n                        g.DrawImage(Resource.Stat_main_Disabled_INT, 11, 296);\n                        break;\n                }\n                g.DrawString(this.character.Name, GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 32f);\n                g.DrawString(ItemStringHelper.GetJobName(charStat.Job), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 50f);\n                g.DrawString(charStat.Level.ToString(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 68f);\n                g.DrawString(charStat.Exp + \" (\" + (charStat.Exp == -1 ? 0 : ((long)charStat.Exp * 100 / charStat.Exptnl)) + \"%)\",\n                    GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 86f);\n                g.DrawString(\"1\", GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 104f);\n                g.DrawString(\"0 (0%)\", GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 122f);\n                g.DrawString(string.IsNullOrEmpty(this.character.Guild) ? \"-\" : this.character.Guild, GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 140f);\n                g.DrawString(charStat.HP + \" / \" + charStat.MaxHP.GetSum(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 158f);\n                g.DrawString(charStat.MP + \" / \" + charStat.MaxMP.GetSum(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 176f);\n                g.DrawString(charStat.Pop.ToString(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 194f);\n\n                g.DrawString(charStat.Ap.ToString().PadLeft(4), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 64f, 236f);\n                g.DrawString(charStat.Strength.ToString(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 263f);\n                g.DrawString(charStat.Dexterity.ToString(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 281f);\n                g.DrawString(charStat.Intelligence.ToString(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 299f);\n                g.DrawString(charStat.Luck.ToString(), GearGraphics.ItemDetailFont, GearGraphics.StatDetailGrayBrush, 72f, 317f);\n            }\n        }\n\n        private void renderDetail(Graphics g)\n        {\n            g.TranslateTransform(Resource.Stat_main_backgrnd.Width,\n                Resource.Stat_main_backgrnd.Height - Resource.Stat_detail_backgrnd.Height);\n            g.DrawImage(Resource.Stat_detail_backgrnd, 0, 0);\n            g.DrawImage(Resource.Stat_detail_backgrnd2, 6, 7);\n\n            if (this.character != null)\n            {\n                CharacterStatus charStat = this.character.Status;\n                //g.DrawString(\"0 ( 0% )\", GearGraphics.GearDetailFont, getDetailBrush(0), 72f, 16f);\n                //g.DrawString(\"0\", GearGraphics.GearDetailFont, getDetailBrush(0), 72f, 34f);\n                int brushSign;\n\n                double max, min;\n                this.character.CalcAttack(out max, out min, out brushSign);\n                float y = 16f;\n                g.DrawString(max == 0 ? \"0\" : Math.Round(min) + \" ~ \" + Math.Round(max), GearGraphics.ItemDetailFont, getDetailBrush(brushSign), 72f, y);\n                g.DrawString(charStat.CriticalRate.GetSum() + \"%\", GearGraphics.ItemDetailFont, charStat.CriticalRate.BuffAdd > 0 ? Brushes.Red : GearGraphics.StatDetailGrayBrush, 72f, (y += 18f));\n                g.DrawString(charStat.PDDamage.ToStringDetail(out brushSign), GearGraphics.ItemDetailFont, getDetailBrush(brushSign), 72f, (y += 18f));\n                g.DrawString(charStat.MDDamage.ToStringDetail(out brushSign), GearGraphics.ItemDetailFont, getDetailBrush(brushSign), 72f, (y += 18f));\n                g.DrawString(charStat.PAccurate.ToStringDetail(out brushSign), GearGraphics.ItemDetailFont, getDetailBrush(brushSign), 72f, (y += 18f));\n                g.DrawString(charStat.MAccurate.ToStringDetail(out brushSign), GearGraphics.ItemDetailFont, getDetailBrush(brushSign), 72f, (y += 18f));\n                g.DrawString(charStat.PEvasion.ToStringDetail(out brushSign), GearGraphics.ItemDetailFont, getDetailBrush(brushSign), 72f, (y += 18f));\n                g.DrawString(charStat.MEvasion.ToStringDetail(out brushSign), GearGraphics.ItemDetailFont, getDetailBrush(brushSign), 72f, (y += 18f));\n                g.DrawString(charStat.MoveSpeed.GetSum() + \"%\", GearGraphics.ItemDetailFont, getDetailBrush(0), 72f, (y += 18f));\n                g.DrawString(charStat.Jump.GetSum() + \"%\", GearGraphics.ItemDetailFont, getDetailBrush(0), 72f, (y += 18f));\n            }\n\n            g.ResetTransform();\n        }\n\n        private Brush getDetailBrush(int sign)\n        {\n            switch (sign)\n            {\n                case 1: return Brushes.Red;\n                case -1: return Brushes.Blue;\n                case 0:\n                default: return GearGraphics.StatDetailGrayBrush;\n            }\n\n        }\n\n        private void btnClose_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.Visible = false;\n        }\n\n        private void aCtrl_RefreshCall(object sender, EventArgs e)\n        {\n            this.waitForRefresh = true;\n        }\n\n        private void btnDetailOpen_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.fullMode = false;\n            this.waitForRefresh = true;\n        }\n\n        private void btnDetailClose_MouseClick(object sender, MouseEventArgs e)\n        {\n            this.fullMode = true;\n            this.waitForRefresh = true;\n        }\n\n        protected override void OnMouseMove(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseMove(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseMove(e);\n        }\n\n        protected override void OnMouseDown(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseDown(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseDown(e);\n        }\n\n        protected override void OnMouseUp(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseUp(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseUp(e);\n        }\n\n        protected override void OnMouseClick(MouseEventArgs e)\n        {\n            foreach (AControl ctrl in this.aControls)\n            {\n                ctrl.OnMouseClick(e);\n            }\n\n            if (this.waitForRefresh)\n            {\n                this.Refresh();\n                waitForRefresh = false;\n            }\n\n            base.OnMouseClick(e);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/AfrmTooltip.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Windows.Forms;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class AfrmTooltip : AlphaForm\n    {\n        public AfrmTooltip()\n        {\n            this.menu = new ContextMenuStrip();\n            this.menu.Items.Add(new ToolStripMenuItem(\"复制(&C)\", null, tsmiCopy_Click));\n            this.menu.Items.Add(new ToolStripMenuItem(\"保存(&S)\", null, tsmiSave_Click));\n            this.ContextMenuStrip = this.menu;\n\n            this.Size = new Size(1, 1);\n            this.HideOnHover = true;\n            this.FamiliarRender = new FamiliarTooltipRender();\n            this.GearRender = new GearTooltipRender2();\n            this.ItemRender = new ItemTooltipRender2();\n            this.SkillRender = new SkillTooltipRender2();\n            this.RecipeRender = new RecipeTooltipRender();\n            this.MobRender = new MobTooltipRenderer();\n            this.NpcRender = new NpcTooltipRenderer();\n            this.SizeChanged += AfrmTooltip_SizeChanged;\n\n            this.MouseClick += AfrmTooltip_MouseClick;\n        }\n\n        private object item;\n\n        private ContextMenuStrip menu;\n        private bool showMenu;\n        private bool showID;\n\n        public Object TargetItem\n        {\n            get { return item; }\n            set { item = value; }\n        }\n\n        public StringLinker StringLinker { get; set; }\n        public Character Character { get; set; }\n\n        \n        public FamiliarTooltipRender FamiliarRender { get; private set; }\n        public GearTooltipRender2 GearRender { get; private set; }\n        public ItemTooltipRender2 ItemRender { get; private set; }\n        public SkillTooltipRender2 SkillRender { get; private set; }\n        public RecipeTooltipRender RecipeRender { get; private set; }\n        public MobTooltipRenderer MobRender { get; private set; }\n        public NpcTooltipRenderer NpcRender { get; private set; }\n\n        public string ImageFileName { get; set; }\n\n        public bool ShowID\n        {\n            get { return this.showID; }\n            set\n            {\n                this.showID = value;\n                this.FamiliarRender.ShowObjectID = value;\n                this.GearRender.ShowObjectID = value;\n                this.ItemRender.ShowObjectID = value;\n                this.SkillRender.ShowObjectID = value;\n                this.RecipeRender.ShowObjectID = value;\n            }\n        }\n\n        public bool ShowMenu\n        {\n            get { return showMenu; }\n            set { showMenu = value; }\n        }\n\n        public override void Refresh()\n        {\n            this.PreRender();\n            if (this.Bitmap != null)\n            {\n                this.SetBitmap(Bitmap);\n                this.CaptionRectangle = new Rectangle(0, 0, Bitmap.Width, Bitmap.Height);\n                base.Refresh();\n            }\n        }\n\n        public void PreRender()\n        {\n            if (this.item == null)\n                return;\n\n            TooltipRender renderer;\n            if (item is Item)\n            {\n                renderer = ItemRender;\n                ItemRender.Item = this.item as Item;\n            }\n            else if (item is Familiar)\n            {\n                renderer = FamiliarRender;\n                FamiliarRender.Familiar = this.item as Familiar;\n                // FamiliarRender.UseAssembleUI = EnableAssembleTooltip;\n            }\n            else if (item is Gear)\n            {\n                renderer = GearRender;\n                GearRender.Gear = this.TargetItem as Gear;\n\n                if (false)\n                {\n                    Gear g = GearRender.Gear;\n                    if (this.StringLinker.StringEqp.ContainsKey(g.ItemID))\n                    {\n                        this.StringLinker.StringEqp[g.ItemID].Name = \"暴君之高卡文黑锅\";\n                        this.StringLinker.StringEqp[g.ItemID].Desc = @\"\"\"#c这个锅 我背了！#\"\" ————gaokawen\";\n                    }\n                    g.Star = 25;\n                    g.Grade = GearGrade.SS;\n                    g.AdditionGrade = GearGrade.B;\n                    g.Props[GearPropType.reqLevel] = 250;\n                    g.Props[GearPropType.reqSTR] = 6;\n                    g.Props[GearPropType.reqDEX] = 6;\n                    g.Props[GearPropType.reqINT] = 6;\n                    g.Props[GearPropType.reqLUK] = 6;\n                    g.Props[GearPropType.reqPOP] = 666;\n                    g.Props[GearPropType.level] = 1;\n                    g.Props[GearPropType.reqJob] = 0;\n                    g.Props[GearPropType.incPAD] = 6;\n                    g.Props[GearPropType.incMAD] = 6;\n                    g.Props[GearPropType.incPDD] = 666;\n                    g.Props[GearPropType.incMDD] = 666;\n                    g.Props[GearPropType.tuc] = 66;\n                    g.Props[GearPropType.superiorEqp] = 1;\n                    g.Props[GearPropType.tradeAvailable] = 2;\n                    //g.Props[GearPropType.charismaEXP] = 88;\n                    //g.Props[GearPropType.willEXP] = 88;\n                    //g.Props[GearPropType.charmEXP] = 88;\n                    g.Props[GearPropType.nActivatedSocket] = 1;\n                    //g.Props[GearPropType.setItemID] = 135;\n                    //g.Options[0] = Potential.LoadFromWz(60001, 3);\n                    //g.Options[1] = Potential.LoadFromWz(60001, 3);\n                    //g.Options[2] = Potential.LoadFromWz(60001, 3);\n                    //g.AdditionalOptions[0] = Potential.LoadFromWz(32086, 10);\n                    //g.AdditionalOptions[1] = Potential.LoadFromWz(32086, 10);\n                    //g.AdditionalOptions[2] = Potential.LoadFromWz(32086, 10);\n                }\n            }\n            else if (item is Skill)\n            {\n                renderer = SkillRender;\n                SkillRender.Skill = this.item as Skill;\n            }\n            else if (item is Recipe)\n            {\n                renderer = RecipeRender;\n                RecipeRender.Recipe = this.item as Recipe;\n            }\n            else if (item is Mob)\n            {\n                renderer = MobRender;\n                MobRender.MobInfo = this.item as Mob;\n            }\n            else if (item is Npc)\n            {\n                renderer = NpcRender;\n                NpcRender.NpcInfo = this.item as Npc;\n            }\n            else\n            {\n                this.Bitmap = null;\n                renderer = null;\n                return;\n            }\n            renderer.StringLinker = StringLinker;\n            this.Bitmap = renderer.Render();\n        }\n\n        void AfrmTooltip_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)\n        {\n            if (e.Button == MouseButtons.Right && this.showMenu)\n            {\n                this.menu.Show(this, e.Location);\n            }\n        }\n\n        void tsmiCopy_Click(object sender, EventArgs e)\n        {\n            if (this.Bitmap != null)\n            {\n                var dataObj = new ImageDataObject(this.Bitmap, this.ImageFileName);\n                Clipboard.SetDataObject(dataObj, false);\n            }\n        }\n\n        void tsmiSave_Click(object sender, EventArgs e)\n        {\n            if (this.Bitmap != null && this.item != null)\n            {\n                using (SaveFileDialog dlg = new SaveFileDialog())\n                {\n                    dlg.Filter = \"*.png|*.png|*.*|*.*\";\n                    dlg.FileName = this.ImageFileName;\n\n                    if (dlg.ShowDialog() == DialogResult.OK)\n                    {\n                        this.Bitmap.Save(dlg.FileName, System.Drawing.Imaging.ImageFormat.Png);\n                    }\n                }\n            }\n        }\n\n        void AfrmTooltip_SizeChanged(object sender, EventArgs e)\n        {\n            if (this.Bitmap != null)\n                this.SetClientSizeCore(this.Bitmap.Width, this.Bitmap.Height);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/ButtonState.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public enum ButtonState\n    {\n        Normal = 0,\n        Pressed,\n        MouseOver,\n        Disabled\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/CharaSimControlGroup.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Windows.Forms;\nusing System.Drawing;\nusing System.Text;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class CharaSimControlGroup\n    {\n        public CharaSimControlGroup()\n        {\n            tooltip = new AfrmTooltip();\n            tooltip.TopMost = true;\n        }\n\n        private AfrmTooltip tooltip;\n        private AfrmItem frmItem;\n        private AfrmStat frmStat;\n        private AfrmEquip frmEquip;\n        private Character character;\n        private StringLinker stringLinker;\n\n        public AfrmItem UIItem\n        {\n            get\n            {\n                if (frmItem == null)\n                {\n                    frmItem = new AfrmItem();\n                    frmItem.KeyDown += new KeyEventHandler(afrm_KeyDown);\n                    frmItem.MouseDown += new MouseEventHandler(frmItem_MouseDown);\n                    frmItem.DragOver += new DragEventHandler(frmItem_DragOver);\n                    frmItem.DragDrop += new DragEventHandler(frmItem_DragDrop);\n                    frmItem.ItemMouseMove += new ItemMouseEventHandler(frmItem_ItemMouseMove);\n                    frmItem.ItemMouseLeave += new EventHandler(frmItem_ItemMouseLeave);\n                    frmItem.Character = this.character;\n                }\n                return frmItem;\n            }\n        }\n\n        public AfrmStat UIStat\n        {\n            get\n            {\n                if (frmStat == null)\n                {\n                    frmStat = new AfrmStat();\n                    frmStat.KeyDown += new KeyEventHandler(afrm_KeyDown);\n                    frmStat.Character = this.character;\n                }\n                return frmStat;\n            }\n        }\n\n        public AfrmEquip UIEquip\n        {\n            get\n            {\n                if (frmEquip == null)\n                {\n                    frmEquip = new AfrmEquip();\n                    frmEquip.KeyDown += new KeyEventHandler(afrm_KeyDown);\n                    frmEquip.MouseDown += new MouseEventHandler(frmEquip_MouseDown);\n                    frmEquip.DragOver += new DragEventHandler(frmEquip_DragOver);\n                    frmEquip.DragDrop += new DragEventHandler(frmEquip_DragDrop);\n                    frmEquip.Character = this.character;\n                }\n                return frmEquip;\n            }\n        }\n\n        public Character Character\n        {\n            get { return character; }\n            set\n            {\n                this.character = value;\n                this.tooltip.Character = value;\n                if (frmEquip != null)\n                    this.frmEquip.Character = value;\n                if (frmStat != null)\n                    this.frmStat.Character = value;\n                if (frmEquip != null)\n                    this.frmEquip.Character = value;\n            }\n        }\n\n        public StringLinker StringLinker\n        {\n            get { return stringLinker; }\n            set\n            {\n                this.stringLinker = value;\n                this.tooltip.StringLinker = value;\n            }\n        }\n\n        private void afrm_KeyDown(object sender, KeyEventArgs e)\n        {\n            Form frm = sender as Form;\n            if (frm == null)\n                return;\n\n            switch (e.KeyCode)\n            {\n                case Keys.Escape:\n                    frm.Hide();\n                    break;\n                case Keys.F1:\n                    break;\n                case Keys.Up:\n                    frm.Top -= 1;\n                    break;\n                case Keys.Down:\n                    frm.Top += 1;\n                    break;\n                case Keys.Left:\n                    frm.Left -= 1;\n                    break;\n                case Keys.Right:\n                    frm.Left += 1;\n                    break;\n            }\n        }\n\n        private void frmItem_MouseDown(object sender, MouseEventArgs e)\n        {\n            ItemBase dragItem = frmItem.GetItemByPoint(e.Location);\n\n            if (dragItem != null)\n            {\n                if (Control.ModifierKeys == Keys.Control)\n                {\n                    int originIdx = Array.IndexOf<ItemBase>(frmItem.SelectedTab.Items, dragItem);\n                    if (originIdx > -1)\n                    {\n                        frmItem.SelectedTab.Items[originIdx] = null;\n                        frmItem.Refresh();\n                    }\n                }\n                else\n                {\n                    frmItem.DoDragDrop(dragItem, DragDropEffects.Move);\n                    tooltip.Visible = false;\n                }\n            }\n        }\n\n        private ItemBase getDragDataItem(IDataObject data)\n        {\n            ItemBase dragItem;\n            if (data != null\n                && ((dragItem = data.GetData(typeof(Item)) as Item) != null\n                || (dragItem = data.GetData(typeof(Gear)) as Gear) != null))\n            {\n                return dragItem;\n            }\n            return null;\n        }\n\n        private void frmItem_DragOver(object sender, DragEventArgs e)\n        {\n            ItemBase dragItem;\n            int idx;\n            if ((e.AllowedEffect & DragDropEffects.Move) != 0\n                && (idx = frmItem.GetItemIndexByPoint(frmItem.PointToClient(new Point(e.X, e.Y)))) != -1\n                && (dragItem = getDragDataItem(e.Data)) != null)\n            {\n                e.Effect = DragDropEffects.Move;\n            }\n            else\n            {\n                e.Effect = DragDropEffects.None;\n            }\n        }\n\n        private void frmItem_DragDrop(object sender, DragEventArgs e)\n        {\n            if ((e.Effect & (DragDropEffects.Move)) == 0)\n            {\n                return;\n            }\n\n            ItemBase dragItem;\n            int dropIdx, originIdx;\n            if ((dragItem = getDragDataItem(e.Data)) == null\n                || (originIdx = Array.IndexOf<ItemBase>(frmItem.SelectedTab.Items, dragItem)) == -1)\n            {\n                return;\n            }\n            dropIdx = frmItem.GetItemIndexByPoint(frmItem.PointToClient(new Point(e.X, e.Y)));\n            if (dropIdx == -1) //移除装备\n            {\n                frmItem.SelectedTab.Items[originIdx] = null;\n            }\n            else if (originIdx != dropIdx)\n            {\n                frmItem.SelectedTab.Items[originIdx] = frmItem.SelectedTab.Items[dropIdx];\n                frmItem.SelectedTab.Items[dropIdx] = dragItem;\n            }\n            else\n            {\n                return;\n            }\n            frmItem.Refresh();\n        }\n\n        private void frmItem_ItemMouseMove(object sender, ItemMouseEventArgs e)\n        {\n            if (e.Item == null)\n            {\n                tooltip.Visible = false;\n                return;\n            }\n            if (e.Item != tooltip.TargetItem)\n            {\n                tooltip.TargetItem = e.Item;\n                tooltip.Refresh();\n            }\n            Point pos = frmItem.PointToScreen(e.Location);\n            pos.Offset(5, 5);\n            tooltip.Location = pos;\n            tooltip.Visible = true;\n            tooltip.BringToFront();\n        }\n\n        private void frmItem_ItemMouseLeave(object sender, EventArgs e)\n        {\n            tooltip.Visible = false;\n        }\n\n        private void frmEquip_MouseDown(object sender, MouseEventArgs e)\n        {\n            //throw new NotImplementedException();\n        }\n\n        private void frmEquip_DragOver(object sender, DragEventArgs e)\n        {\n            //throw new NotImplementedException();\n        }\n\n        private void frmEquip_DragDrop(object sender, DragEventArgs e)\n        {\n            //throw new NotImplementedException();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/DamageSkinTooltipRender.cs",
    "content": "﻿using System;\nusing System.Drawing;\nusing System.Linq;\nusing WzComparerR2.CharaSim;\nusing Resource = CharaSimResource.Resource;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class DamageSkinTooltipRender : TooltipRender\n    {\n        public DamageSkinTooltipRender()\n        {\n        }\n\n        private DamageSkin damageSkin;\n\n        public DamageSkin DamageSkin\n        {\n            get { return damageSkin; }\n            set { damageSkin = value; }\n        }\n\n\n        public override object TargetItem\n        {\n            get { return this.damageSkin; }\n            set { this.damageSkin = value as DamageSkin; }\n        }\n\n        public bool UseMiniSize { get; set; }\n        public bool AlwaysUseMseaFormat { get; set; }\n        public bool DisplayUnitOnSingleLine { get; set; }\n        public long DamageSkinNumber { get; set; }\n\n        public override Bitmap Render()\n        {\n            if (this.damageSkin == null)\n            {\n                return null;\n            }\n\n            Bitmap customSampleNonCritical = GetCustomSample(DamageSkinNumber, UseMiniSize, false);\n            Bitmap customSampleCritical = GetCustomSample(DamageSkinNumber, UseMiniSize, true);\n            Bitmap extraBitmap = GetExtraEffect();\n            Bitmap unitBitmap = null;\n\n            int previewWidth = Math.Max(customSampleNonCritical.Width, customSampleCritical.Width);\n            int previewHeight = customSampleNonCritical.Height + customSampleCritical.Height;\n\n            if (extraBitmap != null)\n            {\n                previewWidth = Math.Max(previewWidth, extraBitmap.Width);\n                previewHeight += extraBitmap.Height;\n                if (DisplayUnitOnSingleLine)\n                {\n                    unitBitmap = GetUnit();\n                    if (unitBitmap != null)\n                    {\n                        previewWidth = Math.Max(previewWidth, unitBitmap.Width);\n                        previewHeight += unitBitmap.Height;\n                    }\n                }\n            }\n\n            int picH = 10;\n\n            Bitmap tooltip = new Bitmap(previewWidth + 30, previewHeight + 30);\n            Graphics g = Graphics.FromImage(tooltip);\n\n            GearGraphics.DrawNewTooltipBack(g, 0, 0, tooltip.Width, tooltip.Height);\n\n            if (this.ShowObjectID)\n            {\n                GearGraphics.DrawGearDetailNumber(g, 3, 3, $\"{this.damageSkin.DamageSkinID.ToString()}\", true);\n            }\n\n            g.DrawImage(customSampleNonCritical, 10, picH, new Rectangle(0, 0, customSampleNonCritical.Width, customSampleNonCritical.Height), GraphicsUnit.Pixel);\n\n            picH += customSampleNonCritical.Height + 5;\n\n            g.DrawImage(customSampleCritical, 10, picH, new Rectangle(0, 0, customSampleCritical.Width, customSampleCritical.Height), GraphicsUnit.Pixel);\n\n            picH += customSampleCritical.Height + 5;\n\n            if (unitBitmap != null)\n            {\n                g.DrawImage(unitBitmap, 10, picH, new Rectangle(0, 0, unitBitmap.Width, unitBitmap.Height), GraphicsUnit.Pixel);\n                picH += unitBitmap.Height + 5;\n            }\n\n            if (extraBitmap != null)\n            {\n                g.DrawImage(extraBitmap, 10, picH, new Rectangle(0, 0, extraBitmap.Width, extraBitmap.Height), GraphicsUnit.Pixel);\n            }\n\n            customSampleNonCritical.Dispose();\n            customSampleCritical.Dispose();\n            g.Dispose();\n            return tooltip;\n        }\n\n        public Bitmap GetCustomSample(long inputNumber, bool useMiniSize, bool isCritical)\n        {\n            string numberStr = \"\";\n            if (DisplayUnitOnSingleLine)\n            {\n                numberStr = inputNumber.ToString();\n            }\n            else\n            {\n                switch (damageSkin.CustomType)\n                {\n                    case \"hangul\": // CJK Detailed\n                        numberStr = ItemStringHelper.ToChineseNumberExpr(inputNumber, true);\n                        break;\n                    case \"hangulUnit\": // CJK\n                        numberStr = ItemStringHelper.ToChineseNumberExpr(inputNumber);\n                        break;\n                    case \"glUnit\": // GMS\n                        numberStr = ItemStringHelper.ToThousandsNumberExpr(inputNumber, this.AlwaysUseMseaFormat);\n                        break;\n                    case \"glUnit2\": // MSEA\n                        numberStr = ItemStringHelper.ToThousandsNumberExpr(inputNumber, true);\n                        break;\n                    default:\n                        if (this.DamageSkin.MiniUnit.Count > 0) // Default to CJK format when units are available\n                        {\n                            numberStr = ItemStringHelper.ToChineseNumberExpr(inputNumber);\n                        }\n                        else\n                        {\n                            numberStr = inputNumber.ToString();\n                        }\n                        break;\n                }\n            }\n\n\n            Bitmap criticalSign = null;\n            if (this.damageSkin.BigCriticalDigit.ContainsKey(\"effect3\"))\n            {\n                criticalSign = this.damageSkin.BigCriticalDigit[\"effect3\"].Bitmap;\n            }\n\n            int totalWidth = 0;\n            int maxHeight = 0;\n            int digitSpacing = isCritical ?\n                            (useMiniSize ? this.damageSkin.MiniCriticalDigitSpacing :\n                            this.damageSkin.BigCriticalDigitSpacing) :\n                            (useMiniSize ? this.damageSkin.MiniDigitSpacing :\n                            this.damageSkin.BigDigitSpacing);\n            int unitSpacing = isCritical ?\n                            (useMiniSize ? this.damageSkin.MiniCriticalUnitSpacing :\n                            this.damageSkin.BigCriticalUnitSpacing) :\n                            (useMiniSize ? this.damageSkin.MiniUnitSpacing :\n                            this.damageSkin.BigUnitSpacing);\n\n            if (isCritical && criticalSign != null)\n            {\n                totalWidth += criticalSign.Width + unitSpacing;\n                maxHeight = Math.Max(maxHeight, criticalSign.Height);\n            }\n\n            // Calculate total width and max height\n            foreach (char c in numberStr)\n            {\n                string character = c.ToString();\n                switch (character)\n                {\n                    case \"0\":\n                    case \"1\":\n                    case \"2\":\n                    case \"3\":\n                    case \"4\":\n                    case \"5\":\n                    case \"6\":\n                    case \"7\":\n                    case \"8\":\n                    case \"9\":\n                        totalWidth += isCritical ?\n                            (useMiniSize ? this.damageSkin.MiniCriticalDigit[character].Bitmap.Width :\n                            this.damageSkin.BigCriticalDigit[character].Bitmap.Width) :\n                            (useMiniSize ? this.damageSkin.MiniDigit[character].Bitmap.Width :\n                            this.damageSkin.BigDigit[character].Bitmap.Width);\n                        totalWidth += digitSpacing;\n                        maxHeight = Math.Max(maxHeight, isCritical ?\n                            (useMiniSize ? this.damageSkin.MiniCriticalDigit[character].Bitmap.Height :\n                            this.damageSkin.BigCriticalDigit[character].Bitmap.Height) :\n                            (useMiniSize ? this.damageSkin.MiniDigit[character].Bitmap.Height :\n                            this.damageSkin.BigDigit[character].Bitmap.Height));\n                        break;\n\n                    case \"十\":\n                    case \".\":\n                        if (this.damageSkin.BigUnit.ContainsKey(\"0\"))\n                        {\n                            totalWidth += isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"0\"].Bitmap.Width :\n                                this.damageSkin.BigCriticalUnit[\"0\"].Bitmap.Width) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"0\"].Bitmap.Width :\n                                this.damageSkin.BigUnit[\"0\"].Bitmap.Width);\n                            totalWidth += unitSpacing;\n                            maxHeight = Math.Max(maxHeight, isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"0\"].Bitmap.Height :\n                                this.damageSkin.BigCriticalUnit[\"0\"].Bitmap.Height) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"0\"].Bitmap.Height :\n                                this.damageSkin.BigUnit[\"0\"].Bitmap.Height));\n                        }\n                        break;\n\n                    case \"百\":\n                    case \"K\":\n                        if (this.damageSkin.BigUnit.ContainsKey(\"1\"))\n                        {\n                            totalWidth += isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"1\"].Bitmap.Width :\n                                this.damageSkin.BigCriticalUnit[\"1\"].Bitmap.Width) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"1\"].Bitmap.Width :\n                                this.damageSkin.BigUnit[\"1\"].Bitmap.Width);\n                            totalWidth += unitSpacing;\n                            maxHeight = Math.Max(maxHeight, isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"1\"].Bitmap.Height :\n                                this.damageSkin.BigCriticalUnit[\"1\"].Bitmap.Height) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"1\"].Bitmap.Height :\n                                this.damageSkin.BigUnit[\"1\"].Bitmap.Height));\n                        }\n                        break;\n\n                    case \"千\":\n                    case \"M\":\n                        if (this.damageSkin.BigUnit.ContainsKey(\"2\"))\n                        {\n                            totalWidth += isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"2\"].Bitmap.Width :\n                                this.damageSkin.BigCriticalUnit[\"2\"].Bitmap.Width) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"2\"].Bitmap.Width :\n                                this.damageSkin.BigUnit[\"2\"].Bitmap.Width);\n                            totalWidth += unitSpacing;\n                            maxHeight = Math.Max(maxHeight, isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"2\"].Bitmap.Height :\n                                this.damageSkin.BigCriticalUnit[\"2\"].Bitmap.Height) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"2\"].Bitmap.Height :\n                                this.damageSkin.BigUnit[\"2\"].Bitmap.Height));\n                        }\n                        break;\n\n                    case \"万\":\n                    case \"B\":\n                        if (this.damageSkin.BigUnit.ContainsKey(\"3\"))\n                        {\n                            totalWidth += isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"3\"].Bitmap.Width :\n                                this.damageSkin.BigCriticalUnit[\"3\"].Bitmap.Width) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"3\"].Bitmap.Width :\n                                this.damageSkin.BigUnit[\"3\"].Bitmap.Width);\n                            totalWidth += unitSpacing;\n                            maxHeight = Math.Max(maxHeight, isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"3\"].Bitmap.Height :\n                                this.damageSkin.BigCriticalUnit[\"3\"].Bitmap.Height) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"3\"].Bitmap.Height :\n                                this.damageSkin.BigUnit[\"3\"].Bitmap.Height));\n                        }\n                        break;\n\n                    case \"亿\":\n                    case \"T\":\n                        if (this.damageSkin.BigUnit.ContainsKey(\"4\"))\n                        {\n                            totalWidth += isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"4\"].Bitmap.Width :\n                                this.damageSkin.BigCriticalUnit[\"4\"].Bitmap.Width) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"4\"].Bitmap.Width :\n                                this.damageSkin.BigUnit[\"4\"].Bitmap.Width);\n                            totalWidth += unitSpacing;\n                            maxHeight = Math.Max(maxHeight, isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"4\"].Bitmap.Height :\n                                this.damageSkin.BigCriticalUnit[\"4\"].Bitmap.Height) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"4\"].Bitmap.Height :\n                                this.damageSkin.BigUnit[\"4\"].Bitmap.Height));\n                        }\n                        break;\n\n\n                    case \"兆\":\n                    case \"Q\":\n                        if (this.damageSkin.BigUnit.ContainsKey(\"5\"))\n                        {\n                            totalWidth += isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"5\"].Bitmap.Width :\n                                this.damageSkin.BigCriticalUnit[\"5\"].Bitmap.Width) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"5\"].Bitmap.Width :\n                                this.damageSkin.BigUnit[\"5\"].Bitmap.Width);\n                            totalWidth += unitSpacing;\n                            maxHeight = Math.Max(maxHeight, isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"5\"].Bitmap.Height :\n                                this.damageSkin.BigCriticalUnit[\"5\"].Bitmap.Height) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"5\"].Bitmap.Height :\n                                this.damageSkin.BigUnit[\"5\"].Bitmap.Height));\n                        }\n                        break;\n\n                    case \"京\":\n                        if (this.damageSkin.BigUnit.ContainsKey(\"6\"))\n                        {\n                            totalWidth += isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"6\"].Bitmap.Width :\n                                this.damageSkin.BigCriticalUnit[\"6\"].Bitmap.Width) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"6\"].Bitmap.Width :\n                                this.damageSkin.BigUnit[\"6\"].Bitmap.Width);\n                            totalWidth += unitSpacing;\n                            maxHeight = Math.Max(maxHeight, isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"6\"].Bitmap.Height :\n                                this.damageSkin.BigCriticalUnit[\"6\"].Bitmap.Height) :\n                                (useMiniSize ? this.damageSkin.MiniUnit[\"6\"].Bitmap.Height :\n                                this.damageSkin.BigUnit[\"6\"].Bitmap.Height));\n                        }\n                        break;\n                }\n            }\n\n            Bitmap finalBitmap = new Bitmap(totalWidth, maxHeight);\n\n            using (Graphics g = Graphics.FromImage(finalBitmap))\n            {\n                g.Clear(Color.Transparent);\n                int offsetX = 0;\n                if (isCritical && criticalSign != null)\n                {\n                    g.DrawImage(criticalSign, offsetX, 0);\n                    offsetX += criticalSign.Width;\n                }\n                foreach (char c in numberStr)\n                {\n                    string character = c.ToString();\n                    Bitmap charBitmap = null;\n                    switch (character)\n                    {\n                        case \"0\":\n                        case \"1\":\n                        case \"2\":\n                        case \"3\":\n                        case \"4\":\n                        case \"5\":\n                        case \"6\":\n                        case \"7\":\n                        case \"8\":\n                        case \"9\":\n                            charBitmap = isCritical ?\n                                (useMiniSize ? this.damageSkin.MiniCriticalDigit[character].Bitmap :\n                                this.damageSkin.BigCriticalDigit[character].Bitmap) :\n                                (useMiniSize ? this.damageSkin.MiniDigit[character].Bitmap :\n                                this.damageSkin.BigDigit[character].Bitmap);\n                            g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                            offsetX += charBitmap.Width + digitSpacing;\n                            break;\n\n                        case \"十\":\n                        case \".\":\n                            if (this.damageSkin.BigUnit.ContainsKey(\"0\"))\n                            {\n                                charBitmap = isCritical ?\n                                    (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"0\"].Bitmap :\n                                    this.damageSkin.BigCriticalUnit[\"0\"].Bitmap) :\n                                    (useMiniSize ? this.damageSkin.MiniUnit[\"0\"].Bitmap :\n                                    this.damageSkin.BigUnit[\"0\"].Bitmap);\n                                g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                                offsetX += charBitmap.Width + unitSpacing;\n                            }\n                            break;\n\n                        case \"百\":\n                        case \"K\":\n                            if (this.damageSkin.BigUnit.ContainsKey(\"1\"))\n                            {\n                                charBitmap = isCritical ?\n                                    (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"1\"].Bitmap :\n                                    this.damageSkin.BigCriticalUnit[\"1\"].Bitmap) :\n                                    (useMiniSize ? this.damageSkin.MiniUnit[\"1\"].Bitmap :\n                                    this.damageSkin.BigUnit[\"1\"].Bitmap);\n                                g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                                offsetX += charBitmap.Width + unitSpacing;\n                            }\n                            break;\n\n                        case \"千\":\n                        case \"M\":\n                            if (this.damageSkin.BigUnit.ContainsKey(\"2\"))\n                            {\n                                charBitmap = isCritical ?\n                                    (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"2\"].Bitmap :\n                                    this.damageSkin.BigCriticalUnit[\"2\"].Bitmap) :\n                                    (useMiniSize ? this.damageSkin.MiniUnit[\"2\"].Bitmap :\n                                    this.damageSkin.BigUnit[\"2\"].Bitmap);\n                                g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                                offsetX += charBitmap.Width + unitSpacing;\n                            }\n                            break;\n\n                        case \"万\":\n                        case \"B\":\n                            if (this.damageSkin.BigUnit.ContainsKey(\"3\"))\n                            {\n                                charBitmap = isCritical ?\n                                    (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"3\"].Bitmap :\n                                    this.damageSkin.BigCriticalUnit[\"3\"].Bitmap) :\n                                    (useMiniSize ? this.damageSkin.MiniUnit[\"3\"].Bitmap :\n                                    this.damageSkin.BigUnit[\"3\"].Bitmap);\n                                g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                                offsetX += charBitmap.Width + unitSpacing;\n                            }\n                            break;\n\n                        case \"亿\":\n                        case \"T\":\n                            if (this.damageSkin.BigUnit.ContainsKey(\"4\"))\n                            {\n                                charBitmap = isCritical ?\n                                    (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"4\"].Bitmap :\n                                    this.damageSkin.BigCriticalUnit[\"4\"].Bitmap) :\n                                    (useMiniSize ? this.damageSkin.MiniUnit[\"4\"].Bitmap :\n                                    this.damageSkin.BigUnit[\"4\"].Bitmap);\n                                g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                                offsetX += charBitmap.Width + unitSpacing;\n                            }\n                            break;\n\n\n                        case \"兆\":\n                        case \"Q\":\n                            if (this.damageSkin.BigUnit.ContainsKey(\"5\"))\n                            {\n                                charBitmap = isCritical ?\n                                    (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"5\"].Bitmap :\n                                    this.damageSkin.BigCriticalUnit[\"5\"].Bitmap) :\n                                    (useMiniSize ? this.damageSkin.MiniUnit[\"5\"].Bitmap :\n                                    this.damageSkin.BigUnit[\"5\"].Bitmap);\n                                g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                                offsetX += charBitmap.Width + unitSpacing;\n                            }\n                            break;\n\n                        case \"京\":\n                            if (this.damageSkin.BigUnit.ContainsKey(\"6\"))\n                            {\n                                charBitmap = isCritical ?\n                                    (useMiniSize ? this.damageSkin.MiniCriticalUnit[\"6\"].Bitmap :\n                                    this.damageSkin.BigCriticalUnit[\"6\"].Bitmap) :\n                                    (useMiniSize ? this.damageSkin.MiniUnit[\"6\"].Bitmap :\n                                    this.damageSkin.BigUnit[\"6\"].Bitmap);\n                                g.DrawImage(charBitmap, offsetX, maxHeight - charBitmap.Height);\n                                offsetX += charBitmap.Width + unitSpacing;\n                            }\n                            break;\n                    }\n                }\n            }\n            return finalBitmap;\n        }\n\n        public Bitmap GetUnit()\n        {\n            Bitmap unitBitmap = null;\n\n            int width = 0;\n            int height = 0;\n\n            if (damageSkin.BigUnit.Count > 0)\n            {\n                if (UseMiniSize)\n                {\n                    foreach (var unit in damageSkin.MiniUnit.Values)\n                    {\n                        width += unit.Bitmap.Width;\n                        height = Math.Max(height, unit.Bitmap.Height);\n                        width += this.damageSkin.MiniUnitSpacing;\n                    }\n                    foreach (var unit in damageSkin.MiniCriticalUnit.Values)\n                    {\n                        width += unit.Bitmap.Width;\n                        height = Math.Max(height, unit.Bitmap.Height);\n                        width += this.damageSkin.MiniCriticalUnitSpacing;\n                    }\n                    unitBitmap = new Bitmap(width, height);\n                    using (Graphics g = Graphics.FromImage(unitBitmap))\n                    {\n                        g.Clear(Color.Transparent);\n                        int offsetX = 0;\n                        foreach (var unit in damageSkin.MiniUnit.Values)\n                        {\n                            g.DrawImage(unit.Bitmap, offsetX, height - unit.Bitmap.Height);\n                            offsetX += unit.Bitmap.Width;\n                            offsetX += this.damageSkin.MiniUnitSpacing;\n                        }\n                        foreach (var unit in damageSkin.MiniCriticalUnit.Values)\n                        {\n                            g.DrawImage(unit.Bitmap, offsetX, height - unit.Bitmap.Height);\n                            offsetX += unit.Bitmap.Width;\n                            offsetX += this.damageSkin.MiniCriticalUnitSpacing;\n                        }\n                    }\n                }\n                else\n                {\n                    foreach (var unit in damageSkin.BigUnit.Values)\n                    {\n                        width += unit.Bitmap.Width;\n                        height = Math.Max(height, unit.Bitmap.Height);\n                        width += this.damageSkin.BigUnitSpacing;\n                    }\n                    foreach (var unit in damageSkin.BigCriticalUnit.Values)\n                    {\n                        width += unit.Bitmap.Width;\n                        height = Math.Max(height, unit.Bitmap.Height);\n                        width += this.damageSkin.BigCriticalUnitSpacing;\n                    }\n                    unitBitmap = new Bitmap(width, height);\n                    using (Graphics g = Graphics.FromImage(unitBitmap))\n                    {\n                        g.Clear(Color.Transparent);\n                        int offsetX = 0;\n                        foreach (var unit in damageSkin.BigUnit.Values)\n                        {\n                            g.DrawImage(unit.Bitmap, offsetX, height - unit.Bitmap.Height);\n                            offsetX += unit.Bitmap.Width;\n                            offsetX += this.damageSkin.BigUnitSpacing;\n                        }\n                        foreach (var unit in damageSkin.BigCriticalUnit.Values)\n                        {\n                            g.DrawImage(unit.Bitmap, offsetX, height - unit.Bitmap.Height);\n                            offsetX += unit.Bitmap.Width;\n                            offsetX += this.damageSkin.BigCriticalUnitSpacing;\n                        }\n                    }\n                }\n            }\n            return unitBitmap;\n        }\n\n        public Bitmap GetExtraEffect()\n        {\n\n            Bitmap[] originalBitmaps = new Bitmap[5]\n            {\n                this.damageSkin.MiniDigit.ContainsKey(\"Miss\") ? this.damageSkin.MiniDigit?[\"Miss\"].Bitmap : null,\n                this.damageSkin.MiniDigit.ContainsKey(\"guard\") ? this.damageSkin.MiniDigit?[\"guard\"].Bitmap : null,\n                this.damageSkin.MiniDigit.ContainsKey(\"resist\") ? this.damageSkin.MiniDigit?[\"resist\"].Bitmap : null,\n                this.damageSkin.MiniDigit.ContainsKey(\"shot\") ? this.damageSkin.MiniDigit?[\"shot\"].Bitmap : null,\n                this.damageSkin.MiniDigit.ContainsKey(\"counter\") ? this.damageSkin.MiniDigit?[\"counter\"].Bitmap : null\n            };\n\n\n            int width = 0;\n            int height = 0;\n\n            foreach (var bo in originalBitmaps)\n            {\n                if (bo == null) continue;\n                width += bo.Width;\n                height = Math.Max(height, bo.Height);\n            }\n\n            Bitmap bitmap = new Bitmap(width, height);\n\n            using (Graphics g = Graphics.FromImage(bitmap))\n            {\n                g.Clear(Color.Transparent);\n                int offsetX = 0;\n                for (int j = 0; j < originalBitmaps.Count(); j++)\n                {\n                    if (originalBitmaps[j] == null) continue;\n                    g.DrawImage(originalBitmaps[j], offsetX, height - originalBitmaps[j].Height);\n                    offsetX += originalBitmaps[j].Width;\n                }\n            }\n\n            return bitmap;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/FamiliarTooltipRender.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Drawing;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Common;\nusing WzComparerR2.PluginBase;\nusing static WzComparerR2.CharaSimControl.RenderHelper;\nusing Resource = CharaSimResource.Resource;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class FamiliarTooltipRender : TooltipRender\n    {\n        public FamiliarTooltipRender()\n        {\n        }\n\n        private Familiar familiar;\n\n        public Familiar Familiar\n        {\n            get { return familiar; }\n            set { familiar = value; }\n        }\n\n        public override object TargetItem\n        {\n            get { return this.familiar; }\n            set { this.familiar = value as Familiar; }\n        }\n\n        public int? ItemID { get; set; }\n        public int FamiliarTier { get; set; }\n        public bool AllowOutOfBounds { get; set; }\n        public bool UseAssembleUI { get; set; }\n\n        public override Bitmap Render()\n        {\n            if (this.familiar == null)\n            {\n                return null;\n            }\n            return UseAssembleUI ? GeneratePostAssembleFamiliarCard() : GeneratePreAssembleFamiliarCard();\n        }\n\n        private Bitmap GeneratePreAssembleFamiliarCard()\n        {\n            Bitmap baseTooltip = Resource.UIFamiliar_img_familiarCard_backgrnd;\n\n            // Get Mob image and name\n            Mob mob = Mob.CreateFromNode(PluginManager.FindWz($@\"Mob\\{familiar.MobID.ToString().PadLeft(7, '0')}.img\"), PluginManager.FindWz);\n            Point alignOrigin = new Point(161, 200);\n            Point mobOrigin = new Point(0, 0);\n            int mobXoffset = 0;\n            int mobYoffset = 0;\n            int tDelta = 0;\n            int bDelta = 0;\n            int lDelta = 0;\n            int rDelta = 0;\n            if (familiar.FamiliarCover.Bitmap != null)\n            {\n                mobOrigin = familiar.FamiliarCover.Origin;\n            }\n            else\n            {\n                mobOrigin = mob.Default.Origin;\n            }\n            Bitmap mobImg = Crop(familiar.FamiliarCover.Bitmap ?? mob.Default.Bitmap, alignOrigin, mobOrigin, out mobXoffset, out mobYoffset, out tDelta, out bDelta, out lDelta, out rDelta);\n\n            Bitmap tooltip = new Bitmap(baseTooltip.Width + lDelta + rDelta, baseTooltip.Height + tDelta + bDelta);\n\n            using (Graphics g = Graphics.FromImage(tooltip))\n            {\n                g.Clear(Color.Transparent);\n                g.DrawImage(baseTooltip, lDelta, tDelta, new Rectangle(0, 0, baseTooltip.Width, baseTooltip.Height), GraphicsUnit.Pixel);\n\n                // Draw Familiar Card basic background\n                g.DrawImage(Resource.UIFamiliar_img_familiarCard_base, 45 + lDelta, 37 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_familiarCard_base.Width, Resource.UIFamiliar_img_familiarCard_base.Height), GraphicsUnit.Pixel);\n                g.DrawImage(Resource.UIFamiliar_img_familiarCard_name, 31 + lDelta, 222 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_familiarCard_name.Width, Resource.UIFamiliar_img_familiarCard_name.Height), GraphicsUnit.Pixel);\n\n                g.DrawImage(mobImg, mobXoffset + lDelta, mobYoffset + tDelta, new Rectangle(0, 0, mobImg.Width, mobImg.Height), GraphicsUnit.Pixel);\n\n                if (this.FamiliarTier == 4)\n                {\n                    // draw Legendary bezel\n                    g.DrawImage(Resource.UIFamiliar_img_familiarCard_legendary, 35 + lDelta, 24 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_familiarCard_legendary.Width, Resource.UIFamiliar_img_familiarCard_legendary.Height), GraphicsUnit.Pixel);\n                }\n\n                g.DrawImage(Resource.UIFamiliar_img_jewel_backgrnd, 25 + lDelta, 21 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_backgrnd.Width, Resource.UIFamiliar_img_jewel_backgrnd.Height), GraphicsUnit.Pixel);\n\n                switch (this.FamiliarTier)\n                {\n                    default:\n                    case 0:\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_normal_5, 30 + lDelta, 27 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_normal_5.Width, Resource.UIFamiliar_img_jewel_normal_5.Height), GraphicsUnit.Pixel);\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_normal_0, 38 + lDelta, 25 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_normal_0.Width, Resource.UIFamiliar_img_jewel_normal_0.Height), GraphicsUnit.Pixel);\n                        break;\n                    case 1:\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_rare_5, 30 + lDelta, 27 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_rare_5.Width, Resource.UIFamiliar_img_jewel_rare_5.Height), GraphicsUnit.Pixel);\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_rare_0, 38 + lDelta, 25 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_rare_0.Width, Resource.UIFamiliar_img_jewel_rare_0.Height), GraphicsUnit.Pixel);\n                        break;\n                    case 2:\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_epic_5, 30 + lDelta, 27 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_epic_5.Width, Resource.UIFamiliar_img_jewel_epic_5.Height), GraphicsUnit.Pixel);\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_epic_0, 38 + lDelta, 25 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_epic_0.Width, Resource.UIFamiliar_img_jewel_epic_0.Height), GraphicsUnit.Pixel);\n                        break;\n                    case 3:\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_unique_5, 30 + lDelta, 27 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_unique_5.Width, Resource.UIFamiliar_img_jewel_unique_5.Height), GraphicsUnit.Pixel);\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_unique_0, 38 + lDelta, 25 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_unique_0.Width, Resource.UIFamiliar_img_jewel_unique_0.Height), GraphicsUnit.Pixel);\n                        break;\n                    case 4:\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_legendary_5, 30 + lDelta, 27 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_legendary_5.Width, Resource.UIFamiliar_img_jewel_legendary_5.Height), GraphicsUnit.Pixel);\n                        g.DrawImage(Resource.UIFamiliar_img_jewel_legendary_0, 38 + lDelta, 25 + tDelta, new Rectangle(0, 0, Resource.UIFamiliar_img_jewel_legendary_0.Width, Resource.UIFamiliar_img_jewel_legendary_0.Height), GraphicsUnit.Pixel);\n                        break;\n                }\n\n                // Pre-Drawing\n                List<TextBlock> titleBlocks = new List<TextBlock>();\n                string mobName = GetMobName(mob.ID);\n                var block = PrepareText(g, mobName ?? \"(null)\", GearGraphics.EpicGearDetailFont, Brushes.White, 0, 0);\n                titleBlocks.Add(block);\n\n                Rectangle titleRect = Measure(titleBlocks);\n\n                int titleXoffset = Resource.UIFamiliar_img_familiarCard_name.Width >= Resource.UIFamiliar_img_familiarCard_name.Width ? (Resource.UIFamiliar_img_familiarCard_name.Width - titleRect.Width) / 2 : 0;\n                int titleYoffset = (Resource.UIFamiliar_img_familiarCard_name.Height - titleRect.Height) / 2;\n\n                foreach (var item in titleBlocks)\n                {\n                    DrawText(g, item, new Point(31 + lDelta + titleXoffset, 222 + tDelta + titleYoffset));\n                }\n\n                // Draw Skill\n                string skillName = GetFamiliarSkillName(this.familiar.SkillID);\n                if (!string.IsNullOrEmpty(skillName))\n                {\n                    List<TextBlock> skillBlocks = new List<TextBlock>();\n                    block = PrepareText(g, skillName, GearGraphics.EpicGearDetailFont, Brushes.White, 0, 0);\n                    skillBlocks.Add(block);\n\n                    Rectangle skillRect = Measure(skillBlocks);\n\n                    int skillXoffset = Resource.UIFamiliar_img_familiarCard_name.Width >= Resource.UIFamiliar_img_familiarCard_name.Width ? (Resource.UIFamiliar_img_familiarCard_name.Width - skillRect.Width) / 2 : 0;\n                    int skillYoffset = Resource.UIFamiliar_img_familiarCard_name.Height + 1;\n\n                    foreach (var item in skillBlocks)\n                    {\n                        DrawText(g, item, new Point(31 + lDelta + skillXoffset, 222 + tDelta + skillYoffset));\n                    }\n\n                }\n\n\n                // Layout\n                if (this.ShowObjectID)\n                {\n                    GearGraphics.DrawGearDetailNumber(g, 24 + lDelta, 24 + tDelta, this.ItemID != null ? $\"{((int)this.ItemID).ToString(\"d8\")}\" : $\"{this.familiar.FamiliarID.ToString()}\", true);\n                }\n            }\n            return tooltip;\n        }\n\n        private Bitmap GeneratePostAssembleFamiliarCard()\n        {\n            // To be implemented\n            return GeneratePreAssembleFamiliarCard();\n        }\n\n        private string GetMobName(int mobID)\n        {\n            StringResult sr;\n            if (this.StringLinker == null || !this.StringLinker.StringMob.TryGetValue(mobID, out sr))\n            {\n                return null;\n            }\n            else\n            {\n                return sr.Name;\n            }\n        }\n\n        private string GetFamiliarSkillName(int skillID)\n        {\n            StringResult sr;\n            if (this.StringLinker == null || !this.StringLinker.StringFamiliarSkill.TryGetValue(skillID, out sr))\n            {\n                return null;\n            }\n            else\n            {\n                return sr.Name;\n            }\n        }\n\n        private Bitmap Crop(Bitmap sourceBmp, Point alignOrigin, Point mobOrigin, out int xOffset, out int yOffset, out int tDelta, out int bDelta, out int lDelta, out int rDelta)\n        {\n            tDelta = 0;\n            bDelta = 0;\n            lDelta = 0;\n            rDelta = 0;\n            xOffset = 0;\n            yOffset = 0;\n\n            if (sourceBmp == null)\n            {\n                return null;\n            }\n\n            xOffset = alignOrigin.X - mobOrigin.X;\n            yOffset = alignOrigin.Y - mobOrigin.Y;\n\n            if (this.AllowOutOfBounds)\n            {\n                lDelta = mobOrigin.X > alignOrigin.X ? mobOrigin.X - alignOrigin.X : 0;\n                rDelta = sourceBmp.Width - mobOrigin.X > 154 ? sourceBmp.Width - mobOrigin.X - 154 : 0;\n                tDelta = mobOrigin.Y > alignOrigin.Y ? mobOrigin.Y - alignOrigin.Y : 0;\n                bDelta = sourceBmp.Height - mobOrigin.Y > 228 ? sourceBmp.Height - mobOrigin.Y - 228 : 0;\n                return sourceBmp;\n            }\n\n            int rectangXoffset = 0;\n            int rectangYoffset = 0;\n            int rectangWidth = 0;\n            int rectangHeight = 0;\n\n            if (mobOrigin.X > 108) // Define Left Border\n            {\n                rectangXoffset = mobOrigin.X - 108;\n                rectangWidth += 108;\n            }\n            else\n            {\n                rectangWidth += mobOrigin.X;\n            }\n\n            if (sourceBmp.Width - mobOrigin.X > 104) // Define Right Border\n            {\n                rectangWidth += 104;\n            }\n            else\n            {\n                rectangWidth += sourceBmp.Width - mobOrigin.X;\n            }\n\n            if (mobOrigin.Y > 154) // Define Top Border\n            {\n                rectangYoffset = mobOrigin.Y - 154;\n                rectangHeight += 154;\n            }\n            else\n            {\n                rectangHeight += mobOrigin.Y;\n            }\n\n            if (sourceBmp.Height - mobOrigin.Y > 18)\n            {\n                rectangHeight += 18;\n            }\n            else\n            {\n                rectangHeight += sourceBmp.Height - mobOrigin.Y;\n            }\n\n            xOffset = rectangXoffset == 0 ? xOffset : 53;\n            yOffset = rectangYoffset == 0 ? yOffset : 46;\n\n            return sourceBmp.Clone(new Rectangle(rectangXoffset, rectangYoffset, rectangWidth, rectangHeight), sourceBmp.PixelFormat);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/GearGraphics.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.Drawing.Drawing2D;\nusing System.Runtime.InteropServices;\nusing CharaSimResource;\nusing WzComparerR2.CharaSim;\nusing TR = System.Windows.Forms.TextRenderer;\nusing TextFormatFlags = System.Windows.Forms.TextFormatFlags;\nusing WzComparerR2.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    /// <summary>\n    /// 提供一系列的静态Graphics工具，用来绘制物品tooltip。\n    /// </summary>\n    public static class GearGraphics\n    {\n        static GearGraphics()\n        {\n            TBrushes = new Dictionary<string, TextureBrush>();\n            TBrushes[\"n\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_n, WrapMode.Tile);\n            TBrushes[\"ne\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_ne, WrapMode.Clamp);\n            TBrushes[\"e\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_e, WrapMode.Tile);\n            TBrushes[\"se\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_se, WrapMode.Clamp);\n            TBrushes[\"s\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_s, WrapMode.Tile);\n            TBrushes[\"sw\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_sw, WrapMode.Clamp);\n            TBrushes[\"w\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_w, WrapMode.Tile);\n            TBrushes[\"nw\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_nw, WrapMode.Clamp);\n            TBrushes[\"c\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame2_c, WrapMode.Tile);\n            SetFontFamily(\"宋体\");\n        }\n\n        public static readonly Dictionary<string, TextureBrush> TBrushes;\n        public static readonly Font ItemNameFont = new Font(\"宋体\", 14f, FontStyle.Bold, GraphicsUnit.Pixel);\n        public static readonly Font ItemDetailFont = new Font(\"宋体\", 12f, GraphicsUnit.Pixel);\n        public static readonly Font EpicGearDetailFont = new Font(\"宋体\", 12f, FontStyle.Bold, GraphicsUnit.Pixel);\n        public static readonly Font TahomaFont = new Font(\"Tahoma\", 12f, GraphicsUnit.Pixel);\n        public static readonly Font SetItemPropFont = new Font(\"宋体\", 12f, GraphicsUnit.Pixel);\n        public static readonly Font ItemReqLevelFont = new Font(\"宋体\", 11f, GraphicsUnit.Pixel);\n\n        public static Font ItemNameFont2 { get; private set; }\n        public static Font ItemDetailFont2 { get; private set; }\n\n        public static void SetFontFamily(string fontName)\n        {\n            if (ItemNameFont2 != null)\n            {\n                ItemNameFont2.Dispose();\n                ItemNameFont2 = null;\n            }\n            ItemNameFont2 = new Font(fontName, 14f, FontStyle.Bold, GraphicsUnit.Pixel);\n\n            if (ItemDetailFont2 != null)\n            {\n                ItemDetailFont2.Dispose();\n                ItemDetailFont2 = null;\n            }\n            ItemDetailFont2 = new Font(fontName, 12f, GraphicsUnit.Pixel);\n        }\n\n        public static readonly Color GearBackColor = Color.FromArgb(204, 0, 51, 85);\n        public static readonly Color EpicGearBackColor = Color.FromArgb(170, 68, 0, 0);\n        public static readonly Color GearIconBackColor = Color.FromArgb(238, 187, 204, 221);\n        public static readonly Color EpicGearIconBackColor = Color.FromArgb(221, 204, 187, 187);\n\n        public static readonly Brush GearBackBrush = new SolidBrush(GearBackColor);\n        public static readonly Brush EpicGearBackBrush = new SolidBrush(EpicGearBackColor);\n        public static readonly Pen GearBackPen = new Pen(GearBackColor);\n        public static readonly Pen EpicGearBackPen = new Pen(EpicGearBackColor);\n        public static readonly Brush GearIconBackBrush = new SolidBrush(GearIconBackColor);\n        public static readonly Brush GearIconBackBrush2 = new SolidBrush(Color.FromArgb(187, 238, 238, 238));\n        public static readonly Brush EpicGearIconBackBrush = new SolidBrush(EpicGearIconBackColor);\n        public static readonly Brush StatDetailGrayBrush = new SolidBrush(Color.FromArgb(85, 85, 85));\n\n        public static readonly Color OrangeBrushColor = Color.FromArgb(255, 153, 0);\n        /// <summary>\n        /// 表示物品说明中带有#c标识的橙色字体画刷。\n        /// </summary>\n        public static readonly Brush OrangeBrush = new SolidBrush(OrangeBrushColor);\n        /// <summary>\n        /// 表示物品附加属性中橙色字体画刷。\n        /// </summary>\n        public static readonly Brush OrangeBrush2 = new SolidBrush(Color.FromArgb(255, 170, 0));\n        public static readonly Color OrangeBrush3Color = Color.FromArgb(255, 204, 0);\n        /// <summary>\n        /// 表示装备职业额外说明中使用的橙黄色画刷。\n        /// </summary>\n        public static readonly Brush OrangeBrush3 = new SolidBrush(OrangeBrush3Color);\n        /// <summary>\n        /// 表示装备属性额外说明中使用的绿色画刷。\n        /// </summary>\n        public static readonly Brush GreenBrush2 = new SolidBrush(Color.FromArgb(204, 255, 0));\n        public static readonly Color GrayColor2 = Color.FromArgb(153, 153, 153);\n        /// <summary>\n        /// 表示装备属性额外说明中使用的卷轴强化数值画刷。\n        /// </summary>\n        public static readonly Color ScrollEnhancementColor = Color.FromArgb(175, 173, 255);\n        public static readonly Brush ScrollEnhancementBrush = new SolidBrush(ScrollEnhancementColor);\n        /// <summary>\n        /// 表示用于绘制“攻击力提升”文字的灰色画刷。\n        /// </summary>\n        public static readonly Brush GrayBrush2 = new SolidBrush(GrayColor2);\n        /// <summary>\n        /// 表示套装名字的绿色画刷。\n        /// </summary>\n        public static readonly Brush SetItemNameBrush = new SolidBrush(Color.FromArgb(119, 255, 0));\n        /// <summary>\n        /// 表示套装属性不可用的灰色画刷。\n        /// </summary>\n        public static readonly Brush SetItemGrayBrush = new SolidBrush(Color.FromArgb(119, 136, 153));\n        /// <summary>\n        /// 表示效果不可用的红色画刷。\n        /// </summary>\n        public static readonly Brush BlockRedBrush = new SolidBrush(Color.FromArgb(255, 0, 102));\n        /// <summary>\n        /// 表示装备tooltip中金锤子描述文字的颜色画刷。\n        /// </summary>\n        public static readonly Brush GoldHammerBrush = new SolidBrush(Color.FromArgb(255, 238, 204));\n        /// <summary>\n        /// 表示灰色品质的装备名字画刷，额外属性小于0。\n        /// </summary>\n        public static readonly Brush GearNameBrushA = new SolidBrush(Color.FromArgb(187, 187, 187));\n        /// <summary>\n        /// 表示白色品质的装备名字画刷，额外属性为0~5。\n        /// </summary>\n        public static readonly Brush GearNameBrushB = new SolidBrush(Color.FromArgb(255, 255, 255));\n        /// <summary>\n        /// 表示橙色品质的装备名字画刷，额外属性为0~5，并且已经附加卷轴。\n        /// </summary>\n        public static readonly Brush GearNameBrushC = new SolidBrush(Color.FromArgb(255, 136, 17));\n        private static Color gearBlueColor = Color.FromArgb(85, 170, 255);\n        /// <summary>\n        /// 表示蓝色品质的装备名字画刷，额外属性为6~22。\n        /// </summary>\n        public static readonly Brush GearNameBrushD = new SolidBrush(gearBlueColor);\n        private static Color gearPurpleColor = Color.FromArgb(204, 102, 255);\n        /// <summary>\n        /// 表示紫色品质的装备名字画刷，额外属性为23~39。\n        /// </summary>\n        public static readonly Brush GearNameBrushE = new SolidBrush(gearPurpleColor);\n        private static Color gearGoldColor = Color.FromArgb(255, 255, 17);\n        /// <summary>\n        /// 表示金色品质的装备名字画刷，额外属性为40~54。\n        /// </summary>\n        public static readonly Brush GearNameBrushF = new SolidBrush(gearGoldColor);\n        private static Color gearGreenColor = Color.FromArgb(51, 255, 0);\n        /// <summary>\n        /// 表示绿色品质的装备名字画刷，额外属性为55~69。\n        /// </summary>\n        public static readonly Brush GearNameBrushG = new SolidBrush(gearGreenColor);\n        /// <summary>\n        /// 表示红色品质的装备名字画刷，额外属性为70以上。\n        /// </summary>\n        public static readonly Brush GearNameBrushH = new SolidBrush(Color.FromArgb(255, 0, 119));\n\n        public static readonly Color gearCyanColor = Color.FromArgb(102, 255, 255);\n        /// <summary>\n        /// 表示装备属性变化的青色画刷。\n        /// </summary>\n        public static readonly Brush GearPropChangeBrush = new SolidBrush(gearCyanColor);\n\n        public static readonly Color SkillSummaryOrangeTextColor = Color.FromArgb(255, 204, 0);\n        public static readonly Brush SkillSummaryOrangeTextBrush = new SolidBrush(SkillSummaryOrangeTextColor);\n\n        public static Brush GetGearNameBrush(int diff, bool up)\n        {\n            if (diff < 0)\n                return GearNameBrushA;\n            if (diff < 6)\n            {\n                if (!up)\n                    return GearNameBrushB;\n                else\n                    return GearNameBrushC;\n            }\n            if (diff < 23)\n                return GearNameBrushD;\n            if (diff < 40)\n                return GearNameBrushE;\n            if (diff < 55)\n                return GearNameBrushF;\n            if (diff < 70)\n                return GearNameBrushG;\n            return GearNameBrushH;\n        }\n\n        public static readonly Pen GearItemBorderPenC = new Pen(Color.FromArgb(255, 0, 102));\n        public static readonly Pen GearItemBorderPenB = new Pen(gearBlueColor);\n        public static readonly Pen GearItemBorderPenA = new Pen(gearPurpleColor);\n        public static readonly Pen GearItemBorderPenS = new Pen(gearGoldColor);\n        public static readonly Pen GearItemBorderPenSS = new Pen(gearGreenColor);\n        public static Pen GetGearItemBorderPen(GearGrade grade)\n        {\n            switch (grade)\n            {\n                case GearGrade.B:\n                    return GearItemBorderPenB;\n                case GearGrade.A:\n                    return GearItemBorderPenA;\n                case GearGrade.S:\n                    return GearItemBorderPenS;\n                case GearGrade.SS:\n                    return GearItemBorderPenSS;\n                default:\n                    return null;\n            }\n        }\n\n        public static Brush GetPotentialTextBrush(GearGrade grade)\n        {\n            switch (grade)\n            {\n                default:\n                case GearGrade.B: return GearPropChangeBrush;\n                case GearGrade.A: return GearNameBrushE;\n                case GearGrade.S: return GearNameBrushF;\n                case GearGrade.SS: return GreenBrush2;\n            }\n        }\n\n        /// <summary>\n        /// 在指定区域绘制包含宏代码的字符串。\n        /// </summary>\n        /// <param Name=\"g\">绘图所关联的graphics。</param>\n        /// <param Name=\"s\">要绘制的string。</param>\n        /// <param Name=\"font\">要绘制string的字体。</param>\n        /// <param Name=\"x\">起始的x坐标。</param>\n        /// <param Name=\"X1\">每行终止的x坐标。</param>\n        /// <param Name=\"y\">起始行的y坐标。</param>\n        public static void DrawString(Graphics g, string s, Font font, int x, int x1, ref int y, int height, TextAlignment alignment = TextAlignment.Left)\n        {\n            DrawString(g, s, font, null, x, x1, ref y, height, alignment);\n        }\n\n        public static void DrawString(Graphics g, string s, Font font, IDictionary<string, Color> fontColorTable, int x, int x1, ref int y, int height, TextAlignment alignment = TextAlignment.Left)\n        {\n            if (s == null)\n                return;\n\n            using (var r = new FormattedTextRenderer())\n            {\n                r.WordWrapEnabled = false;\n                r.UseGDIRenderer = false;\n                r.FontColorTable = fontColorTable;\n                r.DrawString(g, s, font, x, x1, ref y, height, alignment);\n            }\n        }\n\n        public static void DrawPlainText(Graphics g, string s, Font font, Color color, int x, int x1, ref int y, int height, TextAlignment alignment = TextAlignment.Left)\n        {\n            if (s == null)\n                return;\n\n            using (var r = new FormattedTextRenderer())\n            {\n                r.WordWrapEnabled = false;\n                r.UseGDIRenderer = false;\n                r.DrawPlainText(g, s, font, color, x, x1, ref y, height, alignment);\n            }\n        }\n\n        public static Bitmap EnlargeBitmap(Bitmap bitmap)\n        {\n            BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);\n            IntPtr p = data.Scan0;\n            byte[] origin = new byte[bitmap.Width * bitmap.Height * 4];\n            Marshal.Copy(p, origin, 0, origin.Length);\n            bitmap.UnlockBits(data);\n            byte[] newByte = new byte[origin.Length * 4];\n            byte[] buffer = new byte[4];\n            for (int i = 0; i < bitmap.Height; i++)\n            {\n                for (int j = 0; j < bitmap.Width; j++)\n                {\n                    Array.Copy(origin, getOffset(j, i, bitmap.Width), buffer, 0, 4);\n                    Array.Copy(buffer, 0, newByte, getOffset(2 * j, 2 * i, bitmap.Width * 2), 4);\n                    Array.Copy(buffer, 0, newByte, getOffset(2 * j + 1, 2 * i, bitmap.Width * 2), 4);\n                }\n                Array.Copy(newByte, getOffset(0, 2 * i, bitmap.Width * 2), newByte, getOffset(0, 2 * i + 1, bitmap.Width * 2), bitmap.Width * 8);\n            }\n            Bitmap newBitmap = new Bitmap(bitmap.Width * 2, bitmap.Height * 2);\n            data = newBitmap.LockBits(new Rectangle(0, 0, newBitmap.Width, newBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);\n            Marshal.Copy(newByte, 0, data.Scan0, newByte.Length);\n            newBitmap.UnlockBits(data);\n            return newBitmap;\n        }\n\n        private static int getOffset(int x, int y, int width, int unit = 4)\n        {\n            return (y * width + x) * unit;\n        }\n\n        public static Point[] GetBorderPath(int dx, int width, int height)\n        {\n            List<Point> pointList = new List<Point>(13);\n            pointList.Add(new Point(dx + 1, 0));\n            pointList.Add(new Point(dx + 1, 1));\n            pointList.Add(new Point(dx + 0, 1));\n            pointList.Add(new Point(dx + 0, height - 2));\n            pointList.Add(new Point(dx + 1, height - 2));\n            pointList.Add(new Point(dx + 1, height - 1));\n            pointList.Add(new Point(dx + width - 2, height - 1));\n            pointList.Add(new Point(dx + width - 2, height - 2));\n            pointList.Add(new Point(dx + width - 1, height - 2));\n            pointList.Add(new Point(dx + width - 1, 1));\n            pointList.Add(new Point(dx + width - 2, 1));\n            pointList.Add(new Point(dx + width - 2, 0));\n            pointList.Add(new Point(dx + 1, 0));\n            return pointList.ToArray();\n        }\n\n        public static Point[] GetIconBorderPath(int x, int y)\n        {\n            Point[] pointList = new Point[5];\n            pointList[0] = new Point(x + 32, y + 31);\n            pointList[1] = new Point(x + 32, y);\n            pointList[2] = new Point(x, y);\n            pointList[3] = new Point(x, y + 32);\n            pointList[4] = new Point(x + 31, y + 32);\n            return pointList;\n        }\n\n        public static void DrawGearDetailNumber(Graphics g, int x, int y, string num, bool can)\n        {\n            Bitmap bitmap;\n            for (int i = 0; i < num.Length; i++)\n            {\n                switch (num[i])\n                {\n                    case '0':\n                    case '1':\n                    case '2':\n                    case '3':\n                    case '4':\n                    case '5':\n                    case '6':\n                    case '7':\n                    case '8':\n                    case '9':\n                        string resourceName = (can ? \"ToolTip_Equip_Can_\" : \"ToolTip_Equip_Cannot_\") + num[i];\n                        bitmap = (Bitmap)Resource.ResourceManager.GetObject(resourceName);\n                        g.DrawImage(bitmap, x, y);\n                        x += bitmap.Width + 1;\n                        break;\n                    case '-':\n                        bitmap = can ? Resource.ToolTip_Equip_Can_none : Resource.ToolTip_Equip_Cannot_none;\n                        g.DrawImage(bitmap, x, y + 3);\n                        x += bitmap.Width + 1;\n                        break;\n                    case '%':\n                        bitmap = can ? Resource.ToolTip_Equip_Can_percent : Resource.ToolTip_Equip_Cannot_percent;\n                        g.DrawImage(bitmap, x + 1, y);\n                        x += bitmap.Width + 2;\n                        break;\n                }\n            }\n        }\n\n        public static void DrawGearGrowthNumber(Graphics g, int x, int y, string num, bool can)\n        {\n            Bitmap bitmap;\n            for (int i = 0; i < num.Length; i++)\n            {\n                switch (num[i])\n                {\n                    case '0':\n                    case '1':\n                    case '2':\n                    case '3':\n                    case '4':\n                    case '5':\n                    case '6':\n                    case '7':\n                    case '8':\n                    case '9':\n                        string resourceName = (can ? \"ToolTip_Equip_GrowthEnabled_\" : \"ToolTip_Equip_Cannot_\") + num[i];\n                        bitmap = (Bitmap)Resource.ResourceManager.GetObject(resourceName);\n                        g.DrawImage(bitmap, x, y);\n                        x += bitmap.Width + 1;\n                        break;\n                    case '-':\n                        bitmap = can ? Resource.ToolTip_Equip_GrowthDisabled_none : Resource.ToolTip_Equip_GrowthDisabled_none;\n                        g.DrawImage(bitmap, x, y);\n                        x += bitmap.Width + 1;\n                        break;\n                    case '%':\n                        bitmap = can ? Resource.ToolTip_Equip_GrowthEnabled_percent : Resource.ToolTip_Equip_GrowthEnabled_percent;\n                        g.DrawImage(bitmap, x + 7, y - 4);\n                        x += bitmap.Width + 1;\n                        break;\n                    case 'm':\n                        bitmap = can ? Resource.ToolTip_Equip_GrowthEnabled_max : Resource.ToolTip_Equip_GrowthEnabled_max;\n                        g.DrawImage(bitmap, x, y);\n                        x += bitmap.Width + 1;\n                        break;\n                }\n            }\n        }\n\n        public static void DrawNewTooltipBack(Graphics g, int x, int y, int width, int height)\n        {\n            Dictionary<string, TextureBrush> res = TBrushes;\n            //测算准线\n            int[] guideX = new int[4] { 0, res[\"w\"].Image.Width, width - res[\"e\"].Image.Width, width };\n            int[] guideY = new int[4] { 0, res[\"n\"].Image.Height, height - res[\"s\"].Image.Height, height };\n            for (int i = 0; i < guideX.Length; i++) guideX[i] += x;\n            for (int i = 0; i < guideY.Length; i++) guideY[i] += y;\n            //绘制四角\n            FillRect(g, res[\"nw\"], guideX, guideY, 0, 0, 1, 1);\n            FillRect(g, res[\"ne\"], guideX, guideY, 2, 0, 3, 1);\n            FillRect(g, res[\"sw\"], guideX, guideY, 0, 2, 1, 3);\n            FillRect(g, res[\"se\"], guideX, guideY, 2, 2, 3, 3);\n            //填充上下区域\n            if (guideX[2] > guideX[1])\n            {\n                FillRect(g, res[\"n\"], guideX, guideY, 1, 0, 2, 1);\n                FillRect(g, res[\"s\"], guideX, guideY, 1, 2, 2, 3);\n            }\n            //填充左右区域\n            if (guideY[2] > guideY[1])\n            {\n                FillRect(g, res[\"w\"], guideX, guideY, 0, 1, 1, 2);\n                FillRect(g, res[\"e\"], guideX, guideY, 2, 1, 3, 2);\n            }\n            //填充中心\n            if (guideX[2] > guideX[1] && guideY[2] > guideY[1])\n            {\n                FillRect(g, res[\"c\"], guideX, guideY, 1, 1, 2, 2);\n            }\n        }\n\n        private static void FillRect(Graphics g, TextureBrush brush, int[] guideX, int[] guideY, int x0, int y0, int x1, int y1)\n        {\n            brush.ResetTransform();\n            brush.TranslateTransform(guideX[x0], guideY[y0]);\n            g.FillRectangle(brush, guideX[x0], guideY[y0], guideX[x1] - guideX[x0], guideY[y1] - guideY[y0]);\n        }\n\n        public static void DrawNameTag(Graphics g, Wz_Node resNode, string tagName, int picW, ref int picH)\n        {\n            if (g == null || resNode == null)\n                return;\n\n            //加载资源和文本颜色\n            var wce = new[] { \"w\", \"c\", \"e\" }.Select(n =>\n            {\n                var node = resNode.FindNodeByPath(n);\n                if (node == null)\n                {\n                    return new BitmapOrigin();\n                }\n                return BitmapOrigin.CreateFromNode(node, PluginBase.PluginManager.FindWz);\n            }).ToArray();\n\n            Color color = Color.FromArgb(resNode.FindNodeByPath(\"clr\").GetValueEx(-1));\n            BitmapOrigin ani0 = default;\n            Wz_Node ani0Node = resNode.FindNodeByPath(false, \"ani\", \"0\");\n            if (ani0Node != null)\n            {\n                ani0 = BitmapOrigin.CreateFromNode(ani0Node, PluginBase.PluginManager.FindWz);\n            }\n\n            //测试y轴大小\n            int offsetY = wce.Min(bmp => bmp.OpOrigin.Y);\n            int height = wce.Max(bmp => bmp.Rectangle.Bottom);\n\n            //测试宽度\n            var font = GearGraphics.ItemDetailFont2;\n            var fmt = StringFormat.GenericTypographic;\n            int nameWidth = string.IsNullOrEmpty(tagName) ? 0 : (int)Math.Ceiling(g.MeasureString(tagName, font, 261, fmt).Width);\n            int center = picW / 2;\n\n            if (ani0.Bitmap == null) // legacy mode\n            {\n                int left = center - nameWidth / 2;\n                int right = left + nameWidth;\n\n                //开始绘制背景\n                picH -= offsetY;\n                if (wce[0].Bitmap != null)\n                {\n                    g.DrawImage(wce[0].Bitmap, left - wce[0].Origin.X, picH - wce[0].Origin.Y);\n                }\n                if (wce[1].Bitmap != null) //不用拉伸 用纹理平铺 看运气\n                {\n                    var brush = new TextureBrush(wce[1].Bitmap);\n                    Rectangle rect = new Rectangle(left, picH - wce[1].Origin.Y, right - left, brush.Image.Height);\n                    brush.TranslateTransform(rect.X, rect.Y);\n                    g.FillRectangle(brush, rect);\n                    brush.Dispose();\n                }\n                if (wce[2].Bitmap != null)\n                {\n                    g.DrawImage(wce[2].Bitmap, right - wce[2].Origin.X, picH - wce[2].Origin.Y);\n                }\n\n                //绘制文字\n                if (!string.IsNullOrEmpty(tagName))\n                {\n                    using var brush = new SolidBrush(color);\n                    g.DrawString(tagName, font, brush, left, picH, fmt);\n                }\n            }\n            else // ani mode\n            {\n                bool mixedAniMode = wce[1].Bitmap != null && (wce[1].Bitmap.Width > 1 || wce[1].Bitmap.Height > 1);\n\n                offsetY = Math.Min(offsetY, ani0.OpOrigin.Y);\n                height = Math.Max(height, ani0.Rectangle.Bottom);\n\n                int bgWidth = mixedAniMode ? wce[1].Bitmap.Width : nameWidth;\n                int left = center - bgWidth / 2;\n                int right = left + bgWidth;\n                int nameLeft = center - nameWidth / 2;\n\n                picH -= offsetY;\n\n                if (mixedAniMode)\n                {\n                    // draw legay center\n                    // Note: item 1143360 (MILESTONE) does not render well, ignore it.\n                    g.DrawImage(wce[1].Bitmap, left - wce[1].Origin.X, picH - wce[1].Origin.Y);                   \n                    // draw ani0 based on bg center position\n                    g.DrawImage(ani0.Bitmap, left - wce[1].Origin.X - ani0.Origin.X, picH - wce[1].Origin.Y - ani0.Origin.Y);\n                    if (!string.IsNullOrEmpty(tagName)) // draw name\n                    {\n                        using var brush = new SolidBrush(color);\n                        // offsetX with bg for better alignment\n                        g.DrawString(tagName, font, brush, nameLeft - wce[1].Origin.X, picH, fmt);\n                    }\n                }\n                else\n                {\n                    // draw ani0 only\n                    g.DrawImage(ani0.Bitmap, left - ani0.Origin.X, picH - ani0.Origin.Y);\n                }\n            }\n\n            picH += height;\n        }\n\n        [DllImport(\"user32.dll\")]\n        private static extern IntPtr SendMessage(IntPtr hwnd, UInt32 wMsg, IntPtr wParam, IntPtr lParam);\n        private const int WM_SETREDRAW = 0xB;\n\n        public static void SetRedraw(System.Windows.Forms.Control control, bool enable)\n        {\n            if (control != null)\n            {\n                SendMessage(control.Handle, WM_SETREDRAW, new IntPtr(enable ? 1 : 0), IntPtr.Zero);\n            }\n        }\n\n        private class FormattedTextRenderer : WzComparerR2.Text.TextRenderer<Font>, IDisposable\n        {\n            public FormattedTextRenderer()\n            {\n                fmt = (StringFormat)StringFormat.GenericTypographic.Clone();\n            }\n\n            public bool UseGDIRenderer { get; set; }\n            public IDictionary<string, Color> FontColorTable { get; set; }\n\n            const int MAX_RANGES = 32;\n            StringFormat fmt;\n\n            Graphics g;\n            RectangleF infinityRect;\n            int drawX;\n            Color defaultColor;\n\n            public void DrawString(Graphics g, string s, Font font, int x, int x1, ref int y, int height, TextAlignment alignment = TextAlignment.Left)\n            {\n                //初始化环境\n                this.g = g;\n                this.drawX = x;\n                this.defaultColor = Color.White;\n                float fontLineHeight = GetFontLineHeight(font);\n                this.infinityRect = new RectangleF(0, 0, ushort.MaxValue, fontLineHeight);\n\n                base.DrawFormatString(s, font, x1 - x, ref y, height, alignment);\n            }\n\n            public void DrawPlainText(Graphics g, string s, Font font, Color color, int x, int x1, ref int y, int height, TextAlignment alignment = TextAlignment.Left)\n            {\n                //初始化环境\n                this.g = g;\n                this.drawX = x;\n                this.defaultColor = color;\n                float fontLineHeight = GetFontLineHeight(font);\n                this.infinityRect = new RectangleF(0, 0, ushort.MaxValue, fontLineHeight);\n\n                base.DrawPlainText(s, font, x1 - x, ref y, height, alignment);\n            }\n\n            private float GetFontLineHeight(Font font)\n            {\n                var ff = font.FontFamily;\n                return (float)Math.Ceiling(1.0 * font.Height * ff.GetLineSpacing(font.Style) / ff.GetEmHeight(font.Style));\n            }\n\n            protected override void MeasureRuns(List<Run> runs)\n            {\n                List<Run> tempRuns = new List<Run>(MAX_RANGES);\n\n                foreach (var run in runs)\n                {\n                    tempRuns.Add(run);\n                    if (tempRuns.Count >= MAX_RANGES)\n                    {\n                        MeasureBatch(tempRuns);\n                        tempRuns.Clear();\n                    }\n                }\n\n                MeasureBatch(tempRuns);\n\n                //failed\n                if (runs.Where(run => !run.IsBreakLine && run.Length > 0)\n                    .All(run => run.Width == 0))\n                {\n                    float x = 0;\n                    foreach (var run in runs.Where(r => !r.IsBreakLine))\n                    {\n                        run.X = (int)Math.Round(x);\n                        float width = 0;\n                        for (int i = 0; i < run.Length; i++)\n                        {\n                            var chr = this.sb[run.StartIndex + i];\n                            width += chr > 0xff ? this.font.Size : (this.font.Size / 2);\n                        }\n                        run.Width = (int)Math.Round(x);\n                        x += width;\n                    }\n                }\n            }\n\n            private void MeasureBatch(List<Run> runs)\n            {\n                string text = sb.ToString();\n                if (runs.Count > 0 && !runs.All(run => run.IsBreakLine))\n                {\n                    fmt.SetMeasurableCharacterRanges(runs.Select(r => new CharacterRange(r.StartIndex, r.Length)).ToArray());\n                    var regions = g.MeasureCharacterRanges(text, font, infinityRect, fmt);\n                    for (int i = 0; i < runs.Count; i++)\n                    {\n                        var layout = regions[i].GetBounds(g);\n                        runs[i].X = (int)Math.Round(layout.Left);\n                        runs[i].Width = (int)Math.Round(layout.Width);\n                        regions[i].Dispose();\n                    }\n                }\n            }\n\n            protected override Rectangle[] MeasureChars(int startIndex, int length)\n            {\n                string word = sb.ToString(startIndex, length);\n                Rectangle[] rects = new Rectangle[length];\n\n                for (int i = 0; i < length; i += MAX_RANGES)\n                { //批次\n                    int chrCount = Math.Min(length - i, MAX_RANGES);\n                    fmt.SetMeasurableCharacterRanges(\n                        Enumerable.Range(i, chrCount)\n                        .Select(start => new CharacterRange(start, 1))\n                        .ToArray());\n                    var regions = g.MeasureCharacterRanges(word, font, infinityRect, fmt);\n                    for (int i1 = 0; i1 < regions.Length; i1++)\n                    {\n                        var rect = regions[i1].GetBounds(g);\n                        rects[i + i1] = new Rectangle(\n                            (int)Math.Round(rect.Left),\n                            (int)Math.Round(rect.Top),\n                            (int)Math.Round(rect.Width),\n                            (int)Math.Round(rect.Height)\n                            );\n                    }\n                }\n\n                //failed\n                if (rects.All(rect => rect.Width == 0))\n                {\n                    float x = 0;\n                    for (int i = 0; i < rects.Length; i++)\n                    {\n                        var chr = this.sb[startIndex + i];\n                        var width = chr > 0xff ? this.font.Size : (this.font.Size / 2);\n                        rects[i] = new Rectangle(\n                            (int)Math.Round(x),\n                            0,\n                            (int)Math.Round(width),\n                            font.Height\n                            );\n                    }\n                }\n\n                return rects;\n            }\n\n            protected override void Flush(StringBuilder sb, int startIndex, int length, int x, int y, string colorID)\n            {\n                string content = sb.ToString(startIndex, length);\n                colorID = colorID ?? string.Empty;\n                Color color;\n                if (!(this.FontColorTable?.TryGetValue(colorID, out color) ?? false))\n                {\n                    switch (colorID)\n                    {\n                        case \"c\": color = GearGraphics.OrangeBrushColor; break;\n                        default: color = this.defaultColor; break;\n                    }\n                }\n                if (this.UseGDIRenderer)\n                {\n                    TR.DrawText(g, content, font, new Point(this.drawX + x, y), color, TextFormatFlags.NoPrefix | TextFormatFlags.NoPadding);\n                }\n                else\n                {\n                    using (var brush = new SolidBrush(color))\n                    {\n                        g.DrawString(content, font, brush, this.drawX + x, y, fmt);\n                    }\n                }\n            }\n\n            public void Dispose()\n            {\n                if (fmt != null)\n                    fmt.Dispose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/GearTooltipRender.cs",
    "content": "﻿using System;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing System.Collections.Generic;\nusing System.Text;\nusing CharaSimResource;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class GearTooltipRender : TooltipRender\n    {\n        public GearTooltipRender()\n        {\n        }\n\n        private Gear gear;\n        private CharacterStatus charStat;\n\n        public Gear Gear\n        {\n            get { return gear; }\n            set { gear = value; }\n        }\n\n        public CharacterStatus CharacterStatus\n        {\n            get { return charStat; }\n            set { charStat = value; }\n        }\n\n        public override Bitmap Render()\n        {\n            if (this.gear == null)\n            {\n                return null;\n            }\n            int picHeight, iconY, picHeight2, picHeight3;\n            Bitmap left = renderBase(out picHeight, out iconY);\n            Bitmap add = renderAddition(out picHeight2);\n            Bitmap set = renderSetItem(out picHeight3);\n\n            //整合图像\n            int width = 252;\n            if (add != null) width += 252;\n            if (set != null) width += 252;\n            Bitmap tooltip = new Bitmap(width, Math.Max(Math.Max(picHeight, picHeight2), picHeight3));\n            Graphics g = Graphics.FromImage(tooltip);\n            bool epic = gear.Epic;\n\n            width = 0;\n            //绘制主图\n            if (left != null)\n            {\n                g.FillRectangle(epic ? GearGraphics.EpicGearBackBrush : GearGraphics.GearBackBrush, 2, 2, 248, picHeight - 4);\n                g.CompositingMode = CompositingMode.SourceCopy;\n                g.FillRectangle(epic ? GearGraphics.EpicGearIconBackBrush : GearGraphics.GearIconBackBrush, 14, iconY, 68, 68);\n                g.CompositingMode = CompositingMode.SourceOver;\n                g.DrawImage(left, 0, 0, new Rectangle(0, 0, 252, picHeight - 2), GraphicsUnit.Pixel);\n                //绘制外边框\n                g.DrawLines(epic ? GearGraphics.EpicGearBackPen : GearGraphics.GearBackPen, GearGraphics.GetBorderPath(0, 252, picHeight));\n                //绘制等级边框\n                Pen pen = GearGraphics.GetGearItemBorderPen(gear.Grade);\n                if (pen != null)\n                {\n                    g.DrawLines(pen, getRankBorderPath(picHeight));\n                }\n                width += 252;\n            }\n\n            //绘制addition\n            if (add != null)\n            {\n                //底色和边框\n                g.FillRectangle(epic ? GearGraphics.EpicGearBackBrush : GearGraphics.GearBackBrush, width + 2, 2, 248, picHeight - 4);\n                g.DrawLines(epic ? GearGraphics.EpicGearBackPen : GearGraphics.GearBackPen, GearGraphics.GetBorderPath(width, 252, picHeight));\n                //复制原图\n                g.DrawImage(add, width, 0, new Rectangle(0, 0, 252, picHeight2), GraphicsUnit.Pixel);\n                add.Dispose();\n                width += 252;\n            }\n\n            //绘制setitem\n            if (set != null)\n            {\n                //底色和边框\n                g.FillRectangle(GearGraphics.GearBackBrush, width + 2, 2, 248, picHeight3 - 4);\n                g.DrawLines(GearGraphics.GearBackPen, GearGraphics.GetBorderPath(width, 252, picHeight3));\n                //复制原图\n                g.DrawImage(set, width, 0, new Rectangle(0, 0, 252, picHeight3), GraphicsUnit.Pixel);\n                set.Dispose();\n                width += 252;\n            }\n\n               // GearGraphics.DrawGearDetailNumber(g, 2, 2, gear.ItemID.ToString(\"d8\"), true);\n\n            g.Dispose();\n\n            return tooltip;\n        }\n\n        private Bitmap renderBase(out int picHeight, out int iconY)\n        {\n            //绘制左侧部分\n            Bitmap leftPart = new Bitmap(252, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(leftPart);\n            StringFormat format = new StringFormat();\n            int value;\n\n            picHeight = 10;\n            if (gear.Star > 0) //绘制星星\n            {\n                if (gear.Star < 5)\n                {\n                    for (int i = 0; i < gear.Star; i++)\n                    {\n                        g.DrawImage(Resource.ToolTip_Equip_Star_Star, 126 - gear.Star * 13 / 2 + 13 * i, picHeight);\n                    }\n                    picHeight += 18;\n                }\n                else\n                {\n                    int star = gear.Star % 5, star2 = gear.Star / 5;\n                    int dx = 126 - (13 * star + 26 * star2) / 2;\n                    for (int i = 0; i < 1; i++, dx += 26)\n                    {\n                        g.DrawImage(Resource.ToolTip_Equip_Star_Star2, dx, picHeight);\n                    }\n                    for (int i = 0; i < star; i++, dx += 13)\n                    {\n                        g.DrawImage(Resource.ToolTip_Equip_Star_Star, dx, picHeight + 5);\n                    }\n                    for (int i = 1; i < star2; i++, dx += 26)\n                    {\n                        g.DrawImage(Resource.ToolTip_Equip_Star_Star2, dx, picHeight);\n                    }\n                    picHeight += 28;\n                }\n            }\n\n            //装备标题\n            StringResult sr;\n            if (StringLinker == null || !StringLinker.StringEqp.TryGetValue(gear.ItemID, out sr))\n            {\n                sr = new StringResult();\n                sr.Name = \"(null)\";\n            }\n            string gearName = sr.Name;\n            string nameAdd = gear.ScrollUp > 0 ? (\"+\" + gear.ScrollUp) : null;\n            switch (Gear.GetGender(gear.ItemID))\n            {\n                case 0: nameAdd += \"男\"; break;\n                case 1: nameAdd += \"女\"; break;\n            }\n            if (!string.IsNullOrEmpty(nameAdd))\n            {\n                gearName += \" (\" + nameAdd + \")\";\n            }\n            format.Alignment = StringAlignment.Center;\n            g.DrawString(gearName, GearGraphics.ItemNameFont,\n                GearGraphics.GetGearNameBrush(gear.diff, gear.ScrollUp > 0), 124, picHeight, format);//绘制装备名称\n            picHeight += 19;\n\n            //装备rank\n            string rankStr;\n            if (gear.GetBooleanValue(GearPropType.specialGrade))\n                rankStr = ItemStringHelper.GetGearGradeString(GearGrade.Special);\n            else\n                rankStr = ItemStringHelper.GetGearGradeString(gear.Grade);\n            g.DrawString(rankStr, GearGraphics.ItemDetailFont, Brushes.White, 127, picHeight, format);\n            picHeight += 21;\n\n            //额外属性\n            for (int i = 0; i < 2; i++)\n            {\n                string attrStr = GetGearAttributeString(i);\n                if (!string.IsNullOrEmpty(attrStr))\n                {\n                    g.DrawString(attrStr, GearGraphics.ItemDetailFont, GearGraphics.GearNameBrushC, 126, picHeight, format);\n                    picHeight += 19;\n                }\n            }\n\n            //装备限时\n            if (gear.TimeLimited)\n            {\n                DateTime time = DateTime.Now.AddDays(7d);\n                string expireStr = time.ToString(\"到yyyy年 M月 d日 H时 m分可以用\");\n                g.DrawString(expireStr, GearGraphics.ItemDetailFont, Brushes.White, 126, picHeight, format);\n                picHeight += 16;\n            }\n\n            picHeight += 1;\n            iconY = picHeight + 1;\n            bool epic = gear.Epic;\n\n            //绘制图标\n            if (gear.Icon.Bitmap != null)\n            {\n                g.DrawImage(GearGraphics.EnlargeBitmap(gear.Icon.Bitmap),\n                    14 + (1 - gear.Icon.Origin.X) * 2,\n                    iconY + (33 - gear.Icon.Origin.Y) * 2);\n            }\n            if (gear.Cash)\n            {\n                g.DrawImage(GearGraphics.EnlargeBitmap(Resource.CashItem_0),\n                    14 + 68 - 26,\n                    iconY + 68 - 26);\n            }\n\n            //绘制属性要求\n            drawGearReq(g, ref picHeight);\n\n            //绘制装备等级\n            if (gear.Props.TryGetValue(GearPropType.level, out value))\n            {\n                g.DrawImage(Resource.ToolTip_Equip_GrowthEnabled_itemLEV, 96, picHeight);\n                GearGraphics.DrawGearGrowthNumber(g, 160, picHeight + 4, (value == -1) ? \"m\" : value.ToString(), true);\n                picHeight += 12;\n                g.DrawImage(Resource.ToolTip_Equip_GrowthEnabled_itemEXP, 96, picHeight);\n                GearGraphics.DrawGearGrowthNumber(g, 160, picHeight + 4, (value == -1) ? \"m\" : \"0%\", true);\n            }\n            else\n            {\n                g.DrawImage(Resource.ToolTip_Equip_GrowthDisabled_itemLEV, 96, picHeight);\n                g.DrawImage(Resource.ToolTip_Equip_GrowthDisabled_none, 160, picHeight + 4 + 3);\n                picHeight += 12;\n                g.DrawImage(Resource.ToolTip_Equip_GrowthDisabled_itemEXP, 96, picHeight);\n                g.DrawImage(Resource.ToolTip_Equip_GrowthDisabled_none, 160, picHeight + 4 + 3);\n            }\n            picHeight += 12;\n            if (gear.Props.TryGetValue(GearPropType.durability, out value))\n            {\n                if (value > 100) value = 100;\n                g.DrawImage(value > 0 ? Resource.ToolTip_Equip_Can_durability : Resource.ToolTip_Equip_Cannot_durability, 96, picHeight);\n                GearGraphics.DrawGearDetailNumber(g, 173, picHeight, value.ToString() + \"%\", value > 0);\n            }\n            picHeight += 13;\n\n            //绘制职业要求\n            int reqJob;\n            gear.Props.TryGetValue(GearPropType.reqJob, out reqJob);\n            g.DrawString(\"新手\", GearGraphics.ItemDetailFont, reqJob > 0 ? Brushes.Red : Brushes.White, 10, picHeight);\n            if (reqJob == 0) reqJob = 0x1f;//0001 1111\n            if (reqJob == -1) reqJob = 0; //0000 0000\n            g.DrawString(\"战士\", GearGraphics.ItemDetailFont, (reqJob & 1) == 0 ? Brushes.Red : Brushes.White, 46, picHeight);\n            g.DrawString(\"魔法师\", GearGraphics.ItemDetailFont, (reqJob & 2) == 0 ? Brushes.Red : Brushes.White, 82, picHeight);\n            g.DrawString(\"弓箭手\", GearGraphics.ItemDetailFont, (reqJob & 4) == 0 ? Brushes.Red : Brushes.White, 130, picHeight);\n            g.DrawString(\"飞侠\", GearGraphics.ItemDetailFont, (reqJob & 8) == 0 ? Brushes.Red : Brushes.White, 178, picHeight);\n            g.DrawString(\"海盗\", GearGraphics.ItemDetailFont, (reqJob & 16) == 0 ? Brushes.Red : Brushes.White, 214, picHeight);\n            picHeight += 19;\n\n            //额外职业要求\n            string extraReq = ItemStringHelper.GetExtraJobReqString(gear.type) ??\n                (gear.Props.TryGetValue(GearPropType.reqSpecJob, out value) ? ItemStringHelper.GetExtraJobReqString(value) : null);\n            if (!string.IsNullOrEmpty(extraReq))\n            {\n                g.DrawString(extraReq, GearGraphics.ItemDetailFont, GearGraphics.GearNameBrushC, 124, picHeight, format);\n                picHeight += 18;\n            }\n\n            //分割线1号\n            g.DrawLine(Pens.White, 6, picHeight, 245, picHeight);\n            picHeight += 9;\n\n            bool hasPart2 = false;\n            //绘制属性\n            if (gear.Props.TryGetValue(GearPropType.superiorEqp, out value) && value > 0)\n            {\n                g.DrawString(\"极真\", GearGraphics.ItemNameFont, GearGraphics.SetItemNameBrush, 126, picHeight, format);\n                picHeight += 18;\n            }\n            if (gear.Props.TryGetValue(GearPropType.limitBreak, out value) && value > 0)\n            {\n                g.DrawString(\"突破极限\", GearGraphics.ItemNameFont, GearGraphics.SetItemNameBrush, 126, picHeight, format);\n                picHeight += 18;\n            }\n\n            bool isWeapon = Gear.IsLeftWeapon(gear.type) || Gear.IsDoubleHandWeapon(gear.type);\n            string typeStr = ItemStringHelper.GetGearTypeString(gear.type);\n            if (!string.IsNullOrEmpty(typeStr))\n            {\n                g.DrawString(\"·\", GearGraphics.ItemDetailFont, Brushes.White, 8, picHeight);\n                g.DrawString((isWeapon ? \"武器\" : \"装备\") + \"分类 : \" + typeStr,\n                    GearGraphics.ItemDetailFont, Brushes.White, 20, picHeight);\n                picHeight += 16;\n                hasPart2 = true;\n            }\n            if (gear.Props.TryGetValue(GearPropType.attackSpeed, out value))\n            {\n                g.DrawString(\"·\", GearGraphics.ItemDetailFont, Brushes.White, 8, picHeight);\n                g.DrawString(\"攻击速度 : \" + ItemStringHelper.GetAttackSpeedString(value),\n                    GearGraphics.ItemDetailFont, Brushes.White, 20, picHeight);\n                picHeight += 16;\n                hasPart2 = true;\n            }\n            List<GearPropType> props = new List<GearPropType>();\n            foreach (KeyValuePair<GearPropType, int> p in gear.Props)\n            {\n                if ((int)p.Key < 100 && p.Value != 0)\n                    props.Add(p.Key);\n            }\n            props.Sort();\n            foreach (GearPropType type in props)\n            {\n                g.DrawString(\"·\", GearGraphics.ItemDetailFont, Brushes.White, 8, picHeight);\n                g.DrawString(ItemStringHelper.GetGearPropString(type, gear.Props[type]), (epic && Gear.IsEpicPropType(type)) ? GearGraphics.EpicGearDetailFont : GearGraphics.ItemDetailFont, Brushes.White, 20, picHeight);\n                picHeight += 16;\n                hasPart2 = true;\n            }\n            bool hasTuc = gear.HasTuc && gear.Props.TryGetValue(GearPropType.tuc, out value);\n            if (hasTuc)\n            {\n                g.DrawString(\"·可升级次数 : \" + value + \"回\", GearGraphics.ItemDetailFont, Brushes.White, 8, picHeight);\n                picHeight += 16;\n                hasPart2 = true;\n            }\n            if (gear.Props.TryGetValue(GearPropType.limitBreak, out value) && value > 0)\n            {\n                g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.limitBreak, value), GearGraphics.ItemDetailFont, GearGraphics.SetItemNameBrush, 8, picHeight);\n                picHeight += 16;\n                hasPart2 = true;\n            }\n\n            if (hasTuc && gear.Hammer > -1)\n            {\n                if (gear.Hammer == 2)\n                {\n                    g.DrawString(\"黄金锤提炼完成\", GearGraphics.ItemDetailFont, Brushes.White, 8, picHeight);\n                    picHeight += 16;\n                }\n                if (gear.Props.TryGetValue(GearPropType.superiorEqp, out value) && value > 0)\n                {\n                    g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.superiorEqp, value), GearGraphics.ItemDetailFont, GearGraphics.SetItemNameBrush, 8, picHeight);\n                    picHeight += 16;\n                }\n                if (gear.Star > 0)\n                {\n                    g.DrawString(\"·应用\" + gear.Star + \"星强化\", GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush, 8, picHeight);\n                    picHeight += 16;\n                }\n                picHeight += 2;\n                g.DrawString(\"金锤子已提高的强化次数\", GearGraphics.ItemDetailFont, GearGraphics.GoldHammerBrush, 8, picHeight);\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;\n                g.DrawString(\": \" + gear.Hammer.ToString() + (gear.Hammer == 2 ? \"(MAX)\" : null), GearGraphics.TahomaFont, GearGraphics.GoldHammerBrush, 140, picHeight - 2);\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault;\n                picHeight += 14;\n                hasPart2 = true;\n            }\n               \n            \n            //分割线2号\n            if (hasPart2)\n            {\n                g.DrawLine(Pens.White, 6, picHeight, 245, picHeight);\n                picHeight += 9;\n            }\n\n            //绘制潜能\n            int optionCount = 0;\n            foreach (Potential potential in gear.Options)\n            {\n                if (potential != null)\n                {\n                    g.DrawString(\"·\", GearGraphics.ItemDetailFont, Brushes.White, 8, picHeight);\n                    g.DrawString(potential.ConvertSummary(), GearGraphics.ItemDetailFont, Brushes.White, 20, picHeight);\n                    picHeight += 16;\n                    optionCount++;\n                }\n            }\n            if (optionCount>0){\n                picHeight += 4 * optionCount;\n            }\n            else if (gear.CanPotential)\n            {\n                GearGraphics.DrawString(g, \" #c潜能卷轴# 可增加 #cC级物品# 潜力，但需鉴定。\\n #c放大镜# 可解除 #c未鉴定物品# 潜能的封印。\",\n                    GearGraphics.ItemDetailFont, 8, 236, ref picHeight, 16);\n                picHeight += 4;\n            }\n\n            //绘制附加潜能\n            int adOptionCount = 0;\n            foreach (Potential potential in gear.AdditionalOptions)\n            {\n                if (potential != null)\n                {\n                    adOptionCount++;\n                }\n            }\n            if (adOptionCount > 0)\n            {\n                //分割线3号\n                picHeight -= 3;\n                g.DrawLine(Pens.White, 6, picHeight, 245, picHeight);\n                g.DrawImage(GetAdditionalOptionIcon(gear.AdditionGrade), 8, picHeight+1);\n                g.DrawString(\"附加潜能\", GearGraphics.ItemDetailFont, GearGraphics.SetItemNameBrush, 26, picHeight+2);\n                picHeight += 24;\n\n                foreach (Potential potential in gear.AdditionalOptions)\n                {\n                    if (potential != null)\n                    {\n                        g.DrawString(\"+\", GearGraphics.ItemDetailFont, Brushes.White, 8, picHeight);\n                        g.DrawString(potential.ConvertSummary(), GearGraphics.ItemDetailFont, Brushes.White, 20, picHeight);\n                        picHeight += 18;\n                        adOptionCount++;\n                    }\n                }\n                picHeight += 5;\n            }\n\n            //绘制desc\n            if (!string.IsNullOrEmpty(sr.Desc))\n            {\n                if (optionCount > 0) picHeight -= 2;\n                picHeight -= 3;\n                GearGraphics.DrawString(g, sr.Desc, GearGraphics.ItemDetailFont, 8, 236, ref picHeight, 16);\n                picHeight += 5;\n            }\n            if (gear.Props.TryGetValue(GearPropType.tradeAvailable, out value) && value != 0)\n            {\n                g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.tradeAvailable, value),\n                    GearGraphics.ItemDetailFont,\n                    GearGraphics.OrangeBrush,\n                    14, picHeight - 5);\n                picHeight += 16;\n            }\n\n            if (gear.Props.TryGetValue(GearPropType.accountShareTag, out value) && value != 0)\n            {\n                GearGraphics.DrawString(g, \" #c\" + ItemStringHelper.GetGearPropString(GearPropType.accountShareTag, 1) + \"#\",\n                     GearGraphics.ItemDetailFont, 8, 236, ref picHeight, 16);\n                picHeight += 16;\n            }\n\n            //绘制倾向\n            if (gear.State == GearState.itemList)\n            {\n                string incline = null;\n                GearPropType[] inclineTypes = new GearPropType[]{\n                    GearPropType.charismaEXP,\n                    GearPropType.senseEXP,\n                    GearPropType.insightEXP,\n                    GearPropType.willEXP,\n                    GearPropType.craftEXP,\n                    GearPropType.charmEXP };\n\n                string[] inclineString = new string[]{\n                    \"领导力\",\"感性\",\"洞察力\",\"意志\",\"手技\",\"魅力\"};\n\n                for (int i = 0; i < inclineTypes.Length; i++)\n                {\n                    if (gear.Props.TryGetValue(inclineTypes[i], out value) && value > 0)\n                    {\n                        incline += \"，\" + inclineString[i] + value;\n                    }\n                }\n\n                if (!string.IsNullOrEmpty(incline))\n                {\n                    picHeight -= 5;\n                    GearGraphics.DrawString(g, \"\\n #c装备时可以获得\" + incline.Substring(1) + \"的经验值，仅限1次。#\",\n                        GearGraphics.ItemDetailFont, 8, 236, ref picHeight, 16);\n                    picHeight += 8;\n                }\n            }\n            format.Dispose();\n            g.Dispose();\n            return leftPart;\n        }\n\n        private Bitmap renderAddition(out int picHeight)\n        {\n            Bitmap addBitmap = null;\n            picHeight = 0;\n            if (gear.Additions.Count > 0)\n            {\n                addBitmap = new Bitmap(252, DefaultPicHeight);\n                Graphics g = Graphics.FromImage(addBitmap);\n                StringBuilder sb = new StringBuilder();\n                foreach (Addition addition in gear.Additions)\n                {\n                    string conString = addition.GetConString(), propString = addition.GetPropString();\n                    if (!string.IsNullOrEmpty(conString) || !string.IsNullOrEmpty(propString))\n                    {\n                        sb.Append(\"- \");\n                        if (!string.IsNullOrEmpty(conString))\n                            sb.AppendLine(conString);\n                        if (!string.IsNullOrEmpty(propString))\n                            sb.AppendLine(propString);\n                        sb.AppendLine();\n                    }\n                }\n                if (sb.Length > 0)\n                {\n                    picHeight = 10;\n                    GearGraphics.DrawString(g, sb.ToString(), GearGraphics.ItemDetailFont, 8, 236, ref picHeight, 16);\n                }\n                g.Dispose();\n            }\n            return addBitmap;\n        }\n\n        private Bitmap renderSetItem(out int picHeight)\n        {\n            Bitmap setBitmap = null;\n            int setID;\n            picHeight = 0;\n            if (gear.Props.TryGetValue(GearPropType.setItemID, out setID))\n            {\n                SetItem setItem;\n                if (!CharaSimLoader.LoadedSetItems.TryGetValue(setID, out setItem))\n                    return null;\n                setBitmap = new Bitmap(252, DefaultPicHeight);\n                Graphics g = Graphics.FromImage(setBitmap);\n                StringFormat format = new StringFormat();\n                format.Alignment = StringAlignment.Center;\n\n                picHeight = 10;\n                g.DrawString(setItem.SetItemName, GearGraphics.ItemDetailFont, GearGraphics.SetItemNameBrush, 126, 10, format);\n                picHeight += 25;\n\n                format.Alignment=StringAlignment.Far;\n\n                foreach (var setItemPart in setItem.ItemIDs.Parts)\n                {\n                    string itemName = setItemPart.Value.RepresentName;\n                    string typeName = setItemPart.Value.TypeName;\n\n                    if (string.IsNullOrEmpty(itemName) || string.IsNullOrEmpty(typeName))\n                    {\n                        foreach (var itemID in setItemPart.Value.ItemIDs)\n                        {\n                            StringResult sr;\n                            if (!StringLinker.StringEqp.TryGetValue(itemID.Key, out sr))\n                            {\n                                sr = new StringResult();\n                                sr.Name = \"(null)\";\n                            }\n                            itemName = sr.Name;\n                            typeName = ItemStringHelper.GetSetItemGearTypeString(Gear.GetGearType(itemID.Key));\n                            break;\n                        }\n                    }\n\n                    itemName = itemName ?? string.Empty;\n                    typeName = typeName ?? \"装备\";\n\n                    Brush brush = setItemPart.Value.Enabled ? Brushes.White : GearGraphics.SetItemGrayBrush;\n                    g.DrawString(itemName, GearGraphics.ItemDetailFont, brush, 8, picHeight);\n                    g.DrawString(\"(\" + typeName + \")\", GearGraphics.ItemDetailFont, brush, 246, picHeight, format);\n                    picHeight += 18;\n                }\n\n                picHeight += 5;\n                g.DrawLine(Pens.White, 6, picHeight, 245, picHeight);//分割线\n                picHeight += 9;\n                foreach (KeyValuePair<int, SetItemEffect> effect in setItem.Effects)\n                {\n                    g.DrawString(effect.Key + \"套装效果\", GearGraphics.ItemDetailFont, GearGraphics.SetItemNameBrush, 8, picHeight);\n                    picHeight += 16;\n                    Brush brush = effect.Value.Enabled ? Brushes.White : GearGraphics.SetItemGrayBrush;\n                    foreach (KeyValuePair<GearPropType, object> prop in effect.Value.Props)\n                    {\n                        if (prop.Key == GearPropType.Option)\n                        {\n                            List<Potential> ops = (List<Potential>)prop.Value;\n                            foreach (Potential p in ops)\n                            {\n                                g.DrawString(p.ConvertSummary(), GearGraphics.SetItemPropFont, brush, 8, picHeight);\n                                picHeight += 16;\n                            }\n                        }\n                        else\n                        {\n                            g.DrawString(ItemStringHelper.GetGearPropString(prop.Key, Convert.ToInt32(prop.Value)),\n                                GearGraphics.SetItemPropFont, brush, 8, picHeight);\n                            picHeight += 16;\n                        }\n                    }\n                }\n                picHeight += 11;\n                format.Dispose();\n                g.Dispose();\n            }\n            return setBitmap;\n        }\n\n        private string GetGearAttributeString(int line)\n        {\n            int value;\n            List<string> tags = new List<string>();\n            switch (line)\n            {\n                case 0:\n                    if (gear.Props.TryGetValue(GearPropType.only, out value) && value != 0)\n                    {\n                        tags.Add(ItemStringHelper.GetGearPropString(GearPropType.only, value));\n                    }\n                    if (gear.Props.TryGetValue(GearPropType.tradeBlock, out value) && value != 0)\n                    {\n                        tags.Add(ItemStringHelper.GetGearPropString(GearPropType.tradeBlock, value));\n                    }\n                    if (gear.Props.TryGetValue(GearPropType.accountSharable, out value) && value != 0)\n                    {\n                        tags.Add(ItemStringHelper.GetGearPropString(GearPropType.accountSharable, value));\n                    }\n                    if (gear.Props.TryGetValue(GearPropType.equipTradeBlock, out value) && value != 0)\n                    {\n                        if (gear.State == GearState.itemList)\n                        {\n                            tags.Add(ItemStringHelper.GetGearPropString(GearPropType.equipTradeBlock, value));\n                        }\n                        else\n                        {\n                            string tradeBlock = ItemStringHelper.GetGearPropString(GearPropType.tradeBlock, 1);\n                            if (!tags.Contains(tradeBlock))\n                                tags.Add(tradeBlock);\n                        }\n                    }\n                    if (gear.Props.TryGetValue(GearPropType.noPotential, out value) && value != 0)\n                    {\n                        tags.Add(ItemStringHelper.GetGearPropString(GearPropType.noPotential, value));\n                    }\n                    if (gear.Props.TryGetValue(GearPropType.fixedPotential, out value) && value != 0)\n                    {\n                        tags.Add(ItemStringHelper.GetGearPropString(GearPropType.fixedPotential, value));\n                    }\n                    break;\n                case 1:\n                    if (gear.Props.TryGetValue(GearPropType.onlyEquip, out value) && value != 0)\n                    {\n                        tags.Add(ItemStringHelper.GetGearPropString(GearPropType.onlyEquip, value));\n                    }\n                    if (gear.Props.TryGetValue(GearPropType.notExtend, out value) && value != 0)\n                    {\n                        tags.Add(ItemStringHelper.GetGearPropString(GearPropType.notExtend, value));\n                    }\n                    break;\n            }\n            return tags.Count > 0 ? string.Join(\", \", tags.ToArray()) : null;\n        }\n\n        private void drawGearReq(Graphics g, ref int picHeight)\n        {\n            int value;\n            bool isGetProp;\n            bool can;\n\n            //等级要求\n            isGetProp = gear.Props.TryGetValue(GearPropType.reqLevel, out value);\n            can = (charStat == null || charStat.Level >= value);\n            g.DrawImage(can ? Resource.ToolTip_Equip_Can_reqLEV : Resource.ToolTip_Equip_Cannot_reqLEV, 96, picHeight);\n            GearGraphics.DrawGearDetailNumber(g, 156, picHeight + 4, isGetProp ? value.ToString() : \"-\", can);\n            picHeight += 12;\n\n            //力量要求\n            isGetProp = gear.Props.TryGetValue(GearPropType.reqSTR, out value);\n            can = (charStat == null || charStat.Strength.GetSum() >= value);\n            g.DrawImage(can ? Resource.ToolTip_Equip_Can_reqSTR : Resource.ToolTip_Equip_Cannot_reqSTR, 96, picHeight);\n            GearGraphics.DrawGearDetailNumber(g, 156, picHeight + 4, isGetProp ? value.ToString() : \"-\", can);\n            picHeight += 12;\n\n            //敏捷要求\n            isGetProp = gear.Props.TryGetValue(GearPropType.reqDEX, out value);\n            can = (charStat == null || charStat.Dexterity.GetSum() >= value);\n            g.DrawImage(can ? Resource.ToolTip_Equip_Can_reqDEX : Resource.ToolTip_Equip_Cannot_reqDEX, 96, picHeight);\n            GearGraphics.DrawGearDetailNumber(g, 156, picHeight + 4, isGetProp ? value.ToString() : \"-\", can);\n            picHeight += 12;\n\n            //智力要求\n            isGetProp = gear.Props.TryGetValue(GearPropType.reqINT, out value);\n            can = (charStat == null || charStat.Intelligence.GetSum() >= value);\n            g.DrawImage(can ? Resource.ToolTip_Equip_Can_reqINT : Resource.ToolTip_Equip_Cannot_reqINT, 96, picHeight);\n            GearGraphics.DrawGearDetailNumber(g, 156, picHeight + 4, isGetProp ? value.ToString() : \"-\", can);\n            picHeight += 12;\n\n            //运气要求\n            isGetProp = gear.Props.TryGetValue(GearPropType.reqLUK, out value);\n            can = (charStat == null || charStat.Luck.GetSum() >= value);\n            g.DrawImage(can ? Resource.ToolTip_Equip_Can_reqLUK : Resource.ToolTip_Equip_Cannot_reqLUK, 96, picHeight);\n            GearGraphics.DrawGearDetailNumber(g, 156, picHeight + 4, isGetProp ? value.ToString() : \"-\", can);\n            picHeight += 12;\n\n            //人气要求\n            isGetProp = gear.Props.TryGetValue(GearPropType.reqPOP, out value);\n            can = (charStat == null || charStat.Pop >= value);\n            g.DrawImage(can ? Resource.ToolTip_Equip_Can_reqPOP : Resource.ToolTip_Equip_Cannot_reqPOP, 96, picHeight);\n            GearGraphics.DrawGearDetailNumber(g, 156, picHeight + 4, isGetProp ? value.ToString() : \"-\", can);\n            picHeight += 12;\n        }\n\n        private Image GetAdditionalOptionIcon(GearGrade grade)\n        {\n            switch (grade)\n            {\n                case GearGrade.B: return Resource.AdditionalOptionTooltip_rare;\n                case GearGrade.A: return Resource.AdditionalOptionTooltip_epic;\n                case GearGrade.S: return Resource.AdditionalOptionTooltip_unique;\n                case GearGrade.SS: return Resource.AdditionalOptionTooltip_legendary;\n            }\n            return null;\n        }\n\n        private Point[] getRankBorderPath(int height)\n        {\n            List<Point> pointList = new List<Point>(5);\n            pointList.Add(new Point(252 - 4, height - 5));\n            pointList.Add(new Point(252 - 4, 4));\n            pointList.Add(new Point(4, 4));\n            pointList.Add(new Point(4, height - 4));\n            pointList.Add(new Point(252 - 5, height - 4));\n            return pointList.ToArray();\n        }\n\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/GearTooltipRender2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing Resource = CharaSimResource.Resource;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class GearTooltipRender2 : TooltipRender\n    {\n        static GearTooltipRender2()\n        {\n            res = new Dictionary<string, TextureBrush>();\n            res[\"t\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame_top, WrapMode.Clamp);\n            res[\"line\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame_line, WrapMode.Tile);\n            res[\"dotline\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame_dotline, WrapMode.Clamp);\n            res[\"b\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame_bottom, WrapMode.Clamp);\n            res[\"cover\"] = new TextureBrush(Resource.UIToolTip_img_Item_Frame_cover, WrapMode.Clamp);\n        }\n\n        private static Dictionary<string, TextureBrush> res;\n\n        public GearTooltipRender2()\n        {\n        }\n\n        private CharacterStatus charStat;\n\n        public Gear Gear { get; set; }\n\n        public override object TargetItem\n        {\n            get { return this.Gear; }\n            set { this.Gear = value as Gear; }\n        }\n\n        public CharacterStatus CharacterStatus\n        {\n            get { return charStat; }\n            set { charStat = value; }\n        }\n\n        public bool ShowSpeed { get; set; }\n        public bool ShowLevelOrSealed { get; set; }\n        public bool ShowMedalTag { get; set; } = true;\n        public bool IsCombineProperties { get; set; } = true;\n\n        public TooltipRender SetItemRender { get; set; }\n\n        public override Bitmap Render()\n        {\n            if (this.Gear == null)\n            {\n                return null;\n            }\n\n            int[] picH = new int[4];\n            Bitmap left = RenderBase(out picH[0]);\n            Bitmap add = RenderAddition(out picH[1]);\n            Bitmap genesis = RenderGenesisSkills(out int genesisHeight);\n            Bitmap set = RenderSetItem(out int setHeight);\n            picH[2] = genesisHeight + setHeight;\n            Bitmap levelOrSealed = null;\n            if (this.ShowLevelOrSealed)\n            {\n                levelOrSealed = RenderLevelOrSealed(out picH[3]);\n            }\n\n            int width = 261;\n            if (add != null) width += add.Width;\n            if (set != null) width += set.Width;\n            else if (genesis != null) width += genesis.Width; // ideally genesisWeapons always have setitem\n            if (levelOrSealed != null) width += levelOrSealed.Width;\n            int height = 0;\n            for (int i = 0; i < picH.Length; i++)\n            {\n                height = Math.Max(height, picH[i]);\n            }\n            Bitmap tooltip = new Bitmap(width, height);\n            Graphics g = Graphics.FromImage(tooltip);\n\n            //绘制主图\n            width = 0;\n            if (left != null)\n            {\n                //绘制背景\n                g.DrawImage(res[\"t\"].Image, width, 0);\n                FillRect(g, res[\"line\"], width, 13, picH[0] - 13);\n                g.DrawImage(res[\"b\"].Image, width, picH[0] - 13);\n\n                //复制图像\n                g.DrawImage(left, width, 0, new Rectangle(0, 0, left.Width, picH[0]), GraphicsUnit.Pixel);\n\n                //cover\n                g.DrawImage(res[\"cover\"].Image, 3, 3);\n\n                width += left.Width;\n                left.Dispose();\n            }\n\n            //绘制addition\n                if (add != null)\n                {\n                    //绘制背景\n                g.DrawImage(res[\"t\"].Image, width, 0);\n                    FillRect(g, res[\"line\"], width, 13, tooltip.Height - 13);\n                    g.DrawImage(res[\"b\"].Image, width, tooltip.Height - 13);\n\n                    //复制原图\n                g.DrawImage(add, width, 0, new Rectangle(0, 0, add.Width, picH[1]), GraphicsUnit.Pixel);\n\n                width += add.Width;\n                    add.Dispose();\n                }\n\n            //绘制setitem\n            if (genesis != null || set != null)\n            {\n                int y = 0;\n                int partWidth = 0;\n                if (genesis != null)\n                {\n                    // draw background\n                    g.DrawImage(res[\"t\"].Image, width, 0);\n                    FillRect(g, res[\"line\"], width, 13, genesisHeight - 13);\n                    g.DrawImage(res[\"b\"].Image, width, genesisHeight - 13);\n\n                    // copy text layer\n                    g.DrawImage(genesis, width, 0, new Rectangle(0, 0, genesis.Width, genesisHeight), GraphicsUnit.Pixel);\n\n                    y += genesisHeight;\n                    partWidth = Math.Max(partWidth, genesis.Width);\n                    genesis.Dispose();\n                }\n\n                //复制原图\n                if (set != null)\n                {\n                    g.DrawImage(set, width, y, new Rectangle(0, 0, set.Width, setHeight), GraphicsUnit.Pixel);\n                    partWidth = Math.Max(partWidth, set.Width);\n                    set.Dispose();\n                }\n\n                width += partWidth;\n            }\n\n            //绘制levelOrSealed\n            if (levelOrSealed != null)\n            {\n                //绘制背景\n                g.DrawImage(res[\"t\"].Image, width, 0);\n                FillRect(g, res[\"line\"], width, 13, picH[3] - 13);\n                g.DrawImage(res[\"b\"].Image, width, picH[3] - 13);\n\n                //复制原图\n                g.DrawImage(levelOrSealed, width, 0, new Rectangle(0, 0, levelOrSealed.Width, picH[3]), GraphicsUnit.Pixel);\n                width += levelOrSealed.Width;\n                levelOrSealed.Dispose();\n            }\n\n            if (this.ShowObjectID)\n            {\n                GearGraphics.DrawGearDetailNumber(g, 3, 3, Gear.ItemID.ToString(\"d8\"), true);\n            }\n\n            g.Dispose();\n            return tooltip;\n        }\n\n        private Bitmap RenderBase(out int picH)\n        {\n            Bitmap bitmap = new Bitmap(261, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(bitmap);\n            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;\n            StringFormat format = (StringFormat)StringFormat.GenericTypographic.Clone();\n            var itemPropColorTable = new Dictionary<string, Color>()\n            {\n                { \"$y\", GearGraphics.gearCyanColor },\n                { \"$e\", GearGraphics.ScrollEnhancementColor },\n            };\n            int value;\n\n            picH = 13;\n            if (!Gear.GetBooleanValue(GearPropType.blockUpgradeStarforce))\n            {\n                DrawStar2(g, ref picH); //绘制星星\n            }\n\n            //绘制装备名称\n            StringResult sr;\n            if (StringLinker == null || !StringLinker.StringEqp.TryGetValue(Gear.ItemID, out sr))\n            {\n                sr = new StringResult();\n                sr.Name = \"(null)\";\n            }\n            string gearName = sr.Name;\n            string nameAdd = Gear.ScrollUp > 0 ? (\"+\" + Gear.ScrollUp) : null;\n            switch (Gear.GetGender(Gear.ItemID))\n            {\n                case 0: nameAdd += \"男\"; break;\n                case 1: nameAdd += \"女\"; break;\n            }\n            if (!string.IsNullOrEmpty(nameAdd))\n            {\n                gearName += \" (\" + nameAdd + \")\";\n            }\n\n            format.Alignment = StringAlignment.Center;\n            g.DrawString(gearName, GearGraphics.ItemNameFont2,\n                GearGraphics.GetGearNameBrush(Gear.diff, Gear.ScrollUp > 0), 130, picH, format);\n            picH += 23;\n\n            //装备rank\n            string rankStr = null;\n            if (Gear.GetBooleanValue(GearPropType.specialGrade))\n            {\n                rankStr = ItemStringHelper.GetGearGradeString(GearGrade.Special);\n            }\n            else if (!Gear.Cash) //T98后C级物品依然显示\n            {\n                rankStr = ItemStringHelper.GetGearGradeString(Gear.Grade);\n            }\n            if (rankStr != null)\n            {\n                g.DrawString(rankStr, GearGraphics.ItemDetailFont, Brushes.White, 130, picH, format);\n                picH += 15;\n            }\n\n            //额外属性\n            var attrList = GetGearAttributeString();\n            if (attrList.Count > 0)\n            {\n                var font = GearGraphics.ItemDetailFont;\n                string attrStr = null;\n                for (int i = 0; i < attrList.Count; i++)\n                {\n                    var newStr = (attrStr != null ? (attrStr + \", \") : null) + attrList[i];\n                    if (g.MeasureString(newStr, font, short.MaxValue, format).Width > 257)\n                    {\n                        g.DrawString(attrStr, GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush2, 130, picH, format);\n                        picH += 15;\n                        attrStr = attrList[i];\n                    }\n                    else\n                    {\n                        attrStr = newStr;\n                    }\n                }\n                if (!string.IsNullOrEmpty(attrStr))\n                {\n                    g.DrawString(attrStr, GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush2, 130, picH, format);\n                    picH += 15;\n                }\n            }\n\n            //装备限时\n            if (Gear.TimeLimited)\n            {\n                DateTime time = DateTime.Now.AddDays(7d);\n                string expireStr = time.ToString(\"到yyyy年 M月 d日 H时 m分可以用\");\n                g.DrawString(expireStr, GearGraphics.ItemDetailFont, Brushes.White, 130, picH, format);\n                picH += 15;\n            }\n            else if (Gear.GetBooleanValue(GearPropType.abilityTimeLimited))\n            {\n                DateTime time = DateTime.Now.AddDays(7d);\n                string expireStr = time.ToString(\"效果持续到yyyy年M月d日H点m分\");\n                g.DrawString(expireStr, GearGraphics.ItemDetailFont, Brushes.White, 130, picH, format);\n                picH += 15;\n            }\n\n            //分割线1号\n            picH += 7;\n            g.DrawImage(res[\"dotline\"].Image, 0, picH);\n\n            //绘制装备图标\n            if (Gear.Grade > 0 && (int)Gear.Grade <= 4) //绘制外框\n            {\n                Image border = Resource.ResourceManager.GetObject(\"UIToolTip_img_Item_ItemIcon_\" + (int)Gear.Grade) as Image;\n                if (border != null)\n                {\n                    g.DrawImage(border, 13, picH + 11);\n                }\n            }\n            g.DrawImage(Resource.UIToolTip_img_Item_ItemIcon_base, 12, picH + 10); //绘制背景\n            if (Gear.IconRaw.Bitmap != null) //绘制icon\n            {\n                var attr = new System.Drawing.Imaging.ImageAttributes();\n                var matrix = new System.Drawing.Imaging.ColorMatrix(\n                    new[] {\n                        new float[] { 1, 0, 0, 0, 0 },\n                        new float[] { 0, 1, 0, 0, 0 },\n                        new float[] { 0, 0, 1, 0, 0 },\n                        new float[] { 0, 0, 0, 0.5f, 0 },\n                        new float[] { 0, 0, 0, 0, 1 },\n                        });\n                attr.SetColorMatrix(matrix);\n\n                //绘制阴影\n                var shade = Resource.UIToolTip_img_Item_ItemIcon_shade;\n                g.DrawImage(shade,\n                    new Rectangle(18 + 9, picH + 15 + 54, shade.Width, shade.Height),\n                    0, 0, shade.Width, shade.Height,\n                    GraphicsUnit.Pixel,\n                    attr);\n                //绘制图标\n                g.DrawImage(GearGraphics.EnlargeBitmap(Gear.IconRaw.Bitmap),\n                    18 + (1 - Gear.IconRaw.Origin.X) * 2,\n                    picH + 15 + (33 - Gear.IconRaw.Origin.Y) * 2);\n\n                attr.Dispose();\n            }\n            if (Gear.Cash) //绘制cash标识\n            {\n                /* not installed since CMST136\n                 * \n                if (Gear.Props.TryGetValue(GearPropType.royalSpecial, out value) && value > 0)\n                    g.DrawImage(GearGraphics.EnlargeBitmap(Resource.CashItem_label_0),\n                        18 + 68 - 26,\n                        picH + 15 + 68 - 26);\n                else if (Gear.Props.TryGetValue(GearPropType.masterSpecial, out value) && value > 0)\n                    g.DrawImage(GearGraphics.EnlargeBitmap(Resource.CashItem_label_3),\n                        18 + 68 - 26,\n                        picH + 15 + 68 - 26);\n                else\n                */\n                g.DrawImage(GearGraphics.EnlargeBitmap(Resource.CashItem_0),\n                    18 + 68 - 26,\n                    picH + 15 + 68 - 26);\n            }\n            //检查星岩\n            bool hasSocket = Gear.GetBooleanValue(GearPropType.nActivatedSocket);\n            if (hasSocket)\n            {\n                Bitmap socketBmp = GetAlienStoneIcon();\n                if (socketBmp != null)\n                {\n                    g.DrawImage(GearGraphics.EnlargeBitmap(socketBmp),\n                        18 + 2,\n                        picH + 15 + 3);\n                }\n            }\n\n            g.DrawImage(Resource.UIToolTip_img_Item_ItemIcon_cover, 16, picH + 14); //绘制左上角cover\n\n            //绘制攻击力变化\n            format.Alignment = StringAlignment.Far;\n            g.DrawString(\"攻击力增加量\", GearGraphics.ItemDetailFont, GearGraphics.GrayBrush2, 251, picH + 10, format);\n            g.DrawImage(Resource.UIToolTip_img_Item_Equip_Summary_incline_0, 249 - 19, picH + 27); //暂时画个0\n\n            //绘制属性需求\n            DrawGearReq(g, 97, picH + 58);\n            picH += 93;\n\n            //绘制属性变化\n            DrawPropDiffEx(g, 12, picH);\n            picH += 20;\n\n            //绘制职业需求\n            DrawJobReq(g, ref picH);\n\n            //分割线2号\n            g.DrawImage(res[\"dotline\"].Image, 0, picH);\n            picH += 8;\n\n            bool hasPart2 = false;\n            format.Alignment = StringAlignment.Center;\n\n            //绘制属性\n            if (Gear.Props.TryGetValue(GearPropType.superiorEqp, out value) && value > 0)\n            {\n                g.DrawString(\"极真\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 130, picH, format);\n                picH += 16;\n            }\n            if (Gear.Props.TryGetValue(GearPropType.limitBreak, out value) && value > 0)\n            {\n                g.DrawString(\"突破上限武器\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 130, picH, format);\n                picH += 16;\n            }\n\n            //绘制装备升级\n            if (Gear.Props.TryGetValue(GearPropType.level, out value) && !Gear.FixLevel)\n            {\n                bool max = (Gear.Levels != null && value >= Gear.Levels.Count);\n                g.DrawString(\"成长等级: \" + (max ? \"MAX\" : value.ToString()), GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3, 11, picH);\n                picH += 16;\n                g.DrawString(\"成长经验值: \" + (max ? \"MAX\" : \"0%\"), GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3, 11, picH);\n                picH += 16;\n            }\n\n            if (Gear.Props.TryGetValue(GearPropType.@sealed, out value))\n            {\n                bool max = (Gear.Seals != null && value >= Gear.Seals.Count);\n                g.DrawString(\"封印解除阶段 : \" + (max ? \"MAX\" : value.ToString()), GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3, 11, picH);\n                picH += 16;\n                g.DrawString(\"封印解除经验值 : \" + (max ? \"MAX\" : \"0%\"), GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3, 11, picH);\n                picH += 16;\n            }\n\n            //绘制耐久度\n            if (Gear.Props.TryGetValue(GearPropType.durability, out value))\n            {\n                g.DrawString(\"耐久度 : \" + \"100%\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 11, picH);\n                picH += 16;\n            }\n\n            //装备类型\n            bool isWeapon = Gear.IsWeapon(Gear.type);\n            string typeStr = ItemStringHelper.GetGearTypeString(Gear.type);\n            if (!string.IsNullOrEmpty(typeStr))\n            {\n                if (isWeapon)\n                {\n                    typeStr = \"武器分类 : \" + typeStr;\n                }\n                else\n                {\n                    typeStr = \"装备分类 : \" + typeStr;\n                }\n\n                if (Gear.IsLeftWeapon(Gear.type) || Gear.type == GearType.katara)\n                {\n                    typeStr += \" (单手武器)\";\n                }\n                else if (Gear.IsDoubleHandWeapon(Gear.type))\n                {\n                    typeStr += \" (双手武器)\";\n                }\n                g.DrawString(typeStr, GearGraphics.ItemDetailFont, Brushes.White, 11, picH);\n                picH += 16;\n                hasPart2 = true;\n            }\n\n            if (!Gear.Props.TryGetValue(GearPropType.attackSpeed, out value)\n                && (Gear.IsWeapon(Gear.type) || Gear.type == GearType.katara)) //找不到攻速的武器\n            {\n                value = 6; //给予默认速度\n            }\n            //  if (gear.Props.TryGetValue(GearPropType.attackSpeed, out value) && value > 0)\n            if (value > 0)\n            {\n                bool isValidSpeed = (2 <= value && value <= 9);\n                string speedStr = string.Format(\"攻击速度 : {0}{1}{2}\",\n                    ItemStringHelper.GetAttackSpeedString(value),\n                    isValidSpeed ? $\"（第{10 - value}阶段）\" : null,\n                    ShowSpeed ? $\"({value})\" : null\n                );\n\n                g.DrawString(speedStr, GearGraphics.ItemDetailFont, Brushes.White, 11, picH);\n                picH += 16;\n                hasPart2 = true;\n            }\n            //机器人等级\n            if (Gear.Props.TryGetValue(GearPropType.grade, out value) && value > 0)\n            {\n                g.DrawString(\"等级 : \" + value, GearGraphics.ItemDetailFont, Brushes.White, 11, picH);\n                picH += 16;\n                hasPart2 = true;\n            }\n\n\n            //一般属性\n            List<GearPropType> props = new List<GearPropType>();\n            foreach (KeyValuePair<GearPropType, int> p in Gear.PropsV5) //5转过滤\n            {\n                if ((int)p.Key < 100 && p.Value != 0)\n                    props.Add(p.Key);\n            }\n            props.Sort();\n            //bool epic = Gear.Props.TryGetValue(GearPropType.epicItem, out value) && value > 0;\n            foreach (GearPropType type in props)\n            {\n                //var font = (epic && Gear.IsEpicPropType(type)) ? GearGraphics.EpicGearDetailFont : GearGraphics.ItemDetailFont;\n                //g.DrawString(ItemStringHelper.GetGearPropString(type, Gear.Props[type]), font, Brushes.White, 11, picH);\n                //picH += 16;\n\n                //绘制属性变化\n                Gear.StandardProps.TryGetValue(type, out value); //standard value\n                var propStr = ItemStringHelper.GetGearPropDiffString(type, Gear.Props[type], value);\n                GearGraphics.DrawString(g, propStr, GearGraphics.ItemDetailFont, itemPropColorTable, 13, 256, ref picH, 16);\n                hasPart2 = true;\n            }\n\n            //戒指特殊潜能\n            int ringOpt, ringOptLv;\n            if (Gear.Props.TryGetValue(GearPropType.ringOptionSkill, out ringOpt)\n                && Gear.Props.TryGetValue(GearPropType.ringOptionSkillLv, out ringOptLv))\n            {\n                var opt = Potential.LoadFromWz(ringOpt, ringOptLv, PluginBase.PluginManager.FindWz);\n                if (opt != null)\n                {\n                    g.DrawString(opt.ConvertSummary(), GearGraphics.ItemDetailFont2, Brushes.White, 11, picH);\n                    picH += 16;\n                    hasPart2 = true;\n                }\n            }\n\n            bool hasReduce = Gear.Props.TryGetValue(GearPropType.reduceReq, out value);\n            if (hasReduce && value > 0)\n            {\n                g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.reduceReq, value), GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 11, picH);\n                picH += 16;\n                hasPart2 = true;\n            }\n\n            bool hasTuc = Gear.HasTuc && Gear.Props.TryGetValue(GearPropType.tuc, out value);\n            if (Gear.GetBooleanValue(GearPropType.exceptUpgrade))\n            {\n                g.DrawString(\"无法强化\", GearGraphics.ItemDetailFont, Brushes.White, 11, picH);\n                picH += 16;\n            }\n            else if (Gear.GetBooleanValue(GearPropType.blockUpgradeStarforce))\n            {\n                g.DrawString(\"无法进行星之力强化\", GearGraphics.ItemDetailFont, GearGraphics.BlockRedBrush, 11, picH);\n                picH += 16;\n            }\n            else if (hasTuc && !Gear.GetBooleanValue(GearPropType.blockUpgradeStarforce))\n            {\n                var colorTable = new Dictionary<string, Color>\n                {\n                    { \"c\", GearGraphics.OrangeBrush3Color }\n                };\n                GearGraphics.DrawString(g, \"可升级次数 : \" + value + \"回 #c（可修复次数：0）#\", GearGraphics.ItemDetailFont, colorTable, 13, 256, ref picH, 16);\n                hasPart2 = true;\n            }\n\n            //星星锤子\n            if (hasTuc && Gear.Hammer > -1 && !Gear.GetBooleanValue(GearPropType.blockUpgradeStarforce))\n            {\n                if (Gear.Hammer == 2)\n                {\n                    g.DrawString(\"应用黄金锤精炼\", GearGraphics.ItemDetailFont, Brushes.White, 11, picH);\n                    picH += 16;\n                }\n                if (Gear.Props.TryGetValue(GearPropType.superiorEqp, out value) && value > 0) //极真\n                {\n                    g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.superiorEqp, value), GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 11, picH);\n                    picH += 16;\n                }\n            }\n\n            if (Gear.Props.TryGetValue(GearPropType.CuttableCount, out value) && value > 0) //可使用剪刀\n            {\n                g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.CuttableCount, value), GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3, 11, picH);\n                picH += 16;\n                hasPart2 = true;\n            }\n\n            if (Gear.Props.TryGetValue(GearPropType.limitBreak, out value) && value > 0) //突破上限\n            {\n                g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.limitBreak, value), GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 11, picH);\n                picH += 16;\n                hasPart2 = true;\n            }\n\n            if (hasTuc && Gear.Hammer > -1 && !Gear.GetBooleanValue(GearPropType.blockUpgradeStarforce))\n            {\n                g.DrawString(\"金锤子已提高的强化次数\", GearGraphics.ItemDetailFont, GearGraphics.GoldHammerBrush, 11, picH + 2);\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;\n                g.DrawString(\": \" + Gear.Hammer.ToString() + (Gear.Hammer == 2 ? \"(MAX)\" : null), GearGraphics.TahomaFont, GearGraphics.GoldHammerBrush, 145, picH);\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault;\n                picH += 16;\n                hasPart2 = true;\n            }\n            else if (Gear.GetBooleanValue(GearPropType.blockUpgradeExtraOption))\n            {\n                g.DrawString(\"无法设置/重设额外属性\", GearGraphics.ItemDetailFont, GearGraphics.BlockRedBrush, 11, picH);\n                picH += 16;\n            }\n\n            if (hasTuc && Gear.PlatinumHammer > -1 && !Gear.GetBooleanValue(GearPropType.blockUpgradeStarforce))\n            {\n                g.DrawString(\"白金锤强化次数：\" + Gear.PlatinumHammer, GearGraphics.ItemDetailFont, Brushes.White, 11, picH);\n                picH += 16;\n                hasPart2 = true;\n            }\n\n            picH += 5;\n\n            //绘制浮动属性\n            if ((Gear.VariableStat != null && Gear.VariableStat.Count > 0) || hasReduce)\n            {\n                if (hasPart2) //分割线...\n                {\n                    picH -= 1;\n                    g.DrawImage(res[\"dotline\"].Image, 0, picH);\n                    picH += 8;\n                }\n\n                if (Gear.VariableStat != null && Gear.VariableStat.Count > 0)\n                {\n                    int reqLvl;\n                    Gear.Props.TryGetValue(GearPropType.reqLevel, out reqLvl);\n                    g.DrawString(\"增加各角色等级能力值(\" + reqLvl + \"Lv为止)\", GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3, 130, picH, format);\n                    picH += 20;\n\n                    int reduceLvl;\n                    Gear.Props.TryGetValue(GearPropType.reduceReq, out reduceLvl);\n\n                    int curLevel = charStat == null ? reqLvl : Math.Min(charStat.Level, reqLvl);\n\n                    foreach (var kv in Gear.VariableStat)\n                    {\n                        int dLevel = curLevel - reqLvl + reduceLvl;\n                        //int addVal = (int)Math.Floor(kv.Value * dLevel);\n                        //这里有一个计算上的错误 换方式执行\n                        int addVal = (int)Math.Floor(new decimal(kv.Value) * dLevel);\n                        string text = ItemStringHelper.GetGearPropString(kv.Key, addVal, 1);\n                        text += string.Format(\" ({0:f1} x {1})\", kv.Value, dLevel);\n                        g.DrawString(text, GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3, 10, picH, StringFormat.GenericTypographic);\n                        picH += 20;\n                    }\n\n                    if (hasReduce)\n                    {\n                        g.DrawString(\"升级及强化时，视做\" + reqLvl + \"Lv武器\", GearGraphics.ItemDetailFont, GearGraphics.GrayBrush2, 12, picH, StringFormat.GenericTypographic);\n                        picH += 16;\n                    }\n                }\n            }\n\n            //绘制潜能\n            int optionCount = 0;\n            foreach (Potential potential in Gear.Options)\n            {\n                if (potential != null)\n                {\n                    optionCount++;\n                }\n            }\n\n            if (optionCount > 0)\n            {\n                //分割线3号\n                if (hasPart2)\n                {\n                    g.DrawImage(res[\"dotline\"].Image, 0, picH);\n                    picH += 8;\n                }\n                g.DrawImage(GetAdditionalOptionIcon(Gear.Grade), 9, picH - 1);\n                g.DrawString(\"潜在属性\", GearGraphics.ItemDetailFont, GearGraphics.GetPotentialTextBrush(Gear.Grade), 25, picH);\n                picH += 17;\n                foreach (Potential potential in Gear.Options)\n                {\n                    if (potential != null)\n                    {\n                        g.DrawString(potential.ConvertSummary(), GearGraphics.ItemDetailFont2, Brushes.White, 11, picH);\n                        picH += 16;\n                    }\n                }\n                picH += 5;\n            }\n\n            if (hasSocket)\n            {\n                g.DrawLine(Pens.White, 6, picH, 254, picH);\n                picH += 8;\n                GearGraphics.DrawString(g, ItemStringHelper.GetGearPropString(GearPropType.nActivatedSocket, 1),\n                    GearGraphics.ItemDetailFont, 11, 247, ref picH, 16);\n                picH += 3;\n            }\n\n            //绘制附加潜能\n            int adOptionCount = 0;\n            foreach (Potential potential in Gear.AdditionalOptions)\n            {\n                if (potential != null)\n                {\n                    adOptionCount++;\n                }\n            }\n            if (adOptionCount > 0)\n            {\n                //分割线4号\n                if (hasPart2)\n                {\n                    g.DrawImage(res[\"dotline\"].Image, 0, picH);\n                    picH += 8;\n                }\n                g.DrawImage(GetAdditionalOptionIcon(Gear.AdditionGrade), 8, picH - 1);\n                g.DrawString(\"附加潜能\", GearGraphics.ItemDetailFont, GearGraphics.GetPotentialTextBrush(Gear.AdditionGrade), 26, picH);\n                picH += 17;\n\n                foreach (Potential potential in Gear.AdditionalOptions)\n                {\n                    if (potential != null)\n                    {\n                        g.DrawString(\"+ \" + potential.ConvertSummary(), GearGraphics.ItemDetailFont2, Brushes.White, 11, picH);\n                        picH += 15;\n                    }\n                }\n                picH += 5;\n            }\n\n            if (Gear.Props.TryGetValue(GearPropType.Etuc, out value) && value > 0)\n            {\n                //分割线5号\n                if (hasPart2)\n                {\n                    g.DrawImage(res[\"dotline\"].Image, 0, picH);\n                    picH += 8;\n                }\n                g.DrawString(ItemStringHelper.GetGearPropString(GearPropType.Etuc, value), GearGraphics.ItemDetailFont, Brushes.White, 11, picH);\n                picH += 23;\n            }\n\n            //绘制desc\n            List<string> desc = new List<string>();\n            GearPropType[] descTypes = new GearPropType[]{\n                GearPropType.tradeAvailable,\n                GearPropType.accountShareTag,\n                GearPropType.jokerToSetItem,\n                GearPropType.colorvar,\n            };\n            foreach (GearPropType type in descTypes)\n            {\n                if (Gear.Props.TryGetValue(type, out value) && value != 0)\n                {\n                    desc.Add(ItemStringHelper.GetGearPropString(type, value));\n                }\n            }\n\n            //绘制倾向\n            if (Gear.State == GearState.itemList)\n            {\n                StringBuilder incline = new StringBuilder();\n                GearPropType[] inclineTypes = new GearPropType[]{\n                    GearPropType.charismaEXP,\n                    GearPropType.senseEXP,\n                    GearPropType.insightEXP,\n                    GearPropType.willEXP,\n                    GearPropType.craftEXP,\n                    GearPropType.charmEXP };\n\n                string[] inclineString = new string[]{\n                    \"领导力\",\"感性\",\"洞察力\",\"意志\",\"手技\",\"魅力\"};\n\n                for (int i = 0; i < inclineTypes.Length; i++)\n                {\n                    if (Gear.Props.TryGetValue(inclineTypes[i], out value) && value > 0)\n                    {\n                        if (incline.Length > 0)\n                        {\n                            incline.Append(\", \");\n                        }\n                        incline.Append(inclineString[i]).Append(value);\n                    }\n                }\n\n                if (incline.Length > 0)\n                {\n                    desc.Add($\"\\n #c装备时限1次获得{incline}的经验值.(每天限制,超过最大值时除外)#\");\n                }\n            }\n\n            //判断是否绘制徽章\n            Wz_Node medalResNode = null;\n            bool willDrawMedalTag = this.ShowMedalTag && this.Gear.Sample.Bitmap == null\n                && this.Gear.Props.TryGetValue(GearPropType.medalTag, out value)\n                && this.TryGetMedalResource(value, out medalResNode);\n\n            //判断是否绘制技能desc\n            string levelDesc = null;\n            if (Gear.FixLevel && Gear.Props.TryGetValue(GearPropType.level, out value))\n            {\n                var levelInfo = Gear.Levels.FirstOrDefault(info => info.Level == value);\n                if (levelInfo != null && levelInfo.Prob == levelInfo.ProbTotal && !string.IsNullOrEmpty(levelInfo.HS))\n                {\n                    levelDesc = sr[levelInfo.HS];\n                }\n            }\n\n            if (!string.IsNullOrEmpty(sr.Desc) || !string.IsNullOrEmpty(levelDesc) || desc.Count > 0 || Gear.Sample.Bitmap != null || willDrawMedalTag)\n            {\n                //分割线4号\n                if (hasPart2)\n                {\n                    g.DrawImage(res[\"dotline\"].Image, 0, picH);\n                    picH += 8;\n                }\n                if (Gear.Sample.Bitmap != null)\n                {\n                    g.DrawImage(Gear.Sample.Bitmap, (bitmap.Width - Gear.Sample.Bitmap.Width) / 2, picH);\n                    picH += Gear.Sample.Bitmap.Height;\n                    picH += 4;\n                }\n                if (medalResNode != null)\n                {\n                    GearGraphics.DrawNameTag(g, medalResNode, sr.Name, bitmap.Width, ref picH);\n                    picH += 4;\n                }\n                if (!string.IsNullOrEmpty(sr.Desc))\n                {\n                    GearGraphics.DrawString(g, sr.Desc, GearGraphics.ItemDetailFont2, 11, 245, ref picH, 16);\n                }\n                if (!string.IsNullOrEmpty(levelDesc))\n                {\n                    GearGraphics.DrawString(g, \" \" + levelDesc, GearGraphics.ItemDetailFont2, 11, 245, ref picH, 16);\n                }\n                foreach (string str in desc)\n                {\n                    GearGraphics.DrawString(g, str, GearGraphics.ItemDetailFont, 11, 245, ref picH, 16);\n                }\n                picH += 5;\n            }\n\n            foreach (KeyValuePair<int, ExclusiveEquip> kv in CharaSimLoader.LoadedExclusiveEquips)\n            {\n                if (kv.Value.Items.Contains(Gear.ItemID))\n                {\n                    if (hasPart2)\n                    {\n                        g.DrawImage(res[\"dotline\"].Image, 0, picH);\n                        picH += 8;\n                    }\n\n                    string exclusiveEquip;\n                    if (!string.IsNullOrEmpty(kv.Value.Info))\n                    {\n                        exclusiveEquip = \"#c\" + kv.Value.Info + \"类道具无法重复使用。#\";\n                    }\n                    else\n                    {\n                        List<string> itemNames = new List<string>();\n                        foreach (int itemID in kv.Value.Items)\n                        {\n                            StringResult sr2;\n                            if (this.StringLinker == null || !this.StringLinker.StringEqp.TryGetValue(itemID, out sr2))\n                            {\n                                sr2 = new StringResult();\n                                sr2.Name = \"(null)\";\n                            }\n                            itemNames.Add(sr2.Name);\n                        }\n                        exclusiveEquip = \"#c无法重复装备\" + string.Join(\", \", itemNames) + \"。#\";\n                    }\n                    GearGraphics.DrawString(g, exclusiveEquip, GearGraphics.ItemDetailFont, 11, 246, ref picH, 16);\n                    break;\n                }\n            }\n\n            picH += 2;\n            format.Dispose();\n            g.Dispose();\n            return bitmap;\n        }\n\n        private Bitmap RenderAddition(out int picHeight)\n        {\n            Bitmap addBitmap = null;\n            picHeight = 0;\n            if (Gear.Additions.Count > 0 && !Gear.AdditionHideDesc)\n            {\n                addBitmap = new Bitmap(261, DefaultPicHeight);\n                Graphics g = Graphics.FromImage(addBitmap);\n                StringBuilder sb = new StringBuilder();\n                foreach (Addition addition in Gear.Additions)\n                {\n                    string conString = addition.GetConString(), propString = addition.GetPropString();\n                    if (!string.IsNullOrEmpty(conString) || !string.IsNullOrEmpty(propString))\n                    {\n                        sb.Append(\"- \");\n                        if (!string.IsNullOrEmpty(conString))\n                            sb.AppendLine(conString);\n                        if (!string.IsNullOrEmpty(propString))\n                            sb.AppendLine(propString);\n                        sb.AppendLine();\n                    }\n                }\n                if (sb.Length > 0)\n                {\n                    picHeight = 10;\n                    GearGraphics.DrawString(g, sb.ToString(), GearGraphics.ItemDetailFont, 10, 250, ref picHeight, 16);\n                }\n                g.Dispose();\n            }\n            return addBitmap;\n        }\n\n        private Bitmap RenderSetItem(out int picHeight)\n        {\n            Bitmap setBitmap = null;\n            int setID;\n            picHeight = 0;\n            if (Gear.Props.TryGetValue(GearPropType.setItemID, out setID))\n            {\n                SetItem setItem;\n                if (!CharaSimLoader.LoadedSetItems.TryGetValue(setID, out setItem))\n                    return null;\n\n                TooltipRender renderer = this.SetItemRender;\n                if (renderer == null)\n                {\n                    var defaultRenderer = new SetItemTooltipRender();\n                    defaultRenderer.StringLinker = this.StringLinker;\n                    defaultRenderer.ShowObjectID = false;\n                    renderer = defaultRenderer;\n                }\n\n                renderer.TargetItem = setItem;\n                setBitmap = renderer.Render();\n                if (setBitmap != null)\n                    picHeight = setBitmap.Height;\n            }\n            return setBitmap;\n        }\n\n        private Bitmap RenderLevelOrSealed(out int picHeight)\n        {\n            Bitmap levelOrSealed = null;\n            Graphics g = null;\n            StringFormat format = new StringFormat();\n            format.Alignment = StringAlignment.Center;\n            picHeight = 0;\n            if (Gear.Levels != null)\n            {\n                if (levelOrSealed == null)\n                {\n                    levelOrSealed = new Bitmap(261, DefaultPicHeight);\n                    g = Graphics.FromImage(levelOrSealed);\n                }\n                picHeight += 13;\n                g.DrawString(\"装备成长属性\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 130, picHeight, format);\n                picHeight += 16;\n                if (Gear.FixLevel)\n                {\n                    g.DrawString(\"[装备获取时固定等级]\", GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush, 130, picHeight, format);\n                    picHeight += 16;\n                }\n\n                for (int i = 0; i < Gear.Levels.Count; i++)\n                {\n                    var info = Gear.Levels[i];\n                    g.DrawString(\"等级 \" + info.Level + (i >= Gear.Levels.Count - 1 ? \"(MAX)\" : null), GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 10, picHeight);\n                    picHeight += 16;\n                    foreach (var kv in info.BonusProps)\n                    {\n                        GearLevelInfo.Range range = kv.Value;\n\n                        string propString = ItemStringHelper.GetGearPropString(kv.Key, kv.Value.Min);\n                        if (range.Max != range.Min)\n                        {\n                            propString += \" ~ \" + kv.Value.Max + (propString.EndsWith(\"%\") ? \"%\" : null);\n                        }\n                        g.DrawString(propString, GearGraphics.ItemDetailFont, Brushes.White, 10, picHeight);\n                        picHeight += 16;\n                    }\n                    if (info.Skills.Count > 0)\n                    {\n                        string title = string.Format(\"有 {2:P2}({0}/{1}) 的几率获得技能 :\", info.Prob, info.ProbTotal, info.Prob * 1.0 / info.ProbTotal);\n                        g.DrawString(title, GearGraphics.ItemDetailFont, Brushes.White, 10, picHeight);\n                        picHeight += 16;\n                        foreach (var kv in info.Skills)\n                        {\n                            StringResult sr = null;\n                            if (this.StringLinker != null)\n                            {\n                                this.StringLinker.StringSkill.TryGetValue(kv.Key, out sr);\n                            }\n                            string text = string.Format(\"{0}({1}) +{2}\", sr == null ? null : sr.Name, kv.Key, kv.Value);\n                            g.DrawString(text, GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush, 16, picHeight);\n                            picHeight += 16;\n                        }\n                    }\n                    if (info.EquipmentSkills.Count > 0)\n                    {\n                        string title;\n                        if (info.Prob < info.ProbTotal)\n                        {\n                            title = string.Format(\"有 {2:P2}({0}/{1}) 的几率装备时获得技能 :\", info.Prob, info.ProbTotal, info.Prob * 1.0 / info.ProbTotal);\n                        }\n                        else\n                        {\n                            title = \"装备时获得技能 :\";\n                        }\n                        g.DrawString(title, GearGraphics.ItemDetailFont, Brushes.White, 10, picHeight);\n                        picHeight += 16;\n                        foreach (var kv in info.EquipmentSkills)\n                        {\n                            StringResult sr = null;\n                            if (this.StringLinker != null)\n                            {\n                                this.StringLinker.StringSkill.TryGetValue(kv.Key, out sr);\n                            }\n                            string text = string.Format(\"{0}({1}) Lv.{2}\", sr == null ? null : sr.Name, kv.Key, kv.Value);\n                            g.DrawString(text, GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush, 16, picHeight);\n                            picHeight += 16;\n                        }\n                    }\n                    if (info.Exp > 0)\n                    {\n                        g.DrawString(\"经验成长率 : \" + info.Exp + \"%\", GearGraphics.ItemDetailFont, Brushes.White, 10, picHeight);\n                        picHeight += 16;\n                    }\n\n                    picHeight += 2;\n                }\n            }\n\n            if (Gear.Seals != null)\n            {\n                if (levelOrSealed == null)\n                {\n                    levelOrSealed = new Bitmap(261, DefaultPicHeight);\n                    g = Graphics.FromImage(levelOrSealed);\n                }\n                picHeight += 13;\n                g.DrawString(\"封印解除属性\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 130, picHeight, format);\n                picHeight += 16;\n                for (int i = 0; i < Gear.Seals.Count; i++)\n                {\n                    var info = Gear.Seals[i];\n\n                    g.DrawString(\"等级 \" + info.Level + (i >= Gear.Seals.Count - 1 ? \"(MAX)\" : null), GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 10, picHeight);\n                    picHeight += 16;\n                    var props = this.IsCombineProperties ? Gear.CombineProperties(info.BonusProps) : info.BonusProps;\n                    foreach (var kv in props)\n                    {\n                        string propString = ItemStringHelper.GetGearPropString(kv.Key, kv.Value);\n                        g.DrawString(propString, GearGraphics.ItemDetailFont, Brushes.White, 10, picHeight);\n                        picHeight += 16;\n                    }\n                    if (info.HasIcon)\n                    {\n                        Bitmap icon = info.Icon.Bitmap ?? info.IconRaw.Bitmap;\n                        if (icon != null)\n                        {\n                            g.DrawString(\"图标 : \", GearGraphics.ItemDetailFont, Brushes.White, 10, picHeight + icon.Height / 2 - 6);\n                            g.DrawImage(icon, 52, picHeight);\n                            picHeight += icon.Height;\n                        }\n                    }\n                    if (info.Exp > 0)\n                    {\n                        g.DrawString(\"经验成长率 : \" + info.Exp + \"%\", GearGraphics.ItemDetailFont, Brushes.White, 10, picHeight);\n                        picHeight += 16;\n                    }\n                    picHeight += 2;\n                }\n            }\n\n\n            format.Dispose();\n            if (g != null)\n            {\n                g.Dispose();\n                picHeight += 13;\n            }\n            return levelOrSealed;\n        }\n\n        private Bitmap RenderGenesisSkills(out int picHeight)\n        {\n            Bitmap genesisBitmap = null;\n            picHeight = 0;\n            if (Gear.IsGenesisWeapon)\n            {\n                genesisBitmap = new Bitmap(261, DefaultPicHeight);\n                Graphics g = Graphics.FromImage(genesisBitmap);\n                picHeight = 13;\n                foreach (var skillID in new[] { 80002632, 80002633 })\n                {\n                    string skillName;\n                    if (this.StringLinker?.StringSkill.TryGetValue(skillID, out var sr) ?? false && sr.Name != null)\n                    {\n                        skillName = sr.Name;\n                    }\n                    else\n                    {\n                        skillName = skillID.ToString();\n                    }\n                    g.DrawString($\"可使用<{skillName}>\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 10, picHeight);\n                    picHeight += 16;\n                }\n                picHeight += 9;\n                g.Dispose();\n            }\n            return genesisBitmap;\n        }\n\n        private void FillRect(Graphics g, TextureBrush brush, int x, int y0, int y1)\n        {\n            brush.ResetTransform();\n            brush.TranslateTransform(x, y0);\n            g.FillRectangle(brush, x, y0, brush.Image.Width, y1 - y0);\n        }\n\n        private List<string> GetGearAttributeString()\n        {\n            int value;\n            List<string> tags = new List<string>();\n\n            if (Gear.Props.TryGetValue(GearPropType.only, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.only, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.tradeBlock, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.tradeBlock, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.mintable, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.mintable, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.abilityTimeLimited, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.abilityTimeLimited, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.equipTradeBlock, out value) && value != 0)\n            {\n                if (Gear.State == GearState.itemList)\n                {\n                    tags.Add(ItemStringHelper.GetGearPropString(GearPropType.equipTradeBlock, value));\n                }\n                else\n                {\n                    string tradeBlock = ItemStringHelper.GetGearPropString(GearPropType.tradeBlock, 1);\n                    if (!tags.Contains(tradeBlock))\n                        tags.Add(tradeBlock);\n                }\n            }\n            if (Gear.Props.TryGetValue(GearPropType.accountSharable, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.accountSharable, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.blockGoldHammer, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.blockGoldHammer, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.noPotential, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.noPotential, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.fixedPotential, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.fixedPotential, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.onlyEquip, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.onlyEquip, value));\n            }\n            if (Gear.Props.TryGetValue(GearPropType.notExtend, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetGearPropString(GearPropType.notExtend, value));\n            }\n\n            return tags;\n        }\n\n        private Bitmap GetAlienStoneIcon()\n        {\n            if (Gear.AlienStoneSlot == null)\n            {\n                return Resource.ToolTip_Equip_AlienStone_Empty;\n            }\n            else\n            {\n                switch (Gear.AlienStoneSlot.Grade)\n                {\n                    case AlienStoneGrade.Normal:\n                        return Resource.ToolTip_Equip_AlienStone_Normal;\n                    case AlienStoneGrade.Rare:\n                        return Resource.ToolTip_Equip_AlienStone_Rare;\n                    case AlienStoneGrade.Epic:\n                        return Resource.ToolTip_Equip_AlienStone_Epic;\n                    case AlienStoneGrade.Unique:\n                        return Resource.ToolTip_Equip_AlienStone_Unique;\n                    case AlienStoneGrade.Legendary:\n                        return Resource.ToolTip_Equip_AlienStone_Legendary;\n                    default:\n                        return null;\n                }\n            }\n        }\n\n        private void DrawGearReq(Graphics g, int x, int y)\n        {\n            int value;\n            bool can;\n            NumberType type;\n            Size size;\n            //需求等级\n            this.Gear.Props.TryGetValue(GearPropType.reqLevel, out value);\n            {\n                int reduceReq;\n                if (this.Gear.Props.TryGetValue(GearPropType.reduceReq, out reduceReq))\n                {\n                    value = Math.Max(0, value - reduceReq);\n                }\n            }\n            can = this.charStat == null || this.charStat.Level >= value;\n            type = GetReqType(can, value);\n            g.DrawImage(FindReqImage(type, \"reqLEV\", out size), x, y);\n            DrawReqNum(g, value.ToString().PadLeft(3), (type == NumberType.Can ? NumberType.YellowNumber : type), x + 54, y, StringAlignment.Near);\n\n            //需求人气\n            this.Gear.Props.TryGetValue(GearPropType.reqPOP, out value);\n            can = this.charStat == null || this.charStat.Pop >= value;\n            type = GetReqType(can, value);\n            if (value > 0)\n            {\n                g.DrawImage(FindReqImage(type, \"reqPOP\", out size), x + 80, y);\n                DrawReqNum(g, value.ToString(\"D3\"), type, x + 80 + 54, y, StringAlignment.Near);\n            }\n\n            y += 15;\n\n            //需求力量\n            this.Gear.Props.TryGetValue(GearPropType.reqSTR, out value);\n            can = this.charStat == null || this.charStat.Strength.GetSum() >= value;\n            type = GetReqType(can, value);\n            g.DrawImage(FindReqImage(type, \"reqSTR\", out size), x, y);\n            DrawReqNum(g, value.ToString(\"D3\"), type, x + 54, y, StringAlignment.Near);\n\n\n            //需求运气\n            this.Gear.Props.TryGetValue(GearPropType.reqLUK, out value);\n            can = this.charStat == null || this.charStat.Luck.GetSum() >= value;\n            type = GetReqType(can, value);\n            g.DrawImage(FindReqImage(type, \"reqLUK\", out size), x + 80, y);\n            DrawReqNum(g, value.ToString(\"D3\"), type, x + 80 + 54, y, StringAlignment.Near);\n\n            y += 9;\n\n            //需求敏捷\n            this.Gear.Props.TryGetValue(GearPropType.reqDEX, out value);\n            can = this.charStat == null || this.charStat.Dexterity.GetSum() >= value;\n            type = GetReqType(can, value);\n            g.DrawImage(FindReqImage(type, \"reqDEX\", out size), x, y);\n            DrawReqNum(g, value.ToString(\"D3\"), type, x + 54, y, StringAlignment.Near);\n\n            //需求智力\n            this.Gear.Props.TryGetValue(GearPropType.reqINT, out value);\n            can = this.charStat == null || this.charStat.Intelligence.GetSum() >= value;\n            type = GetReqType(can, value);\n            g.DrawImage(FindReqImage(type, \"reqINT\", out size), x + 80, y);\n            DrawReqNum(g, value.ToString(\"D3\"), type, x + 80 + 54, y, StringAlignment.Near);\n        }\n\n        private void DrawPropDiffEx(Graphics g, int x, int y)\n        {\n            int value;\n            string numValue;\n            //防御\n            g.DrawImage(Resource.UIToolTip_img_Item_Equip_Summary_icon_pdd, x, y);\n            x += 62;\n            DrawReqNum(g, \"0\", NumberType.LookAhead, x - 5, y + 6, StringAlignment.Far);\n\n            ////魔防\n            //g.DrawImage(Resource.UIToolTip_img_Item_Equip_Summary_icon_mdd, x, y);\n            //x += 62;\n            //DrawReqNum(g, \"0\", NumberType.LookAhead, x - 5, y + 6, StringAlignment.Far);\n\n            //boss伤\n            g.DrawImage(Resource.UIToolTip_img_Item_Equip_Summary_icon_bdr, x, y);\n            x += 62;\n            this.Gear.Props.TryGetValue(GearPropType.bdR, out value);\n            numValue = (value > 0 ? \"+ \" : null) + value + \" % \";\n            DrawReqNum(g, numValue, NumberType.LookAhead, x - 5 + 3, y + 6, StringAlignment.Far);\n\n            //无视防御\n            g.DrawImage(Resource.UIToolTip_img_Item_Equip_Summary_icon_igpddr, x, y);\n            x += 62;\n            this.Gear.Props.TryGetValue(GearPropType.imdR, out value);\n            numValue = (value > 0 ? \"+ \" : null) + value + \" % \";\n            DrawReqNum(g, numValue, NumberType.LookAhead, x - 5 - 1, y + 6, StringAlignment.Far);\n        }\n\n        private void DrawJobReq(Graphics g, ref int picH)\n        {\n            int value;\n            string extraReq = ItemStringHelper.GetExtraJobReqString(Gear.type);\n            if (extraReq == null && Gear.Props.TryGetValue(GearPropType.reqSpecJob, out value))\n            {\n                extraReq = ItemStringHelper.GetExtraJobReqString(value);\n            }\n            if (extraReq == null && Gear.ReqSpecJobs.Count > 0)\n            {\n                // apply req order fix for CMS only\n                int[] specJobsList1 = new[] { 2, 22, 12, 32, 172 };\n                if (new HashSet<int>(specJobsList1).SetEquals(Gear.ReqSpecJobs))\n                {\n                    extraReq = ItemStringHelper.GetExtraJobReqString(specJobsList1);\n                }\n                else\n                {\n                    extraReq = ItemStringHelper.GetExtraJobReqString(Gear.ReqSpecJobs);\n                }\n            }\n\n            Image jobImage = null;\n            int extraReqWidth = 216;\n            if (extraReq == null)\n            {\n                jobImage = Resource.UIToolTip_img_Item_Equip_Job_normal;\n            }\n            else\n            {\n                // measure jobReq desc\n                // Actually we use GearGraphics.DrawPlainText to render extraReq, the meatured lines may not accurate.\n                using var extraReqFmt = new StringFormat();\n                extraReqFmt.Alignment = StringAlignment.Center;\n                g.MeasureString(extraReq, GearGraphics.ItemDetailFont, new SizeF(extraReqWidth, short.MaxValue), extraReqFmt, out _, out var lines);\n                jobImage = lines == 1 ? Resource.UIToolTip_img_Item_Equip_Job_expand : Resource.UIToolTip_img_Item_Equip_Job_expand2;\n            }\n            g.DrawImage(jobImage, 12, picH);\n\n            int reqJob;\n            Gear.Props.TryGetValue(GearPropType.reqJob, out reqJob);\n            int[] origin = new int[] { 9, 4, 42, 4, 78, 5, 124, 4, 165, 5, 200, 5 };\n            int[] origin2 = new int[] { 10, 6, 44, 6, 79, 6, 126, 6, 166, 6, 201, 6 };\n            for (int i = 0; i <= 5; i++)\n            {\n                bool enable;\n                if (i == 0)\n                {\n                    enable = reqJob <= 0;\n                    if (reqJob == 0) reqJob = 0b11111;\n                    if (reqJob == -1) reqJob = 0b00000;\n                }\n                else\n                {\n                    enable = (reqJob & (1 << (i - 1))) != 0;\n                }\n                if (enable)\n                {\n                    enable = this.charStat == null || Character.CheckJobReq(this.charStat.Job, i);\n                    Image jobImage2 = Resource.ResourceManager.GetObject(\"UIToolTip_img_Item_Equip_Job_\" + (enable ? \"enable\" : \"disable\") + \"_\" + i.ToString()) as Image;\n                    if (jobImage != null)\n                    {\n                        if (enable)\n                            g.DrawImage(jobImage2, 12 + origin[i * 2], picH + origin[i * 2 + 1]);\n                        else\n                            g.DrawImage(jobImage2, 12 + origin2[i * 2], picH + origin2[i * 2 + 1]);\n                    }\n                }\n            }\n            if (extraReq != null)\n            {\n                // ignore yaxis.\n                int tempY = picH + 24;\n                GearGraphics.DrawPlainText(g, extraReq, GearGraphics.ItemDetailFont, GearGraphics.OrangeBrush3Color, \n                    130 - extraReqWidth / 2, 130 + extraReqWidth / 2, ref tempY, 16, Text.TextAlignment.Center);\n            }\n            picH += jobImage.Height + 9;\n        }\n\n        private Image FindReqImage(NumberType type, string req, out Size size)\n        {\n            Image image = Resource.ResourceManager.GetObject(\"UIToolTip_img_Item_Equip_\" + type.ToString() + \"_\" + req) as Image;\n            if (image != null)\n                size = image.Size;\n            else\n                size = Size.Empty;\n            return image;\n        }\n\n        private void DrawStar(Graphics g, ref int picH)\n        {\n            if (Gear.Star > 0)\n            {\n                int totalWidth = Gear.Star * 10 + (Gear.Star / 5 - 1) * 6;\n                int dx = 130 - totalWidth / 2;\n                for (int i = 0; i < Gear.Star; i++)\n                {\n                    g.DrawImage(Resource.UIToolTip_img_Item_Equip_Star_Star, dx, picH);\n                    dx += 10;\n                    if (i > 0 && i % 5 == 4)\n                    {\n                        dx += 6;\n                    }\n                }\n                picH += 18;\n            }\n        }\n\n        private void DrawStar2(Graphics g, ref int picH)\n        {\n            int maxStar = Gear.GetMaxStar();\n            if (maxStar > 0)\n            {\n                for (int i = 0; i < maxStar; i += 15)\n                {\n                    int starLine = Math.Min(maxStar - i, 15);\n                    int totalWidth = starLine * 10 + (starLine / 5 - 1) * 6;\n                    int dx = 130 - totalWidth / 2;\n                    for (int j = 0; j < starLine; j++)\n                    {\n                        g.DrawImage((i + j < Gear.Star) ?\n                            Resource.UIToolTip_img_Item_Equip_Star_Star : Resource.UIToolTip_img_Item_Equip_Star_Star0,\n                            dx, picH);\n                        dx += 10;\n                        if (j > 0 && j % 5 == 4)\n                        {\n                            dx += 6;\n                        }\n                    }\n                    picH += 18;\n                }\n                picH -= 1;\n            }\n        }\n\n        private NumberType GetReqType(bool can, int reqValue)\n        {\n            if (reqValue <= 0)\n                return NumberType.Disabled;\n            if (can)\n                return NumberType.Can;\n            else\n                return NumberType.Cannot;\n        }\n\n        private void DrawReqNum(Graphics g, string numString, NumberType type, int x, int y, StringAlignment align)\n        {\n            if (g == null || numString == null || align == StringAlignment.Center)\n                return;\n            int spaceWidth = type == NumberType.LookAhead ? 3 : 6;\n            bool near = align == StringAlignment.Near;\n\n            for (int i = 0; i < numString.Length; i++)\n            {\n                char c = near ? numString[i] : numString[numString.Length - i - 1];\n                Image image = null;\n                Point origin = Point.Empty;\n                switch (c)\n                {\n                    case ' ':\n                        break;\n                    case '+':\n                        image = Resource.ResourceManager.GetObject(\"UIToolTip_img_Item_Equip_\" + type.ToString() + \"_\" + \"plus\") as Image;\n                        break;\n                    case '-':\n                        image = Resource.ResourceManager.GetObject(\"UIToolTip_img_Item_Equip_\" + type.ToString() + \"_\" + \"minus\") as Image;\n                        origin.Y = 3;\n                        break;\n                    case '%':\n                        image = Resource.ResourceManager.GetObject(\"UIToolTip_img_Item_Equip_\" + type.ToString() + \"_\" + \"percent\") as Image;\n                        break;\n                    default:\n                        if ('0' <= c && c <= '9')\n                        {\n                            image = Resource.ResourceManager.GetObject(\"UIToolTip_img_Item_Equip_\" + type.ToString() + \"_\" + c) as Image;\n                            if (c == '1' && type == NumberType.LookAhead)\n                            {\n                                origin.X = 1;\n                            }\n                        }\n                        break;\n                }\n\n                if (image != null)\n                {\n                    if (near)\n                    {\n                        g.DrawImage(image, x + origin.X, y + origin.Y);\n                        x += image.Width + origin.X + 1;\n                    }\n                    else\n                    {\n                        x -= image.Width + origin.X;\n                        g.DrawImage(image, x + origin.X, y + origin.Y);\n                        x -= 1;\n                    }\n                }\n                else //空格补位\n                {\n                    x += spaceWidth * (near ? 1 : -1);\n                }\n            }\n        }\n\n        private Image GetAdditionalOptionIcon(GearGrade grade)\n        {\n            switch (grade)\n            {\n                default:\n                case GearGrade.B: return Resource.AdditionalOptionTooltip_rare;\n                case GearGrade.A: return Resource.AdditionalOptionTooltip_epic;\n                case GearGrade.S: return Resource.AdditionalOptionTooltip_unique;\n                case GearGrade.SS: return Resource.AdditionalOptionTooltip_legendary;\n            }\n        }\n\n        private bool TryGetMedalResource(int medalTag, out Wz_Node resNode)\n        {\n            resNode = PluginBase.PluginManager.FindWz(\"UI/NameTag.img/medal/\" + medalTag);\n            return resNode != null;\n        }\n\n        private enum NumberType\n        {\n            Can,\n            Cannot,\n            Disabled,\n            LookAhead,\n            YellowNumber,\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/ItemMouseEventArgs.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Windows.Forms;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class ItemMouseEventArgs : MouseEventArgs\n    {\n        public ItemMouseEventArgs(MouseEventArgs e, ItemBase item)\n            : this(e.Button, e.Clicks, e.X, e.Y, e.Delta, item)\n        {\n        }\n\n        public ItemMouseEventArgs(MouseButtons button, int clicks, int x, int y, int delta, ItemBase item) :\n            base(button, clicks, x, y, delta)\n        {\n            this.item = item;\n        }\n\n        private ItemBase item;\n\n        public ItemBase Item\n        {\n            get { return item; }\n            set { item = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/ItemMouseEventHandler.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public delegate void ItemMouseEventHandler(object sender, ItemMouseEventArgs e);\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/ItemTooltipRender.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing CharaSimResource;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class ItemTooltipRender:TooltipRender\n    {\n        public ItemTooltipRender()\n        {\n        }\n\n        private Item item;\n\n        public Item Item\n        {\n            get { return item; }\n            set { item = value; }\n        }\n\n        public override Bitmap Render()\n        {\n            if (this.item == null)\n            {\n                return null;\n            }\n            int picHeight, iconY;\n            Bitmap originBmp = renderItem(out picHeight, out iconY);\n            Bitmap tooltip = new Bitmap(290, picHeight);\n            Graphics g = Graphics.FromImage(tooltip);\n\n            //复制图像\n            g.FillRectangle(GearGraphics.GearBackBrush, 2, 2, 286, picHeight - 4);\n            g.CompositingMode = CompositingMode.SourceCopy;\n            g.FillRectangle(GearGraphics.GearIconBackBrush, 14, iconY, 68, 68);\n            g.CompositingMode = CompositingMode.SourceOver;\n            g.DrawImage(originBmp, 0, 0, new Rectangle(0, 0, 290, picHeight - 2), GraphicsUnit.Pixel);\n            //绘制外边框\n            g.DrawLines(GearGraphics.GearBackPen, GearGraphics.GetBorderPath(0, 290, picHeight));\n\n            //GearGraphics.DrawGearDetailNumber(g, 2, 2, item.ItemID.ToString(\"d8\"), true);\n\n            g.Dispose();\n            return tooltip;\n        }\n\n        private Bitmap renderItem(out int picHeight, out int iconY)\n        {\n            Bitmap tooltip = new Bitmap(290, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(tooltip);\n            StringFormat format = new StringFormat();\n            long value;\n            format.Alignment = StringAlignment.Center;\n            picHeight = 10;\n            iconY = 32;\n\n            //物品标题\n            StringResult sr;\n            if (StringLinker == null || !StringLinker.StringItem.TryGetValue(item.ItemID, out sr))\n            {\n                sr = new StringResult();\n                sr.Name = \"(null)\";\n            }\n            string gearName = sr.Name;\n            g.DrawString(gearName, GearGraphics.ItemNameFont, Brushes.White, 145, picHeight, format);//绘制装备名称\n            picHeight += 21;\n            string attr = GetItemAttributeString();\n            if (!string.IsNullOrEmpty(attr))\n            {\n                g.DrawString(attr, GearGraphics.ItemDetailFont, GearGraphics.GearNameBrushC, 145, picHeight, format);\n                iconY += 19;\n            }\n\n            //绘制图标\n            if (item.Icon.Bitmap != null)\n            {\n                g.DrawImage(GearGraphics.EnlargeBitmap(item.Icon.Bitmap),\n                    14 + (1 - item.Icon.Origin.X) * 2,\n                    iconY + (33 - item.Icon.Origin.Y) * 2);\n            }\n            if (item.Cash)\n            {\n                g.DrawImage(GearGraphics.EnlargeBitmap(Resource.CashItem_0),\n                    14 + 68 - 26,\n                    iconY + 68 - 26);\n            }\n\n            picHeight = iconY;\n            if (item.Props.TryGetValue(ItemPropType.reqLevel, out value))\n            {\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;\n                g.PixelOffsetMode = PixelOffsetMode.HighQuality;\n                g.DrawString(\"要求等级 : \" + value, GearGraphics.ItemReqLevelFont, Brushes.White, 92, picHeight);\n                picHeight += 15;\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault;\n            }\n            else\n            {\n                picHeight += 3;\n            }\n            if (!string.IsNullOrEmpty(sr.Desc))\n            {\n                GearGraphics.DrawString(g, sr.Desc + sr.AutoDesc, GearGraphics.ItemDetailFont, 92, 272, ref picHeight, 16);\n            }\n            if (item.Props.TryGetValue(ItemPropType.tradeAvailable, out value) && value > 0)\n            {\n                attr = ItemStringHelper.GetItemPropString(ItemPropType.tradeAvailable, value);\n                if (!string.IsNullOrEmpty(attr))\n                    GearGraphics.DrawString(g, \"#c\" + attr + \"#\", GearGraphics.ItemDetailFont, 92, 272, ref picHeight, 16);\n            }\n            picHeight = Math.Max(iconY + 84, iconY + 16 * (int)Math.Ceiling((picHeight - iconY) / 16.0));\n            return tooltip;\n        }\n\n        private string GetItemAttributeString()\n        {\n            long value;\n            List<string> tags = new List<string>();\n\n            if (item.Props.TryGetValue(ItemPropType.quest, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.quest, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.pquest, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.pquest, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.only, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.only, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.tradeBlock, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.tradeBlock, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.accountSharable, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.accountSharable, value));\n            }\n\n            return tags.Count > 0 ? string.Join(\", \", tags.ToArray()) : null;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/ItemTooltipRender2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing Resource = CharaSimResource.Resource;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class ItemTooltipRender2 : TooltipRender\n    {\n        public ItemTooltipRender2()\n        {\n        }\n\n        private Item item;\n\n        public Item Item\n        {\n            get { return item; }\n            set { item = value; }\n        }\n\n        public override object TargetItem\n        {\n            get\n            {\n                return this.item;\n            }\n            set\n            {\n                this.item = value as Item;\n            }\n        }\n\n\n        public bool LinkRecipeInfo { get; set; }\n        public bool LinkRecipeItem { get; set; }\n        public bool ShowNickTag { get; set; }\n        public bool ShowDamageSkin { get; set; }\n        public bool ShowDamageSkinID { get; set; }\n        public bool UseMiniSizeDamageSkin { get; set; }\n        public bool AlwaysUseMseaFormatDamageSkin { get; set; }\n        public bool DisplayUnitOnSingleLine { get; set; }\n        public long DamageSkinNumber { get; set; }\n\n        public TooltipRender LinkRecipeInfoRender { get; set; }\n        public TooltipRender LinkRecipeGearRender { get; set; }\n        public TooltipRender LinkRecipeItemRender { get; set; }\n        public TooltipRender FamiliarRender { get; set; }\n        public TooltipRender SetItemRender { get; set; }\n        public TooltipRender DamageSkinRender { get; set; }\n\n        public override Bitmap Render()\n        {\n            if (this.item == null)\n            {\n                return null;\n            }\n            //绘制道具\n            int picHeight;\n            Bitmap itemBmp = RenderItem(out picHeight);\n            List<Bitmap> recipeInfoBmps = new();\n            List<Bitmap> recipeItemBmps = new();\n            Bitmap setItemBmp = null;\n\n            //图纸相关\n            if (this.item.Recipes.Count > 0)\n            {\n                foreach (int recipeID in this.item.Recipes)\n                {\n                    int recipeSkillID = recipeID / 10000;\n                    Recipe recipe = null;\n                    //寻找配方\n                    Wz_Node recipeNode = PluginBase.PluginManager.FindWz(string.Format(@\"Skill\\Recipe_{0}.img\\{1}\", recipeSkillID, recipeID));\n                    if (recipeNode != null)\n                    {\n                        recipe = Recipe.CreateFromNode(recipeNode);\n                    }\n                    //生成配方图像\n                    if (recipe != null)\n                    {\n                        if (this.LinkRecipeInfo)\n                        {\n                            recipeInfoBmps.Add(RenderLinkRecipeInfo(recipe));\n                        }\n\n                        if (this.LinkRecipeItem)\n                        {\n                            int itemID = recipe.MainTargetItemID;\n                            int itemIDClass = itemID / 1000000;\n                            if (itemIDClass == 1) //通过ID寻找装备\n                            {\n                                Wz_Node charaWz = PluginManager.FindWz(Wz_Type.Character);\n                                if (charaWz != null)\n                                {\n                                    string imgName = itemID.ToString(\"d8\") + \".img\";\n                                    foreach (Wz_Node node0 in charaWz.Nodes)\n                                    {\n                                        Wz_Node imgNode = node0.FindNodeByPath(imgName, true);\n                                        if (imgNode != null)\n                                        {\n                                            Gear gear = Gear.CreateFromNode(imgNode, path => PluginManager.FindWz(path));\n                                            if (gear != null)\n                                            {\n                                                recipeItemBmps.Add(RenderLinkRecipeGear(gear));\n                                            }\n\n                                            break;\n                                        }\n                                    }\n                                }\n                            }\n                            else if (itemIDClass >= 2 && itemIDClass <= 5) //通过ID寻找道具\n                            {\n                                Wz_Node itemWz = PluginManager.FindWz(Wz_Type.Item);\n                                if (itemWz != null)\n                                {\n                                    string imgClass = (itemID / 10000).ToString(\"d4\") + \".img\\\\\" + itemID.ToString(\"d8\");\n                                    foreach (Wz_Node node0 in itemWz.Nodes)\n                                    {\n                                        Wz_Node imgNode = node0.FindNodeByPath(imgClass, true);\n                                        if (imgNode != null)\n                                        {\n                                            Item item = Item.CreateFromNode(imgNode, PluginManager.FindWz);\n                                            if (item != null)\n                                            {\n                                                recipeItemBmps.Add(RenderLinkRecipeItem(item));\n                                            }\n\n                                            break;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            if (this.item.Props.TryGetValue(ItemPropType.setItemID, out long setID))\n            {\n                SetItem setItem;\n                if (CharaSimLoader.LoadedSetItems.TryGetValue((int)setID, out setItem))\n                {\n                    setItemBmp = RenderSetItem(setItem);\n                }\n            }\n\n            if (this.item.DamageSkinID != null && ShowDamageSkin)\n            {\n                DamageSkin damageSkin = DamageSkin.CreateFromNode(PluginManager.FindWz($@\"Etc\\DamageSkin.img\\{item.DamageSkinID}\"), PluginManager.FindWz) ?? DamageSkin.CreateFromNode(PluginManager.FindWz($@\"Effect\\DamageSkin.img\\{item.DamageSkinID}\"), PluginManager.FindWz);\n                if (damageSkin != null)\n                {\n                    setItemBmp = RenderDamageSkin(damageSkin);\n                }\n            }\n\n            if (this.item.FamiliarID != null)\n            {\n                Familiar familiar = Familiar.CreateFromNode(PluginManager.FindWz($@\"Character\\Familiar\\{item.FamiliarID}.img\"), PluginManager.FindWz);\n                if (familiar != null)\n                {\n                    return RenderFamiliar(familiar);\n                }\n            }\n\n            //计算布局\n            Size totalSize = new Size(itemBmp.Width, picHeight);\n            Point recipeInfoOrigin = Point.Empty;\n            Point recipeItemOrigin = Point.Empty;\n            Point setItemOrigin = Point.Empty;\n\n            if (recipeItemBmps.Count > 0)\n            {\n                // layout:\n                //   item        |  recipeItem\n                //   recipeInfo  |\n                recipeItemOrigin.X = totalSize.Width;\n                totalSize.Width += recipeItemBmps.Max(bmp => bmp.Width);\n\n                if (recipeInfoBmps.Count > 0)\n                {\n                    recipeInfoOrigin.X = itemBmp.Width - recipeInfoBmps.Max(bmp => bmp.Width);\n                    recipeInfoOrigin.Y = picHeight;\n                    totalSize.Height = Math.Max(picHeight + recipeInfoBmps.Sum(bmp => bmp.Height), recipeItemBmps.Sum(bmp => bmp.Height));\n                }\n                else\n                {\n                    totalSize.Height = Math.Max(picHeight, recipeItemBmps.Sum(bmp => bmp.Height));\n                }\n            }\n            else if (recipeInfoBmps.Count > 0)\n            {\n                // layout:\n                //   item  |  recipeInfo\n                totalSize.Width += recipeInfoBmps.Max(bmp => bmp.Width);\n                totalSize.Height = Math.Max(picHeight, recipeInfoBmps.Sum(bmp => bmp.Height));\n                recipeInfoOrigin.X = itemBmp.Width;\n            }\n            if (setItemBmp != null)\n            {\n                setItemOrigin = new Point(totalSize.Width, 0);\n                totalSize.Width += setItemBmp.Width;\n                totalSize.Height = Math.Max(totalSize.Height, setItemBmp.Height);\n            }\n\n            //开始绘制\n            Bitmap tooltip = new Bitmap(totalSize.Width, totalSize.Height);\n            Graphics g = Graphics.FromImage(tooltip);\n\n            if (itemBmp != null)\n            {\n                //绘制背景区域\n                GearGraphics.DrawNewTooltipBack(g, 0, 0, itemBmp.Width, picHeight);\n                //复制图像\n                g.DrawImage(itemBmp, 0, 0, new Rectangle(0, 0, itemBmp.Width, picHeight), GraphicsUnit.Pixel);\n                //左上角\n                g.DrawImage(Resource.UIToolTip_img_Item_Frame2_cover, 3, 3);\n\n                if (this.ShowObjectID)\n                {\n                    GearGraphics.DrawGearDetailNumber(g, 3, 3, item.ItemID.ToString(\"d8\"), true);\n                }\n            }\n\n            //绘制配方\n            if (recipeInfoBmps.Count > 0)\n            {\n                for (int i = 0, y = recipeInfoOrigin.Y; i < recipeInfoBmps.Count; i++)\n                {\n                    g.DrawImage(recipeInfoBmps[i], recipeInfoOrigin.X, y,\n                        new Rectangle(Point.Empty, recipeInfoBmps[i].Size), GraphicsUnit.Pixel);\n                    y += recipeInfoBmps[i].Height;\n                }\n            }\n\n            //绘制产出道具\n            if (recipeItemBmps.Count > 0)\n            {\n                for (int i = 0, y = recipeItemOrigin.Y; i < recipeItemBmps.Count; i++)\n                {\n                    g.DrawImage(recipeItemBmps[i], recipeItemOrigin.X, y,\n                        new Rectangle(Point.Empty, recipeItemBmps[i].Size), GraphicsUnit.Pixel);\n                    y += recipeItemBmps[i].Height;\n                }\n            }\n\n            //绘制套装\n            if (setItemBmp != null)\n            {\n                g.DrawImage(setItemBmp, setItemOrigin.X, setItemOrigin.Y,\n                    new Rectangle(Point.Empty, setItemBmp.Size), GraphicsUnit.Pixel);\n            }\n\n            if (itemBmp != null)\n                itemBmp.Dispose();\n            if (recipeInfoBmps.Count > 0)\n                recipeInfoBmps.ForEach(bmp => bmp.Dispose());\n            if (recipeItemBmps.Count > 0)\n                recipeItemBmps.ForEach(bmp => bmp.Dispose());\n            if (setItemBmp != null)\n                setItemBmp.Dispose();\n\n            g.Dispose();\n            return tooltip;\n        }\n\n        private Bitmap RenderItem(out int picH)\n        {\n            StringFormat format = (StringFormat)StringFormat.GenericDefault.Clone();\n            long value;\n\n            //物品标题\n            StringResult sr;\n            if (StringLinker == null || !StringLinker.StringItem.TryGetValue(item.ItemID, out sr))\n            {\n                sr = new StringResult();\n                sr.Name = \"(null)\";\n            }\n\n            // calculate image width\n            const int DefualtWidth = 290;\n            int tooltipWidth = DefualtWidth;\n\n            if (int.TryParse(sr[\"fixWidth\"], out int fixWidth) && fixWidth > 0)\n            {\n                tooltipWidth = fixWidth;\n            }\n            else\n            {\n                using (Bitmap dummyImg = new Bitmap(1, 1))\n                using (Graphics tempG = Graphics.FromImage(dummyImg))\n                {\n                    SizeF titleSize = tempG.MeasureString(sr.Name, GearGraphics.ItemNameFont2, short.MaxValue, format);\n                    titleSize.Width += 12 * 2;\n                    if (titleSize.Width > DefualtWidth)\n                    {\n                        tooltipWidth = (int)Math.Ceiling(titleSize.Width);\n                    }\n                }\n            }\n\n            Bitmap tooltip = new Bitmap(tooltipWidth, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(tooltip);\n            picH = 10;\n\n            //绘制标题\n            bool hasPart2 = false;\n            format.Alignment = StringAlignment.Center;\n            g.DrawString(sr.Name, GearGraphics.ItemNameFont2, Brushes.White, tooltip.Width / 2, picH, format);\n            picH += 22;\n\n            //额外特性\n            string attr = GetItemAttributeString();\n            if (!string.IsNullOrEmpty(attr))\n            {\n                g.DrawString(attr, GearGraphics.ItemDetailFont, GearGraphics.GearNameBrushC, tooltip.Width / 2, picH, format);\n                if (attr.Contains(\"\\n\")) {\n                    picH += 31;\n                }\n                else\n                {\n                    picH += 19;\n                }\n                hasPart2 = true;\n            }\n\n            string expireTime = null;\n            if (item.Props.TryGetValue(ItemPropType.permanent, out value) && value != 0)\n            {\n                expireTime = ItemStringHelper.GetItemPropString(ItemPropType.permanent, value);\n            }\n            else if (item.Props.TryGetValue(ItemPropType.life, out value) && value > 0)\n            {\n                DateTime time = DateTime.Now.AddDays(value);\n                expireTime = time.ToString(\"魔法时间：到 yyyy年 M月 d日 H时\");\n            }\n            if (!string.IsNullOrEmpty(expireTime))\n            {\n                g.DrawString(expireTime, GearGraphics.ItemDetailFont, Brushes.White, tooltip.Width / 2, picH, format);\n                picH += 16;\n                hasPart2 = true;\n            }\n\n            if (hasPart2)\n            {\n                picH += 1;\n            }\n\n            //装备限时\n            if (item.TimeLimited)\n            {\n                DateTime time = DateTime.Now.AddDays(7d);\n                string expireStr = time.ToString(\"到yyyy年 M月 d日 H时 m分可以用\");\n                g.DrawString(expireStr, GearGraphics.ItemDetailFont, Brushes.White, tooltip.Width / 2, picH, format);\n                picH += 21;\n            }\n\n            //绘制图标\n            int iconY = picH;\n            int iconX = 14;\n            g.DrawImage(Resource.UIToolTip_img_Item_ItemIcon_base, iconX, picH);\n            if (item.Icon.Bitmap != null)\n            {\n                g.DrawImage(GearGraphics.EnlargeBitmap(item.Icon.Bitmap),\n                iconX + 6 + (1 - item.Icon.Origin.X) * 2,\n                picH + 6 + (33 - item.Icon.Origin.Y) * 2);\n            }\n            if (item.Cash)\n            {\n                Bitmap cashImg = null;\n\n                if (item.Props.TryGetValue(ItemPropType.wonderGrade, out value) && value > 0)\n                {\n                    string resKey = $\"CashShop_img_CashItem_label_{value + 3}\";\n                    cashImg = Resource.ResourceManager.GetObject(resKey) as Bitmap;\n                }\n                if (cashImg == null) //default cashImg\n                {\n                    cashImg = Resource.CashItem_0;\n                }\n\n                g.DrawImage(GearGraphics.EnlargeBitmap(cashImg),\n                    iconX + 6 + 68 - 26,\n                    picH + 6 + 68 - 26);\n            }\n            g.DrawImage(Resource.UIToolTip_img_Item_ItemIcon_cover, iconX + 4, picH + 4); //绘制左上角cover\n\n            if (item.Props.TryGetValue(ItemPropType.reqLevel, out value))\n            {\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;\n                g.PixelOffsetMode = PixelOffsetMode.HighQuality;\n                g.DrawString(\"要求等级 :\" + value, GearGraphics.ItemReqLevelFont, Brushes.White, 97, picH);\n                picH += 15;\n                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault;\n            }\n            else\n            {\n                picH += 3;\n            }\n\n            int right = tooltip.Width - 18;\n\n            string desc = null;\n            if (item.Level > 0)\n            {\n                desc += $\"[LV.{item.Level}] \";\n            }\n            desc += sr.Desc;\n            if (!string.IsNullOrEmpty(desc))\n            {\n                GearGraphics.DrawString(g, desc, GearGraphics.ItemDetailFont2, 100, right, ref picH, 16);\n            }\n            if (!string.IsNullOrEmpty(sr.AutoDesc))\n            {\n                GearGraphics.DrawString(g, sr.AutoDesc, GearGraphics.ItemDetailFont2, 100, right, ref picH, 16);\n            }\n            if (item.Props.TryGetValue(ItemPropType.tradeAvailable, out value) && value > 0)\n            {\n                attr = ItemStringHelper.GetItemPropString(ItemPropType.tradeAvailable, value);\n                if (!string.IsNullOrEmpty(attr))\n                    GearGraphics.DrawString(g, \"#c\" + attr + \"#\", GearGraphics.ItemDetailFont2, 100, right, ref picH, 16);\n            }\n            if (item.Specs.TryGetValue(ItemSpecType.recipeValidDay, out value) && value > 0)\n            {\n                GearGraphics.DrawString(g, \"(可制作时间：\" + value + \"天)\", GearGraphics.ItemDetailFont, 100, right, ref picH, 16);\n            }\n            if (item.Specs.TryGetValue(ItemSpecType.recipeUseCount, out value) && value > 0)\n            {\n                GearGraphics.DrawString(g, \"(可制作次数：\" + value + \"次)\", GearGraphics.ItemDetailFont, 100, right, ref picH, 16);\n            }\n\n            picH += 3;\n\n            Wz_Node nickResNode = null;\n            bool willDrawNickTag = this.ShowNickTag\n                && this.Item.Props.TryGetValue(ItemPropType.nickTag, out value)\n                && this.TryGetNickResource(value, out nickResNode);\n            string descLeftAlign = sr[\"desc_leftalign\"];\n\n            if (!string.IsNullOrEmpty(descLeftAlign) || item.Sample.Bitmap != null || willDrawNickTag)\n            {\n                if (picH < iconY + 84)\n                {\n                    picH = iconY + 84;\n                }\n                if (!string.IsNullOrEmpty(descLeftAlign))\n                {\n                    picH += 12;\n                    GearGraphics.DrawString(g, descLeftAlign, GearGraphics.ItemDetailFont, 14, right, ref picH, 16);\n                }\n                if (item.Sample.Bitmap != null)\n                {\n                    g.DrawImage(item.Sample.Bitmap, (tooltip.Width - item.Sample.Bitmap.Width) / 2, picH);\n                    picH += item.Sample.Bitmap.Height;\n                    picH += 2;\n                }\n                if (nickResNode != null)\n                {\n                    //获取称号名称\n                    string nickName;\n                    string nickWithQR = sr[\"nickWithQR\"];\n                    if (nickWithQR != null)\n                    {\n                        string qrDefault = sr[\"qrDefault\"] ?? string.Empty;\n                        nickName = Regex.Replace(nickWithQR, \"#qr.*?#\", qrDefault);\n                    }\n                    else\n                    {\n                        nickName = sr.Name;\n                    }\n                    GearGraphics.DrawNameTag(g, nickResNode, nickName, tooltip.Width, ref picH);\n                    picH += 4;\n                }\n            }\n\n\n            //绘制配方需求\n            if (item.Recipes.Count > 0)\n            {\n                long reqSkill, reqSkillLevel;\n                if (!item.Specs.TryGetValue(ItemSpecType.reqSkill, out reqSkill))\n                {\n                    reqSkill = item.Recipes[0] / 10000 * 10000;\n                }\n\n                if (!item.Specs.TryGetValue(ItemSpecType.reqSkillLevel, out reqSkillLevel))\n                {\n                    reqSkillLevel = 1;\n                }\n\n                picH = Math.Max(picH, iconY + 107);\n                g.DrawLine(Pens.White, 6, picH, 283, picH);//分割线\n                picH += 10;\n                g.DrawString(\"<使用限制条件>\", GearGraphics.ItemDetailFont, GearGraphics.SetItemNameBrush, 8, picH);\n                picH += 17;\n\n                //技能标题\n                if (StringLinker == null || !StringLinker.StringSkill.TryGetValue((int)reqSkill, out sr))\n                {\n                    sr = new StringResult();\n                    sr.Name = \"(null)\";\n                }\n                g.DrawString(string.Format(\"· {0}{1}级以上\", sr.Name, reqSkillLevel), GearGraphics.ItemDetailFont, GearGraphics.SetItemNameBrush, 13, picH);\n                picH += 16;\n                picH += 6;\n            }\n\n            picH = Math.Max(iconY + 94, picH + 6);\n            return tooltip;\n        }\n\n        private string GetItemAttributeString()\n        {\n            long value;\n            List<string> tags = new List<string>();\n\n            if (item.Props.TryGetValue(ItemPropType.quest, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.quest, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.pquest, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.pquest, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.only, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.only, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.tradeBlock, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.tradeBlock, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.accountSharable, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.accountSharable, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.accountSharableAfterExchange, out value) && value != 0)\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.accountSharableAfterExchange, value));\n            }\n            if (item.Props.TryGetValue(ItemPropType.mintable, out value))\n            {\n                tags.Add(ItemStringHelper.GetItemPropString(ItemPropType.mintable, value));\n            }\n\n            return tags.Count > 0 ? string.Join(\", \", tags.ToArray()) : null;\n        }\n\n        private Bitmap RenderDamageSkin(DamageSkin damageSkin)\n        {\n            TooltipRender renderer = this.DamageSkinRender;\n            if (renderer == null)\n            {\n                DamageSkinTooltipRender defaultRenderer = new DamageSkinTooltipRender();\n                defaultRenderer.StringLinker = this.StringLinker;\n                defaultRenderer.ShowObjectID = this.ShowDamageSkinID;\n                defaultRenderer.UseMiniSize = this.UseMiniSizeDamageSkin;\n                defaultRenderer.AlwaysUseMseaFormat = this.AlwaysUseMseaFormatDamageSkin;\n                defaultRenderer.DisplayUnitOnSingleLine = this.DisplayUnitOnSingleLine;\n                defaultRenderer.DamageSkinNumber = this.DamageSkinNumber;\n                renderer = defaultRenderer;\n                defaultRenderer.DamageSkin = damageSkin;\n            }\n            renderer.TargetItem = damageSkin;\n            return renderer.Render();\n        }\n\n        private Bitmap RenderFamiliar(Familiar familiar)\n        {\n            TooltipRender renderer = this.FamiliarRender;\n            if (renderer == null)\n            {\n                FamiliarTooltipRender defaultRenderer = new FamiliarTooltipRender();\n                defaultRenderer.StringLinker = this.StringLinker;\n                defaultRenderer.ShowObjectID = this.ShowObjectID;\n                defaultRenderer.AllowOutOfBounds = false;\n                defaultRenderer.ItemID = this.item.ItemID;\n                defaultRenderer.FamiliarTier = this.item.Grade;\n                defaultRenderer.UseAssembleUI = false;\n                renderer = defaultRenderer;\n            }\n            renderer.TargetItem = familiar;\n            return renderer.Render();\n        }\n\n        private Bitmap RenderLinkRecipeInfo(Recipe recipe)\n        {\n            TooltipRender renderer = this.LinkRecipeInfoRender;\n            if (renderer == null)\n            {\n                RecipeTooltipRender defaultRenderer = new RecipeTooltipRender();\n                defaultRenderer.StringLinker = this.StringLinker;\n                defaultRenderer.ShowObjectID = false;\n                renderer = defaultRenderer;\n            }\n\n            renderer.TargetItem = recipe;\n            return renderer.Render();\n        }\n\n        private Bitmap RenderLinkRecipeGear(Gear gear)\n        {\n            TooltipRender renderer = this.LinkRecipeGearRender;\n            if (renderer == null)\n            {\n                GearTooltipRender2 defaultRenderer = new GearTooltipRender2();\n                defaultRenderer.StringLinker = this.StringLinker;\n                defaultRenderer.ShowObjectID = false;\n                renderer = defaultRenderer;\n            }\n\n            renderer.TargetItem = gear;\n            return renderer.Render();\n        }\n\n        private Bitmap RenderLinkRecipeItem(Item item)\n        {\n            TooltipRender renderer = this.LinkRecipeItemRender;\n            if (renderer == null)\n            {\n                ItemTooltipRender2 defaultRenderer = new ItemTooltipRender2();\n                defaultRenderer.StringLinker = this.StringLinker;\n                defaultRenderer.ShowObjectID = false;\n                renderer = defaultRenderer;\n            }\n\n            renderer.TargetItem = item;\n            return renderer.Render();\n        }\n\n        private Bitmap RenderSetItem(SetItem setItem)\n        {\n            TooltipRender renderer = this.SetItemRender;\n            if (renderer == null)\n            {\n                var defaultRenderer = new SetItemTooltipRender();\n                defaultRenderer.StringLinker = this.StringLinker;\n                defaultRenderer.ShowObjectID = false;\n                renderer = defaultRenderer;\n            }\n\n            renderer.TargetItem = setItem;\n            return renderer.Render();\n        }\n\n        private bool TryGetNickResource(long nickTag, out Wz_Node resNode)\n        {\n            resNode = PluginBase.PluginManager.FindWz(\"UI/NameTag.img/nick/\" + nickTag);\n            return resNode != null;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/MobTooltipRenderer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Common;\nusing static WzComparerR2.CharaSimControl.RenderHelper;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class MobTooltipRenderer : TooltipRender\n    {\n\n        public MobTooltipRenderer()\n        {\n        }\n\n        public override object TargetItem\n        {\n            get { return this.MobInfo; }\n            set { this.MobInfo = value as Mob; }\n        }\n\n        public Mob MobInfo { get; set; }\n\n        public override Bitmap Render()\n        {\n            if (MobInfo == null)\n            {\n                return null;\n            }\n\n            Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format32bppArgb);\n            Graphics g = Graphics.FromImage(bmp);\n\n            //预绘制\n            List<TextBlock> titleBlocks = new List<TextBlock>();\n\n            if (MobInfo.ID > -1)\n            {\n                string mobName = GetMobName(MobInfo.ID);\n                var block = PrepareText(g, mobName ?? \"(null)\", GearGraphics.ItemNameFont2, Brushes.White, 0, 0);\n                titleBlocks.Add(block);\n                block = PrepareText(g, \"ID:\" + MobInfo.ID, GearGraphics.ItemDetailFont, Brushes.White, block.Size.Width + 4, 4);\n                titleBlocks.Add(block);\n            }\n\n            List<TextBlock> propBlocks = new List<TextBlock>();\n            int picY = 0;\n\n            StringBuilder sbExt = new StringBuilder();\n            if (MobInfo.Boss)\n            {\n                sbExt.Append(\"Boss \");\n            }\n            if (MobInfo.Undead)\n            {\n                sbExt.Append(\"不死系 \");\n            }\n            if (MobInfo.FirstAttack)\n            {\n                sbExt.Append(\"主动攻击 \");\n            }\n            if (!MobInfo.BodyAttack)\n            {\n                sbExt.Append(\"无触碰伤害 \");\n            }\n            if (MobInfo.DamagedByMob)\n            {\n                sbExt.Append(\"只受怪物伤害 \");\n            }\n            if (MobInfo.Invincible)\n            {\n                sbExt.Append(\"无敌 \");\n            }\n            if (MobInfo.NotAttack)\n            {\n                sbExt.Append(\"无法攻击 \");\n            }\n            if (MobInfo.FixedDamage > 0)\n            {\n                sbExt.Append(\"固定伤害\" + MobInfo.FixedDamage + \" \");\n            }\n\n            if (sbExt.Length > 1)\n            {\n                sbExt.Remove(sbExt.Length - 1, 1);\n                propBlocks.Add(PrepareText(g, sbExt.ToString(), GearGraphics.ItemDetailFont, Brushes.GreenYellow, 0, picY));\n                picY += 16;\n            }\n\n            if (MobInfo.RemoveAfter > 0)\n            {\n                propBlocks.Add(PrepareText(g, \"出生\" + MobInfo.RemoveAfter + \"秒后自动消失\", GearGraphics.ItemDetailFont, Brushes.GreenYellow, 0, picY));\n                picY += 16;\n            }\n\n            propBlocks.Add(PrepareText(g, \"Level: \" + MobInfo.Level, GearGraphics.ItemDetailFont, Brushes.White, 0, picY));\n            string hpNum = !string.IsNullOrEmpty(MobInfo.FinalMaxHP) ? this.AddCommaSeparators(MobInfo.FinalMaxHP) : MobInfo.MaxHP.ToString(\"N0\");\n            propBlocks.Add(PrepareText(g, \"HP: \" + hpNum, GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            string mpNum = !string.IsNullOrEmpty(MobInfo.FinalMaxMP) ? this.AddCommaSeparators(MobInfo.FinalMaxMP) : MobInfo.MaxMP.ToString(\"N0\");\n            propBlocks.Add(PrepareText(g, \"MP: \" + mpNum, GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"PAD: \" + MobInfo.PADamage, GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"MAD: \" + MobInfo.MADamage, GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"PDr: \" + MobInfo.PDRate + \"%\", GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"MDr: \" + MobInfo.MDRate + \"%\", GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"Acc: \" + MobInfo.Acc, GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"Eva: \" + MobInfo.Eva, GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"KB: \" + MobInfo.Pushed.ToString(\"N0\"), GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, \"Exp: \" + MobInfo.Exp.ToString(\"N0\"), GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            propBlocks.Add(PrepareText(g, GetElemAttrString(MobInfo.ElemAttr), GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            picY += 28;\n\n            if (MobInfo.Revive.Count > 0)\n            {\n                Dictionary<int, int> reviveCounts = new Dictionary<int, int>();\n                foreach (var reviveID in MobInfo.Revive)\n                {\n                    int count = 0;\n                    reviveCounts.TryGetValue(reviveID, out count);\n                    reviveCounts[reviveID] = count + 1;\n                }\n\n                StringBuilder sb = new StringBuilder();\n                sb.Append(\"死后召唤 \");\n                int rowCount = 0;\n                foreach (var kv in reviveCounts)\n                {\n                    if (rowCount++ > 0)\n                    {\n                        sb.AppendLine().Append(\"    \");\n                    }\n                    string mobName = GetMobName(kv.Key);\n                    sb.AppendFormat(\"{0}({1:D7})\", mobName, kv.Key);\n                    if (kv.Value > 1)\n                    {\n                        sb.Append(\"*\" + kv.Value);\n                    }\n                }\n\n                propBlocks.Add(PrepareText(g, sb.ToString(), GearGraphics.ItemDetailFont, Brushes.GreenYellow, 0, picY));\n            }\n            g.Dispose();\n            bmp.Dispose();\n\n            //计算大小\n            Rectangle titleRect = Measure(titleBlocks);\n            Rectangle imgRect = Rectangle.Empty;\n            Rectangle textRect = Measure(propBlocks);\n            Bitmap mobImg = MobInfo.Default.Bitmap;\n            if (mobImg != null)\n            {\n                if (mobImg.Width > 250 || mobImg.Height > 300) //进行缩放\n                {\n                    double scale = Math.Min((double)250 / mobImg.Width, (double)300 / mobImg.Height);\n                    imgRect = new Rectangle(0, 0, (int)(mobImg.Width * scale), (int)(mobImg.Height * scale));\n                }\n                else\n                {\n                    imgRect = new Rectangle(0, 0, mobImg.Width, mobImg.Height);\n                }\n            }\n\n\n            //布局 \n            //水平排列\n            int width = 0;\n            if (!imgRect.IsEmpty)\n            {\n                textRect.X = imgRect.Width + 4;\n            }\n            width = Math.Max(titleRect.Width, Math.Max(imgRect.Right, textRect.Right));\n            titleRect.X = (width - titleRect.Width) / 2;\n\n            //垂直居中\n            int height = Math.Max(imgRect.Height, textRect.Height);\n            imgRect.Y = (height - imgRect.Height) / 2;\n            textRect.Y = (height - textRect.Height) / 2;\n            if (!titleRect.IsEmpty)\n            {\n                height += titleRect.Height + 4;\n                imgRect.Y += titleRect.Bottom + 4;\n                textRect.Y += titleRect.Bottom + 4;\n            }\n\n            //绘制\n            bmp = new Bitmap(width + 20, height + 20);\n            titleRect.Offset(10, 10);\n            imgRect.Offset(10, 10);\n            textRect.Offset(10, 10);\n            g = Graphics.FromImage(bmp);\n            //绘制背景\n            GearGraphics.DrawNewTooltipBack(g, 0, 0, bmp.Width, bmp.Height);\n            //绘制标题\n            foreach (var item in titleBlocks)\n            {\n                DrawText(g, item, titleRect.Location);\n            }\n            //绘制图像\n            if (mobImg != null && !imgRect.IsEmpty)\n            {\n                g.DrawImage(mobImg, imgRect);\n            }\n            //绘制文本\n            foreach (var item in propBlocks)\n            {\n                DrawText(g, item, textRect.Location);\n            }\n            g.Dispose();\n            return bmp;\n        }\n\n        private string GetMobName(int mobID)\n        {\n            StringResult sr;\n            if (this.StringLinker == null || !this.StringLinker.StringMob.TryGetValue(mobID, out sr))\n            {\n                return null;\n            }\n            return sr.Name;\n        }\n\n        private string GetElemAttrString(MobElemAttr elemAttr)\n        {\n            StringBuilder sb1 = new StringBuilder(),\n                sb2 = new StringBuilder();\n\n            sb1.Append(\"冰雷火毒圣暗物\");\n            sb2.Append(GetElemAttrResistString(elemAttr.I));\n            sb2.Append(GetElemAttrResistString(elemAttr.L));\n            sb2.Append(GetElemAttrResistString(elemAttr.F));\n            sb2.Append(GetElemAttrResistString(elemAttr.S));\n            sb2.Append(GetElemAttrResistString(elemAttr.H));\n            sb2.Append(GetElemAttrResistString(elemAttr.D));\n            sb2.Append(GetElemAttrResistString(elemAttr.P));\n            sb1.AppendLine().Append(sb2.ToString());\n            return sb1.ToString();\n        }\n\n        private string GetElemAttrResistString(ElemResistance resist)\n        {\n            string e = null;\n            switch (resist)\n            {\n                case ElemResistance.Immune: e = \"×\"; break;\n                case ElemResistance.Resist: e = \"△\"; break;\n                case ElemResistance.Normal: e = \"○\"; break;\n                case ElemResistance.Weak: e = \"◎\"; break;\n            }\n            return e ?? \"  \";\n        }\n\n        private string AddCommaSeparators(string number)\n        {\n            return Regex.Replace(number, @\"^(\\d+?)(\\d{3})+$\", m =>\n            {\n                var sb = new StringBuilder();\n                sb.Append(m.Result(\"$1\"));\n                foreach (Capture cap in m.Groups[2].Captures)\n                {\n                    sb.Append(\",\");\n                    sb.Append(cap.ToString());\n                }\n                return sb.ToString();\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/NpcTooltipRenderer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.Common;\nusing static WzComparerR2.CharaSimControl.RenderHelper;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class NpcTooltipRenderer : TooltipRender\n    {\n        public NpcTooltipRenderer()\n        {\n\n        }\n\n\n        public override object TargetItem\n        {\n            get { return this.NpcInfo; }\n            set { this.NpcInfo = value as Npc; }\n        }\n\n        public Npc NpcInfo { get; set; }\n\n        public override Bitmap Render()\n        {\n            if (NpcInfo == null)\n            {\n                return null;\n            }\n            Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format32bppArgb);\n            Graphics g = Graphics.FromImage(bmp);\n\n            //预绘制\n            List<TextBlock> titleBlocks = new List<TextBlock>();\n            List<TextBlock> propBlocks = new List<TextBlock>();\n            int picY = 0;\n\n            if (NpcInfo.ID > -1)\n            {\n                string mobName = GetNpcName(NpcInfo.ID);\n                var block = PrepareText(g, mobName ?? \"(null)\", GearGraphics.ItemNameFont2, Brushes.White, 0, 0);\n                titleBlocks.Add(block);\n                block = PrepareText(g, \"ID:\" + NpcInfo.ID, GearGraphics.ItemDetailFont, Brushes.White, block.Size.Width + 4, 4);\n                titleBlocks.Add(block);\n            }\n\n            propBlocks.Add(PrepareText(g, \"出没地区：\", GearGraphics.ItemDetailFont, GearGraphics.GearNameBrushG, 0, 0));\n            if (NpcInfo?.ID != null)\n            {\n                var locNode = PluginBase.PluginManager.FindWz(\"Etc\\\\NpcLocation.img\\\\\" + NpcInfo.ID.ToString());\n                if (locNode != null)\n                {\n                    foreach (var locMapNode in locNode.Nodes)\n                    {\n                        int mapID;\n                        string mapName = null;\n                        if (int.TryParse(locMapNode.Text, out mapID))\n                        {\n                            mapName = GetMapName(mapID);\n                        }\n                        string npcLoc = string.Format(\" {0}({1})\", mapName ?? \"null\", locMapNode.Text);\n\n                        propBlocks.Add(PrepareText(g, npcLoc, GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n                    }\n                }\n            }\n\n            if (propBlocks.Count == 1) //获取地区失败\n            {\n                propBlocks.Add(PrepareText(g, \" 不明\", GearGraphics.ItemDetailFont, Brushes.White, 0, picY += 16));\n            }\n\n            //计算大小\n            Rectangle titleRect = Measure(titleBlocks);\n            Rectangle imgRect = Rectangle.Empty;\n            Rectangle textRect = Measure(propBlocks);\n            Bitmap npcImg = NpcInfo.Default.Bitmap;\n            if (npcImg != null)\n            {\n                if (npcImg.Width > 250 || npcImg.Height > 300) //进行缩放\n                {\n                    double scale = Math.Min((double)250 / npcImg.Width, (double)300 / npcImg.Height);\n                    imgRect = new Rectangle(0, 0, (int)(npcImg.Width * scale), (int)(npcImg.Height * scale));\n                }\n                else\n                {\n                    imgRect = new Rectangle(0, 0, npcImg.Width, npcImg.Height);\n                }\n            }\n\n            //布局 \n            //水平排列\n            int width = 0;\n            if (!imgRect.IsEmpty)\n            {\n                textRect.X = imgRect.Width + 4;\n            }\n            width = Math.Max(titleRect.Width, Math.Max(imgRect.Right, textRect.Right));\n            titleRect.X = (width - titleRect.Width) / 2;\n\n            //垂直居中\n            int height = Math.Max(imgRect.Height, textRect.Height);\n            imgRect.Y = (height - imgRect.Height) / 2;\n            textRect.Y = (height - textRect.Height) / 2;\n            if (!titleRect.IsEmpty)\n            {\n                height += titleRect.Height + 4;\n                imgRect.Y += titleRect.Bottom + 4;\n                textRect.Y += titleRect.Bottom + 4;\n            }\n\n            //绘制\n            bmp = new Bitmap(width + 20, height + 20);\n            titleRect.Offset(10, 10);\n            imgRect.Offset(10, 10);\n            textRect.Offset(10, 10);\n            g = Graphics.FromImage(bmp);\n            //绘制背景\n            GearGraphics.DrawNewTooltipBack(g, 0, 0, bmp.Width, bmp.Height);\n            //绘制标题\n            foreach (var item in titleBlocks)\n            {\n                DrawText(g, item, titleRect.Location);\n            }\n            //绘制图像\n            if (npcImg != null && !imgRect.IsEmpty)\n            {\n                g.DrawImage(npcImg, imgRect);\n            }\n            //绘制文本\n            foreach (var item in propBlocks)\n            {\n                DrawText(g, item, textRect.Location);\n            }\n            g.Dispose();\n            return bmp;\n        }\n\n        private string GetNpcName(int npcID)\n        {\n            StringResult sr;\n            if (this.StringLinker == null || !this.StringLinker.StringNpc.TryGetValue(npcID, out sr))\n            {\n                return null;\n            }\n            return sr.Name;\n        }\n\n        private string GetMapName(int mapID)\n        {\n            StringResult sr;\n            if (this.StringLinker == null || !this.StringLinker.StringMap.TryGetValue(mapID, out sr))\n            {\n                return null;\n            }\n            return sr.Name;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/RecipeTooltipRender.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing Resource = CharaSimResource.Resource;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class RecipeTooltipRender : TooltipRender\n    {\n        public RecipeTooltipRender()\n        {\n        }\n\n        public Recipe Recipe { get; set; }\n\n        public override object TargetItem\n        {\n            get\n            {\n                return this.Recipe;\n            }\n            set\n            {\n                this.Recipe = value as Recipe;\n            }\n        }\n\n        public override Bitmap Render()\n        {\n            if (this.Recipe == null)\n            {\n                return null;\n            }\n            int picHeight;\n            Bitmap originBmp = RenderRecipe(out picHeight);\n            Bitmap tooltip = new Bitmap(originBmp.Width, picHeight);\n            Graphics g = Graphics.FromImage(tooltip);\n\n            //绘制背景区域\n            GearGraphics.DrawNewTooltipBack(g, 0, 0, tooltip.Width, tooltip.Height);\n\n            //复制图像\n            g.DrawImage(originBmp, 0, 0, new Rectangle(0, 0, originBmp.Width, picHeight), GraphicsUnit.Pixel);\n\n            //左上角\n            g.DrawImage(Resource.UIToolTip_img_Item_Frame2_cover, 3, 3);\n\n            if (this.ShowObjectID)\n            {\n                GearGraphics.DrawGearDetailNumber(g, 3, 3, Recipe.RecipeID.ToString(\"d8\"), true);\n            }\n\n            if (originBmp != null)\n                originBmp.Dispose();\n\n            g.Dispose();\n            return tooltip;\n        }\n\n        private Bitmap RenderRecipe(out int picH)\n        {\n            Bitmap tooltip = new Bitmap(290, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(tooltip);\n            StringFormat fmt = (StringFormat)StringFormat.GenericTypographic.Clone();\n            fmt.Alignment = StringAlignment.Center;\n\n            picH = 10;\n            StringResult sr;\n            string title = \"制作配方\";\n            if (this.Recipe.MainTargetItemID != 0)\n            {\n                sr = GetSRByItemID(this.Recipe.MainTargetItemID);\n                if (sr == null)\n                {\n                    title += \" - \" + this.Recipe.MainTargetItemID;\n                }\n                else\n                {\n                    title += \" - \" + sr.Name;\n                }\n            }\n            g.DrawString(title, GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 145, picH, fmt);\n            picH += 16;\n\n            g.DrawString(\"生成物品\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 13, picH);\n            picH += 16;\n\n            fmt.Alignment = StringAlignment.Far;\n            foreach (RecipeItemInfo itemInfo in this.Recipe.TargetItems)\n            {\n                sr = GetSRByItemID(itemInfo.ItemID);\n                string text = sr != null ? sr.Name : itemInfo.ItemID.ToString();\n                text += \" x \" + itemInfo.Count;\n                g.DrawString(text, GearGraphics.ItemDetailFont2, Brushes.White, 13, picH, StringFormat.GenericTypographic);\n                g.DrawString(itemInfo.ProbWeight + \"%\", GearGraphics.ItemDetailFont2, Brushes.White, 278, picH, fmt);\n                picH += 16;\n            }\n\n            picH += 4;\n\n            g.DrawString(\"消耗物品\", GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 13, picH);\n            picH += 16;\n            foreach (RecipeItemInfo itemInfo in this.Recipe.RecipeItems)\n            {\n                sr = GetSRByItemID(itemInfo.ItemID);\n                string text = sr != null ? sr.Name : itemInfo.ItemID.ToString();\n                text += \" x \" + itemInfo.Count;\n                g.DrawString(text, GearGraphics.ItemDetailFont2, Brushes.White, 13, picH, StringFormat.GenericTypographic);\n                picH += 16;\n            }\n\n            picH += 5;\n            fmt.Dispose();\n            g.Dispose();\n            return tooltip;\n        }\n\n        private StringResult GetSRByItemID(int itemID)\n        {\n            if (StringLinker == null)\n            {\n                return null;\n            }\n\n            StringResult sr = null;\n\n            int itemIDClass = itemID / 1000000;\n            if (itemIDClass == 1)\n            {\n                StringLinker.StringEqp.TryGetValue(itemID, out sr);\n            }\n            else if (itemIDClass >= 2 && itemIDClass <= 5)\n            {\n                StringLinker.StringItem.TryGetValue(itemID, out sr);\n            }\n\n            return sr;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/RenderHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    class RenderHelper\n    {\n        private RenderHelper()\n        {\n        }\n\n        public static TextBlock PrepareText(Graphics g, string text, Font font, Brush brush, int x, int y)\n        {\n            SizeF size = g.MeasureString(text, font, Int32.MaxValue, StringFormat.GenericTypographic);\n            TextBlock block = new TextBlock()\n            {\n                Text = text,\n                Font = font,\n                Brush = brush,\n                Position = new Point(x, y),\n                Size = new Size((int)Math.Round(size.Width, MidpointRounding.AwayFromZero),\n                    (int)Math.Round(size.Height, MidpointRounding.AwayFromZero))\n            };\n            return block;\n        }\n\n        public static void DrawText(Graphics g, TextBlock block, Point offset)\n        {\n            g.DrawString(block.Text, block.Font, block.Brush,\n                block.Position.X + offset.X, block.Position.Y + offset.Y,\n                StringFormat.GenericTypographic);\n        }\n\n        public static Rectangle Measure(IEnumerable<TextBlock> blocks)\n        {\n            Rectangle rect = Rectangle.Empty;\n\n            foreach (var block in blocks)\n            {\n                var blockRect = block.Rectangle;\n                if (!blockRect.IsEmpty)\n                {\n                    rect = Rectangle.Union(rect, blockRect);\n                }\n            }\n            return rect;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/SetItemTooltipRender.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing System.Linq;\nusing Resource = CharaSimResource.Resource;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class SetItemTooltipRender : TooltipRender\n    {\n        public SetItemTooltipRender()\n        {\n        }\n\n        public SetItem SetItem { get; set; }\n\n        public override object TargetItem\n        {\n            get { return this.SetItem; }\n            set { this.SetItem = value as SetItem; }\n        }\n\n        public bool IsCombineProperties { get; set; } = true;\n\n        public override Bitmap Render()\n        {\n            if (this.SetItem == null)\n            {\n                return null;\n            }\n\n            int width = 261;\n            int picHeight1;\n            Bitmap originBmp = RenderSetItem(out picHeight1);\n            int picHeight2 = 0;\n            Bitmap effectBmp = null;\n\n            if (this.SetItem.ExpandToolTip)\n            {\n                effectBmp = RenderEffectPart(out picHeight2);\n                width += 261;\n            }\n\n            Bitmap tooltip = new Bitmap(width, Math.Max(picHeight1, picHeight2));\n            Graphics g = Graphics.FromImage(tooltip);\n\n            //绘制左侧\n            GearGraphics.DrawNewTooltipBack(g, 0, 0, originBmp.Width, picHeight1);\n            g.DrawImage(originBmp, 0, 0, new Rectangle(0, 0, originBmp.Width, picHeight1), GraphicsUnit.Pixel);\n            \n            //绘制右侧\n            if(effectBmp != null)\n            {\n                GearGraphics.DrawNewTooltipBack(g, originBmp.Width, 0, effectBmp.Width, picHeight2);\n                g.DrawImage(effectBmp, originBmp.Width, 0, new Rectangle(0, 0, effectBmp.Width, picHeight2), GraphicsUnit.Pixel);\n            }\n\n            originBmp?.Dispose();\n            effectBmp?.Dispose();\n            g.Dispose();\n            return tooltip;\n        }\n\n        private Bitmap RenderSetItem(out int picHeight)\n        {\n            Bitmap setBitmap = new Bitmap(261, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(setBitmap);\n            StringFormat format = new StringFormat();\n            format.Alignment = StringAlignment.Center;\n\n            picHeight = 10;\n            g.DrawString(this.SetItem.SetItemName, GearGraphics.ItemDetailFont2, GearGraphics.GreenBrush2, 130, 10, format);\n            picHeight += 25;\n\n            format.Alignment = StringAlignment.Far;\n            Wz_Node characterWz = PluginManager.FindWz(Wz_Type.Character);\n\n            foreach (var setItemPart in this.SetItem.ItemIDs.Parts)\n            {\n                string itemName = setItemPart.Value.RepresentName;\n                string typeName = setItemPart.Value.TypeName;\n\n                // for 'parts' setitem, detect typeName by the first itemID per part\n                //if (string.IsNullOrEmpty(typeName) && SetItem.Parts)\n                //{\n                //    typeName = \"特殊\";\n                //}\n\n                ItemBase itemBase = null;\n                bool cash = false;\n                long wonderGrade = 0;\n\n                if (setItemPart.Value.ItemIDs.Count > 0)\n                {\n                    var itemID = setItemPart.Value.ItemIDs.First().Key;\n\n                    switch (itemID / 1000000)\n                    {\n                        case 0: //avatar\n                        case 1: //gear\n                            if (characterWz != null)\n                            {\n                                foreach (Wz_Node typeNode in characterWz.Nodes)\n                                {\n                                    Wz_Node itemNode = typeNode.FindNodeByPath(string.Format(\"{0:D8}.img\", itemID), true);\n                                    if (itemNode != null)\n                                    {\n                                        var gear = Gear.CreateFromNode(itemNode, PluginManager.FindWz);\n                                        cash = gear.Cash;\n                                        itemBase = gear;\n                                        break;\n                                    }\n                                }\n                            }\n                            break;\n\n                        case 5: //Pet\n                            {\n                                Wz_Node itemNode = PluginBase.PluginManager.FindWz(string.Format(@\"Item\\Pet\\{0:D7}.img\", itemID));\n                                if (itemNode != null)\n                                {\n                                    var item = Item.CreateFromNode(itemNode, PluginManager.FindWz);\n                                    cash = item.Cash;\n                                    item.Props.TryGetValue(ItemPropType.wonderGrade, out wonderGrade);\n                                    itemBase = item;\n                                }\n                            }\n                            break;\n                    }\n                }\n\n                if (string.IsNullOrEmpty(itemName) || string.IsNullOrEmpty(typeName))\n                {\n                    if (setItemPart.Value.ItemIDs.Count > 0)\n                    {\n                        var itemID = setItemPart.Value.ItemIDs.First().Key;\n                        StringResult sr = null; ;\n                        if (this.StringLinker != null)\n                        {\n                            if (this.StringLinker.StringEqp.TryGetValue(itemID, out sr))\n                            {\n                                itemName = sr.Name;\n                                if (typeName == null)\n                                {\n                                    typeName = ItemStringHelper.GetSetItemGearTypeString(Gear.GetGearType(itemID));\n                                }\n                            }\n                            else if (this.StringLinker.StringItem.TryGetValue(itemID, out sr)) //兼容宠物\n                            {\n                                itemName = sr.Name;\n                                if (typeName == null)\n                                {\n                                    if (itemID / 10000 == 500)\n                                    {\n                                        typeName = \"特殊\";\n                                    }\n                                    else\n                                    {\n                                        typeName = \"\";\n                                    }\n                                }\n                            }\n                        }\n                        if (sr == null)\n                        {\n                            itemName = \"(null)\";\n                        }\n                    }\n                }\n\n                itemName = itemName ?? string.Empty;\n                typeName = typeName ?? \"装备\";\n\n                if (!Regex.IsMatch(typeName, @\"^(\\(.*\\)|（.*）)$\"))\n                {\n                    typeName = \"(\" + typeName + \")\";\n                }\n\n                Brush brush = setItemPart.Value.Enabled ? Brushes.White : GearGraphics.GrayBrush2;\n                if (!cash)\n                {\n                    g.DrawString(itemName, GearGraphics.ItemDetailFont2, brush, 8, picHeight);\n                    g.DrawString(typeName, GearGraphics.ItemDetailFont2, brush, 254, picHeight, format);\n                    picHeight += 18;\n                }\n                else\n                {\n                    g.FillRectangle(GearGraphics.GearIconBackBrush2, 10, picHeight, 36, 36);\n                    g.DrawImage(Resource.Item_shadow, 10 + 2 + 3, picHeight + 2 + 32 - 6);\n                    if (itemBase?.IconRaw.Bitmap != null)\n                    {\n                        var icon = itemBase.IconRaw;\n                        g.DrawImage(icon.Bitmap, 10 + 2 - icon.Origin.X, picHeight + 2 + 32 - icon.Origin.Y);\n                    }\n\n                    Bitmap cashImg = null;\n                    if (wonderGrade > 0)\n                    {\n                        string resKey = $\"CashShop_img_CashItem_label_{wonderGrade + 3}\";\n                        cashImg = Resource.ResourceManager.GetObject(resKey) as Bitmap;\n                    }\n                    if (cashImg == null) //default cashImg\n                    {\n                        cashImg = Resource.CashItem_0;\n                    }\n                    g.DrawImage(cashImg, 10 + 2 + 20, picHeight + 2 + 32 - 12);\n                    g.DrawString(itemName, GearGraphics.ItemDetailFont2, brush, 50, picHeight);\n                    g.DrawString(typeName, GearGraphics.ItemDetailFont2, brush, 254, picHeight, format);\n                    picHeight += 40;\n                }\n            }\n\n            if (!this.SetItem.ExpandToolTip)\n            {\n                picHeight += 5;\n                g.DrawLine(Pens.White, 6, picHeight, 254, picHeight);//分割线\n                picHeight += 9;\n                RenderEffect(g, ref picHeight);\n            }\n            picHeight += 11;\n\n            format.Dispose();\n            g.Dispose();\n            return setBitmap;\n        }\n\n        private Bitmap RenderEffectPart(out int picHeight)\n        {\n            Bitmap effBitmap = new Bitmap(261, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(effBitmap);\n            picHeight = 9;\n            RenderEffect(g, ref picHeight);\n            picHeight += 11;\n            g.Dispose();\n            return effBitmap;\n        }\n\n        /// <summary>\n        /// 绘制套装属性。\n        /// </summary>\n        private void RenderEffect(Graphics g, ref int picHeight)\n        {\n            foreach (KeyValuePair<int, SetItemEffect> effect in this.SetItem.Effects)\n            {\n                string effTitle;\n                if (this.SetItem.SetItemID < 0)\n                {\n                    effTitle = $\"服务器内重复装备效果({effect.Key} / {this.SetItem.CompleteCount})\";\n                }\n                else\n                {\n                    effTitle = effect.Key + \"套装效果\";\n                }\n                g.DrawString(effTitle, GearGraphics.ItemDetailFont, GearGraphics.GreenBrush2, 8, picHeight);\n                picHeight += 16;\n                //Brush brush = effect.Value.Enabled ? Brushes.White : GearGraphics.GrayBrush2;\n                var color = effect.Value.Enabled ? Color.White : GearGraphics.GrayColor2;\n\n                //T116 合并套装\n                var props = IsCombineProperties ? Gear.CombineProperties(effect.Value.PropsV5) : effect.Value.PropsV5;\n                foreach (KeyValuePair<GearPropType, object> prop in props)\n                {\n                    if (prop.Key == GearPropType.Option)\n                    {\n                        List<Potential> ops = (List<Potential>)prop.Value;\n                        foreach (Potential p in ops)\n                        {\n                            GearGraphics.DrawPlainText(g, p.ConvertSummary(), GearGraphics.ItemDetailFont2, color, 10, 244, ref picHeight, 16);\n                        }\n                    }\n                    else if (prop.Key == GearPropType.OptionToMob)\n                    {\n                        List<SetItemOptionToMob> ops = (List<SetItemOptionToMob>)prop.Value;\n                        foreach (SetItemOptionToMob p in ops)\n                        {\n                            GearGraphics.DrawPlainText(g, p.ConvertSummary(), GearGraphics.ItemDetailFont2, color, 10, 244, ref picHeight, 16);\n                        }\n                    }\n                    else if (prop.Key == GearPropType.activeSkill)\n                    {\n                        List<SetItemActiveSkill> ops = (List<SetItemActiveSkill>)prop.Value;\n                        foreach (SetItemActiveSkill p in ops)\n                        {\n                            StringResult sr;\n                            if (StringLinker == null || !StringLinker.StringSkill.TryGetValue(p.SkillID, out sr))\n                            {\n                                sr = new StringResult();\n                                sr.Name = p.SkillID.ToString();\n                            }\n                            string summary = $\"可以使用<{sr.Name}>技能\";\n                            GearGraphics.DrawPlainText(g, summary, GearGraphics.ItemDetailFont2, color, 10, 244, ref picHeight, 16);\n                        }\n                    }\n                    else if (prop.Key == GearPropType.bonusByTime)\n                    {\n                        var ops = (List<SetItemBonusByTime>)prop.Value;\n                        foreach (SetItemBonusByTime p in ops)\n                        {\n                            GearGraphics.DrawPlainText(g, $\"{p.TermStart}小时后\", GearGraphics.ItemDetailFont2, color, 10, 244, ref picHeight, 16);\n                            foreach (var bonusProp in p.Props)\n                            {\n                                var summary = ItemStringHelper.GetGearPropString(bonusProp.Key, Convert.ToInt32(bonusProp.Value));\n                                GearGraphics.DrawPlainText(g, summary, GearGraphics.ItemDetailFont2, color, 10, 244, ref picHeight, 16);\n                            }\n                        }\n                    }\n                    else\n                    {\n                        var summary = ItemStringHelper.GetGearPropString(prop.Key, Convert.ToInt32(prop.Value));\n                        GearGraphics.DrawPlainText(g, summary, GearGraphics.ItemDetailFont2, color, 10, 244, ref picHeight, 16);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/SkillTooltipRender.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class SkillTooltipRender : TooltipRender\n    {\n        public SkillTooltipRender()\n        {\n        }\n\n        Skill skill;\n\n        public Skill Skill\n        {\n            get { return skill; }\n            set { skill = value; }\n        }\n\n        public override Bitmap Render()\n        {\n            if (this.skill == null)\n            {\n                return null;\n            }\n\n            int picHeight;\n            Bitmap originBmp = RenderSkill(out picHeight);\n            Bitmap tooltip = new Bitmap(290, picHeight);\n            Graphics g = Graphics.FromImage(tooltip);\n\n            int iconY = 33;\n            //复制图像\n            g.FillRectangle(GearGraphics.GearBackBrush, 2, 2, 286, picHeight - 4);\n            g.CompositingMode = CompositingMode.SourceCopy;\n            g.FillRectangle(GearGraphics.GearIconBackBrush, 14, iconY, 68, 68);\n            g.CompositingMode = CompositingMode.SourceOver;\n            g.DrawImage(originBmp, 0, 0, new Rectangle(0, 0, 290, picHeight - 2), GraphicsUnit.Pixel);\n\n            //边框\n            g.DrawLines(GearGraphics.GearBackPen, GearGraphics.GetBorderPath(0, 290, picHeight));\n\n            g.Dispose();\n            return tooltip;\n        }\n\n        private Bitmap RenderSkill(out int picH)\n        {\n            //int h = 128;\n            Bitmap bitmap = new Bitmap(290, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(bitmap);\n\n            picH = 33; //iconY\n\n            if (!(StringLinker.StringSkill.TryGetValue(skill.SkillID, out var _sr) && _sr is StringResultSkill sr))\n            {\n                sr = new StringResultSkill();\n                sr.Name = \"(null)\";\n            }\n\n            StringFormat format = new StringFormat();\n            format.Alignment = StringAlignment.Center;\n\n            g.DrawString(sr.Name, GearGraphics.ItemNameFont, Brushes.White, 143, 10, format);//绘制标题\n            if (skill.Icon.Bitmap != null)\n            {\n                g.DrawImage(GearGraphics.EnlargeBitmap(skill.Icon.Bitmap),\n                14 + (1 - skill.Icon.Origin.X) * 2,\n                picH + (33 - skill.Icon.Bitmap.Height) * 2);//绘制图标\n            }\n\n            //绘制desc\n            picH = 35;\n            GearGraphics.DrawString(g, \"[最高等级：\" + skill.MaxLevel + \"]\", GearGraphics.ItemDetailFont, 90, 270, ref picH, 16);\n            if (sr.Desc != null)\n            {\n                GearGraphics.DrawString(g, sr.Desc, GearGraphics.ItemDetailFont, 90, 270, ref picH, 16);\n            }\n\n            picH = Math.Max(picH, 114);\n            g.DrawLine(Pens.White, 6, picH, 283, picH); //分割线\n            picH += 5;\n\n            if (skill.Level > 0)\n            {\n                string hStr = null;\n                if (skill.PreBBSkill)\n                {\n                    if (sr.SkillH.Count >= skill.Level)\n                    {\n                        hStr = sr.SkillH[skill.Level - 1];\n                    }\n                }\n                else\n                {\n                    if (sr.SkillH.Count > 0)\n                    {\n                        hStr = SummaryParser.GetSkillSummary(skill,skill.Level, sr, SummaryParams.Default);\n                    }\n                }\n\n                picH += 4;\n                GearGraphics.DrawString(g, \"[现在等级 \" + skill.Level + \"]\", GearGraphics.ItemDetailFont, 8, 272, ref picH, 16);\n                GearGraphics.DrawString(g, hStr, GearGraphics.ItemDetailFont, 8, 272, ref picH, 16);\n            }\n\n            if (skill.Level < skill.MaxLevel)\n            {\n                string hStr = null;\n                if (skill.PreBBSkill)\n                {\n                    if (sr.SkillH.Count >= skill.Level + 1)\n                    {\n                        hStr = sr.SkillH[skill.Level];\n                    }\n                }\n                else\n                {\n                    if (sr.SkillH.Count > 0)\n                    {\n                        hStr = SummaryParser.GetSkillSummary(skill, skill.Level+1, sr, SummaryParams.Default); \n                    }\n                }\n\n                picH += 4;\n                GearGraphics.DrawString(g, \"[下次等级 \" + (skill.Level + 1) + \"]\", GearGraphics.ItemDetailFont, 8, 272, ref picH, 16);\n                GearGraphics.DrawString(g, hStr, GearGraphics.ItemDetailFont, 8, 272, ref picH, 16);\n            }\n            picH += 9;\n            g.Dispose();\n            return bitmap;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/SkillTooltipRender2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Linq;\nusing Resource = CharaSimResource.Resource;\nusing WzComparerR2.Common;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class SkillTooltipRender2 : TooltipRender\n    {\n        public SkillTooltipRender2()\n        {\n        }\n\n        public Skill Skill { get; set; }\n\n        public override object TargetItem\n        {\n            get { return this.Skill; }\n            set { this.Skill = value as Skill; }\n        }\n\n        public bool ShowProperties { get; set; } = true;\n        public bool ShowDelay { get; set; }\n        public bool ShowReqSkill { get; set; } = true;\n        public bool DisplayCooltimeMSAsSec { get; set; } = true;\n        public bool DisplayPermyriadAsPercent { get; set; } = true;\n        public bool IgnoreEvalError { get; set; } = false;\n        public bool IsWideMode { get; set; } = true;\n\n        public override Bitmap Render()\n        {\n            if (this.Skill == null)\n            {\n                return null;\n            }\n\n            CanvasRegion region = this.IsWideMode ? CanvasRegion.Wide : CanvasRegion.Original;\n\n            int picHeight;\n            List<int> splitterH;\n            Bitmap originBmp = RenderSkill(region, out picHeight, out splitterH);\n            Bitmap tooltip = new Bitmap(originBmp.Width, picHeight);\n            Graphics g = Graphics.FromImage(tooltip);\n\n            //绘制背景区域\n            GearGraphics.DrawNewTooltipBack(g, 0, 0, tooltip.Width, tooltip.Height);\n            if (splitterH != null && splitterH.Count > 0)\n            {\n                g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;\n                foreach (var y in splitterH)\n                {\n                    DrawV6SkillDotline(g, region.SplitterX1, region.SplitterX2, y);\n                }\n                g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;\n            }\n\n            //复制图像\n            g.DrawImage(originBmp, 0, 0, new Rectangle(0, 0, originBmp.Width, picHeight), GraphicsUnit.Pixel);\n\n            //左上角\n            g.DrawImage(Resource.UIToolTip_img_Skill_Frame_cover, 3, 3);\n\n            if (this.ShowObjectID)\n            {\n                GearGraphics.DrawGearDetailNumber(g, 3, 3, Skill.SkillID.ToString(\"d7\"), true);\n            }\n\n            if (originBmp != null)\n                originBmp.Dispose();\n\n            g.Dispose();\n            return tooltip;\n        }\n\n        private Bitmap RenderSkill(CanvasRegion region, out int picH, out List<int> splitterH)\n        {\n            Bitmap bitmap = new Bitmap(region.Width, DefaultPicHeight);\n            Graphics g = Graphics.FromImage(bitmap);\n            StringFormat format = (StringFormat)StringFormat.GenericDefault.Clone();\n            var v6SkillSummaryFontColorTable = new Dictionary<string, Color>()\n            {\n                { \"c\", GearGraphics.SkillSummaryOrangeTextColor },\n            };\n\n            //初始化 skillCommon\n            Dictionary<string, string> skillCommon = new Dictionary<string, string>(Skill.Common);\n            if (Skill.PerJobAttackInfo.Count > 0)\n            {\n                var perJobInfo = Skill.PerJobAttackInfo.ElementAt(Skill.PerJobIndex).Value;\n                foreach (var i in perJobInfo.Keys)\n                {\n                    skillCommon[i] = perJobInfo[i];\n                }\n            }\n\n            picH = 0;\n            splitterH = new List<int>();\n\n            //获取文字\n            if (StringLinker == null || !(StringLinker.StringSkill.TryGetValue(Skill.SkillID, out var _sr) && _sr is StringResultSkill sr))\n            {\n                sr = new StringResultSkill();\n                sr.Name = \"(null)\";\n            }\n\n            //绘制技能名称\n            format.Alignment = StringAlignment.Center;\n            g.DrawString(sr.Name, GearGraphics.ItemNameFont2, Brushes.White, region.TitleCenterX, 10, format);\n\n            //绘制图标\n            picH = 33;\n            g.DrawImage(Resource.UIToolTip_img_Skill_Frame_iconBackgrnd, 13, picH - 2);\n\n            if (Skill.Icon.Bitmap != null)\n            {\n                g.DrawImage(GearGraphics.EnlargeBitmap(Skill.Icon.Bitmap),\n                15 + (1 - Skill.Icon.Origin.X) * 2,\n                picH + (33 - Skill.Icon.Bitmap.Height) * 2);\n            }\n\n            // for 6th job skills\n            if (Skill.Origin)\n            {\n                g.DrawImage(Resource.UIWindow2_img_Skill_skillTypeIcon_origin, 16, 11);\n            }\n\n            //绘制desc\n            picH = 35;\n            if (!Skill.PreBBSkill)\n                GearGraphics.DrawString(g, \"[最高等级：\" + Skill.MaxLevel + \"]\", GearGraphics.ItemDetailFont2, region.SkillDescLeft, region.TextRight, ref picH, 16);\n\n            if (sr.Desc != null)\n            {\n                string hdesc = SummaryParser.GetSkillSummary(sr.Desc, Skill.Level, skillCommon, SummaryParams.Default);\n                //string hStr = SummaryParser.GetSkillSummary(skill, skill.Level, sr, SummaryParams.Default);\n                GearGraphics.DrawString(g, hdesc, GearGraphics.ItemDetailFont2, v6SkillSummaryFontColorTable, region.SkillDescLeft, region.TextRight, ref picH, 16);\n            }\n            if (Skill.ReqLevel > 0)\n            {\n                GearGraphics.DrawString(g, \"#c[要求等级：\" + Skill.ReqLevel.ToString() + \"]#\", GearGraphics.ItemDetailFont2, region.SkillDescLeft, region.TextRight, ref picH, 16);\n            }\n            if (Skill.ReqAmount > 0)\n            {\n                GearGraphics.DrawString(g, \"#c\" + ItemStringHelper.GetSkillReqAmount(Skill.SkillID, Skill.ReqAmount) + \"#\", GearGraphics.ItemDetailFont2, region.SkillDescLeft, region.TextRight, ref picH, 16);\n            }\n            picH += 13;\n\n            //delay rendering v6 splitter\n            picH = Math.Max(picH, 114);\n            splitterH.Add(picH);\n            picH += 15;\n\n            var skillSummaryOptions = new SkillSummaryOptions\n            {\n                ConvertCooltimeMS = this.DisplayCooltimeMSAsSec,\n                ConvertPerM = this.DisplayPermyriadAsPercent,\n                IgnoreEvalError = this.IgnoreEvalError,\n                EndColorOnNewLine = true,\n            };\n\n            if (Skill.Level > 0)\n            {\n                string hStr = SummaryParser.GetSkillSummary(Skill, Skill.Level, sr, SummaryParams.Default, skillSummaryOptions, skillCommon);\n                GearGraphics.DrawString(g, \"[现在等级 \" + Skill.Level + \"]\", GearGraphics.ItemDetailFont, region.LevelDescLeft, region.TextRight, ref picH, 16);\n                if (hStr != null)\n                {\n                    GearGraphics.DrawString(g, hStr, GearGraphics.ItemDetailFont2, v6SkillSummaryFontColorTable, region.LevelDescLeft, region.TextRight, ref picH, 16);\n                }\n            }\n\n            if (Skill.Level < Skill.MaxLevel)\n            {\n                string hStr = SummaryParser.GetSkillSummary(Skill, Skill.Level + 1, sr, SummaryParams.Default, skillSummaryOptions);\n                GearGraphics.DrawString(g, \"[下次等级 \" + (Skill.Level + 1) + \"]\", GearGraphics.ItemDetailFont, region.LevelDescLeft, region.TextRight, ref picH, 16);\n                if (hStr != null)\n                {\n                    GearGraphics.DrawString(g, hStr, GearGraphics.ItemDetailFont2, v6SkillSummaryFontColorTable, region.LevelDescLeft, region.TextRight, ref picH, 16);\n                }\n            }\n            picH += 9;\n\n            List<string> skillDescEx = new List<string>();\n            if (ShowProperties)\n            {\n                List<string> attr = new List<string>();\n                if (Skill.Invisible)\n                {\n                    attr.Add(\"隐藏技能\");\n                }\n                if (Skill.Hyper != HyperSkillType.None)\n                {\n                    attr.Add(\"超级技能:\" + Skill.Hyper);\n                }\n                if (Skill.CombatOrders)\n                {\n                    attr.Add(\"战斗命令加成\");\n                }\n                if (Skill.NotRemoved)\n                {\n                    attr.Add(\"无法被移除\");\n                }\n                if (Skill.MasterLevel > 0 && Skill.MasterLevel < Skill.MaxLevel)\n                {\n                    attr.Add(\"初始掌握:Lv.\" + Skill.MasterLevel);\n                }\n\n                if (attr.Count > 0)\n                {\n                    skillDescEx.Add(\"#c\" + string.Join(\", \", attr.ToArray()) + \"#\");\n                }\n            }\n\n            if (ShowDelay && Skill.Action.Count > 0)\n            {\n                foreach (string action in Skill.Action)\n                {\n                    skillDescEx.Add(\"#c[技能延时] \" + action + \": \" + CharaSimLoader.GetActionDelay(action) + \" ms#\");\n                }\n            }\n\n            if (ShowReqSkill && Skill.ReqSkill.Count > 0)\n            {\n                foreach (var kv in Skill.ReqSkill)\n                {\n                    string skillName;\n                    if (this.StringLinker != null && this.StringLinker.StringSkill.TryGetValue(kv.Key, out var sr2))\n                    {\n                        skillName = sr2.Name;\n                    }\n                    else\n                    {\n                        skillName = kv.Key.ToString();\n                    }\n                    skillDescEx.Add(\"#c[前置技能] \" + skillName + \": \" + kv.Value + \" 级#\");\n                }\n            }\n            \n            if (Skill.PerJobAttackInfo.Count > 0)\n            {\n                int jobID = Skill.PerJobAttackInfo.ElementAt(Skill.PerJobIndex).Key;\n                skillDescEx.Add($\"#c[适用职业] {ItemStringHelper.GetJobName(jobID)}({jobID})#\");\n            }\n\n            if (skillDescEx.Count > 0)\n            {\n                //delay rendering v6 splitter\n                splitterH.Add(picH);\n                picH += 9;\n                foreach (var descEx in skillDescEx)\n                {\n                    GearGraphics.DrawString(g, descEx, GearGraphics.ItemDetailFont, region.LevelDescLeft, region.TextRight, ref picH, 16);\n                }\n                picH += 9;\n            }\n\n            format.Dispose();\n            g.Dispose();\n            return bitmap;\n        }\n\n        private void DrawV6SkillDotline(Graphics g, int x1, int x2, int y)\n        {\n            // here's a trick that we won't draw left and right part because it looks the same as background border.\n            var picCenter = Resource.UIToolTip_img_Skill_Frame_dotline_c;\n            using (var brush = new TextureBrush(picCenter))\n            {\n                brush.TranslateTransform(x1, y);\n                g.FillRectangle(brush, new Rectangle(x1, y, x2 - x1, picCenter.Height));\n            }\n        }\n\n        private class CanvasRegion\n        {\n            public int Width { get; private set; }\n            public int TitleCenterX { get; private set; }\n            public int SplitterX1 { get; private set; }\n            public int SplitterX2 { get; private set; }\n            public int SkillDescLeft { get; private set; }\n            public int LevelDescLeft { get; private set; }\n            public int TextRight { get; private set; }\n\n            public static CanvasRegion Original { get; } = new CanvasRegion()\n            {\n                Width = 290,\n                TitleCenterX = 144,\n                SplitterX1 = 4,\n                SplitterX2 = 284,\n                SkillDescLeft = 90,\n                LevelDescLeft = 8,\n                TextRight = 272,\n            };\n\n            public static CanvasRegion Wide { get; } = new CanvasRegion()\n            {\n                Width = 430,\n                TitleCenterX = 215,\n                SplitterX1 = 4,\n                SplitterX2 = 424,\n                SkillDescLeft = 92,\n                LevelDescLeft = 10,\n                TextRight = 412,\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/TextBlock.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public class TextBlock\n    {\n        public string Text { get; set; }\n        public Brush Brush { get; set; }\n        public Font Font { get; set; }\n        public Point Position { get; set; }\n        public Size Size { get; set; }\n        public Rectangle Rectangle\n        {\n            get\n            {\n                return new Rectangle(Position, Size);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/CharaSimControl/TooltipRender.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing WzComparerR2.Common;\n\nnamespace WzComparerR2.CharaSimControl\n{\n    public abstract class TooltipRender\n    {\n        static TooltipRender()\n        {\n            Rectangle screenRect = System.Windows.Forms.Screen.PrimaryScreen.Bounds;\n            DefaultPicHeight = screenRect.Height * 2;\n        }\n\n        public TooltipRender()\n        {\n        }\n\n        public static readonly int DefaultPicHeight;\n\n        public StringLinker StringLinker { get; set; }\n\n        public bool ShowObjectID { get; set; }\n\n        public abstract Bitmap Render();\n\n        public virtual object TargetItem { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Comparer/CompareDifference.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Comparer\n{\n    public class CompareDifference\n    {\n        public CompareDifference(Wz_Node nodeNew, Wz_Node nodeOld, DifferenceType type)\n        {\n            this.NodeNew = nodeNew;\n            this.NodeOld = nodeOld;\n            this.DifferenceType = type;\n        }\n\n        public Wz_Node NodeNew { get; protected set; }\n\n        public Wz_Node NodeOld { get; protected set; }\n\n        public virtual object ValueNew\n        {\n            get { return NodeNew?.Value; }\n        }\n\n        public virtual object ValueOld\n        {\n            get { return NodeOld?.Value; }\n        }\n\n        public DifferenceType DifferenceType { get; protected set; }\n\n        public override string ToString()\n        {\n            switch (this.DifferenceType)\n            {\n                case DifferenceType.Append:\n                    return string.Format(\"{0} {1}({2})\", this.DifferenceType, this.NodeNew.Text, this.ValueNew);\n                case DifferenceType.Changed:\n                    return string.Format(\"{0} {1}({2}<-{3})\", this.DifferenceType, this.NodeNew.Text, this.ValueNew, this.ValueOld);\n                case DifferenceType.Remove:\n                    return string.Format(\"{0} {1}({2})\", this.DifferenceType, this.NodeOld.Text, this.ValueOld);\n            }\n            return base.ToString();\n        }\n    }\n\n}\n"
  },
  {
    "path": "WzComparerR2/Comparer/DifferenceType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Comparer\n{\n    public enum DifferenceType\n    {\n        NotChanged = 0,\n        Append,\n        Remove,\n        Changed,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Comparer/EasyComparer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\nusing System.Net;\nusing System.Drawing;\nusing System.Linq;\nusing System.Security.Cryptography;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Comparer\n{\n    public class EasyComparer\n    {\n        public EasyComparer()\n        {\n            this.Comparer = new WzFileComparer();\n        }\n\n        public WzFileComparer Comparer { get; protected set; }\n        private string stateInfo;\n        private string stateDetail;\n        public bool OutputPng { get; set; }\n        public bool OutputAddedImg { get; set; }\n        public bool OutputRemovedImg { get; set; }\n        public List<Color> ColorTable { get; set; }\n        public bool HashPngFileName { get; set; }\n\n        public string StateInfo\n        {\n            get { return stateInfo; }\n            set\n            {\n                stateInfo = value;\n                this.OnStateInfoChanged(EventArgs.Empty);\n            }\n        }\n\n        public string StateDetail\n        {\n            get { return stateDetail; }\n            set\n            {\n                stateDetail = value;\n                this.OnStateDetailChanged(EventArgs.Empty);\n            }\n        }\n\n        public event EventHandler StateInfoChanged;\n        public event EventHandler StateDetailChanged;\n\n        protected virtual void OnStateInfoChanged(EventArgs e)\n        {\n            if (this.StateInfoChanged != null)\n                this.StateInfoChanged(this, e);\n        }\n\n        protected virtual void OnStateDetailChanged(EventArgs e)\n        {\n            if (this.StateDetailChanged != null)\n                this.StateDetailChanged(this, e);\n        }\n\n        public void EasyCompareWzFiles(Wz_File fileNew, Wz_File fileOld, string outputDir)\n        {\n            StateInfo = \"正在对比wz概况...\";\n           \n            if (fileNew.Type == Wz_Type.Base || fileOld.Type == Wz_Type.Base) //至少有一个base 拆分对比\n            {\n                var virtualNodeNew = RebuildWzFile(fileNew);\n                var virtualNodeOld = RebuildWzFile(fileOld);\n                WzFileComparer comparer = new WzFileComparer();\n                comparer.IgnoreWzFile = true;\n\n                var dictNew = SplitVirtualNode(virtualNodeNew);\n                var dictOld = SplitVirtualNode(virtualNodeOld);\n\n                //寻找共同wzType\n                var wzTypeList = dictNew.Select(kv => kv.Key)\n                    .Where(wzType => dictOld.ContainsKey(wzType));\n\n                CreateStyleSheet(outputDir);\n\n                foreach (var wzType in wzTypeList)\n                {\n                    var vNodeNew = dictNew[wzType];\n                    var vNodeOld = dictOld[wzType];\n                    var cmp = comparer.Compare(vNodeNew, vNodeOld);\n                    OutputFile(vNodeNew.LinkNodes.Select(node => node.Value).OfType<Wz_File>().ToList(),\n                        vNodeOld.LinkNodes.Select(node => node.Value).OfType<Wz_File>().ToList(),\n                        wzType,\n                        cmp.ToList(),\n                        outputDir);\n                }\n            }\n            else //执行传统对比\n            {\n                WzFileComparer comparer = new WzFileComparer();\n                comparer.IgnoreWzFile = false;\n                var cmp = comparer.Compare(fileNew.Node, fileOld.Node);\n                CreateStyleSheet(outputDir);\n                OutputFile(fileNew, fileOld, fileNew.Type, cmp.ToList(), outputDir);\n            }\n\n            GC.Collect();\n        }\n\n        private WzVirtualNode RebuildWzFile(Wz_File wzFile)\n        {\n            //分组\n            List<Wz_File> subFiles = new List<Wz_File>();\n            WzVirtualNode topNode = new WzVirtualNode(wzFile.Node);\n\n            foreach (var childNode in wzFile.Node.Nodes)\n            {\n                var subFile = childNode.GetValue<Wz_File>();\n                if (subFile != null && !subFile.IsSubDir) //wz子文件\n                {\n                    subFiles.Add(subFile);\n                }\n                else //其他\n                {\n                    topNode.AddChild(childNode, true);\n                }\n            }\n\n            if (wzFile.Type == Wz_Type.Base)\n            {\n                foreach (var grp in subFiles.GroupBy(f => f.Type))\n                {\n                    WzVirtualNode fileNode = new WzVirtualNode();\n                    fileNode.Name = grp.Key.ToString();\n                    foreach (var file in grp)\n                    {\n                        fileNode.Combine(file.Node);\n                    }\n                    topNode.AddChild(fileNode);\n                }\n            }\n            return topNode;\n        }\n\n        private Dictionary<Wz_Type, WzVirtualNode> SplitVirtualNode(WzVirtualNode node)\n        {\n            var dict = new Dictionary<Wz_Type, WzVirtualNode>();\n            Wz_File wzFile = node.LinkNodes[0].Value as Wz_File;\n            dict[wzFile.Type] = node;\n\n            if (wzFile.Type == Wz_Type.Base) //额外处理\n            {\n                var wzFileList = node.ChildNodes\n                    .Select(child => new { Node = child, WzFile = child.LinkNodes[0].Value as Wz_File })\n                    .Where(item => item.WzFile != null);\n\n                foreach (var item in wzFileList)\n                {\n                    dict[item.WzFile.Type] = item.Node;\n                }\n            }\n\n            return dict;\n        }\n\n        private void OutputFile(Wz_File fileNew, Wz_File fileOld, Wz_Type type, List<CompareDifference> diffLst, string outputDir)\n        {\n            OutputFile(new List<Wz_File>() { fileNew },\n                new List<Wz_File>() { fileOld },\n                type,\n                diffLst,\n                outputDir);\n        }\n        private void OutputFile(List<Wz_File> fileNew, List<Wz_File> fileOld, Wz_Type type, List<CompareDifference> diffLst, string outputDir)\n        {\n            string htmlFilePath = Path.Combine(outputDir, type.ToString() + \".html\");\n            for (int i = 1; File.Exists(htmlFilePath); i++)\n            {\n                htmlFilePath = Path.Combine(outputDir, string.Format(\"{0}_{1}.html\", type, i));\n            }\n            string srcDirPath = Path.Combine(outputDir, Path.GetFileNameWithoutExtension(htmlFilePath) + \"_files\");\n            if (OutputPng && !Directory.Exists(srcDirPath))\n            {\n                Directory.CreateDirectory(srcDirPath);\n            }\n\n            FileStream htmlFile = null;\n            StreamWriter sw = null;\n            StateInfo = \"正在努力对比文件...\" + type;\n            StateDetail = \"正在构造输出文件\";\n            try\n            {\n                htmlFile = new FileStream(htmlFilePath, FileMode.Create, FileAccess.Write);\n                sw = new StreamWriter(htmlFile, Encoding.UTF8);\n                sw.WriteLine(\"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\");\n                sw.WriteLine(\"<html>\");\n                sw.WriteLine(\"<head>\");\n                sw.WriteLine(\"<meta http-equiv=\\\"content-type\\\" content=\\\"text/html;charset=utf-8\\\">\");\n                sw.WriteLine(\"<title>{0} {1}←{2}</title>\", type, fileNew[0].GetMergedVersion(), fileOld[0].GetMergedVersion());\n                sw.WriteLine(\"<link type=\\\"text/css\\\" rel=\\\"stylesheet\\\" href=\\\"style.css\\\" />\");\n                sw.WriteLine(\"</head>\");\n                sw.WriteLine(\"<body>\");\n                //输出概况\n                sw.WriteLine(\"<p class=\\\"wzf\\\">\");\n                sw.WriteLine(\"<table>\");\n                sw.WriteLine(\"<tr><th>&nbsp;</th><th>文件名</th><th>文件大小</th><th>文件版本</th></tr>\");\n                sw.WriteLine(\"<tr><td>新文件</td><td>{0}</td><td>{1}</td><td>{2}</td></tr>\",\n                    string.Join(\"<br/>\", fileNew.Select(wzf => wzf.Header.FileName)),\n                    string.Join(\"<br/>\", fileNew.Select(wzf => wzf.Header.FileSize.ToString(\"N0\"))),\n                    string.Join(\"<br/>\", fileNew.Select(wzf => wzf.GetMergedVersion()))\n                    );\n                sw.WriteLine(\"<tr><td>旧文件</td><td>{0}</td><td>{1}</td><td>{2}</td></tr>\",\n                    string.Join(\"<br/>\", fileOld.Select(wzf => wzf.Header.FileName)),\n                    string.Join(\"<br/>\", fileOld.Select(wzf => wzf.Header.FileSize.ToString(\"N0\"))),\n                    string.Join(\"<br/>\", fileOld.Select(wzf => wzf.GetMergedVersion()))\n                    );\n                sw.WriteLine(\"<tr><td>对比时间</td><td colspan='3'>{0:yyyy-MM-dd HH:mm:ss.fff}</td></tr>\", DateTime.Now);\n                sw.WriteLine(\"<tr><td>参数</td><td colspan='3'>{0}</td></tr>\", string.Join(\"<br/>\", new[] {\n                    this.OutputPng ? \"-OutputPng\" : null,\n                    this.OutputAddedImg ? \"-OutputAddedImg\" : null,\n                    this.OutputRemovedImg ? \"-OutputRemovedImg\" : null,\n                    \"-PngComparison \" + this.Comparer.PngComparison,\n                    this.Comparer.ResolvePngLink ? \"-ResolvePngLink\" : null,\n                }.Where(p => p != null)));\n                sw.WriteLine(\"</table>\");\n                sw.WriteLine(\"</p>\");\n\n                //输出目录\n                StringBuilder[] sb = { new StringBuilder(), new StringBuilder(), new StringBuilder() };\n                int[] count = new int[6];\n                string[] diffStr = { \"修改\", \"新增\", \"移除\" };\n                foreach (CompareDifference diff in diffLst)\n                {\n                    int idx = -1;\n                    string detail = null;\n                    switch (diff.DifferenceType)\n                    {\n                        case DifferenceType.Changed:\n                            idx = 0;\n                            detail = string.Format(\"<a name=\\\"m_{1}_{2}\\\" href=\\\"#a_{1}_{2}\\\">{0}</a>\", diff.NodeNew.FullPathToFile, idx, count[idx]);\n                            break;\n                        case DifferenceType.Append:\n                            idx = 1;\n                            if (this.OutputAddedImg)\n                            {\n                                detail = string.Format(\"<a name=\\\"m_{1}_{2}\\\" href=\\\"#a_{1}_{2}\\\">{0}</a>\", diff.NodeNew.FullPathToFile, idx, count[idx]);\n                            }\n                            else\n                            {\n                                detail = diff.NodeNew.FullPathToFile;\n                            }\n                            break;\n                        case DifferenceType.Remove:\n                            idx = 2;\n                            if (this.OutputRemovedImg)\n                            {\n                                detail = string.Format(\"<a name=\\\"m_{1}_{2}\\\" href=\\\"#a_{1}_{2}\\\">{0}</a>\", diff.NodeOld.FullPathToFile, idx, count[idx]);\n                            }\n                            else\n                            {\n                                detail = diff.NodeOld.FullPathToFile;\n                            }\n                            break;\n                        default:\n                            continue;\n                    }\n                    sb[idx].Append(\"<tr><td>\");\n                    sb[idx].Append(detail);\n                    sb[idx].AppendLine(\"</td></tr>\");\n                    count[idx]++;\n                }\n                StateDetail = \"正在输出目录\";\n                Array.Copy(count, 0, count, 3, 3);\n                for (int i = 0; i < sb.Length; i++)\n                {\n                    sw.WriteLine(\"<table class=\\\"lst{0}\\\">\", i);\n                    sw.WriteLine(\"<tr><th>{0}共{1}项</th></tr>\", diffStr[i], count[i]);\n                    sw.Write(sb[i].ToString());\n                    sw.WriteLine(\"</table>\");\n                    sb[i] = null;\n                    count[i] = 0;\n                }\n\n                foreach (CompareDifference diff in diffLst)\n                {\n                    switch (diff.DifferenceType)\n                    {\n                        case DifferenceType.Changed:\n                            {\n                                StateInfo = string.Format(\"{0}/{1}正在对比{2}\", count[0], count[3], diff.NodeNew.FullPath);\n                                Wz_Image imgNew, imgOld;\n                                if ((imgNew = diff.ValueNew as Wz_Image) != null\n                                    && ((imgOld = diff.ValueOld as Wz_Image) != null))\n                                {\n                                    string anchorName = \"a_0_\" + count[0];\n                                    string menuAnchorName = \"m_0_\" + count[0];\n                                    CompareImg(imgNew, imgOld, diff.NodeNew.FullPathToFile, anchorName, menuAnchorName, srcDirPath, sw);\n                                }\n                                count[0]++;\n                            }\n                            break;\n\n                        case DifferenceType.Append:\n                            if (this.OutputAddedImg)\n                            {\n                                StateInfo = string.Format(\"{0}/{1}正在输出新增{2}\", count[1], count[4], diff.NodeNew.FullPath);\n                                Wz_Image imgNew = diff.ValueNew as Wz_Image;\n                                if (imgNew != null)\n                                {\n                                    string anchorName = \"a_1_\" + count[1];\n                                    string menuAnchorName = \"m_1_\" + count[1];\n                                    OutputImg(imgNew, diff.DifferenceType, diff.NodeNew.FullPathToFile, anchorName, menuAnchorName, srcDirPath, sw);\n                                }\n                                count[1]++;\n                            }\n                            break;\n\n                        case DifferenceType.Remove:\n                            if (this.OutputRemovedImg)\n                            {\n                                StateInfo = string.Format(\"{0}/{1}正在输出删除{2}\", count[2], count[5], diff.NodeOld.FullPath);\n                                Wz_Image imgOld = diff.ValueOld as Wz_Image;\n                                if (imgOld != null)\n                                {\n                                    string anchorName = \"a_2_\" + count[2];\n                                    string menuAnchorName = \"m_2_\" + count[2];\n                                    OutputImg(imgOld, diff.DifferenceType, diff.NodeOld.FullPathToFile, anchorName, menuAnchorName, srcDirPath, sw);\n                                }\n                                count[2]++;\n                            }\n                            break;\n\n                        case DifferenceType.NotChanged:\n                            break;\n                    }\n\n                }\n                //html结束\n                sw.WriteLine(\"</body>\");\n                sw.WriteLine(\"</html>\");\n            }\n            finally\n            {\n                try\n                {\n                    if (sw != null)\n                    {\n                        sw.Flush();\n                        sw.Close();\n                    }\n                }\n                catch\n                {\n                }\n            }\n        }\n\n        private void CompareImg(Wz_Image imgNew, Wz_Image imgOld, string imgName, string anchorName, string menuAnchorName, string outputDir, StreamWriter sw)\n        {\n            StateDetail = \"正在解压img\";\n            if (!imgNew.TryExtract() || !imgOld.TryExtract())\n                return;\n            StateDetail = \"正在对比img\";\n            List<CompareDifference> diffList = new List<CompareDifference>(Comparer.Compare(imgNew.Node, imgOld.Node));\n            StringBuilder sb = new StringBuilder();\n            int[] count = new int[3];\n            StateDetail = \"正在统计概况并输出资源文件...变动项共\" + diffList.Count;\n            foreach (var diff in diffList)\n            {\n                int idx = -1;\n                string fullPath = null;\n                string fullPathToFile = null;\n                switch (diff.DifferenceType)\n                {\n                    case DifferenceType.Changed:\n                        idx = 0;\n                        fullPath = diff.NodeNew.FullPath;\n                        fullPathToFile = diff.NodeNew.FullPathToFile;\n                        break;\n                    case DifferenceType.Append:\n                        idx = 1;\n                        fullPath = diff.NodeNew.FullPath;\n                        fullPathToFile = diff.NodeNew.FullPathToFile;\n                        break;\n                    case DifferenceType.Remove:\n                        idx = 2;\n                        fullPath = diff.NodeOld.FullPath;\n                        fullPathToFile = diff.NodeOld.FullPathToFile;\n                        break;\n                }\n                sb.AppendFormat(\"<tr class=\\\"r{0}\\\">\", idx);\n                sb.AppendFormat(\"<td>{0}</td>\", fullPath ?? \" \");\n                sb.AppendFormat(\"<td>{0}</td>\", OutputNodeValue(fullPathToFile, diff.ValueNew, 0, outputDir) ?? \" \");\n                sb.AppendFormat(\"<td>{0}</td>\", OutputNodeValue(fullPathToFile, diff.ValueOld, 1, outputDir) ?? \" \");\n                sb.AppendLine(\"</tr>\");\n                count[idx]++;\n            }\n            StateDetail = \"正在输出对比报告\";\n            bool noChange = diffList.Count <= 0;\n            sw.WriteLine(\"<table class=\\\"img{0}\\\">\", noChange ? \" noChange\" : \"\");\n            sw.WriteLine(\"<tr><th colspan=\\\"3\\\"><a name=\\\"{1}\\\">{0}</a> 修改:{2} 新增:{3} 移除:{4}</th></tr>\",\n                imgName, anchorName, count[0], count[1], count[2]);\n            sw.WriteLine(sb.ToString());\n            sw.WriteLine(\"<tr><td colspan=\\\"3\\\"><a href=\\\"#{1}\\\">{0}</a></td></tr>\", \"回到目录\", menuAnchorName);\n            sw.WriteLine(\"</table>\");\n            imgNew.Unextract();\n            imgOld.Unextract();\n            sb = null;\n        }\n\n        private void OutputImg(Wz_Image img, DifferenceType diffType, string imgName, string anchorName, string menuAnchorName, string outputDir, StreamWriter sw)\n        {\n            StateDetail = \"正在解压img\";\n            if (!img.TryExtract())\n                return;\n\n            int idx = 0; ;\n            switch (diffType)\n            {\n                case DifferenceType.Changed:\n                    idx = 0;\n                    break;\n                case DifferenceType.Append:\n                    idx = 1;\n                    break;\n                case DifferenceType.Remove:\n                    idx = 2;\n                    break;\n            }\n            Action<Wz_Node> fnOutput = null;\n            fnOutput = node =>\n            {\n                if (node != null)\n                {\n                    string fullPath = node.FullPath;\n                    string fullPathToFile = node.FullPathToFile;\n                    sw.Write(\"<tr class=\\\"r{0}\\\">\", idx);\n                    sw.Write(\"<td>{0}</td>\", fullPath ?? \" \");\n                    sw.Write(\"<td>{0}</td>\", OutputNodeValue(fullPathToFile, node.Value, 0, outputDir) ?? \" \");\n                    sw.WriteLine(\"</tr>\");\n\n                    if (node.Nodes.Count > 0)\n                    {\n                        foreach (Wz_Node child in node.Nodes)\n                        {\n                            fnOutput(child);\n                        }\n                    }\n                }\n            };\n\n            StateDetail = \"正在输出完整img结构\";\n            sw.WriteLine(\"<table class=\\\"img\\\">\");\n            sw.WriteLine(\"<tr><th colspan=\\\"2\\\"><a name=\\\"{1}\\\">wz_image: {0}</a></th></tr>\", imgName, anchorName);\n            fnOutput(img.Node);\n            sw.WriteLine(\"<tr><td colspan=\\\"2\\\"><a href=\\\"#{1}\\\">{0}</a></td></tr>\", \"回到目录\", menuAnchorName);\n            sw.WriteLine(\"</table>\");\n            img.Unextract();\n        }\n\n        protected virtual string OutputNodeValue(string fullPath, object value, int col, string outputDir)\n        {\n            if (value == null)\n                return null;\n\n            switch (value)\n            {\n                case Wz_Png png:\n                    if (OutputPng)\n                    {\n                        char[] invalidChars = Path.GetInvalidFileNameChars();\n                        string colName = col == 0 ? \"new\" : (col == 1 ? \"old\" : col.ToString());\n                        string fileName = fullPath.Replace('\\\\', '.');\n                        string suffix = \"_\" + colName + \".png\";\n\n                        if (this.HashPngFileName)\n                        {\n                            fileName = ToHexString(MD5Hash(fileName));\n                            // TODO: save file name mapping to another file? \n                        }\n                        else if (Environment.OSVersion.Platform == PlatformID.Win32NT && fileName.Length + suffix.Length > 255)\n                        {\n                            // force hashing if the file name too long.\n                            // TODO: also need to check full file path, we have tested that all existing browsers on Windows cannot load\n                            //       local files with excessively long path.\n                            fileName = ToHexString(MD5Hash(fileName));\n                        }\n                        else\n                        {\n                            for (int i = 0; i < invalidChars.Length; i++)\n                            {\n                                fileName = fileName.Replace(invalidChars[i], '_');\n                            }\n                        }\n\n                        fileName = fileName + suffix;\n                        using (Bitmap bmp = png.ExtractPng())\n                        {\n                            bmp.Save(Path.Combine(outputDir, fileName), System.Drawing.Imaging.ImageFormat.Png);\n                        }\n                        return string.Format(\"<img src=\\\"{0}/{1}\\\" />\", new DirectoryInfo(outputDir).Name, WebUtility.UrlEncode(fileName));\n                    }\n                    else\n                    {\n                        return string.Format(\"png {0}*{1} ({2} bytes)\", png.Width, png.Height, png.DataLength);\n                    }\n\n                case Wz_Uol uol:\n                    return uol.Uol;\n\n                case Wz_Vector vector:\n                    return string.Format(\"({0}, {1})\", vector.X, vector.Y);\n\n                case Wz_Sound sound:\n                    return string.Format(\"sound {0}ms\", sound.Ms);\n\n                case Wz_Convex convex:\n                    return string.Format(\"convex {0}\", string.Join(\" \", convex.Points.Select(vec => $\"({vec.X},{vec.Y})\")));\n\n                case Wz_RawData rawData:\n                    return string.Format(\"rawdata {0} bytes\", rawData.Length);\n\n                case Wz_Video video:\n                    return string.Format(\"video {0} bytes\", video.Length);\n\n                case Wz_Image _:\n                    return \"{ img }\";\n\n                default:\n                    return string.Format(\"<span title=\\\"{0}\\\">{1}</span>\", value.GetType().Name, WebUtility.HtmlEncode(Convert.ToString(value)));\n            }\n        }\n\n        public virtual void CreateStyleSheet(string outputDir)\n        {\n            string path = Path.Combine(outputDir, \"style.css\");\n            if (File.Exists(path))\n                return;\n            StringBuilder css = new StringBuilder();\n            css.AppendLine($\"body {{ font-size:12px; background-color:{ColorToHex(ColorTable[0])}; color:{ColorToHex(ColorTable[1])}; }}\");\n            css.AppendLine($\"a {{ color:{ColorToHex(ColorTable[8])}; }}\");\n            css.AppendLine($\"p.wzf {{ }}\");\n            css.AppendLine($\"table, tr, th, td {{ border:1px solid #ff8000; border-collapse:collapse; }}\");\n            css.AppendLine($\"table {{ margin-bottom:16px; }}\");\n            css.AppendLine($\"th {{ text-align:left; }}\");\n            css.AppendLine($\"table.lst0 {{ }}\");\n            css.AppendLine($\"table.lst1 {{ }}\");\n            css.AppendLine($\"table.lst2 {{ }}\");\n            css.AppendLine($\"table.img {{ }}\");\n            css.AppendLine($\"table.img tr.r0 {{ background-color:{ColorToHex(ColorTable[2])}; color:{ColorToHex(ColorTable[5])}; }}\");\n            css.AppendLine($\"table.img tr.r1 {{ background-color:{ColorToHex(ColorTable[3])}; color:{ColorToHex(ColorTable[6])}; }}\");\n            css.AppendLine($\"table.img tr.r2 {{ background-color:{ColorToHex(ColorTable[4])}; color:{ColorToHex(ColorTable[7])}; }}\");\n            css.AppendLine($\"table.img.noChange {{ display:none; }}\");\n            FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);\n            StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);\n            sw.Write(css.ToString());\n            sw.Flush();\n            sw.Close();\n        }\n\n        private static byte[] MD5Hash(string text)\n        {\n            using (var md5 = MD5.Create())\n            {\n                return md5.ComputeHash(Encoding.UTF8.GetBytes(text));\n            }\n        }\n\n        private static string ToHexString(byte[] inArray)\n        {\n            StringBuilder hex = new StringBuilder(inArray.Length * 2);\n            foreach (byte b in inArray)\n            {\n                hex.AppendFormat(\"{0:x2}\", b);\n            }\n            return hex.ToString();\n        }\n        \n        private static string ColorToHex(Color color)\n        {\n            return $\"#{color.R:X2}{color.G:X2}{color.B:X2}\";\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Comparer/WzFileComparer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\n\nnamespace WzComparerR2.Comparer\n{\n    public class WzFileComparer\n    {\n        public WzFileComparer()\n        {\n            this.PngComparison = WzPngComparison.SizeAndDataLength;\n            ResolvePngLink = true;\n        }\n\n        public WzPngComparison PngComparison { get; set; }\n        public bool IgnoreWzFile { get; set; }\n        public bool ResolvePngLink { get; set; }\n\n        private DisposeQueue _disposeQueue;\n        private List<Wz_Image> _currentWzImg = new List<Wz_Image>();\n\n        public IEnumerable<CompareDifference> Compare(Wz_Node nodeNew, Wz_Node nodeOld)\n        {\n            _currentWzImg.Clear();\n            AppendContext(nodeNew);\n            AppendContext(nodeOld);\n\n            var cmp = Compare(\n                nodeNew == null ? null : new WzNodeAgent(nodeNew).Children,\n                nodeOld == null ? null : new WzNodeAgent(nodeOld).Children);\n\n            foreach (var diff in cmp)\n            {\n                yield return diff;\n            }\n\n\n        }\n\n        public IEnumerable<CompareDifference> Compare(WzVirtualNode nodeNew, WzVirtualNode nodeOld)\n        {\n            _currentWzImg.Clear();\n            foreach (var node in nodeNew.LinkNodes)\n                AppendContext(node);\n            foreach (var node in nodeOld.LinkNodes)\n                AppendContext(node);\n\n            var cmp = Compare(\n               nodeNew == null ? null : new WzVirtualNodeAgent(nodeNew).Children,\n               nodeOld == null ? null : new WzVirtualNodeAgent(nodeOld).Children);\n\n            foreach (var diff in cmp)\n            {\n                yield return diff;\n            }\n        }\n\n        private void AppendContext(Wz_Node node)\n        {\n            Wz_Image wzImg = node.GetNodeWzImage();\n            if (wzImg != null)\n            {\n                _currentWzImg.Add(wzImg);\n            }\n        }\n\n        private IEnumerable<CompareDifference> Compare(ComparableNode nodeNew, ComparableNode nodeOld)\n        {\n            var cmp = Compare(\n                nodeNew == null ? null : nodeNew.Children,\n                nodeOld == null ? null : nodeOld.Children);\n\n            foreach (var diff in cmp)\n            {\n                yield return diff;\n            }\n        }\n\n        private IEnumerable<CompareDifference> Compare(IEnumerable<ComparableNode> nodeNew, IEnumerable<ComparableNode> nodeOld)\n        {\n            if (nodeNew == null && nodeOld == null) //do nothing\n            {\n                yield break;\n            }\n\n            //初始化 排序\n            var arrayNew = new List<ComparableNode>();\n            var arrayOld = new List<ComparableNode>();\n\n            if (nodeNew != null)\n            {\n                arrayNew.AddRange(nodeNew);\n                arrayNew.Sort();\n            }\n\n            if (nodeOld != null)\n            {\n                arrayOld.AddRange(nodeOld);\n                arrayOld.Sort();\n            }\n\n            foreach (var diff in CompareSortedNodes(arrayNew, arrayOld))\n            {\n                yield return diff;\n            }\n\n            //移除引用\n            arrayNew = null;\n            arrayOld = null;\n        }\n\n        private IEnumerable<CompareDifference> CompareSortedNodes(IList<ComparableNode> arrayNew, IList<ComparableNode> arrayOld, Comparison<ComparableNode> compFunc = null)\n        {\n            //逐层对比\n            int l = 0, r = 0;\n            while (l < arrayNew.Count || r < arrayOld.Count)\n            {\n                int? comp = null;\n                if (r == arrayOld.Count) //输出左边\n                {\n                    comp = -1;\n                }\n                else if (l == arrayNew.Count) //输出右边\n                {\n                    comp = 1;\n                }\n                else\n                {\n                    comp = compFunc != null ? compFunc(arrayNew[l], arrayOld[r]) : arrayNew[l].CompareTo(arrayOld[r]);\n                }\n\n                switch (comp)\n                {\n                    case -1:\n                        yield return new CompareDifference(arrayNew[l].LinkNode, null, DifferenceType.Append);\n                        if (CompareChild(arrayNew[l], null))\n                        {\n                            foreach (CompareDifference diff in Compare(arrayNew[l], null))\n                            {\n                                yield return diff;\n                            }\n                        }\n                        l++;\n                        break;\n                    case 0:\n                        //TODO: 试着比较多linkNode的场合。。\n                        if ((arrayNew[l].HasMultiValues || arrayOld[r].HasMultiValues)\n                            && !(arrayNew[l].Value is Wz_File wzf1 && !wzf1.IsSubDir || arrayOld[r].Value is Wz_File wzf2 && !wzf2.IsSubDir) //file跳过\n                            )\n                        {\n                            //对比node的绝对路径\n                            var left = (arrayNew[l] as WzVirtualNodeAgent).Target.LinkNodes;\n                            var right = (arrayOld[r] as WzVirtualNodeAgent).Target.LinkNodes;\n                            var compFunc2 = new Comparison<Wz_Node>((a, b) => string.Compare(a.FullPathToFile, b.FullPathToFile));\n                            left.Sort(compFunc2);\n                            right.Sort(compFunc2);\n\n                            foreach (var diff in CompareSortedNodes(\n                                left.Select(n => (ComparableNode)new WzNodeAgent(n)).ToList(),\n                                right.Select(n => (ComparableNode)new WzNodeAgent(n)).ToList(),\n                                (a, b) => Math.Sign(string.CompareOrdinal(a.LinkNode.FullPath, b.LinkNode.FullPath)))\n                                )\n                            {\n                                yield return diff;\n                            }\n                        }\n                        else\n                        {\n                            bool compared = false;\n                            bool linkFilter = false;\n\n                            //同是png 检测link\n                            if (ResolvePngLink)\n                            {\n                                ComparableNode nodeNew = arrayNew[l],\n                                    nodeOld = arrayOld[r];\n                                PngLinkInfo linkInfoNew, linkInfoOld;\n                                bool linkNew = TryGetLink(nodeNew, out linkInfoNew),\n                                    linkOld = TryGetLink(nodeOld, out linkInfoOld);\n\n                                if (linkNew && !linkOld && nodeOld.Value is Wz_Png) //图片转化为link\n                                {\n                                    var newPng = GetLinkedPng(nodeNew.LinkNode);\n                                    if (newPng != null)\n                                    {\n                                        if (!CompareData(newPng, nodeOld.Value))\n                                        {\n                                            yield return new CompareDifference(nodeNew.LinkNode, nodeOld.LinkNode, DifferenceType.Changed);\n                                        }\n                                        else //链接后图片一致 过滤link标记\n                                        {\n                                            linkFilter = true;\n                                        }\n                                        compared = true;\n                                    }\n                                }\n                                else if (!linkNew && linkOld && nodeNew.Value is Wz_Png) //link恢复为图片\n                                {\n                                    var oldPng = GetLinkedPng(nodeOld.LinkNode);\n                                    if (oldPng != null)\n                                    {\n                                        if (!CompareData(nodeNew.Value, oldPng))\n                                        {\n                                            yield return new CompareDifference(nodeNew.LinkNode, nodeOld.LinkNode, DifferenceType.Changed);\n                                        }\n                                        else //链接后图片一致 过滤link标记\n                                        {\n                                            linkFilter = true;\n                                        }\n                                        compared = true;\n                                    }\n                                }\n                                else if (linkNew && linkOld) //两边都是link\n                                {\n                                    if (linkInfoNew.LinkType == linkInfoOld.LinkType \n                                        && linkInfoNew.LinkUrl == linkInfoOld.LinkUrl) //link没有变动\n                                    {\n                                        compared = true;\n                                    }\n                                    else\n                                    {\n                                        var newPng = GetLinkedPng(nodeNew.LinkNode);\n                                        var oldPng = GetLinkedPng(nodeOld.LinkNode);\n                                        if (newPng != null && oldPng != null)\n                                        {\n                                            if (newPng != oldPng && !CompareData(newPng, oldPng)) //对比有差异 不输出dummy\n                                            {\n                                                //yield return new CompareDifference(nodeNew.LinkNode, nodeOld.LinkNode, DifferenceType.Changed);\n                                            }\n                                            else\n                                            {\n                                                linkFilter = true;\n                                            }\n                                            compared = true;\n                                        }\n                                    }\n                                }\n                            }\n\n                            //正常对比\n                            if (!compared && !CompareData(arrayNew[l].Value, arrayOld[r].Value))\n                            {\n                                yield return new CompareDifference(arrayNew[l].LinkNode, arrayOld[r].LinkNode, DifferenceType.Changed);\n                            }\n\n                            //对比子集\n                            if (CompareChild(arrayNew[l], arrayOld[r]))\n                            {\n                                foreach (CompareDifference diff in Compare(arrayNew[l], arrayOld[r]))\n                                {\n                                    if (linkFilter) // && diff.DifferenceType != DifferenceType.Changed) [s]过滤新增或删除[/s] 全部过滤\n                                    {\n                                        if ((diff.NodeNew?.ParentNode == arrayNew[l].LinkNode\n                                            || diff.NodeOld?.ParentNode == arrayOld[r].LinkNode)) //差异节点为当前的子级\n                                        {\n                                            var nodeText = diff.NodeNew?.Text ?? diff.NodeOld?.Text;\n                                            if (nodeText == \"_inlink\" || nodeText == \"_outlink\")\n                                            {\n                                                continue;\n                                            }\n                                        }\n                                    }\n                                    yield return diff;\n                                }\n                            }\n                        }\n\n                        l++; r++;\n                        break;\n                    case 1:\n                        yield return new CompareDifference(null, arrayOld[r].LinkNode, DifferenceType.Remove);\n                        if (CompareChild(null, arrayOld[r]))\n                        {\n                            foreach (CompareDifference diff in Compare(null, arrayOld[r]))\n                            {\n                                yield return diff;\n                            }\n                        }\n                        r++;\n                        break;\n                    default:\n                        throw new Exception(\"什么鬼\");\n                }\n            }\n        }\n\n        private bool CompareChild(ComparableNode node1, ComparableNode node2)\n        {\n            if ((node1 != null && node1.Value is Wz_File wzf1 && !wzf1.IsSubDir)\n                || (node2 != null && node2.Value is Wz_File wzf2 && !wzf2.IsSubDir))\n            {\n                return !IgnoreWzFile;\n            }\n            return true;\n        }\n\n        private bool TryGetLink(ComparableNode node, out PngLinkInfo linkInfo)\n        {\n            linkInfo = new PngLinkInfo();\n            var png = node.Value as Wz_Png;\n            if (png != null && png.Width == 1 && png.Height == 1)\n            {\n                var node1 = node.LinkNode;\n                WzLib.Wz_Node linkNode;\n                if ((linkNode = node1.Nodes[\"_inlink\"]) != null)\n                {\n                    linkInfo.LinkType = PngLinkType.Inlink;\n                    linkInfo.LinkUrl = linkNode.GetValue<string>();\n                    return true;\n                }\n                else if ((linkNode = node1.Nodes[\"_outlink\"]) != null)\n                {\n                    linkInfo.LinkType = PngLinkType.Outlink;\n                    linkInfo.LinkUrl = linkNode.GetValue<string>();\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        private Wz_Png GetLinkedPng(Wz_Node node)\n        {\n            var wzFile = node.GetNodeWzFile();\n            if (wzFile != null)\n            {\n                var linkNode = node.GetLinkedSourceNode(path =>\n                    PluginBase.PluginManager.FindWz(path, wzFile));\n\n                //添加回收池机制...\n                if (linkNode != null)\n                {\n                    var linkImg = linkNode.GetNodeWzImage();\n                    if (linkImg != null && !_currentWzImg.Contains(linkImg))\n                    {\n                        if (_disposeQueue == null)\n                        {\n                            _disposeQueue = new DisposeQueue(32);\n                        }\n                        _disposeQueue.Add(linkImg, _currentWzImg);\n                    }\n                }\n\n                return linkNode.GetValueEx<Wz_Png>(null);\n            }\n            return null;\n        }\n\n        /// <summary>\n        /// 比较两个节点绑定的值是否相同。\n        /// </summary>\n        /// <param Name=\"dataNew\">新的值。</param>\n        /// <param Name=\"dataOld\">旧的值。</param>\n        /// <returns></returns>\n        public virtual bool CompareData(object dataNew, object dataOld)\n        {\n            // skip virtual dir\n            {\n                if (dataNew is Wz_File fileNew && fileNew.IsSubDir)\n                    dataNew = null;\n                if (dataOld is Wz_File fileOld && fileOld.IsSubDir)\n                    dataOld = null;\n            }\n\n            if (dataNew == null && dataOld == null)\n                return true;\n            if (dataNew == null ^ dataOld == null)\n                return false;\n\n            Type type = dataNew.GetType();\n            if (type != dataOld.GetType())\n                return false;\n\n            if (type.IsClass)\n            {\n                switch (dataNew)\n                {\n                    case string str:\n                        return str == (string)dataOld;\n\n                    case Wz_Image img:\n                        Wz_Image imgOld = (Wz_Image)dataOld;\n                        return img.Size == imgOld.Size && img.Checksum == imgOld.Checksum;\n\n                    case Wz_File file:\n                        Wz_File fileOld = (Wz_File)dataOld;\n                        return file.Type == fileOld.Type;\n\n                    case Wz_Png png:\n                        Wz_Png pngOld = (Wz_Png)dataOld;\n                        switch (this.PngComparison)\n                        {\n                            case WzPngComparison.SizeOnly:\n                                return png.Width == pngOld.Width && png.Height == pngOld.Height;\n\n                            case WzPngComparison.SizeAndDataLength:\n                                return png.Width == pngOld.Width\n                                    && png.Height == pngOld.Height\n                                    && png.DataLength == pngOld.DataLength;\n\n                            case WzPngComparison.Pixel:\n                                if (!(png.Width == pngOld.Width && png.Height == pngOld.Height && png.Format == pngOld.Format && png.Scale == pngOld.Scale))\n                                {\n                                    // we don't compare 'Pages' because KMST set pages to 1 for all PNGs.\n                                    return false;\n                                }\n                                byte[] pixelNew = png.GetRawData();\n                                byte[] pixelOld = pngOld.GetRawData();\n                                if (pixelNew == null || pixelOld == null || pixelNew.Length != pixelOld.Length)\n                                {\n                                    return false;\n                                }\n                                return pixelNew.SequenceEqual(pixelOld);\n\n                            default:\n                                goto case WzPngComparison.SizeAndDataLength;\n                        }\n                        break;\n\n                    case Wz_Vector vector:\n                        Wz_Vector vectorOld = (Wz_Vector)dataOld;\n                        return vector.X == vectorOld.X && vector.Y == vectorOld.Y;\n\n                    case Wz_Uol uol:\n                        return uol.Uol == ((Wz_Uol)dataOld).Uol;\n\n                    case Wz_Sound sound:\n                        Wz_Sound soundOld = (Wz_Sound)dataOld;\n                        return sound.Ms == soundOld.Ms && sound.DataLength == soundOld.DataLength;\n\n                    case Wz_Convex convex:\n                        Wz_Convex convexOld = (Wz_Convex)dataOld;\n                        if (convex.Points.Length != convexOld.Points.Length)\n                        {\n                            return false;\n                        }\n                        for (int i = 0; i < convex.Points.Length; i++)\n                        {\n                            var vectorNew = convex.Points[i];\n                            vectorOld = convexOld.Points[i];\n                            if (vectorNew.X != vectorOld.X || vectorNew.Y != vectorOld.Y) \n                                return false;\n                        }\n                        return true;\n\n                    case Wz_RawData rawData:\n                        Wz_RawData rawDataOld = (Wz_RawData)dataOld;\n                        return rawData.Length == rawDataOld.Length;\n\n\n                    case Wz_Video video:\n                        Wz_Video videoOld = (Wz_Video)dataOld;\n                        return video.Length == videoOld.Length;\n                }\n            }\n\n            return object.Equals(dataNew, dataOld);\n        }\n\n        private abstract class ComparableNode : IComparable<ComparableNode>\n        {\n            public abstract string Name { get; }\n            public abstract object Value { get; }\n            public abstract bool HasMultiValues { get; }\n            public virtual IEnumerable<object> Values\n            {\n                get\n                {\n                    if (this.Value == null) return Enumerable.Empty<object>();\n                    return new[] { this.Value };\n                }\n            }\n            public abstract IEnumerable<ComparableNode> Children { get; }\n            public virtual Wz_Node LinkNode\n            {\n                get { return null; }\n            }\n\n            public int CompareTo(ComparableNode other)\n            {\n                return Math.Sign(string.CompareOrdinal(this.Name, other.Name));\n            }\n        }\n\n        private class WzNodeAgent : ComparableNode\n        {\n            public WzNodeAgent(Wz_Node target)\n            {\n                this.Target = target;\n            }\n\n            public Wz_Node Target { get; private set; }\n\n            public override string Name\n            {\n                get { return this.Target.Text; }\n            }\n\n            public override object Value\n            {\n                get { return this.Target.Value; }\n            }\n\n            public override bool HasMultiValues\n            {\n                get { return false; }\n            }\n\n            public override IEnumerable<ComparableNode> Children\n            {\n                get\n                {\n                    foreach (var node in this.Target.Nodes)\n                    {\n                        yield return new WzNodeAgent(node);\n                    }\n                }\n            }\n\n            public override Wz_Node LinkNode\n            {\n                get\n                {\n                    return this.Target;\n                }\n            }\n        }\n\n        private class WzVirtualNodeAgent : ComparableNode\n        {\n            public WzVirtualNodeAgent(WzVirtualNode target)\n            {\n                this.Target = target;\n            }\n\n            public WzVirtualNode Target { get; private set; }\n\n            public override string Name\n            {\n                get { return this.Target.Name; }\n            }\n\n            public override object Value\n            {\n                get\n                {\n                    return this.Target.LinkNodes.Select(n => n.Value)\n                        .Where(v => v != null)\n                        .FirstOrDefault();\n                }\n            }\n\n            public override bool HasMultiValues\n            {\n                get\n                {\n                    return this.Values.Count() > 1;\n                }\n            }\n\n            public override IEnumerable<object> Values\n            {\n                get\n                {\n                    return this.Target.LinkNodes.Select(n => n.Value)\n                      .Where(v => v != null);\n                }\n            }\n\n            public override IEnumerable<ComparableNode> Children\n            {\n                get\n                {\n                    foreach (var node in this.Target.ChildNodes)\n                    {\n                        yield return new WzVirtualNodeAgent(node);\n                    }\n                }\n            }\n\n            public override Wz_Node LinkNode\n            {\n                get\n                {\n                    if (this.Target.LinkNodes.Count <= 0)\n                    {\n                        return null;\n                    }\n                    else if (this.Target.LinkNodes.Count == 1)\n                    {\n                        return this.Target.LinkNodes[0];\n                    }\n                    else\n                    {\n                        foreach (var node in this.Target.LinkNodes)\n                        {\n                            if (node.Value != null)\n                            {\n                                return node;\n                            }\n                        }\n                        return this.Target.LinkNodes[0];\n                    }\n                }\n            }\n        }\n\n        private class DisposeQueue\n        {\n            public DisposeQueue(int maxCount)\n            {\n                this.MaxCount = maxCount;\n                _list = new LinkedList<Wz_Image>();\n                _dict = new Dictionary<Wz_Image, LinkedListNode<Wz_Image>>();\n            }\n\n            public int MaxCount { get; set; }\n\n            private LinkedList<Wz_Image> _list;\n            private Dictionary<Wz_Image, LinkedListNode<Wz_Image>> _dict;\n\n            public void Add(Wz_Image wzImage)\n            {\n                Add(wzImage, null);\n            }\n\n            public void Add(Wz_Image wzImage, List<Wz_Image> currentImages)\n            {\n                LinkedListNode<Wz_Image> node;\n                if (_dict.TryGetValue(wzImage, out node))\n                {\n                    //提升位置\n                    if (node.Previous != null)\n                    {\n                        _list.Remove(node);\n                        _list.AddFirst(node);\n                    }\n                }\n                else\n                {\n                    //添加item\n                    while (_list.Count >= MaxCount && _list.Count > 0)\n                    {\n                        DisposeLast(currentImages);\n                    }\n\n                    node = _list.AddFirst(wzImage);\n                    _dict.Add(wzImage, node);\n                }\n            }\n\n            public void DisposeAll()\n            {\n                while(_list.Count > 0)\n                {\n                    DisposeLast();\n                }\n            }\n\n            private void DisposeLast()\n            {\n                this.DisposeLast(null);\n            }\n\n            private void DisposeLast(List<Wz_Image> currentImages)\n            {\n                var last = _list.Last;\n                if (currentImages == null || !currentImages.Contains(last.Value))\n                {\n                    last.Value.Unextract();\n                }\n                _dict.Remove(last.Value);\n                _list.Remove(last);\n            }\n        }\n\n        private struct PngLinkInfo\n        {\n            public PngLinkType LinkType { get; set; }\n            public string LinkUrl { get; set; }\n        }\n\n        private enum PngLinkType\n        {\n            None = 0,\n            Inlink = 1,\n            Outlink = 2\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Comparer/WzPngComparison.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Comparer\n{\n    public enum WzPngComparison\n    {\n        SizeOnly,\n        SizeAndDataLength,\n        Pixel\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Comparer/WzVirtualNode.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Comparer\n{\n    public class WzVirtualNode\n    {\n        public WzVirtualNode()\n        {\n            this.LinkNodes = new List<Wz_Node>(4);\n            this.ChildNodes = new List<WzVirtualNode>();\n        }\n\n        public WzVirtualNode(Wz_Node wzNode) : this()\n        {\n            this.Name = wzNode.Text;\n            this.LinkNodes.Add(wzNode);\n        }\n\n        public string Name { get; set; }\n        public List<Wz_Node> LinkNodes { get; private set; }\n        public List<WzVirtualNode> ChildNodes { get; private set; }\n\n        public void AddChild(WzVirtualNode childNode)\n        {\n            this.ChildNodes.Add(childNode);\n        }\n\n        public void AddChild(Wz_Node wzNode)\n        {\n            this.AddChild(wzNode, false);\n        }\n\n        public void AddChild(Wz_Node wzNode, bool addAllChildren)\n        {\n            var childNode = new WzVirtualNode(wzNode);\n            this.AddChild(childNode);\n\n            if (addAllChildren && wzNode.Nodes.Count > 0)\n            {\n                foreach (var node in wzNode.Nodes)\n                {\n                    childNode.AddChild(node, addAllChildren);\n                }\n            }\n        }\n\n        public void Combine(Wz_Node wzNode)\n        {\n            this.LinkNodes.Add(wzNode);\n            bool needCheck = this.ChildNodes.Count > 0;\n\n            foreach (var fromChild in wzNode.Nodes)\n            {\n                //如果当前本身为空 省去检查合并 因为wz本身并不重复...\n                WzVirtualNode toChild = null;\n                if (needCheck)\n                {\n                    toChild = FindChild(fromChild.Text);\n                }\n\n                if (toChild == null) //没有找到 新增\n                {\n                    this.AddChild(fromChild, true);\n                }\n                else if (fromChild.Value == null && toChild.HasNoValue()) //同为目录\n                {\n                    toChild.Combine(fromChild);\n                }\n                else if (fromChild.Nodes.Count <= 0 && !toChild.HasDirectory()) //没有子集 合并测试\n                {\n                    toChild.Combine(fromChild);\n                }\n                else\n                {\n                    throw new Exception(string.Format(\"WZ合并失败，{0}已存在并且存在子级。\", fromChild.FullPathToFile));\n                }\n            }\n        }\n\n        private WzVirtualNode FindChild(string name)\n        {\n            foreach (var child in this.ChildNodes)\n            {\n                if (child.Name == name)\n                {\n                    return child;\n                }\n            }\n            return null;\n        }\n\n        private bool HasNoValue()\n        {\n            foreach (var linkNode in this.LinkNodes)\n            {\n                if (linkNode.Value != null)\n                {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        private bool HasDirectory()\n        {\n            foreach(var linkNode in this.LinkNodes)\n            {\n                if (linkNode.Nodes.Count > 0)\n                {\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        public override string ToString()\n        {\n            return string.Format(\"{0} link:{1} child:{2}\", this.Name, this.LinkNodes.Count, this.ChildNodes.Count);\n        }\n\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    [SectionName(\"WcR2.CharaSim\")]\n    public sealed class CharaSimConfig : ConfigSectionBase<CharaSimConfig>\n    {\n        [ConfigurationProperty(\"selectedFontIndex\")]\n        public ConfigItem<int> SelectedFontIndex\n        {\n            get { return (ConfigItem<int>)this[\"selectedFontIndex\"]; }\n            set { this[\"selectedFontIndex\"] = value; }\n        }\n\n        [ConfigurationProperty(\"autoQuickView\")]\n        public ConfigItem<bool> AutoQuickView\n        {\n            get { return (ConfigItem<bool>)this[\"autoQuickView\"]; }\n            set { this[\"autoQuickView\"] = value; }\n        }\n\n        [ConfigurationProperty(\"skill\")]\n        public CharaSimSkillConfig Skill\n        {\n            get { return (CharaSimSkillConfig)this[\"skill\"]; }\n        }\n\n        [ConfigurationProperty(\"damageSkin\")]\n        public CharaSimDamageSkinConfig DamageSkin\n        {\n            get { return (CharaSimDamageSkinConfig)this[\"damageSkin\"]; }\n        }\n\n        [ConfigurationProperty(\"gear\")]\n        public CharaSimGearConfig Gear\n        {\n            get { return (CharaSimGearConfig)this[\"gear\"]; }\n        }\n\n        [ConfigurationProperty(\"item\")]\n        public CharaSimItemConfig Item\n        {\n            get { return (CharaSimItemConfig)this[\"item\"]; }\n        }\n\n        [ConfigurationProperty(\"recipe\")]\n        public CharaSimRecipeConfig Recipe\n        {\n            get { return (CharaSimRecipeConfig)this[\"recipe\"]; }\n        }\n\n        [ConfigurationProperty(\"mob\")]\n        public CharaSimMobConfig Mob\n        {\n            get { return (CharaSimMobConfig)this[\"mob\"]; }\n        }\n\n        [ConfigurationProperty(\"npc\")]\n        public CharaSimNpcConfig Npc\n        {\n            get { return (CharaSimNpcConfig)this[\"npc\"]; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimDamageSkinConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class CharaSimDamageSkinConfig : ConfigurationElement\n    {\n        [ConfigurationProperty(\"showDamageSkinID\", DefaultValue = true)]\n        public bool ShowDamageSkinID\n        {\n            get { return (bool)this[\"showDamageSkinID\"]; }\n            set { this[\"showDamageSkinID\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showDamageSkin\", DefaultValue = true)]\n        public bool ShowDamageSkin\n        {\n            get { return (bool)this[\"showDamageSkin\"]; }\n            set { this[\"showDamageSkin\"] = value; }\n        }\n\n        [ConfigurationProperty(\"useMiniSize\", DefaultValue = false)]\n        public bool UseMiniSize\n        {\n            get { return (bool)this[\"useMiniSize\"]; }\n            set { this[\"useMiniSize\"] = value; }\n        }\n\n        [ConfigurationProperty(\"alwaysUseMseaFormat\", DefaultValue = false)]\n        public bool AlwaysUseMseaFormat\n        {\n            get { return (bool)this[\"alwaysUseMseaFormat\"]; }\n            set { this[\"alwaysUseMseaFormat\"] = value; }\n        }\n\n        [ConfigurationProperty(\"displayUnitOnSingleLine\", DefaultValue = false)]\n        public bool DisplayUnitOnSingleLine\n        {\n            get { return (bool)this[\"displayUnitOnSingleLine\"]; }\n            set { this[\"displayUnitOnSingleLine\"] = value; }\n        }\n\n        [ConfigurationProperty(\"damageSkinNumber\", DefaultValue = (long)1234567890)]\n        public long DamageSkinNumber\n        {\n            get { return (long)this[\"damageSkinNumber\"]; }\n            set { this[\"damageSkinNumber\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimGearConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class CharaSimGearConfig : ConfigurationElement\n    {\n        [ConfigurationProperty(\"showID\", DefaultValue = true)]\n        public bool ShowID\n        {\n            get { return (bool)this[\"showID\"]; }\n            set { this[\"showID\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showWeaponSpeed\", DefaultValue = true)]\n        public bool ShowWeaponSpeed\n        {\n            get { return (bool)this[\"showWeaponSpeed\"]; }\n            set { this[\"showWeaponSpeed\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showLevelOrSealed\", DefaultValue = true)]\n        public bool ShowLevelOrSealed\n        {\n            get { return (bool)this[\"showLevelOrSealed\"]; }\n            set { this[\"showLevelOrSealed\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showMedalTag\", DefaultValue = false)]\n        public bool ShowMedalTag\n        {\n            get { return (bool)this[\"showMedalTag\"]; }\n            set { this[\"showMedalTag\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimItemConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class CharaSimItemConfig : ConfigurationElement\n    {\n        [ConfigurationProperty(\"showID\", DefaultValue = true)]\n        public bool ShowID\n        {\n            get { return (bool)this[\"showID\"]; }\n            set { this[\"showID\"] = value; }\n        }\n\n        [ConfigurationProperty(\"linkRecipeInfo\", DefaultValue = true)]\n        public bool LinkRecipeInfo\n        {\n            get { return (bool)this[\"linkRecipeInfo\"]; }\n            set { this[\"linkRecipeInfo\"] = value; }\n        }\n\n        [ConfigurationProperty(\"linkRecipeItem\", DefaultValue = true)]\n        public bool LinkRecipeItem\n        {\n            get { return (bool)this[\"linkRecipeItem\"]; }\n            set { this[\"linkRecipeItem\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showNickTag\", DefaultValue = false)]\n        public bool ShowNickTag\n        {\n            get { return (bool)this[\"showNickTag\"]; }\n            set { this[\"showNickTag\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimMobConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class CharaSimMobConfig : ConfigurationElement\n    {\n        [ConfigurationProperty(\"showID\", DefaultValue = true)]\n        public bool ShowID\n        {\n            get { return (bool)this[\"showID\"]; }\n            set { this[\"showID\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimNpcConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class CharaSimNpcConfig : ConfigurationElement\n    {\n        [ConfigurationProperty(\"showID\", DefaultValue = true)]\n        public bool ShowID\n        {\n            get { return (bool)this[\"showID\"]; }\n            set { this[\"showID\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimRecipeConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class CharaSimRecipeConfig : ConfigurationElement\n    {\n        [ConfigurationProperty(\"showID\", DefaultValue = true)]\n        public bool ShowID\n        {\n            get { return (bool)this[\"showID\"]; }\n            set { this[\"showID\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CharaSimSkillConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class CharaSimSkillConfig : ConfigurationElement\n    {\n        [ConfigurationProperty(\"showID\", DefaultValue = true)]\n        public bool ShowID\n        {\n            get { return (bool)this[\"showID\"]; }\n            set { this[\"showID\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showProperties\", DefaultValue = true)]\n        public bool ShowProperties\n        {\n            get { return (bool)this[\"showProperties\"]; }\n            set { this[\"showProperties\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showDelay\", DefaultValue = true)]\n        public bool ShowDelay\n        {\n            get { return (bool)this[\"showDelay\"]; }\n            set { this[\"showDelay\"] = value; }\n        }\n\n        [ConfigurationProperty(\"showReqSkill\", DefaultValue = true)]\n        public bool ShowReqSkill\n        {\n            get { return (bool)this[\"showReqSkill\"]; }\n            set { this[\"showReqSkill\"] = value; }\n        }\n\n        [ConfigurationProperty(\"displayCooltimeMSAsSec\", DefaultValue = true)]\n        public bool DisplayCooltimeMSAsSec\n        {\n            get { return (bool)this[\"displayCooltimeMSAsSec\"]; }\n            set { this[\"displayCooltimeMSAsSec\"] = value; }\n        }\n\n        [ConfigurationProperty(\"displayPermyriadAsPercent\", DefaultValue = true)]\n        public bool DisplayPermyriadAsPercent\n        {\n            get { return (bool)this[\"displayPermyriadAsPercent\"]; }\n            set { this[\"displayPermyriadAsPercent\"] = value; }\n        }\n\n        [ConfigurationProperty(\"ignoreEvalError\", DefaultValue = false)]\n        public bool IgnoreEvalError\n        {\n            get { return (bool)this[\"ignoreEvalError\"]; }\n            set { this[\"ignoreEvalError\"] = value; }\n        }\n\n        [ConfigurationProperty(\"defaultLevel\", DefaultValue = DefaultLevel.LevelMax)]\n        public DefaultLevel DefaultLevel\n        {\n            get { return (DefaultLevel)this[\"defaultLevel\"]; }\n            set { this[\"defaultLevel\"] = value; }\n        }\n\n        [ConfigurationProperty(\"intervalLevel\", DefaultValue = 10)]\n        public int IntervalLevel\n        {\n            get { return (int)this[\"intervalLevel\"]; }\n            set { this[\"intervalLevel\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/CustomCSSConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Configuration;\nusing System.Drawing;\nusing System.Globalization;\nusing System.Linq;\nusing System.Text;\nusing System.Windows.Forms.VisualStyles;\n\nnamespace WzComparerR2.Config\n{\n    [SectionName(\"WcR2.CustomCSS\")]\n    public class CustomCSSConfig : ConfigSectionBase<CustomCSSConfig>\n    {\n        public CustomCSSConfig()\n        {\n            BackgroundColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n            NormalTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n            ChangedBackgroundColor = Color.FromArgb(Int32.Parse(\"fffff4c4\", NumberStyles.HexNumber));\n            AddedBackgroundColor = Color.FromArgb(Int32.Parse(\"ffebf2f8\", NumberStyles.HexNumber));\n            RemovedBackgroundColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n            ChangedTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n            AddedTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n            RemovedTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n            HyperlinkColor = Color.FromArgb(Int32.Parse(\"ff0000ff\", NumberStyles.HexNumber));\n        }\n\n        [ConfigurationProperty(\"backgroundColor\")]\n        public Color BackgroundColor\n        {\n            get { return (Color)this[\"backgroundColor\"]; }\n            set { this[\"backgroundColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"normalTextColor\")]\n        public Color NormalTextColor\n        {\n            get { return (Color)this[\"normalTextColor\"]; }\n            set { this[\"normalTextColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"changedBackgroundColor\")]\n        public Color ChangedBackgroundColor\n        {\n            get { return (Color)this[\"changedBackgroundColor\"]; }\n            set { this[\"changedBackgroundColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"addedBackgroundColor\")]\n        public Color AddedBackgroundColor\n        {\n            get { return (Color)this[\"addedBackgroundColor\"]; }\n            set { this[\"addedBackgroundColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"removedBackgroundColor\")]\n        public Color RemovedBackgroundColor\n        {\n            get { return (Color)this[\"removedBackgroundColor\"]; }\n            set { this[\"removedBackgroundColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"changedTextColor\")]\n        public Color ChangedTextColor\n        {\n            get { return (Color)this[\"changedTextColor\"]; }\n            set { this[\"changedTextColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"addedTextColor\")]\n        public Color AddedTextColor\n        {\n            get { return (Color)this[\"addedTextColor\"]; }\n            set { this[\"addedTextColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"removedTextColor\")]\n        public Color RemovedTextColor\n        {\n            get { return (Color)this[\"removedTextColor\"]; }\n            set { this[\"removedTextColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"hyperlinkColor\")]\n        public Color HyperlinkColor\n        {\n            get { return (Color)this[\"hyperlinkColor\"]; }\n            set { this[\"hyperlinkColor\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/ImageHandlerConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\nusing System.Drawing;\n\nnamespace WzComparerR2.Config\n{\n    [SectionName(\"WcR2.ImageHandler\")]\n    public sealed class ImageHandlerConfig : ConfigSectionBase<ImageHandlerConfig>\n    {\n        public ImageHandlerConfig()\n        {\n            BackgroundColor = Color.White;\n            BackgroundType = ImageBackgroundType.Transparent;\n            MinMixedAlpha = 0;\n            MinDelay = 30;\n        }\n\n        [ConfigurationProperty(\"autoSavePictureFolder\")]\n        public ConfigItem<string> AutoSavePictureFolder\n        {\n            get { return (ConfigItem<string>)this[\"autoSavePictureFolder\"]; }\n            set { this[\"autoSavePictureFolder\"] = value; }\n        }\n\n        [ConfigurationProperty(\"autoSaveEnabled\")]\n        public ConfigItem<bool> AutoSaveEnabled\n        {\n            get { return (ConfigItem<bool>)this[\"autoSaveEnabled\"]; }\n            set { this[\"autoSaveEnabled\"] = value; }\n        }\n\n        [ConfigurationProperty(\"savePngFramesEnabled\")]\n        public ConfigItem<bool> SavePngFramesEnabled\n        {\n            get { return (ConfigItem<bool>)this[\"savePngFramesEnabled\"]; }\n            set { this[\"savePngFramesEnabled\"] = value; }\n        }\n\n        [ConfigurationProperty(\"gifEncoder\")]\n        public ConfigItem<int> GifEncoder\n        {\n            get { return (ConfigItem<int>)this[\"gifEncoder\"]; }\n            set { this[\"gifEncoder\"] = value; }\n        }\n\n        [ConfigurationProperty(\"backgroundType\")]\n        public ConfigItem<ImageBackgroundType> BackgroundType\n        {\n            get { return (ConfigItem<ImageBackgroundType>)this[\"backgroundType\"]; }\n            set { this[\"backgroundType\"] = value; }\n        }\n\n        [ConfigurationProperty(\"backgroundColor\")]\n        public ConfigItem<Color> BackgroundColor\n        {\n            get { return (ConfigItem<Color>)this[\"backgroundColor\"]; }\n            set { this[\"backgroundColor\"] = value; }\n        }\n\n        [ConfigurationProperty(\"minMixedAlpha\")]\n        public ConfigItem<int> MinMixedAlpha\n        {\n            get { return (ConfigItem<int>)this[\"minMixedAlpha\"]; }\n            set { this[\"minMixedAlpha\"] = value; }\n        }\n\n        [ConfigurationProperty(\"minDelay\")]\n        public ConfigItem<int> MinDelay\n        {\n            get { return (ConfigItem<int>)this[\"minDelay\"]; }\n            set { this[\"minDelay\"] = value; }\n        }\n\n        [ConfigurationProperty(\"mosaicInfo\")]\n        public MosaicInfo MosaicInfo\n        {\n            get { return (MosaicInfo)this[\"mosaicInfo\"]; }\n            set { this[\"mosaicInfo\"] = value; }\n        }\n\n        [ConfigurationProperty(\"imageNameMethod\")]\n        public ConfigItem<ImageNameMethod> ImageNameMethod\n        {\n            get { return (ConfigItem<ImageNameMethod>)this[\"imageNameMethod\"]; }\n            set { this[\"imageNameMethod\"] = value; }\n        }\n\n        [ConfigurationProperty(\"paletteOptimized\")]\n        public ConfigItem<bool> PaletteOptimized\n        {\n            get { return (ConfigItem<bool>)this[\"paletteOptimized\"]; }\n            set { this[\"paletteOptimized\"] = value; }\n        }\n\n        [ConfigurationProperty(\"ffmpegBinPath\")]\n        public ConfigItem<string> FFmpegBinPath\n        {\n            get { return (ConfigItem<string>)this[\"ffmpegBinPath\"]; }\n            set { this[\"ffmpegBinPath\"] = value; }\n        }\n\n        [ConfigurationProperty(\"ffmpegArgument\")]\n        public ConfigItem<string> FFmpegArgument\n        {\n            get { return (ConfigItem<string>)this[\"ffmpegArgument\"]; }\n            set { this[\"ffmpegArgument\"] = value; }\n        }\n\n        [ConfigurationProperty(\"ffmpegOutputFileExtension\")]\n        public ConfigItem<string> FFmpegOutputFileExtension\n        {\n            get { return (ConfigItem<string>)this[\"ffmpegOutputFileExtension\"]; }\n            set { this[\"ffmpegOutputFileExtension\"] = value; }\n        }\n    }\n\n    public enum ImageBackgroundType\n    {\n        Transparent = 0,\n        Color = 1,\n        Mosaic = 2,\n    }\n\n    public enum ImageNameMethod\n    {\n        Default = 0,\n        PathToImage = 1,\n        PathToWz = 2\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/MosaicInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\nusing System.Drawing;\n\nnamespace WzComparerR2.Config\n{\n    public class MosaicInfo : ConfigurationElement\n    {\n\n        [ConfigurationProperty(\"color0\")]\n        public Color Color0\n        {\n            get { return (Color)this[\"color0\"]; }\n            set { this[\"color0\"] = value; }\n        }\n\n        [ConfigurationProperty(\"color1\")]\n        public Color Color1\n        {\n            get { return (Color)this[\"color1\"]; }\n            set { this[\"color1\"] = value; }\n        }\n\n        [ConfigurationProperty(\"blockSize\")]\n        public int BlockSize\n        {\n            get { return (int)this[\"blockSize\"]; }\n            set { this[\"blockSize\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Config/WcR2Config.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Configuration;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing System.Xml;\nusing WzComparerR2.Patcher;\n\nnamespace WzComparerR2.Config\n{\n    [SectionName(\"WcR2\")]\n    public sealed class WcR2Config : ConfigSectionBase<WcR2Config>\n    {\n        public WcR2Config()\n        {\n            this.MainStyle = DevComponents.DotNetBar.eStyle.Office2007VistaGlass;\n            this.MainStyleColor = Color.DimGray;\n            this.SortWzOnOpened = true;\n            this.AutoDetectExtFiles = true;\n            this.EnableAutoUpdate = true;\n        }\n\n        /// <summary>\n        /// 获取最近打开的文档列表。\n        /// </summary>\n        [ConfigurationProperty(\"recentDocuments\")]\n        [ConfigurationCollection(typeof(ConfigArrayList<string>.ItemElement))]\n        public ConfigArrayList<string> RecentDocuments\n        {\n            get { return (ConfigArrayList<string>)this[\"recentDocuments\"]; }\n        }\n\n        /// <summary>\n        /// 获取或设置主窗体界面样式。\n        /// </summary>\n        [ConfigurationProperty(\"mainStyle\")]\n        public ConfigItem<DevComponents.DotNetBar.eStyle> MainStyle\n        {\n            get { return (ConfigItem<DevComponents.DotNetBar.eStyle>)this[\"mainStyle\"]; }\n            set { this[\"mainStyle\"] = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置主窗体界面主题色。\n        /// </summary>\n        [ConfigurationProperty(\"mainStyleColor\")]\n        public ConfigItem<Color> MainStyleColor\n        {\n            get { return (ConfigItem<Color>)this[\"mainStyleColor\"]; }\n            set { this[\"mainStyleColor\"] = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置Wz对比报告默认输出文件夹。\n        /// </summary>\n        [ConfigurationProperty(\"comparerOutputFolder\")]\n        public ConfigItem<string> ComparerOutputFolder\n        {\n            get { return (ConfigItem<string>)this[\"comparerOutputFolder\"]; }\n            set { this[\"comparerOutputFolder\"] = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置一个值，指示Wz文件加载后是否自动排序。\n        /// </summary>\n        [ConfigurationProperty(\"sortWzOnOpened\")]\n        public ConfigItem<bool> SortWzOnOpened\n        {\n            get { return (ConfigItem<bool>)this[\"sortWzOnOpened\"]; }\n            set { this[\"sortWzOnOpened\"] = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置一个值，指示Wz文件加载后是否自动排序。\n        /// </summary>\n        [ConfigurationProperty(\"sortWzByImgID\")]\n        public ConfigItem<bool> SortWzByImgID\n        {\n            get { return (ConfigItem<bool>)this[\"sortWzByImgID\"]; }\n            set { this[\"sortWzByImgID\"] = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置一个值，指示Wz加载中对于ansi字符串的编码。\n        /// </summary>\n        [ConfigurationProperty(\"wzEncoding\")]\n        public ConfigItem<int> WzEncoding\n        {\n            get { return (ConfigItem<int>)this[\"wzEncoding\"]; }\n            set { this[\"wzEncoding\"] = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置一个值，指示加载Base.wz时是否自动检测扩展wz文件（如Map2、Mob2）。\n        /// </summary>\n        [ConfigurationProperty(\"autoDetectExtFiles\")]\n        public ConfigItem<bool> AutoDetectExtFiles\n        {\n            get { return (ConfigItem<bool>)this[\"autoDetectExtFiles\"]; }\n            set { this[\"autoDetectExtFiles\"] = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置一个值，指示读取wz是否跳过img检测。\n        /// </summary>\n        [ConfigurationProperty(\"imgCheckDisabled\")]\n        public ConfigItem<bool> ImgCheckDisabled\n        {\n            get { return (ConfigItem<bool>)this[\"imgCheckDisabled\"]; }\n            set { this[\"imgCheckDisabled\"] = value; }\n        }\n\n\n        [ConfigurationProperty(\"patcherSettings\")]\n        [ConfigurationCollection(typeof(PatcherSetting), CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap)]\n        public PatcherSettingCollection PatcherSettings\n        {\n            get { return (PatcherSettingCollection)this[\"patcherSettings\"]; }\n        }\n\n\n        /// <summary>\n        /// 获取或设置一个值，指示Release版本下是否需要自动检查更新。\n        /// </summary>\n        [ConfigurationProperty(\"EnableAutoUpdate\")]\n        public ConfigItem<bool> EnableAutoUpdate\n        {\n            get { return (ConfigItem<bool>)this[\"EnableAutoUpdate\"]; }\n            set { this[\"EnableAutoUpdate\"] = value; }\n        }\n\n        private static readonly HashSet<string> obsoleteElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)\n        {\n            \"wzVersionVerifyMode\",\n        };\n\n        protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)\n        {\n            if (obsoleteElements.Contains(elementName))\n            {\n                reader.Skip();\n                return true;\n            }\n            return base.OnDeserializeUnrecognizedElement(elementName, reader);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/DBConnection.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\nusing System.Data;\nusing System.Text.RegularExpressions;\nusing System.Drawing;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.CharaSimControl;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2\n{\n    public class DBConnection\n    {\n        public DBConnection(StringLinker sl)\n        {\n            this.sl = sl;\n        }\n\n        private StringLinker sl;\n\n        public DataSet GenerateSkillTable()\n        {\n            Wz_Node skillWz = PluginManager.FindWz(Wz_Type.Skill);\n            if (skillWz == null)\n                return null;\n\n            Regex r = new Regex(@\"^(\\d+)\\.img\", RegexOptions.Compiled);\n\n            DataSet ds = new DataSet();\n            DataTable jobTable = new DataTable(\"ms_job\");\n            jobTable.Columns.Add(\"jobID\", typeof(string));\n            jobTable.Columns.Add(\"jobName\", typeof(string));\n\n            DataTable skillTable = new DataTable(\"ms_skill\");\n            skillTable.Columns.Add(\"jobID\", typeof(string));\n            skillTable.Columns.Add(\"skillID\", typeof(string));\n            skillTable.Columns.Add(\"skillName\", typeof(string));\n            skillTable.Columns.Add(\"skillDesc\", typeof(string));\n            skillTable.Columns.Add(\"maxLevel\", typeof(int));\n            skillTable.Columns.Add(\"invisible\", typeof(bool));\n            skillTable.Columns.Add(\"hyper\", typeof(int));\n            skillTable.Columns.Add(\"reqSkill\", typeof(string));\n            skillTable.Columns.Add(\"reqSkillLevel\", typeof(int));\n            skillTable.Columns.Add(\"reqLevel\", typeof(int));\n\n            DataTable skillLevelTable = new DataTable(\"ms_skillLevel\");\n            skillLevelTable.Columns.Add(\"skillID\", typeof(string));\n            skillLevelTable.Columns.Add(\"level\", typeof(int));\n            skillLevelTable.Columns.Add(\"levelDesc\", typeof(string));\n\n            DataTable skillCommonTable = new DataTable(\"ms_skillCommon\");\n            skillCommonTable.Columns.Add(\"skillID\", typeof(string));\n            skillCommonTable.Columns.Add(\"commonName\", typeof(string));\n            skillCommonTable.Columns.Add(\"commonValue\", typeof(string));\n\n            DataTable skillPVPCommonTable = new DataTable(\"ms_skillPVPCommon\");\n            skillPVPCommonTable.Columns.Add(\"skillID\", typeof(string));\n            skillPVPCommonTable.Columns.Add(\"commonName\", typeof(string));\n            skillPVPCommonTable.Columns.Add(\"commonValue\", typeof(string));\n\n            DataTable skillHTable = new DataTable(\"ms_skillH\");\n            skillHTable.Columns.Add(\"skillID\", typeof(string));\n            skillHTable.Columns.Add(\"desc\", typeof(string));\n            skillHTable.Columns.Add(\"pdesc\", typeof(string));\n            skillHTable.Columns.Add(\"h\", typeof(string));\n            skillHTable.Columns.Add(\"ph\", typeof(string));\n            skillHTable.Columns.Add(\"hch\", typeof(string));\n\n            StringResultSkill sr;\n\n            foreach (Wz_Node node in skillWz.Nodes)\n            {\n                //获取职业\n                Match m = r.Match(node.Text);\n                Wz_Image img = node.GetValue<Wz_Image>(null);\n                if (!m.Success)\n                {\n                    continue;\n                }\n                if (img == null || !img.TryExtract())\n                {\n                    continue;\n                }\n                //导入职业\n                string jobID = m.Result(\"$1\");\n                sl.StringSkill2.TryGetValue(jobID, out var _sr);\n                jobTable.Rows.Add(jobID, (_sr != null ? _sr[\"bookName\"] : null));\n\n                //获取技能\n                Wz_Node skillListNode = img.Node.FindNodeByPath(\"skill\");\n                if (skillListNode == null || skillListNode.Nodes.Count <= 0)\n                {\n                    continue;\n                }\n\n                foreach (Wz_Node skillNode in skillListNode.Nodes)\n                {\n                    Skill skill = Skill.CreateFromNode(skillNode, PluginManager.FindWz);\n                    if (skill == null)\n                        continue;\n\n                    // if (skill.Invisible) //过滤不可见技能\n                    //     continue;\n\n                    //导入技能\n                    string skillID = skillNode.Text;\n                    sl.StringSkill2.TryGetValue(skillID, out _sr);\n                    sr = _sr as StringResultSkill;\n                    string reqSkill = null;\n                    int reqSkillLevel = 0;\n                    if (skill.ReqSkill.Count > 0)\n                    {\n                        foreach (var kv in skill.ReqSkill)\n                        {\n                            reqSkill = kv.Key.ToString();\n                            reqSkillLevel = kv.Value;\n                        }\n                    }\n\n                    skillTable.Rows.Add(\n                        jobID,\n                        skillID,\n                        sr != null ? sr.Name : null,\n                        sr != null ? sr.Desc : null,\n                        skill.MaxLevel,\n                        skill.Invisible,\n                        skill.Hyper,\n                        reqSkill,\n                        reqSkillLevel,\n                        skill.ReqLevel\n                    );\n\n\n                    if (!skill.PreBBSkill)\n                    {\n                        //导入技能common\n                        foreach (var kv in skill.Common)\n                        {\n                            skillCommonTable.Rows.Add(\n                                skillID,\n                                kv.Key,\n                                kv.Value\n                                );\n                        }\n                        foreach (var kv in skill.PVPcommon)\n                        {\n                            skillPVPCommonTable.Rows.Add(\n                                skillID,\n                                kv.Key,\n                                kv.Value\n                                );\n                        }\n                        //导入技能说明\n                        skillHTable.Rows.Add(\n                            skillID,\n                            sr != null ? sr[\"desc\"] : null,\n                            sr != null ? sr[\"pdesc\"] : null,\n                            sr != null ? sr[\"h\"] : null,\n                            sr != null ? sr[\"ph\"] : null,\n                            sr != null ? sr[\"hch\"] : null\n                            );\n                    }\n\n                    //导入技能等级\n                    for (int i = 1, j = skill.MaxLevel + (skill.CombatOrders ? 2 : 0); i <= j; i++)\n                    {\n                        skill.Level = i;\n                        string levelDesc;\n                        try\n                        {\n                            levelDesc = SummaryParser.GetSkillSummary(skill, sr, SummaryParams.Default);\n                        }\n                        catch(Exception ex)\n                        {\n                            levelDesc = \"错误：\" + ex.Message;\n                        }\n                       \n                        skillLevelTable.Rows.Add(\n                            skillID,\n                            i,\n                            levelDesc);\n                    }\n                }\n\n                img.Unextract();\n            }\n\n            ds.Tables.Add(jobTable);\n            ds.Tables.Add(skillTable);\n            ds.Tables.Add(skillLevelTable);\n            ds.Tables.Add(skillCommonTable);\n            ds.Tables.Add(skillPVPCommonTable);\n            ds.Tables.Add(skillHTable);\n            return ds;\n        }\n\n        public void OutputCsv(StreamWriter sw, DataTable dt)\n        {\n            for (int i = 0; i < dt.Columns.Count; i++)\n            {\n                DataColumn col = dt.Columns[i];\n                sw.Write(ConvertCell(col.ColumnName));\n\n                if (i < dt.Columns.Count - 1)\n                    sw.Write(\",\");\n                else\n                    sw.WriteLine();\n            }\n\n            foreach (DataRow row in dt.Rows)\n            {\n                for (int i = 0; i < dt.Columns.Count; i++)\n                {\n                    sw.Write(ConvertCell(Convert.ToString(row[i])));\n\n                    if (i < dt.Columns.Count - 1)\n                        sw.Write(\",\");\n                    else\n                        sw.WriteLine();\n                }\n            }\n        }\n\n        private string ConvertCell(string input)\n        {\n            if (input != null)\n            {\n                input = ReplaceQoute(input);\n                if (input.IndexOfAny(\",\\\"\\r\\n\".ToCharArray()) > -1)\n                {\n                    input = \"\\\"\" + input + \"\\\"\";\n                }\n            }\n            return input;\n        }\n\n        private string ReplaceQoute(string input)\n        {\n            if (input == null)\n                return null;\n            if (!input.Contains(\"\\\"\"))\n                return input;\n            return input.Replace(\"\\\"\", \"\\\"\\\"\");\n        }\n\n        public void ExportSkillOption(string outputDir)\n        {\n            Wz_Node skillOption = PluginBase.PluginManager.FindWz(\"Item/SkillOption.img\");\n            Wz_Node itemOption = PluginBase.PluginManager.FindWz(\"Item/ItemOption.img\");\n            Wz_Node item0259 = PluginBase.PluginManager.FindWz(\"Item/Consume/0259.img\");\n            Wz_Node skill8000 = PluginBase.PluginManager.FindWz(\"Skill/8000.img/skill\");\n\n            if (skillOption == null || itemOption == null || item0259 == null || skill8000 == null)\n                return;\n\n            ItemTooltipRender2 itemRender = new ItemTooltipRender2();\n            itemRender.StringLinker = this.sl;\n            itemRender.ShowObjectID = true;\n            SkillTooltipRender2 skillRender = new SkillTooltipRender2();\n            skillRender.StringLinker = this.sl;\n            skillRender.ShowObjectID = true;\n            skillRender.ShowDelay = true;\n\n            string skillImageDir = Path.Combine(outputDir, \"skills\");\n            string itemImageDir = Path.Combine(outputDir, \"items\");\n\n            if (!Directory.Exists(outputDir))\n                Directory.CreateDirectory(outputDir);\n            if (!Directory.Exists(skillImageDir))\n                Directory.CreateDirectory(skillImageDir);\n            if (!Directory.Exists(itemImageDir))\n                Directory.CreateDirectory(itemImageDir);\n\n            StringResult sr;\n\n            FileStream fs = new FileStream(Path.Combine(outputDir, \"SkillOption.html\"), FileMode.Create);\n            StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);\n            try\n            {\n                sw.WriteLine(\"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\");\n                sw.WriteLine(\"<html>\");\n                sw.WriteLine(\"<head>\");\n                sw.WriteLine(\"<meta http-equiv=\\\"content-type\\\" content=\\\"text/html;charset=utf-8\\\">\");\n                sw.WriteLine(\"<title>魂武器系统</title>\");\n                sw.WriteLine(@\"<style type=\"\"text/css\"\">\");\n                sw.WriteLine(\"table, tr, th, td { font-size:12px; border-collapse:collapse; border:1px solid #c0c0c0; }\");\n                sw.WriteLine(\"td { padding:4px 5px; }\");\n                sw.WriteLine(@\"</style>\");\n                sw.WriteLine(\"</head>\");\n                sw.WriteLine(\"<body>\");\n\n                foreach (Wz_Node node in item0259.Nodes)\n                {\n                    int itemID;\n                    if (Int32.TryParse(node.Text, out itemID) && itemID / 1000 == 2591) //02591___\n                    {\n                        sw.WriteLine(@\"<table style=\"\"width:500px; \"\"><tbody>\");\n                        sl.StringItem.TryGetValue(itemID, out sr);\n                        if (sr != null)\n                        {\n                            sw.WriteLine(@\"<tr style=\"\"background-color:#ffcccc; \"\"><td>道具名称</td><td>{0} (id:{1})</td></tr>\", sr == null ? \"null\" : sr.Name, itemID);\n                        }\n\n                        Item item = Item.CreateFromNode(node, PluginManager.FindWz);\n                        if (item != null)\n                        {\n                            itemRender.Item = item;\n                            string imageName = Path.Combine(itemImageDir, item.ItemID + \".png\");\n                            if (!File.Exists(imageName))\n                            {\n                                Bitmap itemImage = itemRender.Render();\n                                itemImage.Save(imageName, System.Drawing.Imaging.ImageFormat.Png);\n                                itemImage.Dispose();\n                            }\n                            sw.WriteLine(@\"<tr><td>道具图片</td><td><img src=\"\"items/{0}.png\"\" title=\"\"{0}\"\" /></td></tr>\", item.ItemID);\n                        }\n\n                        Wz_Node skillOptionNode = skillOption.FindNodeByPath(\"skill\\\\\" + (itemID % 1000 + 1));\n                        if (skillOptionNode != null)\n                        {\n                            int skillId = skillOptionNode.Nodes[\"skillId\"].GetValueEx<int>(-1);\n                            int reqLevel = skillOptionNode.Nodes[\"reqLevel\"].GetValueEx<int>(-1);\n                            int incTableId = skillOptionNode.Nodes[\"incTableID\"].GetValueEx<int>(-1);\n                            int incRTableId = skillOptionNode.Nodes[\"incRTableID\"].GetValueEx<int>(-1);\n                            Wz_Node incNode = null;\n                            string per = null;\n                            if (incTableId >= 0)\n                            {\n                                incNode = skillOption.FindNodeByPath(\"inc\\\\\" + incTableId);\n                            }\n                            else if (incRTableId >= 0)\n                            {\n                                incNode = skillOption.FindNodeByPath(\"incR\\\\\" + incRTableId);\n                                per = \"%\";\n                            }\n                            if (incNode != null)\n                            {\n                                sw.WriteLine(@\"<tr><td rowspan=\"\"3\"\">魂珠属性</td><td>阶段{0}: 提升物攻/魔攻 + {1}{2}</td></tr>\", incNode.Nodes[0].Text, incNode.Nodes[0].Value, per);\n                                sw.WriteLine(@\"<tr><td>...</td></tr>\");\n                                sw.WriteLine(@\"<tr><td>阶段{0}: 提升物攻/魔攻 + {1}{2}</td></tr>\", incNode.Nodes[incNode.Nodes.Count - 1].Text, incNode.Nodes[incNode.Nodes.Count - 1].Value, per);\n                            }\n\n                            sw.WriteLine(\"<tr><td>需求等级</td><td>{0}</td></tr>\", reqLevel);\n                            sl.StringSkill.TryGetValue(skillId, out sr);\n                            if (sr != null)\n                            {\n                                sw.WriteLine(\"<tr><td>技能名称</td><td>{0} (id:{1})</td></tr>\", sr == null ? \"null\" : sr.Name, skillId);\n                            }\n\n                            Skill skill = Skill.CreateFromNode(skill8000.Nodes[skillId.ToString(\"D7\")], PluginManager.FindWz);\n                            if (skill != null)\n                            {\n                                skill.Level = skill.MaxLevel;\n                                skillRender.Skill = skill;\n\n                                string imageName = Path.Combine(skillImageDir, skill.SkillID + \".png\");\n                                if (!File.Exists(imageName))\n                                {\n                                    Bitmap skillImage = skillRender.Render();\n                                    skillImage.Save(Path.Combine(skillImageDir, skill.SkillID + \".png\"), System.Drawing.Imaging.ImageFormat.Png);\n                                    skillImage.Dispose();\n                                }\n                                sw.WriteLine(@\"<tr><td>技能图片</td><td><img src=\"\"skills/{0}.png\"\" title=\"\"{0}\"\" /></td></tr>\", skill.SkillID);\n                            }\n\n                            List<KeyValuePair<int, Potential>> tempOptions = new List<KeyValuePair<int, Potential>>();\n                            int totalProb = 0;\n                            Wz_Node tempOptionNode = skillOptionNode.Nodes[\"tempOption\"];\n\n                            if (tempOptionNode != null)\n                            {\n                                foreach (Wz_Node optionNode in tempOptionNode.Nodes)\n                                {\n                                    int id = optionNode.Nodes[\"id\"].GetValueEx<int>(-1);\n                                    int prob = optionNode.Nodes[\"prob\"].GetValueEx<int>(-1);\n                                    if (id >= 0 && prob >= 0)\n                                    {\n                                        Potential optionItem = Potential.CreateFromNode(itemOption.Nodes[id.ToString(\"000000\")], (reqLevel + 9) / 10);\n                                        if (optionItem != null)\n                                        {\n                                            totalProb += prob;\n                                            tempOptions.Add(new KeyValuePair<int, Potential>(prob, optionItem));\n                                        }\n                                    }\n                                }\n                            }\n\n                            if (tempOptions.Count > 0)\n                            {\n                                for (int i = 0; i < tempOptions.Count; i++)\n                                {\n                                    KeyValuePair<int, Potential> opt = tempOptions[i];\n                                    sw.Write(\"<tr>\");\n                                    if (i == 0)\n                                    {\n                                        sw.Write(@\"<td rowspan=\"\"{0}\"\">附加属性</td>\", tempOptions.Count);\n                                    }\n                                    sw.WriteLine(\"<td>{0} &nbsp; &nbsp;[潜能代码:{1:D6}, 获得几率:{2}/{3}({4:P2})])</td></tr>\", opt.Value.ConvertSummary(),\n                                        opt.Value.code, opt.Key, totalProb, (1.0 * opt.Key / totalProb));\n                                }\n                            }\n\n                        }\n\n                        sw.WriteLine(\"</tbody></table><br/>\");\n                    }\n                }\n\n                sw.WriteLine(\"</body>\");\n                sw.WriteLine(\"</html>\");\n            }\n            finally\n            {\n                sw.Close();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Dotnet4Polyfill.cs",
    "content": "﻿using System;\n\n#if NET462\nnamespace System.Runtime.InteropServices\n{\n    internal static class RuntimeInformation\n    {\n        public static Architecture ProcessArchitecture\n        {\n            get\n            {\n                if (string.Equals(Environment.GetEnvironmentVariable(\"PROCESSOR_ARCHITECTURE\"), \"arm64\", StringComparison.OrdinalIgnoreCase))\n                {\n                    return Architecture.Arm64;\n                }\n                return Environment.Is64BitProcess ? Architecture.X64 : Architecture.X86;\n            }\n        }\n    }\n\n    internal enum Architecture\n    {\n        X86 = 0,\n        X64 = 1,\n        Arm = 2,\n        Arm64 = 3,\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2/Dotnet6Patches.cs",
    "content": "﻿#if NET6_0_OR_GREATER\n\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Drawing;\nusing System.Reflection;\nusing System.Reflection.Emit;\nusing System.Runtime.InteropServices;\nusing System.Windows.Forms;\nusing DevComponents.AdvTree;\nusing DevComponents.AdvTree.Display;\nusing DevComponents.DotNetBar;\nusing DevComponents.DotNetBar.Controls;\nusing HarmonyLib;\n\nnamespace WzComparerR2\n{\n    internal static class Dotnet6Patch\n    {\n        static Dotnet6Patch()\n        {\n            harmony = new Harmony(\"WzComparerR2-Dotnet6Patch\");\n        }\n\n        private static readonly Harmony harmony;\n\n        public static void Patch()\n        {\n            harmony.PatchAll();\n        }\n\n        public static void Unpatch()\n        {\n            harmony.UnpatchAll();\n        }\n    }\n\n    [HarmonyPatch(typeof(ComboBoxEx))]\n    internal class ComboBoxExPatch\n    {\n        [HarmonyPatch(\"ᑧ\"), HarmonyPrefix]\n        public static bool ᑧ(ComboBoxEx __instance, IntPtr ळ)\n        {\n            bool flag = false;\n            if (Environment.Version.Major > 5)\n            {\n                flag = true;\n            }\n            else if (Environment.Version.Major == 5 && Environment.Version.Minor >= 1)\n            {\n                flag = true;\n            }\n            if (flag)\n            {\n                SetWindowTheme(ळ, \" \", \" \");\n            }\n\n            return false;\n        }\n\n        [DllImport(\"UxTheme.dll\", CharSet = CharSet.Auto)]\n        internal static extern int SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList);\n    }\n\n    [HarmonyPatch(typeof(AdvTree))]\n    internal class AdvTreePatch\n    {\n        // this function is decompiled by ilspy\n        public static void ٹ(AdvTree __instance, Node _0652, MouseEventArgs ؾ, Point _٧)\n        {\n            #region private members\n            var GetLayoutPosition = AccessTools.MethodDelegate<Func<MouseEventArgs, Point>>(\n                AccessTools.Method(typeof(AdvTree), \"GetLayoutPosition\", new[] { typeof(MouseEventArgs) }), __instance);\n            var InvokeNodeMouseDown = AccessTools.MethodDelegate<Action<TreeNodeMouseEventArgs>>(\"DevComponents.AdvTree.AdvTree:InvokeNodeMouseDown\", __instance);\n            var ա = AccessTools.FieldRefAccess<AdvTree, int>(__instance, \"ա\");\n            var node_CommandButton = AccessTools.Property(typeof(Node), \"CommandButton\");\n            var ڳ = AccessTools.MethodDelegate<Action<Node, CommandButtonEventArgs>>(\"DevComponents.AdvTree.AdvTree:ڳ\", __instance);\n            var _ײ = AccessTools.FieldRefAccess<AdvTree, bool>(__instance, \"_ײ\");\n            var _ת = AccessTools.FieldRefAccess<AdvTree, bool>(__instance, \"_ת\");\n            var _055E = AccessTools.FieldRefAccess<AdvTree, SelectedNodesCollection>(__instance, \"՞\");\n            var __0603 = AccessTools.FieldRefAccess<AdvTree, bool>(__instance, \"_\\u0603\");\n            var __05EB = AccessTools.FieldRefAccess<AdvTree, eMultiSelectRule>(__instance, \"_\\u05EB\");\n            var selectedNodesCollection_ۺ = AccessTools.Property(typeof(SelectedNodesCollection), \"ۺ \");\n            var _2599__25AA = AccessTools.MethodDelegate<Func<Node, Node>>(\"DevComponents.AdvTree.\\u2599:\\u25AA\");\n            var _2599__25A4 = AccessTools.MethodDelegate<Func<Node, Node>>(\"DevComponents.AdvTree.\\u2599:\\u25A4\");\n            var ٮ = AccessTools.MethodDelegate<Action<EventArgs>>(\"DevComponents.AdvTree.AdvTree:ٮ\", __instance);\n            var ڊ = AccessTools.MethodDelegate<Func<Node, int, int, Point, Cell>>(\"DevComponents.AdvTree.AdvTree:ڊ\", __instance);\n            var cell_GetEnabled = AccessTools.Method(\"DevComponents.AdvTree.Cell:GetEnabled\");\n            var cell_CheckBoxBoundsRelative = AccessTools.Property(typeof(Cell), \"CheckBoxBoundsRelative\");\n            var cell_SetMouseDown = AccessTools.Method(\"DevComponents.AdvTree.Cell:SetMouseDown\");\n            var _0602 = AccessTools.StaticFieldRefAccess<AdvTree, string>(\"\\u0602\");\n            var _059B = AccessTools.FieldRefAccess<AdvTree, object>(__instance, \"\\u059B\");\n            #endregion\n\n            #region method body\n#if true\n            Point layoutPosition = GetLayoutPosition(ؾ);\n            InvokeNodeMouseDown(new TreeNodeMouseEventArgs(_0652, ؾ.Button, ؾ.Clicks, ؾ.Delta, layoutPosition.X, layoutPosition.Y));\n            if (ؾ.Button == MouseButtons.Left)\n            {\n                if (NodeDisplay.GetNodeRectangle(eNodeRectanglePart.ExpandHitTestBounds, _0652, _٧).Contains(layoutPosition) && ؾ.Clicks == 1 && _0652.ExpandVisibility != eNodeExpandVisibility.Hidden)\n                {\n                    ա = 0;\n                    _0652.Toggle(eTreeAction.Mouse);\n                    return;\n                }\n                if (/*_0652.CommandButton*/(bool)node_CommandButton.GetValue(_0652))\n                {\n                    ա = 0;\n                    if (NodeDisplay.GetNodeRectangle(eNodeRectanglePart.CommandBounds, _0652, _٧).Contains(layoutPosition))\n                    {\n                        ڳ(_0652, new CommandButtonEventArgs(eTreeAction.Mouse, _0652));\n                        return;\n                    }\n                }\n                Rectangle nodeRectangle = NodeDisplay.GetNodeRectangle(eNodeRectanglePart.NodeContentBounds, _0652, _٧);\n                if ((nodeRectangle.Contains(layoutPosition) || (_ײ && layoutPosition.Y >= nodeRectangle.Y && layoutPosition.Y <= nodeRectangle.Bottom)) && _0652.TreeControl != null)\n                {\n                    if (_0652.TreeControl.SelectedNode != _0652)\n                    {\n                        ա = 0;\n                    }\n                    if (_0652.Selectable)\n                    {\n                        if (_ת && _055E.Count > 0 && Control.ModifierKeys == Keys.None && ؾ.Button == MouseButtons.Left)\n                        {\n                            __0603 = true;\n                        }\n                        else\n                        {\n                            __0603 = false;\n                        }\n                        if (_ת && _055E.Count > 0 && (Control.ModifierKeys == Keys.Shift || Control.ModifierKeys == Keys.Control))\n                        {\n                            ա = 0;\n                            if (__05EB == eMultiSelectRule.SameParent && _055E[0].Parent != _0652.Parent)\n                            {\n                                return;\n                            }\n                            if (Control.ModifierKeys == Keys.Shift && _055E.Count > 0)\n                            {\n                                Node node = _055E[0];\n                                Node node2 = _0652;\n                                bool flag = false;\n                                /*_055E.ۺ = true;*/\n                                selectedNodesCollection_ۺ.SetValue(_055E, true);\n                                try\n                                {\n                                    while (_055E.Count > 1)\n                                    {\n                                        _055E.Remove(_055E[_055E.Count - 1], eTreeAction.Mouse);\n                                        flag = true;\n                                    }\n                                }\n                                finally\n                                {\n                                    /*_055E.ۺ = false;*/\n                                    selectedNodesCollection_ۺ.SetValue(_055E, false);\n                                }\n                                if (node2 != node)\n                                {\n                                    if (node2.Bounds.Y > node.Bounds.Y)\n                                    {\n                                        /*_055E.ۺ = true;*/\n                                        selectedNodesCollection_ۺ.SetValue(_055E, true);\n                                        try\n                                        {\n                                            do\n                                            {\n                                                if (!node2.IsSelected && node2.Selectable && (__05EB == eMultiSelectRule.AnyNode || (__05EB == eMultiSelectRule.SameParent && _055E.Count > 0 && _055E[0].Parent == node2.Parent)))\n                                                {\n                                                    _055E.Add(node2, eTreeAction.Mouse);\n                                                }\n                                                node2 = _2599__25AA(node2);\n                                            }\n                                            while (node != node2 && node2 != null);\n                                            return;\n                                        }\n                                        finally\n                                        {\n                                            /*_055E.ۺ = false;*/\n                                            selectedNodesCollection_ۺ.SetValue(node, false);\n                                            ٮ(EventArgs.Empty);\n                                        }\n                                    }\n                                    /*_055E.ۺ = true;*/\n                                    selectedNodesCollection_ۺ.SetValue(node, true);\n                                    try\n                                    {\n                                        do\n                                        {\n                                            if (!node2.IsSelected && node2.Selectable && (__05EB == eMultiSelectRule.AnyNode || (__05EB == eMultiSelectRule.SameParent && _055E.Count > 0 && _055E[0].Parent == node2.Parent)))\n                                            {\n                                                _055E.Add(node2, eTreeAction.Mouse);\n                                            }\n                                            node2 = _2599__25A4(node2);\n                                        }\n                                        while (node != node2 && node2 != null);\n                                        return;\n                                    }\n                                    finally\n                                    {\n                                        /*_055E.ۺ = false;*/\n                                        selectedNodesCollection_ۺ.SetValue(node, false);\n                                        ٮ(EventArgs.Empty);\n                                    }\n                                }\n                                if (flag)\n                                {\n                                    ٮ(EventArgs.Empty);\n                                }\n                            }\n                            else if (_0652.IsSelected)\n                            {\n                                _055E.Remove(_0652, eTreeAction.Mouse);\n                            }\n                            else\n                            {\n                                _055E.Add(_0652, eTreeAction.Mouse);\n                            }\n                            return;\n                        }\n                        if (!_0652.IsSelected)\n                        {\n                            __instance.SelectNode(_0652, eTreeAction.Mouse);\n                            if (_0652.TreeControl == null || _0652.TreeControl.SelectedNode != _0652)\n                            {\n                                return;\n                            }\n                        }\n                    }\n                    Cell cell = ڊ(_0652, layoutPosition.X, layoutPosition.Y, _٧);\n                    if (cell == null)\n                    {\n                        return;\n                    }\n                    bool flag2 = false;\n                    if (cell.CheckBoxVisible && /*cell.GetEnabled()*/AccessTools.MethodDelegate<Func<bool>>(cell_GetEnabled, cell)())\n                    {\n                        Rectangle checkBoxBoundsRelative = /*cell.CheckBoxBoundsRelative*/(Rectangle)cell_CheckBoxBoundsRelative.GetValue(cell);\n                        checkBoxBoundsRelative.Offset(NodeDisplay.GetNodeRectangle(eNodeRectanglePart.NodeBounds, _0652, _٧).Location);\n                        if (checkBoxBoundsRelative.Contains(layoutPosition))\n                        {\n                            if (cell.CheckBoxThreeState)\n                            {\n                                if (cell.CheckState == CheckState.Checked)\n                                {\n                                    cell.SetChecked(CheckState.Indeterminate, eTreeAction.Mouse);\n                                }\n                                else if (cell.CheckState == CheckState.Unchecked)\n                                {\n                                    cell.SetChecked(CheckState.Checked, eTreeAction.Mouse);\n                                }\n                                else if (cell.CheckState == CheckState.Indeterminate)\n                                {\n                                    cell.SetChecked(CheckState.Unchecked, eTreeAction.Mouse);\n                                }\n                            }\n                            else\n                            {\n                                cell.SetChecked(!cell.Checked, eTreeAction.Mouse);\n                            }\n                            flag2 = true;\n                            ա = 0;\n                        }\n                    }\n                    if (_0652.SelectedCell != cell)\n                    {\n                        ա = 1;\n                    }\n                    else if (!flag2)\n                    {\n                        ա++;\n                    }\n                    _0652.SetSelectedCell(cell, eTreeAction.Mouse);\n                    /*cell.SetMouseDown(over: true);*/\n                    AccessTools.MethodDelegate<Action<bool>>(cell_SetMouseDown, cell)(true);\n                }\n                else\n                {\n                    ա = 0;\n                }\n            }\n            else\n            {\n                if (ؾ.Button != MouseButtons.Right || _0652.TreeControl == null)\n                {\n                    return;\n                }\n                if (!_0652.IsSelected)\n                {\n                    __instance.SelectNode(_0652, eTreeAction.Mouse);\n                }\n                if ((!__instance.MultiSelect && _0652.TreeControl.SelectedNode != _0652) || _0652.ContextMenu == null)\n                {\n                    return;\n                }\n                // the main purpose is to remove these lines, ContextMenu is obsoleted and has been removed from netcore.\n                /*\n                if (_0652.ContextMenu is ContextMenu)\n                {\n                    ContextMenu contextMenu = _0652.ContextMenu as ContextMenu;\n                    contextMenu.Show(this, new Point(ؾ.X, ؾ.Y));\n                }\n                */\n                else if (_0652.ContextMenu.GetType().FullName == \"System.Windows.Forms.ContextMenuStrip\")\n                {\n                    _0652.ContextMenu.GetType().InvokeMember(\"Show\", BindingFlags.InvokeMethod, null, _0652.ContextMenu, new object[2]\n                    {\n                    __instance,\n                    new Point(ؾ.X, ؾ.Y)\n                    });\n                }\n                else if (_0652.ContextMenu.GetType().FullName == \"DevComponents.DotNetBar.ButtonItem\")\n                {\n                    Point point = __instance.PointToScreen(new Point(ؾ.X, ؾ.Y));\n                    ((PopupItem)_0652.ContextMenu).SetSourceControl(__instance);\n                    _0652.ContextMenu.GetType().InvokeMember(\"Popup\", BindingFlags.InvokeMethod, null, _0652.ContextMenu, new object[1] { point });\n                }\n                else\n                {\n                    if (!_0652.ContextMenu.ToString().StartsWith(_0602) || _059B == null)\n                    {\n                        return;\n                    }\n                    string text = _0652.ContextMenu.ToString().Substring(_0602.Length);\n                    object obj = _059B.GetType().InvokeMember(\"ContextMenus\", BindingFlags.GetProperty, null, _059B, null);\n                    int num = (int)obj.GetType().InvokeMember(\"IndexOf\", BindingFlags.InvokeMethod, null, obj, new string[1] { text });\n                    if (num >= 0)\n                    {\n                        IList list = obj as IList;\n                        object obj2 = list[num];\n                        try\n                        {\n                            obj2.GetType().InvokeMember(\"SetSourceControl\", BindingFlags.InvokeMethod, null, obj2, new object[1] { __instance });\n                        }\n                        catch\n                        {\n                        }\n                        Point point2 = __instance.PointToScreen(new Point(ؾ.X, ؾ.Y));\n                        obj2.GetType().InvokeMember(\"Popup\", BindingFlags.InvokeMethod, null, obj2, new object[1] { point2 });\n                    }\n                }\n            }\n#endif\n            #endregion\n        }\n\n        [HarmonyPatch(\"OnMouseDown\"), HarmonyTranspiler]\n        public static IEnumerable<CodeInstruction> OnMouseDown(IEnumerable<CodeInstruction> instructions)\n        {\n            List<CodeInstruction> modifiedIL = new();\n            var searchMethodInfo = AccessTools.Method(typeof(AdvTree), \"ٹ\");\n            foreach (var instruction in instructions)\n            {\n                if (instruction.Calls(searchMethodInfo))\n                {\n                    var replaceMethodInfo = AccessTools.Method(typeof(AdvTreePatch), \"ٹ\");\n                    var inst = new CodeInstruction(OpCodes.Call, replaceMethodInfo);\n                    modifiedIL.Add(inst);\n                }\n                else\n                {\n                    modifiedIL.Add(instruction);\n                }\n            }\n            return modifiedIL;\n        }\n    }\n}\n\n#endif"
  },
  {
    "path": "WzComparerR2/DownloadingItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Threading;\nusing System.Net;\nusing System.IO;\n\nnamespace WzComparerR2\n{\n    public class DownloadingItem\n    {\n        public DownloadingItem(string url, string path)\n        {\n            this.url = url;\n            this.path = path;\n        }\n\n        string url;\n        string path;\n        DateTime lastModified;\n        long fileLength;\n        Thread thread;\n        WebResponse response;\n        Stream responseStream;\n\n        public string Url\n        {\n            get { return url; }\n        }\n\n        public string Path\n        {\n            get { return path; }\n            set { path = value; }\n        }\n\n        public DateTime LastModified\n        {\n            get { return lastModified; }\n        }\n\n        public long FileLength\n        {\n            get { return fileLength; }\n        }\n\n        public void GetFileLength()\n        {\n            var uri = new Uri(this.url);\n            switch (uri.Scheme.ToLower())\n            {\n                case \"http\":\n                case \"https\":\n                    ServicePointManager.SecurityProtocol = (SecurityProtocolType)(3072 | 12288); //TLS1.2/TLS1.3\n                    GetFileLengthHttp();\n                    break;\n\n                case \"ftp\":\n                    GetFileLengthFtp();\n                    break;\n            }\n        }\n\n        private void GetFileLengthHttp()\n        {\n            try\n            {\n                var req = WebRequest.Create(url) as HttpWebRequest;\n                req.Timeout = 15000;\n                using (var resp = req.GetResponse() as HttpWebResponse)\n                {\n                    this.lastModified = resp.LastModified;\n                    this.fileLength = resp.ContentLength;\n                }\n            }\n            catch (Exception ex)\n            {\n                this.fileLength = 0;\n                throw;\n            }\n        }\n\n        private void GetFileLengthFtp()\n        {\n            try\n            {\n                var req = WebRequest.Create(url) as FtpWebRequest;\n                req.Method = WebRequestMethods.Ftp.GetFileSize;\n                req.Timeout = 15000;\n                using (var resp = req.GetResponse() as FtpWebResponse)\n                {\n                    this.fileLength = resp.ContentLength;\n                }\n            }\n            catch (Exception ex)\n            {\n                throw;\n            }\n\n            try\n            {\n                var req = WebRequest.Create(url) as FtpWebRequest;\n                req.Method = WebRequestMethods.Ftp.GetDateTimestamp;\n                req.Timeout = 15000;\n                using (var resp = req.GetResponse() as FtpWebResponse)\n                {\n                    this.lastModified = resp.LastModified;\n                }\n            }\n            catch (Exception ex)\n            {\n                throw;\n            }\n        }\n\n        public void StartDownload()\n        {\n            if (thread == null)\n            {\n                thread = new Thread(tryStartDownload);\n                thread.Start();\n            }\n        }\n\n        private void tryStartDownload()\n        {\n            try\n            {\n                WebRequest request = WebRequest.Create(url);\n                request.Timeout = 15000;\n                response = request.GetResponse();\n                responseStream = response.GetResponseStream();\n                response.Close();\n            }\n            catch (Exception)\n            {\n            }\n            finally\n            {\n            }\n        }\n\n        public void StopDownload()\n        {\n            if (response != null)\n            {\n                try\n                {\n                    response.Close();\n                    response = null;\n                    thread.Abort();\n                    thread = null;\n                }\n                catch (Exception)\n                {\n                }\n                finally\n                {\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/FrmAbout.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmAbout\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmAbout));\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.labelX2 = new DevComponents.DotNetBar.LabelX();\n            this.labelX3 = new DevComponents.DotNetBar.LabelX();\n            this.lblAsmVer = new DevComponents.DotNetBar.LabelX();\n            this.lblFileVer = new DevComponents.DotNetBar.LabelX();\n            this.lblCopyright = new DevComponents.DotNetBar.LabelX();\n            this.buttonX1 = new DevComponents.DotNetBar.ButtonX();\n            this.advTree1 = new DevComponents.AdvTree.AdvTree();\n            this.elementStyle1 = new DevComponents.DotNetBar.ElementStyle();\n            this.lblClrVer = new DevComponents.DotNetBar.LabelX();\n            this.labelX4 = new DevComponents.DotNetBar.LabelX();\n            ((System.ComponentModel.ISupportInitialize)(this.advTree1)).BeginInit();\n            this.SuspendLayout();\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(12, 33);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(68, 18);\n            this.labelX1.TabIndex = 0;\n            this.labelX1.Text = \"程序版本：\";\n            // \n            // labelX2\n            // \n            this.labelX2.AutoSize = true;\n            // \n            // \n            // \n            this.labelX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX2.Location = new System.Drawing.Point(11, 57);\n            this.labelX2.Name = \"labelX2\";\n            this.labelX2.Size = new System.Drawing.Size(68, 18);\n            this.labelX2.TabIndex = 1;\n            this.labelX2.Text = \"文件版本：\";\n            // \n            // labelX3\n            // \n            this.labelX3.AutoSize = true;\n            // \n            // \n            // \n            this.labelX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX3.Location = new System.Drawing.Point(11, 81);\n            this.labelX3.Name = \"labelX3\";\n            this.labelX3.Size = new System.Drawing.Size(68, 18);\n            this.labelX3.TabIndex = 2;\n            this.labelX3.Text = \"版权所有：\";\n            // \n            // lblAsmVer\n            // \n            this.lblAsmVer.AutoSize = true;\n            // \n            // \n            // \n            this.lblAsmVer.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblAsmVer.Location = new System.Drawing.Point(73, 33);\n            this.lblAsmVer.Name = \"lblAsmVer\";\n            this.lblAsmVer.Size = new System.Drawing.Size(13, 16);\n            this.lblAsmVer.TabIndex = 4;\n            this.lblAsmVer.Text = \"-\";\n            // \n            // lblFileVer\n            // \n            this.lblFileVer.AutoSize = true;\n            // \n            // \n            // \n            this.lblFileVer.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblFileVer.Location = new System.Drawing.Point(73, 57);\n            this.lblFileVer.Name = \"lblFileVer\";\n            this.lblFileVer.Size = new System.Drawing.Size(13, 16);\n            this.lblFileVer.TabIndex = 5;\n            this.lblFileVer.Text = \"-\";\n            // \n            // lblCopyright\n            // \n            this.lblCopyright.AutoSize = true;\n            // \n            // \n            // \n            this.lblCopyright.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblCopyright.Location = new System.Drawing.Point(73, 81);\n            this.lblCopyright.Name = \"lblCopyright\";\n            this.lblCopyright.Size = new System.Drawing.Size(13, 16);\n            this.lblCopyright.TabIndex = 6;\n            this.lblCopyright.Text = \"-\";\n            // \n            // buttonX1\n            // \n            this.buttonX1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX1.DialogResult = System.Windows.Forms.DialogResult.OK;\n            this.buttonX1.Location = new System.Drawing.Point(115, 187);\n            this.buttonX1.Name = \"buttonX1\";\n            this.buttonX1.Size = new System.Drawing.Size(75, 23);\n            this.buttonX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX1.TabIndex = 8;\n            this.buttonX1.Text = \"关掉我\";\n            // \n            // advTree1\n            // \n            this.advTree1.AccessibleRole = System.Windows.Forms.AccessibleRole.Outline;\n            this.advTree1.AllowDrop = true;\n            this.advTree1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.advTree1.BackColor = System.Drawing.SystemColors.Window;\n            // \n            // \n            // \n            this.advTree1.BackgroundStyle.Class = \"TreeBorderKey\";\n            this.advTree1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.advTree1.DoubleClickTogglesNode = false;\n            this.advTree1.DragDropEnabled = false;\n            this.advTree1.DragDropNodeCopyEnabled = false;\n            this.advTree1.ExpandWidth = 4;\n            this.advTree1.HideSelection = true;\n            this.advTree1.Location = new System.Drawing.Point(12, 103);\n            this.advTree1.Name = \"advTree1\";\n            this.advTree1.NodeStyle = this.elementStyle1;\n            this.advTree1.PathSeparator = \";\";\n            this.advTree1.Size = new System.Drawing.Size(280, 78);\n            this.advTree1.Styles.Add(this.elementStyle1);\n            this.advTree1.TabIndex = 9;\n            this.advTree1.Text = \"advTree1\";\n            // \n            // elementStyle1\n            // \n            this.elementStyle1.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.elementStyle1.Name = \"elementStyle1\";\n            this.elementStyle1.TextColor = System.Drawing.SystemColors.ControlText;\n            // \n            // lblClrVer\n            // \n            this.lblClrVer.AutoSize = true;\n            // \n            // \n            // \n            this.labelX4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblClrVer.Location = new System.Drawing.Point(73, 9);\n            this.lblClrVer.Name = \"lblClrVer\";\n            this.lblClrVer.Size = new System.Drawing.Size(13, 16);\n            this.lblClrVer.TabIndex = 11;\n            this.lblClrVer.Text = \"-\";\n            // \n            // labelX4\n            // \n            this.labelX4.AutoSize = true;\n            // \n            // \n            // \n            this.labelX4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX4.Location = new System.Drawing.Point(12, 9);\n            this.labelX4.Name = \"labelX4\";\n            this.labelX4.Size = new System.Drawing.Size(62, 18);\n            this.labelX4.TabIndex = 10;\n            this.labelX4.Text = \"CLR版本：\";\n            // \n            // FrmAbout\n            // \n            this.AcceptButton = this.buttonX1;\n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.CancelButton = this.buttonX1;\n            this.ClientSize = new System.Drawing.Size(304, 221);\n            this.Controls.Add(this.lblClrVer);\n            this.Controls.Add(this.labelX4);\n            this.Controls.Add(this.advTree1);\n            this.Controls.Add(this.buttonX1);\n            this.Controls.Add(this.lblCopyright);\n            this.Controls.Add(this.lblFileVer);\n            this.Controls.Add(this.lblAsmVer);\n            this.Controls.Add(this.labelX3);\n            this.Controls.Add(this.labelX2);\n            this.Controls.Add(this.labelX1);\n            this.DoubleBuffered = true;\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n            this.Icon = ((System.Drawing.Icon)(resources.GetObject(\"$this.Icon\")));\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"FrmAbout\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"关于\";\n            ((System.ComponentModel.ISupportInitialize)(this.advTree1)).EndInit();\n            this.ResumeLayout(false);\n            this.PerformLayout();\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.LabelX labelX2;\n        private DevComponents.DotNetBar.LabelX labelX3;\n        private DevComponents.DotNetBar.LabelX lblAsmVer;\n        private DevComponents.DotNetBar.LabelX lblFileVer;\n        private DevComponents.DotNetBar.LabelX lblCopyright;\n        private DevComponents.DotNetBar.ButtonX buttonX1;\n        private DevComponents.AdvTree.AdvTree advTree1;\n        private DevComponents.DotNetBar.ElementStyle elementStyle1;\n        private DevComponents.DotNetBar.LabelX lblClrVer;\n        private DevComponents.DotNetBar.LabelX labelX4;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmAbout.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Data;\nusing System.Drawing;\nusing System.Text;\nusing System.Windows.Forms;\nusing System.Diagnostics;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing DevComponents.AdvTree;\n\nnamespace WzComparerR2\n{\n    public partial class FrmAbout : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmAbout()\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n\n            this.lblClrVer.Text = string.Format(\"{0} ({1})\", Environment.Version, RuntimeInformation.ProcessArchitecture);\n            this.lblAsmVer.Text = GetAsmVersion().ToString();\n            this.lblFileVer.Text = GetFileVersion().ToString();\n            this.lblCopyright.Text = GetAsmCopyright().ToString();\n            GetPluginInfo();\n        }\n\n        private Version GetAsmVersion()\n        {\n            return this.GetType().Assembly.GetName().Version;\n        }\n\n        private string GetFileVersion()\n        {\n            return this.GetAsmAttr<AssemblyInformationalVersionAttribute>()?.InformationalVersion\n                ?? this.GetAsmAttr<AssemblyFileVersionAttribute>()?.Version;\n        }\n\n        private string GetAsmCopyright()\n        {\n            return this.GetAsmAttr<AssemblyCopyrightAttribute>()?.Copyright;\n        }\n\n        private void GetPluginInfo()\n        {\n            this.advTree1.Nodes.Clear();\n\n            if (PluginBase.PluginManager.LoadedPlugins.Count > 0)\n            {\n                foreach (var plugin in PluginBase.PluginManager.LoadedPlugins)\n                {\n                    string nodeTxt = string.Format(\"{0} <font color=\\\"#808080\\\">{1} ({2})</font>\",\n                        plugin.Instance.Name, \n                        plugin.Instance.Version,\n                        plugin.Instance.FileVersion);\n                    Node node = new Node(nodeTxt);\n                    this.advTree1.Nodes.Add(node);\n                }\n            }\n            else\n            {\n                string nodeTxt = \"<font color=\\\"#808080\\\">没有加载插件_(:з」∠)_</font>\";\n                Node node = new Node(nodeTxt);\n                this.advTree1.Nodes.Add(node);\n            }\n        }\n\n        private T GetAsmAttr<T>()\n        {\n            object[] attr = this.GetType().Assembly.GetCustomAttributes(typeof(T), true);\n            if (attr != null && attr.Length > 0)\n            {\n                return (T)attr[0];\n            }\n            return default(T);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/FrmAbout.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Drawing\" name=\"System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />\n  <data name=\"$this.Icon\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n    <value>\n        AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAABMLAAATCwAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAKAIPFJ0aQlz0EjRN8RtCXfATOlbwDjBK8BY+W/AWPl3wDStD8AIPG/IAFSfsAQUJhAEA\n        AUoAAAACAAAAAAAAAAAAAAAAAAAAAAEJDWMLNFXzCi9P8govTvALMlHwCjJQ8As1U/MGJTvkAAEDcAAA\n        AGgAAQJqAAAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAALxxPcuEkdbH/QJze/y2Cxf9Epun/MpTf/yN9vf84n+r/OaDp/yV9\n        wf8aX47/Ay5T/wU3VP8FMlf6BhckwAABACcAAAAAAAAAAAAAABYKMU3MGoDH/xVqqf8RaKb/EWmq/x+F\n        zv8bgtH/GHa7/w9Vh/8JPWX/AB83/wAVJ+MADxS/AAAAVAABAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMHC4Mzj9T/KofI/zuPzv8re7X/QJvc/y6L\n        0f8hdrH/NJTb/zWW2/8hdbP/MY/U/ws4Xf8BM1j/BkBo/wlDcf8CCRGQAAAAAAAAAAABCQ5tElqU/xlu\n        sf8ff8b/GXvB/xZ1uf8ca6b/IIHE/xp8xv8Vb6//Gni5/xNbkP8DLkv/ADBS/wAmQP8AChCDAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4TN03MNpjg/yd/\n        v/87kM//K326/0Cb3P8ui9H/IXaz/zWV2/8tiMz/MIG+/zmb5v8gaJz/ASlL/wIyWf8GRXD/BSM76QAA\n        AAsAAAAACic8yBhvtP8ge7z/FnW8/xh3v/8bfcT/FHK1/xZno/8de8T/GHS6/xNjnv8eg9L/EViQ/wAp\n        Qv8ANFf/AB43+wAAADkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAArHVWB9zmf7P8nfr3/O5DQ/yt8uf9Am9z/LovP/yF2tP82l93/KILC/ziKxv87n+T/JXq5/wAn\n        Q/8ENV3/Bjxj/wcwU/4AAABIAAAALx9Wgewnf7z/MpPY/yiJz/8fdrb/HG+v/yCAx/8SZqT/GnGy/xh6\n        vv8VaKL/HHq8/xl6xP8LQ27/ACpI/wEzVP8FGinWAAAAJwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAIHjiJ0rv84nOX/Jny+/zqMy/8rf7X/QJng/y6N0f8hda//Npfd/yqC\n        w/8nf73/NZXa/yZ8vP8RSG7/ADBS/wE2XP8EOl7/AAMEdAMFCFohbqX/M4zM/zaW3f8uicn/OIvH/zCJ\n        x/8kfb7/G3Oz/xZmo/8ef8v/E2al/yB5uP8agMr/EF6Y/wQzVf8ALEr/ATde/wEaMNgAAAAoAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQoiM7oke7v/NZfe/ymAu/8xjc7/KXm2/z+c\n        3P8tjM//I3i5/zSZ3v8pgcP/I3u8/zGP1v8kfbv/KHSs/wAkQ/8EOF//BTld/wAJDrIUNFDNKYbI/yiC\n        wf8vjNL/Knq2/zuZ3/8zktj/I3qz/y6Jz/8kd7L/IoHE/xVxtv8RZJz/GnjC/xRqqP8OW5T/ACdC/wAv\n        U/8FO2P/ARwx2wAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAlkcYpL/LInI/zCR\n        2f8sfrb/QJ3i/yh3tf9Dmt3/NZPZ/yiHzv89mNz/LoC6/z+Z2P8rh8r/Kn++/zGN1v8KOFr/ADFV/wQ5\n        X/8CLUz/G1mH/y6FxP87ltv/KobE/yt+vv8zltz/NZbb/ymCxP8ogsL/KXe5/zCO0f8ng8j/Gmmf/yGD\n        yv8WbrP/E2qn/w9Rgf8AJkH/ADVc/wY+Z/8BGSXSAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAADRiwKoPH/y5/vP9Gm9z/L3u3/1Gs7f83gbn/WLDo/06o5/8xjtH/Vq3t/ziEvP9huPD/LXm2/0eb\n        2/9PrO//FUZr/wAsUf8CNVn/BjNT/y2Jy/8terv/QaLk/yBxr/82ldj/M5HV/zea4v8th8v/KYLD/yZ8\n        uf8ti8z/N5Xe/yl/vv8bcbb/FnCz/xZnpv8cf8P/BjJX/wIuUf8FO2L/BjdV/wEIDHQAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAuEj1e4UKY1/9HnNr/Lnu2/0GSz/9Uru//OYnI/2Ky7P9Amt3/RZ/f/1Ss\n        6f86hbz/Ybbw/zOLyP9IoeP/WbT1/x9gkf8AKkn/ACpM/x9fkP84l+P/LH22/0Cc4v8ec7D/L47R/zaV\n        3/8oerT/E0Zx/yuExP8wjdL/Ina0/zeZ3v8pgML/H3S0/xp3vv8VZqT/G3vD/xRspf8AJ0H/AzFX/wdA\n        av8FN17/AQgPcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMFVyxun/8+ktH/VrLz/0uj4v9Sq+r/U6zs/zaL\n        yP9etOz/M5HU/0yi4/9Xs/X/O5fX/2O08P8zktX/SaPk/1Gr7f81gsD/HEVu/wYpRv9Pot//T6rq/zN+\n        t/87n+P/H3Ox/zGP0/82mdz/CzZZ/wAiO/8lcaj/MZDY/yN4tP83l97/KILA/yuCwf8qjdf/EV+Z/x13\n        u/8afcn/DE17/wIrSP8EOmP/CEh0/wk+a/8CCA90AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhsyQblBm9f/PZDQ/1Wv\n        7f9Rqur/U6fr/0yl5f8+ltv/Vq3u/zmU2P9PqOr/O36v/zCDw/9jt/P/NZHT/12s7P9Oqej/N4O9/0iS\n        yf83e63/U7Dz/1Gp6P8xfbf/Ua3t/zF/uf88m9//Kn++/wU0U/8BLVT/D0Jk/zCN0/8nd7X/N5jj/yZ/\n        u/86jcz/OJvf/yR4uf8eerj/GnrC/xl3vP8FNFX/ATNW/wlAav8JTID/CT1m/wAJDHUAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        ACMfSmndTazu/0KZ3f9Uq+j/Uarq/1St6/88mNj/UqHh/1qw8P83mN7/I2eX/wAZMv8nb6T/Zr79/zmW\n        1/9So+T/Tqnm/zeDtv9iuvL/Uq7w/1Kq6v9Qpef/L327/1iv7/8zfrj/SKfq/x5ik/8AKEX/AzZe/wIw\n        TP8eaZ//JX/A/zaX3/8rhcT/M4bD/zWV3P8yjtP/Gmyn/xp6v/8YgMf/E2Wj/wEoR/8EPGD/CUR0/wpS\n        iP8FJD3gAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAFDhaFJny6/1qz9P8sjtH/U6jn/06p5f9Sq+r/Qp/f/02g4P9kvvv/Mnqr/wAi\n        Qf8ALFD/IlR4/2K98/9HouX/L47S/1av8P9Ajsr/QpbQ/1Ou7v9Pqur/UKXl/y98tv9Tru//NYXC/0qc\n        2/8RRGn/ADBU/wU5YP8CKUn/BiI3/yJ5uf81ltr/NJHZ/yFysf81lNj/LozP/yd8t/8ritP/FnW6/xuA\n        yP8MS3r/Ai5Q/whAbP8ISXn/CEx//wMgNtAAAAAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdJVFx6Tia4v9Xru3/RZze/0+m4/9quvT/bLrz/1Go\n        4/9YrOb/e8P7/x5HZf8ALlb/ATdb/wswTf9aoMf/Y7vy/z6W2f9etPH/PpPR/zmKyP9Vr/D/Uqrn/1Cp\n        6/8zicj/Uq7u/0me3v8scqr/CjVS/wMzWv8DOmD/AB0x5QAAAHAcVoX0N57m/zKV1v8qerb/QZze/yyJ\n        z/8wf7v/OJvb/xp5wf8YecD/G3u//wc7Yf8DOlz/CUJz/wlOgv8KTH7/Bh0xywACAjsAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgYIdkeYz/9AneP/XrHs/2Sx\n        7f9Hn9v/fcX6/3TB+P9Al9j/fMj//02LtP8AJkb/ATZd/wQ4XP8AKlH/Llx4/3rI+/9Kod3/XK/u/0Cd\n        2/9Ind7/Vq7r/1Cq6f9QqOj/MZDT/1et7v9KpeT/CUNq/wAvU/8GOmX/BTxm/wALEJEAAAAADCQ1sTOS\n        1/8yk9r/KXe3/0Cd3P8zk9r/JHe5/yyJyv8phcz/Fne8/xl/yP8PW5T/AS1O/wdDav8JRnn/CU6A/wlH\n        dv8EGCW8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAn\n        N69ot+//U6nl/2Cv5f9ywPb/Rp3e/3K89v9ywfb/P5zY/3C87/8cRWX/AC5U/wM2W/8FOWD/ACE16AIF\n        CZ9mocb5Tqno/2Sy7v9cr+n/TaHf/1at7P9Rqev/T6Tn/zCO0f9TrvD/S5/c/wY6Yv8GPGL/B0hy/wcn\n        Q+gAAAAwAAAAAAAAAEgkZpj/NZzj/yl6tf9Amdz/M5Ta/yR7tv82js//MZHX/xt5wP8aesL/EmWg/wUw\n        Vf8EO2D/CUV5/wpNfv8JSXr/BBkrvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAABY2XHrygNH//1+w6f9SpOD/fcb7/0Cb2v9ote7/dsD5/0Sd2v9mp9P/AyZC/wA3\n        XP8GNl3/AjVY/wAJE5gAAAAILUhYyW7C+f9Fnt3/dsH5/0Ob2v9bru3/Tqno/1Gp5/81kNT/W7Hw/0ia\n        2P8HNFb/BUNx/wtEdv8BChORAAAAAAAAAAAAAAABDSU5uTKU3P8qfLn/QJzd/zKS2/8qg8H/NYjI/zWV\n        2v8mg8n/GHjB/xZqqP8ZY5f/BTZb/wdDcP8MTIH/Bzlh/wIMF5MAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBwtwYaLQ/3XH//90wPj/PJfV/3jA9v9fsev/TqPh/1+v\n        5v9Xr+f/O2iK/wAsTP8ENV3/BDxg/wMnRv8AAABOAAAAAAMDBmFmoMz/UKrn/2S06/9MoeD/YrXq/1eu\n        7f9LpuT/PZTU/2e6+/8nZJb/Ajha/wpOg/8EK0XnAAAALAAAAAAAAAAAAAAAAAAAAEwjZZf6LYHD/zua\n        3P8xkdb/MpLV/x90rf81lNv/MJTX/xlzs/8Za6j/IIDF/wpIdv8DPWH/CUp//wQkOfAAAABCAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHSkvo3S+8/9yv/r/dMX4/1an\n        4/9vuOr/eMP6/0yj4f9Bmdf/ecr8/yxSc/8AKk7/BTZb/wE3X/8AEyLJAAAAFwAAAAAAAAAIMklh1Vuz\n        7P9Xqef/Y7Hr/0ui3f93xPr/Uqvs/zuY1/9dsfH/G099/wVDc/8JRnX/AwgOjQAAAAAAAAAAAAAAAAAA\n        AAAAAAAACyQ3sip8t/9AneL/MZDW/zGP0/8gdrL/M5PY/zaW2/8ieb//HG2o/yCEy/8Wb7L/BDxf/wlH\n        dv8EJDvyAAAASgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALDNn\n        gvZ/zP//db/4/3K/9/9tvvX/Vafh/3vC9v9vwPf/d8P8/2qw3P8RN1j/ADFW/wI2XP8DOWH/Ag4TrgAA\n        AAAAAAAAAAAAAAMDCGVgm8T/RaTg/2268v9Gnt3/db72/2u79f8+oOL/NH+8/wk9Yf8LU4v/AClB7QAA\n        ACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYcTXP6RKLr/zGT1/8xjdL/InSz/zWV2v80k9r/LIbJ/xxu\n        rP8fgMf/GnzG/w1Qhf8EPmb/ARky5QAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAACY0YYX2WbTu/3m/8/9xvvf/dcH4/1Kp5P+Ex/n/gsf8/5rd//93q8P/ACBD/wE4\n        X/8ENFr/B0Nv/wMPHLUAAAAAAAAAAAAAAAAAAAALLERd0kei5v96xfr/Qp3a/2m28f95w/r/SaXn/xpe\n        kv8JR3X/BTJX+wECA1YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEyk1sD6e4f8yk9n/L4zT/yx9\n        tP89md//MpPY/y6Ky/8hdLD/Hn/F/xp8wv8Tb6//BTRW/wAEBX0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICglcYpe4/2i++v9WquP/f8X7/2++9/9qtvH/bLns/5zZ\n        //+f3P//Woae/wApTP8DMlr/BDtj/wc4XP8ACA6AAAAAAAAAAAAAAAAAAAAAAAkKDWdAiLv/YLLw/2W1\n        7v9In+D/hNT//1uw3/8AOmj/AzVV/AADBlsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AEMkZpX8Nprm/zSW2f8nfrz/K4bH/zWV3f8uh83/Ini0/x9/xv8ad8H/G3/I/wdCcv8AAAB4AAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADR4qql+06P96wfr/WK7q/3q/\n        9P+Nzf//iMz4/2i06/+V0f//nN3+/ydHYv8ALVT/BThf/wZDb/8GKEP6AAAAMgAAAAAAAAAAAAAAAAAA\n        AAAAAAASFzdSzVm48/9wwvb/R6Td/2Sev/89Y3foAAgUsgAFCV0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAABDCEyrDSW2v82l9//J3u9/yiAw/82mN3/LovM/yJ5tP8ghc//GH7L/xVo\n        pv8HKD/IAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkm\n        L6dLot3/abfv/2y78/9ir+f/n9f//5jV//9kse3/h8z9/5PR7P8eRWD/ACtV/wg+Yv8HRXP/AyhE+wAA\n        ADcAAAAAAAAAAAAAAAAAAAAAAAAAAAkLDlImZJDzOWOG5xEmNbkEBQZYAAAALAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0iYpL7OJ/o/yd/vf8pgsT/Npjc/zCM\n        1P8kfL//Fm2p/wYwS9MBCAtuAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAWJS2naLns/1Cl6P+Lzv7/abns/3/E8/+e2f//Ya/p/5TY//9pmLL/ACBE/wM4\n        X/8FQGv/CENw/wUnQ/sAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBQAAAAC8AAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABADMFAgF+LT9KwE5wifdKa4HwPGB++jVj\n        hv87ZYb/OWSD/wwwX/8DKFn/ATNZ/AEJDr4AAAJvAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEh0mom6+8P9csev/lNL8/3a+8v9yvPD/m9j//3rA\n        8v97yPz/a5ev/wAlSP8EPmj/B0Bp/wZBcP8ELET7AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBMfI5lLdI7zUZ3W/3q8\n        7P+n4///od///6bh//+t5v//iNX//3zJ//9vvv3/Xa7x/yd0yP8CWqn/AFua/gAlP9IAAgJdAAAAEQAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGjJOYs17yvz/Wq3s/4vL\n        +P92v/H/dL7w/5vU//+Mzvz/YbLl/ydKZv8AK1P/CD5k/wdBa/8HQXD/AyxE+wAAADcAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAaEBwjpEh0\n        h+5osN7/OJjq/yaT7P8ikvT/I5Pu/x2N8v9FpPH/YrT1/6fW+v+h1v//dcD//4LF//+DzP//So7V/wBp\n        wP8Aes//AFmc/wAhN7EAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AEVSjaj/hNL//4jM+P9os+r/ldL9/2Ox6f+W1P3/isz9/2i45/8iQlz/ADFZ/wg/af8IQGr/B0Rx/wUp\n        QvsAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAABWJTpPyXXA9P+E0P//MpDg/w11z/8pmPL/LJbw/yiX8v8rlfD/JJPy/xqM8f84nO//m9T7/3/E\n        //92w///ecL//4LN/v9Eis//AGG0/wB/1f8Abbv/AB0usgAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAABCTYSl/pHZ//+Rz/v/Xq/q/5zY/v9drun/ldP9/43P//9Xos7/GDxY/wA4\n        Xv8GPmj/B0Bq/wVDbv8EKET7AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAANFBt6WZOx/XjG9P+Cyv//Wqzy/wluyP8lj+j/LZfz/yqY8P8rlPH/K5jw/yuV\n        8v8tl+7/F47y/4fF9/+Qz///db3//3vC//96wP//g8f//0uQ0v8CZLj/AHnN/wBntf8ACA6LAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQlSJpv6Y3v//jM37/2Ky6f+a1f7/Xq7n/5XP\n        /P+T2v//JWKU/wAkRv8IPmn/B0Fq/wc/Z/8IQXH/BjFL+wAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJERZ0X53F/4HT//9+wv//gMf//zOY7P8GaMP/FoHY/y6b\n        9P8rlfP/K5Xx/y+V8f8qmPP/J5Xv/yGR8f+Ty/n/isr//3a+/v93wv3/ecD//3vG//9qrur/EFep/wVg\n        sP8EacL/ADBY4gAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJUiKb+n9///4vM\n        +v9isOj/mdf+/2Kw6v+T0v3/k9f//yVmlP8AKkz/B0Jq/wc+aP8GP2j/B0h1/wcwUvsAAAA3AAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALFBV1bKDB/4TT//94vf//eML//33E\n        //80l+z/BGvF/w9xx/8bgd7/KJTu/yua8v8rk/D/J5Dv/ymY8v99vPj/lNH//3O8//98xPz/eL/8/4HF\n        //9Fn+T/E4LU/x6P5P8trPz/HY/p/xVyvP8EGCKOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAA9Yo6n/Z3j//+My/r/X7Hp/5rT/v9dsOj/lM/8/5TZ//8lZpn/AC1N/wg/av8GP2j/BD9p/wlK\n        f/8GMVH7AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOf6Gj/qbk\n        //+g0vz/hsz//4nR//+Y2f//Tqrs/wRmwf8LccT/CG/E/w1uxf8Mb8r/Bm7L/zGV6P+o1v7/k8///3G+\n        //95wvv/eL77/3rB//97xvz/IozU/ySm9f8Xi+P/Kaj//yuq//8vuf//DDRM0gAAAAcAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAcm2Wp/+f3///jcv7/2Gy6f+a1P7/XrDo/5XS/f+U2P//JWuc/wEx\n        Vv8IQWv/Bj9m/whBbP8KUIX/BjBR+wAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAEDSpBu0up6/8ekvX/MZjw/7De+/+g5///nOb//5vh/v8ujtr/AmvJ/wdvyf8ahtj/LJDi/0yk\n        6/92v/7/esT//3zG//+Byf7/gsf//4HI//+Dxv//iMr+/yiK0f8Lgtb/HZrw/yyr//8qpP//KbX//w4z\n        TNIAAAAHAAAAAAAAAAAAAAAAAAAAAAADAE4AAAAAAAcBRwBGDftQhKX/oeL//4zN+v9gsOf/ndf+/1+u\n        6/+U0P3/lNj//yZrov8AM1T/BkFt/wY8af8IRXP/ClGF/wUtUPsAAAA3AAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAASQxPjP4hmPf/Mpv2/yCO8P+i1/r/pen//5zg//+d5P//kNr8/2bA\n        +f9WsPX/abj7/3/E//99x///gsf//3vB+/9Pjtb/PoHJ/zuZ3P88lt3/O5rd/0KY2/8ahtX/JaP5/yqn\n        //8opv//KrH//yeQz/8HEyGKAAAAAAAAAAAAAAAAAAAAAAAAAAABMwXcBSkQhwEHBXgJbz//LF6G/4nQ\n        //+KzPf/U6TY/47L+f9VqN7/lNH8/5TX//8hYpT/ADNa/wdAaf8HQWr/Ckh6/wlQhf8JL0/7AAAANwAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBYpMq4Mb8b/EHjT/yCK6f8OeNf/esfs/6Lp\n        //+b4f//m+T//5zk//+m6f//ktj//3nB//96wf//hcf//2ar6v8ZY7T/A2q5/wBow/8AcMX/AHjP/wB3\n        0v8AddX/E5ju/zCy//8prv//LrL//yOP0/8IFB2FAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAQUCUQ5X\n        NeARhz7/D5Yo/wc8C/8lbJv/QJri/yt0sP9Vruz/V6Ta/5bV/f+Mx/L/DT5f/wU+aP8GPmn/Bj9r/wlM\n        ff8KToL/BjFQ+wAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElcip79OJnm/wBj\n        v/8DacD/OZLV/5bd+f+g5P//l+H//5zh//+c4v//lt3//37G//92vv//fsf//2Sp6P8RZrf/AHfM/wB6\n        0P8AftL/AIDW/wB3zP8ARHXrACNB0wEmPtQQXZTuI5jj/x6Iwf8FGieLAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAMzHdIELhjNC24d/w6fJf8LjxT/DGxN/wxAc/8FQy3/JXq+/0Kb3f+Kzvr/b7jw/ws9\n        Yv8GPmf/BUBo/wlEcf8JSn3/C0t6/wYxUfoAAAAxAAAAAgAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAWJy2ykdn3/4rV/f9Vq+L/Z7jp/5/k//+g6v//meH//57k//+X5P//n+T//5Xd//94wf//e8D//37E\n        /f8jbLf/AHHK/wB8z/8AetD/AH3U/wBstf4AER+pAAAANwAAAAAAAAABAAEDSQIECYwAAgRoAAAABAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFAtaDGk6/A+hOP8Mkgr/CWYA/wyRIf8HPxr/Dmww/xJT\n        lf8ne73/W7Ly/1au7f8KOF3/Azpg/wc8Zv8LS33/BTpn/wc5av8BJUP8AQgAkAEiANEAAwBiAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAeRlVb0Jng/v+e6P//pez//6bp//+b5P//meD//5nh//+b4v//neL//5ni\n        //+GzP//dbz9/4DK//9cotn/AmC8/wB+0f8AeM//AHzS/wB5y/8AFiauAAAAAQAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsHLBTZBU8L/whm\n        Bv8IXQD/BEMC/wdWCf8EHhv/BhwY/w0+Yv8QSoX/AiVA/wAuUf8BM1r/AyhM/wEdGP8DHwv/BjoI/wpM\n        AP8DHwCnAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZpi1vv+g6P//nOL//5zh//+X3///neL//5vi\n        //+e4v//muP//5zi//+c4f//g83//3jB//96vfj/IHHA/wByyv8Aes7/AHzQ/wN90f8Fc8n/AAwWoAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe\n        Ar0ITCnKBEoY9AGFFf8PlT//B38C/wVaAP8ISxf/EJot/wNdAP8BMAD/BUUA/wMoB/8CDhH/AhUQ/wU3\n        BP8FQQD/BEgA/wZKAP8DQgD/AykAzwIkALgAAwAoAAAAAAAAAAAAAAAAAAAAAAAAACk5WWzbxfH//5jh\n        //+b5P//neD//5nh//+Z5P//muH//53i//+b4f//nOX//4TL//94w///crf2/wtftf8AfdP/AXvP/wV4\n        zv8Ae9L/AHfI/wAOGqMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAJAKXBlUt2g9MIfRbRAf/OS8K/weWLv8SpDb/E5o//wp4C/8GVwD/CXsA/wls\n        AP8JcgD/BDwA/wQxAP8FPQD/BkIA/wU+AP8EOQD/AzIA/wMiAPMHVwD0AS4A1AAAAA0AAAAAAAAAAAAA\n        AAAAAAAVMEFNyc7y/v+b4///mOD//5rl//+c4v//m+D//53k//+c4///md///53k//+HzP//fMH7/3e8\n        9/8QZLj/AHnO/wB70P8Dfc3/AHvV/wB2w/8ADROeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAEOqThTzemMq/w97Mv8JciH/DpQb/wVL\n        Av8Fagf/CEkS/wZbAP8GWwD/BEUA/wZEAP8DLAD/AygA/wMnAP8DIwD/BEUA/wg6AP8BFADEAAIARgAG\n        AG0AAAAMAAAAAAAAAAAAAAAADB03xJzK6//S9f//muD//53k//+a4f//muH//5rk//+a4f//muL//5nj\n        //+d5P//ldv//3zJ//92uvb/DF21/wB/0v8AfNL/AHfN/wB90v8CftX/ATtr4QAAAC0AAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAACsAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAMOkCvToBt/wdb\n        S/8OcXb/Ah8F/wVNAP8FPQD/C20S/xKcQP8OjjT/CGwC/wRVAP8FIgD/B0gA/wdrAP8GUAD/BDAA/wQ0\n        AP8GUAD/B1QA/wACAF0AAAAAAAAAAAAAAAAAAAAAAAAAAGBeX/D5////xO///5ri//+Y3v//m+T//53h\n        //+a4///nOH//5vj//+c3v//meL//5vj//+P0v//dr36/zN+yf8AWLH/A2/D/wF+0v8Ad87/BIDY/wFu\n        uv8AEBuPAAAABAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAQgAfL7UAWIj/ACU5sgAAAAkAAAAAAAAAAAAA\n        AAAAAAAPB2R58ALd//8S0+v/QP///0KXnf8HWQP/BD8A/wZfAP8HcAL/C3sT/wM6Ef8Ljxv/A0QA/wdp\n        AP8KiQD/BWQA/whMAP8DNQD/BUoA/wQ8AP8DEwCcAAAAAAAAAAAAAAAAAAAAAAAAAABhXlzw9////8Ty\n        //+X3v//muP//53i//+X4f//nOP//5rg//+b4f//muH//53j//+j5///aLfn/xyB1P8wlen/KIDa/wZh\n        uv8AeM7/AHzR/wB3zf8Ag9z/AGWs/gAmQNIAAgNeAAAATwAAAEsADReCACZD2ABIfe4Adsf/Ap78/wOM\n        2/8ACRGLAAAAAAAAAAAAAAAAAAAABEI7I8jQ3rL/xv/0/7P///9wxcX/CVgb/wAvLv8COBT/CYIT/xOE\n        KP9CZV//FH5K/wVLNv8ARkD/BlEA/wg9AP8DOQD/BUoA/wdJAPwEJgDGASoA5wAGAGwAAAAAAAAAAAAA\n        AAAAAAAAYV5d8PP////T8P7/ruj+/5Xg//+a4f//nuD//5zk//+Z4v//nOL//5nj//+i6f//gs3s/xFx\n        yf8klfH/KZjx/zCh/P8Wccn/AGvB/wF80v8Ces7/AHjN/wOB2f8Agtn/AFyb/wBWlP8AVZD/AGWu/wCF\n        4P8AhN3/AnHI/wOM4P8Dofb/AlWD5AABAUIAAAAAAAAAAAAAAAAFAABaxoo7///ttv/b////RpeO/wFI\n        Iv8EYH7/AKe4/wNiT/8wl5T//7JP/21eHP8AVTD/AJjI/xCDtv9Qamn/BjAG/wIzAP8AAAB6AAAABAAB\n        ACsAAAArAAAAAAAAAAAAAAAAAAAAAGBcW+vw/v//3fT9/8ju/v+T3///muX//5ri//+Y4f//neH//5ni\n        //+c4v//n+b//zuW1/8IbMn/KJPu/yuX8/8rlfD/L5jx/xBsxP8BccX/BHzT/wB6z/8Fecz/AXnR/wCC\n        2f8Bgd7/AILb/wCB2/8AfND/AGjE/wNqwf8FnfT/BJbs/wSh+P8AV4TuAAgOcwAAAAAAAAAAAAAAACYU\n        AXmqtpH/Ov///0fq6v96oIz/UWFH/6rWqP9M4t//Tvz//8XktP/Zp1b/N0MZ/wiTtf8Xve7/C7To/wAW\n        EO4ANAD/ADMA4QADAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQDg5yvsHD/+f8///R9Pv/nuP//5ze\n        //+b4v//nuL//5vi//+Z5P//n9///5jg/f8Uds3/CWzD/xuF2/8unPb/LZbx/yqX9f8wmvP/DV24/wB4\n        zv8AfND/AH3T/wB/zv8AetL/Bn7P/wF50f8Accb/B2O6/zF3uv8ZbcD/AJXm/wOW7f8Cnff/AIXJ/wAb\n        J78AAAAAAAAAAAAAAAAAAAAAABkbdACAkPaAx7D/8r5e/4JHEv//qT//r9Gp/3L///99////zOjA//q+\n        aP9qtKj/r3M0/wil0v8ACg+CAAIAVQBEAOkAIwDJAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAYLm6\n        uv/i/f//3/P8/7zt//+U4P//m+H//5rj//+Z4P//nuD//57m//+X4f//KY3f/wZrw/8Nccr/H4fi/yqY\n        7/8umfX/K5v1/x9muv8IZrv/AGnB/wBqxP8AZ8D/AGvD/wBowv8IZ7z/M3bE/2aq4P+Ezv//erjt/xls\n        wP8Aluz/BZ72/wKGyf8AGie/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIkGBUMk4xMFPJSOhf4a6ON/kzy\n        8v+F////nf///3L//f+M6dr/J7rZ/1OhpP8GZX7xAAAAFwAAAAAAAwA7AAQAPwAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAC53dXbf6Pn//9r0/P/U9P//nuH//5ni//+c4f//meH//5nl//+Z4f//o+j//2S7\n        8f8Aasr/CGrA/whvw/8VeNT/GYDd/w1yz/9gsu3/WqHj/ziAzP9Dis7/QIfQ/0CIzf89gc3/ZKfm/3/K\n        //99xv//esD//4LL//9His//AH7W/wOl/v8DV4nzAAgNdwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAIAAAAhAAAAKgBcatIo/v//vfz1/8j78P9J+v7/C+b//y7J4/9Nrr//AzxJwwAAAAMAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDEwt+b0+f/Y9f//2/L8/8nv+v+c5f//lt///5zj\n        //+X4f//nuD//5zi//+h5f//U7Pv/w50zf8BacT/AWTA/wlswv9WquD/nuL//63d//+c2f//e8b//4DJ\n        //+Ayf//gMj//3rF//90vP//f8X//33C//93wP//g8n+/0iP2f8Gd8X/ABorpAAAABgAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg5VgJd87//ur//m++n/Iez//zfU4P/zvV3/vmEL/wIA\n        AFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHB16trq//5////9jy\n        /P/c8/3/ze7+/6Pm/P+b4f//nuD//5Lk//+W4P//meP//6Lm//+D0fj/c8Du/3TD7P+H0PT/nef//1Og\n        2/9XpOL/f8b6/5HO//96v///fcr//4XL//+Jzf//i9T//5Ta//+W4f//gcr//3jC//99x///ETFf8QAA\n        ADkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8FAE6acCzu5M+T/xTp\n        //9OxL//umsb/BsIAHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAOSkhJxO/8/v/a+v//2fH7/9v1/P/Y8fr/zfH+/8fv+/+76vz/uOj//6Xm//+V4f//neX//6Pq\n        //+i6///qe///2q66v8AW73/BnfU/xOC5P+j0fP/nN///5LY//+b5v//m+L//5ri//+n6P//rer//6bp\n        //+R0f//b7jx/w4YH6EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAANjMmE7gQXmr/ADxGxgMAAEoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUEBF+9vLv/5Pz//9fz+//Z8fr/2fb//9zz+v/b8fv/3/b+/9zy\n        /v/Y9Pr/wu///6nn//+e6f//M4bQ/2Sx6f+F0vH/C2rG/wBiwP8id8r/qNjw/6Dp//+b4f//neD//53k\n        //+f5v//hs/x/5vU+P+c0/r/rvD//1x9i/gAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHUk5O0vb+/v/l/v//2fL9/9rx\n        /f/Z9P3/2fP+/9ry+f/Y8/7/2vL9/+Dx/P/Y9Pr/tej//3jF7/+Gzfb/o+v//4HM8/9sv+j/kdLz/6Po\n        //+c4P//neX//5rh//+e6P//dcHr/xF0y/8fke7/Ipj9/ymCzP8kIyWMAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB\n        ATFYWFfawMjJ/977///Z9f3/2O/7/9r2/P/Z8v7/2fL9/9j1+v/Z9f7/2vP+/+D0///S9///ouf//5Tf\n        //+i5///oez//5zk//+V3///mOH//5fh//+b4///md/+/xqA0/8Gacb/Jpb3/yiGz/8IFB6LAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAADiIfH6Hq7e3/5P///+b9///l/v//6f///+P5///d9f//2fP5/9ry\n        /f/Z8vr/3PT8/8vv+v+i5P//n+j//6Hk//+m5v//o+f//6Xl//+c5P//nOL//5Xg/f8cfsz/BHHS/wxl\n        sP8FERyFAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAue3p64tve3P+FtNP/SprP/0qV\n        zf+dz+v/3vv//9/0/f/Z8v3/2fT6/9ny/P/a9P//1/L9/9Tw/v/X8f3/1vD+/9Xx/f/X8f3/ye3+/7Pr\n        //+88P//ltz6/xhosf8ADRmLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB\n        ADEJCAiEAgMHgQAABoAAAAB6ASI9wjp5rf/a9///3fb8/9fz/f/a8/7/2fL+/9ry+v/e9///4/7//9z8\n        ///e/P//5Pz8/9r1/v/o/P//6v3//73Lz/8YICKLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAdeUn2X7uz9///Z8///1vP5/9j0\n        ///d9/7/vOby/0aCrf8sdaj/OHql/5LC2f/o////vcvO/1phZd4NDQx2AAAABAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgm\n        JaDl6ej/9f///+/////x/////P///1Noc+AAAABWAAAAUAAAAEoDGSiaR1BX3xUWFHsAAAAeAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAzSklJ7IqKifOBgYHwg4KC8YOBgfYjISGFAAAAAAAAAAAAAAAAAAAABAAA\n        AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAA////////////8AAeAB/////gAAwAA////+AADAAD////wAAEAAH////AAAAAAP///8AAAAAA\n        f///gAAAAAA///+AAAAAAB///4AAAAAAH///AAAAAAAP//8AAAAAAAf//gAAAAAAA//+AAAAAAAB//4A\n        AAAAAAD//AAAAAAAAH/8AAAAAgAAf/gAAAACAAB/+AAAAAYAAH/4AAQABwAAf/gABAAPgAB/8AAOAA+A\n        AH/wAA4AH8AA//AADwA/wAD/8AAPAH/AAP/wAA+B/+AB//AAD8/+AAP/8AAP//gAAP/gAA//4AAAf+AA\n        D//AAAA/4AAP/4AAAD/gAA//AAAAH+AAD/4AAAAf4AAP/AAAAA/gAA/4AAAAD0AAD/gAAAAfAAAP8AAA\n        AB8AAA/wAAAAfwAAA/AAABB/AAAD4AAAP/+AAAPgAAB//wAAAeAAAH//AAAA4AAAf/+AAADgAAA/j4AA\n        A+AAAB4HAAAD4AAAAAcAAAHgAAAAA4AAAeAAAAABwAAH4AAAAAHgAAPgAAAAAfAAJ+AAAAAB+AA/8AAA\n        AAH/AH/wAAAAA/+A//AAAAAH/8H/+AAAAAf////4AAAAD/////wAAAAf/////gAAAB//////AAAAf///\n        //+AAAD///////wAAP///////wAD////////Ac////////////8=\n</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmCustomCSS.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmCustomCSS\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmGifSetting));\n            this.lblBackgroundColor = new DevComponents.DotNetBar.LabelX();\n            this.lblNormalTextColor = new DevComponents.DotNetBar.LabelX();\n            this.lblChangedBackgroundColor = new DevComponents.DotNetBar.LabelX();\n            this.lblAddedBackgroundColor = new DevComponents.DotNetBar.LabelX();\n            this.lblRemovedBackgroundColor = new DevComponents.DotNetBar.LabelX();\n            this.lblChangedTextColor = new DevComponents.DotNetBar.LabelX();\n            this.lblAddedTextColor = new DevComponents.DotNetBar.LabelX();\n            this.lblRemovedTextColor = new DevComponents.DotNetBar.LabelX();\n            this.lblHyperlinkColor = new DevComponents.DotNetBar.LabelX();\n            this.btnOk = new DevComponents.DotNetBar.ButtonX();\n            this.btnCancel = new DevComponents.DotNetBar.ButtonX();\n            this.btnDefault = new DevComponents.DotNetBar.ButtonX();\n            this.richTextBoxEx1 = new DevComponents.DotNetBar.Controls.RichTextBoxEx();\n            this.colorPickerBackgroundColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerNormalTextColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerChangedBackgroundColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerAddedBackgroundColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerRemovedBackgroundColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerChangedTextColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerAddedTextColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerRemovedTextColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerHyperlinkColor = new DevComponents.DotNetBar.ColorPickerButton();\n            this.SuspendLayout();\n            // \n            // lblBackgroundColor\n            // \n            this.lblBackgroundColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblBackgroundColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblBackgroundColor.Location = new System.Drawing.Point(10, 10);\n            this.lblBackgroundColor.Name = \"lblBackgroundColor\";\n            this.lblBackgroundColor.Size = new System.Drawing.Size(222, 16);\n            this.lblBackgroundColor.TabIndex = 0;\n            this.lblBackgroundColor.Text = \"backColor\";\n            // \n            // lblNormalTextColor\n            // \n            this.lblNormalTextColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblNormalTextColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblNormalTextColor.Location = new System.Drawing.Point(165, 10);\n            this.lblNormalTextColor.Name = \"lblNormalTextColor\";\n            this.lblNormalTextColor.Size = new System.Drawing.Size(222, 16);\n            this.lblNormalTextColor.TabIndex = 0;\n            this.lblNormalTextColor.Text = \"foreColor\";\n            // \n            // lblChangedBackgroundColor\n            // \n            this.lblChangedBackgroundColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblChangedBackgroundColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblChangedBackgroundColor.Location = new System.Drawing.Point(10, 38);\n            this.lblChangedBackgroundColor.Name = \"lblChangedBackgroundColor\";\n            this.lblChangedBackgroundColor.Size = new System.Drawing.Size(222, 16);\n            this.lblChangedBackgroundColor.TabIndex = 0;\n            this.lblChangedBackgroundColor.Text = \"[Changed] backColor\";\n            // \n            // lblChangedTextColor\n            // \n            this.lblChangedTextColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblChangedTextColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblChangedTextColor.Location = new System.Drawing.Point(165, 38);\n            this.lblChangedTextColor.Name = \"lblChangedTextColor\";\n            this.lblChangedTextColor.Size = new System.Drawing.Size(222, 16);\n            this.lblChangedTextColor.TabIndex = 0;\n            this.lblChangedTextColor.Text = \"[Changed] foreColor\";\n            // \n            // lblAddedBackgroundColor\n            // \n            this.lblAddedBackgroundColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblAddedBackgroundColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblAddedBackgroundColor.Location = new System.Drawing.Point(10, 66);\n            this.lblAddedBackgroundColor.Name = \"lblAddedBackgroundColor\";\n            this.lblAddedBackgroundColor.Size = new System.Drawing.Size(222, 16);\n            this.lblAddedBackgroundColor.TabIndex = 0;\n            this.lblAddedBackgroundColor.Text = \"[Added] backColor\";\n            // \n            // lblAddedTextColor\n            // \n            this.lblAddedTextColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblAddedTextColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblAddedTextColor.Location = new System.Drawing.Point(165, 66);\n            this.lblAddedTextColor.Name = \"lblAddedTextColor\";\n            this.lblAddedTextColor.Size = new System.Drawing.Size(222, 16);\n            this.lblAddedTextColor.TabIndex = 0;\n            this.lblAddedTextColor.Text = \"[Added] foreColor\";\n            // \n            // lblRemovedBackgroundColor\n            // \n            this.lblRemovedBackgroundColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblRemovedBackgroundColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblRemovedBackgroundColor.Location = new System.Drawing.Point(10, 94);\n            this.lblRemovedBackgroundColor.Name = \"lblRemovedBackgroundColor\";\n            this.lblRemovedBackgroundColor.Size = new System.Drawing.Size(222, 16);\n            this.lblRemovedBackgroundColor.TabIndex = 0;\n            this.lblRemovedBackgroundColor.Text = \"[Removed] backColor\";\n            // \n            // lblRemovedTextColor\n            // \n            this.lblRemovedTextColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblRemovedTextColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblRemovedTextColor.Location = new System.Drawing.Point(165, 94);\n            this.lblRemovedTextColor.Name = \"lblRemovedTextColor\";\n            this.lblRemovedTextColor.Size = new System.Drawing.Size(222, 16);\n            this.lblRemovedTextColor.TabIndex = 0;\n            this.lblRemovedTextColor.Text = \"[Removed] foreColor\";\n            // \n            // lblHyperlinkColor\n            // \n            this.lblHyperlinkColor.AutoSize = true;\n            // \n            // \n            // \n            this.lblHyperlinkColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblHyperlinkColor.Location = new System.Drawing.Point(10, 122);\n            this.lblHyperlinkColor.Name = \"lblHyperlinkColor\";\n            this.lblHyperlinkColor.Size = new System.Drawing.Size(222, 16);\n            this.lblHyperlinkColor.TabIndex = 0;\n            this.lblHyperlinkColor.Text = \"[Hyperlink] Color\";\n            // \n            // colorPickerBackgroundColor\n            // \n            this.colorPickerBackgroundColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerBackgroundColor.AutoExpandOnClick = true;\n            this.colorPickerBackgroundColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerBackgroundColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerBackgroundColor.Location = new System.Drawing.Point(120, 7);\n            this.colorPickerBackgroundColor.Name = \"colorPickerBackgroundColor\";\n            this.colorPickerBackgroundColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerBackgroundColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerBackgroundColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerBackgroundColor.TabIndex = 1;\n            this.colorPickerBackgroundColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerNormalTextColor\n            // \n            this.colorPickerNormalTextColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerNormalTextColor.AutoExpandOnClick = true;\n            this.colorPickerNormalTextColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerNormalTextColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerNormalTextColor.Location = new System.Drawing.Point(280, 7);\n            this.colorPickerNormalTextColor.Name = \"colorPickerNormalTextColor\";\n            this.colorPickerNormalTextColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerNormalTextColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerNormalTextColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerNormalTextColor.TabIndex = 2;\n            this.colorPickerNormalTextColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerChangedBackgroundColor\n            // \n            this.colorPickerChangedBackgroundColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerChangedBackgroundColor.AutoExpandOnClick = true;\n            this.colorPickerChangedBackgroundColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerChangedBackgroundColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerChangedBackgroundColor.Location = new System.Drawing.Point(120, 35);\n            this.colorPickerChangedBackgroundColor.Name = \"colorPickerChangedBackgroundColor\";\n            this.colorPickerChangedBackgroundColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerChangedBackgroundColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerChangedBackgroundColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerChangedBackgroundColor.TabIndex = 3;\n            this.colorPickerChangedBackgroundColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerAddedBackgroundColor\n            // \n            this.colorPickerAddedBackgroundColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerAddedBackgroundColor.AutoExpandOnClick = true;\n            this.colorPickerAddedBackgroundColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerAddedBackgroundColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerAddedBackgroundColor.Location = new System.Drawing.Point(120, 63);\n            this.colorPickerAddedBackgroundColor.Name = \"colorPickerAddedBackgroundColor\";\n            this.colorPickerAddedBackgroundColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerAddedBackgroundColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerAddedBackgroundColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerAddedBackgroundColor.TabIndex = 4;\n            this.colorPickerAddedBackgroundColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerRemovedBackgroundColor\n            // \n            this.colorPickerRemovedBackgroundColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerRemovedBackgroundColor.AutoExpandOnClick = true;\n            this.colorPickerRemovedBackgroundColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerRemovedBackgroundColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerRemovedBackgroundColor.Location = new System.Drawing.Point(120, 91);\n            this.colorPickerRemovedBackgroundColor.Name = \"colorPickerRemovedBackgroundColor\";\n            this.colorPickerRemovedBackgroundColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerRemovedBackgroundColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerRemovedBackgroundColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerRemovedBackgroundColor.TabIndex = 5;\n            this.colorPickerRemovedBackgroundColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerChangedTextColor\n            // \n            this.colorPickerChangedTextColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerChangedTextColor.AutoExpandOnClick = true;\n            this.colorPickerChangedTextColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerChangedTextColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerChangedTextColor.Location = new System.Drawing.Point(280, 35);\n            this.colorPickerChangedTextColor.Name = \"colorPickerChangedTextColor\";\n            this.colorPickerChangedTextColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerChangedTextColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerChangedTextColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerChangedTextColor.TabIndex = 6;\n            this.colorPickerChangedTextColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerAddedTextColor\n            // \n            this.colorPickerAddedTextColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerAddedTextColor.AutoExpandOnClick = true;\n            this.colorPickerAddedTextColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerAddedTextColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerAddedTextColor.Location = new System.Drawing.Point(280, 63);\n            this.colorPickerAddedTextColor.Name = \"colorPickerAddedTextColor\";\n            this.colorPickerAddedTextColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerAddedTextColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerAddedTextColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerAddedTextColor.TabIndex = 7;\n            this.colorPickerAddedTextColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerRemovedTextColor\n            // \n            this.colorPickerRemovedTextColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerRemovedTextColor.AutoExpandOnClick = true;\n            this.colorPickerRemovedTextColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerRemovedTextColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerRemovedTextColor.Location = new System.Drawing.Point(280, 91);\n            this.colorPickerRemovedTextColor.Name = \"colorPickerRemovedTextColor\";\n            this.colorPickerRemovedTextColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerRemovedTextColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerRemovedTextColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerRemovedTextColor.TabIndex = 8;\n            this.colorPickerRemovedTextColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // colorPickerHyperlinkColor\n            // \n            this.colorPickerHyperlinkColor.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerHyperlinkColor.AutoExpandOnClick = true;\n            this.colorPickerHyperlinkColor.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerHyperlinkColor.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerHyperlinkColor.Location = new System.Drawing.Point(120, 119);\n            this.colorPickerHyperlinkColor.Name = \"colorPickerHyperlinkColor\";\n            this.colorPickerHyperlinkColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerHyperlinkColor.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerHyperlinkColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerHyperlinkColor.TabIndex = 9;\n            this.colorPickerHyperlinkColor.SelectedColorChanged += new System.EventHandler(this.colorPickers_SelectedColorChanged);\n            // \n            // richTextBoxEx1\n            // \n            // \n            // \n            // \n            this.richTextBoxEx1.BackgroundStyle.Class = \"RichTextBoxBorder\";\n            this.richTextBoxEx1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.richTextBoxEx1.Location = new System.Drawing.Point(12, 144);\n            this.richTextBoxEx1.Name = \"richTextBoxEx1\";\n            this.richTextBoxEx1.Rtf = \"{\\\\rtf1\\\\ansi\\\\ansicpg936\\\\deff0\\\\deflang1033\\\\deflangfe1042{\\\\fonttbl{\\\\f0\\\\fnil\\\\fcharset\" +\n    \"129 \\\\\\'b5\\\\\\'b8\\\\\\'bf\\\\\\'f2;}}\\r\\n\\\\viewkind4\\\\uc1\\\\pard\\\\lang1042\\\\f0\\\\fs18\\\\par\\r\\n}\\r\\n\";\n            this.richTextBoxEx1.Size = new System.Drawing.Size(305, 83);\n            this.richTextBoxEx1.TabIndex = 10;\n            this.richTextBoxEx1.Font = new System.Drawing.Font(\"SimSun\", 9f);\n            // \n            // \n            // btnOk\n            // \n            this.btnOk.Location = new System.Drawing.Point(12, 231);\n            this.btnOk.Name = \"btnOk\";\n            this.btnOk.Size = new System.Drawing.Size(100, 35);\n            this.btnOk.TabIndex = 11;\n            this.btnOk.Text = \"OK\";\n            this.btnOk.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnOk.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK;\n            // \n            // btnCancel\n            // \n            this.btnCancel.Location = new System.Drawing.Point(117, 231);\n            this.btnCancel.Name = \"btnCancel\";\n            this.btnCancel.Size = new System.Drawing.Size(100, 35);\n            this.btnCancel.TabIndex = 12;\n            this.btnCancel.Text = \"Cancel\";\n            this.btnCancel.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnCancel.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;\n            // \n            // btnDefault\n            // \n            this.btnDefault.Location = new System.Drawing.Point(222, 231);\n            this.btnDefault.Name = \"btnDefault\";\n            this.btnDefault.Size = new System.Drawing.Size(100, 35);\n            this.btnDefault.TabIndex = 13;\n            this.btnDefault.Text = \"Default\";\n            this.btnDefault.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnDefault.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.btnDefault.Click += new System.EventHandler(this.btnDefault_Click);\n            // \n            // FrmCustomCSS\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(329, 280);\n            this.Controls.Add(this.btnOk);\n            this.Controls.Add(this.btnCancel);\n            this.Controls.Add(this.btnDefault);\n            this.Controls.Add(this.richTextBoxEx1);\n            this.Controls.Add(this.colorPickerBackgroundColor);\n            this.Controls.Add(this.colorPickerNormalTextColor);\n            this.Controls.Add(this.colorPickerChangedBackgroundColor);\n            this.Controls.Add(this.colorPickerAddedBackgroundColor);\n            this.Controls.Add(this.colorPickerRemovedBackgroundColor);\n            this.Controls.Add(this.colorPickerChangedTextColor);\n            this.Controls.Add(this.colorPickerAddedTextColor);\n            this.Controls.Add(this.colorPickerRemovedTextColor);\n            this.Controls.Add(this.colorPickerHyperlinkColor);\n            this.Controls.Add(this.lblBackgroundColor);\n            this.Controls.Add(this.lblNormalTextColor);\n            this.Controls.Add(this.lblChangedBackgroundColor);\n            this.Controls.Add(this.lblChangedTextColor);\n            this.Controls.Add(this.lblAddedBackgroundColor);\n            this.Controls.Add(this.lblAddedTextColor);\n            this.Controls.Add(this.lblRemovedBackgroundColor);\n            this.Controls.Add(this.lblRemovedTextColor);\n            this.Controls.Add(this.lblHyperlinkColor);\n            this.DoubleBuffered = true;\n            this.Font = new System.Drawing.Font(\"SimSun\", 9F);\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"FrmCustomCSS\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"CustomCSS\";\n            this.ResumeLayout(false);\n            this.PerformLayout();\n\n        }\n\n        #endregion\n        \n        private DevComponents.DotNetBar.LabelX lblBackgroundColor;\n        private DevComponents.DotNetBar.LabelX lblNormalTextColor;\n        private DevComponents.DotNetBar.LabelX lblChangedBackgroundColor;\n        private DevComponents.DotNetBar.LabelX lblAddedBackgroundColor;\n        private DevComponents.DotNetBar.LabelX lblRemovedBackgroundColor;\n        private DevComponents.DotNetBar.LabelX lblChangedTextColor;\n        private DevComponents.DotNetBar.LabelX lblAddedTextColor;\n        private DevComponents.DotNetBar.LabelX lblRemovedTextColor;\n        private DevComponents.DotNetBar.LabelX lblHyperlinkColor;\n        private DevComponents.DotNetBar.ButtonX btnOk;\n        private DevComponents.DotNetBar.ButtonX btnCancel;\n        private DevComponents.DotNetBar.ButtonX btnDefault;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerBackgroundColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerNormalTextColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerChangedBackgroundColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerAddedBackgroundColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerRemovedBackgroundColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerChangedTextColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerAddedTextColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerRemovedTextColor;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerHyperlinkColor;\n        private DevComponents.DotNetBar.Controls.RichTextBoxEx richTextBoxEx1;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmCustomCSS.cs",
    "content": "﻿using System;\nusing System.Drawing;\nusing System.Globalization;\nusing System.Windows.Forms;\nusing WzComparerR2.Config;\n\nnamespace WzComparerR2\n{\n    public partial class FrmCustomCSS : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmCustomCSS()\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n        }\n\n        private bool DarkMode;\n\n        public Color backgroundColor\n        {\n            get { return colorPickerBackgroundColor.SelectedColor; }\n            set { colorPickerBackgroundColor.SelectedColor = value; }\n        }\n\n        public Color normalTextColor\n        {\n            get { return colorPickerNormalTextColor.SelectedColor; }\n            set { colorPickerNormalTextColor.SelectedColor = value; }\n        }\n        public Color changedBackgroundColor\n        {\n            get { return colorPickerChangedBackgroundColor.SelectedColor; }\n            set { colorPickerChangedBackgroundColor.SelectedColor = value; }\n        }\n        public Color addedBackgroundColor\n        {\n            get { return colorPickerAddedBackgroundColor.SelectedColor; }\n            set { colorPickerAddedBackgroundColor.SelectedColor = value; }\n        }\n\n        public Color removedBackgroundColor\n        {\n            get { return colorPickerRemovedBackgroundColor.SelectedColor; }\n            set { colorPickerRemovedBackgroundColor.SelectedColor = value; }\n        }\n\n        public Color changedTextColor\n        {\n            get { return colorPickerChangedTextColor.SelectedColor; }\n            set { colorPickerChangedTextColor.SelectedColor = value; }\n        }\n\n        public Color addedTextColor\n        {\n            get { return colorPickerAddedTextColor.SelectedColor; }\n            set { colorPickerAddedTextColor.SelectedColor = value; }\n        }\n\n        public Color removedTextColor\n        {\n            get { return colorPickerRemovedTextColor.SelectedColor; }\n            set { colorPickerRemovedTextColor.SelectedColor = value; }\n        }\n\n        public Color hyperlinkColor\n        {\n            get { return colorPickerHyperlinkColor.SelectedColor; }\n            set { colorPickerHyperlinkColor.SelectedColor = value; }\n        }\n\n        private void btnDefault_Click(object sender, EventArgs e)\n        {\n            if (!DarkMode)\n            {\n                this.backgroundColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n                this.normalTextColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n                this.changedBackgroundColor = Color.FromArgb(Int32.Parse(\"ff003049\", NumberStyles.HexNumber));\n                this.addedBackgroundColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n                this.removedBackgroundColor = Color.FromArgb(Int32.Parse(\"ff462306\", NumberStyles.HexNumber));\n                this.changedTextColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n                this.addedTextColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n                this.removedTextColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n                this.hyperlinkColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n            }\n            else\n            {\n                this.backgroundColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n                this.normalTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n                this.changedBackgroundColor = Color.FromArgb(Int32.Parse(\"fffff4c4\", NumberStyles.HexNumber));\n                this.addedBackgroundColor = Color.FromArgb(Int32.Parse(\"ffebf2f8\", NumberStyles.HexNumber));\n                this.removedBackgroundColor = Color.FromArgb(Int32.Parse(\"ffffffff\", NumberStyles.HexNumber));\n                this.changedTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n                this.addedTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n                this.removedTextColor = Color.FromArgb(Int32.Parse(\"ff000000\", NumberStyles.HexNumber));\n                this.hyperlinkColor = Color.FromArgb(Int32.Parse(\"ff0000ff\", NumberStyles.HexNumber));\n            }\n            this.DarkMode = !this.DarkMode;\n        }\n\n        private void colorPickers_SelectedColorChanged(object sender, EventArgs e)\n        {\n            this.richTextBoxEx1.ReadOnly = false;\n            this.richTextBoxEx1.Clear();\n            this.richTextBoxEx1.BackColorRichTextBox = backgroundColor;\n            this.richTextBoxEx1.AppendText(\"                     \");\n            var contents = new[]\n            {\n                new {Text = \"Hyperlink\" + Environment.NewLine, Fore = hyperlinkColor, Back = backgroundColor, Underline = true},\n                new {Text = \"                Normal Content Text               \" + Environment.NewLine, Fore = normalTextColor, Back = backgroundColor, Underline = false},\n                new {Text = \"               Changed Content Text               \" + Environment.NewLine, Fore = changedTextColor, Back = changedBackgroundColor, Underline = false},\n                new {Text = \"                 Added Content Text               \" + Environment.NewLine, Fore = addedTextColor, Back = addedBackgroundColor, Underline = false},\n                new {Text = \"               Removed Content Text               \", Fore = removedTextColor, Back = removedBackgroundColor, Underline = false}\n            };\n            foreach (var line in contents)\n            {\n                int start = richTextBoxEx1.TextLength;\n                richTextBoxEx1.AppendText(line.Text);\n                richTextBoxEx1.Select(start, line.Text.Length);\n                richTextBoxEx1.SelectionColor = line.Fore;\n                richTextBoxEx1.SelectionBackColor = line.Back;\n                if (line.Underline)\n                {\n                    richTextBoxEx1.SelectionFont = new Font(richTextBoxEx1.Font, FontStyle.Underline);\n                }\n            }\n            this.richTextBoxEx1.ReadOnly = true;\n        }\n\n        public void LoadConfig(CustomCSSConfig config)\n        {\n            this.backgroundColor = config.BackgroundColor;\n            this.normalTextColor = config.NormalTextColor;\n            this.changedBackgroundColor = config.ChangedBackgroundColor;\n            this.addedBackgroundColor = config.AddedBackgroundColor;\n            this.removedBackgroundColor = config.RemovedBackgroundColor;\n            this.changedTextColor = config.ChangedTextColor;\n            this.addedTextColor = config.AddedTextColor;\n            this.removedTextColor = config.RemovedTextColor;\n            this.changedBackgroundColor = config.ChangedBackgroundColor;\n            this.hyperlinkColor = config.HyperlinkColor;\n        }\n\n        public void SaveConfig(CustomCSSConfig config)\n        {\n            config.BackgroundColor = this.backgroundColor;\n            config.NormalTextColor = this.normalTextColor;\n            config.ChangedBackgroundColor = this.changedBackgroundColor;\n            config.AddedBackgroundColor = this.addedBackgroundColor;\n            config.RemovedBackgroundColor = this.removedBackgroundColor;\n            config.ChangedTextColor = this.changedTextColor;\n            config.AddedTextColor = this.addedTextColor;\n            config.RemovedTextColor = this.removedTextColor;\n            config.ChangedBackgroundColor = this.changedBackgroundColor;\n            config.HyperlinkColor = this.hyperlinkColor;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/FrmCustomCSS.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <metadata name=\"lblSelectJobIntro.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 17</value>\n  </metadata>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmGifClipOptions.Designer.cs",
    "content": "﻿\nnamespace WzComparerR2\n{\n    partial class FrmGifClipOptions\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.buttonOK = new DevComponents.DotNetBar.ButtonX();\n            this.buttonCancel = new DevComponents.DotNetBar.ButtonX();\n            this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();\n            this.labelX13 = new DevComponents.DotNetBar.LabelX();\n            this.line2 = new DevComponents.DotNetBar.Controls.Line();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.line1 = new DevComponents.DotNetBar.Controls.Line();\n            this.labelX3 = new DevComponents.DotNetBar.LabelX();\n            this.labelX4 = new DevComponents.DotNetBar.LabelX();\n            this.labelX2 = new DevComponents.DotNetBar.LabelX();\n            this.labelX5 = new DevComponents.DotNetBar.LabelX();\n            this.labelX6 = new DevComponents.DotNetBar.LabelX();\n            this.labelX7 = new DevComponents.DotNetBar.LabelX();\n            this.labelX8 = new DevComponents.DotNetBar.LabelX();\n            this.labelX9 = new DevComponents.DotNetBar.LabelX();\n            this.labelX10 = new DevComponents.DotNetBar.LabelX();\n            this.line3 = new DevComponents.DotNetBar.Controls.Line();\n            this.labelX11 = new DevComponents.DotNetBar.LabelX();\n            this.labelX12 = new DevComponents.DotNetBar.LabelX();\n            this.labelX14 = new DevComponents.DotNetBar.LabelX();\n            this.txtStartTime = new DevComponents.Editors.IntegerInput();\n            this.txtStopTime = new DevComponents.Editors.IntegerInput();\n            this.lblDuration = new DevComponents.DotNetBar.LabelX();\n            this.txtClipLeft = new DevComponents.Editors.IntegerInput();\n            this.txtClipTop = new DevComponents.Editors.IntegerInput();\n            this.txtClipRight = new DevComponents.Editors.IntegerInput();\n            this.txtClipBottom = new DevComponents.Editors.IntegerInput();\n            this.lblClipSize = new DevComponents.DotNetBar.LabelX();\n            this.txtWidth = new DevComponents.Editors.IntegerInput();\n            this.txtHeight = new DevComponents.Editors.IntegerInput();\n            this.txtScale = new DevComponents.Editors.IntegerInput();\n            this.txtStartTimeNew = new DevComponents.Editors.IntegerInput();\n            this.txtStopTimeNew = new DevComponents.Editors.IntegerInput();\n            this.lblDurationNew = new DevComponents.DotNetBar.LabelX();\n            this.txtClipLeftNew = new DevComponents.Editors.IntegerInput();\n            this.txtClipTopNew = new DevComponents.Editors.IntegerInput();\n            this.txtClipRightNew = new DevComponents.Editors.IntegerInput();\n            this.txtClipBottomNew = new DevComponents.Editors.IntegerInput();\n            this.lblClipSizeNew = new DevComponents.DotNetBar.LabelX();\n            this.txtWidthNew = new DevComponents.Editors.IntegerInput();\n            this.txtHeightNew = new DevComponents.Editors.IntegerInput();\n            this.txtScaleNew = new DevComponents.Editors.IntegerInput();\n            this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();\n            this.tableLayoutPanel1.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.txtStartTime)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtStopTime)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipLeft)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipTop)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipRight)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipBottom)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtWidth)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtHeight)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtScale)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtStartTimeNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtStopTimeNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipLeftNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipTopNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipRightNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipBottomNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtWidthNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtHeightNew)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtScaleNew)).BeginInit();\n            this.tableLayoutPanel2.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // buttonOK\n            // \n            this.buttonOK.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonOK.Anchor = System.Windows.Forms.AnchorStyles.None;\n            this.buttonOK.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK;\n            this.buttonOK.Location = new System.Drawing.Point(58, 3);\n            this.buttonOK.Margin = new System.Windows.Forms.Padding(30, 3, 3, 3);\n            this.buttonOK.Name = \"buttonOK\";\n            this.buttonOK.Size = new System.Drawing.Size(75, 23);\n            this.buttonOK.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonOK.Symbol = \"\";\n            this.buttonOK.SymbolSize = 1F;\n            this.buttonOK.TabIndex = 0;\n            this.buttonOK.Text = \"OK\";\n            // \n            // buttonCancel\n            // \n            this.buttonCancel.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonCancel.Anchor = System.Windows.Forms.AnchorStyles.None;\n            this.buttonCancel.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;\n            this.buttonCancel.Location = new System.Drawing.Point(195, 3);\n            this.buttonCancel.Margin = new System.Windows.Forms.Padding(3, 3, 30, 3);\n            this.buttonCancel.Name = \"buttonCancel\";\n            this.buttonCancel.Size = new System.Drawing.Size(75, 23);\n            this.buttonCancel.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonCancel.TabIndex = 1;\n            this.buttonCancel.Text = \"Cancel\";\n            // \n            // tableLayoutPanel1\n            // \n            this.tableLayoutPanel1.ColumnCount = 4;\n            this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F));\n            this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F));\n            this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));\n            this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));\n            this.tableLayoutPanel1.Controls.Add(this.labelX13, 1, 11);\n            this.tableLayoutPanel1.Controls.Add(this.line2, 0, 9);\n            this.tableLayoutPanel1.Controls.Add(this.labelX1, 0, 0);\n            this.tableLayoutPanel1.Controls.Add(this.line1, 0, 3);\n            this.tableLayoutPanel1.Controls.Add(this.labelX3, 1, 0);\n            this.tableLayoutPanel1.Controls.Add(this.labelX4, 1, 1);\n            this.tableLayoutPanel1.Controls.Add(this.labelX2, 0, 4);\n            this.tableLayoutPanel1.Controls.Add(this.labelX5, 1, 2);\n            this.tableLayoutPanel1.Controls.Add(this.labelX6, 1, 4);\n            this.tableLayoutPanel1.Controls.Add(this.labelX7, 1, 5);\n            this.tableLayoutPanel1.Controls.Add(this.labelX8, 1, 6);\n            this.tableLayoutPanel1.Controls.Add(this.labelX9, 1, 7);\n            this.tableLayoutPanel1.Controls.Add(this.labelX10, 1, 8);\n            this.tableLayoutPanel1.Controls.Add(this.line3, 0, 13);\n            this.tableLayoutPanel1.Controls.Add(this.labelX11, 0, 10);\n            this.tableLayoutPanel1.Controls.Add(this.labelX12, 1, 10);\n            this.tableLayoutPanel1.Controls.Add(this.labelX14, 1, 12);\n            this.tableLayoutPanel1.Controls.Add(this.txtStartTime, 2, 0);\n            this.tableLayoutPanel1.Controls.Add(this.txtStopTime, 2, 1);\n            this.tableLayoutPanel1.Controls.Add(this.lblDuration, 2, 2);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipLeft, 2, 4);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipTop, 2, 5);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipRight, 2, 6);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipBottom, 2, 7);\n            this.tableLayoutPanel1.Controls.Add(this.lblClipSize, 2, 8);\n            this.tableLayoutPanel1.Controls.Add(this.txtWidth, 2, 10);\n            this.tableLayoutPanel1.Controls.Add(this.txtHeight, 2, 11);\n            this.tableLayoutPanel1.Controls.Add(this.txtScale, 2, 12);\n            this.tableLayoutPanel1.Controls.Add(this.txtStartTimeNew, 3, 0);\n            this.tableLayoutPanel1.Controls.Add(this.txtStopTimeNew, 3, 1);\n            this.tableLayoutPanel1.Controls.Add(this.lblDurationNew, 3, 2);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipLeftNew, 3, 4);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipTopNew, 3, 5);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipRightNew, 3, 6);\n            this.tableLayoutPanel1.Controls.Add(this.txtClipBottomNew, 3, 7);\n            this.tableLayoutPanel1.Controls.Add(this.lblClipSizeNew, 3, 8);\n            this.tableLayoutPanel1.Controls.Add(this.txtWidthNew, 3, 10);\n            this.tableLayoutPanel1.Controls.Add(this.txtHeightNew, 3, 11);\n            this.tableLayoutPanel1.Controls.Add(this.txtScaleNew, 3, 12);\n            this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.tableLayoutPanel1.Location = new System.Drawing.Point(8, 8);\n            this.tableLayoutPanel1.Name = \"tableLayoutPanel1\";\n            this.tableLayoutPanel1.RowCount = 14;\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 6F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 6F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 9.090908F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 6F));\n            this.tableLayoutPanel1.Size = new System.Drawing.Size(328, 305);\n            this.tableLayoutPanel1.TabIndex = 2;\n            // \n            // labelX13\n            // \n            this.labelX13.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX13.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX13.Location = new System.Drawing.Point(83, 249);\n            this.labelX13.Name = \"labelX13\";\n            this.labelX13.Size = new System.Drawing.Size(94, 20);\n            this.labelX13.TabIndex = 38;\n            this.labelX13.Text = \"Height (px)\";\n            // \n            // line2\n            // \n            this.line2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));\n            this.tableLayoutPanel1.SetColumnSpan(this.line2, 4);\n            this.line2.ForeColor = System.Drawing.Color.Gray;\n            this.line2.Location = new System.Drawing.Point(3, 217);\n            this.line2.Name = \"line2\";\n            this.line2.Size = new System.Drawing.Size(322, 1);\n            this.line2.TabIndex = 34;\n            this.line2.TabStop = false;\n            this.line2.Text = \"line2\";\n            // \n            // labelX1\n            // \n            this.labelX1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(3, 3);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(74, 20);\n            this.labelX1.TabIndex = 0;\n            this.labelX1.Text = \"Clip Length\";\n            // \n            // line1\n            // \n            this.line1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));\n            this.tableLayoutPanel1.SetColumnSpan(this.line1, 4);\n            this.line1.ForeColor = System.Drawing.Color.Gray;\n            this.line1.Location = new System.Drawing.Point(3, 81);\n            this.line1.Name = \"line1\";\n            this.line1.Size = new System.Drawing.Size(322, 1);\n            this.line1.TabIndex = 7;\n            this.line1.TabStop = false;\n            this.line1.Text = \"line1\";\n            // \n            // labelX3\n            // \n            this.labelX3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX3.Location = new System.Drawing.Point(83, 3);\n            this.labelX3.Name = \"labelX3\";\n            this.labelX3.Size = new System.Drawing.Size(94, 20);\n            this.labelX3.TabIndex = 8;\n            this.labelX3.Text = \"StartTime (ms)\";\n            // \n            // labelX4\n            // \n            this.labelX4.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX4.Location = new System.Drawing.Point(83, 29);\n            this.labelX4.Name = \"labelX4\";\n            this.labelX4.Size = new System.Drawing.Size(94, 20);\n            this.labelX4.TabIndex = 9;\n            this.labelX4.Text = \"StopTime (ms)\";\n            // \n            // labelX2\n            // \n            this.labelX2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX2.Location = new System.Drawing.Point(3, 87);\n            this.labelX2.Name = \"labelX2\";\n            this.labelX2.Size = new System.Drawing.Size(74, 20);\n            this.labelX2.TabIndex = 1;\n            this.labelX2.Text = \"Clip Bounds\";\n            // \n            // labelX5\n            // \n            this.labelX5.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX5.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX5.Location = new System.Drawing.Point(83, 55);\n            this.labelX5.Name = \"labelX5\";\n            this.labelX5.Size = new System.Drawing.Size(94, 20);\n            this.labelX5.TabIndex = 10;\n            this.labelX5.Text = \"Duration\";\n            // \n            // labelX6\n            // \n            this.labelX6.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX6.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX6.Location = new System.Drawing.Point(83, 87);\n            this.labelX6.Name = \"labelX6\";\n            this.labelX6.Size = new System.Drawing.Size(94, 20);\n            this.labelX6.TabIndex = 25;\n            this.labelX6.Text = \"Left (px)\";\n            // \n            // labelX7\n            // \n            this.labelX7.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX7.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX7.Location = new System.Drawing.Point(83, 113);\n            this.labelX7.Name = \"labelX7\";\n            this.labelX7.Size = new System.Drawing.Size(94, 20);\n            this.labelX7.TabIndex = 26;\n            this.labelX7.Text = \"Top (px)\";\n            // \n            // labelX8\n            // \n            this.labelX8.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX8.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX8.Location = new System.Drawing.Point(83, 139);\n            this.labelX8.Name = \"labelX8\";\n            this.labelX8.Size = new System.Drawing.Size(94, 20);\n            this.labelX8.TabIndex = 27;\n            this.labelX8.Text = \"Right (px)\";\n            // \n            // labelX9\n            // \n            this.labelX9.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX9.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX9.Location = new System.Drawing.Point(83, 165);\n            this.labelX9.Name = \"labelX9\";\n            this.labelX9.Size = new System.Drawing.Size(94, 20);\n            this.labelX9.TabIndex = 28;\n            this.labelX9.Text = \"Bottom (px)\";\n            // \n            // labelX10\n            // \n            this.labelX10.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX10.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX10.Location = new System.Drawing.Point(83, 191);\n            this.labelX10.Name = \"labelX10\";\n            this.labelX10.Size = new System.Drawing.Size(94, 20);\n            this.labelX10.TabIndex = 29;\n            this.labelX10.Text = \"Expected Size\";\n            // \n            // line3\n            // \n            this.line3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));\n            this.tableLayoutPanel1.SetColumnSpan(this.line3, 4);\n            this.line3.ForeColor = System.Drawing.Color.Gray;\n            this.line3.Location = new System.Drawing.Point(3, 301);\n            this.line3.Name = \"line3\";\n            this.line3.Size = new System.Drawing.Size(322, 1);\n            this.line3.TabIndex = 35;\n            this.line3.TabStop = false;\n            this.line3.Text = \"line3\";\n            // \n            // labelX11\n            // \n            this.labelX11.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX11.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX11.Location = new System.Drawing.Point(3, 223);\n            this.labelX11.Name = \"labelX11\";\n            this.labelX11.Size = new System.Drawing.Size(74, 20);\n            this.labelX11.TabIndex = 36;\n            this.labelX11.Text = \"Scale\";\n            // \n            // labelX12\n            // \n            this.labelX12.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX12.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX12.Location = new System.Drawing.Point(83, 223);\n            this.labelX12.Name = \"labelX12\";\n            this.labelX12.Size = new System.Drawing.Size(94, 20);\n            this.labelX12.TabIndex = 37;\n            this.labelX12.Text = \"Width (px)\";\n            // \n            // labelX14\n            // \n            this.labelX14.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX14.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX14.Location = new System.Drawing.Point(83, 275);\n            this.labelX14.Name = \"labelX14\";\n            this.labelX14.Size = new System.Drawing.Size(94, 20);\n            this.labelX14.TabIndex = 38;\n            this.labelX14.Text = \"Scale (%)\";\n            // \n            // txtStartTime\n            // \n            this.txtStartTime.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtStartTime.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtStartTime.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtStartTime.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtStartTime.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtStartTime.IsInputReadOnly = true;\n            this.txtStartTime.Location = new System.Drawing.Point(183, 3);\n            this.txtStartTime.Name = \"txtStartTime\";\n            this.txtStartTime.Size = new System.Drawing.Size(68, 21);\n            this.txtStartTime.TabIndex = 0;\n            this.txtStartTime.TabStop = false;\n            this.txtStartTime.ValueObjectChanged += new System.EventHandler(this.txtTime_ValueObjectChanged);\n            // \n            // txtStopTime\n            // \n            this.txtStopTime.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtStopTime.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtStopTime.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtStopTime.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtStopTime.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtStopTime.IsInputReadOnly = true;\n            this.txtStopTime.Location = new System.Drawing.Point(183, 29);\n            this.txtStopTime.Name = \"txtStopTime\";\n            this.txtStopTime.Size = new System.Drawing.Size(68, 21);\n            this.txtStopTime.TabIndex = 2;\n            this.txtStopTime.TabStop = false;\n            this.txtStopTime.ValueObjectChanged += new System.EventHandler(this.txtTime_ValueObjectChanged);\n            // \n            // lblDuration\n            // \n            this.lblDuration.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.lblDuration.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblDuration.Location = new System.Drawing.Point(183, 55);\n            this.lblDuration.Name = \"lblDuration\";\n            this.lblDuration.Size = new System.Drawing.Size(68, 20);\n            this.lblDuration.TabIndex = 4;\n            this.lblDuration.Text = \"0 ms\";\n            // \n            // txtClipLeft\n            // \n            this.txtClipLeft.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipLeft.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipLeft.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipLeft.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipLeft.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtClipLeft.IsInputReadOnly = true;\n            this.txtClipLeft.Location = new System.Drawing.Point(183, 87);\n            this.txtClipLeft.Name = \"txtClipLeft\";\n            this.txtClipLeft.Size = new System.Drawing.Size(68, 21);\n            this.txtClipLeft.TabIndex = 6;\n            this.txtClipLeft.TabStop = false;\n            this.txtClipLeft.ValueObjectChanged += new System.EventHandler(this.txtBound_ValueObjectChanged);\n            // \n            // txtClipTop\n            // \n            this.txtClipTop.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipTop.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipTop.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipTop.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipTop.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtClipTop.IsInputReadOnly = true;\n            this.txtClipTop.Location = new System.Drawing.Point(183, 113);\n            this.txtClipTop.Name = \"txtClipTop\";\n            this.txtClipTop.Size = new System.Drawing.Size(68, 21);\n            this.txtClipTop.TabIndex = 8;\n            this.txtClipTop.TabStop = false;\n            this.txtClipTop.ValueObjectChanged += new System.EventHandler(this.txtBound_ValueObjectChanged);\n            // \n            // txtClipRight\n            // \n            this.txtClipRight.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipRight.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipRight.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipRight.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipRight.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtClipRight.IsInputReadOnly = true;\n            this.txtClipRight.Location = new System.Drawing.Point(183, 139);\n            this.txtClipRight.Name = \"txtClipRight\";\n            this.txtClipRight.Size = new System.Drawing.Size(68, 21);\n            this.txtClipRight.TabIndex = 10;\n            this.txtClipRight.TabStop = false;\n            this.txtClipRight.ValueObjectChanged += new System.EventHandler(this.txtBound_ValueObjectChanged);\n            // \n            // txtClipBottom\n            // \n            this.txtClipBottom.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipBottom.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipBottom.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipBottom.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipBottom.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtClipBottom.IsInputReadOnly = true;\n            this.txtClipBottom.Location = new System.Drawing.Point(183, 165);\n            this.txtClipBottom.Name = \"txtClipBottom\";\n            this.txtClipBottom.Size = new System.Drawing.Size(68, 21);\n            this.txtClipBottom.TabIndex = 12;\n            this.txtClipBottom.TabStop = false;\n            this.txtClipBottom.ValueObjectChanged += new System.EventHandler(this.txtBound_ValueObjectChanged);\n            // \n            // lblClipSize\n            // \n            this.lblClipSize.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.lblClipSize.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblClipSize.Location = new System.Drawing.Point(183, 191);\n            this.lblClipSize.Name = \"lblClipSize\";\n            this.lblClipSize.Size = new System.Drawing.Size(68, 20);\n            this.lblClipSize.TabIndex = 14;\n            this.lblClipSize.Text = \"1280x720\";\n            // \n            // txtWidth\n            // \n            this.txtWidth.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtWidth.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtWidth.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtWidth.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtWidth.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtWidth.IsInputReadOnly = true;\n            this.txtWidth.Location = new System.Drawing.Point(183, 223);\n            this.txtWidth.Name = \"txtWidth\";\n            this.txtWidth.Size = new System.Drawing.Size(68, 21);\n            this.txtWidth.TabIndex = 17;\n            this.txtWidth.TabStop = false;\n            this.txtWidth.ValueObjectChanged += new System.EventHandler(this.txtSize_ValueObjectChanged);\n            // \n            // txtHeight\n            // \n            this.txtHeight.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtHeight.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtHeight.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtHeight.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtHeight.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtHeight.IsInputReadOnly = true;\n            this.txtHeight.Location = new System.Drawing.Point(183, 249);\n            this.txtHeight.Name = \"txtHeight\";\n            this.txtHeight.Size = new System.Drawing.Size(68, 21);\n            this.txtHeight.TabIndex = 19;\n            this.txtHeight.TabStop = false;\n            this.txtHeight.ValueObjectChanged += new System.EventHandler(this.txtSize_ValueObjectChanged);\n            // \n            // txtScale\n            // \n            this.txtScale.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtScale.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtScale.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtScale.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtScale.ForeColor = System.Drawing.SystemColors.ControlDark;\n            this.txtScale.IsInputReadOnly = true;\n            this.txtScale.Location = new System.Drawing.Point(183, 275);\n            this.txtScale.Name = \"txtScale\";\n            this.txtScale.Size = new System.Drawing.Size(68, 21);\n            this.txtScale.TabIndex = 21;\n            this.txtScale.TabStop = false;\n            // \n            // txtStartTimeNew\n            // \n            this.txtStartTimeNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtStartTimeNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtStartTimeNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtStartTimeNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtStartTimeNew.Increment = 10;\n            this.txtStartTimeNew.Location = new System.Drawing.Point(257, 3);\n            this.txtStartTimeNew.MinValue = 0;\n            this.txtStartTimeNew.Name = \"txtStartTimeNew\";\n            this.txtStartTimeNew.ShowUpDown = true;\n            this.txtStartTimeNew.Size = new System.Drawing.Size(68, 21);\n            this.txtStartTimeNew.TabIndex = 1;\n            this.txtStartTimeNew.ValueObjectChanged += new System.EventHandler(this.txtTimeNew_ValueObjectChanged);\n            // \n            // txtStopTimeNew\n            // \n            this.txtStopTimeNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtStopTimeNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtStopTimeNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtStopTimeNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtStopTimeNew.Increment = 10;\n            this.txtStopTimeNew.Location = new System.Drawing.Point(257, 29);\n            this.txtStopTimeNew.MinValue = 0;\n            this.txtStopTimeNew.Name = \"txtStopTimeNew\";\n            this.txtStopTimeNew.ShowUpDown = true;\n            this.txtStopTimeNew.Size = new System.Drawing.Size(68, 21);\n            this.txtStopTimeNew.TabIndex = 3;\n            this.txtStopTimeNew.ValueObjectChanged += new System.EventHandler(this.txtTimeNew_ValueObjectChanged);\n            // \n            // lblDurationNew\n            // \n            this.lblDurationNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.lblDurationNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblDurationNew.Location = new System.Drawing.Point(257, 55);\n            this.lblDurationNew.Name = \"lblDurationNew\";\n            this.lblDurationNew.Size = new System.Drawing.Size(68, 20);\n            this.lblDurationNew.TabIndex = 5;\n            this.lblDurationNew.Text = \"0 ms\";\n            // \n            // txtClipLeftNew\n            // \n            this.txtClipLeftNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipLeftNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipLeftNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipLeftNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipLeftNew.Location = new System.Drawing.Point(257, 87);\n            this.txtClipLeftNew.MaxValue = 16384;\n            this.txtClipLeftNew.MinValue = -16384;\n            this.txtClipLeftNew.Name = \"txtClipLeftNew\";\n            this.txtClipLeftNew.ShowUpDown = true;\n            this.txtClipLeftNew.Size = new System.Drawing.Size(68, 21);\n            this.txtClipLeftNew.TabIndex = 7;\n            this.txtClipLeftNew.ValueObjectChanged += new System.EventHandler(this.txtBoundNew_ValueObjectChanged);\n            // \n            // txtClipTopNew\n            // \n            this.txtClipTopNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipTopNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipTopNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipTopNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipTopNew.Location = new System.Drawing.Point(257, 113);\n            this.txtClipTopNew.MaxValue = 16384;\n            this.txtClipTopNew.MinValue = -16384;\n            this.txtClipTopNew.Name = \"txtClipTopNew\";\n            this.txtClipTopNew.ShowUpDown = true;\n            this.txtClipTopNew.Size = new System.Drawing.Size(68, 21);\n            this.txtClipTopNew.TabIndex = 9;\n            this.txtClipTopNew.ValueObjectChanged += new System.EventHandler(this.txtBoundNew_ValueObjectChanged);\n            // \n            // txtClipRightNew\n            // \n            this.txtClipRightNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipRightNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipRightNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipRightNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipRightNew.Location = new System.Drawing.Point(257, 139);\n            this.txtClipRightNew.MaxValue = 16384;\n            this.txtClipRightNew.MinValue = -16384;\n            this.txtClipRightNew.Name = \"txtClipRightNew\";\n            this.txtClipRightNew.ShowUpDown = true;\n            this.txtClipRightNew.Size = new System.Drawing.Size(68, 21);\n            this.txtClipRightNew.TabIndex = 11;\n            this.txtClipRightNew.ValueObjectChanged += new System.EventHandler(this.txtBoundNew_ValueObjectChanged);\n            // \n            // txtClipBottomNew\n            // \n            this.txtClipBottomNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtClipBottomNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtClipBottomNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtClipBottomNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtClipBottomNew.Location = new System.Drawing.Point(257, 165);\n            this.txtClipBottomNew.MaxValue = 16384;\n            this.txtClipBottomNew.MinValue = -16384;\n            this.txtClipBottomNew.Name = \"txtClipBottomNew\";\n            this.txtClipBottomNew.ShowUpDown = true;\n            this.txtClipBottomNew.Size = new System.Drawing.Size(68, 21);\n            this.txtClipBottomNew.TabIndex = 13;\n            this.txtClipBottomNew.ValueObjectChanged += new System.EventHandler(this.txtBoundNew_ValueObjectChanged);\n            // \n            // lblClipSizeNew\n            // \n            this.lblClipSizeNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.lblClipSizeNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblClipSizeNew.Location = new System.Drawing.Point(257, 191);\n            this.lblClipSizeNew.Name = \"lblClipSizeNew\";\n            this.lblClipSizeNew.Size = new System.Drawing.Size(68, 20);\n            this.lblClipSizeNew.TabIndex = 15;\n            this.lblClipSizeNew.Text = \"1280x720\";\n            // \n            // txtWidthNew\n            // \n            this.txtWidthNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtWidthNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtWidthNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtWidthNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtWidthNew.Location = new System.Drawing.Point(257, 223);\n            this.txtWidthNew.MaxValue = 16384;\n            this.txtWidthNew.MinValue = -16384;\n            this.txtWidthNew.Name = \"txtWidthNew\";\n            this.txtWidthNew.ShowUpDown = true;\n            this.txtWidthNew.Size = new System.Drawing.Size(68, 21);\n            this.txtWidthNew.TabIndex = 18;\n            this.txtWidthNew.ValueObjectChanged += new System.EventHandler(this.txtSizeNew_ValueObjectChanged);\n            // \n            // txtHeightNew\n            // \n            this.txtHeightNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtHeightNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtHeightNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtHeightNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtHeightNew.Location = new System.Drawing.Point(257, 249);\n            this.txtHeightNew.MaxValue = 16384;\n            this.txtHeightNew.MinValue = -16384;\n            this.txtHeightNew.Name = \"txtHeightNew\";\n            this.txtHeightNew.ShowUpDown = true;\n            this.txtHeightNew.Size = new System.Drawing.Size(68, 21);\n            this.txtHeightNew.TabIndex = 20;\n            this.txtHeightNew.ValueObjectChanged += new System.EventHandler(this.txtSizeNew_ValueObjectChanged);\n            // \n            // txtScaleNew\n            // \n            this.txtScaleNew.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtScaleNew.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.txtScaleNew.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtScaleNew.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.txtScaleNew.Location = new System.Drawing.Point(257, 275);\n            this.txtScaleNew.MaxValue = 65535;\n            this.txtScaleNew.MinValue = 0;\n            this.txtScaleNew.Name = \"txtScaleNew\";\n            this.txtScaleNew.ShowUpDown = true;\n            this.txtScaleNew.Size = new System.Drawing.Size(68, 21);\n            this.txtScaleNew.TabIndex = 22;\n            this.txtScaleNew.ValueObjectChanged += new System.EventHandler(this.txtScaleNew_ValueObjectChanged);\n            // \n            // tableLayoutPanel2\n            // \n            this.tableLayoutPanel2.ColumnCount = 2;\n            this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));\n            this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));\n            this.tableLayoutPanel2.Controls.Add(this.buttonCancel, 1, 0);\n            this.tableLayoutPanel2.Controls.Add(this.buttonOK, 0, 0);\n            this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.tableLayoutPanel2.Location = new System.Drawing.Point(8, 313);\n            this.tableLayoutPanel2.Name = \"tableLayoutPanel2\";\n            this.tableLayoutPanel2.RowCount = 1;\n            this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));\n            this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));\n            this.tableLayoutPanel2.Size = new System.Drawing.Size(328, 30);\n            this.tableLayoutPanel2.TabIndex = 3;\n            // \n            // FrmGifClipOptions\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.CancelButton = this.buttonCancel;\n            this.ClientSize = new System.Drawing.Size(344, 351);\n            this.Controls.Add(this.tableLayoutPanel1);\n            this.Controls.Add(this.tableLayoutPanel2);\n            this.DoubleBuffered = true;\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"FrmGifClipOptions\";\n            this.Padding = new System.Windows.Forms.Padding(8);\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"GifClipOptions\";\n            this.tableLayoutPanel1.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.txtStartTime)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtStopTime)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipLeft)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipTop)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipRight)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipBottom)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtWidth)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtHeight)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtScale)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtStartTimeNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtStopTimeNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipLeftNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipTopNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipRightNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtClipBottomNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtWidthNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtHeightNew)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.txtScaleNew)).EndInit();\n            this.tableLayoutPanel2.ResumeLayout(false);\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.ButtonX buttonOK;\n        private DevComponents.DotNetBar.ButtonX buttonCancel;\n        private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.Line line1;\n        private DevComponents.DotNetBar.LabelX labelX2;\n        private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;\n        private DevComponents.DotNetBar.LabelX labelX3;\n        private DevComponents.Editors.IntegerInput txtStartTimeNew;\n        private DevComponents.DotNetBar.LabelX labelX4;\n        private DevComponents.DotNetBar.LabelX labelX5;\n        private DevComponents.Editors.IntegerInput txtStartTime;\n        private DevComponents.Editors.IntegerInput txtStopTime;\n        private DevComponents.Editors.IntegerInput txtStopTimeNew;\n        private DevComponents.Editors.IntegerInput txtClipLeft;\n        private DevComponents.Editors.IntegerInput txtClipLeftNew;\n        private DevComponents.Editors.IntegerInput txtClipTop;\n        private DevComponents.Editors.IntegerInput txtClipTopNew;\n        private DevComponents.Editors.IntegerInput txtClipRight;\n        private DevComponents.Editors.IntegerInput txtClipRightNew;\n        private DevComponents.Editors.IntegerInput txtClipBottom;\n        private DevComponents.Editors.IntegerInput txtClipBottomNew;\n        private DevComponents.DotNetBar.LabelX labelX6;\n        private DevComponents.DotNetBar.LabelX labelX7;\n        private DevComponents.DotNetBar.LabelX labelX8;\n        private DevComponents.DotNetBar.LabelX labelX9;\n        private DevComponents.DotNetBar.LabelX labelX10;\n        private DevComponents.DotNetBar.LabelX lblDuration;\n        private DevComponents.DotNetBar.LabelX lblDurationNew;\n        private DevComponents.DotNetBar.LabelX lblClipSize;\n        private DevComponents.DotNetBar.LabelX lblClipSizeNew;\n        private DevComponents.DotNetBar.Controls.Line line2;\n        private DevComponents.DotNetBar.Controls.Line line3;\n        private DevComponents.DotNetBar.LabelX labelX13;\n        private DevComponents.DotNetBar.LabelX labelX11;\n        private DevComponents.DotNetBar.LabelX labelX12;\n        private DevComponents.DotNetBar.LabelX labelX14;\n        private DevComponents.Editors.IntegerInput txtWidth;\n        private DevComponents.Editors.IntegerInput txtHeight;\n        private DevComponents.Editors.IntegerInput txtScale;\n        private DevComponents.Editors.IntegerInput txtWidthNew;\n        private DevComponents.Editors.IntegerInput txtHeightNew;\n        private DevComponents.Editors.IntegerInput txtScaleNew;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmGifClipOptions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Data;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing System.Windows.Forms;\nusing DevComponents.Editors;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2\n{\n    public partial class FrmGifClipOptions : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmGifClipOptions()\n        {\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n            InitializeComponent();\n        }\n\n        public AnimationClipOptions ClipOptions\n        {\n            get { return this.CollectControlValues(false); }\n            set { this.ApplyControlValues(value, false); }\n        }\n\n        public AnimationClipOptions ClipOptionsNew\n        {\n            get { return this.CollectControlValues(true); }\n            set { this.ApplyControlValues(value, true); }\n        }\n\n        private bool isUpdating;\n\n        private IntegerInput[] GetInputGroup(bool isNew)\n        {\n            return isNew\n               ? new[] { this.txtStartTimeNew, this.txtStopTimeNew, this.txtClipLeftNew, this.txtClipTopNew, this.txtClipRightNew, this.txtClipBottomNew, this.txtWidthNew, this.txtHeightNew, this.txtScaleNew }\n               : new[] { this.txtStartTime, this.txtStopTime, this.txtClipLeft, this.txtClipTop, this.txtClipRight, this.txtClipBottom, this.txtWidth, this.txtHeight, this.txtScale };\n        }\n\n        private void ApplyControlValues(AnimationClipOptions value, bool isNew)\n        {\n            var controls = GetInputGroup(isNew);\n\n            if (value != null)\n            {\n                controls[0].ValueObject = value.StartTime;\n                controls[1].ValueObject = value.StopTime;\n                controls[2].ValueObject = value.Left;\n                controls[3].ValueObject = value.Top;\n                controls[4].ValueObject = value.Right;\n                controls[5].ValueObject = value.Bottom;\n                controls[6].ValueObject = value.OutputWidth;\n                controls[7].ValueObject = value.OutputHeight;\n            }\n            else\n            {\n                foreach (var txt in controls)\n                {\n                    txt.ValueObject = null;\n                }\n            }\n        }\n\n        private AnimationClipOptions CollectControlValues(bool isNew)\n        {\n            var controls = GetInputGroup(isNew);\n\n            return new AnimationClipOptions()\n            {\n                StartTime = controls[0].ValueObject as int?,\n                StopTime = controls[1].ValueObject as int?,\n                Left = controls[2].ValueObject as int?,\n                Top = controls[3].ValueObject as int?,\n                Right = controls[4].ValueObject as int?,\n                Bottom = controls[5].ValueObject as int?,\n                OutputWidth = controls[6].ValueObject as int?,\n                OutputHeight = controls[7].ValueObject as int?,\n            };\n        }\n\n        private void LockEvent(Action action)\n        {\n            if (this.isUpdating)\n            {\n                return;\n            }\n            this.isUpdating = true;\n            try\n            {\n                action?.Invoke();\n            }\n            finally\n            {\n                this.isUpdating = false;\n            }\n        }\n\n        private void txtTime_ValueObjectChanged(object sender, EventArgs e)\n        {\n            LockEvent(()=>this.onUpdateDuration(false));\n        }\n\n        private void txtTimeNew_ValueObjectChanged(object sender, EventArgs e)\n        {\n            LockEvent(() => this.onUpdateDuration(true));\n        }\n\n        private void txtBound_ValueObjectChanged(object sender, EventArgs e)\n        {\n            LockEvent(() => this.onUpdateSizeAndScale(false, isBoundChanging: true));\n        }\n\n        private void txtBoundNew_ValueObjectChanged(object sender, EventArgs e)\n        {\n            LockEvent(() => this.onUpdateSizeAndScale(true, isBoundChanging: true));\n        }\n\n        private void txtSize_ValueObjectChanged(object sender, EventArgs e)\n        {\n            LockEvent(() => this.onUpdateSizeAndScale(false, isOutSizeChanging: true));\n        }\n\n        private void txtSizeNew_ValueObjectChanged(object sender, EventArgs e)\n        {\n            LockEvent(() => this.onUpdateSizeAndScale(true, isOutSizeChanging: true));\n        }\n\n        private void txtScaleNew_ValueObjectChanged(object sender, EventArgs e)\n        {\n            LockEvent(() => this.onUpdateSizeAndScale(true, isScaleChanging: true));\n        }\n\n        private void onUpdateDuration(bool isNew)\n        {\n            var controls = GetInputGroup(isNew);\n            var lbl = isNew ? this.lblDurationNew : this.lblDuration;\n            int? duration = (controls[1].ValueObject as int?) - (controls[0].ValueObject as int?);\n            lbl.Text = $\"{duration?.ToString() ?? \"-\"} ms\";\n        }\n\n        private void onUpdateSizeAndScale(bool isNew, bool isBoundChanging = false, bool isOutSizeChanging = false, bool isScaleChanging = false)\n        {\n            var controls = GetInputGroup(isNew);\n\n            int? width = (controls[4].ValueObject as int?) - (controls[2].ValueObject as int?);\n            int? height = (controls[5].ValueObject as int?) - (controls[3].ValueObject as int?);\n            int? outWidth = (controls[6].ValueObject as int?);\n            int? outHeight = (controls[7].ValueObject as int?);\n            var lblSize = isNew ? this.lblClipSizeNew : this.lblClipSize;\n            var txtScale = isNew ? this.txtScaleNew : this.txtScale;\n\n            if (isBoundChanging)\n            {\n                lblSize.Text = $\"{width?.ToString() ?? \"?\"}x{height?.ToString() ?? \"?\"}\";\n                isOutSizeChanging = true;\n            }\n\n            if (isOutSizeChanging)\n            {\n                if (new[] { width, height, outWidth, outHeight }.All(v => v != null))\n                {\n                    var scaleX = 1.0 * outWidth / width;\n                    var scaleY = 1.0 * outHeight / height;\n                    txtScale.ValueObject = (int)Math.Round(Math.Min(scaleX.Value, scaleY.Value) * 100);\n                }\n                else\n                {\n                    txtScale.ValueObject = null;\n                }\n            }\n\n            if (isScaleChanging)\n            {\n                if (new[] { width, height, txtScale.ValueObject }.All(v => v != null))\n                {\n                    controls[6].ValueObject = (int)Math.Round(0.01 * width.Value * (int)txtScale.ValueObject);\n                    controls[7].ValueObject = (int)Math.Round(0.01 * height.Value * (int)txtScale.ValueObject);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/FrmGifClipOptions.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmGifMaker.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmGifMaker\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.SuspendLayout();\n            // \n            // FrmGifMaker\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(284, 261);\n            this.DoubleBuffered = true;\n            this.Name = \"FrmGifMaker\";\n            this.Text = \"FrmGifMaker\";\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmGifMaker.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Data;\nusing System.Drawing;\nusing System.Text;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing DevComponents.AdvTree;\n\nnamespace WzComparerR2\n{\n    public partial class FrmGifMaker : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmGifMaker()\n        {\n            InitializeComponent();\n        }\n\n\n\n        private class GifFragment\n        {\n\n        }\n\n        private class TimeLine\n        {\n        }\n\n        private class ImageLayer\n        {\n\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/FrmGifMaker.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmGifSetting.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmGifSetting\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param Name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmGifSetting));\n            this.colorPickerButton1 = new DevComponents.DotNetBar.ColorPickerButton();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.checkBoxX1 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.labelX2 = new DevComponents.DotNetBar.LabelX();\n            this.comboBoxEx1 = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.comboItem1 = new DevComponents.Editors.ComboItem();\n            this.comboItem2 = new DevComponents.Editors.ComboItem();\n            this.comboItem6 = new DevComponents.Editors.ComboItem();\n            this.comboItem7 = new DevComponents.Editors.ComboItem();\n            this.labelX3 = new DevComponents.DotNetBar.LabelX();\n            this.slider1 = new DevComponents.DotNetBar.Controls.Slider();\n            this.labelX4 = new DevComponents.DotNetBar.LabelX();\n            this.rdoMosaic = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.panelExMosaic = new DevComponents.DotNetBar.PanelEx();\n            this.labelX7 = new DevComponents.DotNetBar.LabelX();\n            this.labelX6 = new DevComponents.DotNetBar.LabelX();\n            this.labelX5 = new DevComponents.DotNetBar.LabelX();\n            this.slider2 = new DevComponents.DotNetBar.Controls.Slider();\n            this.colorPickerButton3 = new DevComponents.DotNetBar.ColorPickerButton();\n            this.colorPickerButton2 = new DevComponents.DotNetBar.ColorPickerButton();\n            this.panelExColor = new DevComponents.DotNetBar.PanelEx();\n            this.rdoColor = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTooltip1 = new DevComponents.DotNetBar.SuperTooltip();\n            this.checkBoxX2 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.labelX8 = new DevComponents.DotNetBar.LabelX();\n            this.integerInput1 = new DevComponents.Editors.IntegerInput();\n            this.labelX9 = new DevComponents.DotNetBar.LabelX();\n            this.comboBoxEx2 = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.comboItem3 = new DevComponents.Editors.ComboItem();\n            this.comboItem4 = new DevComponents.Editors.ComboItem();\n            this.comboItem5 = new DevComponents.Editors.ComboItem();\n            this.superTabControl1 = new DevComponents.DotNetBar.SuperTabControl();\n            this.superTabControlPanel1 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.superTabItem1 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel3 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.buttonX3 = new DevComponents.DotNetBar.ButtonX();\n            this.textBoxX2 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.labelX11 = new DevComponents.DotNetBar.LabelX();\n            this.textBoxX1 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.labelX10 = new DevComponents.DotNetBar.LabelX();\n            this.superTabItem3 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel2 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.checkBoxX3 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTabItem2 = new DevComponents.DotNetBar.SuperTabItem();\n            this.panelEx1 = new DevComponents.DotNetBar.PanelEx();\n            this.buttonX2 = new DevComponents.DotNetBar.ButtonX();\n            this.buttonX1 = new DevComponents.DotNetBar.ButtonX();\n            this.labelX12 = new DevComponents.DotNetBar.LabelX();\n            this.textBoxX3 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.labelX13 = new DevComponents.DotNetBar.LabelX();\n            this.btnPreset = new DevComponents.DotNetBar.ButtonX();\n            this.btnNonTransparentMP4Preset = new DevComponents.DotNetBar.ButtonItem();\n            this.btnGreenBackdropMP4Preset = new DevComponents.DotNetBar.ButtonItem();\n            this.btnBlueBackdropMP4Preset = new DevComponents.DotNetBar.ButtonItem();\n            this.btnTransparentMOVPreset = new DevComponents.DotNetBar.ButtonItem();\n            this.btnTransparentWebMPreset = new DevComponents.DotNetBar.ButtonItem();\n            this.btnDefaultPreset = new DevComponents.DotNetBar.ButtonItem();\n            this.panelExMosaic.SuspendLayout();\n            this.panelExColor.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.integerInput1)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).BeginInit();\n            this.superTabControl1.SuspendLayout();\n            this.superTabControlPanel1.SuspendLayout();\n            this.superTabControlPanel3.SuspendLayout();\n            this.superTabControlPanel2.SuspendLayout();\n            this.panelEx1.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // colorPickerButton1\n            // \n            this.colorPickerButton1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerButton1.AutoExpandOnClick = true;\n            this.colorPickerButton1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerButton1.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton1.Image\")));\n            this.colorPickerButton1.Location = new System.Drawing.Point(116, 6);\n            this.colorPickerButton1.Name = \"colorPickerButton1\";\n            this.colorPickerButton1.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerButton1.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerButton1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerButton1.TabIndex = 1;\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(11, 13);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(99, 16);\n            this.labelX1.TabIndex = 0;\n            this.labelX1.Text = \"BackGroundColor\";\n            //\n            // btnPreset\n            //\n            this.btnPreset.Location = new System.Drawing.Point(160, 6);\n            this.btnPreset.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnPreset.Size = new System.Drawing.Size(56, 23);\n            this.btnPreset.AutoExpandOnClick = true;\n            this.btnPreset.Name = \"btnPreset\";\n            this.btnPreset.TabIndex = 4;\n            this.btnPreset.Text = \"Preset\";\n            this.btnPreset.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.btnNonTransparentMP4Preset,\n            this.btnGreenBackdropMP4Preset,\n            this.btnBlueBackdropMP4Preset,\n            this.btnTransparentMOVPreset,\n            this.btnTransparentWebMPreset,\n            this.btnDefaultPreset});\n            //\n            // btnNonTransparentMP4Preset\n            //\n            this.btnNonTransparentMP4Preset.Name = \"btnNonTransparentMP4Preset\";\n            this.btnNonTransparentMP4Preset.Text = \"NonTransparentMP4\";\n            this.btnNonTransparentMP4Preset.Click += new System.EventHandler(this.btnNonTransparentMP4Preset_Click);\n            //\n            // btnGreenBackdropMP4Preset\n            //\n            this.btnGreenBackdropMP4Preset.Name = \"btnGreenBackdropMP4Preset\";\n            this.btnGreenBackdropMP4Preset.Text = \"GreenBackdropMP4\";\n            this.btnGreenBackdropMP4Preset.Click += new System.EventHandler(this.btnGreenBackdropMP4Preset_Click);\n            //\n            // btnBlueBackdropMP4Preset\n            //\n            this.btnBlueBackdropMP4Preset.Name = \"btnBlueBackdropMP4Preset\";\n            this.btnBlueBackdropMP4Preset.Text = \"BlueBackdropMP4\";\n            this.btnBlueBackdropMP4Preset.Click += new System.EventHandler(this.btnBlueBackdropMP4Preset_Click);\n            //\n            // btnTransparentMOVPreset\n            //\n            this.btnTransparentMOVPreset.Name = \"btnTransparentMOVPreset\";\n            this.btnTransparentMOVPreset.Text = \"TransparentMOV\";\n            this.btnTransparentMOVPreset.Click += new System.EventHandler(this.btnTransparentMOVPreset_Click);\n            //\n            // btnTransparentWebMPreset\n            //\n            this.btnTransparentWebMPreset.Name = \"btnTransparentWebMPreset\";\n            this.btnTransparentWebMPreset.Text = \"TransparentWebM\";\n            this.btnTransparentWebMPreset.Click += new System.EventHandler(this.btnTransparentWebMPreset_Click);\n            //\n            // btnDefaultPreset\n            //\n            this.btnDefaultPreset.Name = \"btnDefaultPreset\";\n            this.btnDefaultPreset.Text = \"Default\";\n            this.btnDefaultPreset.Click += new System.EventHandler(this.btnDefaultPreset_Click);\n            // \n            // checkBoxX1\n            // \n            this.checkBoxX1.AutoSize = true;\n            // \n            // \n            // \n            this.checkBoxX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX1.Location = new System.Drawing.Point(11, 36);\n            this.checkBoxX1.Name = \"checkBoxX1\";\n            this.checkBoxX1.Size = new System.Drawing.Size(163, 16);\n            this.checkBoxX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX1.TabIndex = 2;\n            this.checkBoxX1.Text = \"Background &Transparent\";\n            // \n            // labelX2\n            // \n            this.labelX2.AutoSize = true;\n            this.labelX2.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX2.Location = new System.Drawing.Point(16, 207);\n            this.labelX2.Name = \"labelX2\";\n            this.labelX2.Size = new System.Drawing.Size(56, 16);\n            this.labelX2.TabIndex = 5;\n            this.labelX2.Text = \"FileName\";\n            // \n            // comboBoxEx1\n            // \n            this.comboBoxEx1.DisplayMember = \"Text\";\n            this.comboBoxEx1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.comboBoxEx1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.comboBoxEx1.FormattingEnabled = true;\n            this.comboBoxEx1.ItemHeight = 15;\n            this.comboBoxEx1.Items.AddRange(new object[] {\n            this.comboItem1,\n            this.comboItem2,\n            this.comboItem6,\n            this.comboItem7});\n            this.comboBoxEx1.Location = new System.Drawing.Point(80, 178);\n            this.comboBoxEx1.Name = \"comboBoxEx1\";\n            this.comboBoxEx1.Size = new System.Drawing.Size(129, 21);\n            this.comboBoxEx1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.comboBoxEx1.TabIndex = 6;\n            // \n            // comboItem1\n            // \n            this.comboItem1.Text = \"BuildIn\";\n            // \n            // comboItem2\n            // \n            this.comboItem2.Text = \"Index Gif Encoder\";\n            // \n            // comboItem6\n            // \n            this.comboItem6.Text = \"Apng Encoder\";\n            // \n            // comboItem7\n            // \n            this.comboItem7.Text = \"FFmpeg Encoder\";\n            // \n            // labelX3\n            // \n            this.labelX3.AutoSize = true;\n            // \n            // \n            // \n            this.labelX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX3.Location = new System.Drawing.Point(11, 61);\n            this.labelX3.Name = \"labelX3\";\n            this.labelX3.Size = new System.Drawing.Size(87, 16);\n            this.labelX3.TabIndex = 3;\n            this.labelX3.Text = \"MinAlphaMixed\";\n            // \n            // slider1\n            // \n            // \n            // \n            // \n            this.slider1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.slider1.LabelPosition = DevComponents.DotNetBar.eSliderLabelPosition.Right;\n            this.slider1.LabelWidth = 25;\n            this.slider1.Location = new System.Drawing.Point(112, 58);\n            this.slider1.Maximum = 254;\n            this.slider1.Name = \"slider1\";\n            this.slider1.Size = new System.Drawing.Size(136, 23);\n            this.slider1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.slider1.TabIndex = 4;\n            this.slider1.Text = \"0\";\n            this.slider1.Value = 0;\n            this.slider1.ValueChanged += new System.EventHandler(this.slider1_ValueChanged);\n            // \n            // labelX4\n            // \n            this.labelX4.AutoSize = true;\n            this.labelX4.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX4.Location = new System.Drawing.Point(16, 20);\n            this.labelX4.Name = \"labelX4\";\n            this.labelX4.Size = new System.Drawing.Size(68, 16);\n            this.labelX4.TabIndex = 0;\n            this.labelX4.Text = \"BackGround\";\n            // \n            // rdoMosaic\n            // \n            this.rdoMosaic.AutoSize = true;\n            this.rdoMosaic.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.rdoMosaic.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.rdoMosaic.CheckBoxStyle = DevComponents.DotNetBar.eCheckBoxStyle.RadioButton;\n            this.rdoMosaic.Location = new System.Drawing.Point(16, 130);\n            this.rdoMosaic.Name = \"rdoMosaic\";\n            this.rdoMosaic.Size = new System.Drawing.Size(64, 16);\n            this.rdoMosaic.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.rdoMosaic, new DevComponents.DotNetBar.SuperTooltipInfo(\"\", \"\", \"使用马赛克晶格作为背景生成Gif动画\", null, null, DevComponents.DotNetBar.eTooltipColor.System));\n            this.rdoMosaic.TabIndex = 3;\n            this.rdoMosaic.Text = \"Mosaic\";\n            this.rdoMosaic.CheckedChanged += new System.EventHandler(this.rdoMosaic_CheckedChanged);\n            // \n            // panelExMosaic\n            // \n            this.panelExMosaic.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelExMosaic.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelExMosaic.Controls.Add(this.labelX7);\n            this.panelExMosaic.Controls.Add(this.labelX6);\n            this.panelExMosaic.Controls.Add(this.labelX5);\n            this.panelExMosaic.Controls.Add(this.slider2);\n            this.panelExMosaic.Controls.Add(this.colorPickerButton3);\n            this.panelExMosaic.Controls.Add(this.colorPickerButton2);\n            this.panelExMosaic.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelExMosaic.Enabled = false;\n            this.panelExMosaic.Location = new System.Drawing.Point(80, 105);\n            this.panelExMosaic.Name = \"panelExMosaic\";\n            this.panelExMosaic.Size = new System.Drawing.Size(256, 66);\n            this.panelExMosaic.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelExMosaic.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelExMosaic.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelExMosaic.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelExMosaic.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelExMosaic.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelExMosaic.Style.GradientAngle = 90;\n            this.panelExMosaic.TabIndex = 4;\n            // \n            // labelX7\n            // \n            this.labelX7.AutoSize = true;\n            // \n            // \n            // \n            this.labelX7.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX7.Location = new System.Drawing.Point(11, 42);\n            this.labelX7.Name = \"labelX7\";\n            this.labelX7.Size = new System.Drawing.Size(62, 16);\n            this.labelX7.TabIndex = 4;\n            this.labelX7.Text = \"BlockSize\";\n            // \n            // labelX6\n            // \n            this.labelX6.AutoSize = true;\n            // \n            // \n            // \n            this.labelX6.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX6.Location = new System.Drawing.Point(104, 14);\n            this.labelX6.Name = \"labelX6\";\n            this.labelX6.Size = new System.Drawing.Size(44, 16);\n            this.labelX6.TabIndex = 2;\n            this.labelX6.Text = \"Color1\";\n            // \n            // labelX5\n            // \n            this.labelX5.AutoSize = true;\n            // \n            // \n            // \n            this.labelX5.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX5.Location = new System.Drawing.Point(11, 14);\n            this.labelX5.Name = \"labelX5\";\n            this.labelX5.Size = new System.Drawing.Size(44, 16);\n            this.labelX5.TabIndex = 0;\n            this.labelX5.Text = \"Color0\";\n            // \n            // slider2\n            // \n            // \n            // \n            // \n            this.slider2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.slider2.LabelPosition = DevComponents.DotNetBar.eSliderLabelPosition.Right;\n            this.slider2.LabelWidth = 25;\n            this.slider2.Location = new System.Drawing.Point(79, 35);\n            this.slider2.Maximum = 256;\n            this.slider2.Minimum = 1;\n            this.slider2.Name = \"slider2\";\n            this.slider2.Size = new System.Drawing.Size(136, 23);\n            this.slider2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.slider2.TabIndex = 5;\n            this.slider2.Text = \"1\";\n            this.slider2.Value = 1;\n            this.slider2.ValueChanged += new System.EventHandler(this.slider1_ValueChanged);\n            // \n            // colorPickerButton3\n            // \n            this.colorPickerButton3.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerButton3.AutoExpandOnClick = true;\n            this.colorPickerButton3.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerButton3.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton3.Image\")));\n            this.colorPickerButton3.Location = new System.Drawing.Point(154, 7);\n            this.colorPickerButton3.Name = \"colorPickerButton3\";\n            this.colorPickerButton3.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerButton3.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerButton3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerButton3.TabIndex = 3;\n            // \n            // colorPickerButton2\n            // \n            this.colorPickerButton2.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.colorPickerButton2.AutoExpandOnClick = true;\n            this.colorPickerButton2.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.colorPickerButton2.Image = ((System.Drawing.Image)(resources.GetObject(\"colorPickerButton2.Image\")));\n            this.colorPickerButton2.Location = new System.Drawing.Point(61, 7);\n            this.colorPickerButton2.Name = \"colorPickerButton2\";\n            this.colorPickerButton2.SelectedColorImageRectangle = new System.Drawing.Rectangle(2, 2, 12, 12);\n            this.colorPickerButton2.Size = new System.Drawing.Size(37, 23);\n            this.colorPickerButton2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.colorPickerButton2.TabIndex = 1;\n            // \n            // panelExColor\n            // \n            this.panelExColor.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelExColor.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelExColor.Controls.Add(this.labelX1);\n            this.panelExColor.Controls.Add(this.colorPickerButton1);\n            this.panelExColor.Controls.Add(this.checkBoxX1);\n            this.panelExColor.Controls.Add(this.labelX3);\n            this.panelExColor.Controls.Add(this.slider1);\n            this.panelExColor.Controls.Add(this.btnPreset);\n            this.panelExColor.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelExColor.Enabled = false;\n            this.panelExColor.Location = new System.Drawing.Point(80, 14);\n            this.panelExColor.Name = \"panelExColor\";\n            this.panelExColor.Size = new System.Drawing.Size(256, 86);\n            this.panelExColor.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelExColor.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelExColor.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelExColor.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelExColor.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelExColor.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelExColor.Style.GradientAngle = 90;\n            this.panelExColor.TabIndex = 2;\n            // \n            // rdoColor\n            // \n            this.rdoColor.AutoSize = true;\n            this.rdoColor.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.rdoColor.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.rdoColor.CheckBoxStyle = DevComponents.DotNetBar.eCheckBoxStyle.RadioButton;\n            this.rdoColor.Location = new System.Drawing.Point(16, 59);\n            this.rdoColor.Name = \"rdoColor\";\n            this.rdoColor.Size = new System.Drawing.Size(57, 16);\n            this.rdoColor.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.rdoColor, new DevComponents.DotNetBar.SuperTooltipInfo(\"\", \"\", \"使用透明或纯色背景生成Gif动画。\", null, null, DevComponents.DotNetBar.eTooltipColor.System));\n            this.rdoColor.TabIndex = 1;\n            this.rdoColor.Text = \"Color\";\n            this.rdoColor.CheckedChanged += new System.EventHandler(this.rdoColor_CheckedChanged);\n            // \n            // superTooltip1\n            // \n            this.superTooltip1.DefaultTooltipSettings = new DevComponents.DotNetBar.SuperTooltipInfo(\"\", \"\", \"\", null, null, DevComponents.DotNetBar.eTooltipColor.Gray);\n            // \n            // checkBoxX2\n            // \n            this.checkBoxX2.AutoSize = true;\n            this.checkBoxX2.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX2.Location = new System.Drawing.Point(16, 231);\n            this.checkBoxX2.Name = \"checkBoxX2\";\n            this.checkBoxX2.Size = new System.Drawing.Size(144, 16);\n            this.checkBoxX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX2.TabIndex = 8;\n            this.checkBoxX2.Text = \"Save Frames As .png\";\n            // \n            // labelX8\n            // \n            this.labelX8.AutoSize = true;\n            this.labelX8.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX8.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX8.Location = new System.Drawing.Point(16, 256);\n            this.labelX8.Name = \"labelX8\";\n            this.labelX8.Size = new System.Drawing.Size(37, 16);\n            this.labelX8.TabIndex = 8;\n            this.labelX8.Text = \"Delay\";\n            // \n            // integerInput1\n            // \n            // \n            // \n            // \n            this.integerInput1.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.integerInput1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.integerInput1.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.integerInput1.Increment = 10;\n            this.integerInput1.Location = new System.Drawing.Point(80, 253);\n            this.integerInput1.MaxValue = 1000;\n            this.integerInput1.MinValue = 10;\n            this.integerInput1.Name = \"integerInput1\";\n            this.integerInput1.ShowUpDown = true;\n            this.integerInput1.Size = new System.Drawing.Size(80, 21);\n            this.integerInput1.TabIndex = 9;\n            this.integerInput1.Value = 10;\n            // \n            // labelX9\n            // \n            this.labelX9.AutoSize = true;\n            this.labelX9.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX9.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX9.Location = new System.Drawing.Point(16, 182);\n            this.labelX9.Name = \"labelX9\";\n            this.labelX9.Size = new System.Drawing.Size(50, 16);\n            this.labelX9.TabIndex = 16;\n            this.labelX9.Text = \"Encoder\";\n            // \n            // comboBoxEx2\n            // \n            this.comboBoxEx2.DisplayMember = \"Text\";\n            this.comboBoxEx2.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.comboBoxEx2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.comboBoxEx2.FormattingEnabled = true;\n            this.comboBoxEx2.ItemHeight = 15;\n            this.comboBoxEx2.Items.AddRange(new object[] {\n            this.comboItem3,\n            this.comboItem4,\n            this.comboItem5});\n            this.comboBoxEx2.Location = new System.Drawing.Point(80, 205);\n            this.comboBoxEx2.Name = \"comboBoxEx2\";\n            this.comboBoxEx2.Size = new System.Drawing.Size(129, 21);\n            this.comboBoxEx2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.comboBoxEx2.TabIndex = 7;\n            // \n            // comboItem3\n            // \n            this.comboItem3.Text = \"Default\";\n            // \n            // comboItem4\n            // \n            this.comboItem4.Text = \"PathToImage\";\n            // \n            // comboItem5\n            // \n            this.comboItem5.Text = \"PathToWz\";\n            // \n            // superTabControl1\n            // \n            // \n            // \n            // \n            // \n            // \n            // \n            this.superTabControl1.ControlBox.CloseBox.Name = \"\";\n            // \n            // \n            // \n            this.superTabControl1.ControlBox.MenuBox.Name = \"\";\n            this.superTabControl1.ControlBox.Name = \"\";\n            this.superTabControl1.ControlBox.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabControl1.ControlBox.MenuBox,\n            this.superTabControl1.ControlBox.CloseBox});\n            this.superTabControl1.Controls.Add(this.superTabControlPanel3);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel1);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel2);\n            this.superTabControl1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControl1.Location = new System.Drawing.Point(0, 0);\n            this.superTabControl1.Name = \"superTabControl1\";\n            this.superTabControl1.ReorderTabsEnabled = true;\n            this.superTabControl1.SelectedTabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Bold);\n            this.superTabControl1.SelectedTabIndex = 0;\n            this.superTabControl1.Size = new System.Drawing.Size(454, 311);\n            this.superTabControl1.TabAlignment = DevComponents.DotNetBar.eTabStripAlignment.Left;\n            this.superTabControl1.TabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.superTabControl1.TabIndex = 30;\n            this.superTabControl1.Tabs.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabItem1,\n            this.superTabItem2,\n            this.superTabItem3});\n            this.superTabControl1.Text = \"superTabControl1\";\n            // \n            // superTabControlPanel1\n            // \n            this.superTabControlPanel1.Controls.Add(this.comboBoxEx2);\n            this.superTabControlPanel1.Controls.Add(this.labelX9);\n            this.superTabControlPanel1.Controls.Add(this.labelX2);\n            this.superTabControlPanel1.Controls.Add(this.integerInput1);\n            this.superTabControlPanel1.Controls.Add(this.comboBoxEx1);\n            this.superTabControlPanel1.Controls.Add(this.labelX8);\n            this.superTabControlPanel1.Controls.Add(this.rdoMosaic);\n            this.superTabControlPanel1.Controls.Add(this.checkBoxX2);\n            this.superTabControlPanel1.Controls.Add(this.panelExColor);\n            this.superTabControlPanel1.Controls.Add(this.rdoColor);\n            this.superTabControlPanel1.Controls.Add(this.panelExMosaic);\n            this.superTabControlPanel1.Controls.Add(this.labelX4);\n            this.superTabControlPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel1.Location = new System.Drawing.Point(105, 0);\n            this.superTabControlPanel1.Name = \"superTabControlPanel1\";\n            this.superTabControlPanel1.Size = new System.Drawing.Size(349, 311);\n            this.superTabControlPanel1.TabIndex = 1;\n            this.superTabControlPanel1.TabItem = this.superTabItem1;\n            // \n            // superTabItem1\n            // \n            this.superTabItem1.AttachedControl = this.superTabControlPanel1;\n            this.superTabItem1.GlobalItem = false;\n            this.superTabItem1.Name = \"superTabItem1\";\n            this.superTabItem1.Text = \"General\";\n            // \n            // superTabControlPanel3\n            // \n            this.superTabControlPanel3.Controls.Add(this.labelX13);\n            this.superTabControlPanel3.Controls.Add(this.textBoxX3);\n            this.superTabControlPanel3.Controls.Add(this.labelX12);\n            this.superTabControlPanel3.Controls.Add(this.buttonX3);\n            this.superTabControlPanel3.Controls.Add(this.textBoxX2);\n            this.superTabControlPanel3.Controls.Add(this.labelX11);\n            this.superTabControlPanel3.Controls.Add(this.textBoxX1);\n            this.superTabControlPanel3.Controls.Add(this.labelX10);\n            this.superTabControlPanel3.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel3.Location = new System.Drawing.Point(108, 0);\n            this.superTabControlPanel3.Name = \"superTabControlPanel3\";\n            this.superTabControlPanel3.Size = new System.Drawing.Size(346, 311);\n            this.superTabControlPanel3.TabIndex = 0;\n            this.superTabControlPanel3.TabItem = this.superTabItem3;\n            // \n            // buttonX3\n            // \n            this.buttonX3.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX3.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX3.Location = new System.Drawing.Point(315, 14);\n            this.buttonX3.Name = \"buttonX3\";\n            this.buttonX3.Size = new System.Drawing.Size(24, 24);\n            this.buttonX3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX3.Symbol = \"\";\n            this.buttonX3.SymbolSize = 12F;\n            this.buttonX3.TabIndex = 15;\n            this.buttonX3.Click += new System.EventHandler(this.buttonX3_Click);\n            // \n            // textBoxX2\n            // \n            this.textBoxX2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.textBoxX2.Border.Class = \"TextBoxBorder\";\n            this.textBoxX2.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.textBoxX2.Location = new System.Drawing.Point(109, 46);\n            this.textBoxX2.Multiline = true;\n            this.textBoxX2.Name = \"textBoxX2\";\n            this.textBoxX2.PreventEnterBeep = true;\n            this.textBoxX2.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;\n            this.textBoxX2.Size = new System.Drawing.Size(201, 124);\n            this.textBoxX2.TabIndex = 14;\n            // \n            // labelX11\n            // \n            this.labelX11.AutoSize = true;\n            this.labelX11.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX11.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX11.Location = new System.Drawing.Point(16, 50);\n            this.labelX11.Name = \"labelX11\";\n            this.labelX11.Size = new System.Drawing.Size(93, 16);\n            this.labelX11.TabIndex = 13;\n            this.labelX11.Text = \"FFmpegArgument\";\n            // \n            // textBoxX1\n            // \n            this.textBoxX1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.textBoxX1.Border.Class = \"TextBoxBorder\";\n            this.textBoxX1.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.textBoxX1.Location = new System.Drawing.Point(109, 16);\n            this.textBoxX1.Name = \"textBoxX1\";\n            this.textBoxX1.PreventEnterBeep = true;\n            this.textBoxX1.Size = new System.Drawing.Size(201, 21);\n            this.textBoxX1.TabIndex = 12;\n            // \n            // labelX10\n            // \n            this.labelX10.AutoSize = true;\n            this.labelX10.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX10.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX10.Location = new System.Drawing.Point(16, 20);\n            this.labelX10.Name = \"labelX10\";\n            this.labelX10.Size = new System.Drawing.Size(87, 16);\n            this.labelX10.TabIndex = 10;\n            this.labelX10.Text = \"FFmpegBinPath\";\n            // \n            // superTabItem3\n            // \n            this.superTabItem3.AttachedControl = this.superTabControlPanel3;\n            this.superTabItem3.GlobalItem = false;\n            this.superTabItem3.Name = \"superTabItem3\";\n            this.superTabItem3.Text = \"FFmpegEncoder\";\n            // \n            // superTabControlPanel2\n            // \n            this.superTabControlPanel2.Controls.Add(this.checkBoxX3);\n            this.superTabControlPanel2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel2.Location = new System.Drawing.Point(105, 0);\n            this.superTabControlPanel2.Name = \"superTabControlPanel2\";\n            this.superTabControlPanel2.Size = new System.Drawing.Size(349, 291);\n            this.superTabControlPanel2.TabIndex = 0;\n            this.superTabControlPanel2.TabItem = this.superTabItem2;\n            // \n            // checkBoxX3\n            // \n            this.checkBoxX3.AutoSize = true;\n            this.checkBoxX3.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX3.Location = new System.Drawing.Point(16, 20);\n            this.checkBoxX3.Name = \"checkBoxX3\";\n            this.checkBoxX3.Size = new System.Drawing.Size(255, 16);\n            this.checkBoxX3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX3.TabIndex = 24;\n            this.checkBoxX3.Text = \"Optimize File Size (8bit palette png)\";\n            // \n            // superTabItem2\n            // \n            this.superTabItem2.AttachedControl = this.superTabControlPanel2;\n            this.superTabItem2.GlobalItem = false;\n            this.superTabItem2.Name = \"superTabItem2\";\n            this.superTabItem2.Text = \"APngEncoder\";\n            // \n            // panelEx1\n            // \n            this.panelEx1.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx1.Controls.Add(this.buttonX2);\n            this.panelEx1.Controls.Add(this.buttonX1);\n            this.panelEx1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx1.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.panelEx1.Location = new System.Drawing.Point(0, 311);\n            this.panelEx1.Name = \"panelEx1\";\n            this.panelEx1.Size = new System.Drawing.Size(454, 30);\n            this.panelEx1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx1.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx1.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx1.Style.GradientAngle = 90;\n            this.panelEx1.TabIndex = 38;\n            // \n            // buttonX2\n            // \n            this.buttonX2.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX2.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX2.DialogResult = System.Windows.Forms.DialogResult.Cancel;\n            this.buttonX2.Location = new System.Drawing.Point(385, 4);\n            this.buttonX2.Name = \"buttonX2\";\n            this.buttonX2.Size = new System.Drawing.Size(60, 23);\n            this.buttonX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX2.TabIndex = 1;\n            this.buttonX2.Text = \"取消\";\n            // \n            // buttonX1\n            // \n            this.buttonX1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX1.DialogResult = System.Windows.Forms.DialogResult.OK;\n            this.buttonX1.Location = new System.Drawing.Point(318, 4);\n            this.buttonX1.Name = \"buttonX1\";\n            this.buttonX1.Size = new System.Drawing.Size(60, 23);\n            this.buttonX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX1.TabIndex = 0;\n            this.buttonX1.Text = \"确定\";\n            // \n            // labelX12\n            // \n            this.labelX12.AutoSize = true;\n            this.labelX12.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX12.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX12.Location = new System.Drawing.Point(16, 184);\n            this.labelX12.Name = \"labelX12\";\n            this.labelX12.Size = new System.Drawing.Size(87, 16);\n            this.labelX12.TabIndex = 16;\n            this.labelX12.Text = \"OutputFileExt\";\n            // \n            // textBoxX3\n            // \n            this.textBoxX3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.textBoxX3.Border.Class = \"TextBoxBorder\";\n            this.textBoxX3.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.textBoxX3.Location = new System.Drawing.Point(109, 180);\n            this.textBoxX3.Name = \"textBoxX3\";\n            this.textBoxX3.PreventEnterBeep = true;\n            this.textBoxX3.Size = new System.Drawing.Size(75, 21);\n            this.textBoxX3.TabIndex = 17;\n            // \n            // labelX13\n            // \n            this.labelX13.AutoSize = true;\n            this.labelX13.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX13.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX13.ForeColor = System.Drawing.SystemColors.ControlDarkDark;\n            this.labelX13.Location = new System.Drawing.Point(16, 207);\n            this.labelX13.Name = \"labelX13\";\n            this.labelX13.Size = new System.Drawing.Size(126, 86);\n            this.labelX13.TabIndex = 18;\n            this.labelX13.Text = \"参数通配符：<br/>\\r\\n &nbsp;%i 输入文件名<br/>\\r\\n &nbsp;%o 输出文件名<br/>\\r\\n &nbsp;%w 输入图像宽度(px)<br/\" +\n    \">\\r\\n &nbsp;%h 输出图像高度(px)<br/>\\r\\n &nbsp;%t 帧间隔(ms)\";\n            // \n            // FrmGifSetting\n            // \n            this.CancelButton = this.buttonX2;\n            this.ClientSize = new System.Drawing.Size(454, 341);\n            this.Controls.Add(this.superTabControl1);\n            this.Controls.Add(this.panelEx1);\n            this.DoubleBuffered = true;\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"FrmGifSetting\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"Gif输出设置\";\n            this.panelExMosaic.ResumeLayout(false);\n            this.panelExMosaic.PerformLayout();\n            this.panelExColor.ResumeLayout(false);\n            this.panelExColor.PerformLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.integerInput1)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).EndInit();\n            this.superTabControl1.ResumeLayout(false);\n            this.superTabControlPanel1.ResumeLayout(false);\n            this.superTabControlPanel1.PerformLayout();\n            this.superTabControlPanel3.ResumeLayout(false);\n            this.superTabControlPanel3.PerformLayout();\n            this.superTabControlPanel2.ResumeLayout(false);\n            this.superTabControlPanel2.PerformLayout();\n            this.panelEx1.ResumeLayout(false);\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerButton1;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX1;\n        private DevComponents.DotNetBar.LabelX labelX2;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx comboBoxEx1;\n        private DevComponents.Editors.ComboItem comboItem1;\n        private DevComponents.DotNetBar.LabelX labelX3;\n        private DevComponents.DotNetBar.Controls.Slider slider1;\n        private DevComponents.Editors.ComboItem comboItem2;\n        private DevComponents.DotNetBar.LabelX labelX4;\n        private DevComponents.DotNetBar.Controls.CheckBoxX rdoMosaic;\n        private DevComponents.DotNetBar.PanelEx panelExMosaic;\n        private DevComponents.DotNetBar.LabelX labelX7;\n        private DevComponents.DotNetBar.LabelX labelX6;\n        private DevComponents.DotNetBar.LabelX labelX5;\n        private DevComponents.DotNetBar.Controls.Slider slider2;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerButton3;\n        private DevComponents.DotNetBar.ColorPickerButton colorPickerButton2;\n        private DevComponents.DotNetBar.PanelEx panelExColor;\n        private DevComponents.DotNetBar.Controls.CheckBoxX rdoColor;\n        private DevComponents.DotNetBar.SuperTooltip superTooltip1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX2;\n        private DevComponents.DotNetBar.LabelX labelX8;\n        private DevComponents.Editors.IntegerInput integerInput1;\n        private DevComponents.DotNetBar.LabelX labelX9;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx comboBoxEx2;\n        private DevComponents.Editors.ComboItem comboItem3;\n        private DevComponents.Editors.ComboItem comboItem4;\n        private DevComponents.Editors.ComboItem comboItem5;\n        private DevComponents.Editors.ComboItem comboItem6;\n        private DevComponents.DotNetBar.SuperTabControl superTabControl1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel1;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem1;\n        private DevComponents.DotNetBar.PanelEx panelEx1;\n        private DevComponents.DotNetBar.ButtonX buttonX2;\n        private DevComponents.DotNetBar.ButtonX buttonX1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel2;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem2;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel3;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem3;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX3;\n        private DevComponents.DotNetBar.LabelX labelX10;\n        private DevComponents.DotNetBar.ButtonX buttonX3;\n        private DevComponents.DotNetBar.Controls.TextBoxX textBoxX2;\n        private DevComponents.DotNetBar.LabelX labelX11;\n        private DevComponents.DotNetBar.Controls.TextBoxX textBoxX1;\n        private DevComponents.Editors.ComboItem comboItem7;\n        private DevComponents.DotNetBar.LabelX labelX12;\n        private DevComponents.DotNetBar.Controls.TextBoxX textBoxX3;\n        private DevComponents.DotNetBar.LabelX labelX13;\n        private DevComponents.DotNetBar.ButtonX btnPreset;\n        private DevComponents.DotNetBar.ButtonItem btnNonTransparentMP4Preset;\n        private DevComponents.DotNetBar.ButtonItem btnGreenBackdropMP4Preset;\n        private DevComponents.DotNetBar.ButtonItem btnBlueBackdropMP4Preset;\n        private DevComponents.DotNetBar.ButtonItem btnTransparentMOVPreset;\n        private DevComponents.DotNetBar.ButtonItem btnTransparentWebMPreset;\n        private DevComponents.DotNetBar.ButtonItem btnDefaultPreset;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmGifSetting.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Data;\nusing System.Drawing;\nusing System.Text;\nusing System.Linq;\nusing System.Windows.Forms;\nusing System.Reflection;\nusing DevComponents.DotNetBar;\nusing WzComparerR2.Config;\nusing MathHelper = Microsoft.Xna.Framework.MathHelper;\n\nnamespace WzComparerR2\n{\n    public partial class FrmGifSetting : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmGifSetting()\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n            initSelection();\n        }\n\n        private void initSelection()\n        {\n            comboBoxEx1.SelectedIndex = 0;\n            comboBoxEx2.SelectedIndex = 0;\n        }\n\n        public bool SavePngFramesEnabled\n        {\n            get { return checkBoxX2.Checked; }\n            set { checkBoxX2.Checked = value; }\n        }\n\n        public int GifEncoder\n        {\n            get { return comboBoxEx1.SelectedIndex; }\n            set { comboBoxEx1.SelectedIndex = MathHelper.Clamp(value, 0, comboBoxEx1.Items.Count - 1); }\n        }\n\n        public ImageNameMethod ImageNameMethod\n        {\n            get { return (ImageNameMethod)comboBoxEx2.SelectedIndex; }\n            set { comboBoxEx2.SelectedIndex = MathHelper.Clamp((int)value, 0, comboBoxEx2.Items.Count - 1); }\n        }\n\n        public ImageBackgroundType BackgroundType\n        {\n            get\n            {\n                if (rdoColor.Checked)\n                {\n                    return checkBoxX1.Checked ? ImageBackgroundType.Transparent : ImageBackgroundType.Color;\n                }\n                else if (rdoMosaic.Checked)\n                {\n                    return ImageBackgroundType.Mosaic;\n                }\n                else //默认\n                {\n                    return ImageBackgroundType.Transparent;\n                }\n            }\n            set\n            {\n                switch (value)\n                {\n                    default:\n                    case ImageBackgroundType.Transparent:\n                        rdoColor.Checked = true;\n                        checkBoxX1.Checked = true;\n                        break;\n\n                    case ImageBackgroundType.Color:\n                        rdoColor.Checked = true;\n                        checkBoxX1.Checked = false;\n                        break;\n\n                    case ImageBackgroundType.Mosaic:\n                        rdoMosaic.Checked = true;\n                        break;\n                }\n            }\n        }\n\n        public Color BackgroundColor\n        {\n            get { return colorPickerButton1.SelectedColor; }\n            set { colorPickerButton1.SelectedColor = value; }\n        }\n\n        public int MinMixedAlpha\n        {\n            get { return slider1.Value; }\n            set { slider1.Value = MathHelper.Clamp(value, slider1.Minimum, slider1.Maximum); }\n        }\n\n        public int MinDelay\n        {\n            get { return integerInput1.Value; }\n            set { integerInput1.Value = MathHelper.Clamp(value, integerInput1.MinValue, integerInput1.MaxValue); }\n        }\n\n        public Color MosaicColor0\n        {\n            get { return colorPickerButton2.SelectedColor; }\n            set { colorPickerButton2.SelectedColor = value; }\n        }\n\n        public Color MosaicColor1\n        {\n            get { return colorPickerButton3.SelectedColor; }\n            set { colorPickerButton3.SelectedColor = value; }\n        }\n\n        public int MosaicBlockSize\n        {\n            get { return slider2.Value; }\n            set { slider2.Value = MathHelper.Clamp(value, slider2.Minimum, slider2.Maximum); }\n        }\n\n        public bool PaletteOptimized\n        {\n            get { return checkBoxX3.Checked; }\n            set { checkBoxX3.Checked = value; }\n        }\n\n        public string FFmpegBinPath\n        {\n            get { return textBoxX1.Text; }\n            set { textBoxX1.Text = value; }\n        }\n\n        public string FFmpegArgument\n        {\n            get { return textBoxX2.Text; }\n            set { textBoxX2.Text = value; }\n        }\n\n        public string FFmpegDefaultExtension\n        {\n            get { return textBoxX3.Text; }\n            set { textBoxX3.Text = value; }\n        }\n\n        public string FFmpegBinPathHint\n        {\n            set { textBoxX1.WatermarkText = value; }\n        }\n\n        public string FFmpegArgumentHint\n        {\n            set { textBoxX2.WatermarkText = value; }\n        }\n\n        public string FFmpegDefaultExtensionHint\n        {\n            set { textBoxX3.WatermarkText = value; }\n        }\n\n        public void Load(ImageHandlerConfig config)\n        {\n            this.SavePngFramesEnabled = config.SavePngFramesEnabled;\n            this.GifEncoder = config.GifEncoder;\n            this.ImageNameMethod = config.ImageNameMethod;\n            this.BackgroundType = config.BackgroundType;\n            this.BackgroundColor = config.BackgroundColor;\n            this.MinMixedAlpha = config.MinMixedAlpha;\n            this.MinDelay = config.MinDelay;\n\n            this.MosaicColor0 = config.MosaicInfo.Color0;\n            this.MosaicColor1 = config.MosaicInfo.Color1;\n            this.MosaicBlockSize = config.MosaicInfo.BlockSize;\n\n            this.PaletteOptimized = config.PaletteOptimized;\n\n            this.FFmpegBinPath = config.FFmpegBinPath;\n            this.FFmpegArgument = config.FFmpegArgument;\n            this.FFmpegDefaultExtension = config.FFmpegOutputFileExtension;\n        }\n\n        public void Save(ImageHandlerConfig config)\n        {\n            config.SavePngFramesEnabled = this.SavePngFramesEnabled;\n            config.GifEncoder = this.GifEncoder;\n            config.ImageNameMethod = this.ImageNameMethod;\n            config.BackgroundType = this.BackgroundType;\n            config.BackgroundColor = this.BackgroundColor;\n            config.MinMixedAlpha = this.MinMixedAlpha;\n            config.MinDelay = this.MinDelay;\n\n            config.MosaicInfo.Color0 = this.MosaicColor0;\n            config.MosaicInfo.Color1 = this.MosaicColor1;\n            config.MosaicInfo.BlockSize = this.MosaicBlockSize;\n\n            config.PaletteOptimized = this.PaletteOptimized;\n\n            config.FFmpegBinPath = this.FFmpegBinPath;\n            config.FFmpegArgument = this.FFmpegArgument;\n            config.FFmpegOutputFileExtension = this.FFmpegDefaultExtension;\n        }\n\n        private void btnNonTransparentMP4Preset_Click(object sender, EventArgs e)\n        {\n            GifEncoder = 3;\n            BackgroundType = ImageBackgroundType.Color;\n            BackgroundColor = Color.White;\n            FFmpegArgument = string.Empty;\n            FFmpegDefaultExtension = string.Empty;\n        }\n\n        private void btnGreenBackdropMP4Preset_Click(object sender, EventArgs e)\n        {\n            GifEncoder = 3;\n            BackgroundType = ImageBackgroundType.Color;\n            BackgroundColor = Color.FromArgb(0, 255, 0);\n            FFmpegArgument = string.Empty;\n            FFmpegDefaultExtension = string.Empty;\n        }\n\n        private void btnBlueBackdropMP4Preset_Click(object sender, EventArgs e)\n        {\n            GifEncoder = 3;\n            BackgroundType = ImageBackgroundType.Color;\n            BackgroundColor = Color.Blue;\n            FFmpegArgument = string.Empty;\n            FFmpegDefaultExtension = string.Empty;\n        }\n\n        private void btnTransparentMOVPreset_Click(object sender, EventArgs e)\n        {\n            GifEncoder = 3;\n            BackgroundType = ImageBackgroundType.Transparent;\n            MinMixedAlpha = 0;\n            BackgroundColor = Color.White;\n            FFmpegArgument = @$\"-y -f rawvideo -pixel_format bgra -s %w*%h -r 1000/%t -i \"\"%i\"\" -vf \"\"crop=trunc(iw/2)*2:trunc(ih/2)*2\"\" -vcodec qtrle -pix_fmt argb \"\"%o\"\"\";\n            FFmpegDefaultExtension = \".mov\";\n        }\n\n        private void btnTransparentWebMPreset_Click(object sender, EventArgs e)\n        {\n            GifEncoder = 3;\n            BackgroundType = ImageBackgroundType.Transparent;\n            MinMixedAlpha = 0;\n            BackgroundColor = Color.White;\n            FFmpegArgument = @$\"-y -f rawvideo -pixel_format bgra -s %w*%h -r 1000/%t -i \"\"%i\"\" -vf \"\"crop=trunc(iw/2)*2:trunc(ih/2)*2\"\" -vcodec libvpx-vp9 -pix_fmt yuva420p \"\"%o\"\"\";\n            FFmpegDefaultExtension = \".webm\";\n        }\n\n        private void btnDefaultPreset_Click(object sender, EventArgs e)\n        {\n            GifEncoder = 0;\n            BackgroundType = ImageBackgroundType.Transparent;\n            MinMixedAlpha = 0;\n            BackgroundColor = Color.White;\n            FFmpegArgument = string.Empty;\n            FFmpegDefaultExtension = string.Empty;\n        }\n\n        private void slider1_ValueChanged(object sender, EventArgs e)\n        {\n            var slider = sender as DevComponents.DotNetBar.Controls.Slider;\n            slider.Text = slider.Value.ToString();\n        }\n\n        private void rdoColor_CheckedChanged(object sender, EventArgs e)\n        {\n            panelExColor.Enabled = rdoColor.Checked;\n        }\n\n        private void rdoMosaic_CheckedChanged(object sender, EventArgs e)\n        {\n            panelExMosaic.Enabled = rdoMosaic.Checked;\n        }\n\n        private void buttonX3_Click(object sender, EventArgs e)\n        {\n            OpenFileDialog dlg = new();\n            dlg.Title = \"请选择FFmpeg可执行文件路径...\";\n            dlg.Filter = \"ffmpeg.exe|*.exe|*.*|*.*\";\n            dlg.FileName = this.FFmpegBinPath;\n            if (dlg.ShowDialog(this) == DialogResult.OK)\n            {\n                this.FFmpegBinPath = dlg.FileName;\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmGifSetting.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Drawing\" name=\"System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />\n  <data name=\"colorPickerButton1.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n    <value>\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6\n        JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAKklEQVQ4T2P4//8/RRhCAClyMIoBIJoU\n        PGrAqAEgPBwNIAfDDSAf/2cAALEslYfUgx+eAAAAAElFTkSuQmCC\n</value>\n  </data>\n  <metadata name=\"superTooltip1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 17</value>\n  </metadata>\n  <data name=\"colorPickerButton3.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n    <value>\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6\n        JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAKklEQVQ4T2P4//8/RRhCAClyMIoBIJoU\n        PGrAqAEgPBwNIAfDDSAf/2cAALEslYfUgx+eAAAAAElFTkSuQmCC\n</value>\n  </data>\n  <data name=\"colorPickerButton2.Image\" type=\"System.Drawing.Bitmap, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n    <value>\n        iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6\n        JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAKklEQVQ4T2P4//8/RRhCAClyMIoBIJoU\n        PGrAqAEgPBwNIAfDDSAf/2cAALEslYfUgx+eAAAAAElFTkSuQmCC\n</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmOptions.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmOptions\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.panelEx1 = new DevComponents.DotNetBar.PanelEx();\n            this.buttonX2 = new DevComponents.DotNetBar.ButtonX();\n            this.buttonX1 = new DevComponents.DotNetBar.ButtonX();\n            this.superTabControl1 = new DevComponents.DotNetBar.SuperTabControl();\n            this.superTabControlPanel1 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.chkAutoCheckExtFiles = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.cmbWzEncoding = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.chkWzAutoSort = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTabItem1 = new DevComponents.DotNetBar.SuperTabItem();\n            this.chkWzSortByImgID = new DevComponents.DotNetBar.Controls.CheckBoxX(); \n            this.chkImgCheckDisabled = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.panelEx1.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).BeginInit();\n            this.superTabControl1.SuspendLayout();\n            this.superTabControlPanel1.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // panelEx1\n            // \n            this.panelEx1.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx1.Controls.Add(this.buttonX2);\n            this.panelEx1.Controls.Add(this.buttonX1);\n            this.panelEx1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx1.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.panelEx1.Location = new System.Drawing.Point(0, 171);\n            this.panelEx1.Name = \"panelEx1\";\n            this.panelEx1.Size = new System.Drawing.Size(304, 30);\n            this.panelEx1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx1.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx1.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx1.Style.GradientAngle = 90;\n            this.panelEx1.TabIndex = 0;\n            // \n            // buttonX2\n            // \n            this.buttonX2.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX2.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX2.DialogResult = System.Windows.Forms.DialogResult.Cancel;\n            this.buttonX2.Location = new System.Drawing.Point(235, 4);\n            this.buttonX2.Name = \"buttonX2\";\n            this.buttonX2.Size = new System.Drawing.Size(60, 23);\n            this.buttonX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX2.TabIndex = 1;\n            this.buttonX2.Text = \"取消\";\n            // \n            // buttonX1\n            // \n            this.buttonX1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX1.DialogResult = System.Windows.Forms.DialogResult.OK;\n            this.buttonX1.Location = new System.Drawing.Point(168, 4);\n            this.buttonX1.Name = \"buttonX1\";\n            this.buttonX1.Size = new System.Drawing.Size(60, 23);\n            this.buttonX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX1.TabIndex = 0;\n            this.buttonX1.Text = \"确定\";\n            // \n            // superTabControl1\n            // \n            this.superTabControl1.AutoCloseTabs = false;\n            // \n            // \n            // \n            // \n            // \n            // \n            this.superTabControl1.ControlBox.CloseBox.Name = \"\";\n            // \n            // \n            // \n            this.superTabControl1.ControlBox.MenuBox.Name = \"\";\n            this.superTabControl1.ControlBox.Name = \"\";\n            this.superTabControl1.ControlBox.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabControl1.ControlBox.MenuBox,\n            this.superTabControl1.ControlBox.CloseBox});\n            this.superTabControl1.Controls.Add(this.superTabControlPanel1);\n            this.superTabControl1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControl1.Location = new System.Drawing.Point(0, 0);\n            this.superTabControl1.Name = \"superTabControl1\";\n            this.superTabControl1.ReorderTabsEnabled = true;\n            this.superTabControl1.SelectedTabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Bold);\n            this.superTabControl1.SelectedTabIndex = 0;\n            this.superTabControl1.Size = new System.Drawing.Size(304, 171);\n            this.superTabControl1.TabAlignment = DevComponents.DotNetBar.eTabStripAlignment.Left;\n            this.superTabControl1.TabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.superTabControl1.TabIndex = 4;\n            this.superTabControl1.Tabs.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabItem1});\n            this.superTabControl1.Text = \"superTabControl1\";\n            // \n            // superTabControlPanel1\n            // \n            this.superTabControlPanel1.Controls.Add(this.chkImgCheckDisabled);\n            this.superTabControlPanel1.Controls.Add(this.chkWzSortByImgID);\n            this.superTabControlPanel1.Controls.Add(this.chkAutoCheckExtFiles);\n            this.superTabControlPanel1.Controls.Add(this.cmbWzEncoding);\n            this.superTabControlPanel1.Controls.Add(this.labelX1);\n            this.superTabControlPanel1.Controls.Add(this.chkWzAutoSort);\n            this.superTabControlPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel1.Location = new System.Drawing.Point(82, 0);\n            this.superTabControlPanel1.Name = \"superTabControlPanel1\";\n            this.superTabControlPanel1.Size = new System.Drawing.Size(222, 171);\n            this.superTabControlPanel1.TabIndex = 1;\n            this.superTabControlPanel1.TabItem = this.superTabItem1;\n            // \n            // chkAutoCheckExtFiles\n            // \n            this.chkAutoCheckExtFiles.AutoSize = true;\n            this.chkAutoCheckExtFiles.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkAutoCheckExtFiles.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkAutoCheckExtFiles.Location = new System.Drawing.Point(14, 86);\n            this.chkAutoCheckExtFiles.Name = \"chkAutoCheckExtFiles\";\n            this.chkAutoCheckExtFiles.Size = new System.Drawing.Size(193, 18);\n            this.chkAutoCheckExtFiles.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkAutoCheckExtFiles.TabIndex = 4;\n            this.chkAutoCheckExtFiles.Text = \"自动检测扩展wz文件(map2...)\";\n            // \n            // cmbWzEncoding\n            // \n            this.cmbWzEncoding.DisplayMember = \"Text\";\n            this.cmbWzEncoding.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbWzEncoding.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbWzEncoding.FormattingEnabled = true;\n            this.cmbWzEncoding.ItemHeight = 15;\n            this.cmbWzEncoding.Location = new System.Drawing.Point(86, 59);\n            this.cmbWzEncoding.Name = \"cmbWzEncoding\";\n            this.cmbWzEncoding.Size = new System.Drawing.Size(121, 21);\n            this.cmbWzEncoding.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbWzEncoding.TabIndex = 3;\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            this.labelX1.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(14, 61);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(68, 18);\n            this.labelX1.TabIndex = 2;\n            this.labelX1.Text = \"wz默认编码\";\n            // \n            // chkWzAutoSort\n            // \n            this.chkWzAutoSort.AutoSize = true;\n            this.chkWzAutoSort.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkWzAutoSort.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkWzAutoSort.Location = new System.Drawing.Point(14, 13);\n            this.chkWzAutoSort.Name = \"chkWzAutoSort\";\n            this.chkWzAutoSort.Size = new System.Drawing.Size(125, 18);\n            this.chkWzAutoSort.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkWzAutoSort.TabIndex = 0;\n            this.chkWzAutoSort.Text = \"Wz加载时自动排序\";\n            // \n            // superTabItem1\n            // \n            this.superTabItem1.AttachedControl = this.superTabControlPanel1;\n            this.superTabItem1.GlobalItem = false;\n            this.superTabItem1.Name = \"superTabItem1\";\n            this.superTabItem1.Text = \"WzLoading\";\n            // \n            // chkWzSortByImgID\n            // \n            this.chkWzSortByImgID.AutoSize = true;\n            this.chkWzSortByImgID.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkWzSortByImgID.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkWzSortByImgID.Location = new System.Drawing.Point(31, 36);\n            this.chkWzSortByImgID.Name = \"chkWzSortByImgID\";\n            this.chkWzSortByImgID.Size = new System.Drawing.Size(107, 18);\n            this.chkWzSortByImgID.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkWzSortByImgID.TabIndex = 5;\n            this.chkWzSortByImgID.Text = \"按照ImgID排序\";\n            // \n            // chkImgCheckDisabled\n            // \n            this.chkImgCheckDisabled.AutoSize = true;\n            this.chkImgCheckDisabled.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkImgCheckDisabled.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkImgCheckDisabled.Location = new System.Drawing.Point(14, 110);\n            this.chkImgCheckDisabled.Name = \"chkImgCheckDisabled\";\n            this.chkImgCheckDisabled.Size = new System.Drawing.Size(132, 18);\n            this.chkImgCheckDisabled.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkImgCheckDisabled.TabIndex = 6;\n            this.chkImgCheckDisabled.Text = \"跳过img校验和检测\";\n            // \n            // FrmOptions\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(304, 201);\n            this.Controls.Add(this.superTabControl1);\n            this.Controls.Add(this.panelEx1);\n            this.DoubleBuffered = true;\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n            this.MaximizeBox = false;\n            this.Name = \"FrmOptions\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"通用设置\";\n            this.panelEx1.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).EndInit();\n            this.superTabControl1.ResumeLayout(false);\n            this.superTabControlPanel1.ResumeLayout(false);\n            this.superTabControlPanel1.PerformLayout();\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.PanelEx panelEx1;\n        private DevComponents.DotNetBar.SuperTabControl superTabControl1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel1;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem1;\n        private DevComponents.DotNetBar.ButtonX buttonX2;\n        private DevComponents.DotNetBar.ButtonX buttonX1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkWzAutoSort;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbWzEncoding;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkAutoCheckExtFiles;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkWzSortByImgID;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkImgCheckDisabled;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmOptions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Data;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing DevComponents.Editors;\nusing WzComparerR2.Config;\n\nnamespace WzComparerR2\n{\n    public partial class FrmOptions : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmOptions()\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n\n            cmbWzEncoding.Items.AddRange(new[]\n            {\n                new ComboItem(\"默认\"){ Value = 0 },\n                new ComboItem(\"CMS(gb2312)\"){ Value = 936 },\n                new ComboItem(\"KMS(euc-kr)\"){ Value = 949 },\n                new ComboItem(\"JMS(shift-jis)\"){ Value = 932 },\n                new ComboItem(\"TMS(big5)\"){ Value = 950 },\n                new ComboItem(\"GMS(iso-8859-1)\"){ Value = 1252 },\n                new ComboItem(\"自定义\"){ Value = -1 },\n            });\n        }\n\n        public bool SortWzOnOpened\n        {\n            get { return chkWzAutoSort.Checked; }\n            set { chkWzAutoSort.Checked = value; }\n        }\n\n        public bool SortWzByImgID\n        {\n            get { return chkWzSortByImgID.Checked; }\n            set { chkWzSortByImgID.Checked = value; }\n        }\n\n        public int DefaultWzCodePage\n        {\n            get\n            {\n                return ((cmbWzEncoding.SelectedItem as ComboItem)?.Value as int?) ?? 0;\n            }\n            set\n            {\n                var items = cmbWzEncoding.Items.Cast<ComboItem>();\n                var item = items.FirstOrDefault(_item => _item.Value as int? == value)\n                    ?? items.Last();\n                item.Value = value;\n                cmbWzEncoding.SelectedItem = item;\n            }\n        }\n\n        public bool AutoDetectExtFiles\n        {\n            get { return chkAutoCheckExtFiles.Checked; }\n            set { chkAutoCheckExtFiles.Checked = value; }\n        }\n\n        public bool ImgCheckDisabled\n        {\n            get { return chkImgCheckDisabled.Checked; }\n            set { chkImgCheckDisabled.Checked = value; }\n        }\n\n        public void Load(WcR2Config config)\n        {\n            this.SortWzOnOpened = config.SortWzOnOpened;\n            this.SortWzByImgID = config.SortWzByImgID;\n            this.DefaultWzCodePage = config.WzEncoding;\n            this.AutoDetectExtFiles = config.AutoDetectExtFiles;\n            this.ImgCheckDisabled = config.ImgCheckDisabled;\n        }\n\n        public void Save(WcR2Config config)\n        {\n            config.SortWzOnOpened = this.SortWzOnOpened;\n            config.SortWzByImgID = this.SortWzByImgID;\n            config.WzEncoding = this.DefaultWzCodePage;\n            config.AutoDetectExtFiles = this.AutoDetectExtFiles;\n            config.ImgCheckDisabled = this.ImgCheckDisabled;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/FrmOptions.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmPatcher.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmPatcher\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param Name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.comboBoxEx1 = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.integerInput1 = new DevComponents.Editors.IntegerInput();\n            this.txtUrl = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.buttonXPatch = new DevComponents.DotNetBar.ButtonX();\n            this.chkDeadPatch = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkPrePatch = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.buttonXOpen2 = new DevComponents.DotNetBar.ButtonX();\n            this.txtMSFolder = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.labelX2 = new DevComponents.DotNetBar.LabelX();\n            this.buttonXOpen1 = new DevComponents.DotNetBar.ButtonX();\n            this.txtPatchFile = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.expandablePanel1 = new DevComponents.DotNetBar.ExpandablePanel();\n            this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();\n            this.buttonXCheck = new DevComponents.DotNetBar.ButtonX();\n            this.expandablePanel2 = new DevComponents.DotNetBar.ExpandablePanel();\n            this.chkEnableDarkMode = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkOutputRemovedImg = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkOutputAddedImg = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.cmbComparePng = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.chkOutputPng = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkCompare = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.panelEx2 = new DevComponents.DotNetBar.PanelEx();\n            this.superTabControl1 = new DevComponents.DotNetBar.SuperTabControl();\n            this.superTabControlPanel1 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.txtNotice = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.superTabItem1 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel2 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.advTreePatchFiles = new DevComponents.AdvTree.AdvTree();\n            this.columnHeader1 = new DevComponents.AdvTree.ColumnHeader();\n            this.columnHeader2 = new DevComponents.AdvTree.ColumnHeader();\n            this.columnHeader3 = new DevComponents.AdvTree.ColumnHeader();\n            this.columnHeader4 = new DevComponents.AdvTree.ColumnHeader();\n            this.columnHeader5 = new DevComponents.AdvTree.ColumnHeader();\n            this.columnHeader6 = new DevComponents.AdvTree.ColumnHeader();\n            this.nodeConnector1 = new DevComponents.AdvTree.NodeConnector();\n            this.elementStyle1 = new DevComponents.DotNetBar.ElementStyle();\n            this.superTabItem2 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel3 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.txtPatchState = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.progressBarX1 = new DevComponents.DotNetBar.Controls.ProgressBarX();\n            this.superTabItem3 = new DevComponents.DotNetBar.SuperTabItem();\n            this.panelEx1 = new DevComponents.DotNetBar.PanelEx();\n            this.expandablePanel3 = new DevComponents.DotNetBar.ExpandablePanel();\n            this.buttonXCreate = new DevComponents.DotNetBar.ButtonX();\n            this.txtPatchFile2 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.buttonXOpen4 = new DevComponents.DotNetBar.ButtonX();\n            this.buttonXOpen3 = new DevComponents.DotNetBar.ButtonX();\n            this.txtMSFolder2 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.labelX4 = new DevComponents.DotNetBar.LabelX();\n            this.labelX5 = new DevComponents.DotNetBar.LabelX();\n            this.labelX3 = new DevComponents.DotNetBar.LabelX();\n            this.superTooltip1 = new DevComponents.DotNetBar.SuperTooltip();\n            this.chkResolvePngLink = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            ((System.ComponentModel.ISupportInitialize)(this.integerInput1)).BeginInit();\n            this.expandablePanel1.SuspendLayout();\n            this.flowLayoutPanel1.SuspendLayout();\n            this.expandablePanel2.SuspendLayout();\n            this.panelEx2.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).BeginInit();\n            this.superTabControl1.SuspendLayout();\n            this.superTabControlPanel1.SuspendLayout();\n            this.superTabControlPanel2.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.advTreePatchFiles)).BeginInit();\n            this.superTabControlPanel3.SuspendLayout();\n            this.panelEx1.SuspendLayout();\n            this.expandablePanel3.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // comboBoxEx1\n            // \n            this.comboBoxEx1.DisplayMember = \"Text\";\n            this.comboBoxEx1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.comboBoxEx1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.comboBoxEx1.FormattingEnabled = true;\n            this.comboBoxEx1.ItemHeight = 15;\n            this.comboBoxEx1.Location = new System.Drawing.Point(12, 35);\n            this.comboBoxEx1.Name = \"comboBoxEx1\";\n            this.comboBoxEx1.Size = new System.Drawing.Size(86, 21);\n            this.comboBoxEx1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.comboBoxEx1.TabIndex = 0;\n            this.comboBoxEx1.SelectedIndexChanged += new System.EventHandler(this.comboBoxEx1_SelectedIndexChanged);\n            // \n            // integerInput1\n            // \n            this.integerInput1.AllowEmptyState = false;\n            // \n            // \n            // \n            this.integerInput1.BackgroundStyle.Class = \"DateTimeInputBackground\";\n            this.integerInput1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.integerInput1.ButtonFreeText.Shortcut = DevComponents.DotNetBar.eShortcut.F2;\n            this.integerInput1.DisplayFormat = \"00000\";\n            this.integerInput1.Location = new System.Drawing.Point(3, 3);\n            this.integerInput1.MaxValue = 99999;\n            this.integerInput1.MinValue = 0;\n            this.integerInput1.Name = \"integerInput1\";\n            this.integerInput1.ShowUpDown = true;\n            this.integerInput1.Size = new System.Drawing.Size(60, 21);\n            this.integerInput1.TabIndex = 1;\n            this.integerInput1.ValueChanged += new System.EventHandler(this.integerInput_ValueChanged);\n            // \n            // txtUrl\n            // \n            this.txtUrl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtUrl.Border.Class = \"TextBoxBorder\";\n            this.txtUrl.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtUrl.Location = new System.Drawing.Point(12, 62);\n            this.txtUrl.Name = \"txtUrl\";\n            this.txtUrl.ReadOnly = true;\n            this.txtUrl.Size = new System.Drawing.Size(304, 21);\n            this.txtUrl.TabIndex = 3;\n            // \n            // buttonXPatch\n            // \n            this.buttonXPatch.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonXPatch.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonXPatch.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonXPatch.Location = new System.Drawing.Point(323, 82);\n            this.buttonXPatch.Name = \"buttonXPatch\";\n            this.buttonXPatch.Size = new System.Drawing.Size(40, 25);\n            this.buttonXPatch.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonXPatch.TabIndex = 8;\n            this.buttonXPatch.Text = \"Patch\";\n            this.buttonXPatch.Click += new System.EventHandler(this.buttonXPatch_Click);\n            // \n            // chkDeadPatch\n            // \n            this.chkDeadPatch.AutoSize = true;\n            this.chkDeadPatch.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkDeadPatch.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkDeadPatch.Location = new System.Drawing.Point(81, 87);\n            this.chkDeadPatch.Name = \"chkDeadPatch\";\n            this.chkDeadPatch.Size = new System.Drawing.Size(82, 16);\n            this.chkDeadPatch.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkDeadPatch, new DevComponents.DotNetBar.SuperTooltipInfo(\"DeadPatch\", \"\", \"开启此项后，每更新完一个子文件，将立即覆盖原文件并删除临时文件。这样做会减少临时文件空间的需要，但是伴随一定风险。\\r\\n\\r\\n在KMST1125格式的补丁上，需同时启\" +\n            \"用PrePatch才能生效。\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 180)));\n            this.chkDeadPatch.TabIndex = 7;\n            this.chkDeadPatch.Text = \"DeadPatch\";\n            // \n            // chkPrePatch\n            // \n            this.chkPrePatch.AutoSize = true;\n            this.chkPrePatch.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkPrePatch.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkPrePatch.Location = new System.Drawing.Point(6, 87);\n            this.chkPrePatch.Name = \"chkPrePatch\";\n            this.chkPrePatch.Size = new System.Drawing.Size(76, 16);\n            this.chkPrePatch.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkPrePatch, new DevComponents.DotNetBar.SuperTooltipInfo(\"PrePatch\", \"\", \"开启此项则会在补丁更新前进行预加载。预加载后可以自由选择要更新的子文件，或调整子文件更新顺序。调整完毕后再次单击\\\"Patch\\\"按钮开始更新。\", null, null, DevComponents.DotNetBar.eTooltipColor.Default, true, false, new System.Drawing.Size(180, 140)));\n            this.chkPrePatch.TabIndex = 6;\n            this.chkPrePatch.Text = \"PrePatch\";\n            // \n            // buttonXOpen2\n            // \n            this.buttonXOpen2.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonXOpen2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonXOpen2.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonXOpen2.Location = new System.Drawing.Point(330, 59);\n            this.buttonXOpen2.Name = \"buttonXOpen2\";\n            this.buttonXOpen2.Size = new System.Drawing.Size(33, 21);\n            this.buttonXOpen2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonXOpen2.TabIndex = 4;\n            this.buttonXOpen2.Text = \"Open\";\n            this.buttonXOpen2.Click += new System.EventHandler(this.buttonXOpen2_Click);\n            // \n            // txtMSFolder\n            // \n            this.txtMSFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtMSFolder.Border.Class = \"TextBoxBorder\";\n            this.txtMSFolder.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtMSFolder.Location = new System.Drawing.Point(68, 59);\n            this.txtMSFolder.Name = \"txtMSFolder\";\n            this.txtMSFolder.Size = new System.Drawing.Size(259, 21);\n            this.txtMSFolder.TabIndex = 3;\n            // \n            // labelX2\n            // \n            this.labelX2.AutoSize = true;\n            this.labelX2.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX2.Location = new System.Drawing.Point(7, 61);\n            this.labelX2.Name = \"labelX2\";\n            this.labelX2.Size = new System.Drawing.Size(62, 16);\n            this.labelX2.TabIndex = 5;\n            this.labelX2.Text = \"MS Folder\";\n            // \n            // buttonXOpen1\n            // \n            this.buttonXOpen1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonXOpen1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonXOpen1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonXOpen1.Location = new System.Drawing.Point(330, 34);\n            this.buttonXOpen1.Name = \"buttonXOpen1\";\n            this.buttonXOpen1.Size = new System.Drawing.Size(33, 21);\n            this.buttonXOpen1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonXOpen1.TabIndex = 1;\n            this.buttonXOpen1.Text = \"Open\";\n            this.buttonXOpen1.Click += new System.EventHandler(this.buttonXOpen1_Click);\n            // \n            // txtPatchFile\n            // \n            this.txtPatchFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtPatchFile.Border.Class = \"TextBoxBorder\";\n            this.txtPatchFile.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtPatchFile.Location = new System.Drawing.Point(68, 34);\n            this.txtPatchFile.Name = \"txtPatchFile\";\n            this.txtPatchFile.Size = new System.Drawing.Size(259, 21);\n            this.txtPatchFile.TabIndex = 0;\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            this.labelX1.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(7, 36);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(62, 16);\n            this.labelX1.TabIndex = 2;\n            this.labelX1.Text = \"PatchFile\";\n            // \n            // expandablePanel1\n            // \n            this.expandablePanel1.CanvasColor = System.Drawing.SystemColors.Control;\n            this.expandablePanel1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.expandablePanel1.Controls.Add(this.flowLayoutPanel1);\n            this.expandablePanel1.Controls.Add(this.buttonXCheck);\n            this.expandablePanel1.Controls.Add(this.comboBoxEx1);\n            this.expandablePanel1.Controls.Add(this.txtUrl);\n            this.expandablePanel1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.expandablePanel1.Dock = System.Windows.Forms.DockStyle.Top;\n            this.expandablePanel1.ExpandOnTitleClick = true;\n            this.expandablePanel1.Location = new System.Drawing.Point(0, 0);\n            this.expandablePanel1.Name = \"expandablePanel1\";\n            this.expandablePanel1.Size = new System.Drawing.Size(384, 87);\n            this.expandablePanel1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.expandablePanel1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandablePanel1.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.expandablePanel1.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.expandablePanel1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.expandablePanel1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandablePanel1.Style.GradientAngle = 90;\n            this.expandablePanel1.TabIndex = 6;\n            this.expandablePanel1.TitleStyle.Alignment = System.Drawing.StringAlignment.Center;\n            this.expandablePanel1.TitleStyle.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandablePanel1.TitleStyle.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.expandablePanel1.TitleStyle.Border = DevComponents.DotNetBar.eBorderType.RaisedInner;\n            this.expandablePanel1.TitleStyle.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandablePanel1.TitleStyle.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.expandablePanel1.TitleStyle.GradientAngle = 90;\n            this.expandablePanel1.TitleText = \"Patch Download Url\";\n            // \n            // flowLayoutPanel1\n            // \n            this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.flowLayoutPanel1.BackColor = System.Drawing.Color.Transparent;\n            this.flowLayoutPanel1.Controls.Add(this.integerInput1);\n            this.flowLayoutPanel1.Location = new System.Drawing.Point(104, 33);\n            this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0);\n            this.flowLayoutPanel1.Name = \"flowLayoutPanel1\";\n            this.flowLayoutPanel1.Size = new System.Drawing.Size(270, 26);\n            this.flowLayoutPanel1.TabIndex = 5;\n            // \n            // buttonXCheck\n            // \n            this.buttonXCheck.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonXCheck.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonXCheck.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonXCheck.Location = new System.Drawing.Point(322, 61);\n            this.buttonXCheck.Name = \"buttonXCheck\";\n            this.buttonXCheck.Size = new System.Drawing.Size(41, 23);\n            this.buttonXCheck.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonXCheck.TabIndex = 4;\n            this.buttonXCheck.Text = \"Check\";\n            this.buttonXCheck.Click += new System.EventHandler(this.buttonXCheck_Click);\n            // \n            // expandablePanel2\n            // \n            this.expandablePanel2.CanvasColor = System.Drawing.SystemColors.Control;\n            this.expandablePanel2.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.expandablePanel2.Controls.Add(this.chkResolvePngLink);\n            this.expandablePanel2.Controls.Add(this.chkEnableDarkMode);\n            this.expandablePanel2.Controls.Add(this.chkOutputRemovedImg);\n            this.expandablePanel2.Controls.Add(this.chkOutputAddedImg);\n            this.expandablePanel2.Controls.Add(this.cmbComparePng);\n            this.expandablePanel2.Controls.Add(this.chkOutputPng);\n            this.expandablePanel2.Controls.Add(this.chkCompare);\n            this.expandablePanel2.Controls.Add(this.panelEx2);\n            this.expandablePanel2.Controls.Add(this.buttonXPatch);\n            this.expandablePanel2.Controls.Add(this.chkDeadPatch);\n            this.expandablePanel2.Controls.Add(this.chkPrePatch);\n            this.expandablePanel2.Controls.Add(this.txtPatchFile);\n            this.expandablePanel2.Controls.Add(this.buttonXOpen2);\n            this.expandablePanel2.Controls.Add(this.buttonXOpen1);\n            this.expandablePanel2.Controls.Add(this.txtMSFolder);\n            this.expandablePanel2.Controls.Add(this.labelX2);\n            this.expandablePanel2.Controls.Add(this.labelX1);\n            this.expandablePanel2.DisabledBackColor = System.Drawing.Color.Empty;\n            this.expandablePanel2.Dock = System.Windows.Forms.DockStyle.Top;\n            this.expandablePanel2.ExpandOnTitleClick = true;\n            this.expandablePanel2.Location = new System.Drawing.Point(0, 87);\n            this.expandablePanel2.Name = \"expandablePanel2\";\n            this.expandablePanel2.Size = new System.Drawing.Size(384, 157);\n            this.expandablePanel2.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.expandablePanel2.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandablePanel2.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.expandablePanel2.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.expandablePanel2.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.expandablePanel2.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandablePanel2.Style.GradientAngle = 90;\n            this.expandablePanel2.TabIndex = 7;\n            this.expandablePanel2.TitleStyle.Alignment = System.Drawing.StringAlignment.Center;\n            this.expandablePanel2.TitleStyle.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandablePanel2.TitleStyle.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.expandablePanel2.TitleStyle.Border = DevComponents.DotNetBar.eBorderType.RaisedInner;\n            this.expandablePanel2.TitleStyle.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandablePanel2.TitleStyle.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.expandablePanel2.TitleStyle.GradientAngle = 90;\n            this.expandablePanel2.TitleText = \"Manual Patcher\";\n            // \n            // chkEnableDarkMode\n            // \n            this.chkEnableDarkMode.AutoSize = true;\n            this.chkEnableDarkMode.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkEnableDarkMode.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkEnableDarkMode.Location = new System.Drawing.Point(253, 135);\n            this.chkEnableDarkMode.Name = \"chkEnableDarkMode\";\n            this.chkEnableDarkMode.Size = new System.Drawing.Size(125, 16);\n            this.chkEnableDarkMode.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkEnableDarkMode, new DevComponents.DotNetBar.SuperTooltipInfo(\"EnableDarkMode\", \"\", \"将比对结果以暗黑模式 HTML 输出\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkEnableDarkMode.TabIndex = 14;\n            this.chkEnableDarkMode.Text = \"EnableDarkMode\";\n            // \n            // chkOutputRemovedImg\n            // \n            this.chkOutputRemovedImg.AutoSize = true;\n            this.chkOutputRemovedImg.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkOutputRemovedImg.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkOutputRemovedImg.Location = new System.Drawing.Point(125, 135);\n            this.chkOutputRemovedImg.Name = \"chkOutputRemovedImg\";\n            this.chkOutputRemovedImg.Size = new System.Drawing.Size(125, 16);\n            this.chkOutputRemovedImg.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkOutputRemovedImg, new DevComponents.DotNetBar.SuperTooltipInfo(\"OutputRemovedImg\", \"\", \"对比报告中是否输出被整体移除的Image的完整结构\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkOutputRemovedImg.TabIndex = 14;\n            this.chkOutputRemovedImg.Text = \"OutputRemovedImg\";\n            // \n            // chkOutputAddedImg\n            // \n            this.chkOutputAddedImg.AutoSize = true;\n            this.chkOutputAddedImg.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkOutputAddedImg.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkOutputAddedImg.Location = new System.Drawing.Point(6, 135);\n            this.chkOutputAddedImg.Name = \"chkOutputAddedImg\";\n            this.chkOutputAddedImg.Size = new System.Drawing.Size(113, 16);\n            this.chkOutputAddedImg.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkOutputAddedImg, new DevComponents.DotNetBar.SuperTooltipInfo(\"OutputAddedImg\", \"\", \"对比报告中是否输出新增Image的完整结构\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkOutputAddedImg.TabIndex = 13;\n            this.chkOutputAddedImg.Text = \"OutputAddedImg\";\n            // \n            // cmbComparePng\n            // \n            this.cmbComparePng.DisplayMember = \"Text\";\n            this.cmbComparePng.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbComparePng.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbComparePng.FormattingEnabled = true;\n            this.cmbComparePng.ItemHeight = 15;\n            this.cmbComparePng.Location = new System.Drawing.Point(79, 108);\n            this.cmbComparePng.Name = \"cmbComparePng\";\n            this.cmbComparePng.Size = new System.Drawing.Size(120, 21);\n            this.cmbComparePng.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.cmbComparePng, new DevComponents.DotNetBar.SuperTooltipInfo(\"PngComparison\", \"\", \"对于对比报告中图片的对比方式。\\r\\nSizeOnly - 仅对比图片大小，可能会遗漏。\\r\\nSizeAndDataLength - 同时对比图片大小和压缩流长度，可能\" +\n            \"会误判。\\r\\nPixel - 像素级对比，非常精确但可能略耗时。\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, true, new System.Drawing.Size(300, 130)));\n            this.cmbComparePng.TabIndex = 12;\n            // \n            // chkOutputPng\n            // \n            this.chkOutputPng.AutoSize = true;\n            this.chkOutputPng.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkOutputPng.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkOutputPng.Checked = true;\n            this.chkOutputPng.CheckState = System.Windows.Forms.CheckState.Checked;\n            this.chkOutputPng.CheckValue = \"Y\";\n            this.chkOutputPng.Location = new System.Drawing.Point(208, 111);\n            this.chkOutputPng.Name = \"chkOutputPng\";\n            this.chkOutputPng.Size = new System.Drawing.Size(82, 16);\n            this.chkOutputPng.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkOutputPng, new DevComponents.DotNetBar.SuperTooltipInfo(\"OutputPng\", \"\", \"对比报告中是否输出有差异的图片文件。\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkOutputPng.TabIndex = 11;\n            this.chkOutputPng.Text = \"OutputPng\";\n            // \n            // chkCompare\n            // \n            this.chkCompare.AutoSize = true;\n            this.chkCompare.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkCompare.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkCompare.Location = new System.Drawing.Point(6, 111);\n            this.chkCompare.Name = \"chkCompare\";\n            this.chkCompare.Size = new System.Drawing.Size(70, 16);\n            this.chkCompare.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkCompare, new DevComponents.DotNetBar.SuperTooltipInfo(\"Compare\", \"\", \"开启此项后，每更新完一个Wz文件，将会进行新旧文件对比并输出更新报告。有一些额外的选项可以控制对比更新的执行方式。\\r\\n对比过程出错不会影响补丁继续执行。\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(238, 130)));\n            this.chkCompare.TabIndex = 10;\n            this.chkCompare.Text = \"Compare\";\n            // \n            // panelEx2\n            // \n            this.panelEx2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.panelEx2.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx2.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx2.Controls.Add(this.superTabControl1);\n            this.panelEx2.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx2.Location = new System.Drawing.Point(3, 157);\n            this.panelEx2.Name = \"panelEx2\";\n            this.panelEx2.Size = new System.Drawing.Size(360, 180);\n            this.panelEx2.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx2.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx2.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx2.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx2.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx2.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx2.Style.GradientAngle = 90;\n            this.panelEx2.TabIndex = 9;\n            this.panelEx2.Text = \"panelEx2\";\n            this.panelEx2.Visible = false;\n            // \n            // superTabControl1\n            // \n            // \n            // \n            // \n            // \n            // \n            // \n            this.superTabControl1.ControlBox.CloseBox.Name = \"\";\n            // \n            // \n            // \n            this.superTabControl1.ControlBox.MenuBox.Name = \"\";\n            this.superTabControl1.ControlBox.Name = \"\";\n            this.superTabControl1.ControlBox.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabControl1.ControlBox.MenuBox,\n            this.superTabControl1.ControlBox.CloseBox});\n            this.superTabControl1.Controls.Add(this.superTabControlPanel1);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel2);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel3);\n            this.superTabControl1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControl1.Location = new System.Drawing.Point(0, 0);\n            this.superTabControl1.Name = \"superTabControl1\";\n            this.superTabControl1.ReorderTabsEnabled = true;\n            this.superTabControl1.SelectedTabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Bold);\n            this.superTabControl1.SelectedTabIndex = 1;\n            this.superTabControl1.Size = new System.Drawing.Size(360, 180);\n            this.superTabControl1.TabAlignment = DevComponents.DotNetBar.eTabStripAlignment.Left;\n            this.superTabControl1.TabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.superTabControl1.TabIndex = 2;\n            this.superTabControl1.Tabs.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabItem1,\n            this.superTabItem2,\n            this.superTabItem3});\n            this.superTabControl1.Text = \"superTabControl1\";\n            // \n            // superTabControlPanel1\n            // \n            this.superTabControlPanel1.Controls.Add(this.txtNotice);\n            this.superTabControlPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel1.Location = new System.Drawing.Point(63, 0);\n            this.superTabControlPanel1.Name = \"superTabControlPanel1\";\n            this.superTabControlPanel1.Size = new System.Drawing.Size(297, 180);\n            this.superTabControlPanel1.TabIndex = 1;\n            this.superTabControlPanel1.TabItem = this.superTabItem1;\n            // \n            // txtNotice\n            // \n            // \n            // \n            // \n            this.txtNotice.Border.Class = \"TextBoxBorder\";\n            this.txtNotice.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtNotice.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.txtNotice.Location = new System.Drawing.Point(0, 0);\n            this.txtNotice.Multiline = true;\n            this.txtNotice.Name = \"txtNotice\";\n            this.txtNotice.ReadOnly = true;\n            this.txtNotice.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;\n            this.txtNotice.Size = new System.Drawing.Size(297, 180);\n            this.txtNotice.TabIndex = 0;\n            // \n            // superTabItem1\n            // \n            this.superTabItem1.AttachedControl = this.superTabControlPanel1;\n            this.superTabItem1.GlobalItem = false;\n            this.superTabItem1.Name = \"superTabItem1\";\n            this.superTabItem1.Text = \"Notice\";\n            // \n            // superTabControlPanel2\n            // \n            this.superTabControlPanel2.Controls.Add(this.advTreePatchFiles);\n            this.superTabControlPanel2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel2.Location = new System.Drawing.Point(63, 0);\n            this.superTabControlPanel2.Name = \"superTabControlPanel2\";\n            this.superTabControlPanel2.Size = new System.Drawing.Size(257, 180);\n            this.superTabControlPanel2.TabIndex = 0;\n            this.superTabControlPanel2.TabItem = this.superTabItem2;\n            // \n            // advTreePatchFiles\n            // \n            this.advTreePatchFiles.AccessibleRole = System.Windows.Forms.AccessibleRole.Outline;\n            this.advTreePatchFiles.AllowDrop = true;\n            this.advTreePatchFiles.BackColor = System.Drawing.SystemColors.Window;\n            // \n            // \n            // \n            this.advTreePatchFiles.BackgroundStyle.Class = \"TreeBorderKey\";\n            this.advTreePatchFiles.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.advTreePatchFiles.Columns.Add(this.columnHeader1);\n            this.advTreePatchFiles.Columns.Add(this.columnHeader2);\n            this.advTreePatchFiles.Columns.Add(this.columnHeader3);\n            this.advTreePatchFiles.Columns.Add(this.columnHeader4);\n            this.advTreePatchFiles.Columns.Add(this.columnHeader5);\n            this.advTreePatchFiles.Columns.Add(this.columnHeader6);\n            this.advTreePatchFiles.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.advTreePatchFiles.DoubleClickTogglesNode = false;\n            this.advTreePatchFiles.DragDropNodeCopyEnabled = false;\n            this.advTreePatchFiles.DropAsChildOffset = 65535;\n            this.advTreePatchFiles.Enabled = false;\n            this.advTreePatchFiles.ExpandWidth = 0;\n            this.advTreePatchFiles.GridRowLines = true;\n            this.advTreePatchFiles.Location = new System.Drawing.Point(0, 0);\n            this.advTreePatchFiles.MultiSelect = true;\n            this.advTreePatchFiles.MultiSelectRule = DevComponents.AdvTree.eMultiSelectRule.AnyNode;\n            this.advTreePatchFiles.Name = \"advTreePatchFiles\";\n            this.advTreePatchFiles.NodesConnector = this.nodeConnector1;\n            this.advTreePatchFiles.NodeStyle = this.elementStyle1;\n            this.advTreePatchFiles.PathSeparator = \";\";\n            this.advTreePatchFiles.Size = new System.Drawing.Size(257, 180);\n            this.advTreePatchFiles.Styles.Add(this.elementStyle1);\n            this.advTreePatchFiles.TabIndex = 0;\n            this.advTreePatchFiles.Text = \"advTree1\";\n            // \n            // columnHeader1\n            // \n            this.columnHeader1.Name = \"columnHeader1\";\n            this.columnHeader1.Text = \"FileName\";\n            this.columnHeader1.Width.Absolute = 100;\n            // \n            // columnHeader2\n            // \n            this.columnHeader2.Editable = false;\n            this.columnHeader2.Name = \"columnHeader2\";\n            this.columnHeader2.Text = \"PatchType\";\n            this.columnHeader2.Width.Absolute = 70;\n            // \n            // columnHeader3\n            // \n            this.columnHeader3.Editable = false;\n            this.columnHeader3.Name = \"columnHeader3\";\n            this.columnHeader3.Text = \"FileLength\";\n            this.columnHeader3.Width.Absolute = 70;\n            // \n            // columnHeader4\n            // \n            this.columnHeader4.Editable = false;\n            this.columnHeader4.Name = \"columnHeader4\";\n            this.columnHeader4.Text = \"Checksum\";\n            this.columnHeader4.Width.Absolute = 70;\n            // \n            // columnHeader5\n            // \n            this.columnHeader5.Name = \"columnHeader5\";\n            this.columnHeader5.Text = \"OldChecksum\";\n            this.columnHeader5.Width.Absolute = 70;\n            // \n            // columnHeader6\n            // \n            this.columnHeader6.Name = \"columnHeader6\";\n            this.columnHeader6.Text = \"Action\";\n            this.columnHeader6.Width.Absolute = 150;\n            // \n            // nodeConnector1\n            // \n            this.nodeConnector1.LineColor = System.Drawing.SystemColors.ControlText;\n            // \n            // elementStyle1\n            // \n            this.elementStyle1.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.elementStyle1.Name = \"elementStyle1\";\n            this.elementStyle1.TextColor = System.Drawing.SystemColors.ControlText;\n            // \n            // superTabItem2\n            // \n            this.superTabItem2.AttachedControl = this.superTabControlPanel2;\n            this.superTabItem2.GlobalItem = false;\n            this.superTabItem2.Name = \"superTabItem2\";\n            this.superTabItem2.Text = \"Files\";\n            // \n            // superTabControlPanel3\n            // \n            this.superTabControlPanel3.Controls.Add(this.txtPatchState);\n            this.superTabControlPanel3.Controls.Add(this.progressBarX1);\n            this.superTabControlPanel3.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel3.Location = new System.Drawing.Point(63, 0);\n            this.superTabControlPanel3.Name = \"superTabControlPanel3\";\n            this.superTabControlPanel3.Size = new System.Drawing.Size(257, 180);\n            this.superTabControlPanel3.TabIndex = 0;\n            this.superTabControlPanel3.TabItem = this.superTabItem3;\n            // \n            // txtPatchState\n            // \n            // \n            // \n            // \n            this.txtPatchState.Border.Class = \"TextBoxBorder\";\n            this.txtPatchState.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtPatchState.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.txtPatchState.Location = new System.Drawing.Point(0, 0);\n            this.txtPatchState.Multiline = true;\n            this.txtPatchState.Name = \"txtPatchState\";\n            this.txtPatchState.ReadOnly = true;\n            this.txtPatchState.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;\n            this.txtPatchState.Size = new System.Drawing.Size(257, 162);\n            this.txtPatchState.TabIndex = 1;\n            // \n            // progressBarX1\n            // \n            // \n            // \n            // \n            this.progressBarX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.progressBarX1.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.progressBarX1.Location = new System.Drawing.Point(0, 162);\n            this.progressBarX1.Name = \"progressBarX1\";\n            this.progressBarX1.Size = new System.Drawing.Size(257, 18);\n            this.progressBarX1.TabIndex = 2;\n            this.progressBarX1.TextVisible = true;\n            // \n            // superTabItem3\n            // \n            this.superTabItem3.AttachedControl = this.superTabControlPanel3;\n            this.superTabItem3.GlobalItem = false;\n            this.superTabItem3.Name = \"superTabItem3\";\n            this.superTabItem3.Text = \"State\";\n            // \n            // panelEx1\n            // \n            this.panelEx1.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx1.Controls.Add(this.expandablePanel3);\n            this.panelEx1.Controls.Add(this.expandablePanel2);\n            this.panelEx1.Controls.Add(this.expandablePanel1);\n            this.panelEx1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.panelEx1.Location = new System.Drawing.Point(0, 0);\n            this.panelEx1.Name = \"panelEx1\";\n            this.panelEx1.Size = new System.Drawing.Size(384, 361);\n            this.panelEx1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx1.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx1.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx1.Style.GradientAngle = 90;\n            this.panelEx1.TabIndex = 8;\n            // \n            // expandablePanel3\n            // \n            this.expandablePanel3.CanvasColor = System.Drawing.SystemColors.Control;\n            this.expandablePanel3.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.expandablePanel3.Controls.Add(this.buttonXCreate);\n            this.expandablePanel3.Controls.Add(this.txtPatchFile2);\n            this.expandablePanel3.Controls.Add(this.buttonXOpen4);\n            this.expandablePanel3.Controls.Add(this.buttonXOpen3);\n            this.expandablePanel3.Controls.Add(this.txtMSFolder2);\n            this.expandablePanel3.Controls.Add(this.labelX4);\n            this.expandablePanel3.Controls.Add(this.labelX5);\n            this.expandablePanel3.Controls.Add(this.labelX3);\n            this.expandablePanel3.DisabledBackColor = System.Drawing.Color.Empty;\n            this.expandablePanel3.Dock = System.Windows.Forms.DockStyle.Top;\n            this.expandablePanel3.ExpandOnTitleClick = true;\n            this.expandablePanel3.Location = new System.Drawing.Point(0, 244);\n            this.expandablePanel3.Name = \"expandablePanel3\";\n            this.expandablePanel3.Size = new System.Drawing.Size(384, 110);\n            this.expandablePanel3.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.expandablePanel3.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandablePanel3.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.expandablePanel3.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.expandablePanel3.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.expandablePanel3.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandablePanel3.Style.GradientAngle = 90;\n            this.expandablePanel3.TabIndex = 8;\n            this.expandablePanel3.TitleStyle.Alignment = System.Drawing.StringAlignment.Center;\n            this.expandablePanel3.TitleStyle.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandablePanel3.TitleStyle.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.expandablePanel3.TitleStyle.Border = DevComponents.DotNetBar.eBorderType.RaisedInner;\n            this.expandablePanel3.TitleStyle.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandablePanel3.TitleStyle.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.expandablePanel3.TitleStyle.GradientAngle = 90;\n            this.expandablePanel3.TitleText = \"Reverse Patcher\";\n            // \n            // buttonXCreate\n            // \n            this.buttonXCreate.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonXCreate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonXCreate.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonXCreate.Location = new System.Drawing.Point(315, 82);\n            this.buttonXCreate.Name = \"buttonXCreate\";\n            this.buttonXCreate.Size = new System.Drawing.Size(48, 21);\n            this.buttonXCreate.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonXCreate.TabIndex = 12;\n            this.buttonXCreate.Text = \"Create\";\n            this.buttonXCreate.Click += new System.EventHandler(this.buttonXCreate_Click);\n            // \n            // txtPatchFile2\n            // \n            this.txtPatchFile2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtPatchFile2.Border.Class = \"TextBoxBorder\";\n            this.txtPatchFile2.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtPatchFile2.Location = new System.Drawing.Point(68, 34);\n            this.txtPatchFile2.Name = \"txtPatchFile2\";\n            this.txtPatchFile2.Size = new System.Drawing.Size(259, 21);\n            this.txtPatchFile2.TabIndex = 6;\n            // \n            // buttonXOpen4\n            // \n            this.buttonXOpen4.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonXOpen4.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonXOpen4.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonXOpen4.Location = new System.Drawing.Point(330, 59);\n            this.buttonXOpen4.Name = \"buttonXOpen4\";\n            this.buttonXOpen4.Size = new System.Drawing.Size(33, 21);\n            this.buttonXOpen4.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonXOpen4.TabIndex = 10;\n            this.buttonXOpen4.Text = \"Open\";\n            this.buttonXOpen4.Click += new System.EventHandler(this.buttonXOpen4_Click);\n            // \n            // buttonXOpen3\n            // \n            this.buttonXOpen3.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonXOpen3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonXOpen3.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonXOpen3.Location = new System.Drawing.Point(330, 34);\n            this.buttonXOpen3.Name = \"buttonXOpen3\";\n            this.buttonXOpen3.Size = new System.Drawing.Size(33, 21);\n            this.buttonXOpen3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonXOpen3.TabIndex = 7;\n            this.buttonXOpen3.Text = \"Open\";\n            this.buttonXOpen3.Click += new System.EventHandler(this.buttonXOpen3_Click);\n            // \n            // txtMSFolder2\n            // \n            this.txtMSFolder2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.txtMSFolder2.Border.Class = \"TextBoxBorder\";\n            this.txtMSFolder2.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtMSFolder2.Location = new System.Drawing.Point(68, 59);\n            this.txtMSFolder2.Name = \"txtMSFolder2\";\n            this.txtMSFolder2.Size = new System.Drawing.Size(259, 21);\n            this.txtMSFolder2.TabIndex = 9;\n            // \n            // labelX4\n            // \n            this.labelX4.AutoSize = true;\n            this.labelX4.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX4.Location = new System.Drawing.Point(7, 61);\n            this.labelX4.Name = \"labelX4\";\n            this.labelX4.Size = new System.Drawing.Size(62, 16);\n            this.labelX4.TabIndex = 11;\n            this.labelX4.Text = \"MS Folder\";\n            // \n            // labelX5\n            // \n            this.labelX5.AutoSize = true;\n            this.labelX5.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX5.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX5.Location = new System.Drawing.Point(7, 36);\n            this.labelX5.Name = \"labelX5\";\n            this.labelX5.Size = new System.Drawing.Size(62, 16);\n            this.labelX5.TabIndex = 8;\n            this.labelX5.Text = \"PatchFile\";\n            // \n            // labelX3\n            // \n            this.labelX3.AutoSize = true;\n            this.labelX3.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX3.Location = new System.Drawing.Point(12, 86);\n            this.labelX3.Name = \"labelX3\";\n            this.labelX3.Size = new System.Drawing.Size(285, 18);\n            this.labelX3.TabIndex = 1;\n            this.labelX3.Text = \"这功能太危险了在考虑要不要放出来...还是算了吧\";\n            // \n            // superTooltip1\n            // \n            this.superTooltip1.DefaultTooltipSettings = new DevComponents.DotNetBar.SuperTooltipInfo(\"\", \"\", \"\", null, null, DevComponents.DotNetBar.eTooltipColor.Gray);\n            // \n            // chkResolvePngLink\n            // \n            this.chkResolvePngLink.AutoSize = true;\n            this.chkResolvePngLink.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkResolvePngLink.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkResolvePngLink.Location = new System.Drawing.Point(290, 111);\n            this.chkResolvePngLink.Name = \"chkResolvePngLink\";\n            this.chkResolvePngLink.Size = new System.Drawing.Size(95, 16);\n            this.chkResolvePngLink.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkResolvePngLink, new DevComponents.DotNetBar.SuperTooltipInfo(\"ResolvePngLink\", \"\", \"对比报告中是否智能解析对比被Link的图片\\r\\n这会过滤掉无用的变更内容\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 90)));\n            this.chkResolvePngLink.TabIndex = 18;\n            this.chkResolvePngLink.Text = \"ResolveLink\";\n            // \n            // FrmPatcher\n            // \n            this.ClientSize = new System.Drawing.Size(384, 361);\n            this.Controls.Add(this.panelEx1);\n            this.DoubleBuffered = true;\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"FrmPatcher\";\n            this.Text = \"更新装置\";\n            this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FrmPatcher_FormClosed);\n            ((System.ComponentModel.ISupportInitialize)(this.integerInput1)).EndInit();\n            this.expandablePanel1.ResumeLayout(false);\n            this.flowLayoutPanel1.ResumeLayout(false);\n            this.expandablePanel2.ResumeLayout(false);\n            this.expandablePanel2.PerformLayout();\n            this.panelEx2.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).EndInit();\n            this.superTabControl1.ResumeLayout(false);\n            this.superTabControlPanel1.ResumeLayout(false);\n            this.superTabControlPanel2.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.advTreePatchFiles)).EndInit();\n            this.superTabControlPanel3.ResumeLayout(false);\n            this.panelEx1.ResumeLayout(false);\n            this.expandablePanel3.ResumeLayout(false);\n            this.expandablePanel3.PerformLayout();\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.Controls.ComboBoxEx comboBoxEx1;\n        private DevComponents.Editors.IntegerInput integerInput1;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtUrl;\n        private DevComponents.DotNetBar.ButtonX buttonXOpen2;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtMSFolder;\n        private DevComponents.DotNetBar.LabelX labelX2;\n        private DevComponents.DotNetBar.ButtonX buttonXOpen1;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtPatchFile;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.ButtonX buttonXPatch;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkDeadPatch;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkPrePatch;\n        private DevComponents.DotNetBar.ExpandablePanel expandablePanel1;\n        private DevComponents.DotNetBar.ExpandablePanel expandablePanel2;\n        private DevComponents.DotNetBar.PanelEx panelEx1;\n        private DevComponents.DotNetBar.PanelEx panelEx2;\n        private DevComponents.DotNetBar.SuperTabControl superTabControl1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel2;\n        private DevComponents.AdvTree.AdvTree advTreePatchFiles;\n        private DevComponents.AdvTree.ColumnHeader columnHeader1;\n        private DevComponents.AdvTree.NodeConnector nodeConnector1;\n        private DevComponents.DotNetBar.ElementStyle elementStyle1;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem2;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel3;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem3;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel1;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtNotice;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem1;\n        private DevComponents.AdvTree.ColumnHeader columnHeader2;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtPatchState;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkCompare;\n        private DevComponents.AdvTree.ColumnHeader columnHeader3;\n        private DevComponents.AdvTree.ColumnHeader columnHeader4;\n        private DevComponents.DotNetBar.Controls.ProgressBarX progressBarX1;\n        private DevComponents.AdvTree.ColumnHeader columnHeader5;\n        private DevComponents.DotNetBar.ButtonX buttonXCheck;\n        private DevComponents.AdvTree.ColumnHeader columnHeader6;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbComparePng;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkOutputPng;\n        private DevComponents.DotNetBar.SuperTooltip superTooltip1;\n        private DevComponents.DotNetBar.ExpandablePanel expandablePanel3;\n        private DevComponents.DotNetBar.LabelX labelX3;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtPatchFile2;\n        private DevComponents.DotNetBar.ButtonX buttonXOpen4;\n        private DevComponents.DotNetBar.ButtonX buttonXOpen3;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtMSFolder2;\n        private DevComponents.DotNetBar.LabelX labelX4;\n        private DevComponents.DotNetBar.LabelX labelX5;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkOutputRemovedImg;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkOutputAddedImg;\n        private DevComponents.DotNetBar.ButtonX buttonXCreate;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkResolvePngLink;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkEnableDarkMode;\n        private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmPatcher.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Data;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing DevComponents.AdvTree;\nusing DevComponents.DotNetBar;\nusing DevComponents.Editors;\nusing WzComparerR2.Comparer;\nusing WzComparerR2.Config;\nusing WzComparerR2.Patcher;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2\n{\n    public partial class FrmPatcher : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmPatcher()\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n            panelEx1.AutoScroll = true;\n\n            var settings = WcR2Config.Default.PatcherSettings;\n            if (settings.Count <= 0)\n            {\n                settings.Add(new PatcherSetting(\"KMST\", \"http://maplestory.dn.nexoncdn.co.kr/PatchT/{1:d5}/{0:d5}to{1:d5}.patch\", 2));\n                settings.Add(new PatcherSetting(\"KMST-Minor\", \"http://maplestory.dn.nexoncdn.co.kr/PatchT/{0:d5}/Minor/{1:d2}to{2:d2}.patch\", 3));\n                settings.Add(new PatcherSetting(\"KMS\", \"http://maplestory.dn.nexoncdn.co.kr/Patch/{1:d5}/{0:d5}to{1:d5}.patch\", 2));\n                settings.Add(new PatcherSetting(\"KMS-Minor\", \"http://maplestory.dn.nexoncdn.co.kr/Patch/{0:d5}/Minor/{1:d2}to{2:d2}.patch\", 3));\n                settings.Add(new PatcherSetting(\"JMS\", \"http://webdown2.nexon.co.jp/maple/patch/patchdir/{1:d5}/{0:d5}to{1:d5}.patch\", 2));\n                settings.Add(new PatcherSetting(\"GMS\", \"http://download2.nexon.net/Game/MapleStory/patch/patchdir/{1:d5}/CustomPatch{0}to{1}.exe\", 2));\n                settings.Add(new PatcherSetting(\"TMS\", \"http://tw.cdnpatch.maplestory.beanfun.com/maplestory/patch/patchdir/{1:d5}/{0:d5}to{1:d5}.patch\", 2));\n                settings.Add(new PatcherSetting(\"MSEA\", \"http://patch.maplesea.com/sea/patch/patchdir/{1:d5}/{0:d5}to{1:d5}.patch\", 2));\n                settings.Add(new PatcherSetting(\"CMS\", \"http://mxd.clientdown.sdo.com/maplestory/patch/patchdir/{1:d5}/{0:d5}to{1:d5}.patch\", 2));\n            }\n\n            foreach (PatcherSetting p in settings)\n            {\n                this.MigrateSetting(p);\n                comboBoxEx1.Items.Add(p);\n            }\n            if (comboBoxEx1.Items.Count > 0)\n                comboBoxEx1.SelectedIndex = 0;\n\n            foreach (WzPngComparison comp in Enum.GetValues(typeof(WzPngComparison)))\n            {\n                cmbComparePng.Items.Add(comp);\n            }\n            cmbComparePng.SelectedItem = WzPngComparison.SizeAndDataLength;\n        }\n\n        public Encoding PatcherNoticeEncoding { get; set; }\n\n        private bool isUpdating;\n        private PatcherSession patcherSession;\n\n        private PatcherSetting SelectedPatcherSetting => comboBoxEx1.SelectedItem as PatcherSetting;\n\n        private void MigrateSetting(PatcherSetting patcherSetting)\n        {\n            if (patcherSetting.MaxVersion == 0 && patcherSetting.Versions == null)\n            {\n                patcherSetting.MaxVersion = 2;\n                patcherSetting.Versions = new[] { patcherSetting.Version0 ?? 0, patcherSetting.Version1 ?? 0 };\n                patcherSetting.Version0 = null;\n                patcherSetting.Version1 = null;\n            }\n            if (patcherSetting.Versions != null && patcherSetting.Versions.Length < patcherSetting.MaxVersion)\n            {\n                var newVersions = new int[patcherSetting.MaxVersion];\n                Array.Copy(patcherSetting.Versions, newVersions, patcherSetting.Versions.Length);\n                patcherSetting.Versions = newVersions;\n            }\n        }\n\n        private void ApplySetting(PatcherSetting p)\n        {\n            if (isUpdating)\n            {\n                return;\n            }\n            isUpdating = true;\n            try\n            {\n                if (this.flowLayoutPanel1.Controls.Count < p.MaxVersion)\n                {\n                    var inputTemplate = this.integerInput1;\n                    var preAddedControls = Enumerable.Range(0, p.MaxVersion - this.flowLayoutPanel1.Controls.Count)\n                        .Select(_ =>\n                        {\n                            var input = new IntegerInput()\n                            {\n                                AllowEmptyState = inputTemplate.AllowEmptyState,\n                                Size = inputTemplate.Size,\n                                Value = 0,\n                                MinValue = inputTemplate.MinValue,\n                                MaxValue = inputTemplate.MaxValue,\n                                DisplayFormat = inputTemplate.DisplayFormat,\n                                ShowUpDown = inputTemplate.ShowUpDown,\n                            };\n                            input.BackgroundStyle.ApplyStyle(inputTemplate.BackgroundStyle);\n                            input.ValueChanged += this.integerInput_ValueChanged;\n                            return input;\n                        }).ToArray();\n                    this.flowLayoutPanel1.Controls.AddRange(preAddedControls);\n                }\n                for (int i = 0; i < this.flowLayoutPanel1.Controls.Count; i++)\n                {\n                    var input = (IntegerInput)this.flowLayoutPanel1.Controls[i];\n                    if (i < p.MaxVersion)\n                    {\n                        input.Show();\n                        input.Value = (p.Versions != null && i < p.Versions.Length) ? p.Versions[i] : 0;\n                    }\n                    else\n                    {\n                        input.Hide();\n                        input.Value = 0;\n                    }\n                }\n                this.txtUrl.Text = p.Url;\n            }\n            finally\n            {\n                isUpdating = false;\n            }\n        }\n\n        private void combineUrl()\n        {\n            if (this.SelectedPatcherSetting is var p)\n            {\n                txtUrl.Text = p.Url;\n            }\n        }\n\n        private void comboBoxEx1_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            if (this.SelectedPatcherSetting is var p)\n            {\n                this.ApplySetting(p);\n            }\n        }\n\n        private void integerInput_ValueChanged(object sender, EventArgs e)\n        {\n            if (this.SelectedPatcherSetting is var p && sender is IntegerInput input)\n            {\n                var i = this.flowLayoutPanel1.Controls.IndexOf(input);\n                if (i > -1 && i < p.MaxVersion)\n                {\n                    if (p.Versions == null)\n                    {\n                        p.Versions = new int[p.MaxVersion];\n                    }\n                    p.Versions[i] = input.Value;\n                }\n                this.ApplySetting(p);\n            }\n        }\n\n        private void buttonXCheck_Click(object sender, EventArgs e)\n        {\n            DownloadingItem item = new DownloadingItem(txtUrl.Text, null);\n            try\n            {\n                item.GetFileLength();\n                if (item.FileLength > 0)\n                {\n                    switch (MessageBoxEx.Show(string.Format(\"文件大小：{0:N0} bytes, 更新时间：{1:yyyy-MM-dd HH:mm:ss}\\r\\n是否立即开始下载文件？\", item.FileLength, item.LastModified), \"Patcher\", MessageBoxButtons.YesNo))\n                    {\n                        case DialogResult.Yes:\n#if NET6_0_OR_GREATER\n                            Process.Start(new ProcessStartInfo\n                            {\n                                UseShellExecute = true,\n                                FileName = txtUrl.Text,\n                            });\n#else\n                            Process.Start(txtUrl.Text);\n#endif\n                            return;\n\n                        case DialogResult.No:\n                            return;\n                    }\n                }\n                else\n                {\n                    MessageBoxEx.Show(\"文件不存在\");\n                }\n            }\n            catch (Exception ex)\n            {\n                MessageBoxEx.Show(\"出现错误：\" + ex.Message);\n            }\n        }\n\n        private void FrmPatcher_FormClosed(object sender, FormClosedEventArgs e)\n        {\n            if (this.patcherSession != null && !this.patcherSession.IsCompleted)\n            {\n                this.patcherSession.Cancel();\n            }\n            ConfigManager.Reload();\n            WcR2Config.Default.PatcherSettings.Clear();\n            foreach (PatcherSetting item in comboBoxEx1.Items)\n            {\n                WcR2Config.Default.PatcherSettings.Add(item);\n            }\n            ConfigManager.Save();\n        }\n\n        private void NewFile(BinaryReader reader, string fileName, string patchDir)\n        {\n            string tmpFile = Path.Combine(patchDir, fileName);\n            string dir = Path.GetDirectoryName(tmpFile);\n            if (!Directory.Exists(dir))\n                Directory.CreateDirectory(dir);\n        }\n\n        private void buttonXOpen1_Click(object sender, EventArgs e)\n        {\n            OpenFileDialog dlg = new OpenFileDialog();\n            dlg.Title = \"请选择补丁文件路径\";\n            dlg.Filter = \"*.patch;*.exe|*.patch;*.exe\";\n            if (dlg.ShowDialog(this) == DialogResult.OK)\n            {\n                txtPatchFile.Text = dlg.FileName;\n            }\n        }\n\n        private void buttonXOpen2_Click(object sender, EventArgs e)\n        {\n            FolderBrowserDialog dlg = new FolderBrowserDialog();\n            dlg.Description = \"请选择冒险岛文件夹路径\";\n            if (dlg.ShowDialog(this) == DialogResult.OK)\n            {\n                txtMSFolder.Text = dlg.SelectedPath;\n            }\n        }\n\n        private void buttonXPatch_Click(object sender, EventArgs e)\n        {\n            if (this.patcherSession != null)\n            {\n                if (this.patcherSession.State == PatcherTaskState.WaitForContinue)\n                {\n                    this.patcherSession.Continue();\n                    return;\n                }\n                else if (!this.patcherSession.PatchExecTask.IsCompleted)\n                {\n                    MessageBoxEx.Show(\"已经开始了一个补丁进程...\");\n                    return;\n                }\n            }\n            string compareFolder = null;\n            if (chkCompare.Checked)\n            {\n                FolderBrowserDialog dlg = new FolderBrowserDialog();\n                dlg.Description = \"请选择对比报告输出文件夹\";\n                if (dlg.ShowDialog(this) != DialogResult.OK)\n                {\n                    return;\n                }\n                compareFolder = dlg.SelectedPath;\n            }\n\n            var session = new PatcherSession()\n            {\n                PatchFile = txtPatchFile.Text,\n                MSFolder = txtMSFolder.Text,\n                PrePatch = chkPrePatch.Checked,\n                DeadPatch = chkDeadPatch.Checked,\n            };\n            session.LoggingFileName = Path.Combine(session.MSFolder, $\"wcpatcher_{DateTime.Now:yyyyMMdd_HHmmssfff}.log\");\n            session.PatchExecTask = Task.Run(() => this.ExecutePatchAsync(session, session.CancellationToken));\n            this.patcherSession = session;\n        }\n\n        private async Task ExecutePatchAsync(PatcherSession session, CancellationToken cancellationToken)\n        {\n            void AppendStateText(string text)\n            {\n                this.Invoke(new Action<string>(t => this.txtPatchState.AppendText(t)), text);\n                if (session.LoggingFileName != null)\n                {\n                    File.AppendAllText(session.LoggingFileName, text, Encoding.UTF8);\n                }\n            }\n\n            this.Invoke(() =>\n            {\n                this.advTreePatchFiles.Nodes.Clear();\n                this.txtNotice.Clear();\n                this.txtPatchState.Clear();\n                this.panelEx2.Visible = true;\n                this.expandablePanel2.Height = 340;\n            });\n\n            WzPatcher patcher = null;\n            session.State = PatcherTaskState.Prepatch;\n\n            try\n            {\n                patcher = new WzPatcher(session.PatchFile);\n                patcher.NoticeEncoding = this.PatcherNoticeEncoding ?? Encoding.Default;\n                patcher.PatchingStateChanged += (o, e) => this.patcher_PatchingStateChanged(o, e, session, AppendStateText);\n                AppendStateText($\"补丁文件：{session.PatchFile}\\r\\n\");\n                AppendStateText(\"正在检查补丁...\");\n                patcher.OpenDecompress(cancellationToken);\n                AppendStateText(\"成功\\r\\n\");\n                if (session.PrePatch)\n                {\n                    AppendStateText(\"正在预读补丁...\\r\\n\");\n                    long decompressedSize = patcher.PrePatch(cancellationToken);\n                    if (patcher.IsKMST1125Format.Value)\n                    {\n                        AppendStateText(\"补丁类型：KMST1125\\r\\n\");\n                        if (patcher.OldFileHash != null)\n                        {\n                            AppendStateText($\"获取原文件信息：{patcher.OldFileHash.Count} 个\\r\\n\");\n                        }\n                    }\n                    AppendStateText(string.Format(\"补丁大小: {0:N0} bytes...\\r\\n\", decompressedSize));\n                    AppendStateText(string.Format(\"文件变动: {0} 个...\\r\\n\", patcher.PatchParts.Count));\n\n                    this.Invoke(() =>\n                    {\n                        this.advTreePatchFiles.BeginUpdate();\n                        this.txtNotice.Text = patcher.NoticeText;\n                        foreach (PatchPartContext part in patcher.PatchParts)\n                        {\n                            this.advTreePatchFiles.Nodes.Add(CreateFileNode(part));\n                        }\n                        this.advTreePatchFiles.Enabled = true;\n                        this.advTreePatchFiles.EndUpdate();\n                    });\n\n                    AppendStateText(\"等待调整更新顺序...\\r\\n\");\n                    session.State = PatcherTaskState.WaitForContinue;\n                    await session.WaitForContinueAsync();\n                    this.Invoke(() =>\n                    {\n                        this.advTreePatchFiles.Enabled = false;\n                    });\n                    session.State = PatcherTaskState.Patching;\n                    patcher.PatchParts.Clear();\n                    foreach (Node node in this.advTreePatchFiles.Nodes)\n                    {\n                        if (node.Checked && node.Tag is PatchPartContext part)\n                        {\n                            patcher.PatchParts.Add(part);\n                        }\n                    }\n                    if (patcher.IsKMST1125Format.Value && session.DeadPatch)\n                    {\n                        AppendStateText(\"生成deadPatch执行计划：\\r\\n\");\n                        session.deadPatchExecutionPlan = new();\n                        session.deadPatchExecutionPlan.Build(patcher.PatchParts);\n                        foreach (var part in patcher.PatchParts)\n                        {\n                            if (session.deadPatchExecutionPlan.Check(part.FileName, out var filesCanInstantUpdate))\n                            {\n                                AppendStateText($\"+ 执行文件{part.FileName}\\r\\n\");\n                                foreach (var fileName in filesCanInstantUpdate)\n                                {\n                                    AppendStateText($\"  - 应用文件{fileName}\\r\\n\");\n                                }\n                            }\n                            else\n                            {\n                                AppendStateText($\"- 执行文件{part.FileName}，但延迟应用\\r\\n\");\n                            }\n                        }\n                        // disable force validation\n                        patcher.ThrowOnValidationFailed = false;\n                    }\n                }\n                AppendStateText(\"开始更新\\r\\n\");\n                var sw = Stopwatch.StartNew();\n                patcher.Patch(session.MSFolder, cancellationToken);\n                sw.Stop();\n                AppendStateText(\"完成\\r\\n\");\n                session.State = PatcherTaskState.Complete;\n                MessageBoxEx.Show(this, \"补丁结束，用时\" + sw.Elapsed, \"Patcher\");\n            }\n            catch (OperationCanceledException)\n            {\n                MessageBoxEx.Show(this.Owner, \"补丁中止。\", \"Patcher\");\n            }\n            catch (UnauthorizedAccessException ex)\n            {\n                // File IO permission error\n                MessageBoxEx.Show(this, ex.ToString(), \"Patcher\");\n            }\n            catch (Exception ex)\n            {\n                AppendStateText(ex.ToString());\n                MessageBoxEx.Show(this, ex.ToString(), \"Patcher\");\n            }\n            finally\n            {\n                session.State = PatcherTaskState.Complete;\n                if (patcher != null)\n                {\n                    patcher.Close();\n                    patcher = null;\n                }\n                GC.Collect();\n                panelEx2.Visible = false;\n                expandablePanel2.Height = 157;\n            }\n        }\n\n        private void patcher_PatchingStateChanged(object sender, PatchingEventArgs e, PatcherSession session, Action<string> logFunc)\n        {\n            switch (e.State)\n            {\n                case PatchingState.PatchStart:\n                    logFunc(\"开始更新\" + e.Part.FileName + \"\\r\\n\");\n                    break;\n                case PatchingState.VerifyOldChecksumBegin:\n                    logFunc(\"  检查旧文件checksum...\");\n                    break;\n                case PatchingState.VerifyOldChecksumEnd:\n                    logFunc(\"  结束\\r\\n\");\n                    break;\n                case PatchingState.VerifyNewChecksumBegin:\n                    logFunc(\"  检查新文件checksum...\");\n                    break;\n                case PatchingState.VerifyNewChecksumEnd:\n                    logFunc(\"  结束\\r\\n\");\n                    break;\n                case PatchingState.TempFileCreated:\n                    logFunc(\"  创建临时文件...\\r\\n\");\n                    progressBarX1.Maximum = e.Part.NewFileLength;\n                    session.TemporaryFileMapping.Add(e.Part.FileName, e.Part.TempFilePath);\n                    break;\n                case PatchingState.TempFileBuildProcessChanged:\n                    progressBarX1.Value = (int)e.CurrentFileLength;\n                    progressBarX1.Text = string.Format(\"{0:N0}/{1:N0}\", e.CurrentFileLength, e.Part.NewFileLength);\n                    break;\n                case PatchingState.TempFileClosed:\n                    logFunc(\"  关闭临时文件...\\r\\n\");\n                    progressBarX1.Value = 0;\n                    progressBarX1.Maximum = 0;\n                    progressBarX1.Text = string.Empty;\n\n                    if (!string.IsNullOrEmpty(session.CompareFolder)\n                        && e.Part.Type == 1\n                        && Path.GetExtension(e.Part.FileName).Equals(\".wz\", StringComparison.OrdinalIgnoreCase)\n                        && !Path.GetFileName(e.Part.FileName).Equals(\"list.wz\", StringComparison.OrdinalIgnoreCase))\n                    {\n                        Wz_Structure wznew = new Wz_Structure();\n                        Wz_Structure wzold = new Wz_Structure();\n                        try\n                        {\n                            logFunc(\"  (comparer)正在对比文件...\\r\\n\");\n                            EasyComparer comparer = new EasyComparer();\n                            comparer.OutputPng = chkOutputPng.Checked;\n                            comparer.OutputAddedImg = chkOutputAddedImg.Checked;\n                            comparer.OutputRemovedImg = chkOutputRemovedImg.Checked;\n                            comparer.Comparer.PngComparison = (WzPngComparison)cmbComparePng.SelectedItem;\n                            comparer.Comparer.ResolvePngLink = chkResolvePngLink.Checked;\n                            comparer.ColorTable = new List<System.Drawing.Color>()\n                            {\n                                CustomCSSConfig.Default.BackgroundColor,\n                                CustomCSSConfig.Default.NormalTextColor,\n                                CustomCSSConfig.Default.ChangedBackgroundColor,\n                                CustomCSSConfig.Default.AddedBackgroundColor,\n                                CustomCSSConfig.Default.RemovedBackgroundColor,\n                                CustomCSSConfig.Default.ChangedTextColor,\n                                CustomCSSConfig.Default.AddedTextColor,\n                                CustomCSSConfig.Default.RemovedTextColor,\n                                CustomCSSConfig.Default.HyperlinkColor\n                            };\n                            wznew.Load(e.Part.TempFilePath, false);\n                            wzold.Load(e.Part.OldFilePath, false);\n                            comparer.EasyCompareWzFiles(wznew.wz_files[0], wzold.wz_files[0], session.CompareFolder);\n                        }\n                        catch (Exception ex)\n                        {\n                            txtPatchState.AppendText(ex.ToString());\n                        }\n                        finally\n                        {\n                            wznew.Clear();\n                            wzold.Clear();\n                            GC.Collect();\n                        }\n                    }\n\n                    if (session.DeadPatch && e.Part.Type == 1 && sender is WzPatcher patcher)\n                    {\n                        if (patcher.IsKMST1125Format.Value)\n                        {\n                            if (session.deadPatchExecutionPlan?.Check(e.Part.FileName, out var filesCanInstantUpdate) ?? false)\n                            {\n                                foreach (string fileName in filesCanInstantUpdate)\n                                {\n                                    if (session.TemporaryFileMapping.TryGetValue(fileName, out var temporaryFileName))\n                                    {\n                                        logFunc($\"  (deadpatch)正在应用文件{fileName}...\\r\\n\");\n                                        patcher.SafeMove(temporaryFileName, Path.Combine(session.MSFolder, fileName));\n                                    }\n                                }\n                            }\n                            else\n                            {\n                                logFunc(\"  (deadpatch)延迟应用文件...\\r\\n\");\n                            }\n                        }\n                        else\n                        {\n                            logFunc(\"  (deadpatch)正在应用文件...\\r\\n\");\n                            patcher.SafeMove(e.Part.TempFilePath, e.Part.OldFilePath);\n                        }\n                    }\n                    break;\n                case PatchingState.PrepareVerifyOldChecksumBegin:\n                    logFunc($\"预检查旧文件checksum: {e.Part.FileName}\");\n                    break;\n                case PatchingState.PrepareVerifyOldChecksumEnd:\n                    if (e.Part.OldChecksum != e.Part.OldChecksumActual)\n                    {\n                        logFunc(\" 不一致\\r\\n\");\n                    }\n                    else\n                    {\n                        logFunc(\" 结束\\r\\n\");\n                    }\n                    break;\n                case PatchingState.ApplyFile:\n                    logFunc($\"应用文件: {e.Part.FileName}\\r\\n\");\n                    break;\n                case PatchingState.FileSkipped:\n                    logFunc(\"  跳过\" + e.Part.FileName + \"\\r\\n\");\n                    break;\n            }\n        }\n\n        private Node CreateFileNode(PatchPartContext part)\n        {\n            Node node = new Node(part.FileName) { CheckBoxVisible = true, Checked = true };\n            ElementStyle style = new ElementStyle();\n            style.TextAlignment = eStyleTextAlignment.Far;\n            node.Cells.Add(new Cell(part.Type.ToString(), style));\n            node.Cells.Add(new Cell(part.NewFileLength.ToString(\"n0\"), style));\n            node.Cells.Add(new Cell(part.NewChecksum.ToString(\"x8\"), style));\n            node.Cells.Add(new Cell(part.OldChecksum?.ToString(\"x8\"), style));\n            if (part.Type == 1)\n            {\n                string text = string.Format(\"{0}|{1}|{2}|{3}\", part.Action0, part.Action1, part.Action2, part.DependencyFiles.Count);\n                node.Cells.Add(new Cell(text, style));\n            }\n            node.Tag = part;\n            return node;\n        }\n\n        private void buttonXOpen3_Click(object sender, EventArgs e)\n        {\n            OpenFileDialog dlg = new OpenFileDialog();\n            dlg.Title = \"请选择补丁文件路径\";\n            dlg.Filter = \"*.patch;*.exe|*.patch;*.exe\";\n            if (dlg.ShowDialog(this) == DialogResult.OK)\n            {\n                txtPatchFile2.Text = dlg.FileName;\n            }\n        }\n\n        private void buttonXOpen4_Click(object sender, EventArgs e)\n        {\n            FolderBrowserDialog dlg = new FolderBrowserDialog();\n            dlg.Description = \"请选择冒险岛文件夹路径\";\n            if (dlg.ShowDialog(this) == DialogResult.OK)\n            {\n                txtMSFolder2.Text = dlg.SelectedPath;\n            }\n        }\n\n        private void buttonXCreate_Click(object sender, EventArgs e)\n        {\n            MessageBoxEx.Show(@\"> 这是一个测试功能...\n> 还没完成 所以请选择patch文件  exe补丁暂时懒得分离\n> 没有检查原客户端版本 为了正确执行请预先确认\n> 暂时不提供文件块的筛选或文件缺失提示\n> 没优化 于是可能生成文件体积较大 但是几乎可以保证完整性\n> 对于KMST1125后无法正常工作\", \"声明\");\n\n            SaveFileDialog dlg = new SaveFileDialog();\n            dlg.Filter = \"*.patch|*.patch\";\n            dlg.Title = \"选择输出文件\";\n            dlg.CheckFileExists = false;\n            dlg.InitialDirectory = Path.GetDirectoryName(txtPatchFile2.Text);\n            dlg.FileName = Path.GetFileNameWithoutExtension(txtPatchFile2.Text) + \"_reverse.patch\";\n\n            if (dlg.ShowDialog(this) == DialogResult.OK)\n            {\n                try\n                {\n                    ReversePatcherBuilder builder = new ReversePatcherBuilder();\n                    builder.msDir = txtMSFolder2.Text;\n                    builder.patchFileName = txtPatchFile2.Text;\n                    builder.outputFileName = dlg.FileName;\n                    builder.Build();\n                }\n                catch (Exception ex)\n                {\n                }\n            }\n        }\n\n        class PatcherSession\n        {\n            public PatcherSession()\n            {\n                this.cancellationTokenSource = new CancellationTokenSource();\n            }\n\n            public string PatchFile;\n            public string MSFolder;\n            public string CompareFolder;\n            public bool PrePatch;\n            public bool DeadPatch;\n\n            public Task PatchExecTask;\n            public string LoggingFileName;\n            public PatcherTaskState State;\n\n            public DeadPatchExecutionPlan deadPatchExecutionPlan;\n            public Dictionary<string, string> TemporaryFileMapping = new ();\n\n            public CancellationToken CancellationToken => this.cancellationTokenSource.Token;\n            private CancellationTokenSource cancellationTokenSource;\n            private TaskCompletionSource<bool> tcsWaiting;\n\n            public bool IsCompleted => this.PatchExecTask?.IsCompleted ?? true;\n\n            public void Cancel()\n            {\n                this.cancellationTokenSource.Cancel();\n            }\n\n            public async Task WaitForContinueAsync()\n            {\n                var tcs = new TaskCompletionSource<bool>();\n                this.tcsWaiting = tcs;\n                this.cancellationTokenSource.Token.Register(() => tcs.TrySetCanceled());\n                await tcs.Task;\n            }\n\n            public void Continue()\n            {\n                if (this.tcsWaiting != null)\n                {\n                    this.tcsWaiting.SetResult(true);\n                }\n            }\n        }\n\n        enum PatcherTaskState\n        {\n            NotStarted = 0,\n            Prepatch = 1,\n            WaitForContinue = 2,\n            Patching = 3,\n            Complete = 4,\n        }\n\n        class DeadPatchExecutionPlan\n        {\n            public DeadPatchExecutionPlan()\n            {\n                this.FileUpdateDependencies = new Dictionary<string, List<string>>();\n            }\n\n            public Dictionary<string, List<string>> FileUpdateDependencies { get; private set; }\n\n            public void Build(IEnumerable<PatchPartContext> orderedParts)\n            {\n                /*\n                 *  for examle:\n                 *    fileName   | type | dependencies               \n                 *    -----------|------|---------------     \n                 *    Mob_000.wz | 1    | Mob_000.wz   (self update)\n                 *    Mob_001.wz | 1    | Mob_001.wz, Mob_002.wz  (merge data)\n                 *    Mob_002.wz | 1    | Mob_001.wz, Mob_002.wz  (merge data)\n                 *    Mob_003.wz | 1    | Mob_001.wz, Mob_002.wz  (balance size from other file)\n                 *                                                 \n                 *  fileLastDependecy:                             \n                 *    key        | value                           \n                 *    -----------|----------------                 \n                 *    Mob_000.wz | Mob_000.wz\n                 *    Mob_001.wz | Mob_003.wz\n                 *    Mob_002.wz | Mob_003.wz\n                 *    Mob_003.wz | Mob_003.wz\n                 *    \n                 *  FileUpdateDependencies:\n                 *    key        | value\n                 *    -----------|----------------\n                 *    Mob_000.wz | Mob000.wz\n                 *    Mob_003.wz | Mob001.wz, Mob002.wz, Mob003.wz\n                 */\n\n                // find the last dependency\n                Dictionary<string, string> fileLastDependecy = new();\n                foreach (var part in orderedParts)\n                {\n                    if (part.Type == 0)\n                    {\n                        fileLastDependecy[part.FileName] = part.FileName;\n                    }\n                    else if (part.Type == 1)\n                    {\n                        fileLastDependecy[part.FileName] = part.FileName;\n                        foreach (var dep in part.DependencyFiles)\n                        {\n                            fileLastDependecy[dep] = part.FileName;\n                        }\n                    }\n                }\n\n                // reverse key and value\n                this.FileUpdateDependencies.Clear();\n                foreach (var grp in fileLastDependecy.GroupBy(kv => kv.Value, kv => kv.Key))\n                {\n                    this.FileUpdateDependencies.Add(grp.Key, grp.ToList());\n                }\n            }\n\n            public bool Check(string fileName, out IReadOnlyList<string> filesCanInstantUpdate)\n            {\n                if (this.FileUpdateDependencies.TryGetValue(fileName, out var value) && value != null && value.Count > 0)\n                {\n                    filesCanInstantUpdate = value;\n                    return true;\n                }\n\n                filesCanInstantUpdate = null;\n                return false;\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmPatcher.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <metadata name=\"superTooltip1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>163, 55</value>\n  </metadata>\n  <metadata name=\"columnHeader1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader2.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>146, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader3.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>275, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader4.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>404, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader5.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>533, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader6.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 55</value>\n  </metadata>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmQuickViewSetting.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmQuickViewSetting\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.superTabControl1 = new DevComponents.DotNetBar.SuperTabControl();\n            this.superTabControlPanel1 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.checkBoxX10 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.labelX3 = new DevComponents.DotNetBar.LabelX();\n            this.comboBoxEx2 = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.comboItem5 = new DevComponents.Editors.ComboItem();\n            this.comboItem6 = new DevComponents.Editors.ComboItem();\n            this.comboItem7 = new DevComponents.Editors.ComboItem();\n            this.comboItem8 = new DevComponents.Editors.ComboItem();\n            this.comboItem9 = new DevComponents.Editors.ComboItem();\n            this.comboItem10 = new DevComponents.Editors.ComboItem();\n            this.labelX2 = new DevComponents.DotNetBar.LabelX();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.comboBoxEx1 = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.comboItem1 = new DevComponents.Editors.ComboItem();\n            this.comboItem2 = new DevComponents.Editors.ComboItem();\n            this.comboItem3 = new DevComponents.Editors.ComboItem();\n            this.comboItem4 = new DevComponents.Editors.ComboItem();\n            this.checkBoxX2 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX1 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTabItem1 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel4 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.checkBoxX7 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTabItem4 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel3 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.checkBoxX9 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX8 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX5 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTabItem3 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel2 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.checkBoxX6 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX4 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX3 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTabItem2 = new DevComponents.DotNetBar.SuperTabItem();\n            this.panelEx1 = new DevComponents.DotNetBar.PanelEx();\n            this.buttonX2 = new DevComponents.DotNetBar.ButtonX();\n            this.buttonX1 = new DevComponents.DotNetBar.ButtonX();\n            this.checkBoxX11 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX12 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX13 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX14 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX15 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.superTabControlPanel5 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.superTabItem5 = new DevComponents.DotNetBar.SuperTabItem();\n            this.chkShowDamageSkinID = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkShowDamageSkin = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkUseMiniSize = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkAlwaysUseMseaFormat = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkDisplayUnitOnSingleLine = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.lblDamageSkinNumber = new DevComponents.DotNetBar.LabelX();\n            this.txtDamageSkinNumber = new DevComponents.DotNetBar.Controls.TextBoxX();\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).BeginInit();\n            this.superTabControl1.SuspendLayout();\n            this.superTabControlPanel1.SuspendLayout();\n            this.superTabControlPanel4.SuspendLayout();\n            this.superTabControlPanel3.SuspendLayout();\n            this.superTabControlPanel2.SuspendLayout();\n            this.superTabControlPanel5.SuspendLayout();\n            this.panelEx1.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // superTabControl1\n            // \n            this.superTabControl1.CloseButtonOnTabsAlwaysDisplayed = false;\n            // \n            // \n            // \n            // \n            // \n            // \n            this.superTabControl1.ControlBox.CloseBox.Name = \"\";\n            // \n            // \n            // \n            this.superTabControl1.ControlBox.MenuBox.Name = \"\";\n            this.superTabControl1.ControlBox.Name = \"\";\n            this.superTabControl1.ControlBox.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabControl1.ControlBox.MenuBox,\n            this.superTabControl1.ControlBox.CloseBox});\n            this.superTabControl1.Controls.Add(this.superTabControlPanel2);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel3);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel1);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel4);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel5);\n            this.superTabControl1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControl1.Location = new System.Drawing.Point(0, 0);\n            this.superTabControl1.Name = \"superTabControl1\";\n            this.superTabControl1.ReorderTabsEnabled = true;\n            this.superTabControl1.SelectedTabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Bold);\n            this.superTabControl1.SelectedTabIndex = 0;\n            this.superTabControl1.Size = new System.Drawing.Size(304, 251);\n            this.superTabControl1.TabAlignment = DevComponents.DotNetBar.eTabStripAlignment.Left;\n            this.superTabControl1.TabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.superTabControl1.TabIndex = 0;\n            this.superTabControl1.Tabs.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabItem1,\n            this.superTabItem2,\n            this.superTabItem3,\n            this.superTabItem4,\n            this.superTabItem5});\n            this.superTabControl1.Text = \"superTabControl1\";\n            // \n            // superTabControlPanel1\n            // \n            this.superTabControlPanel1.Controls.Add(this.checkBoxX15);\n            this.superTabControlPanel1.Controls.Add(this.checkBoxX14);\n            this.superTabControlPanel1.Controls.Add(this.checkBoxX13);\n            this.superTabControlPanel1.Controls.Add(this.checkBoxX10);\n            this.superTabControlPanel1.Controls.Add(this.labelX3);\n            this.superTabControlPanel1.Controls.Add(this.comboBoxEx2);\n            this.superTabControlPanel1.Controls.Add(this.labelX2);\n            this.superTabControlPanel1.Controls.Add(this.labelX1);\n            this.superTabControlPanel1.Controls.Add(this.comboBoxEx1);\n            this.superTabControlPanel1.Controls.Add(this.checkBoxX2);\n            this.superTabControlPanel1.Controls.Add(this.checkBoxX1);\n            this.superTabControlPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel1.Location = new System.Drawing.Point(62, 0);\n            this.superTabControlPanel1.Name = \"superTabControlPanel1\";\n            this.superTabControlPanel1.Size = new System.Drawing.Size(242, 251);\n            this.superTabControlPanel1.TabIndex = 1;\n            this.superTabControlPanel1.TabItem = this.superTabItem1;\n            // \n            // checkBoxX10\n            // \n            this.checkBoxX10.AutoSize = true;\n            this.checkBoxX10.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX10.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX10.Location = new System.Drawing.Point(13, 9);\n            this.checkBoxX10.Name = \"checkBoxX10\";\n            this.checkBoxX10.Size = new System.Drawing.Size(125, 18);\n            this.checkBoxX10.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX10.TabIndex = 7;\n            this.checkBoxX10.Text = \"显示技能扩展属性\";\n            // \n            // labelX3\n            // \n            this.labelX3.AutoSize = true;\n            this.labelX3.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX3.ForeColor = System.Drawing.SystemColors.ControlDarkDark;\n            this.labelX3.Location = new System.Drawing.Point(13, 211);\n            this.labelX3.Name = \"labelX3\";\n            this.labelX3.Size = new System.Drawing.Size(142, 30);\n            this.labelX3.TabIndex = 6;\n            this.labelX3.Text = \"对tooltip窗口为焦点，<br/>\\r\\n使用 <b>- + [ ]</b> 键调整等级<br/>\\r\\n使用 <b>PgUp / PgDn</b> 键调整适用职业\";\n            // \n            // comboBoxEx2\n            // \n            this.comboBoxEx2.DisplayMember = \"Text\";\n            this.comboBoxEx2.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.comboBoxEx2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.comboBoxEx2.FormattingEnabled = true;\n            this.comboBoxEx2.ItemHeight = 15;\n            this.comboBoxEx2.Items.AddRange(new object[] {\n            this.comboItem5,\n            this.comboItem6,\n            this.comboItem7,\n            this.comboItem8,\n            this.comboItem9,\n            this.comboItem10});\n            this.comboBoxEx2.Location = new System.Drawing.Point(94, 179);\n            this.comboBoxEx2.Name = \"comboBoxEx2\";\n            this.comboBoxEx2.Size = new System.Drawing.Size(90, 21);\n            this.comboBoxEx2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.comboBoxEx2.TabIndex = 5;\n            // \n            // comboItem5\n            // \n            this.comboItem5.Text = \"1\";\n            // \n            // comboItem6\n            // \n            this.comboItem6.Text = \"5\";\n            // \n            // comboItem7\n            // \n            this.comboItem7.Text = \"10\";\n            // \n            // comboItem8\n            // \n            this.comboItem8.Text = \"15\";\n            // \n            // comboItem9\n            // \n            this.comboItem9.Text = \"30\";\n            // \n            // comboItem10\n            // \n            this.comboItem10.Text = \"32\";\n            // \n            // labelX2\n            // \n            this.labelX2.AutoSize = true;\n            this.labelX2.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX2.Location = new System.Drawing.Point(13, 182);\n            this.labelX2.Name = \"labelX2\";\n            this.labelX2.Size = new System.Drawing.Size(81, 18);\n            this.labelX2.TabIndex = 4;\n            this.labelX2.Text = \"调整等级间隔\";\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            this.labelX1.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(13, 157);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(81, 18);\n            this.labelX1.TabIndex = 3;\n            this.labelX1.Text = \"默认技能等级\";\n            // \n            // comboBoxEx1\n            // \n            this.comboBoxEx1.DisplayMember = \"Text\";\n            this.comboBoxEx1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.comboBoxEx1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.comboBoxEx1.FormattingEnabled = true;\n            this.comboBoxEx1.ItemHeight = 15;\n            this.comboBoxEx1.Items.AddRange(new object[] {\n            this.comboItem1,\n            this.comboItem2,\n            this.comboItem3,\n            this.comboItem4});\n            this.comboBoxEx1.Location = new System.Drawing.Point(94, 154);\n            this.comboBoxEx1.Name = \"comboBoxEx1\";\n            this.comboBoxEx1.Size = new System.Drawing.Size(90, 21);\n            this.comboBoxEx1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.comboBoxEx1.TabIndex = 2;\n            // \n            // comboItem1\n            // \n            this.comboItem1.Text = \"Lv.0\";\n            // \n            // comboItem2\n            // \n            this.comboItem2.Text = \"Lv.1\";\n            // \n            // comboItem3\n            // \n            this.comboItem3.Text = \"Lv.Max\";\n            // \n            // comboItem4\n            // \n            this.comboItem4.Text = \"Lv.Max+2\";\n            // \n            // checkBoxX2\n            // \n            this.checkBoxX2.AutoSize = true;\n            this.checkBoxX2.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX2.Location = new System.Drawing.Point(13, 57);\n            this.checkBoxX2.Name = \"checkBoxX2\";\n            this.checkBoxX2.Size = new System.Drawing.Size(125, 18);\n            this.checkBoxX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX2.TabIndex = 1;\n            this.checkBoxX2.Text = \"显示技能动作延时\";\n            // \n            // checkBoxX1\n            // \n            this.checkBoxX1.AutoSize = true;\n            this.checkBoxX1.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX1.Location = new System.Drawing.Point(13, 33);\n            this.checkBoxX1.Name = \"checkBoxX1\";\n            this.checkBoxX1.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX1.TabIndex = 0;\n            this.checkBoxX1.Text = \"显示技能代码\";\n            // \n            // superTabItem1\n            // \n            this.superTabItem1.AttachedControl = this.superTabControlPanel1;\n            this.superTabItem1.GlobalItem = false;\n            this.superTabItem1.Name = \"superTabItem1\";\n            this.superTabItem1.Text = \"Skill\";\n            // \n            // superTabControlPanel4\n            // \n            this.superTabControlPanel4.Controls.Add(this.checkBoxX7);\n            this.superTabControlPanel4.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel4.Location = new System.Drawing.Point(63, 0);\n            this.superTabControlPanel4.Name = \"superTabControlPanel4\";\n            this.superTabControlPanel4.Size = new System.Drawing.Size(241, 231);\n            this.superTabControlPanel4.TabIndex = 0;\n            this.superTabControlPanel4.TabItem = this.superTabItem4;\n            // \n            // checkBoxX7\n            // \n            this.checkBoxX7.AutoSize = true;\n            this.checkBoxX7.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX7.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX7.Location = new System.Drawing.Point(13, 12);\n            this.checkBoxX7.Name = \"checkBoxX7\";\n            this.checkBoxX7.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX7.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX7.TabIndex = 3;\n            this.checkBoxX7.Text = \"显示制造代码\";\n            // \n            // superTabItem4\n            // \n            this.superTabItem4.AttachedControl = this.superTabControlPanel4;\n            this.superTabItem4.GlobalItem = false;\n            this.superTabItem4.Name = \"superTabItem4\";\n            this.superTabItem4.Text = \"Recipe\";\n            // \n            // superTabControlPanel3\n            // \n            this.superTabControlPanel3.Controls.Add(this.checkBoxX12);\n            this.superTabControlPanel3.Controls.Add(this.checkBoxX9);\n            this.superTabControlPanel3.Controls.Add(this.checkBoxX8);\n            this.superTabControlPanel3.Controls.Add(this.checkBoxX5);\n            this.superTabControlPanel3.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel3.Location = new System.Drawing.Point(62, 0);\n            this.superTabControlPanel3.Name = \"superTabControlPanel3\";\n            this.superTabControlPanel3.Size = new System.Drawing.Size(242, 231);\n            this.superTabControlPanel3.TabIndex = 0;\n            this.superTabControlPanel3.TabItem = this.superTabItem3;\n            // \n            // checkBoxX12\n            // \n            this.checkBoxX12.AutoSize = true;\n            this.checkBoxX12.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX12.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX12.Location = new System.Drawing.Point(13, 84);\n            this.checkBoxX12.Name = \"checkBoxX12\";\n            this.checkBoxX12.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX12.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX12.TabIndex = 5;\n            this.checkBoxX12.Text = \"显示称号样式\";\n            // \n            // checkBoxX9\n            // \n            this.checkBoxX9.AutoSize = true;\n            this.checkBoxX9.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX9.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX9.Location = new System.Drawing.Point(13, 60);\n            this.checkBoxX9.Name = \"checkBoxX9\";\n            this.checkBoxX9.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX9.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX9.TabIndex = 4;\n            this.checkBoxX9.Text = \"图纸联动道具\";\n            // \n            // checkBoxX8\n            // \n            this.checkBoxX8.AutoSize = true;\n            this.checkBoxX8.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX8.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX8.Location = new System.Drawing.Point(13, 36);\n            this.checkBoxX8.Name = \"checkBoxX8\";\n            this.checkBoxX8.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX8.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX8.TabIndex = 3;\n            this.checkBoxX8.Text = \"图纸联动配方\";\n            // \n            // checkBoxX5\n            // \n            this.checkBoxX5.AutoSize = true;\n            this.checkBoxX5.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX5.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX5.Location = new System.Drawing.Point(13, 12);\n            this.checkBoxX5.Name = \"checkBoxX5\";\n            this.checkBoxX5.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX5.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX5.TabIndex = 2;\n            this.checkBoxX5.Text = \"显示物品代码\";\n            // \n            // superTabItem3\n            // \n            this.superTabItem3.AttachedControl = this.superTabControlPanel3;\n            this.superTabItem3.GlobalItem = false;\n            this.superTabItem3.Name = \"superTabItem3\";\n            this.superTabItem3.Text = \"Item\";\n            // \n            // superTabControlPanel2\n            // \n            this.superTabControlPanel2.Controls.Add(this.checkBoxX11);\n            this.superTabControlPanel2.Controls.Add(this.checkBoxX6);\n            this.superTabControlPanel2.Controls.Add(this.checkBoxX4);\n            this.superTabControlPanel2.Controls.Add(this.checkBoxX3);\n            this.superTabControlPanel2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel2.Location = new System.Drawing.Point(62, 0);\n            this.superTabControlPanel2.Name = \"superTabControlPanel2\";\n            this.superTabControlPanel2.Size = new System.Drawing.Size(242, 231);\n            this.superTabControlPanel2.TabIndex = 0;\n            this.superTabControlPanel2.TabItem = this.superTabItem2;\n            // \n            // checkBoxX6\n            // \n            this.checkBoxX6.AutoSize = true;\n            this.checkBoxX6.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX6.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX6.Location = new System.Drawing.Point(13, 60);\n            this.checkBoxX6.Name = \"checkBoxX6\";\n            this.checkBoxX6.Size = new System.Drawing.Size(125, 18);\n            this.checkBoxX6.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX6.TabIndex = 3;\n            this.checkBoxX6.Text = \"显示装备成长信息\";\n            // \n            // checkBoxX4\n            // \n            this.checkBoxX4.AutoSize = true;\n            this.checkBoxX4.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX4.Location = new System.Drawing.Point(13, 36);\n            this.checkBoxX4.Name = \"checkBoxX4\";\n            this.checkBoxX4.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX4.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX4.TabIndex = 2;\n            this.checkBoxX4.Text = \"显示武器攻速\";\n            // \n            // checkBoxX3\n            // \n            this.checkBoxX3.AutoSize = true;\n            this.checkBoxX3.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX3.Location = new System.Drawing.Point(13, 12);\n            this.checkBoxX3.Name = \"checkBoxX3\";\n            this.checkBoxX3.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX3.TabIndex = 1;\n            this.checkBoxX3.Text = \"显示装备代码\";\n            // \n            // superTabItem2\n            // \n            this.superTabItem2.AttachedControl = this.superTabControlPanel2;\n            this.superTabItem2.GlobalItem = false;\n            this.superTabItem2.Name = \"superTabItem2\";\n            this.superTabItem2.Text = \"Gear\";\n            // \n            // panelEx1\n            // \n            this.panelEx1.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx1.Controls.Add(this.buttonX2);\n            this.panelEx1.Controls.Add(this.buttonX1);\n            this.panelEx1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx1.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.panelEx1.Location = new System.Drawing.Point(0, 251);\n            this.panelEx1.Name = \"panelEx1\";\n            this.panelEx1.Size = new System.Drawing.Size(304, 30);\n            this.panelEx1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx1.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx1.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx1.Style.GradientAngle = 90;\n            this.panelEx1.TabIndex = 1;\n            // \n            // buttonX2\n            // \n            this.buttonX2.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX2.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX2.DialogResult = System.Windows.Forms.DialogResult.Cancel;\n            this.buttonX2.Location = new System.Drawing.Point(234, 4);\n            this.buttonX2.Name = \"buttonX2\";\n            this.buttonX2.Size = new System.Drawing.Size(60, 23);\n            this.buttonX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX2.TabIndex = 1;\n            this.buttonX2.Text = \"取消\";\n            // \n            // buttonX1\n            // \n            this.buttonX1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX1.DialogResult = System.Windows.Forms.DialogResult.OK;\n            this.buttonX1.Location = new System.Drawing.Point(167, 4);\n            this.buttonX1.Name = \"buttonX1\";\n            this.buttonX1.Size = new System.Drawing.Size(60, 23);\n            this.buttonX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX1.TabIndex = 0;\n            this.buttonX1.Text = \"确定\";\n            // \n            // checkBoxX11\n            // \n            this.checkBoxX11.AutoSize = true;\n            this.checkBoxX11.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX11.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX11.Location = new System.Drawing.Point(13, 84);\n            this.checkBoxX11.Name = \"checkBoxX11\";\n            this.checkBoxX11.Size = new System.Drawing.Size(101, 18);\n            this.checkBoxX11.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX11.TabIndex = 4;\n            this.checkBoxX11.Text = \"显示勋章样式\";\n            // \n            // checkBoxX13\n            // \n            this.checkBoxX13.AutoSize = true;\n            this.checkBoxX13.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX13.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX13.Location = new System.Drawing.Point(13, 81);\n            this.checkBoxX13.Name = \"checkBoxX13\";\n            this.checkBoxX13.Size = new System.Drawing.Size(138, 18);\n            this.checkBoxX13.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX13.TabIndex = 8;\n            this.checkBoxX13.Text = \"冷却时间转换为[秒]\";\n            // \n            // checkBoxX14\n            // \n            this.checkBoxX14.AutoSize = true;\n            this.checkBoxX14.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX14.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX14.Location = new System.Drawing.Point(13, 105);\n            this.checkBoxX14.Name = \"checkBoxX14\";\n            this.checkBoxX14.Size = new System.Drawing.Size(150, 18);\n            this.checkBoxX14.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX14.TabIndex = 9;\n            this.checkBoxX14.Text = \"PerM属性转换为百分制\";\n            // \n            // checkBoxX15\n            // \n            this.checkBoxX15.AutoSize = true;\n            this.checkBoxX15.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.checkBoxX15.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX15.Location = new System.Drawing.Point(13, 129);\n            this.checkBoxX15.Name = \"checkBoxX15\";\n            this.checkBoxX15.Size = new System.Drawing.Size(125, 18);\n            this.checkBoxX15.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX15.TabIndex = 10;\n            this.checkBoxX15.Text = \"忽略公式求解错误\";\n            // \n            // superTabControlPanel5\n            // \n            this.superTabControlPanel5.Controls.Add(this.txtDamageSkinNumber);\n            this.superTabControlPanel5.Controls.Add(this.lblDamageSkinNumber);\n            this.superTabControlPanel5.Controls.Add(this.chkDisplayUnitOnSingleLine);\n            this.superTabControlPanel5.Controls.Add(this.chkAlwaysUseMseaFormat);\n            this.superTabControlPanel5.Controls.Add(this.chkUseMiniSize);\n            this.superTabControlPanel5.Controls.Add(this.chkShowDamageSkin);\n            this.superTabControlPanel5.Controls.Add(this.chkShowDamageSkinID);\n            this.superTabControlPanel5.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel5.Location = new System.Drawing.Point(62, 0);\n            this.superTabControlPanel5.Name = \"superTabControlPanel5\";\n            this.superTabControlPanel5.Size = new System.Drawing.Size(242, 211);\n            this.superTabControlPanel5.TabIndex = 0;\n            this.superTabControlPanel5.TabItem = this.superTabItem5;\n            this.superTabControlPanel5.Visible = false;\n            // \n            // superTabItem5\n            // \n            this.superTabItem5.AttachedControl = this.superTabControlPanel5;\n            this.superTabItem5.GlobalItem = false;\n            this.superTabItem5.Name = \"superTabItem5\";\n            this.superTabItem5.Text = \"D.Skin\";\n\n            // \n            // txtDamageSkinNumber\n            // \n            // \n            // \n            // \n            this.txtDamageSkinNumber.Border.Class = \"TextBoxBorder\";\n            this.txtDamageSkinNumber.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.txtDamageSkinNumber.Location = new System.Drawing.Point(76, 130);\n            this.txtDamageSkinNumber.Name = \"txtDamageSkinNumber\";\n            this.txtDamageSkinNumber.Size = new System.Drawing.Size(160, 23);\n            this.txtDamageSkinNumber.WatermarkText = \"1234567890\";\n            this.txtDamageSkinNumber.MaxLength = 18;\n            this.txtDamageSkinNumber.TextChanged += new System.EventHandler(this.txtDamageSkinNumber_TextChanged);\n            this.txtDamageSkinNumber.TabIndex = 7;\n            // \n            // lblDamageSkinNumber\n            // \n            this.lblDamageSkinNumber.AutoSize = true;\n            this.lblDamageSkinNumber.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.lblDamageSkinNumber.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblDamageSkinNumber.Location = new System.Drawing.Point(13, 132);\n            this.lblDamageSkinNumber.Name = \"lblDamageSkinNumber\";\n            this.lblDamageSkinNumber.Size = new System.Drawing.Size(87, 16);\n            this.lblDamageSkinNumber.TabIndex = 6;\n            this.lblDamageSkinNumber.Text = \"伤害数字\";\n            // \n            // chkDisplayUnitOnSingleLine\n            // \n            this.chkDisplayUnitOnSingleLine.AutoSize = true;\n            this.chkDisplayUnitOnSingleLine.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkDisplayUnitOnSingleLine.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkDisplayUnitOnSingleLine.Location = new System.Drawing.Point(13, 108);\n            this.chkDisplayUnitOnSingleLine.Name = \"chkDisplayUnitOnSingleLine\";\n            this.chkDisplayUnitOnSingleLine.Size = new System.Drawing.Size(133, 16);\n            this.chkDisplayUnitOnSingleLine.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkDisplayUnitOnSingleLine.TabIndex = 5;\n            this.chkDisplayUnitOnSingleLine.Text = \"在单独一行显示单位\";\n            // \n            // chkAlwaysUseMseaFormat\n            // \n            this.chkAlwaysUseMseaFormat.AutoSize = true;\n            this.chkAlwaysUseMseaFormat.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkAlwaysUseMseaFormat.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkAlwaysUseMseaFormat.Location = new System.Drawing.Point(13, 84);\n            this.chkAlwaysUseMseaFormat.Name = \"chkAlwaysUseMseaFormat\";\n            this.chkAlwaysUseMseaFormat.Size = new System.Drawing.Size(133, 16);\n            this.chkAlwaysUseMseaFormat.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkAlwaysUseMseaFormat.TabIndex = 4;\n            this.chkAlwaysUseMseaFormat.Text = \"使用MSEA的千位分隔符\";\n            // \n            // chkUseMiniSize\n            // \n            this.chkUseMiniSize.AutoSize = true;\n            this.chkUseMiniSize.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkUseMiniSize.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkUseMiniSize.Location = new System.Drawing.Point(13, 60);\n            this.chkUseMiniSize.Name = \"chkUseMiniSize\";\n            this.chkUseMiniSize.Size = new System.Drawing.Size(145, 16);\n            this.chkUseMiniSize.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkUseMiniSize.TabIndex = 3;\n            this.chkUseMiniSize.Text = \"使用迷你尺寸伤害皮肤\";\n            // \n            // chkShowDamageSkin\n            // \n            this.chkShowDamageSkin.AutoSize = true;\n            this.chkShowDamageSkin.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkShowDamageSkin.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkShowDamageSkin.Location = new System.Drawing.Point(13, 36);\n            this.chkShowDamageSkin.Name = \"chkShowDamageSkin\";\n            this.chkShowDamageSkin.Size = new System.Drawing.Size(172, 16);\n            this.chkShowDamageSkin.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkShowDamageSkin.TabIndex = 2;\n            this.chkShowDamageSkin.Text = \"显示伤害皮肤\";\n            // \n            // chkShowDamageSkinID\n            // \n            this.chkShowDamageSkinID.AutoSize = true;\n            this.chkShowDamageSkinID.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkShowDamageSkinID.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkShowDamageSkinID.Location = new System.Drawing.Point(13, 12);\n            this.chkShowDamageSkinID.Name = \"chkShowDamageSkinID\";\n            this.chkShowDamageSkinID.Size = new System.Drawing.Size(117, 16);\n            this.chkShowDamageSkinID.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkShowDamageSkinID.TabIndex = 1;\n            this.chkShowDamageSkinID.Text = \"显示伤害皮肤代码\";\n            // \n            // FrmQuickViewSetting\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(304, 281);\n            this.Controls.Add(this.superTabControl1);\n            this.Controls.Add(this.panelEx1);\n            this.DoubleBuffered = true;\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n            this.MaximizeBox = false;\n            this.Name = \"FrmQuickViewSetting\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"快速预览设置\";\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).EndInit();\n            this.superTabControl1.ResumeLayout(false);\n            this.superTabControlPanel1.ResumeLayout(false);\n            this.superTabControlPanel1.PerformLayout();\n            this.superTabControlPanel4.ResumeLayout(false);\n            this.superTabControlPanel4.PerformLayout();\n            this.superTabControlPanel3.ResumeLayout(false);\n            this.superTabControlPanel3.PerformLayout();\n            this.superTabControlPanel2.ResumeLayout(false);\n            this.superTabControlPanel2.PerformLayout();\n            this.superTabControlPanel5.ResumeLayout(false);\n            this.superTabControlPanel5.PerformLayout();\n            this.panelEx1.ResumeLayout(false);\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.SuperTabControl superTabControl1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX1;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel3;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem3;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel2;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem2;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx comboBoxEx1;\n        private DevComponents.Editors.ComboItem comboItem1;\n        private DevComponents.Editors.ComboItem comboItem2;\n        private DevComponents.Editors.ComboItem comboItem3;\n        private DevComponents.Editors.ComboItem comboItem4;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX2;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx comboBoxEx2;\n        private DevComponents.Editors.ComboItem comboItem5;\n        private DevComponents.Editors.ComboItem comboItem6;\n        private DevComponents.DotNetBar.LabelX labelX2;\n        private DevComponents.Editors.ComboItem comboItem7;\n        private DevComponents.Editors.ComboItem comboItem8;\n        private DevComponents.Editors.ComboItem comboItem9;\n        private DevComponents.Editors.ComboItem comboItem10;\n        private DevComponents.DotNetBar.PanelEx panelEx1;\n        private DevComponents.DotNetBar.ButtonX buttonX1;\n        private DevComponents.DotNetBar.ButtonX buttonX2;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX3;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX5;\n        private DevComponents.DotNetBar.LabelX labelX3;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX4;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel4;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem4;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX6;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX7;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX9;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX8;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX10;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX11;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX12;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX13;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX14;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX15;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel5;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem5;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkShowDamageSkinID;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkShowDamageSkin;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkUseMiniSize;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkAlwaysUseMseaFormat;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkDisplayUnitOnSingleLine;\n        private DevComponents.DotNetBar.LabelX lblDamageSkinNumber;\n        private DevComponents.DotNetBar.Controls.TextBoxX txtDamageSkinNumber;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmQuickViewSetting.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Data;\nusing System.Drawing;\nusing System.Text;\nusing System.Windows.Forms;\nusing System.Reflection;\nusing DevComponents.Editors;\nusing WzComparerR2.Config;\n\n\nnamespace WzComparerR2\n{\n    public partial class FrmQuickViewSetting : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmQuickViewSetting()\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n            this.comboBoxEx1.SelectedIndex = 0;\n            this.comboBoxEx2.SelectedIndex = 0;\n        }\n\n        [Link]\n        public bool Skill_ShowProperties\n        {\n            get { return checkBoxX10.Checked; }\n            set { checkBoxX10.Checked = value; }\n        }\n\n        [Link]\n        public bool Skill_ShowID\n        {\n            get { return checkBoxX1.Checked; }\n            set { checkBoxX1.Checked = value; }\n        }\n\n        [Link]\n        public bool Skill_ShowDelay\n        {\n            get { return checkBoxX2.Checked; }\n            set { checkBoxX2.Checked = value; }\n        }\n\n        [Link]\n        public DefaultLevel Skill_DefaultLevel\n        {\n            get { return (DefaultLevel)comboBoxEx1.SelectedIndex; }\n            set { comboBoxEx1.SelectedIndex = (int)value; }\n        }\n\n        [Link]\n        public int Skill_IntervalLevel\n        {\n            get { return Convert.ToInt32(((ComboItem)comboBoxEx2.SelectedItem).Text); }\n            set\n            {\n                for (int i = 0; i < comboBoxEx2.Items.Count; i++)\n                {\n                    if (value <= Convert.ToInt32(((ComboItem)comboBoxEx2.Items[i]).Text))\n                    {\n                        comboBoxEx2.SelectedIndex = i;\n                        return;\n                    }\n                }\n                comboBoxEx2.SelectedIndex = comboBoxEx2.Items.Count - 1;\n            }\n        }\n\n        [Link]\n        public bool Skill_DisplayCooltimeMSAsSec\n        {\n            get { return checkBoxX13.Checked; }\n            set { checkBoxX13.Checked = value; }\n        }\n\n        [Link]\n        public bool Skill_DisplayPermyriadAsPercent\n        {\n            get { return checkBoxX14.Checked; }\n            set { checkBoxX14.Checked = value; }\n        }\n\n        [Link]\n        public bool Skill_IgnoreEvalError\n        {\n            get { return checkBoxX15.Checked; }\n            set { checkBoxX15.Checked = value; }\n        }\n\n        [Link]\n        public bool DamageSkin_ShowDamageSkinID\n        {\n            get { return chkShowDamageSkinID.Checked; }\n            set { chkShowDamageSkinID.Checked = value; }\n        }\n\n        [Link]\n        public bool DamageSkin_ShowDamageSkin\n        {\n            get { return chkShowDamageSkin.Checked; }\n            set { chkShowDamageSkin.Checked = value; }\n        }\n\n        [Link]\n        public bool DamageSkin_UseMiniSize\n        {\n            get { return chkUseMiniSize.Checked; }\n            set { chkUseMiniSize.Checked = value; }\n        }\n\n        [Link]\n        public bool DamageSkin_AlwaysUseMseaFormat\n        {\n            get { return chkAlwaysUseMseaFormat.Checked; }\n            set { chkAlwaysUseMseaFormat.Checked = value; }\n        }\n\n        [Link]\n        public bool DamageSkin_DisplayUnitOnSingleLine\n        {\n            get { return chkDisplayUnitOnSingleLine.Checked; }\n            set { chkDisplayUnitOnSingleLine.Checked = value; }\n        }\n\n        [Link]\n        public long DamageSkin_DamageSkinNumber\n        {\n            get { return long.TryParse(txtDamageSkinNumber.Text, out long val) ? val : 0; }\n            set { txtDamageSkinNumber.Text = value.ToString(); }\n        }\n\n        [Link]\n        public bool Gear_ShowID\n        {\n            get { return checkBoxX3.Checked; }\n            set { checkBoxX3.Checked = value; }\n        }\n\n        [Link]\n        public bool Gear_ShowWeaponSpeed\n        {\n            get { return checkBoxX4.Checked; }\n            set { checkBoxX4.Checked = value; }\n        }\n\n        [Link]\n        public bool Item_ShowID\n        {\n            get { return checkBoxX5.Checked; }\n            set { checkBoxX5.Checked = value; }\n        }\n\n        [Link]\n        public bool Gear_ShowLevelOrSealed\n        {\n            get { return checkBoxX6.Checked; }\n            set { checkBoxX6.Checked = value; }\n        }\n\n        [Link]\n        public bool Gear_ShowMedalTag\n        {\n            get { return checkBoxX11.Checked; }\n            set { checkBoxX11.Checked = value; }\n        }\n\n\n        [Link]\n        public bool Recipe_ShowID\n        {\n            get { return checkBoxX7.Checked; }\n            set { checkBoxX7.Checked = value; }\n        }\n\n        [Link]\n        public bool Item_LinkRecipeInfo\n        {\n            get { return checkBoxX8.Checked; }\n            set { checkBoxX8.Checked = value; }\n        }\n\n        [Link]\n        public bool Item_LinkRecipeItem\n        {\n            get { return checkBoxX9.Checked; }\n            set { checkBoxX9.Checked = value; }\n        }\n\n        [Link]\n        public bool Item_ShowNickTag\n        {\n            get { return checkBoxX12.Checked; }\n            set { checkBoxX12.Checked = value; }\n        }\n\n        private void txtDamageSkinNumber_TextChanged(object sender, EventArgs e)\n        {\n            this.buttonX1.Enabled = !(string.IsNullOrEmpty(txtDamageSkinNumber.Text) || txtDamageSkinNumber.Text == \"0\");\n\n            string digitsOnly = new string(txtDamageSkinNumber.Text.Where(char.IsDigit).ToArray());\n\n            if (txtDamageSkinNumber.Text != digitsOnly)\n            {\n                int cursorPos = txtDamageSkinNumber.SelectionStart;\n                txtDamageSkinNumber.Text = digitsOnly;\n                txtDamageSkinNumber.SelectionStart = Math.Min(cursorPos, txtDamageSkinNumber.Text.Length);\n            }\n        }\n\n        public void Load(CharaSimConfig config)\n        {\n            var linkProp = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)\n                .Where(prop => prop.GetCustomAttributes(typeof(LinkAttribute), false).Length > 0);\n\n            foreach (var prop in linkProp)\n            {\n                string[] path = prop.Name.Split('_');\n                try\n                {\n                    var configGroup = config.GetType().GetProperty(path[0]).GetValue(config, null);\n                    var configPropInfo = configGroup.GetType().GetProperty(path[1]);\n                    var value = configPropInfo.GetGetMethod().Invoke(configGroup, null);\n                    prop.GetSetMethod().Invoke(this, new object[] { value });\n                }\n                catch { }\n            }\n        }\n\n        public void Save(CharaSimConfig config)\n        {\n            var linkProp = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)\n                .Where(prop => prop.GetCustomAttributes(typeof(LinkAttribute), false).Length > 0);\n\n            foreach (var prop in linkProp)\n            {\n                string[] path = prop.Name.Split('_');\n                try\n                {\n                    var configGroup = config.GetType().GetProperty(path[0]).GetValue(config, null);\n                    var configPropInfo = configGroup.GetType().GetProperty(path[1]);\n                    var value = prop.GetGetMethod().Invoke(this, null);\n                    configPropInfo.GetSetMethod().Invoke(configGroup, new object[] { value });\n                }\n                catch { }\n            }\n        }\n\n        private sealed class LinkAttribute : Attribute\n        {\n        }\n    }\n\n    public enum DefaultLevel\n    {\n        Level0 = 0,\n        Level1 = 1,\n        LevelMax = 2,\n        LevelMaxWithCO = 3,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/FrmQuickViewSetting.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2/FrmUpdater.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class FrmUpdater\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmUpdater));\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.labelX2 = new DevComponents.DotNetBar.LabelX();\n            this.labelX3 = new DevComponents.DotNetBar.LabelX();\n            this.lblCurrentVer = new DevComponents.DotNetBar.LabelX();\n            this.lblLatestVer = new DevComponents.DotNetBar.LabelX();\n            this.lblUpdateContent = new DevComponents.DotNetBar.LabelX();\n            this.buttonX1 = new DevComponents.DotNetBar.ButtonX();\n            this.richTextBoxEx1 = new DevComponents.DotNetBar.Controls.RichTextBoxEx();\n            this.chkEnableAutoUpdate = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.elementStyle1 = new DevComponents.DotNetBar.ElementStyle();\n            this.SuspendLayout();\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(12, 9);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(56, 16);\n            this.labelX1.TabIndex = 0;\n            this.labelX1.Text = \"当前版本:\";\n            // \n            // labelX2\n            // \n            this.labelX2.AutoSize = true;\n            // \n            // \n            // \n            this.labelX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX2.Location = new System.Drawing.Point(11, 33);\n            this.labelX2.Name = \"labelX2\";\n            this.labelX2.Size = new System.Drawing.Size(81, 16);\n            this.labelX2.TabIndex = 1;\n            this.labelX2.Text = \"最新版本:\";\n            // \n            // labelX3\n            // \n            this.labelX3.AutoSize = true;\n            // \n            // \n            // \n            this.labelX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX3.Location = new System.Drawing.Point(11, 57);\n            this.labelX3.Name = \"labelX3\";\n            this.labelX3.Size = new System.Drawing.Size(50, 16);\n            this.labelX3.TabIndex = 2;\n            this.labelX3.Text = \"版本信息:\";\n            // \n            // lblCurrentVer\n            // \n            this.lblCurrentVer.AutoSize = true;\n            // \n            // \n            // \n            this.lblCurrentVer.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblCurrentVer.Location = new System.Drawing.Point(110, 9);\n            this.lblCurrentVer.Name = \"lblCurrentVer\";\n            this.lblCurrentVer.Size = new System.Drawing.Size(13, 16);\n            this.lblCurrentVer.TabIndex = 4;\n            this.lblCurrentVer.Text = \"-\";\n            // \n            // lblLatestVer\n            // \n            this.lblLatestVer.AutoSize = true;\n            // \n            // \n            // \n            this.lblLatestVer.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblLatestVer.Location = new System.Drawing.Point(110, 33);\n            this.lblLatestVer.Name = \"lblLatestVer\";\n            this.lblLatestVer.Size = new System.Drawing.Size(13, 16);\n            this.lblLatestVer.TabIndex = 5;\n            this.lblLatestVer.Text = \"-\";\n            // \n            // lblUpdateContent\n            // \n            this.lblUpdateContent.AutoSize = true;\n            // \n            // \n            // \n            this.lblUpdateContent.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.lblUpdateContent.Location = new System.Drawing.Point(110, 57);\n            this.lblUpdateContent.Name = \"lblUpdateContent\";\n            this.lblUpdateContent.Size = new System.Drawing.Size(13, 16);\n            this.lblUpdateContent.TabIndex = 6;\n            this.lblUpdateContent.Text = \"正在检查更新...\";\n            // \n            // buttonX1\n            // \n            this.buttonX1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            // this.buttonX1.DialogResult = System.Windows.Forms.DialogResult.OK;\n            this.buttonX1.Location = new System.Drawing.Point(110, 187);\n            this.buttonX1.Name = \"buttonX1\";\n            this.buttonX1.Size = new System.Drawing.Size(85, 23);\n            this.buttonX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX1.TabIndex = 8;\n            this.buttonX1.Enabled = false;\n            this.buttonX1.Text = \"更新\";\n            this.buttonX1.Click += new System.EventHandler(this.buttonX1_Click);\n            // \n            // richTextBoxEx1\n            // \n            // \n            // \n            // \n            this.richTextBoxEx1.BackgroundStyle.Class = \"RichTextBoxBorder\";\n            this.richTextBoxEx1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.richTextBoxEx1.Location = new System.Drawing.Point(12, 81);\n            this.richTextBoxEx1.Name = \"richTextBoxEx1\";\n            this.richTextBoxEx1.Rtf = \"{\\\\rtf1\\\\ansi\\\\ansicpg936\\\\deff0\\\\deflang1033\\\\deflangfe1042{\\\\fonttbl{\\\\f0\\\\fnil\\\\fcharset\" +\n    \"129 \\\\\\'b5\\\\\\'b8\\\\\\'bf\\\\\\'f2;}}\\r\\n\\\\viewkind4\\\\uc1\\\\pard\\\\lang1042\\\\f0\\\\fs18\\\\par\\r\\n}\\r\\n\";\n            this.richTextBoxEx1.Size = new System.Drawing.Size(280, 100);\n            this.richTextBoxEx1.ReadOnly = true;\n            this.richTextBoxEx1.TabIndex = 9;\n            // \n            // elementStyle1\n            // \n            this.elementStyle1.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.elementStyle1.Name = \"elementStyle1\";\n            this.elementStyle1.TextColor = System.Drawing.SystemColors.ControlText;\n            // \n            // chkEnableAutoUpdate\n            // \n            this.chkEnableAutoUpdate.AutoSize = true;\n            this.chkEnableAutoUpdate.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkEnableAutoUpdate.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkEnableAutoUpdate.Location = new System.Drawing.Point(12, 189);\n            this.chkEnableAutoUpdate.Name = \"chkEnableAutoUpdate\";\n            this.chkEnableAutoUpdate.Size = new System.Drawing.Size(170, 23);\n            this.chkEnableAutoUpdate.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkEnableAutoUpdate.TabIndex = 10;\n            this.chkEnableAutoUpdate.Text = \"自动检查更新\";\n            this.chkEnableAutoUpdate.CheckedChanged += new System.EventHandler(this.chkEnableAutoUpdate_CheckedChanged);\n            // \n            // FrmUpdater\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(304, 221);\n            this.Controls.Add(this.richTextBoxEx1);\n            this.Controls.Add(this.buttonX1);\n            this.Controls.Add(this.lblUpdateContent);\n            this.Controls.Add(this.lblLatestVer);\n            this.Controls.Add(this.lblCurrentVer);\n            this.Controls.Add(this.labelX3);\n            this.Controls.Add(this.labelX2);\n            this.Controls.Add(this.labelX1);\n            this.Controls.Add(this.chkEnableAutoUpdate);\n            this.DoubleBuffered = true;\n            this.Font = new System.Drawing.Font(\"Microsoft Sans Serif\", 8F);\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n            this.Icon = ((System.Drawing.Icon)(resources.GetObject(\"$this.Icon\")));\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"FrmUpdater\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"更新程序\";\n            this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FrmUpdater_FormClosed);\n            this.Load += new System.EventHandler(this.FrmUpdater_Load);\n            this.ResumeLayout(false);\n            this.PerformLayout();\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.LabelX labelX2;\n        private DevComponents.DotNetBar.LabelX labelX3;\n        private DevComponents.DotNetBar.LabelX lblCurrentVer;\n        private DevComponents.DotNetBar.LabelX lblLatestVer;\n        private DevComponents.DotNetBar.LabelX lblUpdateContent;\n        private DevComponents.DotNetBar.ButtonX buttonX1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkEnableAutoUpdate;\n        private DevComponents.DotNetBar.Controls.RichTextBoxEx richTextBoxEx1;\n        private DevComponents.DotNetBar.ElementStyle elementStyle1;\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmUpdater.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.IO;\nusing System.Reflection;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing WzComparerR2.Config;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2\n{\n    public partial class FrmUpdater : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmUpdater() : this(new Updater())\n        {\n        }\n\n        public FrmUpdater(Updater updater)\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n\n            this.Updater = updater;\n            this.lblCurrentVer.Text = updater.CurrentVersionString;\n        }\n\n        public Updater Updater { get; set; }\n\n        public bool EnableAutoUpdate\n        {\n            get { return chkEnableAutoUpdate.Checked; }\n            set { chkEnableAutoUpdate.Checked = value; }\n        }\n\n        private CancellationTokenSource cts;\n\n        private async void FrmUpdater_Load(object sender, EventArgs e)\n        {\n            var updater = this.Updater;\n            if (!updater.LatestReleaseFetched)\n            {\n                using var cts = new CancellationTokenSource();\n                this.cts = cts;\n                try\n                {\n                    await updater.QueryUpdateAsync(cts.Token);\n                    \n                }\n                catch (TaskCanceledException)\n                {\n                    return;\n                }\n                catch (Exception ex)\n                {\n                    this.lblUpdateContent.Text = \"更新检查失败\";\n                    this.AppendText(ex.Message + \"\\r\\n\" + ex.StackTrace, Color.Red);\n                    return;\n                }\n                finally\n                {\n                    this.cts = null;\n                }\n            }\n\n            if (updater.LatestReleaseFetched)\n            {\n                this.lblLatestVer.Text = updater.LatestVersionString;\n                this.AppendText(updater.Release?.CreatedAt.ToLocalTime().ToString() + \"\\r\\n\" + updater.Release?.Body, Color.Black);\n                this.richTextBoxEx1.SelectionStart = 0;\n                if (updater.UpdateAvailable)\n                {\n                    this.buttonX1.Enabled = true;\n                    this.lblUpdateContent.Text = \"更新可用\";\n                }\n                else\n                {\n                    this.lblUpdateContent.Text = \"已是最新版本\";\n                }\n            }\n        }\n\n        private void buttonX1_Click(object sender, EventArgs e)\n        {\n            var updater = this.Updater;\n            if (!updater.UpdateAvailable)\n            {\n                MessageBoxEx.Show(this, \"没有获取到更新，请重试。\", \"提示\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n                return;\n            }\n\n            var runtimeVer = Environment.Version.Major;\n            var asset = runtimeVer switch\n            {\n                4 => updater.Net462Asset,\n                6 => updater.Net6Asset,\n                8 => updater.Net8Asset,\n                10 => updater.Net10Asset,\n                _ => null,\n            };\n\n            if (asset == null)\n            {\n                MessageBoxEx.Show(this, $\"没有找到.Net {runtimeVer}的对应版本，请手动下载最新版。\", \"提示\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n                return;\n            }\n\n            if (this.cts != null)\n            {\n                MessageBoxEx.Show(this, \"已有另一个任务正在运行，请稍后再试。\", \"提示\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n                return;\n            }\n\n            using var cts = new CancellationTokenSource();\n            this.cts = cts;\n            this.buttonX1.Enabled = false;\n            this.lblUpdateContent.Text = \"正在下载更新...\";\n\n            try\n            {\n                string savePath = Path.Combine(Application.StartupPath, \"update.zip\");\n                var result = ProgressDialog.Show(this, \"下载更新中..\", \"Updater\", true, true, async (ctx, cancellationToken) =>\n                {\n                    cancellationToken.Register(() => cts.Cancel());\n                    \n                    try\n                    {\n                        await updater.DownloadAssetAsync(asset, savePath, (downloaded, total) =>\n                        {\n                            if (total > 0)\n                            {\n                                if (ctx.Progress == 0)\n                                {\n                                    ctx.ProgressMin = 0;\n                                    ctx.ProgressMax = (int)total;\n                                }\n                                ctx.Progress = (int)downloaded;\n                                ctx.Message = $\"已下载 {(1.0 * downloaded / total):P1}\";\n                            }\n                            else\n                            {\n                                ctx.Message = $\"已下载 {downloaded:N0}\";\n                            }\n                        }, cts.Token);\n                    }\n                    catch (TaskCanceledException)\n                    {\n                        this.lblUpdateContent.Text = \"更新取消\";\n                        throw;\n                    }\n                    catch (Exception ex)\n                    {\n                        this.lblUpdateContent.Text = \"更新下载失败\";\n                        this.AppendText(ex.Message + \"\\r\\n\" + ex.StackTrace, Color.Red);\n                        throw;\n                    }\n                });\n\n                if (result == DialogResult.OK)\n                {\n                    if (DialogResult.OK == MessageBoxEx.Show(this, \"新版本已下载完成，点击确认更新。\", \"提示\", MessageBoxButtons.OKCancel, MessageBoxIcon.Information))\n                    {\n                        this.ExecuteUpdater(savePath);\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                this.lblUpdateContent.Text = \"更新失败\";\n                AppendText(ex.Message + \"\\r\\n\" + ex.StackTrace, Color.Red);\n            }\n            finally\n            {\n                this.cts = null;\n                if (!this.IsDisposed)\n                {\n                    this.buttonX1.Enabled = true;\n                }\n            }\n        }\n\n        private void ExecuteUpdater(string assetFileName)\n        {\n            string wcR2Folder = Application.StartupPath;\n            ExtractResource(\"WzComparerR2.WzComparerR2.Updater.exe\", Path.Combine(wcR2Folder, \"WzComparerR2.Updater.exe\"));\n#if NET6_0_OR_GREATER\n            ExtractResource(\"WzComparerR2.WzComparerR2.Updater.deps.json\", Path.Combine(wcR2Folder, \"WzComparerR2.Updater.deps.json\"));\n            ExtractResource(\"WzComparerR2.WzComparerR2.Updater.dll\", Path.Combine(wcR2Folder, \"WzComparerR2.Updater.dll\"));\n            ExtractResource(\"WzComparerR2.WzComparerR2.Updater.dll.config\", Path.Combine(wcR2Folder, \"WzComparerR2.Updater.dll.config\"));\n            ExtractResource(\"WzComparerR2.WzComparerR2.Updater.runtimeconfig.json\", Path.Combine(wcR2Folder, \"WzComparerR2.Updater.runtimeconfig.json\"));\n#else\n            ExtractResource(\"WzComparerR2.WzComparerR2.Updater.exe.config\", Path.Combine(wcR2Folder, \"WzComparerR2.Updater.exe.config\"));\n#endif\n            RunProgram(\"WzComparerR2.Updater.exe\", \"\\\"\" + assetFileName + \"\\\"\");\n        }\n\n        private void RunProgram(string url, string argument)\n        {\n#if NET6_0_OR_GREATER\n            Process.Start(new ProcessStartInfo\n            {\n                UseShellExecute = true,\n                FileName = url,\n                Arguments = argument,\n            });\n#else\n            Process.Start(url, argument);\n#endif\n        }\n\n        private void AppendText(string text, Color color)\n        {\n            this.richTextBoxEx1.SelectionStart = this.richTextBoxEx1.TextLength;\n            this.richTextBoxEx1.SelectionLength = 0;\n\n            this.richTextBoxEx1.SelectionColor = color;\n            this.richTextBoxEx1.AppendText(text);\n            this.richTextBoxEx1.SelectionColor = this.richTextBoxEx1.ForeColor;\n        }\n\n        private void ExtractResource(string resourceName, string outputPath)\n        {\n            var assembly = Assembly.GetExecutingAssembly();\n\n            using Stream? resourceStream = assembly.GetManifestResourceStream(resourceName);\n            if (resourceStream == null)\n                throw new InvalidOperationException($\"Resource not found: {resourceName}\");\n\n            Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);\n\n            using FileStream fileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write);\n            resourceStream.CopyTo(fileStream);\n        }\n\n        private void chkEnableAutoUpdate_CheckedChanged(object sender, EventArgs e)\n        {\n            var config = WcR2Config.Default;\n            config.EnableAutoUpdate = chkEnableAutoUpdate.Checked;\n            ConfigManager.Save();\n        }\n\n        public void LoadConfig(WcR2Config config)\n        {\n            this.EnableAutoUpdate = config.EnableAutoUpdate;\n        }\n\n        private void FrmUpdater_FormClosed(object sender, System.Windows.Forms.FormClosedEventArgs e)\n        {\n            if (this.cts != null)\n            {\n                this.cts.Cancel();\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2/FrmUpdater.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <metadata name=\"labelX1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 17</value>\n  </metadata>\n  <metadata name=\"labelX2.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>112, 17</value>\n  </metadata>\n  <metadata name=\"labelX3.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>207, 17</value>\n  </metadata>\n  <metadata name=\"lblAsmVer.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>302, 17</value>\n  </metadata>\n  <metadata name=\"lblFileVer.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>414, 17</value>\n  </metadata>\n  <metadata name=\"lblCopyright.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>520, 17</value>\n  </metadata>\n  <metadata name=\"buttonX1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>645, 17</value>\n  </metadata>\n  <metadata name=\"advTree1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>752, 17</value>\n  </metadata>\n  <metadata name=\"lblClrVer.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>858, 17</value>\n  </metadata>\n  <metadata name=\"labelX4.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>960, 17</value>\n  </metadata>\n  <assembly alias=\"System.Drawing\" name=\"System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />\n  <data name=\"$this.Icon\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n    <value>\n        AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAABMLAAATCwAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAKAIPFJ0aQlz0EjRN8RtCXfATOlbwDjBK8BY+W/AWPl3wDStD8AIPG/IAFSfsAQUJhAEA\n        AUoAAAACAAAAAAAAAAAAAAAAAAAAAAEJDWMLNFXzCi9P8govTvALMlHwCjJQ8As1U/MGJTvkAAEDcAAA\n        AGgAAQJqAAAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAALxxPcuEkdbH/QJze/y2Cxf9Epun/MpTf/yN9vf84n+r/OaDp/yV9\n        wf8aX47/Ay5T/wU3VP8FMlf6BhckwAABACcAAAAAAAAAAAAAABYKMU3MGoDH/xVqqf8RaKb/EWmq/x+F\n        zv8bgtH/GHa7/w9Vh/8JPWX/AB83/wAVJ+MADxS/AAAAVAABAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMHC4Mzj9T/KofI/zuPzv8re7X/QJvc/y6L\n        0f8hdrH/NJTb/zWW2/8hdbP/MY/U/ws4Xf8BM1j/BkBo/wlDcf8CCRGQAAAAAAAAAAABCQ5tElqU/xlu\n        sf8ff8b/GXvB/xZ1uf8ca6b/IIHE/xp8xv8Vb6//Gni5/xNbkP8DLkv/ADBS/wAmQP8AChCDAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4TN03MNpjg/yd/\n        v/87kM//K326/0Cb3P8ui9H/IXaz/zWV2/8tiMz/MIG+/zmb5v8gaJz/ASlL/wIyWf8GRXD/BSM76QAA\n        AAsAAAAACic8yBhvtP8ge7z/FnW8/xh3v/8bfcT/FHK1/xZno/8de8T/GHS6/xNjnv8eg9L/EViQ/wAp\n        Qv8ANFf/AB43+wAAADkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAArHVWB9zmf7P8nfr3/O5DQ/yt8uf9Am9z/LovP/yF2tP82l93/KILC/ziKxv87n+T/JXq5/wAn\n        Q/8ENV3/Bjxj/wcwU/4AAABIAAAALx9Wgewnf7z/MpPY/yiJz/8fdrb/HG+v/yCAx/8SZqT/GnGy/xh6\n        vv8VaKL/HHq8/xl6xP8LQ27/ACpI/wEzVP8FGinWAAAAJwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAIHjiJ0rv84nOX/Jny+/zqMy/8rf7X/QJng/y6N0f8hda//Npfd/yqC\n        w/8nf73/NZXa/yZ8vP8RSG7/ADBS/wE2XP8EOl7/AAMEdAMFCFohbqX/M4zM/zaW3f8uicn/OIvH/zCJ\n        x/8kfb7/G3Oz/xZmo/8ef8v/E2al/yB5uP8agMr/EF6Y/wQzVf8ALEr/ATde/wEaMNgAAAAoAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQoiM7oke7v/NZfe/ymAu/8xjc7/KXm2/z+c\n        3P8tjM//I3i5/zSZ3v8pgcP/I3u8/zGP1v8kfbv/KHSs/wAkQ/8EOF//BTld/wAJDrIUNFDNKYbI/yiC\n        wf8vjNL/Knq2/zuZ3/8zktj/I3qz/y6Jz/8kd7L/IoHE/xVxtv8RZJz/GnjC/xRqqP8OW5T/ACdC/wAv\n        U/8FO2P/ARwx2wAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAlkcYpL/LInI/zCR\n        2f8sfrb/QJ3i/yh3tf9Dmt3/NZPZ/yiHzv89mNz/LoC6/z+Z2P8rh8r/Kn++/zGN1v8KOFr/ADFV/wQ5\n        X/8CLUz/G1mH/y6FxP87ltv/KobE/yt+vv8zltz/NZbb/ymCxP8ogsL/KXe5/zCO0f8ng8j/Gmmf/yGD\n        yv8WbrP/E2qn/w9Rgf8AJkH/ADVc/wY+Z/8BGSXSAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAADRiwKoPH/y5/vP9Gm9z/L3u3/1Gs7f83gbn/WLDo/06o5/8xjtH/Vq3t/ziEvP9huPD/LXm2/0eb\n        2/9PrO//FUZr/wAsUf8CNVn/BjNT/y2Jy/8terv/QaLk/yBxr/82ldj/M5HV/zea4v8th8v/KYLD/yZ8\n        uf8ti8z/N5Xe/yl/vv8bcbb/FnCz/xZnpv8cf8P/BjJX/wIuUf8FO2L/BjdV/wEIDHQAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAuEj1e4UKY1/9HnNr/Lnu2/0GSz/9Uru//OYnI/2Ky7P9Amt3/RZ/f/1Ss\n        6f86hbz/Ybbw/zOLyP9IoeP/WbT1/x9gkf8AKkn/ACpM/x9fkP84l+P/LH22/0Cc4v8ec7D/L47R/zaV\n        3/8oerT/E0Zx/yuExP8wjdL/Ina0/zeZ3v8pgML/H3S0/xp3vv8VZqT/G3vD/xRspf8AJ0H/AzFX/wdA\n        av8FN17/AQgPcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMFVyxun/8+ktH/VrLz/0uj4v9Sq+r/U6zs/zaL\n        yP9etOz/M5HU/0yi4/9Xs/X/O5fX/2O08P8zktX/SaPk/1Gr7f81gsD/HEVu/wYpRv9Pot//T6rq/zN+\n        t/87n+P/H3Ox/zGP0/82mdz/CzZZ/wAiO/8lcaj/MZDY/yN4tP83l97/KILA/yuCwf8qjdf/EV+Z/x13\n        u/8afcn/DE17/wIrSP8EOmP/CEh0/wk+a/8CCA90AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhsyQblBm9f/PZDQ/1Wv\n        7f9Rqur/U6fr/0yl5f8+ltv/Vq3u/zmU2P9PqOr/O36v/zCDw/9jt/P/NZHT/12s7P9Oqej/N4O9/0iS\n        yf83e63/U7Dz/1Gp6P8xfbf/Ua3t/zF/uf88m9//Kn++/wU0U/8BLVT/D0Jk/zCN0/8nd7X/N5jj/yZ/\n        u/86jcz/OJvf/yR4uf8eerj/GnrC/xl3vP8FNFX/ATNW/wlAav8JTID/CT1m/wAJDHUAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        ACMfSmndTazu/0KZ3f9Uq+j/Uarq/1St6/88mNj/UqHh/1qw8P83mN7/I2eX/wAZMv8nb6T/Zr79/zmW\n        1/9So+T/Tqnm/zeDtv9iuvL/Uq7w/1Kq6v9Qpef/L327/1iv7/8zfrj/SKfq/x5ik/8AKEX/AzZe/wIw\n        TP8eaZ//JX/A/zaX3/8rhcT/M4bD/zWV3P8yjtP/Gmyn/xp6v/8YgMf/E2Wj/wEoR/8EPGD/CUR0/wpS\n        iP8FJD3gAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAFDhaFJny6/1qz9P8sjtH/U6jn/06p5f9Sq+r/Qp/f/02g4P9kvvv/Mnqr/wAi\n        Qf8ALFD/IlR4/2K98/9HouX/L47S/1av8P9Ajsr/QpbQ/1Ou7v9Pqur/UKXl/y98tv9Tru//NYXC/0qc\n        2/8RRGn/ADBU/wU5YP8CKUn/BiI3/yJ5uf81ltr/NJHZ/yFysf81lNj/LozP/yd8t/8ritP/FnW6/xuA\n        yP8MS3r/Ai5Q/whAbP8ISXn/CEx//wMgNtAAAAAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdJVFx6Tia4v9Xru3/RZze/0+m4/9quvT/bLrz/1Go\n        4/9YrOb/e8P7/x5HZf8ALlb/ATdb/wswTf9aoMf/Y7vy/z6W2f9etPH/PpPR/zmKyP9Vr/D/Uqrn/1Cp\n        6/8zicj/Uq7u/0me3v8scqr/CjVS/wMzWv8DOmD/AB0x5QAAAHAcVoX0N57m/zKV1v8qerb/QZze/yyJ\n        z/8wf7v/OJvb/xp5wf8YecD/G3u//wc7Yf8DOlz/CUJz/wlOgv8KTH7/Bh0xywACAjsAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgYIdkeYz/9AneP/XrHs/2Sx\n        7f9Hn9v/fcX6/3TB+P9Al9j/fMj//02LtP8AJkb/ATZd/wQ4XP8AKlH/Llx4/3rI+/9Kod3/XK/u/0Cd\n        2/9Ind7/Vq7r/1Cq6f9QqOj/MZDT/1et7v9KpeT/CUNq/wAvU/8GOmX/BTxm/wALEJEAAAAADCQ1sTOS\n        1/8yk9r/KXe3/0Cd3P8zk9r/JHe5/yyJyv8phcz/Fne8/xl/yP8PW5T/AS1O/wdDav8JRnn/CU6A/wlH\n        dv8EGCW8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAn\n        N69ot+//U6nl/2Cv5f9ywPb/Rp3e/3K89v9ywfb/P5zY/3C87/8cRWX/AC5U/wM2W/8FOWD/ACE16AIF\n        CZ9mocb5Tqno/2Sy7v9cr+n/TaHf/1at7P9Rqev/T6Tn/zCO0f9TrvD/S5/c/wY6Yv8GPGL/B0hy/wcn\n        Q+gAAAAwAAAAAAAAAEgkZpj/NZzj/yl6tf9Amdz/M5Ta/yR7tv82js//MZHX/xt5wP8aesL/EmWg/wUw\n        Vf8EO2D/CUV5/wpNfv8JSXr/BBkrvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAABY2XHrygNH//1+w6f9SpOD/fcb7/0Cb2v9ote7/dsD5/0Sd2v9mp9P/AyZC/wA3\n        XP8GNl3/AjVY/wAJE5gAAAAILUhYyW7C+f9Fnt3/dsH5/0Ob2v9bru3/Tqno/1Gp5/81kNT/W7Hw/0ia\n        2P8HNFb/BUNx/wtEdv8BChORAAAAAAAAAAAAAAABDSU5uTKU3P8qfLn/QJzd/zKS2/8qg8H/NYjI/zWV\n        2v8mg8n/GHjB/xZqqP8ZY5f/BTZb/wdDcP8MTIH/Bzlh/wIMF5MAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBwtwYaLQ/3XH//90wPj/PJfV/3jA9v9fsev/TqPh/1+v\n        5v9Xr+f/O2iK/wAsTP8ENV3/BDxg/wMnRv8AAABOAAAAAAMDBmFmoMz/UKrn/2S06/9MoeD/YrXq/1eu\n        7f9LpuT/PZTU/2e6+/8nZJb/Ajha/wpOg/8EK0XnAAAALAAAAAAAAAAAAAAAAAAAAEwjZZf6LYHD/zua\n        3P8xkdb/MpLV/x90rf81lNv/MJTX/xlzs/8Za6j/IIDF/wpIdv8DPWH/CUp//wQkOfAAAABCAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHSkvo3S+8/9yv/r/dMX4/1an\n        4/9vuOr/eMP6/0yj4f9Bmdf/ecr8/yxSc/8AKk7/BTZb/wE3X/8AEyLJAAAAFwAAAAAAAAAIMklh1Vuz\n        7P9Xqef/Y7Hr/0ui3f93xPr/Uqvs/zuY1/9dsfH/G099/wVDc/8JRnX/AwgOjQAAAAAAAAAAAAAAAAAA\n        AAAAAAAACyQ3sip8t/9AneL/MZDW/zGP0/8gdrL/M5PY/zaW2/8ieb//HG2o/yCEy/8Wb7L/BDxf/wlH\n        dv8EJDvyAAAASgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALDNn\n        gvZ/zP//db/4/3K/9/9tvvX/Vafh/3vC9v9vwPf/d8P8/2qw3P8RN1j/ADFW/wI2XP8DOWH/Ag4TrgAA\n        AAAAAAAAAAAAAAMDCGVgm8T/RaTg/2268v9Gnt3/db72/2u79f8+oOL/NH+8/wk9Yf8LU4v/AClB7QAA\n        ACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYcTXP6RKLr/zGT1/8xjdL/InSz/zWV2v80k9r/LIbJ/xxu\n        rP8fgMf/GnzG/w1Qhf8EPmb/ARky5QAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAACY0YYX2WbTu/3m/8/9xvvf/dcH4/1Kp5P+Ex/n/gsf8/5rd//93q8P/ACBD/wE4\n        X/8ENFr/B0Nv/wMPHLUAAAAAAAAAAAAAAAAAAAALLERd0kei5v96xfr/Qp3a/2m28f95w/r/SaXn/xpe\n        kv8JR3X/BTJX+wECA1YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEyk1sD6e4f8yk9n/L4zT/yx9\n        tP89md//MpPY/y6Ky/8hdLD/Hn/F/xp8wv8Tb6//BTRW/wAEBX0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICglcYpe4/2i++v9WquP/f8X7/2++9/9qtvH/bLns/5zZ\n        //+f3P//Woae/wApTP8DMlr/BDtj/wc4XP8ACA6AAAAAAAAAAAAAAAAAAAAAAAkKDWdAiLv/YLLw/2W1\n        7v9In+D/hNT//1uw3/8AOmj/AzVV/AADBlsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AEMkZpX8Nprm/zSW2f8nfrz/K4bH/zWV3f8uh83/Ini0/x9/xv8ad8H/G3/I/wdCcv8AAAB4AAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADR4qql+06P96wfr/WK7q/3q/\n        9P+Nzf//iMz4/2i06/+V0f//nN3+/ydHYv8ALVT/BThf/wZDb/8GKEP6AAAAMgAAAAAAAAAAAAAAAAAA\n        AAAAAAASFzdSzVm48/9wwvb/R6Td/2Sev/89Y3foAAgUsgAFCV0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAABDCEyrDSW2v82l9//J3u9/yiAw/82mN3/LovM/yJ5tP8ghc//GH7L/xVo\n        pv8HKD/IAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkm\n        L6dLot3/abfv/2y78/9ir+f/n9f//5jV//9kse3/h8z9/5PR7P8eRWD/ACtV/wg+Yv8HRXP/AyhE+wAA\n        ADcAAAAAAAAAAAAAAAAAAAAAAAAAAAkLDlImZJDzOWOG5xEmNbkEBQZYAAAALAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0iYpL7OJ/o/yd/vf8pgsT/Npjc/zCM\n        1P8kfL//Fm2p/wYwS9MBCAtuAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAWJS2naLns/1Cl6P+Lzv7/abns/3/E8/+e2f//Ya/p/5TY//9pmLL/ACBE/wM4\n        X/8FQGv/CENw/wUnQ/sAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBQAAAAC8AAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABADMFAgF+LT9KwE5wifdKa4HwPGB++jVj\n        hv87ZYb/OWSD/wwwX/8DKFn/ATNZ/AEJDr4AAAJvAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEh0mom6+8P9csev/lNL8/3a+8v9yvPD/m9j//3rA\n        8v97yPz/a5ev/wAlSP8EPmj/B0Bp/wZBcP8ELET7AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBMfI5lLdI7zUZ3W/3q8\n        7P+n4///od///6bh//+t5v//iNX//3zJ//9vvv3/Xa7x/yd0yP8CWqn/AFua/gAlP9IAAgJdAAAAEQAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGjJOYs17yvz/Wq3s/4vL\n        +P92v/H/dL7w/5vU//+Mzvz/YbLl/ydKZv8AK1P/CD5k/wdBa/8HQXD/AyxE+wAAADcAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAaEBwjpEh0\n        h+5osN7/OJjq/yaT7P8ikvT/I5Pu/x2N8v9FpPH/YrT1/6fW+v+h1v//dcD//4LF//+DzP//So7V/wBp\n        wP8Aes//AFmc/wAhN7EAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AEVSjaj/hNL//4jM+P9os+r/ldL9/2Ox6f+W1P3/isz9/2i45/8iQlz/ADFZ/wg/af8IQGr/B0Rx/wUp\n        QvsAAAA3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAABWJTpPyXXA9P+E0P//MpDg/w11z/8pmPL/LJbw/yiX8v8rlfD/JJPy/xqM8f84nO//m9T7/3/E\n        //92w///ecL//4LN/v9Eis//AGG0/wB/1f8Abbv/AB0usgAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAABCTYSl/pHZ//+Rz/v/Xq/q/5zY/v9drun/ldP9/43P//9Xos7/GDxY/wA4\n        Xv8GPmj/B0Bq/wVDbv8EKET7AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAANFBt6WZOx/XjG9P+Cyv//Wqzy/wluyP8lj+j/LZfz/yqY8P8rlPH/K5jw/yuV\n        8v8tl+7/F47y/4fF9/+Qz///db3//3vC//96wP//g8f//0uQ0v8CZLj/AHnN/wBntf8ACA6LAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQlSJpv6Y3v//jM37/2Ky6f+a1f7/Xq7n/5XP\n        /P+T2v//JWKU/wAkRv8IPmn/B0Fq/wc/Z/8IQXH/BjFL+wAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJERZ0X53F/4HT//9+wv//gMf//zOY7P8GaMP/FoHY/y6b\n        9P8rlfP/K5Xx/y+V8f8qmPP/J5Xv/yGR8f+Ty/n/isr//3a+/v93wv3/ecD//3vG//9qrur/EFep/wVg\n        sP8EacL/ADBY4gAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJUiKb+n9///4vM\n        +v9isOj/mdf+/2Kw6v+T0v3/k9f//yVmlP8AKkz/B0Jq/wc+aP8GP2j/B0h1/wcwUvsAAAA3AAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALFBV1bKDB/4TT//94vf//eML//33E\n        //80l+z/BGvF/w9xx/8bgd7/KJTu/yua8v8rk/D/J5Dv/ymY8v99vPj/lNH//3O8//98xPz/eL/8/4HF\n        //9Fn+T/E4LU/x6P5P8trPz/HY/p/xVyvP8EGCKOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAA9Yo6n/Z3j//+My/r/X7Hp/5rT/v9dsOj/lM/8/5TZ//8lZpn/AC1N/wg/av8GP2j/BD9p/wlK\n        f/8GMVH7AAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOf6Gj/qbk\n        //+g0vz/hsz//4nR//+Y2f//Tqrs/wRmwf8LccT/CG/E/w1uxf8Mb8r/Bm7L/zGV6P+o1v7/k8///3G+\n        //95wvv/eL77/3rB//97xvz/IozU/ySm9f8Xi+P/Kaj//yuq//8vuf//DDRM0gAAAAcAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAcm2Wp/+f3///jcv7/2Gy6f+a1P7/XrDo/5XS/f+U2P//JWuc/wEx\n        Vv8IQWv/Bj9m/whBbP8KUIX/BjBR+wAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAEDSpBu0up6/8ekvX/MZjw/7De+/+g5///nOb//5vh/v8ujtr/AmvJ/wdvyf8ahtj/LJDi/0yk\n        6/92v/7/esT//3zG//+Byf7/gsf//4HI//+Dxv//iMr+/yiK0f8Lgtb/HZrw/yyr//8qpP//KbX//w4z\n        TNIAAAAHAAAAAAAAAAAAAAAAAAAAAAADAE4AAAAAAAcBRwBGDftQhKX/oeL//4zN+v9gsOf/ndf+/1+u\n        6/+U0P3/lNj//yZrov8AM1T/BkFt/wY8af8IRXP/ClGF/wUtUPsAAAA3AAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAASQxPjP4hmPf/Mpv2/yCO8P+i1/r/pen//5zg//+d5P//kNr8/2bA\n        +f9WsPX/abj7/3/E//99x///gsf//3vB+/9Pjtb/PoHJ/zuZ3P88lt3/O5rd/0KY2/8ahtX/JaP5/yqn\n        //8opv//KrH//yeQz/8HEyGKAAAAAAAAAAAAAAAAAAAAAAAAAAABMwXcBSkQhwEHBXgJbz//LF6G/4nQ\n        //+KzPf/U6TY/47L+f9VqN7/lNH8/5TX//8hYpT/ADNa/wdAaf8HQWr/Ckh6/wlQhf8JL0/7AAAANwAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBYpMq4Mb8b/EHjT/yCK6f8OeNf/esfs/6Lp\n        //+b4f//m+T//5zk//+m6f//ktj//3nB//96wf//hcf//2ar6v8ZY7T/A2q5/wBow/8AcMX/AHjP/wB3\n        0v8AddX/E5ju/zCy//8prv//LrL//yOP0/8IFB2FAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAQUCUQ5X\n        NeARhz7/D5Yo/wc8C/8lbJv/QJri/yt0sP9Vruz/V6Ta/5bV/f+Mx/L/DT5f/wU+aP8GPmn/Bj9r/wlM\n        ff8KToL/BjFQ+wAAADcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAElcip79OJnm/wBj\n        v/8DacD/OZLV/5bd+f+g5P//l+H//5zh//+c4v//lt3//37G//92vv//fsf//2Sp6P8RZrf/AHfM/wB6\n        0P8AftL/AIDW/wB3zP8ARHXrACNB0wEmPtQQXZTuI5jj/x6Iwf8FGieLAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAMzHdIELhjNC24d/w6fJf8LjxT/DGxN/wxAc/8FQy3/JXq+/0Kb3f+Kzvr/b7jw/ws9\n        Yv8GPmf/BUBo/wlEcf8JSn3/C0t6/wYxUfoAAAAxAAAAAgAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAWJy2ykdn3/4rV/f9Vq+L/Z7jp/5/k//+g6v//meH//57k//+X5P//n+T//5Xd//94wf//e8D//37E\n        /f8jbLf/AHHK/wB8z/8AetD/AH3U/wBstf4AER+pAAAANwAAAAAAAAABAAEDSQIECYwAAgRoAAAABAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFAtaDGk6/A+hOP8Mkgr/CWYA/wyRIf8HPxr/Dmww/xJT\n        lf8ne73/W7Ly/1au7f8KOF3/Azpg/wc8Zv8LS33/BTpn/wc5av8BJUP8AQgAkAEiANEAAwBiAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAeRlVb0Jng/v+e6P//pez//6bp//+b5P//meD//5nh//+b4v//neL//5ni\n        //+GzP//dbz9/4DK//9cotn/AmC8/wB+0f8AeM//AHzS/wB5y/8AFiauAAAAAQAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsHLBTZBU8L/whm\n        Bv8IXQD/BEMC/wdWCf8EHhv/BhwY/w0+Yv8QSoX/AiVA/wAuUf8BM1r/AyhM/wEdGP8DHwv/BjoI/wpM\n        AP8DHwCnAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZpi1vv+g6P//nOL//5zh//+X3///neL//5vi\n        //+e4v//muP//5zi//+c4f//g83//3jB//96vfj/IHHA/wByyv8Aes7/AHzQ/wN90f8Fc8n/AAwWoAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe\n        Ar0ITCnKBEoY9AGFFf8PlT//B38C/wVaAP8ISxf/EJot/wNdAP8BMAD/BUUA/wMoB/8CDhH/AhUQ/wU3\n        BP8FQQD/BEgA/wZKAP8DQgD/AykAzwIkALgAAwAoAAAAAAAAAAAAAAAAAAAAAAAAACk5WWzbxfH//5jh\n        //+b5P//neD//5nh//+Z5P//muH//53i//+b4f//nOX//4TL//94w///crf2/wtftf8AfdP/AXvP/wV4\n        zv8Ae9L/AHfI/wAOGqMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAJAKXBlUt2g9MIfRbRAf/OS8K/weWLv8SpDb/E5o//wp4C/8GVwD/CXsA/wls\n        AP8JcgD/BDwA/wQxAP8FPQD/BkIA/wU+AP8EOQD/AzIA/wMiAPMHVwD0AS4A1AAAAA0AAAAAAAAAAAAA\n        AAAAAAAVMEFNyc7y/v+b4///mOD//5rl//+c4v//m+D//53k//+c4///md///53k//+HzP//fMH7/3e8\n        9/8QZLj/AHnO/wB70P8Dfc3/AHvV/wB2w/8ADROeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAEOqThTzemMq/w97Mv8JciH/DpQb/wVL\n        Av8Fagf/CEkS/wZbAP8GWwD/BEUA/wZEAP8DLAD/AygA/wMnAP8DIwD/BEUA/wg6AP8BFADEAAIARgAG\n        AG0AAAAMAAAAAAAAAAAAAAAADB03xJzK6//S9f//muD//53k//+a4f//muH//5rk//+a4f//muL//5nj\n        //+d5P//ldv//3zJ//92uvb/DF21/wB/0v8AfNL/AHfN/wB90v8CftX/ATtr4QAAAC0AAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAACsAAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAMOkCvToBt/wdb\n        S/8OcXb/Ah8F/wVNAP8FPQD/C20S/xKcQP8OjjT/CGwC/wRVAP8FIgD/B0gA/wdrAP8GUAD/BDAA/wQ0\n        AP8GUAD/B1QA/wACAF0AAAAAAAAAAAAAAAAAAAAAAAAAAGBeX/D5////xO///5ri//+Y3v//m+T//53h\n        //+a4///nOH//5vj//+c3v//meL//5vj//+P0v//dr36/zN+yf8AWLH/A2/D/wF+0v8Ad87/BIDY/wFu\n        uv8AEBuPAAAABAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAQgAfL7UAWIj/ACU5sgAAAAkAAAAAAAAAAAAA\n        AAAAAAAPB2R58ALd//8S0+v/QP///0KXnf8HWQP/BD8A/wZfAP8HcAL/C3sT/wM6Ef8Ljxv/A0QA/wdp\n        AP8KiQD/BWQA/whMAP8DNQD/BUoA/wQ8AP8DEwCcAAAAAAAAAAAAAAAAAAAAAAAAAABhXlzw9////8Ty\n        //+X3v//muP//53i//+X4f//nOP//5rg//+b4f//muH//53j//+j5///aLfn/xyB1P8wlen/KIDa/wZh\n        uv8AeM7/AHzR/wB3zf8Ag9z/AGWs/gAmQNIAAgNeAAAATwAAAEsADReCACZD2ABIfe4Adsf/Ap78/wOM\n        2/8ACRGLAAAAAAAAAAAAAAAAAAAABEI7I8jQ3rL/xv/0/7P///9wxcX/CVgb/wAvLv8COBT/CYIT/xOE\n        KP9CZV//FH5K/wVLNv8ARkD/BlEA/wg9AP8DOQD/BUoA/wdJAPwEJgDGASoA5wAGAGwAAAAAAAAAAAAA\n        AAAAAAAAYV5d8PP////T8P7/ruj+/5Xg//+a4f//nuD//5zk//+Z4v//nOL//5nj//+i6f//gs3s/xFx\n        yf8klfH/KZjx/zCh/P8Wccn/AGvB/wF80v8Ces7/AHjN/wOB2f8Agtn/AFyb/wBWlP8AVZD/AGWu/wCF\n        4P8AhN3/AnHI/wOM4P8Dofb/AlWD5AABAUIAAAAAAAAAAAAAAAAFAABaxoo7///ttv/b////RpeO/wFI\n        Iv8EYH7/AKe4/wNiT/8wl5T//7JP/21eHP8AVTD/AJjI/xCDtv9Qamn/BjAG/wIzAP8AAAB6AAAABAAB\n        ACsAAAArAAAAAAAAAAAAAAAAAAAAAGBcW+vw/v//3fT9/8ju/v+T3///muX//5ri//+Y4f//neH//5ni\n        //+c4v//n+b//zuW1/8IbMn/KJPu/yuX8/8rlfD/L5jx/xBsxP8BccX/BHzT/wB6z/8Fecz/AXnR/wCC\n        2f8Bgd7/AILb/wCB2/8AfND/AGjE/wNqwf8FnfT/BJbs/wSh+P8AV4TuAAgOcwAAAAAAAAAAAAAAACYU\n        AXmqtpH/Ov///0fq6v96oIz/UWFH/6rWqP9M4t//Tvz//8XktP/Zp1b/N0MZ/wiTtf8Xve7/C7To/wAW\n        EO4ANAD/ADMA4QADAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQDg5yvsHD/+f8///R9Pv/nuP//5ze\n        //+b4v//nuL//5vi//+Z5P//n9///5jg/f8Uds3/CWzD/xuF2/8unPb/LZbx/yqX9f8wmvP/DV24/wB4\n        zv8AfND/AH3T/wB/zv8AetL/Bn7P/wF50f8Accb/B2O6/zF3uv8ZbcD/AJXm/wOW7f8Cnff/AIXJ/wAb\n        J78AAAAAAAAAAAAAAAAAAAAAABkbdACAkPaAx7D/8r5e/4JHEv//qT//r9Gp/3L///99////zOjA//q+\n        aP9qtKj/r3M0/wil0v8ACg+CAAIAVQBEAOkAIwDJAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAYLm6\n        uv/i/f//3/P8/7zt//+U4P//m+H//5rj//+Z4P//nuD//57m//+X4f//KY3f/wZrw/8Nccr/H4fi/yqY\n        7/8umfX/K5v1/x9muv8IZrv/AGnB/wBqxP8AZ8D/AGvD/wBowv8IZ7z/M3bE/2aq4P+Ezv//erjt/xls\n        wP8Aluz/BZ72/wKGyf8AGie/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIkGBUMk4xMFPJSOhf4a6ON/kzy\n        8v+F////nf///3L//f+M6dr/J7rZ/1OhpP8GZX7xAAAAFwAAAAAAAwA7AAQAPwAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAC53dXbf6Pn//9r0/P/U9P//nuH//5ni//+c4f//meH//5nl//+Z4f//o+j//2S7\n        8f8Aasr/CGrA/whvw/8VeNT/GYDd/w1yz/9gsu3/WqHj/ziAzP9Dis7/QIfQ/0CIzf89gc3/ZKfm/3/K\n        //99xv//esD//4LL//9His//AH7W/wOl/v8DV4nzAAgNdwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAIAAAAhAAAAKgBcatIo/v//vfz1/8j78P9J+v7/C+b//y7J4/9Nrr//AzxJwwAAAAMAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANDEwt+b0+f/Y9f//2/L8/8nv+v+c5f//lt///5zj\n        //+X4f//nuD//5zi//+h5f//U7Pv/w50zf8BacT/AWTA/wlswv9WquD/nuL//63d//+c2f//e8b//4DJ\n        //+Ayf//gMj//3rF//90vP//f8X//33C//93wP//g8n+/0iP2f8Gd8X/ABorpAAAABgAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACg5VgJd87//ur//m++n/Iez//zfU4P/zvV3/vmEL/wIA\n        AFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgHB16trq//5////9jy\n        /P/c8/3/ze7+/6Pm/P+b4f//nuD//5Lk//+W4P//meP//6Lm//+D0fj/c8Du/3TD7P+H0PT/nef//1Og\n        2/9XpOL/f8b6/5HO//96v///fcr//4XL//+Jzf//i9T//5Ta//+W4f//gcr//3jC//99x///ETFf8QAA\n        ADkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8FAE6acCzu5M+T/xTp\n        //9OxL//umsb/BsIAHEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAOSkhJxO/8/v/a+v//2fH7/9v1/P/Y8fr/zfH+/8fv+/+76vz/uOj//6Xm//+V4f//neX//6Pq\n        //+i6///qe///2q66v8AW73/BnfU/xOC5P+j0fP/nN///5LY//+b5v//m+L//5ri//+n6P//rer//6bp\n        //+R0f//b7jx/w4YH6EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAANjMmE7gQXmr/ADxGxgMAAEoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUEBF+9vLv/5Pz//9fz+//Z8fr/2fb//9zz+v/b8fv/3/b+/9zy\n        /v/Y9Pr/wu///6nn//+e6f//M4bQ/2Sx6f+F0vH/C2rG/wBiwP8id8r/qNjw/6Dp//+b4f//neD//53k\n        //+f5v//hs/x/5vU+P+c0/r/rvD//1x9i/gAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHUk5O0vb+/v/l/v//2fL9/9rx\n        /f/Z9P3/2fP+/9ry+f/Y8/7/2vL9/+Dx/P/Y9Pr/tej//3jF7/+Gzfb/o+v//4HM8/9sv+j/kdLz/6Po\n        //+c4P//neX//5rh//+e6P//dcHr/xF0y/8fke7/Ipj9/ymCzP8kIyWMAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB\n        ATFYWFfawMjJ/977///Z9f3/2O/7/9r2/P/Z8v7/2fL9/9j1+v/Z9f7/2vP+/+D0///S9///ouf//5Tf\n        //+i5///oez//5zk//+V3///mOH//5fh//+b4///md/+/xqA0/8Gacb/Jpb3/yiGz/8IFB6LAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAADiIfH6Hq7e3/5P///+b9///l/v//6f///+P5///d9f//2fP5/9ry\n        /f/Z8vr/3PT8/8vv+v+i5P//n+j//6Hk//+m5v//o+f//6Xl//+c5P//nOL//5Xg/f8cfsz/BHHS/wxl\n        sP8FERyFAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAue3p64tve3P+FtNP/SprP/0qV\n        zf+dz+v/3vv//9/0/f/Z8v3/2fT6/9ny/P/a9P//1/L9/9Tw/v/X8f3/1vD+/9Xx/f/X8f3/ye3+/7Pr\n        //+88P//ltz6/xhosf8ADRmLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB\n        ADEJCAiEAgMHgQAABoAAAAB6ASI9wjp5rf/a9///3fb8/9fz/f/a8/7/2fL+/9ry+v/e9///4/7//9z8\n        ///e/P//5Pz8/9r1/v/o/P//6v3//73Lz/8YICKLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAdeUn2X7uz9///Z8///1vP5/9j0\n        ///d9/7/vOby/0aCrf8sdaj/OHql/5LC2f/o////vcvO/1phZd4NDQx2AAAABAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgm\n        JaDl6ej/9f///+/////x/////P///1Noc+AAAABWAAAAUAAAAEoDGSiaR1BX3xUWFHsAAAAeAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAzSklJ7IqKifOBgYHwg4KC8YOBgfYjISGFAAAAAAAAAAAAAAAAAAAABAAA\n        AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAA////////////8AAeAB/////gAAwAA////+AADAAD////wAAEAAH////AAAAAAP///8AAAAAA\n        f///gAAAAAA///+AAAAAAB///4AAAAAAH///AAAAAAAP//8AAAAAAAf//gAAAAAAA//+AAAAAAAB//4A\n        AAAAAAD//AAAAAAAAH/8AAAAAgAAf/gAAAACAAB/+AAAAAYAAH/4AAQABwAAf/gABAAPgAB/8AAOAA+A\n        AH/wAA4AH8AA//AADwA/wAD/8AAPAH/AAP/wAA+B/+AB//AAD8/+AAP/8AAP//gAAP/gAA//4AAAf+AA\n        D//AAAA/4AAP/4AAAD/gAA//AAAAH+AAD/4AAAAf4AAP/AAAAA/gAA/4AAAAD0AAD/gAAAAfAAAP8AAA\n        AB8AAA/wAAAAfwAAA/AAABB/AAAD4AAAP/+AAAPgAAB//wAAAeAAAH//AAAA4AAAf/+AAADgAAA/j4AA\n        A+AAAB4HAAAD4AAAAAcAAAHgAAAAA4AAAeAAAAABwAAH4AAAAAHgAAPgAAAAAfAAJ+AAAAAB+AA/8AAA\n        AAH/AH/wAAAAA/+A//AAAAAH/8H/+AAAAAf////4AAAAD/////wAAAAf/////gAAAB//////AAAAf///\n        //+AAAD///////wAAP///////wAD////////Ac////////////8=\n</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2/HistoryList.cs",
    "content": "﻿using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2\n{\n    public class HistoryList<T> : IEnumerable<T>\n    {\n        public HistoryList()\n        {\n            this.stackPrev = new Stack<T>();\n            this.stackNext = new Stack<T>();\n        }\n        private Stack<T> stackPrev;\n        private Stack<T> stackNext;\n\n        /// <summary>\n        /// 在历史列表中添加一个新项，这会舍弃全部next列表中的项。\n        /// </summary>\n        /// <param Name=\"Item\">要添加的新项。</param>\n        public void Add(T item)\n        {\n            this.stackPrev.Push(item);\n            this.stackNext.Clear();\n        }\n\n        public void AddRange(IEnumerable<T> collection)\n        {\n            foreach (T item in collection)\n            {\n                this.stackPrev.Push(item);\n            }\n            this.stackNext.Clear();\n        }\n\n        /// <summary>\n        /// 返回历史列表的上一项。\n        /// </summary>\n        /// <returns></returns>\n        /// <exception cref=\"System.InvalidOperationException\"></exception>\n        public T MovePrev()\n        {\n            stackNext.Push(stackPrev.Pop());\n            return this.Current;\n        }\n\n        /// <summary>\n        /// 返回历史列表的下一项。\n        /// </summary>\n        /// <returns></returns>\n        /// <exception cref=\"System.InvalidOperationException\"></exception>\n        public T MoveNext()\n        {\n            stackPrev.Push(stackNext.Pop());\n            return this.Current;\n        }\n\n        /// <summary>\n        /// 获取历史列表的当前项。\n        /// </summary>\n        /// <returns></returns>\n        /// <exception cref=\"System.InvalidOperationException\"></exception>\n        public T Current\n        {\n            get\n            {\n                if (stackPrev.Count > 0)\n                    return stackPrev.Peek();\n                else\n                    throw new InvalidOperationException(\"当前列表中没有添加项。\");\n            }\n        }\n\n        public int PrevCount\n        {\n            get { return stackPrev.Count == 0 ? 0 : stackPrev.Count - 1; }\n        }\n\n        public int NextCount\n        {\n            get { return stackNext.Count; }\n        }\n\n        public int Count\n        {\n            get { return stackPrev.Count + stackNext.Count; }\n        }\n\n        public void Clear()\n        {\n            this.stackPrev.Clear();\n            this.stackNext.Clear();\n        }\n\n        public IEnumerator<T> GetEnumerator()\n        {\n            List<T> lst = new List<T>(this.stackPrev.Count + this.stackNext.Count);\n            lst.AddRange(stackNext);\n            lst.Reverse(0, lst.Count);\n            lst.AddRange(stackPrev);\n            return lst.GetEnumerator();\n        }\n\n        IEnumerator IEnumerable.GetEnumerator()\n        {\n            return this.GetEnumerator();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/ImageDragHandler.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Windows.Forms;\nusing System.Drawing;\nusing System.IO;\nusing WzComparerR2.Common;\n\nnamespace WzComparerR2\n{\n    public class ImageDragHandler\n    {\n        public ImageDragHandler(PictureBox owner)\n        {\n            this.OwnerControl = owner;\n            this.dragBox = Rectangle.Empty;\n        }\n\n        public PictureBox OwnerControl { get; private set; }\n\n        private Rectangle dragBox;\n\n        public void AttachEvents()\n        {\n            this.OwnerControl.MouseDown += OwnerControl_MouseDown;\n            this.OwnerControl.MouseMove += OwnerControl_MouseMove;\n            this.OwnerControl.MouseUp += OwnerControl_MouseUp;\n        }\n\n        void OwnerControl_MouseDown(object sender, MouseEventArgs e)\n        {\n            if (e.Button == MouseButtons.Left && OwnerControl.Image != null)\n            {\n                this.dragBox = new Rectangle(e.Location, SystemInformation.DragSize);\n            }\n        }\n\n        void OwnerControl_MouseMove(object sender, MouseEventArgs e)\n        {\n            if (e.Button == MouseButtons.Left && OwnerControl.Image != null\n                && this.dragBox != Rectangle.Empty && !this.dragBox.Contains(e.Location))\n            {\n                string fileName = Convert.ToString(OwnerControl.Tag);\n                ImageDataObject dataObj = new ImageDataObject(OwnerControl.Image, fileName);\n                var dragEff = this.OwnerControl.DoDragDrop(dataObj, DragDropEffects.Copy);\n            }\n        }\n\n        void OwnerControl_MouseUp(object sender, MouseEventArgs e)\n        {\n            this.dragBox = Rectangle.Empty;\n        }\n\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/MainForm.Designer.cs",
    "content": "﻿namespace WzComparerR2\n{\n    partial class MainForm\n    {\n        /// <summary>\n        /// 必需的设计器变量。\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// 清理所有正在使用的资源。\n        /// </summary>\n        /// <param Name=\"disposing\">如果应释放托管资源，为 true；否则为 false。</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows 窗体设计器生成的代码\n\n        /// <summary>\n        /// 设计器支持所需的方法 - 不要\n        /// 使用代码编辑器修改此方法的内容。\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.components = new System.ComponentModel.Container();\n            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));\n            this.ribbonControl1 = new DevComponents.DotNetBar.RibbonControl();\n            this.ribbonPanel2 = new DevComponents.DotNetBar.RibbonPanel();\n            this.ribbonBar8 = new DevComponents.DotNetBar.RibbonBar();\n            this.itemContainer37 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer38 = new DevComponents.DotNetBar.ItemContainer();\n            this.comboBoxItemCharacter = new DevComponents.DotNetBar.ComboBoxItem();\n            this.itemContainer39 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer40 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemCreateChara = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemEdit = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer41 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemLoadChara = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemSaveChara = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer23 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer24 = new DevComponents.DotNetBar.ItemContainer();\n            this.comboBoxItemLanguage = new DevComponents.DotNetBar.ComboBoxItem();\n            this.comboItem13 = new DevComponents.Editors.ComboItem();\n            this.comboItem14 = new DevComponents.Editors.ComboItem();\n            this.comboItem15 = new DevComponents.Editors.ComboItem();\n            this.comboItem16 = new DevComponents.Editors.ComboItem();\n            this.comboItem17 = new DevComponents.Editors.ComboItem();\n            this.comboItem18 = new DevComponents.Editors.ComboItem();\n            this.itemContainer25 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemQuickView = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer42 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemAutoQuickView = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemQuickViewSetting = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer26 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemSetItems = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer43 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemClearSetItems = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer28 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer29 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemCharItem = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer30 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemCharaStat = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer31 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemCharaEquip = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer32 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer33 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemAddItem = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer34 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer35 = new DevComponents.DotNetBar.ItemContainer();\n            this.ribbonBar3 = new DevComponents.DotNetBar.RibbonBar();\n            this.itemContainer6 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer7 = new DevComponents.DotNetBar.ItemContainer();\n            this.labelItemSoundTitle = new DevComponents.DotNetBar.LabelItem();\n            this.itemContainer9 = new DevComponents.DotNetBar.ItemContainer();\n            this.sliderItemSoundTime = new DevComponents.DotNetBar.SliderItem();\n            this.checkBoxItemSoundLoop = new DevComponents.DotNetBar.CheckBoxItem();\n            this.itemContainer18 = new DevComponents.DotNetBar.ItemContainer();\n            this.labelItemSoundTime = new DevComponents.DotNetBar.LabelItem();\n            this.itemContainer13 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemLoadSound = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemSoundPlay = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemSoundStop = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemSoundSave = new DevComponents.DotNetBar.ButtonItem();\n            this.sliderItemSoundVol = new DevComponents.DotNetBar.SliderItem();\n            this.ribbonPanel1 = new DevComponents.DotNetBar.RibbonPanel();\n            this.ribbonBar9 = new DevComponents.DotNetBar.RibbonBar();\n            this.buttonItemPatcher = new DevComponents.DotNetBar.ButtonItem();\n            this.ribbonBar4 = new DevComponents.DotNetBar.RibbonBar();\n            this.itemContainer10 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer8 = new DevComponents.DotNetBar.ItemContainer();\n            this.labelItem2 = new DevComponents.DotNetBar.LabelItem();\n            this.textBoxItemSearchString = new DevComponents.DotNetBar.TextBoxItem();\n            this.itemContainer11 = new DevComponents.DotNetBar.ItemContainer();\n            this.checkBoxItemExact2 = new DevComponents.DotNetBar.CheckBoxItem();\n            this.comboBoxItem2 = new DevComponents.DotNetBar.ComboBoxItem();\n            this.comboItem3 = new DevComponents.Editors.ComboItem();\n            this.comboItem4 = new DevComponents.Editors.ComboItem();\n            this.comboItem5 = new DevComponents.Editors.ComboItem();\n            this.comboItem6 = new DevComponents.Editors.ComboItem();\n            this.comboItem7 = new DevComponents.Editors.ComboItem();\n            this.comboItem8 = new DevComponents.Editors.ComboItem();\n            this.comboItem9 = new DevComponents.Editors.ComboItem();\n            this.itemContainer12 = new DevComponents.DotNetBar.ItemContainer();\n            this.checkBoxItemRegex2 = new DevComponents.DotNetBar.CheckBoxItem();\n            this.buttonItemSearchString = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemSelectStringWz = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemClearStringWz = new DevComponents.DotNetBar.ButtonItem();\n            this.ribbonBar1 = new DevComponents.DotNetBar.RibbonBar();\n            this.itemContainer14 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer15 = new DevComponents.DotNetBar.ItemContainer();\n            this.labelItem3 = new DevComponents.DotNetBar.LabelItem();\n            this.textBoxItemSearchWz = new DevComponents.DotNetBar.TextBoxItem();\n            this.itemContainer16 = new DevComponents.DotNetBar.ItemContainer();\n            this.checkBoxItemExact1 = new DevComponents.DotNetBar.CheckBoxItem();\n            this.comboBoxItem1 = new DevComponents.DotNetBar.ComboBoxItem();\n            this.comboItem10 = new DevComponents.Editors.ComboItem();\n            this.comboItem11 = new DevComponents.Editors.ComboItem();\n            this.comboItem12 = new DevComponents.Editors.ComboItem();\n            this.itemContainer17 = new DevComponents.DotNetBar.ItemContainer();\n            this.checkBoxItemRegex1 = new DevComponents.DotNetBar.CheckBoxItem();\n            this.buttonItemSearchWz = new DevComponents.DotNetBar.ButtonItem();\n            this.ribbonPanel3 = new DevComponents.DotNetBar.RibbonPanel();\n            this.ribbonBar11 = new DevComponents.DotNetBar.RibbonBar();\n            this.buttonItem1 = new DevComponents.DotNetBar.ButtonItem();\n            this.ribbonBar7 = new DevComponents.DotNetBar.RibbonBar();\n            this.buttonItemUpdate = new DevComponents.DotNetBar.ButtonItem();\n            this.ribbonBar6 = new DevComponents.DotNetBar.RibbonBar();\n            this.buttonItemAbout = new DevComponents.DotNetBar.ButtonItem();\n            this.ribbonTabItem1 = new DevComponents.DotNetBar.RibbonTabItem();\n            this.ribbonTabItem2 = new DevComponents.DotNetBar.RibbonTabItem();\n            this.ribbonTabItem3 = new DevComponents.DotNetBar.RibbonTabItem();\n            this.buttonItemStyle = new DevComponents.DotNetBar.ButtonItem();\n            this.office2007StartButton1 = new DevComponents.DotNetBar.Office2007StartButton();\n            this.itemContainer1 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer2 = new DevComponents.DotNetBar.ItemContainer();\n            this.itemContainer3 = new DevComponents.DotNetBar.ItemContainer();\n            this.btnItemOpenWz = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemClose = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemCloseAll = new DevComponents.DotNetBar.ButtonItem();\n            this.galleryContainerRecent = new DevComponents.DotNetBar.GalleryContainer();\n            this.labelItem8 = new DevComponents.DotNetBar.LabelItem();\n            this.itemContainer4 = new DevComponents.DotNetBar.ItemContainer();\n            this.btnItemOptions = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItem13 = new DevComponents.DotNetBar.ButtonItem();\n            this.styleManager1 = new DevComponents.DotNetBar.StyleManager(this.components);\n            this.colorPickerDropDown1 = new DevComponents.DotNetBar.ColorPickerDropDown();\n            this.ribbonBar2 = new DevComponents.DotNetBar.RibbonBar();\n            this.labelItemStatus = new DevComponents.DotNetBar.LabelItem();\n            this.progressBarItem1 = new DevComponents.DotNetBar.ProgressBarItem();\n            this.panelExMain = new DevComponents.DotNetBar.PanelEx();\n            this.panelExRight = new DevComponents.DotNetBar.PanelEx();\n            this.superTabControl1 = new DevComponents.DotNetBar.SuperTabControl();\n            this.superTabControlPanel1 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.panelEx2 = new DevComponents.DotNetBar.PanelEx();\n            this.pictureBoxEx1 = new WzComparerR2.PictureBoxEx();\n            this.ribbonBar5 = new DevComponents.DotNetBar.RibbonBar();\n            this.cmbItemAniNames = new DevComponents.DotNetBar.ComboBoxItem();\n            this.cmbItemSkins = new DevComponents.DotNetBar.ComboBoxItem();\n            this.buttonItemSaveImage = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer27 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemAutoSave = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemAutoSaveFolder = new DevComponents.DotNetBar.ButtonItem();\n            this.labelItemAutoSaveFolder = new DevComponents.DotNetBar.LabelItem();\n            this.buttonItemGif = new DevComponents.DotNetBar.ButtonItem();\n            this.itemContainer36 = new DevComponents.DotNetBar.ItemContainer();\n            this.buttonItemExtractGifEx = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemGifSetting = new DevComponents.DotNetBar.ButtonItem();\n            this.colorPickerPicBoxBgColor = new DevComponents.DotNetBar.ColorPickerDropDown();\n            this.textBoxX1 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.expandableSplitter1 = new DevComponents.DotNetBar.ExpandableSplitter();\n            this.panelEx1 = new DevComponents.DotNetBar.PanelEx();\n            this.advTree3 = new DevComponents.AdvTree.AdvTree();\n            this.columnHeader3 = new DevComponents.AdvTree.ColumnHeader();\n            this.columnHeader4 = new DevComponents.AdvTree.ColumnHeader();\n            this.columnHeader5 = new DevComponents.AdvTree.ColumnHeader();\n            this.contextMenuStrip2 = new System.Windows.Forms.ContextMenuStrip(this.components);\n            this.tsmi2SaveAs = new System.Windows.Forms.ToolStripMenuItem();\n            this.tsmi2HandleUol = new System.Windows.Forms.ToolStripMenuItem();\n            this.tsmi2Splitter1 = new System.Windows.Forms.ToolStripSeparator();\n            this.tsmi2ExpandAll = new System.Windows.Forms.ToolStripMenuItem();\n            this.tsmi2CollapseAll = new System.Windows.Forms.ToolStripMenuItem();\n            this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();\n            this.tsmi2ExpandLevel = new System.Windows.Forms.ToolStripMenuItem();\n            this.tsmi2CollapseLevel = new System.Windows.Forms.ToolStripMenuItem();\n            this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator();\n            this.tsmi2Prev = new System.Windows.Forms.ToolStripMenuItem();\n            this.tsmi2Next = new System.Windows.Forms.ToolStripMenuItem();\n            this.imageList1 = new System.Windows.Forms.ImageList(this.components);\n            this.nodeConnector3 = new DevComponents.AdvTree.NodeConnector();\n            this.elementStyle3 = new DevComponents.DotNetBar.ElementStyle();\n            this.listViewExWzDetail = new DevComponents.DotNetBar.Controls.ListViewEx();\n            this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));\n            this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));\n            this.superTabItem1 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel2 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.chkResolvePngLink = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.btnCustomCSS = new DevComponents.DotNetBar.ButtonX();\n            this.superTooltip1 = new DevComponents.DotNetBar.SuperTooltip();\n            this.chkOutputRemovedImg = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkOutputAddedImg = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.chkOutputPng = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.cmbComparePng = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.labelXComp2 = new DevComponents.DotNetBar.LabelX();\n            this.labelXComp1 = new DevComponents.DotNetBar.LabelX();\n            this.btnEasyCompare = new DevComponents.DotNetBar.ButtonX();\n            this.superTabItem2 = new DevComponents.DotNetBar.SuperTabItem();\n            this.superTabControlPanel3 = new DevComponents.DotNetBar.SuperTabControlPanel();\n            this.btnExportSkillOption = new DevComponents.DotNetBar.ButtonX();\n            this.btnExportSkill = new DevComponents.DotNetBar.ButtonX();\n            this.superTabItem3 = new DevComponents.DotNetBar.SuperTabItem();\n            this.btnNodeBack = new DevComponents.DotNetBar.ButtonItem();\n            this.btnNodeForward = new DevComponents.DotNetBar.ButtonItem();\n            this.panelExLeft = new DevComponents.DotNetBar.PanelEx();\n            this.advTree2 = new DevComponents.AdvTree.AdvTree();\n            this.nodeConnector2 = new DevComponents.AdvTree.NodeConnector();\n            this.elementStyle2 = new DevComponents.DotNetBar.ElementStyle();\n            this.expandableSplitter2 = new DevComponents.DotNetBar.ExpandableSplitter();\n            this.advTree1 = new DevComponents.AdvTree.AdvTree();\n            this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);\n            this.tsmi1Sort = new System.Windows.Forms.ToolStripMenuItem();\n            this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();\n            this.tsmi1Export = new System.Windows.Forms.ToolStripMenuItem();\n            this.tsmi1DumpAsXml = new System.Windows.Forms.ToolStripMenuItem();\n            this.nodeConnector1 = new DevComponents.AdvTree.NodeConnector();\n            this.elementStyle1 = new DevComponents.DotNetBar.ElementStyle();\n            this.listViewExString = new DevComponents.DotNetBar.Controls.ListViewEx();\n            this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));\n            this.columnHeader7 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));\n            this.columnHeader8 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));\n            this.columnHeader9 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));\n            this.comboItem1 = new DevComponents.Editors.ComboItem();\n            this.comboItem2 = new DevComponents.Editors.ComboItem();\n            this.dotNetBarManager1 = new DevComponents.DotNetBar.DotNetBarManager(this.components);\n            this.dockSite4 = new DevComponents.DotNetBar.DockSite();\n            this.bar1 = new DevComponents.DotNetBar.Bar();\n            this.panelDockContainer1 = new DevComponents.DotNetBar.PanelDockContainer();\n            this.dockContainerItem1 = new DevComponents.DotNetBar.DockContainerItem();\n            this.dockSite1 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite2 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite8 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite5 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite6 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite7 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite3 = new DevComponents.DotNetBar.DockSite();\n            this.dockContainerItem2 = new DevComponents.DotNetBar.DockContainerItem();\n            this.panelDockContainer2 = new DevComponents.DotNetBar.PanelDockContainer();\n            this.chkHashPngFileName = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.btnItemOpenImg = new DevComponents.DotNetBar.ButtonItem();\n            this.buttonItemSaveWithOptions = new DevComponents.DotNetBar.ButtonItem();\n            this.toolStripMenuItem4 = new System.Windows.Forms.ToolStripSeparator();\n            this.tsmi2CopyFullPath = new System.Windows.Forms.ToolStripMenuItem();\n            this.comboItem19 = new DevComponents.Editors.ComboItem();\n            this.ribbonControl1.SuspendLayout();\n            this.ribbonPanel1.SuspendLayout();\n            this.ribbonPanel2.SuspendLayout();\n            this.ribbonPanel3.SuspendLayout();\n            this.panelExMain.SuspendLayout();\n            this.panelExRight.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).BeginInit();\n            this.superTabControl1.SuspendLayout();\n            this.superTabControlPanel1.SuspendLayout();\n            this.panelEx2.SuspendLayout();\n            this.panelEx1.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.advTree3)).BeginInit();\n            this.contextMenuStrip2.SuspendLayout();\n            this.superTabControlPanel2.SuspendLayout();\n            this.superTabControlPanel3.SuspendLayout();\n            this.panelExLeft.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.advTree2)).BeginInit();\n            ((System.ComponentModel.ISupportInitialize)(this.advTree1)).BeginInit();\n            this.contextMenuStrip1.SuspendLayout();\n            this.dockSite4.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).BeginInit();\n            this.bar1.SuspendLayout();\n            this.panelDockContainer1.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // ribbonControl1\n            // \n            // \n            // \n            // \n            this.ribbonControl1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonControl1.CanCustomize = false;\n            this.ribbonControl1.CaptionVisible = true;\n            this.ribbonControl1.Controls.Add(this.ribbonPanel1);\n            this.ribbonControl1.Controls.Add(this.ribbonPanel2);\n            this.ribbonControl1.Controls.Add(this.ribbonPanel3);\n            this.ribbonControl1.Dock = System.Windows.Forms.DockStyle.Top;\n            this.ribbonControl1.Expanded = false;\n            this.ribbonControl1.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.ribbonTabItem1,\n            this.ribbonTabItem2,\n            this.ribbonTabItem3,\n            this.buttonItemStyle});\n            this.ribbonControl1.KeyTipsFont = new System.Drawing.Font(\"Tahoma\", 7F);\n            this.ribbonControl1.Location = new System.Drawing.Point(5, 1);\n            this.ribbonControl1.Name = \"ribbonControl1\";\n            this.ribbonControl1.Padding = new System.Windows.Forms.Padding(0, 0, 0, 3);\n            this.ribbonControl1.QuickToolbarItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.office2007StartButton1});\n            this.ribbonControl1.Size = new System.Drawing.Size(740, 153);\n            this.ribbonControl1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonControl1.SystemText.MaximizeRibbonText = \"&Maximize the Ribbon\";\n            this.ribbonControl1.SystemText.MinimizeRibbonText = \"Mi&nimize the Ribbon\";\n            this.ribbonControl1.SystemText.QatAddItemText = \"&Add to Quick Access Toolbar\";\n            this.ribbonControl1.SystemText.QatCustomizeMenuLabel = \"<b>Customize Quick Access Toolbar</b>\";\n            this.ribbonControl1.SystemText.QatCustomizeText = \"&Customize Quick Access Toolbar...\";\n            this.ribbonControl1.SystemText.QatDialogAddButton = \"&Add >>\";\n            this.ribbonControl1.SystemText.QatDialogCancelButton = \"Cancel\";\n            this.ribbonControl1.SystemText.QatDialogCaption = \"Customize Quick Access Toolbar\";\n            this.ribbonControl1.SystemText.QatDialogCategoriesLabel = \"&Choose commands from:\";\n            this.ribbonControl1.SystemText.QatDialogOkButton = \"OK\";\n            this.ribbonControl1.SystemText.QatDialogPlacementCheckbox = \"&Place Quick Access Toolbar below the Ribbon\";\n            this.ribbonControl1.SystemText.QatDialogRemoveButton = \"&Remove\";\n            this.ribbonControl1.SystemText.QatPlaceAboveRibbonText = \"&Place Quick Access Toolbar above the Ribbon\";\n            this.ribbonControl1.SystemText.QatPlaceBelowRibbonText = \"&Place Quick Access Toolbar below the Ribbon\";\n            this.ribbonControl1.SystemText.QatRemoveItemText = \"&Remove from Quick Access Toolbar\";\n            this.ribbonControl1.TabGroupHeight = 14;\n            this.ribbonControl1.TabIndex = 0;\n            this.ribbonControl1.Text = \"ribbonControl1\";\n            this.ribbonControl1.UseCustomizeDialog = false;\n            // \n            // ribbonPanel2\n            // \n            this.ribbonPanel2.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonPanel2.Controls.Add(this.ribbonBar8);\n            this.ribbonPanel2.Controls.Add(this.ribbonBar3);\n            this.ribbonPanel2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.ribbonPanel2.Location = new System.Drawing.Point(0, 56);\n            this.ribbonPanel2.Name = \"ribbonPanel2\";\n            this.ribbonPanel2.Padding = new System.Windows.Forms.Padding(3, 0, 3, 3);\n            this.ribbonPanel2.Size = new System.Drawing.Size(740, 94);\n            // \n            // \n            // \n            this.ribbonPanel2.Style.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonPanel2.StyleMouseDown.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonPanel2.StyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonPanel2.TabIndex = 2;\n            this.ribbonPanel2.Visible = false;\n            // \n            // ribbonBar8\n            // \n            this.ribbonBar8.AutoOverflowEnabled = true;\n            // \n            // \n            // \n            this.ribbonBar8.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar8.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar8.ContainerControlProcessDialogKey = true;\n            this.ribbonBar8.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar8.DragDropSupport = true;\n            this.ribbonBar8.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer37,\n            this.itemContainer23,\n            this.itemContainer28,\n            this.itemContainer32});\n            this.ribbonBar8.Location = new System.Drawing.Point(265, 0);\n            this.ribbonBar8.Name = \"ribbonBar8\";\n            this.ribbonBar8.Size = new System.Drawing.Size(270, 91);\n            this.ribbonBar8.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar8.TabIndex = 1;\n            this.ribbonBar8.Text = \"CharaSim\";\n            // \n            // \n            // \n            this.ribbonBar8.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar8.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer37\n            // \n            // \n            // \n            // \n            this.itemContainer37.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer37.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer37.Name = \"itemContainer37\";\n            this.itemContainer37.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer38,\n            this.itemContainer39});\n            // \n            // \n            // \n            this.itemContainer37.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer37.Visible = false;\n            // \n            // itemContainer38\n            // \n            // \n            // \n            // \n            this.itemContainer38.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer38.Name = \"itemContainer38\";\n            this.itemContainer38.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.comboBoxItemCharacter});\n            // \n            // \n            // \n            this.itemContainer38.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // comboBoxItemCharacter\n            // \n            this.comboBoxItemCharacter.ComboWidth = 80;\n            this.comboBoxItemCharacter.DropDownHeight = 106;\n            this.comboBoxItemCharacter.ItemHeight = 16;\n            this.comboBoxItemCharacter.Name = \"comboBoxItemCharacter\";\n            this.comboBoxItemCharacter.Text = \"comboBoxItem3\";\n            // \n            // itemContainer39\n            // \n            // \n            // \n            // \n            this.itemContainer39.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer39.Name = \"itemContainer39\";\n            this.itemContainer39.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer40,\n            this.itemContainer41});\n            // \n            // \n            // \n            this.itemContainer39.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer39.Visible = false;\n            // \n            // itemContainer40\n            // \n            // \n            // \n            // \n            this.itemContainer40.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer40.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer40.Name = \"itemContainer40\";\n            this.itemContainer40.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemCreateChara,\n            this.buttonItemEdit});\n            // \n            // \n            // \n            this.itemContainer40.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemCreateChara\n            // \n            this.buttonItemCreateChara.Name = \"buttonItemCreateChara\";\n            this.buttonItemCreateChara.Text = \"Create\";\n            // \n            // buttonItemEdit\n            // \n            this.buttonItemEdit.Name = \"buttonItemEdit\";\n            this.buttonItemEdit.Text = \"Edit\";\n            // \n            // itemContainer41\n            // \n            // \n            // \n            // \n            this.itemContainer41.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer41.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer41.Name = \"itemContainer41\";\n            this.itemContainer41.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemLoadChara,\n            this.buttonItemSaveChara});\n            // \n            // \n            // \n            this.itemContainer41.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemLoadChara\n            // \n            this.buttonItemLoadChara.Name = \"buttonItemLoadChara\";\n            this.buttonItemLoadChara.Text = \"Load\";\n            // \n            // buttonItemSaveChara\n            // \n            this.buttonItemSaveChara.Name = \"buttonItemSaveChara\";\n            this.buttonItemSaveChara.Text = \"Save\";\n            // \n            // itemContainer23\n            // \n            // \n            // \n            // \n            this.itemContainer23.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer23.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer23.Name = \"itemContainer23\";\n            this.itemContainer23.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer24,\n            this.itemContainer25,\n            this.itemContainer26});\n            // \n            // \n            // \n            this.itemContainer23.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer24\n            // \n            // \n            // \n            // \n            this.itemContainer24.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer24.Name = \"itemContainer24\";\n            this.itemContainer24.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.comboBoxItemLanguage});\n            // \n            // \n            // \n            this.itemContainer24.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // comboBoxItemLanguage\n            // \n            this.comboBoxItemLanguage.ComboWidth = 75;\n            this.comboBoxItemLanguage.DropDownHeight = 106;\n            this.comboBoxItemLanguage.ItemHeight = 16;\n            this.comboBoxItemLanguage.Items.AddRange(new object[] {\n            this.comboItem13,\n            this.comboItem14,\n            this.comboItem15,\n            this.comboItem16,\n            this.comboItem17,\n            this.comboItem18});\n            this.comboBoxItemLanguage.Name = \"comboBoxItemLanguage\";\n            this.comboBoxItemLanguage.Text = \"comboBoxItem3\";\n            this.comboBoxItemLanguage.SelectedIndexChanged += new System.EventHandler(this.comboBoxItemLanguage_SelectedIndexChanged);\n            // \n            // comboItem14\n            // \n            this.comboItem14.Text = \"宋体\";\n            // \n            // comboItem15\n            // \n            this.comboItem15.Text = \"微软雅黑\";\n            // \n            // comboItem16\n            // \n            this.comboItem16.Text = \"MS Gothic\";\n            // \n            // comboItem17\n            // \n            this.comboItem17.Text = \"굴림체\";\n            // \n            // comboItem18\n            // \n            this.comboItem18.Text = \"돋움\";\n            // \n            // itemContainer25\n            // \n            // \n            // \n            // \n            this.itemContainer25.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer25.Name = \"itemContainer25\";\n            this.itemContainer25.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemQuickView});\n            // \n            // \n            // \n            this.itemContainer25.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemQuickView\n            // \n            this.buttonItemQuickView.Name = \"buttonItemQuickView\";\n            this.buttonItemQuickView.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer42});\n            this.buttonItemQuickView.Text = \"QuickView\";\n            this.buttonItemQuickView.Click += new System.EventHandler(this.buttonItemQuickView_Click);\n            // \n            // itemContainer42\n            // \n            // \n            // \n            // \n            this.itemContainer42.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer42.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer42.Name = \"itemContainer42\";\n            this.itemContainer42.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemAutoQuickView,\n            this.buttonItemQuickViewSetting});\n            // \n            // \n            // \n            this.itemContainer42.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemAutoQuickView\n            // \n            this.buttonItemAutoQuickView.AutoCheckOnClick = true;\n            this.buttonItemAutoQuickView.Name = \"buttonItemAutoQuickView\";\n            this.buttonItemAutoQuickView.Text = \"AutoQuickView\";\n            this.buttonItemAutoQuickView.Tooltip = \"开启/关闭自动预览机能\";\n            this.buttonItemAutoQuickView.Click += new System.EventHandler(this.buttonItemAutoQuickView_Click);\n            // \n            // buttonItemQuickViewSetting\n            // \n            this.buttonItemQuickViewSetting.Name = \"buttonItemQuickViewSetting\";\n            this.buttonItemQuickViewSetting.Text = \"Setting\";\n            this.buttonItemQuickViewSetting.Click += new System.EventHandler(this.buttonItemQuickViewSetting_Click);\n            // \n            // itemContainer26\n            // \n            // \n            // \n            // \n            this.itemContainer26.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer26.Name = \"itemContainer26\";\n            this.itemContainer26.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemSetItems});\n            // \n            // \n            // \n            this.itemContainer26.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemSetItems\n            // \n            this.buttonItemSetItems.Name = \"buttonItemSetItems\";\n            this.buttonItemSetItems.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer43});\n            this.buttonItemSetItems.Text = \"SetItems\";\n            // \n            // itemContainer43\n            // \n            // \n            // \n            // \n            this.itemContainer43.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer43.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer43.Name = \"itemContainer43\";\n            this.itemContainer43.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemClearSetItems});\n            // \n            // \n            // \n            this.itemContainer43.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemClearSetItems\n            // \n            this.buttonItemClearSetItems.Name = \"buttonItemClearSetItems\";\n            this.buttonItemClearSetItems.Text = \"Clear SetItems\";\n            this.buttonItemClearSetItems.Click += new System.EventHandler(this.buttonItemClearSetItems_Click);\n            // \n            // itemContainer28\n            // \n            // \n            // \n            // \n            this.itemContainer28.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer28.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer28.Name = \"itemContainer28\";\n            this.itemContainer28.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer29,\n            this.itemContainer30,\n            this.itemContainer31});\n            // \n            // \n            // \n            this.itemContainer28.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer29\n            // \n            // \n            // \n            // \n            this.itemContainer29.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer29.Name = \"itemContainer29\";\n            this.itemContainer29.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemCharItem});\n            // \n            // \n            // \n            this.itemContainer29.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemCharItem\n            // \n            this.buttonItemCharItem.AutoCheckOnClick = true;\n            this.buttonItemCharItem.Name = \"buttonItemCharItem\";\n            this.buttonItemCharItem.Text = \"Item\";\n            this.buttonItemCharItem.Tooltip = \"开启/关闭道具栏\";\n            this.buttonItemCharItem.CheckedChanged += new System.EventHandler(this.buttonItemCharItem_CheckedChanged);\n            // \n            // itemContainer30\n            // \n            // \n            // \n            // \n            this.itemContainer30.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer30.Name = \"itemContainer30\";\n            this.itemContainer30.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemCharaStat});\n            // \n            // \n            // \n            this.itemContainer30.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemCharaStat\n            // \n            this.buttonItemCharaStat.AutoCheckOnClick = true;\n            this.buttonItemCharaStat.Name = \"buttonItemCharaStat\";\n            this.buttonItemCharaStat.Text = \"Stat\";\n            this.buttonItemCharaStat.Tooltip = \"开启/关闭状态栏\";\n            this.buttonItemCharaStat.CheckedChanged += new System.EventHandler(this.buttonItemCharaStat_CheckedChanged);\n            // \n            // itemContainer31\n            // \n            // \n            // \n            // \n            this.itemContainer31.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer31.Name = \"itemContainer31\";\n            this.itemContainer31.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemCharaEquip});\n            // \n            // \n            // \n            this.itemContainer31.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemCharaEquip\n            // \n            this.buttonItemCharaEquip.AutoCheckOnClick = true;\n            this.buttonItemCharaEquip.Name = \"buttonItemCharaEquip\";\n            this.buttonItemCharaEquip.Text = \"Equip\";\n            this.buttonItemCharaEquip.Tooltip = \"开启/关闭装备栏\";\n            this.buttonItemCharaEquip.CheckedChanged += new System.EventHandler(this.buttonItemCharaEquip_CheckedChanged);\n            // \n            // itemContainer32\n            // \n            // \n            // \n            // \n            this.itemContainer32.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer32.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer32.Name = \"itemContainer32\";\n            this.itemContainer32.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer33,\n            this.itemContainer34,\n            this.itemContainer35});\n            // \n            // \n            // \n            this.itemContainer32.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer33\n            // \n            // \n            // \n            // \n            this.itemContainer33.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer33.Name = \"itemContainer33\";\n            this.itemContainer33.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemAddItem});\n            // \n            // \n            // \n            this.itemContainer33.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemAddItem\n            // \n            this.buttonItemAddItem.Name = \"buttonItemAddItem\";\n            this.buttonItemAddItem.Text = \"AddItem\";\n            this.buttonItemAddItem.Tooltip = \"把最后预览的装备或道具添加至背包\";\n            this.buttonItemAddItem.Click += new System.EventHandler(this.buttonItemAddItem_Click);\n            // \n            // itemContainer34\n            // \n            // \n            // \n            // \n            this.itemContainer34.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer34.Name = \"itemContainer34\";\n            // \n            // \n            // \n            this.itemContainer34.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer35\n            // \n            // \n            // \n            // \n            this.itemContainer35.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer35.Name = \"itemContainer35\";\n            // \n            // \n            // \n            this.itemContainer35.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // ribbonBar3\n            // \n            this.ribbonBar3.AllowDrop = true;\n            this.ribbonBar3.AutoOverflowEnabled = false;\n            // \n            // \n            // \n            this.ribbonBar3.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar3.ContainerControlProcessDialogKey = true;\n            this.ribbonBar3.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar3.DragDropSupport = true;\n            this.ribbonBar3.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer6});\n            this.ribbonBar3.Location = new System.Drawing.Point(3, 0);\n            this.ribbonBar3.Name = \"ribbonBar3\";\n            this.ribbonBar3.Size = new System.Drawing.Size(262, 91);\n            this.ribbonBar3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar3.TabIndex = 0;\n            this.ribbonBar3.Text = \"SoundPlayer\";\n            // \n            // \n            // \n            this.ribbonBar3.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar3.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar3.DragDrop += new System.Windows.Forms.DragEventHandler(this.ribbonBar3_DragDrop);\n            this.ribbonBar3.DragEnter += new System.Windows.Forms.DragEventHandler(this.ribbonBar3_DragEnter);\n            // \n            // itemContainer6\n            // \n            // \n            // \n            // \n            this.itemContainer6.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer6.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer6.Name = \"itemContainer6\";\n            this.itemContainer6.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer7,\n            this.itemContainer9,\n            this.itemContainer18,\n            this.itemContainer13});\n            // \n            // \n            // \n            this.itemContainer6.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer7\n            // \n            // \n            // \n            // \n            this.itemContainer7.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer7.Name = \"itemContainer7\";\n            this.itemContainer7.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.labelItemSoundTitle});\n            // \n            // \n            // \n            this.itemContainer7.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // labelItemSoundTitle\n            // \n            this.labelItemSoundTitle.Name = \"labelItemSoundTitle\";\n            this.labelItemSoundTitle.Text = \"player\";\n            this.labelItemSoundTitle.Width = 254;\n            // \n            // itemContainer9\n            // \n            // \n            // \n            // \n            this.itemContainer9.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer9.Name = \"itemContainer9\";\n            this.itemContainer9.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.sliderItemSoundTime,\n            this.checkBoxItemSoundLoop});\n            // \n            // \n            // \n            this.itemContainer9.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // sliderItemSoundTime\n            // \n            this.sliderItemSoundTime.LabelWidth = 6;\n            this.sliderItemSoundTime.Name = \"sliderItemSoundTime\";\n            this.sliderItemSoundTime.Step = 10;\n            this.sliderItemSoundTime.Value = 0;\n            this.sliderItemSoundTime.Width = 195;\n            this.sliderItemSoundTime.ValueChanged += new System.EventHandler(this.sliderItemSoundTime_ValueChanged);\n            // \n            // checkBoxItemSoundLoop\n            // \n            this.checkBoxItemSoundLoop.Name = \"checkBoxItemSoundLoop\";\n            this.checkBoxItemSoundLoop.Text = \"loop\";\n            this.checkBoxItemSoundLoop.CheckedChanged += new DevComponents.DotNetBar.CheckBoxChangeEventHandler(this.checkBoxItemSoundLoop_CheckedChanged);\n            // \n            // itemContainer18\n            // \n            // \n            // \n            // \n            this.itemContainer18.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer18.Name = \"itemContainer18\";\n            this.itemContainer18.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.labelItemSoundTime});\n            // \n            // \n            // \n            this.itemContainer18.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // labelItemSoundTime\n            // \n            this.labelItemSoundTime.Name = \"labelItemSoundTime\";\n            this.labelItemSoundTime.Text = \"00:00 / 00:00\";\n            this.labelItemSoundTime.TextAlignment = System.Drawing.StringAlignment.Center;\n            this.labelItemSoundTime.Width = 222;\n            // \n            // itemContainer13\n            // \n            // \n            // \n            // \n            this.itemContainer13.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer13.Name = \"itemContainer13\";\n            this.itemContainer13.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemLoadSound,\n            this.buttonItemSoundPlay,\n            this.buttonItemSoundStop,\n            this.buttonItemSoundSave,\n            this.sliderItemSoundVol});\n            // \n            // \n            // \n            this.itemContainer13.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemLoadSound\n            // \n            this.buttonItemLoadSound.Image = global::WzComparerR2.Properties.Resources.Open;\n            this.buttonItemLoadSound.Name = \"buttonItemLoadSound\";\n            this.buttonItemLoadSound.Text = \"Load\";\n            this.buttonItemLoadSound.Click += new System.EventHandler(this.buttonItemLoadSound_Click);\n            // \n            // buttonItemSoundPlay\n            // \n            this.buttonItemSoundPlay.Image = global::WzComparerR2.Properties.Resources.Play;\n            this.buttonItemSoundPlay.Name = \"buttonItemSoundPlay\";\n            this.buttonItemSoundPlay.Text = \" Play\";\n            this.buttonItemSoundPlay.Click += new System.EventHandler(this.buttonItemSoundPlay_Click);\n            // \n            // buttonItemSoundStop\n            // \n            this.buttonItemSoundStop.Image = global::WzComparerR2.Properties.Resources.Stop;\n            this.buttonItemSoundStop.Name = \"buttonItemSoundStop\";\n            this.buttonItemSoundStop.Text = \"Stop\";\n            this.buttonItemSoundStop.Click += new System.EventHandler(this.buttonItemSoundStop_Click);\n            // \n            // buttonItemSoundSave\n            // \n            this.buttonItemSoundSave.Image = global::WzComparerR2.Properties.Resources.Save;\n            this.buttonItemSoundSave.Name = \"buttonItemSoundSave\";\n            this.buttonItemSoundSave.Text = \"Save\";\n            this.buttonItemSoundSave.Click += new System.EventHandler(this.buttonItemSoundSave_Click);\n            // \n            // sliderItemSoundVol\n            // \n            this.sliderItemSoundVol.LabelWidth = 25;\n            this.sliderItemSoundVol.Name = \"sliderItemSoundVol\";\n            this.sliderItemSoundVol.Text = \"vol\";\n            this.sliderItemSoundVol.Value = 100;\n            this.sliderItemSoundVol.Width = 110;\n            this.sliderItemSoundVol.ValueChanged += new System.EventHandler(this.sliderItemSoundVol_ValueChanged);\n            // \n            // ribbonPanel1\n            // \n            this.ribbonPanel1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonPanel1.Controls.Add(this.ribbonBar9);\n            this.ribbonPanel1.Controls.Add(this.ribbonBar4);\n            this.ribbonPanel1.Controls.Add(this.ribbonBar1);\n            this.ribbonPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.ribbonPanel1.Location = new System.Drawing.Point(0, 56);\n            this.ribbonPanel1.Name = \"ribbonPanel1\";\n            this.ribbonPanel1.Padding = new System.Windows.Forms.Padding(3, 0, 3, 3);\n            this.ribbonPanel1.Size = new System.Drawing.Size(740, 94);\n            // \n            // \n            // \n            this.ribbonPanel1.Style.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonPanel1.StyleMouseDown.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonPanel1.StyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonPanel1.TabIndex = 1;\n            // \n            // ribbonBar9\n            // \n            this.ribbonBar9.AutoOverflowEnabled = true;\n            // \n            // \n            // \n            this.ribbonBar9.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar9.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar9.ContainerControlProcessDialogKey = true;\n            this.ribbonBar9.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar9.DragDropSupport = true;\n            this.ribbonBar9.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemPatcher});\n            this.ribbonBar9.Location = new System.Drawing.Point(339, 0);\n            this.ribbonBar9.Name = \"ribbonBar9\";\n            this.ribbonBar9.Size = new System.Drawing.Size(63, 91);\n            this.ribbonBar9.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar9.TabIndex = 2;\n            this.ribbonBar9.Text = \"Patcher\";\n            // \n            // \n            // \n            this.ribbonBar9.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar9.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemPatcher\n            // \n            this.buttonItemPatcher.Name = \"buttonItemPatcher\";\n            this.buttonItemPatcher.SubItemsExpandWidth = 14;\n            this.buttonItemPatcher.Text = \"Patcher\";\n            this.buttonItemPatcher.Click += new System.EventHandler(this.buttonItemPatcher_Click);\n            // \n            // ribbonBar4\n            // \n            this.ribbonBar4.AutoOverflowEnabled = true;\n            // \n            // \n            // \n            this.ribbonBar4.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar4.ContainerControlProcessDialogKey = true;\n            this.ribbonBar4.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar4.DragDropSupport = true;\n            this.ribbonBar4.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer10});\n            this.ribbonBar4.Location = new System.Drawing.Point(171, 0);\n            this.ribbonBar4.Name = \"ribbonBar4\";\n            this.ribbonBar4.Size = new System.Drawing.Size(168, 91);\n            this.ribbonBar4.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar4.TabIndex = 1;\n            this.ribbonBar4.Text = \"SearchString\";\n            // \n            // \n            // \n            this.ribbonBar4.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar4.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer10\n            // \n            // \n            // \n            // \n            this.itemContainer10.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer10.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer10.Name = \"itemContainer10\";\n            this.itemContainer10.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer8,\n            this.itemContainer11,\n            this.itemContainer12});\n            // \n            // \n            // \n            this.itemContainer10.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer8\n            // \n            // \n            // \n            // \n            this.itemContainer8.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer8.Name = \"itemContainer8\";\n            this.itemContainer8.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.labelItem2,\n            this.textBoxItemSearchString});\n            // \n            // \n            // \n            this.itemContainer8.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // labelItem2\n            // \n            this.labelItem2.Name = \"labelItem2\";\n            this.labelItem2.Text = \"String\";\n            // \n            // textBoxItemSearchString\n            // \n            this.textBoxItemSearchString.Name = \"textBoxItemSearchString\";\n            this.textBoxItemSearchString.TextBoxWidth = 110;\n            this.textBoxItemSearchString.WatermarkColor = System.Drawing.SystemColors.GrayText;\n            this.textBoxItemSearchString.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBoxItemSearchString_KeyDown);\n            // \n            // itemContainer11\n            // \n            // \n            // \n            // \n            this.itemContainer11.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer11.Name = \"itemContainer11\";\n            this.itemContainer11.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.checkBoxItemExact2,\n            this.comboBoxItem2});\n            // \n            // \n            // \n            this.itemContainer11.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // checkBoxItemExact2\n            // \n            this.checkBoxItemExact2.Name = \"checkBoxItemExact2\";\n            this.checkBoxItemExact2.Text = \"IsExact\";\n            // \n            // comboBoxItem2\n            // \n            this.comboBoxItem2.ComboWidth = 85;\n            this.comboBoxItem2.DropDownHeight = 106;\n            this.comboBoxItem2.ItemHeight = 16;\n            this.comboBoxItem2.Items.AddRange(new object[] {\n            this.comboItem3,\n            this.comboItem4,\n            this.comboItem5,\n            this.comboItem6,\n            this.comboItem7,\n            this.comboItem8,\n            this.comboItem9});\n            this.comboBoxItem2.Name = \"comboBoxItem2\";\n            // \n            // comboItem3\n            // \n            this.comboItem3.Text = \"All\";\n            // \n            // comboItem4\n            // \n            this.comboItem4.Text = \"Eqp\";\n            // \n            // comboItem5\n            // \n            this.comboItem5.Text = \"Item\";\n            // \n            // comboItem6\n            // \n            this.comboItem6.Text = \"Map\";\n            // \n            // comboItem7\n            // \n            this.comboItem7.Text = \"Mob\";\n            // \n            // comboItem8\n            // \n            this.comboItem8.Text = \"Npc\";\n            // \n            // comboItem9\n            // \n            this.comboItem9.Text = \"Skill\";\n            // \n            // itemContainer12\n            // \n            // \n            // \n            // \n            this.itemContainer12.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer12.ItemSpacing = 40;\n            this.itemContainer12.Name = \"itemContainer12\";\n            this.itemContainer12.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.checkBoxItemRegex2,\n            this.buttonItemSearchString});\n            // \n            // \n            // \n            this.itemContainer12.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // checkBoxItemRegex2\n            // \n            this.checkBoxItemRegex2.Name = \"checkBoxItemRegex2\";\n            this.checkBoxItemRegex2.Text = \"Regex\";\n            // \n            // buttonItemSearchString\n            // \n            this.buttonItemSearchString.Name = \"buttonItemSearchString\";\n            this.buttonItemSearchString.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemSelectStringWz,\n            this.buttonItemClearStringWz});\n            this.buttonItemSearchString.Text = \"Search\";\n            this.buttonItemSearchString.Click += new System.EventHandler(this.buttonItemSearchString_Click);\n            // \n            // buttonItemSelectStringWz\n            // \n            this.buttonItemSelectStringWz.Name = \"buttonItemSelectStringWz\";\n            this.buttonItemSelectStringWz.Text = \"&Select String.wz\";\n            this.buttonItemSelectStringWz.Click += new System.EventHandler(this.buttonItemSelectStringWz_Click);\n            // \n            // buttonItemClearStringWz\n            // \n            this.buttonItemClearStringWz.Name = \"buttonItemClearStringWz\";\n            this.buttonItemClearStringWz.Text = \"Clear StringLinker\";\n            this.buttonItemClearStringWz.Click += new System.EventHandler(this.buttonItemClearStringWz_Click);\n            // \n            // ribbonBar1\n            // \n            this.ribbonBar1.AutoOverflowEnabled = true;\n            // \n            // \n            // \n            this.ribbonBar1.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar1.ContainerControlProcessDialogKey = true;\n            this.ribbonBar1.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar1.DragDropSupport = true;\n            this.ribbonBar1.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer14});\n            this.ribbonBar1.Location = new System.Drawing.Point(3, 0);\n            this.ribbonBar1.Name = \"ribbonBar1\";\n            this.ribbonBar1.Size = new System.Drawing.Size(168, 91);\n            this.ribbonBar1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar1.TabIndex = 0;\n            this.ribbonBar1.Text = \"SearchWzNode\";\n            // \n            // \n            // \n            this.ribbonBar1.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar1.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer14\n            // \n            // \n            // \n            // \n            this.itemContainer14.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer14.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer14.Name = \"itemContainer14\";\n            this.itemContainer14.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer15,\n            this.itemContainer16,\n            this.itemContainer17});\n            // \n            // \n            // \n            this.itemContainer14.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer15\n            // \n            // \n            // \n            // \n            this.itemContainer15.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer15.Name = \"itemContainer15\";\n            this.itemContainer15.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.labelItem3,\n            this.textBoxItemSearchWz});\n            // \n            // \n            // \n            this.itemContainer15.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // labelItem3\n            // \n            this.labelItem3.Name = \"labelItem3\";\n            this.labelItem3.Text = \"NodeText\";\n            // \n            // textBoxItemSearchWz\n            // \n            this.textBoxItemSearchWz.MaxLength = 50;\n            this.textBoxItemSearchWz.Name = \"textBoxItemSearchWz\";\n            this.textBoxItemSearchWz.TextBoxWidth = 98;\n            this.textBoxItemSearchWz.WatermarkColor = System.Drawing.SystemColors.GrayText;\n            this.textBoxItemSearchWz.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBoxItemSearchWz_KeyDown);\n            // \n            // itemContainer16\n            // \n            // \n            // \n            // \n            this.itemContainer16.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer16.Name = \"itemContainer16\";\n            this.itemContainer16.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.checkBoxItemExact1,\n            this.comboBoxItem1});\n            // \n            // \n            // \n            this.itemContainer16.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // checkBoxItemExact1\n            // \n            this.checkBoxItemExact1.Name = \"checkBoxItemExact1\";\n            this.checkBoxItemExact1.Text = \"IsExact\";\n            // \n            // comboBoxItem1\n            // \n            this.comboBoxItem1.ComboWidth = 85;\n            this.comboBoxItem1.DropDownHeight = 106;\n            this.comboBoxItem1.ItemHeight = 16;\n            this.comboBoxItem1.Items.AddRange(new object[] {\n            this.comboItem10,\n            this.comboItem11,\n            this.comboItem12,\n            this.comboItem19});\n            this.comboBoxItem1.Name = \"comboBoxItem1\";\n            // \n            // comboItem10\n            // \n            this.comboItem10.Text = \"wzNode\";\n            // \n            // comboItem11\n            // \n            this.comboItem11.Text = \"imageNode\";\n            // \n            // comboItem12\n            // \n            this.comboItem12.Text = \"imageValue\";\n            // \n            // itemContainer17\n            // \n            // \n            // \n            // \n            this.itemContainer17.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer17.ItemSpacing = 30;\n            this.itemContainer17.Name = \"itemContainer17\";\n            this.itemContainer17.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.checkBoxItemRegex1,\n            this.buttonItemSearchWz});\n            // \n            // \n            // \n            this.itemContainer17.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // checkBoxItemRegex1\n            // \n            this.checkBoxItemRegex1.Name = \"checkBoxItemRegex1\";\n            this.checkBoxItemRegex1.Text = \"Regex\";\n            // \n            // buttonItemSearchWz\n            // \n            this.buttonItemSearchWz.Name = \"buttonItemSearchWz\";\n            this.buttonItemSearchWz.Text = \"SearchNext\";\n            this.buttonItemSearchWz.Click += new System.EventHandler(this.buttonItemSearchWz_Click);\n            // \n            // ribbonPanel3\n            // \n            this.ribbonPanel3.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonPanel3.Controls.Add(this.ribbonBar11);\n            this.ribbonPanel3.Controls.Add(this.ribbonBar7);\n            this.ribbonPanel3.Controls.Add(this.ribbonBar6);\n            this.ribbonPanel3.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.ribbonPanel3.Location = new System.Drawing.Point(0, 56);\n            this.ribbonPanel3.Name = \"ribbonPanel3\";\n            this.ribbonPanel3.Padding = new System.Windows.Forms.Padding(3, 0, 3, 3);\n            this.ribbonPanel3.Size = new System.Drawing.Size(740, 94);\n            // \n            // \n            // \n            this.ribbonPanel3.Style.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonPanel3.StyleMouseDown.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonPanel3.StyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonPanel3.TabIndex = 3;\n            this.ribbonPanel3.Visible = false;\n            // \n            // ribbonBar11\n            // \n            this.ribbonBar11.AutoOverflowEnabled = true;\n            // \n            // \n            // \n            this.ribbonBar11.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar11.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar11.ContainerControlProcessDialogKey = true;\n            this.ribbonBar11.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar11.DragDropSupport = true;\n            this.ribbonBar11.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItem1});\n            this.ribbonBar11.Location = new System.Drawing.Point(110, 0);\n            this.ribbonBar11.Name = \"ribbonBar11\";\n            this.ribbonBar11.Size = new System.Drawing.Size(69, 91);\n            this.ribbonBar11.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar11.TabIndex = 2;\n            this.ribbonBar11.Text = \"测试用\";\n            // \n            // \n            // \n            this.ribbonBar11.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar11.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItem1\n            // \n            this.buttonItem1.Name = \"buttonItem1\";\n            this.buttonItem1.SubItemsExpandWidth = 14;\n            this.buttonItem1.Text = \"不要按我\";\n            this.buttonItem1.Click += new System.EventHandler(this.buttonItem1_Click);\n            // \n            // ribbonBar7\n            // \n            this.ribbonBar7.AutoOverflowEnabled = true;\n            // \n            // \n            // \n            this.ribbonBar7.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar7.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar7.ContainerControlProcessDialogKey = true;\n            this.ribbonBar7.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar7.DragDropSupport = true;\n            this.ribbonBar7.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemUpdate});\n            this.ribbonBar7.Location = new System.Drawing.Point(53, 0);\n            this.ribbonBar7.Name = \"ribbonBar7\";\n            this.ribbonBar7.Size = new System.Drawing.Size(57, 91);\n            this.ribbonBar7.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar7.TabIndex = 1;\n            this.ribbonBar7.Text = \"Update\";\n            // \n            // \n            // \n            this.ribbonBar7.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar7.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemUpdate\n            // \n            this.buttonItemUpdate.Name = \"buttonItemUpdate\";\n            this.buttonItemUpdate.SubItemsExpandWidth = 14;\n            this.buttonItemUpdate.Text = \"Update\";\n            this.buttonItemUpdate.Click += new System.EventHandler(this.buttonItemUpdate_Click);\n            // \n            // ribbonBar6\n            // \n            this.ribbonBar6.AutoOverflowEnabled = true;\n            // \n            // \n            // \n            this.ribbonBar6.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar6.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar6.ContainerControlProcessDialogKey = true;\n            this.ribbonBar6.Dock = System.Windows.Forms.DockStyle.Left;\n            this.ribbonBar6.DragDropSupport = true;\n            this.ribbonBar6.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemAbout});\n            this.ribbonBar6.Location = new System.Drawing.Point(3, 0);\n            this.ribbonBar6.Name = \"ribbonBar6\";\n            this.ribbonBar6.Size = new System.Drawing.Size(50, 91);\n            this.ribbonBar6.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar6.TabIndex = 0;\n            this.ribbonBar6.Text = \"About\";\n            // \n            // \n            // \n            this.ribbonBar6.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar6.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemAbout\n            // \n            this.buttonItemAbout.Name = \"buttonItemAbout\";\n            this.buttonItemAbout.SubItemsExpandWidth = 14;\n            this.buttonItemAbout.Text = \"About\";\n            this.buttonItemAbout.Click += new System.EventHandler(this.buttonItemAbout_Click);\n            // \n            // ribbonTabItem1\n            // \n            this.ribbonTabItem1.Checked = true;\n            this.ribbonTabItem1.Name = \"ribbonTabItem1\";\n            this.ribbonTabItem1.Panel = this.ribbonPanel1;\n            this.ribbonTabItem1.Tag = \"Tools\";\n            this.ribbonTabItem1.Text = \"&Tools\";\n            // \n            // ribbonTabItem2\n            // \n            this.ribbonTabItem2.Name = \"ribbonTabItem2\";\n            this.ribbonTabItem2.Panel = this.ribbonPanel2;\n            this.ribbonTabItem2.Tag = \"Modules\";\n            this.ribbonTabItem2.Text = \"&Modules\";\n            // \n            // ribbonTabItem3\n            // \n            this.ribbonTabItem3.Name = \"ribbonTabItem3\";\n            this.ribbonTabItem3.Panel = this.ribbonPanel3;\n            this.ribbonTabItem3.Tag = \"Help\";\n            this.ribbonTabItem3.Text = \"&Help\";\n            // \n            // buttonItemStyle\n            // \n            this.buttonItemStyle.AutoExpandOnClick = true;\n            this.buttonItemStyle.ItemAlignment = DevComponents.DotNetBar.eItemAlignment.Far;\n            this.buttonItemStyle.Name = \"buttonItemStyle\";\n            this.buttonItemStyle.Text = \"&Style\";\n            // \n            // office2007StartButton1\n            // \n            this.office2007StartButton1.AutoExpandOnClick = true;\n            this.office2007StartButton1.CanCustomize = false;\n            this.office2007StartButton1.HotFontUnderline = true;\n            this.office2007StartButton1.HotTrackingStyle = DevComponents.DotNetBar.eHotTrackingStyle.Image;\n            this.office2007StartButton1.ImagePaddingHorizontal = 2;\n            this.office2007StartButton1.ImagePaddingVertical = 2;\n            this.office2007StartButton1.Name = \"office2007StartButton1\";\n            this.office2007StartButton1.ShowSubItems = false;\n            this.office2007StartButton1.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer1});\n            this.office2007StartButton1.Text = \"&File\";\n            // \n            // itemContainer1\n            // \n            // \n            // \n            // \n            this.itemContainer1.BackgroundStyle.Class = \"RibbonFileMenuContainer\";\n            this.itemContainer1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer1.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer1.Name = \"itemContainer1\";\n            this.itemContainer1.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer2,\n            this.itemContainer4});\n            // \n            // \n            // \n            this.itemContainer1.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer2\n            // \n            // \n            // \n            // \n            this.itemContainer2.BackgroundStyle.Class = \"RibbonFileMenuTwoColumnContainer\";\n            this.itemContainer2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer2.ItemSpacing = 0;\n            this.itemContainer2.Name = \"itemContainer2\";\n            this.itemContainer2.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer3,\n            this.galleryContainerRecent});\n            // \n            // \n            // \n            this.itemContainer2.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // itemContainer3\n            // \n            // \n            // \n            // \n            this.itemContainer3.BackgroundStyle.Class = \"RibbonFileMenuColumnOneContainer\";\n            this.itemContainer3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer3.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer3.MinimumSize = new System.Drawing.Size(120, 0);\n            this.itemContainer3.Name = \"itemContainer3\";\n            this.itemContainer3.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.btnItemOpenWz,\n            this.btnItemOpenImg,\n            this.buttonItemClose,\n            this.buttonItemCloseAll});\n            // \n            // \n            // \n            this.itemContainer3.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // btnItemOpenWz\n            // \n            this.btnItemOpenWz.ButtonStyle = DevComponents.DotNetBar.eButtonStyle.ImageAndText;\n            this.btnItemOpenWz.Name = \"btnItemOpenWz\";\n            this.btnItemOpenWz.SubItemsExpandWidth = 24;\n            this.btnItemOpenWz.Text = \"&Open Wz...\";\n            this.btnItemOpenWz.Click += new System.EventHandler(this.btnItemOpenWz_Click);\n            // \n            // btnItemOpenImg\n            // \n            this.btnItemOpenImg.Name = \"btnItemOpenImg\";\n            this.btnItemOpenImg.Text = \"Open Img...\";\n            this.btnItemOpenImg.Click += new System.EventHandler(this.btnItemOpenImg_Click);\n            // \n            // buttonItemClose\n            // \n            this.buttonItemClose.ButtonStyle = DevComponents.DotNetBar.eButtonStyle.ImageAndText;\n            this.buttonItemClose.Name = \"buttonItemClose\";\n            this.buttonItemClose.SubItemsExpandWidth = 24;\n            this.buttonItemClose.Text = \"&Close...\";\n            this.buttonItemClose.Click += new System.EventHandler(this.buttonItemClose_Click);\n            // \n            // buttonItemCloseAll\n            // \n            this.buttonItemCloseAll.Name = \"buttonItemCloseAll\";\n            this.buttonItemCloseAll.Text = \"Close All...\";\n            this.buttonItemCloseAll.Click += new System.EventHandler(this.buttonItemCloseAll_Click);\n            // \n            // galleryContainerRecent\n            // \n            // \n            // \n            // \n            this.galleryContainerRecent.BackgroundStyle.Class = \"RibbonFileMenuColumnTwoContainer\";\n            this.galleryContainerRecent.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.galleryContainerRecent.EnableGalleryPopup = false;\n            this.galleryContainerRecent.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.galleryContainerRecent.MinimumSize = new System.Drawing.Size(180, 140);\n            this.galleryContainerRecent.MultiLine = false;\n            this.galleryContainerRecent.Name = \"galleryContainerRecent\";\n            this.galleryContainerRecent.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.labelItem8});\n            // \n            // \n            // \n            this.galleryContainerRecent.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // labelItem8\n            // \n            this.labelItem8.BorderSide = DevComponents.DotNetBar.eBorderSide.Bottom;\n            this.labelItem8.BorderType = DevComponents.DotNetBar.eBorderType.Etched;\n            this.labelItem8.CanCustomize = false;\n            this.labelItem8.ForeColor = System.Drawing.SystemColors.ControlText;\n            this.labelItem8.Name = \"labelItem8\";\n            this.labelItem8.PaddingBottom = 2;\n            this.labelItem8.PaddingTop = 2;\n            this.labelItem8.Stretch = true;\n            this.labelItem8.Text = \"Recent Documents\";\n            // \n            // itemContainer4\n            // \n            // \n            // \n            // \n            this.itemContainer4.BackgroundStyle.Class = \"RibbonFileMenuBottomContainer\";\n            this.itemContainer4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer4.CanCustomize = false;\n            this.itemContainer4.HorizontalItemAlignment = DevComponents.DotNetBar.eHorizontalItemsAlignment.Right;\n            this.itemContainer4.Name = \"itemContainer4\";\n            this.itemContainer4.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.btnItemOptions,\n            this.buttonItem13});\n            // \n            // \n            // \n            this.itemContainer4.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // btnItemOptions\n            // \n            this.btnItemOptions.ButtonStyle = DevComponents.DotNetBar.eButtonStyle.ImageAndText;\n            this.btnItemOptions.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnItemOptions.Name = \"btnItemOptions\";\n            this.btnItemOptions.SubItemsExpandWidth = 24;\n            this.btnItemOptions.Text = \"Opt&ions\";\n            this.btnItemOptions.Click += new System.EventHandler(this.btnItemOptions_Click);\n            // \n            // buttonItem13\n            // \n            this.buttonItem13.ButtonStyle = DevComponents.DotNetBar.eButtonStyle.ImageAndText;\n            this.buttonItem13.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonItem13.Enabled = false;\n            this.buttonItem13.Name = \"buttonItem13\";\n            this.buttonItem13.SubItemsExpandWidth = 24;\n            this.buttonItem13.Text = \"E&xit\";\n            // \n            // styleManager1\n            // \n            this.styleManager1.ManagerStyle = DevComponents.DotNetBar.eStyle.Office2007VistaGlass;\n            this.styleManager1.MetroColorParameters = new DevComponents.DotNetBar.Metro.ColorTables.MetroColorGeneratorParameters(System.Drawing.Color.White, System.Drawing.Color.FromArgb(((int)(((byte)(43)))), ((int)(((byte)(87)))), ((int)(((byte)(154))))));\n            // \n            // colorPickerDropDown1\n            // \n            this.colorPickerDropDown1.Name = \"colorPickerDropDown1\";\n            this.colorPickerDropDown1.SubItemsExpandWidth = 14;\n            this.colorPickerDropDown1.Text = \"colorPickerDropDown1\";\n            // \n            // ribbonBar2\n            // \n            this.ribbonBar2.AutoOverflowEnabled = false;\n            // \n            // \n            // \n            this.ribbonBar2.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar2.CanCustomize = false;\n            this.ribbonBar2.ContainerControlProcessDialogKey = true;\n            this.ribbonBar2.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.ribbonBar2.DragDropSupport = true;\n            this.ribbonBar2.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.labelItemStatus,\n            this.progressBarItem1});\n            this.ribbonBar2.Location = new System.Drawing.Point(5, 486);\n            this.ribbonBar2.Name = \"ribbonBar2\";\n            this.ribbonBar2.Size = new System.Drawing.Size(740, 24);\n            this.ribbonBar2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar2.TabIndex = 1;\n            this.ribbonBar2.Text = \"ribbonBar2\";\n            // \n            // \n            // \n            this.ribbonBar2.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar2.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar2.TitleVisible = false;\n            // \n            // labelItemStatus\n            // \n            this.labelItemStatus.Name = \"labelItemStatus\";\n            this.labelItemStatus.Text = \"kira~\";\n            this.labelItemStatus.TextChanged += new System.EventHandler(this.labelItemStatus_TextChanged);\n            // \n            // progressBarItem1\n            // \n            // \n            // \n            // \n            this.progressBarItem1.BackStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.progressBarItem1.CanCustomize = false;\n            this.progressBarItem1.ChunkGradientAngle = 0F;\n            this.progressBarItem1.ItemAlignment = DevComponents.DotNetBar.eItemAlignment.Far;\n            this.progressBarItem1.MenuVisibility = DevComponents.DotNetBar.eMenuVisibility.VisibleAlways;\n            this.progressBarItem1.Name = \"progressBarItem1\";\n            this.progressBarItem1.RecentlyUsed = false;\n            this.progressBarItem1.ShowSubItems = false;\n            this.progressBarItem1.Text = \"progressBarItem1\";\n            this.progressBarItem1.Width = 100;\n            // \n            // panelExMain\n            // \n            this.panelExMain.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelExMain.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelExMain.Controls.Add(this.panelExRight);\n            this.panelExMain.Controls.Add(this.panelExLeft);\n            this.panelExMain.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelExMain.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.panelExMain.Location = new System.Drawing.Point(5, 154);\n            this.panelExMain.Name = \"panelExMain\";\n            this.panelExMain.Size = new System.Drawing.Size(740, 234);\n            this.panelExMain.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelExMain.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelExMain.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelExMain.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelExMain.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelExMain.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelExMain.Style.GradientAngle = 90;\n            this.panelExMain.TabIndex = 2;\n            // \n            // panelExRight\n            // \n            this.panelExRight.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.panelExRight.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelExRight.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelExRight.Controls.Add(this.superTabControl1);\n            this.panelExRight.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelExRight.Location = new System.Drawing.Point(207, 3);\n            this.panelExRight.Name = \"panelExRight\";\n            this.panelExRight.Size = new System.Drawing.Size(530, 228);\n            this.panelExRight.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelExRight.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelExRight.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelExRight.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelExRight.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelExRight.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelExRight.Style.GradientAngle = 90;\n            this.panelExRight.TabIndex = 1;\n            // \n            // superTabControl1\n            // \n            // \n            // \n            // \n            // \n            // \n            // \n            this.superTabControl1.ControlBox.CloseBox.Name = \"\";\n            // \n            // \n            // \n            this.superTabControl1.ControlBox.MenuBox.Name = \"\";\n            this.superTabControl1.ControlBox.Name = \"\";\n            this.superTabControl1.ControlBox.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.superTabControl1.ControlBox.MenuBox,\n            this.superTabControl1.ControlBox.CloseBox});\n            this.superTabControl1.Controls.Add(this.superTabControlPanel1);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel2);\n            this.superTabControl1.Controls.Add(this.superTabControlPanel3);\n            this.superTabControl1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControl1.Location = new System.Drawing.Point(0, 0);\n            this.superTabControl1.Name = \"superTabControl1\";\n            this.superTabControl1.ReorderTabsEnabled = true;\n            this.superTabControl1.SelectedTabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Bold);\n            this.superTabControl1.SelectedTabIndex = 0;\n            this.superTabControl1.Size = new System.Drawing.Size(530, 228);\n            this.superTabControl1.TabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.superTabControl1.TabIndex = 0;\n            this.superTabControl1.Tabs.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.btnNodeBack,\n            this.btnNodeForward,\n            this.superTabItem1,\n            this.superTabItem2,\n            this.superTabItem3});\n            this.superTabControl1.Text = \"superTabControl1\";\n            // \n            // superTabControlPanel1\n            // \n            this.superTabControlPanel1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTabControlPanel1.Controls.Add(this.panelEx2);\n            this.superTabControlPanel1.Controls.Add(this.expandableSplitter1);\n            this.superTabControlPanel1.Controls.Add(this.panelEx1);\n            this.superTabControlPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel1.Location = new System.Drawing.Point(0, 0);\n            this.superTabControlPanel1.Name = \"superTabControlPanel1\";\n            this.superTabControlPanel1.Size = new System.Drawing.Size(530, 228);\n            this.superTabControlPanel1.TabIndex = 1;\n            this.superTabControlPanel1.TabItem = this.superTabItem1;\n            // \n            // panelEx2\n            // \n            this.panelEx2.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx2.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx2.Controls.Add(this.pictureBoxEx1);\n            this.panelEx2.Controls.Add(this.ribbonBar5);\n            this.panelEx2.Controls.Add(this.textBoxX1);\n            this.panelEx2.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.panelEx2.Location = new System.Drawing.Point(238, 0);\n            this.panelEx2.Name = \"panelEx2\";\n            this.panelEx2.Size = new System.Drawing.Size(292, 228);\n            this.panelEx2.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx2.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx2.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx2.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx2.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx2.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx2.Style.GradientAngle = 90;\n            this.panelEx2.TabIndex = 2;\n            // \n            // pictureBoxEx1\n            // \n            this.pictureBoxEx1.AutoAdjustPosition = true;\n            this.pictureBoxEx1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.pictureBoxEx1.FrameInterval = 30;\n            this.pictureBoxEx1.GlobalScale = 1F;\n            this.pictureBoxEx1.IsPlaying = true;\n            this.pictureBoxEx1.Location = new System.Drawing.Point(0, 79);\n            this.pictureBoxEx1.MouseDragEnabled = true;\n            this.pictureBoxEx1.MouseDragSaveEnabled = true;\n            this.pictureBoxEx1.Name = \"pictureBoxEx1\";\n            this.pictureBoxEx1.Padding = new System.Windows.Forms.Padding(0, 14, 0, 0);\n            this.pictureBoxEx1.PictureName = null;\n            this.pictureBoxEx1.ShowInfo = true;\n            this.pictureBoxEx1.ShowPositionGridOnDrag = true;\n            this.pictureBoxEx1.Size = new System.Drawing.Size(292, 123);\n            this.pictureBoxEx1.TabIndex = 7;\n            this.pictureBoxEx1.Text = \"pictureBoxEx1\";\n            // \n            // ribbonBar5\n            // \n            this.ribbonBar5.AutoOverflowEnabled = false;\n            // \n            // \n            // \n            this.ribbonBar5.BackgroundMouseOverStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar5.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar5.ContainerControlProcessDialogKey = true;\n            this.ribbonBar5.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.ribbonBar5.DragDropSupport = true;\n            this.ribbonBar5.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.cmbItemAniNames,\n            this.cmbItemSkins,\n            this.buttonItemSaveImage,\n            this.buttonItemGif,\n            this.colorPickerPicBoxBgColor});\n            this.ribbonBar5.Location = new System.Drawing.Point(0, 202);\n            this.ribbonBar5.Name = \"ribbonBar5\";\n            this.ribbonBar5.Size = new System.Drawing.Size(292, 26);\n            this.ribbonBar5.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.ribbonBar5.TabIndex = 2;\n            this.ribbonBar5.Text = \"ribbonBar5\";\n            // \n            // \n            // \n            this.ribbonBar5.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // \n            // \n            this.ribbonBar5.TitleStyleMouseOver.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.ribbonBar5.TitleVisible = false;\n            // \n            // cmbItemAniNames\n            // \n            this.cmbItemAniNames.ComboWidth = 80;\n            this.cmbItemAniNames.DropDownHeight = 106;\n            this.cmbItemAniNames.DropDownWidth = 180;\n            this.cmbItemAniNames.ItemHeight = 14;\n            this.cmbItemAniNames.Name = \"cmbItemAniNames\";\n            this.cmbItemAniNames.SelectedIndexChanged += new System.EventHandler(this.cmbItemAniNames_SelectedIndexChanged);\n            // \n            // cmbItemSkins\n            // \n            this.cmbItemSkins.ComboWidth = 80;\n            this.cmbItemSkins.DropDownHeight = 106;\n            this.cmbItemSkins.DropDownWidth = 180;\n            this.cmbItemSkins.ItemHeight = 14;\n            this.cmbItemSkins.Name = \"cmbItemSkins\";\n            this.cmbItemSkins.Visible = false;\n            this.cmbItemSkins.SelectedIndexChanged += new System.EventHandler(this.cmbItemSkins_SelectedIndexChanged);\n            // \n            // buttonItemSaveImage\n            // \n            this.buttonItemSaveImage.Name = \"buttonItemSaveImage\";\n            this.buttonItemSaveImage.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer27});\n            this.buttonItemSaveImage.SubItemsExpandWidth = 14;\n            this.buttonItemSaveImage.Text = \"SavePicture\";\n            this.buttonItemSaveImage.Click += new System.EventHandler(this.buttonItemSaveImage_Click);\n            // \n            // itemContainer27\n            // \n            // \n            // \n            // \n            this.itemContainer27.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer27.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer27.Name = \"itemContainer27\";\n            this.itemContainer27.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemAutoSave,\n            this.buttonItemAutoSaveFolder,\n            this.buttonItemSaveWithOptions});\n            // \n            // \n            // \n            this.itemContainer27.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemAutoSave\n            // \n            this.buttonItemAutoSave.AutoCheckOnClick = true;\n            this.buttonItemAutoSave.Name = \"buttonItemAutoSave\";\n            this.buttonItemAutoSave.Text = \"Auto Save\";\n            this.buttonItemAutoSave.Click += new System.EventHandler(this.buttonItemAutoSave_Click);\n            // \n            // buttonItemAutoSaveFolder\n            // \n            this.buttonItemAutoSaveFolder.Name = \"buttonItemAutoSaveFolder\";\n            this.buttonItemAutoSaveFolder.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.labelItemAutoSaveFolder});\n            this.buttonItemAutoSaveFolder.Text = \"Select Folder\";\n            this.buttonItemAutoSaveFolder.Click += new System.EventHandler(this.buttonItemAutoSaveFolder_Click);\n            // \n            // labelItemAutoSaveFolder\n            // \n            this.labelItemAutoSaveFolder.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(221)))), ((int)(((byte)(231)))), ((int)(((byte)(238)))));\n            this.labelItemAutoSaveFolder.BorderSide = DevComponents.DotNetBar.eBorderSide.Bottom;\n            this.labelItemAutoSaveFolder.BorderType = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.labelItemAutoSaveFolder.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(21)))), ((int)(((byte)(110)))));\n            this.labelItemAutoSaveFolder.Name = \"labelItemAutoSaveFolder\";\n            this.labelItemAutoSaveFolder.PaddingBottom = 1;\n            this.labelItemAutoSaveFolder.PaddingLeft = 10;\n            this.labelItemAutoSaveFolder.PaddingTop = 1;\n            this.labelItemAutoSaveFolder.SingleLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(197)))), ((int)(((byte)(197)))), ((int)(((byte)(197)))));\n            this.labelItemAutoSaveFolder.Text = \"labelItem1\";\n            this.labelItemAutoSaveFolder.Click += new System.EventHandler(this.labelItemAutoSaveFolder_Click);\n            // \n            // buttonItemSaveWithOptions\n            // \n            this.buttonItemSaveWithOptions.Name = \"buttonItemSaveWithOptions\";\n            this.buttonItemSaveWithOptions.Text = \"Save with options\";\n            this.buttonItemSaveWithOptions.Click += new System.EventHandler(this.buttonItemSaveWithOptions_Click);\n            // \n            // buttonItemGif\n            // \n            this.buttonItemGif.Name = \"buttonItemGif\";\n            this.buttonItemGif.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.itemContainer36});\n            this.buttonItemGif.SubItemsExpandWidth = 14;\n            this.buttonItemGif.Text = \"ExtractGif\";\n            this.buttonItemGif.Click += new System.EventHandler(this.buttonItemGif_Click);\n            // \n            // itemContainer36\n            // \n            // \n            // \n            // \n            this.itemContainer36.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemContainer36.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemContainer36.Name = \"itemContainer36\";\n            this.itemContainer36.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.buttonItemExtractGifEx,\n            this.buttonItemGifSetting});\n            // \n            // \n            // \n            this.itemContainer36.TitleStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            // \n            // buttonItemExtractGifEx\n            // \n            this.buttonItemExtractGifEx.Name = \"buttonItemExtractGifEx\";\n            this.buttonItemExtractGifEx.Text = \"ExtractGifEx\";\n            this.buttonItemExtractGifEx.Tooltip = \"对所选节点的全部子节点提取帧动画，而不使用序数节点名称。\";\n            this.buttonItemExtractGifEx.Click += new System.EventHandler(this.buttonItemGif_Click);\n            // \n            // buttonItemGifSetting\n            // \n            this.buttonItemGifSetting.Name = \"buttonItemGifSetting\";\n            this.buttonItemGifSetting.Text = \"GifSettings\";\n            this.buttonItemGifSetting.Click += new System.EventHandler(this.buttonItemGifSetting_Click);\n            // \n            // colorPickerPicBoxBgColor\n            // \n            this.colorPickerPicBoxBgColor.AutoExpandOnClick = true;\n            this.colorPickerPicBoxBgColor.BeginGroup = true;\n            this.colorPickerPicBoxBgColor.ImagePaddingHorizontal = 6;\n            this.colorPickerPicBoxBgColor.ImagePaddingVertical = 0;\n            this.colorPickerPicBoxBgColor.ImagePosition = DevComponents.DotNetBar.eImagePosition.Bottom;\n            this.colorPickerPicBoxBgColor.Name = \"colorPickerPicBoxBgColor\";\n            this.colorPickerPicBoxBgColor.SelectedColorImageRectangle = new System.Drawing.Rectangle(0, 0, 100, 100);\n            this.colorPickerPicBoxBgColor.ShowSubItems = false;\n            this.colorPickerPicBoxBgColor.SubItemsExpandWidth = 14;\n            this.colorPickerPicBoxBgColor.Symbol = \"57914\";\n            this.colorPickerPicBoxBgColor.SymbolSet = DevComponents.DotNetBar.eSymbolSet.Material;\n            this.colorPickerPicBoxBgColor.SymbolSize = 12F;\n            this.colorPickerPicBoxBgColor.Tooltip = \"Set Background Color for Image Viewer\";\n            this.colorPickerPicBoxBgColor.SelectedColorChanged += new System.EventHandler(this.colorPickerPicBoxBgColor_SelectedColorChanged);\n            // \n            // textBoxX1\n            // \n            // \n            // \n            // \n            this.textBoxX1.Border.Class = \"TextBoxBorder\";\n            this.textBoxX1.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.textBoxX1.DisabledBackColor = System.Drawing.Color.White;\n            this.textBoxX1.Dock = System.Windows.Forms.DockStyle.Top;\n            this.textBoxX1.Location = new System.Drawing.Point(0, 0);\n            this.textBoxX1.Multiline = true;\n            this.textBoxX1.Name = \"textBoxX1\";\n            this.textBoxX1.ReadOnly = true;\n            this.textBoxX1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;\n            this.textBoxX1.Size = new System.Drawing.Size(292, 79);\n            this.textBoxX1.TabIndex = 0;\n            // \n            // expandableSplitter1\n            // \n            this.expandableSplitter1.BackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter1.BackColor2SchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter1.BackColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandableSplitter1.ExpandActionClick = false;\n            this.expandableSplitter1.ExpandFillColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter1.ExpandFillColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter1.ExpandLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));\n            this.expandableSplitter1.ExpandLineColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandableSplitter1.GripDarkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));\n            this.expandableSplitter1.GripDarkColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandableSplitter1.GripLightColor = System.Drawing.Color.FromArgb(((int)(((byte)(254)))), ((int)(((byte)(254)))), ((int)(((byte)(255)))));\n            this.expandableSplitter1.GripLightColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.expandableSplitter1.HotBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(229)))), ((int)(((byte)(244)))), ((int)(((byte)(252)))));\n            this.expandableSplitter1.HotBackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(104)))), ((int)(((byte)(179)))), ((int)(((byte)(219)))));\n            this.expandableSplitter1.HotBackColor2SchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemPressedBackground2;\n            this.expandableSplitter1.HotBackColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemPressedBackground;\n            this.expandableSplitter1.HotExpandFillColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter1.HotExpandFillColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter1.HotExpandLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));\n            this.expandableSplitter1.HotExpandLineColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandableSplitter1.HotGripDarkColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter1.HotGripDarkColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter1.HotGripLightColor = System.Drawing.Color.FromArgb(((int)(((byte)(254)))), ((int)(((byte)(254)))), ((int)(((byte)(255)))));\n            this.expandableSplitter1.HotGripLightColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.expandableSplitter1.Location = new System.Drawing.Point(233, 0);\n            this.expandableSplitter1.Name = \"expandableSplitter1\";\n            this.expandableSplitter1.Size = new System.Drawing.Size(5, 228);\n            this.expandableSplitter1.Style = DevComponents.DotNetBar.eSplitterStyle.Office2007;\n            this.expandableSplitter1.TabIndex = 1;\n            this.expandableSplitter1.TabStop = false;\n            // \n            // panelEx1\n            // \n            this.panelEx1.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx1.Controls.Add(this.advTree3);\n            this.panelEx1.Controls.Add(this.listViewExWzDetail);\n            this.panelEx1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx1.Dock = System.Windows.Forms.DockStyle.Left;\n            this.panelEx1.Location = new System.Drawing.Point(0, 0);\n            this.panelEx1.Name = \"panelEx1\";\n            this.panelEx1.Size = new System.Drawing.Size(233, 228);\n            this.panelEx1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx1.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx1.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx1.Style.GradientAngle = 90;\n            this.panelEx1.TabIndex = 0;\n            // \n            // advTree3\n            // \n            this.advTree3.AccessibleRole = System.Windows.Forms.AccessibleRole.Outline;\n            this.advTree3.AllowDrop = true;\n            this.advTree3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.advTree3.BackColor = System.Drawing.SystemColors.Window;\n            // \n            // \n            // \n            this.advTree3.BackgroundStyle.Class = \"TreeBorderKey\";\n            this.advTree3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.advTree3.Columns.Add(this.columnHeader3);\n            this.advTree3.Columns.Add(this.columnHeader4);\n            this.advTree3.Columns.Add(this.columnHeader5);\n            this.advTree3.ContextMenuStrip = this.contextMenuStrip2;\n            this.advTree3.DragDropEnabled = false;\n            this.advTree3.ImageList = this.imageList1;\n            this.advTree3.Location = new System.Drawing.Point(3, 83);\n            this.advTree3.Name = \"advTree3\";\n            this.advTree3.NodesConnector = this.nodeConnector3;\n            this.advTree3.NodeStyle = this.elementStyle3;\n            this.advTree3.PathSeparator = \";\";\n            this.advTree3.Size = new System.Drawing.Size(227, 142);\n            this.advTree3.Styles.Add(this.elementStyle3);\n            this.advTree3.TabIndex = 1;\n            this.advTree3.Text = \"advTree3\";\n            this.advTree3.AfterNodeSelect += new DevComponents.AdvTree.AdvTreeNodeEventHandler(this.advTree3_AfterNodeSelect);\n            // \n            // columnHeader3\n            // \n            this.columnHeader3.Name = \"columnHeader3\";\n            this.columnHeader3.Text = \"ImageNode\";\n            this.columnHeader3.Width.Absolute = 150;\n            // \n            // columnHeader4\n            // \n            this.columnHeader4.Name = \"columnHeader4\";\n            this.columnHeader4.Text = \"Value\";\n            this.columnHeader4.Width.Absolute = 150;\n            // \n            // columnHeader5\n            // \n            this.columnHeader5.Name = \"columnHeader5\";\n            this.columnHeader5.Text = \"ValueType\";\n            this.columnHeader5.Width.Absolute = 150;\n            // \n            // contextMenuStrip2\n            // \n            this.contextMenuStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {\n            this.tsmi2SaveAs,\n            this.tsmi2HandleUol,\n            this.tsmi2Splitter1,\n            this.tsmi2ExpandAll,\n            this.tsmi2CollapseAll,\n            this.toolStripMenuItem1,\n            this.tsmi2ExpandLevel,\n            this.tsmi2CollapseLevel,\n            this.toolStripMenuItem2,\n            this.tsmi2Prev,\n            this.tsmi2Next,\n            this.toolStripMenuItem4,\n            this.tsmi2CopyFullPath});\n            this.contextMenuStrip2.Name = \"contextMenuStrip2\";\n            this.contextMenuStrip2.Size = new System.Drawing.Size(196, 226);\n            this.contextMenuStrip2.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip2_Opening);\n            // \n            // tsmi2SaveAs\n            // \n            this.tsmi2SaveAs.Name = \"tsmi2SaveAs\";\n            this.tsmi2SaveAs.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S)));\n            this.tsmi2SaveAs.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2SaveAs.Text = \"Save as...\";\n            this.tsmi2SaveAs.Click += new System.EventHandler(this.tsmi2SaveAs_Click);\n            // \n            // tsmi2HandleUol\n            // \n            this.tsmi2HandleUol.Name = \"tsmi2HandleUol\";\n            this.tsmi2HandleUol.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2HandleUol.Text = \"Handle Uol\";\n            this.tsmi2HandleUol.Click += new System.EventHandler(this.tsmi2HandleUol_Click);\n            // \n            // tsmi2Splitter1\n            // \n            this.tsmi2Splitter1.Name = \"tsmi2Splitter1\";\n            this.tsmi2Splitter1.Size = new System.Drawing.Size(192, 6);\n            // \n            // tsmi2ExpandAll\n            // \n            this.tsmi2ExpandAll.Name = \"tsmi2ExpandAll\";\n            this.tsmi2ExpandAll.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2ExpandAll.Text = \"&Expand All\";\n            this.tsmi2ExpandAll.Click += new System.EventHandler(this.tsmi2ExpandAll_Click);\n            // \n            // tsmi2CollapseAll\n            // \n            this.tsmi2CollapseAll.Name = \"tsmi2CollapseAll\";\n            this.tsmi2CollapseAll.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2CollapseAll.Text = \"&Collapse All\";\n            this.tsmi2CollapseAll.Click += new System.EventHandler(this.tsmi2CollapseAll_Click);\n            // \n            // toolStripMenuItem1\n            // \n            this.toolStripMenuItem1.Name = \"toolStripMenuItem1\";\n            this.toolStripMenuItem1.Size = new System.Drawing.Size(192, 6);\n            // \n            // tsmi2ExpandLevel\n            // \n            this.tsmi2ExpandLevel.Name = \"tsmi2ExpandLevel\";\n            this.tsmi2ExpandLevel.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2ExpandLevel.Text = \"E&xpand Equal Level\";\n            this.tsmi2ExpandLevel.Click += new System.EventHandler(this.tsmi2ExpandLevel_Click);\n            // \n            // tsmi2CollapseLevel\n            // \n            this.tsmi2CollapseLevel.Name = \"tsmi2CollapseLevel\";\n            this.tsmi2CollapseLevel.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2CollapseLevel.Text = \"C&ollapse Equal Level\";\n            this.tsmi2CollapseLevel.Click += new System.EventHandler(this.tsmi2CollapseLevel_Click);\n            // \n            // toolStripMenuItem2\n            // \n            this.toolStripMenuItem2.Name = \"toolStripMenuItem2\";\n            this.toolStripMenuItem2.Size = new System.Drawing.Size(192, 6);\n            // \n            // tsmi2Prev\n            // \n            this.tsmi2Prev.Name = \"tsmi2Prev\";\n            this.tsmi2Prev.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Z)));\n            this.tsmi2Prev.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2Prev.Text = \"&Prev Select\";\n            this.tsmi2Prev.Click += new System.EventHandler(this.tsmi2Prev_Click);\n            // \n            // tsmi2Next\n            // \n            this.tsmi2Next.Name = \"tsmi2Next\";\n            this.tsmi2Next.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.X)));\n            this.tsmi2Next.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2Next.Text = \"&Next Select\";\n            this.tsmi2Next.Click += new System.EventHandler(this.tsmi2Next_Click);\n            // \n            // imageList1\n            // \n            this.imageList1.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;\n            this.imageList1.ImageSize = new System.Drawing.Size(16, 16);\n            this.imageList1.TransparentColor = System.Drawing.Color.Transparent;\n            // \n            // nodeConnector3\n            // \n            this.nodeConnector3.LineColor = System.Drawing.SystemColors.ControlText;\n            // \n            // elementStyle3\n            // \n            this.elementStyle3.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.elementStyle3.Name = \"elementStyle3\";\n            this.elementStyle3.TextColor = System.Drawing.SystemColors.ControlText;\n            // \n            // listViewExWzDetail\n            // \n            this.listViewExWzDetail.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.listViewExWzDetail.Border.Class = \"ListViewBorder\";\n            this.listViewExWzDetail.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.listViewExWzDetail.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {\n            this.columnHeader1,\n            this.columnHeader2});\n            this.listViewExWzDetail.DisabledBackColor = System.Drawing.Color.Empty;\n            this.listViewExWzDetail.FullRowSelect = true;\n            this.listViewExWzDetail.GridLines = true;\n            this.listViewExWzDetail.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;\n            this.listViewExWzDetail.HideSelection = false;\n            this.listViewExWzDetail.Location = new System.Drawing.Point(3, 3);\n            this.listViewExWzDetail.MultiSelect = false;\n            this.listViewExWzDetail.Name = \"listViewExWzDetail\";\n            this.listViewExWzDetail.ShowGroups = false;\n            this.listViewExWzDetail.Size = new System.Drawing.Size(227, 78);\n            this.listViewExWzDetail.TabIndex = 0;\n            this.listViewExWzDetail.UseCompatibleStateImageBehavior = false;\n            this.listViewExWzDetail.View = System.Windows.Forms.View.Details;\n            // \n            // columnHeader1\n            // \n            this.columnHeader1.Width = 80;\n            // \n            // columnHeader2\n            // \n            this.columnHeader2.Width = 100;\n            // \n            // superTabItem1\n            // \n            this.superTabItem1.AttachedControl = this.superTabControlPanel1;\n            this.superTabItem1.GlobalItem = false;\n            this.superTabItem1.Name = \"superTabItem1\";\n            this.superTabItem1.Text = \"WzView\";\n            // \n            // superTabControlPanel2\n            // \n            this.superTabControlPanel2.Controls.Add(this.chkHashPngFileName);\n            this.superTabControlPanel2.Controls.Add(this.btnCustomCSS);\n            this.superTabControlPanel2.Controls.Add(this.chkResolvePngLink);\n            this.superTabControlPanel2.Controls.Add(this.chkOutputRemovedImg);\n            this.superTabControlPanel2.Controls.Add(this.chkOutputAddedImg);\n            this.superTabControlPanel2.Controls.Add(this.labelX1);\n            this.superTabControlPanel2.Controls.Add(this.chkOutputPng);\n            this.superTabControlPanel2.Controls.Add(this.cmbComparePng);\n            this.superTabControlPanel2.Controls.Add(this.labelXComp2);\n            this.superTabControlPanel2.Controls.Add(this.labelXComp1);\n            this.superTabControlPanel2.Controls.Add(this.btnEasyCompare);\n            this.superTabControlPanel2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel2.Location = new System.Drawing.Point(0, 0);\n            this.superTabControlPanel2.Name = \"superTabControlPanel2\";\n            this.superTabControlPanel2.Size = new System.Drawing.Size(530, 228);\n            this.superTabControlPanel2.TabIndex = 0;\n            this.superTabControlPanel2.TabItem = this.superTabItem2;\n            // \n            // chkResolvePngLink\n            // \n            // \n            // \n            // \n            this.chkResolvePngLink.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkResolvePngLink.Location = new System.Drawing.Point(318, 34);\n            this.chkResolvePngLink.Name = \"chkResolvePngLink\";\n            this.chkResolvePngLink.Size = new System.Drawing.Size(107, 23);\n            this.chkResolvePngLink.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkResolvePngLink, new DevComponents.DotNetBar.SuperTooltipInfo(\"ResolvePngLink\", \"\", \"对比报告中是否智能解析对比被Link的图片\\r\\n这会过滤掉无用的变更内容\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 90)));\n            this.chkResolvePngLink.TabIndex = 9;\n            this.chkResolvePngLink.Text = \"ResolvePngLink\";\n            // \n            // btnCustomCSS\n            // \n            // \n            // \n            // \n            this.btnCustomCSS.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.btnCustomCSS.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnCustomCSS.Location = new System.Drawing.Point(400, 61);\n            this.btnCustomCSS.Name = \"btnCustomCSS\";\n            this.btnCustomCSS.Size = new System.Drawing.Size(80, 23);\n            this.btnCustomCSS.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.btnCustomCSS, new DevComponents.DotNetBar.SuperTooltipInfo(\"CustomCSS\", \"\", \"修改对比报告的配色。\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 90)));\n            this.btnCustomCSS.TabIndex = 10;\n            this.btnCustomCSS.Text = \"CustomCSS\";\n            this.btnCustomCSS.Click += new System.EventHandler(this.btnCustomCSS_Click);\n            // \n            // chkOutputRemovedImg\n            // \n            // \n            // \n            // \n            this.chkOutputRemovedImg.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkOutputRemovedImg.Location = new System.Drawing.Point(152, 61);\n            this.chkOutputRemovedImg.Name = \"chkOutputRemovedImg\";\n            this.chkOutputRemovedImg.Size = new System.Drawing.Size(135, 23);\n            this.chkOutputRemovedImg.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkOutputRemovedImg, new DevComponents.DotNetBar.SuperTooltipInfo(\"OutputRemovedImg\", \"\", \"对比报告中是否输出被整体移除的Image的完整结构\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkOutputRemovedImg.TabIndex = 8;\n            this.chkOutputRemovedImg.Text = \"OutputRemovedImg\";\n            // \n            // chkOutputAddedImg\n            // \n            // \n            // \n            // \n            this.chkOutputAddedImg.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkOutputAddedImg.Location = new System.Drawing.Point(34, 61);\n            this.chkOutputAddedImg.Name = \"chkOutputAddedImg\";\n            this.chkOutputAddedImg.Size = new System.Drawing.Size(135, 23);\n            this.chkOutputAddedImg.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkOutputAddedImg, new DevComponents.DotNetBar.SuperTooltipInfo(\"OutputAddedImg\", \"\", \"对比报告中是否输出新增Image的完整结构\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkOutputAddedImg.TabIndex = 7;\n            this.chkOutputAddedImg.Text = \"OutputAddedImg\";\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(34, 39);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(44, 16);\n            this.labelX1.TabIndex = 6;\n            this.labelX1.Text = \"wzPng:\";\n            // \n            // chkOutputPng\n            // \n            // \n            // \n            // \n            this.chkOutputPng.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkOutputPng.Checked = true;\n            this.chkOutputPng.CheckState = System.Windows.Forms.CheckState.Checked;\n            this.chkOutputPng.CheckValue = \"Y\";\n            this.chkOutputPng.Location = new System.Drawing.Point(205, 34);\n            this.chkOutputPng.Name = \"chkOutputPng\";\n            this.chkOutputPng.Size = new System.Drawing.Size(107, 23);\n            this.chkOutputPng.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkOutputPng, new DevComponents.DotNetBar.SuperTooltipInfo(\"OutputPngFile\", \"\", \"对比报告中是否输出有差异的图片文件。\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkOutputPng.TabIndex = 5;\n            this.chkOutputPng.Text = \"OutputPngFile\";\n            // \n            // cmbComparePng\n            // \n            this.cmbComparePng.DisplayMember = \"Text\";\n            this.cmbComparePng.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbComparePng.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbComparePng.FormattingEnabled = true;\n            this.cmbComparePng.ItemHeight = 15;\n            this.cmbComparePng.Location = new System.Drawing.Point(83, 36);\n            this.cmbComparePng.Name = \"cmbComparePng\";\n            this.cmbComparePng.Size = new System.Drawing.Size(120, 21);\n            this.cmbComparePng.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.cmbComparePng, new DevComponents.DotNetBar.SuperTooltipInfo(\"PngComparison\", \"\", \"对于对比报告中图片的对比方式。\\r\\nSizeOnly - 仅对比图片大小，可能会遗漏。\\r\\nSizeAndDataLength - 同时对比图片大小和压缩流长度，可能\" +\n            \"会误判。\\r\\nPixel - 像素级对比，非常精确但可能略耗时。\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, true, new System.Drawing.Size(300, 130)));\n            this.cmbComparePng.TabIndex = 4;\n            // \n            // labelXComp2\n            // \n            this.labelXComp2.AutoSize = true;\n            // \n            // \n            // \n            this.labelXComp2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelXComp2.Location = new System.Drawing.Point(3, 113);\n            this.labelXComp2.Name = \"labelXComp2\";\n            this.labelXComp2.Size = new System.Drawing.Size(44, 16);\n            this.labelXComp2.TabIndex = 3;\n            this.labelXComp2.Text = \"detail\";\n            this.labelXComp2.TextLineAlignment = System.Drawing.StringAlignment.Near;\n            // \n            // labelXComp1\n            // \n            this.labelXComp1.AutoSize = true;\n            // \n            // \n            // \n            this.labelXComp1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelXComp1.Location = new System.Drawing.Point(3, 91);\n            this.labelXComp1.Name = \"labelXComp1\";\n            this.labelXComp1.Size = new System.Drawing.Size(31, 16);\n            this.labelXComp1.TabIndex = 1;\n            this.labelXComp1.Text = \"tail\";\n            this.labelXComp1.TextLineAlignment = System.Drawing.StringAlignment.Near;\n            // \n            // btnEasyCompare\n            // \n            this.btnEasyCompare.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.btnEasyCompare.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnEasyCompare.Location = new System.Drawing.Point(3, 3);\n            this.btnEasyCompare.Name = \"btnEasyCompare\";\n            this.btnEasyCompare.Size = new System.Drawing.Size(100, 30);\n            this.btnEasyCompare.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.btnEasyCompare.TabIndex = 0;\n            this.btnEasyCompare.Text = \"Easy Compare\";\n            this.btnEasyCompare.Click += new System.EventHandler(this.btnEasyCompare_Click);\n            // \n            // superTabItem2\n            // \n            this.superTabItem2.AttachedControl = this.superTabControlPanel2;\n            this.superTabItem2.GlobalItem = false;\n            this.superTabItem2.Name = \"superTabItem2\";\n            this.superTabItem2.Text = \"WzCompare\";\n            // \n            // superTabControlPanel3\n            // \n            this.superTabControlPanel3.Controls.Add(this.btnExportSkillOption);\n            this.superTabControlPanel3.Controls.Add(this.btnExportSkill);\n            this.superTabControlPanel3.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.superTabControlPanel3.Location = new System.Drawing.Point(0, 0);\n            this.superTabControlPanel3.Name = \"superTabControlPanel3\";\n            this.superTabControlPanel3.Size = new System.Drawing.Size(530, 228);\n            this.superTabControlPanel3.TabIndex = 0;\n            this.superTabControlPanel3.TabItem = this.superTabItem3;\n            // \n            // btnExportSkillOption\n            // \n            this.btnExportSkillOption.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.btnExportSkillOption.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnExportSkillOption.Location = new System.Drawing.Point(99, 6);\n            this.btnExportSkillOption.Name = \"btnExportSkillOption\";\n            this.btnExportSkillOption.Size = new System.Drawing.Size(111, 23);\n            this.btnExportSkillOption.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.btnExportSkillOption.TabIndex = 1;\n            this.btnExportSkillOption.Text = \"ExportSkillOption\";\n            this.btnExportSkillOption.Click += new System.EventHandler(this.btnExportSkillOption_Click);\n            // \n            // btnExportSkill\n            // \n            this.btnExportSkill.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.btnExportSkill.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.btnExportSkill.Location = new System.Drawing.Point(6, 6);\n            this.btnExportSkill.Name = \"btnExportSkill\";\n            this.btnExportSkill.Size = new System.Drawing.Size(75, 23);\n            this.btnExportSkill.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.btnExportSkill.TabIndex = 0;\n            this.btnExportSkill.Text = \"ExportSkill\";\n            this.btnExportSkill.Click += new System.EventHandler(this.btnExportSkill_Click);\n            // \n            // superTabItem3\n            // \n            this.superTabItem3.AttachedControl = this.superTabControlPanel3;\n            this.superTabItem3.GlobalItem = false;\n            this.superTabItem3.Name = \"superTabItem3\";\n            this.superTabItem3.Text = \"DataBase\";\n            // \n            // btnNodeBack\n            // \n            this.btnNodeBack.ImagePaddingHorizontal = 6;\n            this.btnNodeBack.ImagePaddingVertical = 4;\n            this.btnNodeBack.Name = \"btnNodeBack\";\n            this.btnNodeBack.Symbol = \"\";\n            this.btnNodeBack.SymbolColor = System.Drawing.Color.Gray;\n            this.btnNodeBack.SymbolSize = 12F;\n            this.btnNodeBack.Text = \"back\";\n            this.btnNodeBack.Click += new System.EventHandler(this.btnNodeBack_Click);\n            // \n            // btnNodeForward\n            // \n            this.btnNodeForward.ImagePaddingHorizontal = 6;\n            this.btnNodeForward.ImagePaddingVertical = 4;\n            this.btnNodeForward.Name = \"btnNodeForward\";\n            this.btnNodeForward.Symbol = \"\";\n            this.btnNodeForward.SymbolColor = System.Drawing.Color.Gray;\n            this.btnNodeForward.SymbolSize = 12F;\n            this.btnNodeForward.Text = \"forward\";\n            this.btnNodeForward.Click += new System.EventHandler(this.btnNodeForward_Click);\n            // \n            // panelExLeft\n            // \n            this.panelExLeft.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left)));\n            this.panelExLeft.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelExLeft.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelExLeft.Controls.Add(this.advTree2);\n            this.panelExLeft.Controls.Add(this.expandableSplitter2);\n            this.panelExLeft.Controls.Add(this.advTree1);\n            this.panelExLeft.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelExLeft.Location = new System.Drawing.Point(3, 3);\n            this.panelExLeft.Name = \"panelExLeft\";\n            this.panelExLeft.Size = new System.Drawing.Size(200, 228);\n            this.panelExLeft.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelExLeft.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelExLeft.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelExLeft.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelExLeft.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelExLeft.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelExLeft.Style.GradientAngle = 90;\n            this.panelExLeft.TabIndex = 0;\n            this.panelExLeft.SizeChanged += new System.EventHandler(this.panelExLeft_SizeChanged);\n            // \n            // advTree2\n            // \n            this.advTree2.AccessibleRole = System.Windows.Forms.AccessibleRole.Outline;\n            this.advTree2.AllowDrop = true;\n            this.advTree2.BackColor = System.Drawing.SystemColors.Window;\n            // \n            // \n            // \n            this.advTree2.BackgroundStyle.Class = \"TreeBorderKey\";\n            this.advTree2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.advTree2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.advTree2.DoubleClickTogglesNode = false;\n            this.advTree2.DragDropEnabled = false;\n            this.advTree2.Location = new System.Drawing.Point(0, 157);\n            this.advTree2.Name = \"advTree2\";\n            this.advTree2.NodesConnector = this.nodeConnector2;\n            this.advTree2.NodeStyle = this.elementStyle2;\n            this.advTree2.PathSeparator = \";\";\n            this.advTree2.Size = new System.Drawing.Size(200, 71);\n            this.advTree2.Styles.Add(this.elementStyle2);\n            this.advTree2.TabIndex = 1;\n            this.advTree2.Text = \"advTree2\";\n            this.advTree2.NodeDoubleClick += new DevComponents.AdvTree.TreeNodeMouseEventHandler(this.advTree2_NodeDoubleClick);\n            // \n            // nodeConnector2\n            // \n            this.nodeConnector2.LineColor = System.Drawing.SystemColors.ControlText;\n            // \n            // elementStyle2\n            // \n            this.elementStyle2.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.elementStyle2.Name = \"elementStyle2\";\n            this.elementStyle2.TextColor = System.Drawing.SystemColors.ControlText;\n            // \n            // expandableSplitter2\n            // \n            this.expandableSplitter2.BackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter2.BackColor2SchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter2.BackColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.expandableSplitter2.Dock = System.Windows.Forms.DockStyle.Top;\n            this.expandableSplitter2.ExpandActionClick = false;\n            this.expandableSplitter2.ExpandFillColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter2.ExpandFillColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter2.ExpandLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));\n            this.expandableSplitter2.ExpandLineColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandableSplitter2.GripDarkColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));\n            this.expandableSplitter2.GripDarkColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandableSplitter2.GripLightColor = System.Drawing.Color.FromArgb(((int)(((byte)(254)))), ((int)(((byte)(254)))), ((int)(((byte)(255)))));\n            this.expandableSplitter2.GripLightColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.expandableSplitter2.HotBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(229)))), ((int)(((byte)(244)))), ((int)(((byte)(252)))));\n            this.expandableSplitter2.HotBackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(104)))), ((int)(((byte)(179)))), ((int)(((byte)(219)))));\n            this.expandableSplitter2.HotBackColor2SchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemPressedBackground2;\n            this.expandableSplitter2.HotBackColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemPressedBackground;\n            this.expandableSplitter2.HotExpandFillColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter2.HotExpandFillColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter2.HotExpandLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));\n            this.expandableSplitter2.HotExpandLineColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.expandableSplitter2.HotGripDarkColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(110)))), ((int)(((byte)(121)))));\n            this.expandableSplitter2.HotGripDarkColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.expandableSplitter2.HotGripLightColor = System.Drawing.Color.FromArgb(((int)(((byte)(254)))), ((int)(((byte)(254)))), ((int)(((byte)(255)))));\n            this.expandableSplitter2.HotGripLightColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.expandableSplitter2.Location = new System.Drawing.Point(0, 150);\n            this.expandableSplitter2.MinExtra = 0;\n            this.expandableSplitter2.MinSize = 0;\n            this.expandableSplitter2.Name = \"expandableSplitter2\";\n            this.expandableSplitter2.Size = new System.Drawing.Size(200, 7);\n            this.expandableSplitter2.Style = DevComponents.DotNetBar.eSplitterStyle.Office2007;\n            this.expandableSplitter2.TabIndex = 2;\n            this.expandableSplitter2.TabStop = false;\n            // \n            // advTree1\n            // \n            this.advTree1.AccessibleRole = System.Windows.Forms.AccessibleRole.Outline;\n            this.advTree1.AllowDrop = true;\n            this.advTree1.BackColor = System.Drawing.SystemColors.Window;\n            // \n            // \n            // \n            this.advTree1.BackgroundStyle.Class = \"TreeBorderKey\";\n            this.advTree1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.advTree1.ContextMenuStrip = this.contextMenuStrip1;\n            this.advTree1.Dock = System.Windows.Forms.DockStyle.Top;\n            this.advTree1.DragDropEnabled = false;\n            this.advTree1.Indent = 14;\n            this.advTree1.Location = new System.Drawing.Point(0, 0);\n            this.advTree1.Name = \"advTree1\";\n            this.advTree1.NodesConnector = this.nodeConnector1;\n            this.advTree1.NodeStyle = this.elementStyle1;\n            this.advTree1.PathSeparator = \";\";\n            this.advTree1.Size = new System.Drawing.Size(200, 150);\n            this.advTree1.Styles.Add(this.elementStyle1);\n            this.advTree1.TabIndex = 0;\n            this.advTree1.Text = \"advTree1\";\n            this.advTree1.AfterNodeSelect += new DevComponents.AdvTree.AdvTreeNodeEventHandler(this.advTree1_AfterNodeSelect);\n            this.advTree1.DragDrop += new System.Windows.Forms.DragEventHandler(this.advTree1_DragDrop);\n            this.advTree1.DragEnter += new System.Windows.Forms.DragEventHandler(this.advTree1_DragEnter);\n            // \n            // contextMenuStrip1\n            // \n            this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {\n            this.tsmi1Sort,\n            this.toolStripMenuItem3,\n            this.tsmi1Export,\n            this.tsmi1DumpAsXml});\n            this.contextMenuStrip1.Name = \"contextMenuStrip1\";\n            this.contextMenuStrip1.Size = new System.Drawing.Size(155, 76);\n            // \n            // tsmi1Sort\n            // \n            this.tsmi1Sort.Name = \"tsmi1Sort\";\n            this.tsmi1Sort.Size = new System.Drawing.Size(154, 22);\n            this.tsmi1Sort.Text = \"&Sort\";\n            this.tsmi1Sort.Click += new System.EventHandler(this.tsmi1Sort_Click);\n            // \n            // toolStripMenuItem3\n            // \n            this.toolStripMenuItem3.Name = \"toolStripMenuItem3\";\n            this.toolStripMenuItem3.Size = new System.Drawing.Size(151, 6);\n            // \n            // tsmi1Export\n            // \n            this.tsmi1Export.Name = \"tsmi1Export\";\n            this.tsmi1Export.Size = new System.Drawing.Size(154, 22);\n            this.tsmi1Export.Text = \"&Export\";\n            this.tsmi1Export.Click += new System.EventHandler(this.tsmi1Export_Click);\n            // \n            // tsmi1DumpAsXml\n            // \n            this.tsmi1DumpAsXml.Name = \"tsmi1DumpAsXml\";\n            this.tsmi1DumpAsXml.Size = new System.Drawing.Size(154, 22);\n            this.tsmi1DumpAsXml.Text = \"&Dump as Xml\";\n            this.tsmi1DumpAsXml.Click += new System.EventHandler(this.tsmi1DumpAsXml_Click);\n            // \n            // elementStyle1\n            // \n            this.elementStyle1.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.elementStyle1.Name = \"elementStyle1\";\n            this.elementStyle1.TextColor = System.Drawing.SystemColors.ControlText;\n            // \n            // listViewExString\n            // \n            // \n            // \n            // \n            this.listViewExString.Border.Class = \"ListViewBorder\";\n            this.listViewExString.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.listViewExString.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {\n            this.columnHeader6,\n            this.columnHeader7,\n            this.columnHeader8,\n            this.columnHeader9});\n            this.listViewExString.DisabledBackColor = System.Drawing.Color.Empty;\n            this.listViewExString.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.listViewExString.FullRowSelect = true;\n            this.listViewExString.GridLines = true;\n            this.listViewExString.HideSelection = false;\n            this.listViewExString.Location = new System.Drawing.Point(0, 0);\n            this.listViewExString.MultiSelect = false;\n            this.listViewExString.Name = \"listViewExString\";\n            this.listViewExString.ShowGroups = false;\n            this.listViewExString.ShowItemToolTips = true;\n            this.listViewExString.Size = new System.Drawing.Size(734, 69);\n            this.listViewExString.TabIndex = 0;\n            this.listViewExString.UseCompatibleStateImageBehavior = false;\n            this.listViewExString.View = System.Windows.Forms.View.Details;\n            this.listViewExString.KeyDown += new System.Windows.Forms.KeyEventHandler(this.listViewExString_KeyDown);\n            this.listViewExString.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.listViewExString_MouseDoubleClick);\n            // \n            // columnHeader6\n            // \n            this.columnHeader6.Text = \"Code\";\n            this.columnHeader6.Width = 80;\n            // \n            // columnHeader7\n            // \n            this.columnHeader7.Text = \"Name\";\n            this.columnHeader7.Width = 100;\n            // \n            // columnHeader8\n            // \n            this.columnHeader8.Text = \"Desc\";\n            this.columnHeader8.Width = 350;\n            // \n            // columnHeader9\n            // \n            this.columnHeader9.Text = \"StringPath\";\n            this.columnHeader9.Width = 150;\n            // \n            // comboItem1\n            // \n            this.comboItem1.Text = \"wz\";\n            // \n            // comboItem2\n            // \n            this.comboItem2.Text = \"img\";\n            // \n            // dotNetBarManager1\n            // \n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.F1);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlC);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlA);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlV);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlX);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlZ);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlY);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.Del);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.Ins);\n            this.dotNetBarManager1.BottomDockSite = this.dockSite4;\n            this.dotNetBarManager1.EnableFullSizeDock = false;\n            this.dotNetBarManager1.LeftDockSite = this.dockSite1;\n            this.dotNetBarManager1.ParentForm = this;\n            this.dotNetBarManager1.RightDockSite = this.dockSite2;\n            this.dotNetBarManager1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.dotNetBarManager1.ToolbarBottomDockSite = this.dockSite8;\n            this.dotNetBarManager1.ToolbarLeftDockSite = this.dockSite5;\n            this.dotNetBarManager1.ToolbarRightDockSite = this.dockSite6;\n            this.dotNetBarManager1.ToolbarTopDockSite = this.dockSite7;\n            this.dotNetBarManager1.TopDockSite = this.dockSite3;\n            // \n            // dockSite4\n            // \n            this.dockSite4.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite4.Controls.Add(this.bar1);\n            this.dockSite4.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.dockSite4.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer(new DevComponents.DotNetBar.DocumentBaseContainer[] {\n            ((DevComponents.DotNetBar.DocumentBaseContainer)(new DevComponents.DotNetBar.DocumentBarContainer(this.bar1, 740, 95)))}, DevComponents.DotNetBar.eOrientation.Vertical);\n            this.dockSite4.Location = new System.Drawing.Point(5, 388);\n            this.dockSite4.Name = \"dockSite4\";\n            this.dockSite4.Size = new System.Drawing.Size(740, 98);\n            this.dockSite4.TabIndex = 7;\n            this.dockSite4.TabStop = false;\n            // \n            // bar1\n            // \n            this.bar1.AccessibleDescription = \"DotNetBar Bar (bar1)\";\n            this.bar1.AccessibleName = \"DotNetBar Bar\";\n            this.bar1.AccessibleRole = System.Windows.Forms.AccessibleRole.Grouping;\n            this.bar1.AutoSyncBarCaption = true;\n            this.bar1.CanCustomize = false;\n            this.bar1.CanDockLeft = false;\n            this.bar1.CanDockRight = false;\n            this.bar1.CanDockTab = false;\n            this.bar1.CanDockTop = false;\n            this.bar1.CloseSingleTab = true;\n            this.bar1.Controls.Add(this.panelDockContainer1);\n            this.bar1.Font = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.bar1.GrabHandleStyle = DevComponents.DotNetBar.eGrabHandleStyle.Caption;\n            this.bar1.IsMaximized = false;\n            this.bar1.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.dockContainerItem1});\n            this.bar1.LayoutType = DevComponents.DotNetBar.eLayoutType.DockContainer;\n            this.bar1.Location = new System.Drawing.Point(0, 3);\n            this.bar1.Name = \"bar1\";\n            this.bar1.Size = new System.Drawing.Size(740, 95);\n            this.bar1.Stretch = true;\n            this.bar1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.bar1.TabIndex = 0;\n            this.bar1.TabStop = false;\n            this.bar1.Text = \"搜索结果\";\n            // \n            // panelDockContainer1\n            // \n            this.panelDockContainer1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelDockContainer1.Controls.Add(this.listViewExString);\n            this.panelDockContainer1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelDockContainer1.Location = new System.Drawing.Point(3, 23);\n            this.panelDockContainer1.Name = \"panelDockContainer1\";\n            this.panelDockContainer1.Size = new System.Drawing.Size(734, 69);\n            this.panelDockContainer1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelDockContainer1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.panelDockContainer1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.panelDockContainer1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.panelDockContainer1.Style.GradientAngle = 90;\n            this.panelDockContainer1.TabIndex = 0;\n            // \n            // dockContainerItem1\n            // \n            this.dockContainerItem1.Control = this.panelDockContainer1;\n            this.dockContainerItem1.Name = \"dockContainerItem1\";\n            this.dockContainerItem1.Text = \"搜索结果\";\n            // \n            // dockSite1\n            // \n            this.dockSite1.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite1.Dock = System.Windows.Forms.DockStyle.Left;\n            this.dockSite1.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite1.Location = new System.Drawing.Point(5, 154);\n            this.dockSite1.Name = \"dockSite1\";\n            this.dockSite1.Size = new System.Drawing.Size(0, 234);\n            this.dockSite1.TabIndex = 4;\n            this.dockSite1.TabStop = false;\n            // \n            // dockSite2\n            // \n            this.dockSite2.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite2.Dock = System.Windows.Forms.DockStyle.Right;\n            this.dockSite2.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite2.Location = new System.Drawing.Point(745, 154);\n            this.dockSite2.Name = \"dockSite2\";\n            this.dockSite2.Size = new System.Drawing.Size(0, 234);\n            this.dockSite2.TabIndex = 5;\n            this.dockSite2.TabStop = false;\n            // \n            // dockSite8\n            // \n            this.dockSite8.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite8.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.dockSite8.Location = new System.Drawing.Point(5, 486);\n            this.dockSite8.Name = \"dockSite8\";\n            this.dockSite8.Size = new System.Drawing.Size(740, 0);\n            this.dockSite8.TabIndex = 11;\n            this.dockSite8.TabStop = false;\n            // \n            // dockSite5\n            // \n            this.dockSite5.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite5.Dock = System.Windows.Forms.DockStyle.Left;\n            this.dockSite5.Location = new System.Drawing.Point(5, 1);\n            this.dockSite5.Name = \"dockSite5\";\n            this.dockSite5.Size = new System.Drawing.Size(0, 485);\n            this.dockSite5.TabIndex = 8;\n            this.dockSite5.TabStop = false;\n            // \n            // dockSite6\n            // \n            this.dockSite6.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite6.Dock = System.Windows.Forms.DockStyle.Right;\n            this.dockSite6.Location = new System.Drawing.Point(745, 1);\n            this.dockSite6.Name = \"dockSite6\";\n            this.dockSite6.Size = new System.Drawing.Size(0, 485);\n            this.dockSite6.TabIndex = 9;\n            this.dockSite6.TabStop = false;\n            // \n            // dockSite7\n            // \n            this.dockSite7.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite7.Dock = System.Windows.Forms.DockStyle.Top;\n            this.dockSite7.Location = new System.Drawing.Point(5, 1);\n            this.dockSite7.Name = \"dockSite7\";\n            this.dockSite7.Size = new System.Drawing.Size(740, 0);\n            this.dockSite7.TabIndex = 10;\n            this.dockSite7.TabStop = false;\n            // \n            // dockSite3\n            // \n            this.dockSite3.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite3.Dock = System.Windows.Forms.DockStyle.Top;\n            this.dockSite3.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite3.Location = new System.Drawing.Point(5, 1);\n            this.dockSite3.Name = \"dockSite3\";\n            this.dockSite3.Size = new System.Drawing.Size(740, 0);\n            this.dockSite3.TabIndex = 6;\n            this.dockSite3.TabStop = false;\n            // \n            // dockContainerItem2\n            // \n            this.dockContainerItem2.Name = \"dockContainerItem2\";\n            this.dockContainerItem2.Text = \"dockContainerItem2\";\n            // \n            // panelDockContainer2\n            // \n            this.panelDockContainer2.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelDockContainer2.Location = new System.Drawing.Point(3, 23);\n            this.panelDockContainer2.Name = \"panelDockContainer2\";\n            this.panelDockContainer2.Size = new System.Drawing.Size(734, 44);\n            this.panelDockContainer2.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelDockContainer2.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.panelDockContainer2.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.panelDockContainer2.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.panelDockContainer2.Style.GradientAngle = 90;\n            this.panelDockContainer2.TabIndex = 2;\n            // \n            // chkHashPngFileName\n            // \n            // \n            // \n            // \n            this.chkHashPngFileName.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkHashPngFileName.Location = new System.Drawing.Point(280, 61);\n            this.chkHashPngFileName.Name = \"chkHashPngFileName\";\n            this.chkHashPngFileName.Size = new System.Drawing.Size(115, 23);\n            this.chkHashPngFileName.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.superTooltip1.SetSuperTooltip(this.chkHashPngFileName, new DevComponents.DotNetBar.SuperTooltipInfo(\"HashPngFileName\", \"\", \"以 MD5 校验值输出 PNG 文件名称\", null, null, DevComponents.DotNetBar.eTooltipColor.System, true, false, new System.Drawing.Size(180, 80)));\n            this.chkHashPngFileName.TabIndex = 9;\n            this.chkHashPngFileName.Text = \"HashPngFileName\";\n            // \n            // toolStripMenuItem4\n            // \n            this.toolStripMenuItem4.Name = \"toolStripMenuItem4\";\n            this.toolStripMenuItem4.Size = new System.Drawing.Size(192, 6);\n            // \n            // tsmi2CopyFullPath\n            // \n            this.tsmi2CopyFullPath.Name = \"tsmi2CopyFullPath\";\n            this.tsmi2CopyFullPath.Size = new System.Drawing.Size(195, 22);\n            this.tsmi2CopyFullPath.Text = \"Copy Full Path\";\n            this.tsmi2CopyFullPath.Click += new System.EventHandler(this.tsmi2CopyFullPath_Click);\n            // \n            // comboItem19\n            // \n            this.comboItem19.Text = \"fullPath\";\n            // \n            // MainForm\n            // \n            this.ClientSize = new System.Drawing.Size(750, 512);\n            this.Controls.Add(this.dockSite2);\n            this.Controls.Add(this.dockSite1);\n            this.Controls.Add(this.panelExMain);\n            this.Controls.Add(this.ribbonControl1);\n            this.Controls.Add(this.dockSite3);\n            this.Controls.Add(this.dockSite4);\n            this.Controls.Add(this.dockSite5);\n            this.Controls.Add(this.dockSite6);\n            this.Controls.Add(this.dockSite7);\n            this.Controls.Add(this.dockSite8);\n            this.Controls.Add(this.ribbonBar2);\n            this.Icon = ((System.Drawing.Icon)(resources.GetObject(\"$this.Icon\")));\n            this.MinimumSize = new System.Drawing.Size(750, 513);\n            this.Name = \"MainForm\";\n            this.Text = \"WzComparerR2\";\n            this.Shown += new System.EventHandler(MainForm_Shown);\n            this.ribbonControl1.ResumeLayout(false);\n            this.ribbonControl1.PerformLayout();\n            this.ribbonPanel1.ResumeLayout(false);\n            this.ribbonPanel2.ResumeLayout(false);\n            this.ribbonPanel3.ResumeLayout(false);\n            this.panelExMain.ResumeLayout(false);\n            this.panelExRight.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.superTabControl1)).EndInit();\n            this.superTabControl1.ResumeLayout(false);\n            this.superTabControlPanel1.ResumeLayout(false);\n            this.panelEx2.ResumeLayout(false);\n            this.panelEx1.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.advTree3)).EndInit();\n            this.contextMenuStrip2.ResumeLayout(false);\n            this.superTabControlPanel2.ResumeLayout(false);\n            this.superTabControlPanel2.PerformLayout();\n            this.superTabControlPanel3.ResumeLayout(false);\n            this.panelExLeft.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.advTree2)).EndInit();\n            ((System.ComponentModel.ISupportInitialize)(this.advTree1)).EndInit();\n            this.contextMenuStrip1.ResumeLayout(false);\n            this.dockSite4.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).EndInit();\n            this.bar1.ResumeLayout(false);\n            this.panelDockContainer1.ResumeLayout(false);\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.RibbonControl ribbonControl1;\n        private DevComponents.DotNetBar.RibbonPanel ribbonPanel1;\n        private DevComponents.DotNetBar.RibbonPanel ribbonPanel2;\n        private DevComponents.DotNetBar.Office2007StartButton office2007StartButton1;\n        private DevComponents.DotNetBar.ItemContainer itemContainer1;\n        private DevComponents.DotNetBar.ItemContainer itemContainer2;\n        private DevComponents.DotNetBar.ItemContainer itemContainer3;\n        private DevComponents.DotNetBar.ButtonItem btnItemOpenWz;\n        private DevComponents.DotNetBar.ButtonItem buttonItemClose;\n        private DevComponents.DotNetBar.GalleryContainer galleryContainerRecent;\n        private DevComponents.DotNetBar.LabelItem labelItem8;\n        private DevComponents.DotNetBar.ItemContainer itemContainer4;\n        private DevComponents.DotNetBar.ButtonItem btnItemOptions;\n        private DevComponents.DotNetBar.ButtonItem buttonItem13;\n        private DevComponents.DotNetBar.RibbonTabItem ribbonTabItem1;\n        private DevComponents.DotNetBar.RibbonTabItem ribbonTabItem2;\n        private DevComponents.DotNetBar.StyleManager styleManager1;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar1;\n        private DevComponents.DotNetBar.ColorPickerDropDown colorPickerDropDown1;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar2;\n        private DevComponents.DotNetBar.LabelItem labelItemStatus;\n        private DevComponents.DotNetBar.ProgressBarItem progressBarItem1;\n        private DevComponents.DotNetBar.PanelEx panelExMain;\n        private DevComponents.DotNetBar.PanelEx panelExRight;\n        private DevComponents.DotNetBar.PanelEx panelExLeft;\n        private DevComponents.DotNetBar.ButtonItem buttonItemStyle;\n        private DevComponents.AdvTree.AdvTree advTree2;\n        private DevComponents.AdvTree.NodeConnector nodeConnector2;\n        private DevComponents.DotNetBar.ElementStyle elementStyle2;\n        private DevComponents.AdvTree.AdvTree advTree1;\n        private DevComponents.DotNetBar.ElementStyle elementStyle1;\n        private DevComponents.AdvTree.NodeConnector nodeConnector1;\n        private DevComponents.DotNetBar.SuperTabControl superTabControl1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel1;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem1;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel2;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem2;\n        private DevComponents.DotNetBar.SuperTabControlPanel superTabControlPanel3;\n        private DevComponents.DotNetBar.SuperTabItem superTabItem3;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar3;\n        private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;\n        private System.Windows.Forms.ToolStripMenuItem tsmi1Sort;\n        private DevComponents.DotNetBar.Controls.ListViewEx listViewExString;\n        private DevComponents.Editors.ComboItem comboItem1;\n        private DevComponents.Editors.ComboItem comboItem2;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar4;\n        private DevComponents.DotNetBar.ItemContainer itemContainer10;\n        private DevComponents.DotNetBar.ItemContainer itemContainer11;\n        private DevComponents.DotNetBar.ItemContainer itemContainer12;\n        private DevComponents.DotNetBar.CheckBoxItem checkBoxItemExact2;\n        private DevComponents.DotNetBar.ComboBoxItem comboBoxItem2;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSearchString;\n        private DevComponents.DotNetBar.ItemContainer itemContainer14;\n        private DevComponents.DotNetBar.ItemContainer itemContainer15;\n        private DevComponents.DotNetBar.LabelItem labelItem3;\n        private DevComponents.DotNetBar.ItemContainer itemContainer16;\n        private DevComponents.DotNetBar.CheckBoxItem checkBoxItemExact1;\n        private DevComponents.DotNetBar.ComboBoxItem comboBoxItem1;\n        private DevComponents.DotNetBar.ItemContainer itemContainer17;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSearchWz;\n        private DevComponents.DotNetBar.PanelEx panelEx2;\n        private DevComponents.DotNetBar.Controls.TextBoxX textBoxX1;\n        private DevComponents.DotNetBar.ExpandableSplitter expandableSplitter1;\n        private DevComponents.DotNetBar.PanelEx panelEx1;\n        private DevComponents.AdvTree.AdvTree advTree3;\n        private DevComponents.AdvTree.NodeConnector nodeConnector3;\n        private DevComponents.DotNetBar.ElementStyle elementStyle3;\n        private DevComponents.DotNetBar.Controls.ListViewEx listViewExWzDetail;\n        private System.Windows.Forms.ColumnHeader columnHeader1;\n        private System.Windows.Forms.ColumnHeader columnHeader2;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar5;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSaveImage;\n        private System.Windows.Forms.ImageList imageList1;\n        private DevComponents.AdvTree.ColumnHeader columnHeader3;\n        private DevComponents.AdvTree.ColumnHeader columnHeader4;\n        private DevComponents.AdvTree.ColumnHeader columnHeader5;\n        private DevComponents.DotNetBar.ItemContainer itemContainer6;\n        private DevComponents.DotNetBar.ItemContainer itemContainer7;\n        private DevComponents.DotNetBar.LabelItem labelItemSoundTitle;\n        private DevComponents.DotNetBar.ItemContainer itemContainer9;\n        private DevComponents.DotNetBar.SliderItem sliderItemSoundTime;\n        private DevComponents.DotNetBar.CheckBoxItem checkBoxItemSoundLoop;\n        private DevComponents.DotNetBar.ItemContainer itemContainer13;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSoundStop;\n        private DevComponents.DotNetBar.SliderItem sliderItemSoundVol;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSoundSave;\n        private DevComponents.DotNetBar.ItemContainer itemContainer18;\n        private DevComponents.DotNetBar.LabelItem labelItemSoundTime;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSoundPlay;\n        private DevComponents.Editors.ComboItem comboItem3;\n        private DevComponents.Editors.ComboItem comboItem4;\n        private DevComponents.Editors.ComboItem comboItem5;\n        private DevComponents.Editors.ComboItem comboItem6;\n        private DevComponents.Editors.ComboItem comboItem7;\n        private DevComponents.Editors.ComboItem comboItem8;\n        private DevComponents.Editors.ComboItem comboItem9;\n        private DevComponents.DotNetBar.TextBoxItem textBoxItemSearchWz;\n        private DevComponents.DotNetBar.ButtonItem buttonItemCloseAll;\n        private DevComponents.Editors.ComboItem comboItem10;\n        private DevComponents.Editors.ComboItem comboItem11;\n        private DevComponents.Editors.ComboItem comboItem12;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSelectStringWz;\n        private DevComponents.DotNetBar.ItemContainer itemContainer8;\n        private DevComponents.DotNetBar.LabelItem labelItem2;\n        private DevComponents.DotNetBar.TextBoxItem textBoxItemSearchString;\n        private DevComponents.DotNetBar.ButtonItem buttonItemClearStringWz;\n        private System.Windows.Forms.ColumnHeader columnHeader6;\n        private System.Windows.Forms.ColumnHeader columnHeader7;\n        private System.Windows.Forms.ColumnHeader columnHeader8;\n        private System.Windows.Forms.ColumnHeader columnHeader9;\n        private DevComponents.DotNetBar.RibbonPanel ribbonPanel3;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar7;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar6;\n        private DevComponents.DotNetBar.ButtonItem buttonItemAbout;\n        private DevComponents.DotNetBar.RibbonTabItem ribbonTabItem3;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar8;\n        private DevComponents.DotNetBar.ItemContainer itemContainer23;\n        private DevComponents.DotNetBar.ItemContainer itemContainer24;\n        private DevComponents.DotNetBar.ItemContainer itemContainer25;\n        private DevComponents.DotNetBar.ButtonItem buttonItemQuickView;\n        private DevComponents.DotNetBar.ButtonItem buttonItemLoadSound;\n        private DevComponents.DotNetBar.ItemContainer itemContainer26;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSetItems;\n        private DevComponents.DotNetBar.ButtonItem buttonItemClearSetItems;\n        private DevComponents.DotNetBar.ItemContainer itemContainer27;\n        private DevComponents.DotNetBar.ButtonItem buttonItemAutoSave;\n        private DevComponents.DotNetBar.ButtonItem buttonItemAutoSaveFolder;\n        private DevComponents.DotNetBar.LabelItem labelItemAutoSaveFolder;\n        private DevComponents.DotNetBar.ItemContainer itemContainer28;\n        private DevComponents.DotNetBar.ItemContainer itemContainer29;\n        private DevComponents.DotNetBar.ButtonItem buttonItemCharItem;\n        private DevComponents.DotNetBar.ItemContainer itemContainer30;\n        private DevComponents.DotNetBar.ButtonItem buttonItemCharaStat;\n        private DevComponents.DotNetBar.ItemContainer itemContainer31;\n        private DevComponents.DotNetBar.ButtonItem buttonItemCharaEquip;\n        private DevComponents.DotNetBar.ItemContainer itemContainer32;\n        private DevComponents.DotNetBar.ItemContainer itemContainer33;\n        private DevComponents.DotNetBar.ButtonItem buttonItemAddItem;\n        private DevComponents.DotNetBar.ItemContainer itemContainer34;\n        private DevComponents.DotNetBar.ItemContainer itemContainer35;\n        private DevComponents.DotNetBar.ButtonItem buttonItemGif;\n        private DevComponents.DotNetBar.ItemContainer itemContainer36;\n        private DevComponents.DotNetBar.ButtonItem buttonItemExtractGifEx;\n        private DevComponents.DotNetBar.ButtonItem buttonItemGifSetting;\n        private System.Windows.Forms.ContextMenuStrip contextMenuStrip2;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2ExpandAll;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2CollapseAll;\n        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2ExpandLevel;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2CollapseLevel;\n        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem2;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2Prev;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2Next;\n        private DevComponents.DotNetBar.ItemContainer itemContainer37;\n        private DevComponents.DotNetBar.ItemContainer itemContainer38;\n        private DevComponents.DotNetBar.ComboBoxItem comboBoxItemCharacter;\n        private DevComponents.DotNetBar.ItemContainer itemContainer39;\n        private DevComponents.DotNetBar.ItemContainer itemContainer40;\n        private DevComponents.DotNetBar.ButtonItem buttonItemCreateChara;\n        private DevComponents.DotNetBar.ButtonItem buttonItemEdit;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar9;\n        private DevComponents.DotNetBar.ButtonItem buttonItemPatcher;\n        private DevComponents.DotNetBar.ItemContainer itemContainer41;\n        private DevComponents.DotNetBar.ButtonItem buttonItemLoadChara;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSaveChara;\n        private DevComponents.DotNetBar.ExpandableSplitter expandableSplitter2;\n        private DevComponents.DotNetBar.ButtonX btnEasyCompare;\n        private DevComponents.DotNetBar.LabelX labelXComp1;\n        private DevComponents.DotNetBar.LabelX labelXComp2;\n        private System.Windows.Forms.ToolStripMenuItem tsmi1Export;\n        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem3;\n        private DevComponents.DotNetBar.ComboBoxItem comboBoxItemLanguage;\n        private DevComponents.Editors.ComboItem comboItem13;\n        private DevComponents.Editors.ComboItem comboItem14;\n        private DevComponents.Editors.ComboItem comboItem15;\n        private DevComponents.Editors.ComboItem comboItem16;\n        private DevComponents.Editors.ComboItem comboItem17;\n        private DevComponents.DotNetBar.ItemContainer itemContainer42;\n        private DevComponents.DotNetBar.ButtonItem buttonItemAutoQuickView;\n        private DevComponents.DotNetBar.ButtonItem buttonItemQuickViewSetting;\n        private DevComponents.DotNetBar.ItemContainer itemContainer43;\n        private DevComponents.DotNetBar.ButtonX btnExportSkillOption;\n        private DevComponents.DotNetBar.ButtonX btnExportSkill;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkOutputPng;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbComparePng;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkOutputRemovedImg;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkOutputAddedImg;\n        private DevComponents.DotNetBar.DotNetBarManager dotNetBarManager1;\n        private DevComponents.DotNetBar.DockSite dockSite4;\n        private DevComponents.DotNetBar.Bar bar1;\n        private DevComponents.DotNetBar.PanelDockContainer panelDockContainer1;\n        private DevComponents.DotNetBar.DockContainerItem dockContainerItem1;\n        private DevComponents.DotNetBar.DockSite dockSite1;\n        private DevComponents.DotNetBar.DockSite dockSite2;\n        private DevComponents.DotNetBar.DockSite dockSite3;\n        private DevComponents.DotNetBar.DockSite dockSite5;\n        private DevComponents.DotNetBar.DockSite dockSite6;\n        private DevComponents.DotNetBar.DockSite dockSite7;\n        private DevComponents.DotNetBar.DockSite dockSite8;\n        private DevComponents.DotNetBar.DockContainerItem dockContainerItem2;\n        private DevComponents.DotNetBar.PanelDockContainer panelDockContainer2;\n        private DevComponents.DotNetBar.RibbonBar ribbonBar11;\n        private DevComponents.DotNetBar.ButtonItem buttonItem1;\n        private DevComponents.DotNetBar.ButtonItem btnNodeBack;\n        private DevComponents.DotNetBar.ButtonItem btnNodeForward;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2SaveAs;\n        private System.Windows.Forms.ToolStripSeparator tsmi2Splitter1;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2HandleUol;\n        private PictureBoxEx pictureBoxEx1;\n        private DevComponents.DotNetBar.ComboBoxItem cmbItemAniNames;\n        private DevComponents.DotNetBar.ButtonItem buttonItemUpdate;\n        private System.Windows.Forms.ToolStripMenuItem tsmi1DumpAsXml;\n        private DevComponents.Editors.ComboItem comboItem18;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkResolvePngLink;\n        private DevComponents.DotNetBar.ComboBoxItem cmbItemSkins;\n        private DevComponents.DotNetBar.ButtonItem btnItemOpenImg;\n        private DevComponents.DotNetBar.ButtonItem buttonItemSaveWithOptions;\n        private DevComponents.DotNetBar.CheckBoxItem checkBoxItemRegex1;\n        private DevComponents.DotNetBar.CheckBoxItem checkBoxItemRegex2;\n        private DevComponents.DotNetBar.SuperTooltip superTooltip1;\n        private DevComponents.DotNetBar.ButtonX btnCustomCSS;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkHashPngFileName;\n        private DevComponents.Editors.ComboItem comboItem19;\n        private System.Windows.Forms.ToolStripSeparator toolStripMenuItem4;\n        private System.Windows.Forms.ToolStripMenuItem tsmi2CopyFullPath;\n        private DevComponents.DotNetBar.ColorPickerDropDown colorPickerPicBoxBgColor;\n    }\n}"
  },
  {
    "path": "WzComparerR2/MainForm.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Data;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing System.Xml;\nusing static Microsoft.Xna.Framework.MathHelper;\nusing Timer = System.Timers.Timer;\nusing DevComponents.AdvTree;\nusing DevComponents.AdvTree.Display;\nusing DevComponents.DotNetBar;\nusing DevComponents.DotNetBar.Controls;\n\nusing WzComparerR2.Animation;\nusing WzComparerR2.CharaSim;\nusing WzComparerR2.CharaSimControl;\nusing WzComparerR2.Common;\nusing WzComparerR2.Comparer;\nusing WzComparerR2.Config;\nusing WzComparerR2.Controls;\nusing WzComparerR2.Encoders;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2\n{\n    public partial class MainForm : Office2007RibbonForm, PluginContextProvider\n    {\n        public MainForm()\n        {\n            InitializeComponent();\n#if NET6_0_OR_GREATER\n            // https://learn.microsoft.com/en-us/dotnet/core/compatibility/fx-core#controldefaultfont-changed-to-segoe-ui-9pt\n            this.Font = new Font(new FontFamily(\"Microsoft Sans Serif\"), 8f);\n#endif\n            Form.CheckForIllegalCrossThreadCalls = false;\n            this.MinimumSize = new Size(600, 450);\n            advTree1.AfterNodeSelect += new AdvTreeNodeEventHandler(advTree1_AfterNodeSelect_2);\n            advTree2.AfterNodeSelect += new AdvTreeNodeEventHandler(advTree2_AfterNodeSelect_2);\n            //new ImageDragHandler(this.pictureBox1).AttachEvents();\n            RegisterPluginEvents();\n            createStyleItems();\n            initFields();\n        }\n\n        List<Wz_Structure> openedWz;\n        StringLinker stringLinker;\n        HistoryList<Node> historyNodeList;\n        bool historySelecting;\n\n        //soundPlayer\n        BassSoundPlayer soundPlayer;\n        Timer soundTimer;\n        bool timerChangeValue;\n\n        //charaSim\n        AfrmTooltip tooltipQuickView;\n        CharaSimControlGroup charaSimCtrl;\n        AdvTree lastSelectedTree;\n        DefaultLevel skillDefaultLevel = DefaultLevel.Level0;\n        int skillInterval = 32;\n\n        //compare\n        Thread compareThread;\n\n        private void initFields()\n        {\n            openedWz = new List<Wz_Structure>();\n            stringLinker = new StringLinker();\n            historyNodeList = new HistoryList<Node>();\n\n            tooltipQuickView = new AfrmTooltip();\n            tooltipQuickView.Visible = false;\n            tooltipQuickView.StringLinker = this.stringLinker;\n            tooltipQuickView.KeyDown += new KeyEventHandler(afrm_KeyDown);\n            tooltipQuickView.ShowID = true;\n            tooltipQuickView.ShowMenu = true;\n\n            charaSimCtrl = new CharaSimControlGroup();\n            charaSimCtrl.StringLinker = this.stringLinker;\n            charaSimCtrl.Character = new Character();\n            charaSimCtrl.Character.Name = \"Test\";\n\n            string[] images = new string[] { \"dir\", \"mp3\", \"num\", \"png\", \"str\", \"uol\", \"vector\", \"img\", \"rawdata\", \"convex\", \"video\" };\n            foreach (string img in images)\n            {\n                imageList1.Images.Add(img, (Image)Properties.Resources.ResourceManager.GetObject(img));\n            }\n\n            soundPlayer = new BassSoundPlayer();\n            if (!soundPlayer.Init())\n            {\n                ManagedBass.Errors error = soundPlayer.GetLastError();\n                MessageBoxEx.Show(\"Bass初始化失败！\\r\\n\\r\\nerrorCode : \" + (int)error + \"(\" + error + \")\", \"虫子\");\n            }\n            soundTimer = new Timer(120d);\n            soundTimer.Elapsed += new System.Timers.ElapsedEventHandler(soundTimer_Elapsed);\n            soundTimer.Enabled = true;\n\n            PluginBase.PluginManager.WzFileFinding += new FindWzEventHandler(CharaSimLoader_WzFileFinding);\n\n            foreach (WzPngComparison comp in Enum.GetValues(typeof(WzPngComparison)))\n            {\n                cmbComparePng.Items.Add(comp);\n            }\n            cmbComparePng.SelectedItem = WzPngComparison.SizeAndDataLength;\n        }\n\n        /// <summary>\n        /// 插件加载时执行的方法，用于初始化配置文件。\n        /// </summary>\n        internal void PluginOnLoad()\n        {\n            ConfigManager.RegisterAllSection(this.GetType().Assembly);\n            var conf = ImageHandlerConfig.Default;\n            //刷新最近打开文件列表\n            refreshRecentDocItems();\n            //读取CharaSim配置\n            UpdateCharaSimSettings();\n            //wz加载配置\n            UpdateWzLoadingSettings();\n\n            //杂项配置\n            labelItemAutoSaveFolder.Text = ImageHandlerConfig.Default.AutoSavePictureFolder;\n            buttonItemAutoSave.Checked = ImageHandlerConfig.Default.AutoSaveEnabled;\n            comboBoxItemLanguage.SelectedIndex = Clamp(CharaSimConfig.Default.SelectedFontIndex, 0, comboBoxItemLanguage.Items.Count);\n\n\n            //更新界面颜色\n            styleManager1.ManagerStyle = WcR2Config.Default.MainStyle;\n            UpdateButtonItemStyles();\n            styleManager1.ManagerColorTint = WcR2Config.Default.MainStyleColor;\n        }\n\n        void UpdateCharaSimSettings()\n        {\n            var Setting = CharaSimConfig.Default;\n            this.buttonItemAutoQuickView.Checked = Setting.AutoQuickView;\n            tooltipQuickView.SkillRender.ShowProperties = Setting.Skill.ShowProperties;\n            tooltipQuickView.SkillRender.ShowObjectID = Setting.Skill.ShowID;\n            tooltipQuickView.SkillRender.ShowDelay = Setting.Skill.ShowDelay;\n            tooltipQuickView.SkillRender.DisplayCooltimeMSAsSec = Setting.Skill.DisplayCooltimeMSAsSec;\n            tooltipQuickView.SkillRender.DisplayPermyriadAsPercent = Setting.Skill.DisplayPermyriadAsPercent;\n            tooltipQuickView.SkillRender.IgnoreEvalError = Setting.Skill.IgnoreEvalError;\n            this.skillDefaultLevel = Setting.Skill.DefaultLevel;\n            this.skillInterval = Setting.Skill.IntervalLevel;\n            tooltipQuickView.FamiliarRender.ShowObjectID = Setting.Gear.ShowID;\n            tooltipQuickView.GearRender.ShowObjectID = Setting.Gear.ShowID;\n            tooltipQuickView.GearRender.ShowSpeed = Setting.Gear.ShowWeaponSpeed;\n            tooltipQuickView.GearRender.ShowLevelOrSealed = Setting.Gear.ShowLevelOrSealed;\n            tooltipQuickView.GearRender.ShowMedalTag = Setting.Gear.ShowMedalTag;\n            tooltipQuickView.ItemRender.ShowObjectID = Setting.Item.ShowID;\n            tooltipQuickView.ItemRender.LinkRecipeInfo = Setting.Item.LinkRecipeInfo;\n            tooltipQuickView.ItemRender.LinkRecipeItem = Setting.Item.LinkRecipeItem;\n            tooltipQuickView.ItemRender.ShowNickTag = Setting.Item.ShowNickTag;\n            tooltipQuickView.ItemRender.ShowDamageSkin = Setting.DamageSkin.ShowDamageSkin;\n            tooltipQuickView.ItemRender.ShowDamageSkinID = Setting.DamageSkin.ShowDamageSkinID;\n            tooltipQuickView.ItemRender.UseMiniSizeDamageSkin = Setting.DamageSkin.UseMiniSize;\n            tooltipQuickView.ItemRender.AlwaysUseMseaFormatDamageSkin = Setting.DamageSkin.AlwaysUseMseaFormat;\n            tooltipQuickView.ItemRender.DisplayUnitOnSingleLine = Setting.DamageSkin.DisplayUnitOnSingleLine;\n            tooltipQuickView.ItemRender.DamageSkinNumber = Setting.DamageSkin.DamageSkinNumber;\n            tooltipQuickView.RecipeRender.ShowObjectID = Setting.Recipe.ShowID;\n        }\n\n        void UpdateWzLoadingSettings()\n        {\n            var config = WcR2Config.Default;\n            Encoding enc;\n            try\n            {\n                enc = Encoding.GetEncoding(config.WzEncoding);\n            }\n            catch\n            {\n                enc = null;\n            }\n            Wz_Structure.DefaultEncoding = enc;\n            Wz_Structure.DefaultAutoDetectExtFiles = config.AutoDetectExtFiles;\n            Wz_Structure.DefaultImgCheckDisabled = config.ImgCheckDisabled;\n        }\n\n        async Task AutomaticCheckUpdate()\n        {\n            var config = WcR2Config.Default;\n            if (config.EnableAutoUpdate)\n            {\n                var updater = new Updater();\n                try\n                {\n                    await updater.QueryUpdateAsync();\n                    if (updater.UpdateAvailable)\n                    {\n                        ToastNotification.Show(this, $\"检查到更新版本{updater.LatestVersionString}\", 5000, eToastPosition.TopCenter);\n                        var frmUpdater = new FrmUpdater(updater);\n                        frmUpdater.LoadConfig(config);\n                        frmUpdater.ShowDialog(this);\n                    }\n                }\n                catch\n                {\n                    // ignore error\n                }\n            }\n        }\n\n        void CharaSimLoader_WzFileFinding(object sender, FindWzEventArgs e)\n        {\n            string[] fullPath = null;\n            if (!string.IsNullOrEmpty(e.FullPath)) //用fullpath作为输入参数\n            {\n                fullPath = e.FullPath.Split('/', '\\\\');\n                e.WzType = Enum.TryParse<Wz_Type>(fullPath[0], true, out var wzType) ? wzType : Wz_Type.Unknown;\n            }\n\n            List<Wz_Node> preSearch = new List<Wz_Node>();\n            if (e.WzType != Wz_Type.Unknown) //用wztype作为输入参数\n            {\n                IEnumerable<Wz_Structure> preSearchWz = e.WzFile?.WzStructure != null ?\n                    Enumerable.Repeat(e.WzFile.WzStructure, 1) :\n                    this.openedWz;\n                foreach (var wzs in preSearchWz)\n                {\n                    Wz_File baseWz = null;\n                    bool find = false;\n                    foreach (Wz_File wz_f in wzs.wz_files)\n                    {\n                        if (wz_f.Type == e.WzType)\n                        {\n                            preSearch.Add(wz_f.Node);\n                            find = true;\n                            //e.WzFile = wz_f;\n                        }\n                        if (wz_f.Type == Wz_Type.Base)\n                        {\n                            baseWz = wz_f;\n                        }\n                    }\n\n                    // detect data.wz\n                    if (baseWz != null && !find)\n                    {\n                        string key = e.WzType.ToString();\n                        foreach (Wz_Node node in baseWz.Node.Nodes)\n                        {\n                            if (node.Text == key && node.Nodes.Count > 0)\n                            {\n                                preSearch.Add(node);\n                            }\n                        }\n                    }\n                }\n            }\n\n            if (fullPath == null || fullPath.Length <= 1)\n            {\n                if (e.WzType != Wz_Type.Unknown && preSearch.Count > 0) //返回wzFile\n                {\n                    e.WzNode = preSearch[0];\n                    e.WzFile = preSearch[0].Value as Wz_File;\n                }\n                return;\n            }\n\n            if (preSearch.Count <= 0)\n            {\n                return;\n            }\n\n            foreach (var wzFileNode in preSearch)\n            {\n                var searchNode = wzFileNode;\n                for (int i = 1; i < fullPath.Length && searchNode != null; i++)\n                {\n                    searchNode = searchNode.Nodes[fullPath[i]];\n                    var img = searchNode.GetValueEx<Wz_Image>(null);\n                    if (img != null)\n                    {\n                        searchNode = img.TryExtract() ? img.Node : null;\n                    }\n                }\n\n                if (searchNode != null)\n                {\n                    e.WzNode = searchNode;\n                    e.WzFile = wzFileNode.Value as Wz_File;\n                    return;\n                }\n            }\n            //寻找失败\n            e.WzNode = null;\n        }\n\n        #region 界面主题配置\n        private void createStyleItems()\n        {\n            //添加菜单\n            foreach (eStyle style in Enum.GetValues(typeof(eStyle)).OfType<eStyle>().Distinct())\n            {\n                var buttonItemStyle = new ButtonItem() { Tag = style, Text = style.ToString(), Checked = (styleManager1.ManagerStyle == style) };\n                buttonItemStyle.Click += new EventHandler(buttonItemStyle_Click);\n                this.buttonItemStyle.SubItems.Add(buttonItemStyle);\n            }\n\n            var styleColorPicker = new ColorPickerDropDown() { Text = \"StyleColorTint\", BeginGroup = true, SelectedColor = styleManager1.ManagerColorTint };\n            styleColorPicker.SelectedColorChanged += new EventHandler(styleColorPicker_SelectedColorChanged);\n            buttonItemStyle.SubItems.Add(styleColorPicker);\n        }\n\n        private void buttonItemStyle_Click(object sender, EventArgs e)\n        {\n            var style = (eStyle)((sender as ButtonItem).Tag);\n            styleManager1.ManagerStyle = style;\n            UpdateButtonItemStyles();\n            ConfigManager.Reload();\n            WcR2Config.Default.MainStyle = style;\n            ConfigManager.Save();\n        }\n\n        private void UpdateButtonItemStyles()\n        {\n            foreach (BaseItem item in buttonItemStyle.SubItems)\n            {\n                ButtonItem buttonItem = item as ButtonItem;\n                if (buttonItem != null)\n                {\n                    buttonItem.Checked = (buttonItem.Tag as eStyle?) == styleManager1.ManagerStyle;\n                }\n            }\n        }\n\n        private void styleColorPicker_SelectedColorChanged(object sender, EventArgs e)\n        {\n            var color = (sender as ColorPickerDropDown).SelectedColor;\n            styleManager1.ManagerColorTint = color;\n            ConfigManager.Reload();\n            WcR2Config.Default.MainStyleColor = color;\n            ConfigManager.Save();\n        }\n        #endregion\n\n        #region 读取wz相关方法\n        private Node createNode(Wz_Node wzNode)\n        {\n            if (wzNode == null)\n                return null;\n\n            Node parentNode = new Node(wzNode.Text) { Tag = new WeakReference(wzNode) };\n            foreach (Wz_Node subNode in wzNode.Nodes)\n            {\n                Node subTreeNode = createNode(subNode);\n                if (subTreeNode != null)\n                    parentNode.Nodes.Add(subTreeNode);\n            }\n            return parentNode;\n        }\n\n        private void sortWzNode(Wz_Node wzNode)\n        {\n            this.sortWzNode(wzNode, WcR2Config.Default.SortWzByImgID);\n        }\n\n        private void sortWzNode(Wz_Node wzNode, bool sortByImgID)\n        {\n            if (wzNode.Nodes.Count > 1)\n            {\n                if (sortByImgID)\n                {\n                    wzNode.Nodes.SortByImgID();\n                }\n                else\n                {\n                    wzNode.Nodes.Sort();\n                }\n            }\n            foreach (Wz_Node subNode in wzNode.Nodes)\n            {\n                sortWzNode(subNode, sortByImgID);\n            }\n        }\n        #endregion\n\n        #region wz提取右侧\n        private void cmbItemAniNames_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            if (this.cmbItemAniNames.SelectedIndex > -1 && this.pictureBoxEx1.Items.Count > 0)\n            {\n                if (this.pictureBoxEx1.Items[0] is ISpineAnimator aniItem)\n                {\n                    string aniName = this.cmbItemAniNames.SelectedItem as string;\n                    aniItem.SelectedAnimationName = aniName;\n                    this.cmbItemAniNames.Tooltip = aniName;\n                }\n                else if (this.pictureBoxEx1.Items[0] is FrameAnimator frameAni && this.cmbItemAniNames.SelectedItem is int selectedpage)\n                {\n                    if (frameAni.Data.Frames.Count == 1)\n                    {\n                        var png = frameAni.Data.Frames[0].Png;\n                        if (png != null && png.ActualPages > 1 && 0 <= selectedpage && selectedpage < png.ActualPages)\n                        {\n                            this.pictureBoxEx1.ShowImage(png, selectedpage);\n                        }\n                    }\n                }\n            }\n        }\n\n        private void cmbItemSkins_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            if (this.cmbItemSkins.SelectedIndex > -1 && this.pictureBoxEx1.Items.Count > 0)\n            {\n                if (this.pictureBoxEx1.Items[0] is ISpineAnimator aniItem)\n                {\n                    string skinName = this.cmbItemSkins.SelectedItem as string;\n                    aniItem.SelectedSkin = skinName;\n                    this.cmbItemSkins.Tooltip = skinName;\n                }\n            }\n        }\n\n        private void buttonItemSaveImage_Click(object sender, EventArgs e)\n        {\n            this.OnSaveImage(false);\n        }\n\n        private void buttonItemSaveWithOptions_Click(object sender, EventArgs e)\n        {\n            this.OnSaveImage(true);\n        }\n\n        private Node handleUol(Node currentNode, string uolString)\n        {\n            if (currentNode == null || currentNode.Parent == null || string.IsNullOrEmpty(uolString))\n                return null;\n            string[] dirs = uolString.Split('/');\n            currentNode = currentNode.Parent;\n\n            for (int i = 0; i < dirs.Length; i++)\n            {\n                string dir = dirs[i];\n                if (dir == \"..\")\n                {\n                    currentNode = currentNode.Parent;\n                }\n                else\n                {\n                    bool find = false;\n                    foreach (Node child in currentNode.Nodes)\n                    {\n                        if (child.Text == dir)\n                        {\n                            currentNode = child;\n                            find = true;\n                            break;\n                        }\n                    }\n                    if (!find)\n                        currentNode = null;\n                }\n                if (currentNode == null)\n                    return null;\n            }\n            return currentNode;\n        }\n\n        private void labelItemAutoSaveFolder_Click(object sender, EventArgs e)\n        {\n            string dir = ImageHandlerConfig.Default.AutoSavePictureFolder;\n            if (!string.IsNullOrEmpty(dir))\n            {\n                System.Diagnostics.Process.Start(\"explorer.exe\", dir);\n            }\n        }\n\n        private void buttonItemGif_Click(object sender, EventArgs e)\n        {\n            if (advTree3.SelectedNode == null)\n                return;\n\n            Wz_Node node = advTree3.SelectedNode.AsWzNode();\n            string aniName = GetSelectedNodeImageName();\n\n            //添加到动画控件\n            var spineDetectResult = SpineLoader.Detect(node);\n            if (spineDetectResult.Success)\n            {\n                var spineData = this.pictureBoxEx1.LoadSpineAnimation(spineDetectResult);\n\n                if (spineData != null)\n                {\n                    this.pictureBoxEx1.ShowAnimation(spineData);\n                    var aniItem = this.pictureBoxEx1.Items[0] as ISpineAnimator;\n\n                    this.cmbItemAniNames.Items.Clear();\n                    this.cmbItemAniNames.Items.Add(\"\");\n                    this.cmbItemAniNames.Items.AddRange(aniItem.Animations.ToArray());\n                    this.cmbItemAniNames.SelectedIndex = 0;\n\n                    this.cmbItemSkins.Visible = true;\n                    this.cmbItemSkins.Items.Clear();\n                    this.cmbItemSkins.Items.AddRange(aniItem.Skins.ToArray());\n                    this.cmbItemSkins.SelectedIndex = aniItem.Skins.IndexOf(aniItem.SelectedSkin);\n                }\n            }\n            else\n            {\n                var options = (sender == this.buttonItemExtractGifEx) ? FrameAnimationCreatingOptions.ScanAllChildrenFrames: default;\n                var frameData = this.pictureBoxEx1.LoadFrameAnimation(node, options);\n\n                if (frameData != null)\n                {\n                    this.pictureBoxEx1.ShowAnimation(frameData);\n                    this.cmbItemAniNames.Items.Clear();\n                    this.cmbItemSkins.Visible = false;\n                }\n            }\n            this.pictureBoxEx1.PictureName = aniName;\n        }\n\n        private string GetSelectedNodeImageName()\n        {\n            Wz_Node node = advTree3.SelectedNode.AsWzNode();\n\n            string aniName;\n            switch (ImageHandlerConfig.Default.ImageNameMethod.Value)\n            {\n                default:\n                case ImageNameMethod.Default:\n                    advTree3.PathSeparator = \".\";\n                    aniName = advTree3.SelectedNode.FullPath;\n                    break;\n\n                case ImageNameMethod.PathToImage:\n                    aniName = node.FullPath.Replace('\\\\', '.');\n                    break;\n\n                case ImageNameMethod.PathToWz:\n                    aniName = node.FullPathToFile.Replace('\\\\', '.');\n                    break;\n            }\n\n            return aniName;\n        }\n\n        private void buttonItemGifSetting_Click(object sender, EventArgs e)\n        {\n            FrmGifSetting frm = new FrmGifSetting();\n            frm.Load(ImageHandlerConfig.Default);\n            frm.FFmpegBinPathHint = FFmpegEncoder.DefaultExecutionFileName;\n            frm.FFmpegArgumentHint = FFmpegEncoder.DefaultArgumentFormat;\n            frm.FFmpegDefaultExtensionHint = FFmpegEncoder.DefaultOutputFileExtension;\n            if (frm.ShowDialog() == DialogResult.OK)\n            {\n                ConfigManager.Reload();\n                frm.Save(ImageHandlerConfig.Default);\n                ConfigManager.Save();\n            }\n        }\n\n        private void buttonItemAutoSave_Click(object sender, EventArgs e)\n        {\n            ConfigManager.Reload();\n            ImageHandlerConfig.Default.AutoSaveEnabled = buttonItemAutoSave.Checked;\n            ConfigManager.Save();\n        }\n\n        private void buttonItemAutoSaveFolder_Click(object sender, EventArgs e)\n        {\n            using (FolderBrowserDialog dlg = new FolderBrowserDialog())\n            {\n                dlg.Description = \"请选择自动保存图片的文件夹...\";\n                dlg.SelectedPath = ImageHandlerConfig.Default.AutoSavePictureFolder;\n                if (DialogResult.OK == dlg.ShowDialog())\n                {\n                    labelItemAutoSaveFolder.Text = dlg.SelectedPath;\n                    ConfigManager.Reload();\n                    ImageHandlerConfig.Default.AutoSavePictureFolder = dlg.SelectedPath;\n                    ConfigManager.Save();\n                }\n            }\n        }\n\n        private void OnSaveImage(bool options)\n        {\n            if (this.pictureBoxEx1.Items.Count <= 0)\n            {\n                return;\n            }\n\n            var aniItem = this.pictureBoxEx1.Items[0];\n            var frameData = (aniItem as FrameAnimator)?.Data;\n            if (frameData != null && frameData.Frames.Count == 1 \n                && frameData.Frames[0].A0 == 255 && frameData.Frames[0].A1 == 255 && frameData.Frames[0].Delay == 0)\n            {\n                // save still picture as png\n                this.OnSavePngFile(frameData.Frames[0]);\n            }\n            else\n            {\n                // save as gif/apng\n                this.OnSaveGifFile(aniItem, options);\n            }\n        }\n\n        private void OnSavePngFile(Frame frame)\n        {\n            if (frame.Png != null)\n            {\n                var config = ImageHandlerConfig.Default;\n                int page = frame.Page;\n                string pngFileName = pictureBoxEx1.PictureName + (frame.Png.ActualPages > 1 ? $\".{page}\" : null) + \".png\";\n\n                if (config.AutoSaveEnabled)\n                {\n                    pngFileName = Path.Combine(config.AutoSavePictureFolder, string.Join(\"_\", pngFileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.None)));\n                }\n                else\n                {\n                    var dlg = new SaveFileDialog();\n                    dlg.Filter = \"Png图片(*.png)|*.png|全部文件(*.*)|*.*\";\n                    dlg.FileName = pngFileName;\n                    if (dlg.ShowDialog() != DialogResult.OK)\n                    {\n                        return;\n                    }\n\n                    pngFileName = dlg.FileName;\n                }\n\n                using (var bmp = frame.Png.ExtractPng(page))\n                {\n                    bmp.Save(pngFileName, System.Drawing.Imaging.ImageFormat.Png);\n                }\n                labelItemStatus.Text = \"图片保存于\" + pngFileName;\n            }\n            else\n            {\n                labelItemStatus.Text = \"没有文件被保存。\";\n            }\n        }\n\n        private void OnSaveGifFile(AnimationItem aniItem, bool options)\n        {\n            var config = ImageHandlerConfig.Default;\n            using var encoder = AnimateEncoderFactory.CreateEncoder(config);\n            var cap = encoder.Compatibility;\n\n            string aniName = this.cmbItemAniNames.SelectedItem as string;\n            string aniFileName = pictureBoxEx1.PictureName\n                    + (string.IsNullOrEmpty(aniName) ? \"\" : (\".\" + aniName))\n                    + cap.DefaultExtension;\n\n            if (config.AutoSaveEnabled)\n            {\n                var fullFileName = Path.Combine(config.AutoSavePictureFolder, string.Join(\"_\", aniFileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.None)));\n                int i = 1;\n                while (File.Exists(fullFileName))\n                {\n                    fullFileName = Path.Combine(config.AutoSavePictureFolder, string.Format(\"{0}({1}){2}\",\n                        Path.GetFileNameWithoutExtension(aniFileName), i, Path.GetExtension(aniFileName)));\n                    i++;\n                }\n                aniFileName = fullFileName;\n            }\n            else\n            {\n                var dlg = new SaveFileDialog();\n                string extensionFilter = string.Join(\";\", cap.SupportedExtensions.Select(ext => $\"*{ext}\"));\n                dlg.Filter = string.Format(\"{0} Supported Files ({1})|{1}|All files (*.*)|*.*\", encoder.Name, extensionFilter);\n                dlg.FileName = aniFileName;\n\n                if (dlg.ShowDialog() != DialogResult.OK)\n                {\n                    return;\n                }\n                aniFileName = dlg.FileName;\n            }\n\n            var clonedAniItem = (AnimationItem)aniItem.Clone();\n            if (this.pictureBoxEx1.SaveAsGif(clonedAniItem, aniFileName, config, encoder, options))\n            {\n                labelItemStatus.Text = \"图片保存于\" + aniFileName;\n            }\n        }\n        #endregion\n\n        #region File菜单的事件\n        private void btnItemOpenWz_Click(object sender, EventArgs e)\n        {\n            using (OpenFileDialog dlg = new OpenFileDialog())\n            {\n                dlg.Title = \"请选择冒险岛wz文件...\";\n                dlg.Filter = \"MapleStory Data File(Base.wz, *.wz, *.ms, *.mn)|*.wz;*.ms;*.mn\";\n                if (dlg.ShowDialog() == DialogResult.OK)\n                {\n                    openWz(dlg.FileName);\n                }\n            }\n        }\n\n        private void openWz(string wzFilePath)\n        {\n            foreach (Wz_Structure wzs in openedWz)\n            {\n                foreach (Wz_File wz_f in wzs.wz_files)\n                {\n                    if (string.Compare(wz_f.Header.FileName, wzFilePath, true) == 0)\n                    {\n                        MessageBoxEx.Show(\"已经打开的wz。\", \"喵~\");\n                        return;\n                    }\n                }\n            }\n\n            Wz_Structure wz = new Wz_Structure();\n            QueryPerformance.Start();\n            advTree1.BeginUpdate();\n            try\n            {\n                string[] msFileExtensions = { \".ms\", \".mn\" };\n                if (msFileExtensions.Any(ext => string.Equals(Path.GetExtension(wzFilePath), ext, StringComparison.OrdinalIgnoreCase)))\n                {\n                    wz.LoadMsFile(wzFilePath);\n                }\n                else if (wz.IsKMST1125WzFormat(wzFilePath))\n                {\n                    wz.LoadKMST1125DataWz(wzFilePath);\n                    if (string.Equals(Path.GetFileName(wzFilePath), \"Base.wz\", StringComparison.OrdinalIgnoreCase))\n                    {\n                        string packsDir = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(wzFilePath)), \"Packs\");\n                        if (Directory.Exists(packsDir))\n                        {\n                            foreach (var ext in msFileExtensions)\n                            {\n                                foreach (var msFile in Directory.GetFiles(packsDir, $\"*{ext}\"))\n                                {\n                                    wz.LoadMsFile(msFile);\n                                }\n                            }\n                        }\n                    }\n                }\n                else\n                {\n                    wz.Load(wzFilePath, true);\n                }\n                \n                if (WcR2Config.Default.SortWzOnOpened)\n                {\n                    sortWzNode(wz.WzNode);\n                }\n                Node node = createNode(wz.WzNode);\n                node.Expand();\n                advTree1.Nodes.Add(node);\n                this.openedWz.Add(wz);\n                OnWzOpened(new WzStructureEventArgs(wz)); //触发事件\n                QueryPerformance.End();\n                labelItemStatus.Text = \"读取成功,用时\" + (Math.Round(QueryPerformance.GetLastInterval(), 4) * 1000) + \"ms,共读取\" + wz.img_number + \"img.\";\n\n                ConfigManager.Reload();\n                WcR2Config.Default.RecentDocuments.Remove(wzFilePath);\n                WcR2Config.Default.RecentDocuments.Insert(0, wzFilePath);\n                ConfigManager.Save();\n                refreshRecentDocItems();\n            }\n            catch (FileNotFoundException)\n            {\n                MessageBoxEx.Show(\"文件没有找到\", \"嗯?\");\n            }\n            catch (Exception ex)\n            {\n                MessageBoxEx.Show(ex.ToString(), \"嗯?\");\n                wz.Clear();\n            }\n            finally\n            {\n                advTree1.EndUpdate();\n            }\n        }\n\n        private void btnItemOpenImg_Click(object sender, EventArgs e)\n        {\n            using (OpenFileDialog dlg = new OpenFileDialog())\n            {\n                dlg.Title = \"请选择冒险岛img文件...\";\n                dlg.Filter = \"*.img|*.img|*.wz|*.wz\";\n                if (dlg.ShowDialog() == DialogResult.OK)\n                {\n                    openImg(dlg.FileName);\n                }\n            }\n        }\n\n        private void openImg(string imgFileName)\n        {\n            foreach (Wz_Structure wzs in openedWz)\n            {\n                foreach (Wz_File wz_f in wzs.wz_files)\n                {\n                    if (StringComparer.OrdinalIgnoreCase.Equals(wz_f.Header.FileName, imgFileName))\n                    {\n                        MessageBoxEx.Show(\"已经打开的wz。\", \"喵~\");\n                        return;\n                    }\n                }\n            }\n\n            Wz_Structure wz = new Wz_Structure();\n            var sw = Stopwatch.StartNew();\n            advTree1.BeginUpdate();\n            try\n            {\n                wz.LoadImg(imgFileName);\n\n                Node node = createNode(wz.WzNode);\n                node.Expand();\n                advTree1.Nodes.Add(node);\n                this.openedWz.Add(wz);\n                OnWzOpened(new WzStructureEventArgs(wz)); //触发事件\n                sw.Stop();\n                labelItemStatus.Text = $\"读取成功,用时{sw.ElapsedMilliseconds}ms.\";\n                refreshRecentDocItems();\n            }\n            catch (FileNotFoundException)\n            {\n                MessageBoxEx.Show(\"文件没有找到\", \"嗯?\");\n            }\n            catch (Exception ex)\n            {\n                MessageBoxEx.Show(ex.ToString(), \"嗯?\");\n                wz.Clear();\n            }\n            finally\n            {\n                advTree1.EndUpdate();\n            }\n        }\n\n        private void buttonItemClose_Click(object sender, EventArgs e)\n        {\n            if (advTree1.SelectedNode == null)\n            {\n                MessageBoxEx.Show(\"没有选中要关闭的wz.\", \"喵~\");\n                return;\n            }\n            Node baseWzNode = advTree1.SelectedNode;\n            while (baseWzNode.Parent != null)\n                baseWzNode = baseWzNode.Parent;\n            if (baseWzNode.Text.ToLower() == \"list.wz\")\n            {\n                advTree1.Nodes.Remove(baseWzNode);\n                labelItemStatus.Text = \"已经关闭list.wz...\";\n                return;\n            }\n\n            Wz_File wz_f = advTree1.SelectedNode.AsWzNode()?.GetNodeWzFile();\n            if (wz_f == null)\n            {\n                MessageBoxEx.Show(\"没有正确选择要关闭的wz。\", \"喵~\");\n                return;\n            }\n            Wz_Structure wz = wz_f.WzStructure;\n\n            advTree1.Nodes.Remove(baseWzNode);\n\n            listViewExWzDetail.Items.Clear();\n\n            Wz_Image image = null;\n            if (advTree2.Nodes.Count > 0\n                && (image = advTree2.Nodes[0].AsWzNode()?.GetValue<Wz_Image>()) != null\n                && image.WzFile.WzStructure == wz)\n            {\n                advTree2.Nodes.Clear();\n            }\n\n            if (advTree3.Nodes.Count > 0\n                && (image = advTree3.Nodes[0].AsWzNode()?.GetNodeWzImage()) != null\n                && image.WzFile.WzStructure == wz)\n            {\n                advTree3.Nodes.Clear();\n            }\n\n            OnWzClosing(new WzStructureEventArgs(wz));\n            wz.Clear();\n            if (this.openedWz.Remove(wz))\n                labelItemStatus.Text = \"已经关闭所选wz...\";\n            else\n                labelItemStatus.Text = \"wz已经关闭,但是发生了诡异的错误...\";\n        }\n\n        private void buttonItemCloseAll_Click(object sender, EventArgs e)\n        {\n            advTree1.ClearAndDisposeAllNodes();\n            advTree1.ClearLayoutCellInfo();\n            advTree2.ClearAndDisposeAllNodes();\n            advTree2.ClearLayoutCellInfo();\n            advTree3.ClearAndDisposeAllNodes();\n            advTree3.ClearLayoutCellInfo();\n            foreach (Wz_Structure wz in openedWz)\n            {\n                OnWzClosing(new WzStructureEventArgs(wz));\n                wz.Clear();\n            }\n            openedWz.Clear();\n            CharaSimLoader.ClearAll();\n            stringLinker.Clear();\n            labelItemStatus.Text = \"已经清理全部已读取资源...\";\n            GC.Collect();\n        }\n\n        private void refreshRecentDocItems()\n        {\n            List<BaseItem> items = new List<BaseItem>();\n            foreach (BaseItem item in galleryContainerRecent.SubItems)\n            {\n                if (item is ButtonItem)\n                {\n                    items.Add(item);\n                }\n            }\n            galleryContainerRecent.SubItems.RemoveRange(items.ToArray());\n            items.Clear();\n\n            foreach (var doc in WcR2Config.Default.RecentDocuments)\n            {\n                ButtonItem item = new ButtonItem() { Text = \"&\" + (items.Count + 1) + \". \" + Path.GetFileName(doc), Tooltip = doc, Tag = doc };\n                item.Click += new EventHandler(buttonItemRecentDocument_Click);\n                items.Add(item);\n            }\n            galleryContainerRecent.SubItems.AddRange(items.ToArray());\n        }\n\n        void buttonItemRecentDocument_Click(object sender, EventArgs e)\n        {\n            ButtonItem btnItem = sender as ButtonItem;\n            string path;\n            if (btnItem == null || (path = btnItem.Tag as string) == null)\n                return;\n            openWz(path);\n        }\n        #endregion\n\n        #region wzView和提取的事件和方法\n        private void advTree1_DragEnter(object sender, DragEventArgs e)\n        {\n            string[] types = e.Data.GetFormats();\n            if (e.Data.GetDataPresent(DataFormats.FileDrop))\n            {\n                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);\n                foreach (string file in files)\n                {\n                    if (Path.GetExtension(file) != \".wz\")\n                    {\n                        e.Effect = DragDropEffects.None;\n                        return;\n                    }\n                }\n                e.Effect = DragDropEffects.Move;\n            }\n            else\n            {\n                e.Effect = DragDropEffects.None;\n            }\n        }\n\n        private void advTree1_DragDrop(object sender, DragEventArgs e)\n        {\n            if (e.Data.GetDataPresent(DataFormats.FileDrop))\n            {\n                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);\n                foreach (string file in files)\n                {\n                    openWz(file);\n                }\n            }\n        }\n\n        private void advTree1_AfterNodeSelect(object sender, AdvTreeNodeEventArgs e)\n        {\n            Wz_Node selectedNode = e.Node.AsWzNode();\n\n            if (selectedNode == null)\n            {\n                return;\n            }\n\n            listViewExWzDetail.BeginUpdate();\n            listViewExWzDetail.Items.Clear();\n\n            if (selectedNode.Value == null)\n            {\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Dir Name\", Path.GetFileName(e.Node.Text) }));\n                autoResizeColumns(listViewExWzDetail);\n            }\n            else if (selectedNode.Value is Wz_File wzFile)\n            {\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"File Name\", wzFile.Header.FileName }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"File Size\", wzFile.Header.FileSize + \" bytes\" }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Copyright\", wzFile.Header.Copyright }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Version\", wzFile.GetMergedVersion().ToString() }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Wz Type\", wzFile.IsSubDir ? \"SubDir\" : wzFile.Type.ToString() }));\n\n                foreach (Wz_File subFile in wzFile.MergedWzFiles)\n                {\n                    listViewExWzDetail.Items.Add(\" \");\n                    listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"File Name\", subFile.Header.FileName }));\n                    listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"File Size\", subFile.Header.FileSize + \" bytes\" }));\n                    listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Copyright\", subFile.Header.Copyright }));\n                    listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Version\", subFile.Header.WzVersion.ToString() }));\n                }\n\n                autoResizeColumns(listViewExWzDetail);\n            }\n            else if (selectedNode.Value is Wz_Image wzImage)\n            {\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Image Name\", wzImage.Name }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Image Size\", wzImage.Size + \" bytes\" }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Image Offset\", wzImage.Offset + \" bytes\" }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Path\", wzImage.Node.FullPathToFile }));\n                listViewExWzDetail.Items.Add(new ListViewItem(new string[] { \"Check Sum\", wzImage.Checksum.ToString() }));\n                autoResizeColumns(listViewExWzDetail);\n\n                advTree2.ClearAndDisposeAllNodes();\n                //advTree2.Nodes.Clear();\n\n                QueryPerformance.Start();\n                try\n                {\n                    Exception ex;\n                    if (wzImage.TryExtract(out ex))\n                    {\n                        advTree2.Nodes.Add(createNode(wzImage.Node));\n                        advTree2.Nodes[0].Expand();\n                        QueryPerformance.End();\n                        double ms = (Math.Round(QueryPerformance.GetLastInterval(), 4) * 1000);\n\n                        labelItemStatus.Text = \"读取image成功~用时\" + ms + \"ms...\";\n                    }\n                    else\n                    {\n\n                        labelItemStatus.Text = \"读取image失败...\" + ex.Message;\n                    }\n                }\n                catch (Exception ex)\n                {\n                    labelItemStatus.Text = \"读取image失败:\" + ex.Message;\n                }\n            }\n            listViewExWzDetail.EndUpdate();\n        }\n\n        private void autoResizeColumns(ListViewEx listView)\n        {\n            listView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);\n            foreach (System.Windows.Forms.ColumnHeader column in listView.Columns)\n            {\n                column.Width += (int)(listView.Font.Size * 2);\n            }\n        }\n\n        private void advTree2_NodeDoubleClick(object sender, TreeNodeMouseEventArgs e)\n        {\n            if (e.Node == null || e.Button != MouseButtons.Left)\n                return;\n            historyNodeList.Clear();\n            advTree3.Nodes.Clear();\n\n            var selectedNode = e.Node.AsWzNode();\n            if (selectedNode != null)\n            {\n                advTree3.BeginUpdate();\n                try\n                {\n                    var node = createNodeDetail(e.Node);\n                    node.ExpandAll();\n                    advTree3.Nodes.Add(node);\n                    advTree3.SelectedNode = node;\n                }\n                finally\n                {\n                    advTree3.EndUpdate();\n                }\n            }\n        }\n\n        private Node createNodeDetail(Node parentNode)\n        {\n            Node newNode = new Node(parentNode.Text);\n            newNode.Tag = parentNode.Tag;\n            Wz_Node wzNode = newNode.AsWzNode();\n            if (wzNode != null)\n            {\n                newNode.Cells.Add(new Cell(wzNode.Value == null ? \"<\" + parentNode.Nodes.Count + \">\" : getValueString(wzNode.Value)));\n                newNode.Cells.Add(new Cell(wzNode.Value == null ? null : wzNode.Value.GetType().Name));\n                newNode.ImageKey = wzNode.Value == null ? \"dir\" : (getValueImageKey(wzNode.Value) ?? \"num\");\n            }\n            foreach (Node subNode in parentNode.Nodes)\n            {\n                newNode.Nodes.Add(createNodeDetail(subNode));\n            }\n            return newNode;\n        }\n\n        private string getValueString(object value)\n        {\n            switch (value)\n            {\n                case Wz_Png png:\n                    return $\"png {png.Width}*{png.Height} ({(int)png.Format}{(png.Scale > 0 ? $\", {png.Scale}\" : null)})\";\n\n                case Wz_Vector vector:\n                    return $\"({vector.X}, {vector.Y})\";\n\n                case Wz_Uol uol:\n                    return uol.Uol;\n\n                case Wz_Sound sound:\n                    return $\"sound {sound.Ms}ms\";\n\n                case Wz_Image img:\n                    return $\"<{img.Node.Nodes.Count}>\";\n\n                case Wz_RawData rawData:\n                    return $\"rawdata {rawData.Length}\";\n\n                case Wz_Convex convex:\n                    return $\"convex [{convex.Points.Length}]\";\n\n                case Wz_Video video:\n                    return $\"video {video.Length}\";\n\n                default:\n                    string cellVal = Convert.ToString(value);\n                    if (cellVal != null && cellVal.Length > 50)\n                    {\n                        cellVal = cellVal.Substring(0, 50);\n                    }\n                    return cellVal;\n            }\n        }\n\n        private string getValueImageKey(object value)\n        {\n            return value switch\n            {\n                string => \"str\",\n                short or int or long or float or double=> \"num\",\n                Wz_Png => \"png\",\n                Wz_Vector => \"vector\",\n                Wz_Uol => \"uol\",\n                Wz_Sound sound => sound.SoundType == Wz_SoundType.Binary ? \"rawdata\" : \"mp3\",\n                Wz_Image => \"img\",\n                Wz_RawData => \"rawdata\",\n                Wz_Convex => \"convex\",\n                Wz_Video => \"video\",\n                _ => null\n            };\n        }\n\n        private void advTree3_AfterNodeSelect(object sender, AdvTreeNodeEventArgs e)\n        {\n            if (e.Node == null)\n                return;\n\n            if (!historySelecting && (historyNodeList.Count == 0 || e.Node != historyNodeList.Current))\n            {\n                historyNodeList.Add(e.Node);\n            }\n            else\n            {\n                historySelecting = false;\n            }\n\n            Wz_Node selectedNode = e.Node.AsWzNode();\n            if (selectedNode == null)\n                return;\n\n            switch (selectedNode.Value)\n            {\n                case Wz_Png png:\n                    pictureBoxEx1.PictureName = GetSelectedNodeImageName();\n                    pictureBoxEx1.ShowImage(png);\n                    this.cmbItemAniNames.Items.Clear();\n                    if (png.ActualPages > 1)\n                    {\n                        for (int i = 0; i < png.ActualPages; i++)\n                            this.cmbItemAniNames.Items.Add(i);\n                    }\n\n                    advTree3.PathSeparator = \".\";\n                    textBoxX1.Text = \"dataLength: \" + png.DataLength + \" bytes\\r\\n\" +\n                        \"offset: \" + png.Offset + \"\\r\\n\" +\n                        \"size: \" + png.Width + \"*\" + png.Height + \"\\r\\n\" +\n                        \"png format: \" + png.Format + \"(\" + (int)png.Format + \")\\r\\n\" +\n                        \"scale: \" + png.Scale + \"(x\" + png.ActualScale + \")\\r\\n\" +\n                        \"pages: \" + png.Pages + \"(\" + png.ActualPages + \")\\r\\n\" +\n                        \"unknown1: \" + png.Unknown1;\n                    break;\n\n                case Wz_Vector vector:\n                    textBoxX1.Text = \"x: \" + vector.X + \" px\\r\\n\" +\n                        \"y: \" + vector.Y + \" px\";\n                    break;\n\n                case Wz_Convex convex:\n                    var sb = new StringBuilder();\n                    for (int i = 0; i < convex.Points.Length; i++)\n                    {\n                        if (i > 0) sb.AppendLine();\n                        sb.AppendFormat(\"({0}, {1})\", convex.Points[i].X, convex.Points[i].Y);\n                    }\n                    textBoxX1.Text = sb.ToString();\n                    break;\n\n                case Wz_Uol uol:\n                    textBoxX1.Text = \"uolPath: \" + uol.Uol;\n                    break;\n\n                case Wz_Sound sound:\n                    preLoadSound(sound, selectedNode.Text);\n                    textBoxX1.Text = \"dataLength: \" + sound.DataLength + \" bytes\\r\\n\" +\n                        \"offset: \" + sound.Offset + \"\\r\\n\" +\n                        \"duration: \" + sound.Ms + \" ms\\r\\n\" +\n                        \"channels: \" + sound.Channels + \"\\r\\n\" +\n                        \"freq: \" + sound.Frequency + \" Hz\\r\\n\" +\n                        \"type: \" + sound.SoundType.ToString();\n                    break;\n\n                case Wz_Image:\n                    //do nothing;\n                    break;\n\n                case Wz_RawData rawData:\n                    textBoxX1.Text = \"dataLength: \" + rawData.Length + \" bytes\\r\\n\" +\n                        \"offset: \" + rawData.Offset;\n                    break;\n\n                case Wz_Video video:\n                    textBoxX1.Text = \"dataLength: \" + video.Length + \" bytes\\r\\n\" +\n                        \"offset: \" + video.Offset;\n                    var videoFrameData = this.pictureBoxEx1.LoadVideo(video);\n                    pictureBoxEx1.PictureName = GetSelectedNodeImageName();\n                    this.pictureBoxEx1.ShowAnimation(videoFrameData);\n                    this.cmbItemAniNames.Items.Clear();\n                    break;\n\n                default:\n                    string valueStr = Convert.ToString(selectedNode.Value);\n                    if (valueStr != null && valueStr.Contains(\"\\n\") && !valueStr.Contains(\"\\r\\n\"))\n                    {\n                        valueStr = valueStr.Replace(\"\\n\", \"\\r\\n\");\n                    }\n                    textBoxX1.Text = Convert.ToString(valueStr);\n\n                    switch (selectedNode.Text)\n                    {\n                        case \"source\":\n                        case \"_inlink\":\n                        case \"_outlink\":\n                            {\n                                var parentNode = selectedNode.ParentNode;\n                                if (parentNode != null && parentNode.Value is Wz_Png)\n                                {\n                                    var linkNode = parentNode.GetLinkedSourceNode(PluginManager.FindWz);\n                                    var png = linkNode.GetValueEx<Wz_Png>(null);\n\n                                    if (png != null)\n                                    {\n                                        pictureBoxEx1.PictureName = GetSelectedNodeImageName();\n                                        pictureBoxEx1.ShowImage(png);\n                                        this.cmbItemAniNames.Items.Clear();\n                                        advTree3.PathSeparator = \".\";\n                                        textBoxX1.AppendText(\"\\r\\n\\r\\ndataLength: \" + png.DataLength + \" bytes\\r\\n\" +\n                                            \"offset: \" + png.Offset + \"\\r\\n\" +\n                                            \"size: \" + png.Width + \"*\" + png.Height + \"\\r\\n\" +\n                                            \"png format: \" + png.Format + \"(\" + (int)png.Format + \")\\r\\n\" +\n                                            \"scale: \" + png.Scale + \"(x\" + png.ActualScale + \")\\r\\n\" +\n                                            \"pages: \" + png.Pages + \"(\" + png.ActualPages + \")\");\n                                    }\n                                }\n                            }\n                            break;\n                    }\n                    break;\n            }\n        }\n\n        private void pictureBox1_MouseDoubleClick(object sender, MouseEventArgs e)\n        {\n            /*\n            if (pictureBox1.Image != null && e.Button == MouseButtons.Left)\n            {\n                string tempFile = Path.Combine(Path.GetTempPath(), Convert.ToString(pictureBox1.Tag));\n                switch (Path.GetExtension(tempFile))\n                {\n                    case \".png\":\n                        pictureBox1.Image.Save(tempFile, System.Drawing.Imaging.ImageFormat.Png);\n                        System.Diagnostics.Process.Start(tempFile);\n                        break;\n                    case \".gif\":\n                        pictureBox1.Image.Save(tempFile, System.Drawing.Imaging.ImageFormat.Gif);\n                        System.Diagnostics.Process.Start(tempFile);\n                        break;\n                    default:\n                        MessageBoxEx.Show(\"不识别的文件名：\" + tempFile, \"喵~\");\n                        break;\n                }\n            }*/\n        }\n\n        private void listViewExString_MouseDoubleClick(object sender, MouseEventArgs e)\n        {\n            if (e.Button == MouseButtons.Left)\n            {\n                this.listViewExStringFind();\n            }\n        }\n\n        private void listViewExString_KeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.KeyCode == Keys.Enter)\n            {\n                this.listViewExStringFind();\n            }\n            else if (e.KeyCode == Keys.C && e.Control)\n            {\n                this.listViewExStringCopy();\n            }\n        }\n\n        private void listViewExStringFind()\n        {\n            if (listViewExString.SelectedItems.Count == 0 || advTree1.Nodes.Count == 0)\n            {\n                return;\n            }\n            string id = listViewExString.SelectedItems[0].Text;\n            string nodePath = listViewExString.SelectedItems[0].SubItems[3].Text;\n            List<string[]> objPathList = detectObjPathByStringPath(id, nodePath);\n\n            //分离wz路径和img路径\n            foreach (string[] fullPath in objPathList)\n            {\n                //寻找所有可能的wzfile\n                List<Wz_Node> allWzFile = new List<Wz_Node>();\n                Wz_Type wzType = ParseType(fullPath[0]);\n                foreach (var wzs in this.openedWz)\n                {\n                    foreach (var wzf in wzs.wz_files)\n                    {\n                        if (wzf.Type == wzType && wzf.OwnerWzFile == null)\n                        {\n                            allWzFile.Add(wzf.Node);\n                        }\n                    }\n                }\n\n                //开始搜索\n                foreach (var wzFileNode in allWzFile)\n                {\n                    Wz_Node node = SearchNode(wzFileNode, fullPath, 1);\n                    if (node != null)\n                    {\n                        OnSelectedWzNode(node); //遇到第一个 选中 返回\n                        return;\n                    }\n                }\n            }\n\n            //失败\n            string path;\n            if (objPathList.Count == 1)\n            {\n                path = string.Join(\"\\\\\", objPathList[0]);\n            }\n            else\n            {\n                path = \"(\" + objPathList.Count + \")个节点\";\n            }\n            labelItemStatus.Text = \"无法找到imageNode: \" + path;\n        }\n\n        private Wz_Node SearchNode(Wz_Node parent, string[] path, int startIndex)\n        {\n            if (startIndex >= path.Length)\n            {\n                return null;\n            }\n            if (parent.Value is Wz_Image)\n            {\n                Wz_Image img = parent.GetValue<Wz_Image>();\n                if (!img.TryExtract())\n                {\n                    return null;\n                }\n                parent = img.Node;\n            }\n            string nodeName = path[startIndex];\n            if (!string.IsNullOrEmpty(nodeName))\n            {\n                Wz_Node child = parent.FindNodeByPath(false, true, nodeName);\n                if (child != null)\n                {\n                    return (startIndex == path.Length - 1) ? child : SearchNode(child, path, startIndex + 1);\n                }\n            }\n            else //遍历全部\n            {\n                foreach (Wz_Node child in parent.Nodes)\n                {\n                    if (child.Nodes.Count == 0) //只过滤文件夹 未来有需求再改\n                    {\n                        continue;\n                    }\n                    Wz_Node find = SearchNode(child, path, startIndex + 1);\n                    if (find != null)\n                    {\n                        return (startIndex == path.Length - 1) ? null : find;\n                    }\n\n                }\n            }\n\n            return null;\n        }\n\n        private bool OnSelectedWzNode(Wz_Node wzNode)\n        {\n            Wz_File wzFile = wzNode.GetNodeWzFile();\n            string[] path = wzNode.FullPathToFile.Split('\\\\');\n            if (wzFile == null)\n            {\n                return false;\n            }\n\n            Node treeNode = findWzFileTreeNode(wzFile);\n            if (treeNode == null)\n            {\n                return false;\n            }\n\n            for (int i = 1; i < path.Length; i++)\n            {\n                Node find = null;\n                foreach (Node child in treeNode.Nodes)\n                {\n                    if (child.Text == path[i])\n                    {\n                        find = child;\n                        break;\n                    }\n                }\n                if (find == null)\n                {\n                    return false;\n                }\n\n                if (find.AsWzNode()?.Value is Wz_Image)\n                {\n                    advTree1.SelectedNode = find;\n                    if (advTree2.Nodes.Count > 0)\n                    {\n                        treeNode = advTree2.Nodes[0];\n                    }\n                    else\n                    {\n                        return false;\n                    }\n                }\n                else\n                {\n                    treeNode = find;\n                }\n            }\n\n            advTree2.SelectedNode = treeNode;\n            return true;\n        }\n\n        private void listViewExStringCopy()\n        {\n            if (listViewExString.SelectedItems.Count == 0 || advTree1.Nodes.Count == 0)\n            {\n                return;\n            }\n\n            StringBuilder sb = new StringBuilder();\n            foreach (ListViewItem.ListViewSubItem item in listViewExString.SelectedItems[0].SubItems)\n            {\n                sb.Append(item.Text).Append(\" \");\n            }\n            sb.Remove(sb.Length - 1, 1);\n            Clipboard.SetText(sb.ToString(), TextDataFormat.UnicodeText);\n            labelItemStatus.Text = \"已复制string条目到剪切板。\";\n        }\n\n        private List<string[]> detectObjPathByStringPath(string id, string stringNodePath)\n        {\n            List<string[]> pathList = new List<string[]>();\n\n            List<string> wzPath = new List<string>();\n            List<string> imagePath = new List<string>();\n\n            Action addPath = () =>\n            {\n                List<string> fullPath = new List<string>(wzPath.Count + imagePath.Count);\n                fullPath.AddRange(wzPath);\n                fullPath.AddRange(imagePath);\n                pathList.Add(fullPath.ToArray());\n            };\n\n            string[] pathArray = stringNodePath.Split('\\\\');\n            switch (pathArray[0])\n            {\n                case \"Cash.img\":\n                case \"Consume.img\":\n                case \"Etc.img\":\n                case \"Pet.img\":\n                    wzPath.Add(\"Item\");\n                    wzPath.Add(pathArray[0].Substring(0, pathArray[0].IndexOf(\".img\")));\n                    if (pathArray[0] == \"Pet.img\")\n                    {\n                        wzPath.Add(id.TrimStart('0') + \".img\");\n                    }\n                    else\n                    {\n                        id = id.PadLeft(8, '0');\n                        wzPath.Add(id.Substring(0, 4) + \".img\");\n                        imagePath.Add(id);\n                    }\n                    addPath();\n                    break;\n\n                case \"Ins.img\": //KMST1066\n                    wzPath.Add(\"Item\");\n                    wzPath.Add(\"Install\");\n                    wzPath.Add(\"\");\n                    id = id.PadLeft(8, '0');\n                    imagePath.Add(id);\n                    for (int len = 4; len <= 6; len++)\n                    {\n                        wzPath[2] = id.Substring(0, len) + \".img\";\n                        addPath();\n                    }\n                    break;\n\n                case \"Eqp.img\":\n                    wzPath.Add(\"Character\");\n                    if (pathArray[2] == \"Taming\")\n                    {\n                        wzPath.Add(\"TamingMob\");\n                    }\n                    else\n                    {\n                        wzPath.Add(pathArray[2]);\n                    }\n                    wzPath.Add(id.PadLeft(8, '0') + \".img\");\n                    addPath();\n                    //往往这个不靠谱。。 加一个任意门备用\n                    wzPath[1] = \"\";\n                    addPath();\n                    break;\n\n                case \"Map.img\":\n                    id = id.PadLeft(9, '0');\n                    wzPath.AddRange(new string[] { \"Map\", \"Map\", \"Map\" + id[0], id + \".img\" });\n                    addPath();\n                    break;\n\n                case \"Mob.img\":\n                    wzPath.Add(\"Mob\");\n                    wzPath.Add(id.PadLeft(7, '0') + \".img\");\n                    addPath();\n                    break;\n\n                case \"Npc.img\":\n                    wzPath.Add(\"Npc\");\n                    wzPath.Add(id.PadLeft(7, '0') + \".img\");\n                    addPath();\n                    break;\n\n                case \"Skill.img\":\n                    id = id.PadLeft(7, '0');\n                    wzPath.Add(\"Skill\");\n                    //old skill\n                    wzPath.Add(id.Substring(0, id.Length - 4) + \".img\");\n                    imagePath.Add(\"skill\");\n                    imagePath.Add(id);\n                    addPath();\n                    if (Regex.IsMatch(id, @\"80\\d{6}\")) //kmst new skill\n                    {\n                        wzPath[1] = id.Substring(0, 6) + \".img\";\n                        addPath();\n                    }\n                    break;\n                default:\n                    break;\n            }\n\n            return pathList;\n        }\n\n        /// <summary>\n        /// 通过给定的wz名称，在advTree1中寻找第一个对应的wz_file节点。\n        /// </summary>\n        /// <param Name=\"wzName\">要寻找的wz名称，不包含\".wz\"后缀。</param>\n        /// <returns></returns>\n        private Node findWzFileTreeNode(string wzName)\n        {\n            Wz_Type type = ParseType(wzName);\n            if (type == Wz_Type.Unknown)\n            {\n                return null;\n            }\n\n            foreach (var wzs in this.openedWz)\n            {\n                foreach (var wzf in wzs.wz_files)\n                {\n                    if (wzf.Type == type)\n                    {\n                        Node node = findWzFileTreeNode(wzf);\n                        if (node != null)\n                        {\n                            return node;\n                        }\n                    }\n                }\n            }\n\n            return null;\n        }\n\n        private Wz_Type ParseType(string wzName)\n        {\n            Wz_Type type;\n            try\n            {\n                type = (Wz_Type)Enum.Parse(typeof(Wz_Type), wzName, true);\n            }\n            catch\n            {\n                type = Wz_Type.Unknown;\n            }\n\n            return type;\n        }\n\n        private Node findWzFileTreeNode(Wz_File wzFile)\n        {\n            foreach (Node baseNode in advTree1.Nodes)\n            {\n                Wz_File wz_f = baseNode.AsWzNode()?.Value as Wz_File;\n                if (wz_f != null)\n                {\n                    if (wz_f == wzFile)\n                    {\n                        return baseNode;\n                    }\n                    else if (wz_f.Type == Wz_Type.Base)\n                    {\n                        foreach (Node wzNode in baseNode.Nodes)\n                        {\n                            if ((wz_f = wzNode.AsWzNode()?.Value as Wz_File) != null && wz_f == wzFile)\n                            {\n                                return wzNode;\n                            }\n                        }\n                    }\n                }\n            }\n            return null;\n        }\n\n        private Node findChildTreeNode(Node parent, string[] path)\n        {\n            if (parent == null || path == null)\n                return null;\n            for (int i = 0; i < path.Length; i++)\n            {\n                bool find = false;\n                foreach (Node subNode in parent.Nodes)\n                {\n                    if (subNode.Text == path[i])\n                    {\n                        parent = subNode;\n                        find = true;\n                        break;\n                    }\n                }\n                if (!find)\n                {\n                    return null;\n                }\n            }\n            return parent;\n        }\n        #endregion\n\n        #region contextMenuStrip1\n        private void tsmi1Sort_Click(object sender, EventArgs e)\n        {\n            if (openedWz.Count > 0)\n            {\n                var sw = Stopwatch.StartNew();\n                advTree1.BeginUpdate();\n                try\n                {\n                    advTree1.ClearAndDisposeAllNodes();\n                    foreach (Wz_Structure wz in openedWz)\n                    {\n                        sortWzNode(wz.WzNode);\n                        Node node = createNode(wz.WzNode);\n                        node.Expand();\n                        advTree1.Nodes.Add(node);\n                    }\n                }\n                finally\n                {\n                    advTree1.EndUpdate();\n                    sw.Stop();\n                }\n                GC.Collect();\n                labelItemStatus.Text = $\"排序成功,用时{sw.ElapsedMilliseconds}ms.\";\n            }\n            else\n            {\n                labelItemStatus.Text = \"没有打开的wz\";\n            }\n        }\n\n        private void tsmi1Export_Click(object sender, EventArgs e)\n        {\n            Wz_Image img = advTree1.SelectedNode?.AsWzNode()?.GetValue<Wz_Image>();\n            if (img == null)\n            {\n                MessageBoxEx.Show(\"没有选中一个用于导出的wz_img。\");\n                return;\n            }\n            SaveFileDialog dlg = new SaveFileDialog();\n            dlg.DefaultExt = \".img\";\n            dlg.FileName = img.Name;\n            dlg.Filter = \"*.img|*.img\";\n            if (dlg.ShowDialog() == DialogResult.OK)\n            {\n                FileStream fs = null;\n                try\n                {\n                    fs = new FileStream(dlg.FileName, FileMode.Create, FileAccess.Write);\n                    var s = img.OpenRead();\n                    s.Position = 0;\n                    s.CopyTo(fs);\n                    fs.Close();\n                    labelItemStatus.Text = img.Name + \"导出完毕。\";\n                }\n                catch (Exception ex)\n                {\n                    fs?.Close();\n                    MessageBoxEx.Show(ex.ToString(), \"错了\");\n                }\n            }\n        }\n\n        private void tsmi1DumpAsXml_Click(object sender, EventArgs e)\n        {\n            Wz_Image img = advTree1.SelectedNode?.AsWzNode()?.GetValue<Wz_Image>();\n            if (img == null)\n            {\n                MessageBoxEx.Show(\"没有选中一个用于导出的wz_img。\");\n                return;\n            }\n            SaveFileDialog dlg = new SaveFileDialog();\n            dlg.DefaultExt = \".xml\";\n            dlg.Filter = \"*.xml|*.xml\";\n            dlg.FileName = img.Node.FullPathToFile.Replace('\\\\', '.') + \".xml\";\n            if (dlg.ShowDialog() == DialogResult.OK)\n            {\n                FileStream fs = null;\n                try\n                {\n                    fs = new FileStream(dlg.FileName, FileMode.Create, FileAccess.Write);\n                    var xsetting = new XmlWriterSettings()\n                    {\n                        CloseOutput = false,\n                        Indent = true,\n                        Encoding = Encoding.UTF8,\n                        CheckCharacters = true,\n                        NewLineChars = Environment.NewLine,\n                        NewLineOnAttributes = false,\n                    };\n                    var writer = XmlWriter.Create(fs, xsetting);\n                    writer.WriteStartDocument(true);\n                    img.Node.DumpAsXml(writer);\n                    writer.WriteEndDocument();\n                    writer.Close();\n\n                    labelItemStatus.Text = img.Name + \"导出完毕。\";\n                }\n                catch (Exception ex)\n                {\n                    MessageBoxEx.Show(ex.ToString(), \"错了\");\n                }\n                finally\n                {\n                    if (fs != null)\n                    {\n                        fs.Close();\n                    }\n                }\n            }\n        }\n        #endregion\n\n        #region Tools菜单事件和方法\n        private void buttonItemSearchWz_Click(object sender, EventArgs e)\n        {\n            if (string.IsNullOrEmpty(textBoxItemSearchWz.Text))\n                return;\n            if (comboBoxItem1.SelectedIndex == -1)\n            {\n                comboBoxItem1.SelectedIndex = 0;\n            }\n\n            switch (comboBoxItem1.SelectedIndex)\n            {\n                case 0:\n                    searchAdvTree(advTree1, 0, textBoxItemSearchWz.Text, checkBoxItemExact1.Checked, checkBoxItemRegex1.Checked);\n                    break;\n                case 1:\n                    searchAdvTree(advTree2, 0, textBoxItemSearchWz.Text, checkBoxItemExact1.Checked, checkBoxItemRegex1.Checked);\n                    break;\n                case 2:\n                    searchAdvTree(advTree3, 1, textBoxItemSearchWz.Text, checkBoxItemExact1.Checked, checkBoxItemRegex1.Checked);\n                    break;\n                case 3: //full path\n                    searchAdvTreeFullPath(textBoxItemSearchWz.Text);\n                    break;\n            }\n        }\n\n        private void searchAdvTree(AdvTree advTree, int cellIndex, string searchText, bool exact, bool regex)\n        {\n            if (string.IsNullOrEmpty(searchText))\n                return;\n\n            try\n            {\n                Node searchNode = searchAdvTree(advTree, cellIndex, searchText, exact, regex, true);\n                advTree.SelectedNode = searchNode;\n                if (searchNode == null)\n                    MessageBoxEx.Show(\"已经搜索到末尾。\", \"喵呜~\");\n            }\n            catch (Exception ex)\n            {\n                MessageBoxEx.Show(this, ex.Message, \"错误\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n            }\n        }\n\n        private Node searchAdvTree(AdvTree advTree, int cellIndex, string searchText, bool exact, bool isRegex, bool ignoreCase)\n        {\n            if (advTree.Nodes.Count == 0)\n                return null;\n\n            if (isRegex)\n            {\n                Regex r = new Regex(searchText, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);\n                foreach (var node in findNextNode(advTree))\n                {\n                    if (node != null && node.Cells.Count > cellIndex && r.IsMatch(node.Cells[cellIndex].Text))\n                    {\n                        return node;\n                    }\n                }\n            }\n            else\n            {\n                string[] pattern = searchText.Split('\\\\');\n                foreach (var node in findNextNode(advTree))\n                {\n                    if (checkSearchNodeText(node, cellIndex, pattern, exact, ignoreCase))\n                    {\n                        return node;\n                    }\n                }\n            }\n            \n            return null;\n        }\n\n        private void searchAdvTreeFullPath(string fullPath)\n        {\n            string[] pathSegments = fullPath.Split('/');\n\n            bool isNodePathMatches(string pathSegment, string nodeName, StringComparison stringComparison)\n            {\n                if (string.Equals(pathSegment, nodeName, stringComparison))\n                {\n                    return true;\n                }\n                int pathExtIndex = pathSegment.LastIndexOf('.');\n                int nodeExtIndex = nodeName.LastIndexOf(\".\");\n                if (pathExtIndex != -1 || nodeExtIndex != -1)\n                {\n                    ReadOnlySpan<char> pathWithoutExt = pathExtIndex == -1 ? pathSegment.AsSpan() : pathSegment.AsSpan(0, pathExtIndex);\n                    ReadOnlySpan<char> nodeWithoutExt = nodeExtIndex == -1 ? nodeName.AsSpan() : nodeName.AsSpan(0, nodeExtIndex);\n                    return pathWithoutExt.Equals(nodeWithoutExt, stringComparison);\n                }\n                return false;\n            }\n\n            IEnumerable<(Node result, int resultPathLevel)> searchNode(Node treeNode, int pathLevel)\n            {\n                string pathSegment = pathSegments[pathLevel];\n                if (isNodePathMatches(pathSegment, treeNode.Text, StringComparison.OrdinalIgnoreCase))\n                {\n                    if (treeNode.Nodes.Count == 0 || pathLevel == pathSegments.Length - 1)\n                    {\n                        yield return (treeNode, pathLevel);\n                    }\n                    foreach (Node childNode in treeNode.Nodes)\n                    {\n                        foreach (var resultTuple in searchNode(childNode, pathLevel + 1))\n                        {\n                            yield return resultTuple;\n                        }\n                    }\n                }\n            }\n\n            foreach (var node in findNextNode(this.advTree1))\n            {\n                foreach ((Node result, int resultPathLevel) in searchNode(node, 0))\n                {\n                    if (resultPathLevel == pathSegments.Length - 1)\n                    {\n                        this.advTree1.SelectedNode = node;\n                        return;\n                    }\n\n                    var wzNode = result.AsWzNode();\n                    if (wzNode != null && wzNode.Value is Wz_Image wzImg && wzImg.TryExtract(out _))\n                    {\n                        // find remaining path in wzImg\n                        wzNode = wzImg.Node;\n                        for (int i = resultPathLevel + 1; i < pathSegments.Length; i++)\n                        {\n                            string pathSegment = pathSegments[i];\n                            wzNode = wzNode.Nodes.FirstOrDefault(child => isNodePathMatches(pathSegment, child.Text, StringComparison.OrdinalIgnoreCase));\n                            if (wzNode == null)\n                            {\n                                break;\n                            }\n                        }\n                        if (wzNode != null && this.OnSelectedWzNode(wzNode))\n                        {\n                            return;\n                        }\n                    }\n                }\n            }\n\n            this.advTree1.SelectedNode = null;\n            MessageBoxEx.Show(this, \"已经搜索到末尾。\");\n        }\n\n        private IEnumerable<Node> findNextNode(AdvTree advTree)\n        {\n            var node = advTree.SelectedNode;\n            if (node == null)\n            {\n                node = advTree.Nodes[0];\n                yield return node;\n            }\n\n            var levelStack = new Stack<int>();\n            int index = node.Index + 1;\n\n            while (true)\n            {\n                if (node.Nodes.Count > 0)\n                {\n                    levelStack.Push(index);\n                    index = 0;\n                    yield return node = node.Nodes[index++];\n                    continue;\n                }\n\n                NodeCollection owner;\n\n                while (index >= (owner = (node.Parent?.Nodes ?? advTree.Nodes)).Count)\n                {\n                    node = node.Parent;\n                    if (node == null)\n                    {\n                        yield break;\n                    }\n                    if (levelStack.Count > 0)\n                    {\n                        index = levelStack.Pop();\n                    }\n                    else\n                    {\n                        index = node.Index + 1;\n                    }\n                }\n\n                yield return node = owner[index++];\n            }\n        }\n\n        private bool checkSearchNodeText(Node node, int cellIndex, string[] searchTextArray, bool exact, bool ignoreCase)\n        {\n            if (node == null || searchTextArray == null || searchTextArray.Length == 0)\n                return false;\n            for (int i = searchTextArray.Length - 1; i >= 0; i--)\n            {\n                if (node == null || node.Cells.Count <= cellIndex)\n                    return false;\n                if (exact)\n                {\n                    if (string.Compare(node.Cells[cellIndex].Text, searchTextArray[i], ignoreCase) != 0)\n                        return false;\n                }\n                else\n                {\n                    if (ignoreCase ? node.Cells[cellIndex].Text.IndexOf(searchTextArray[i], StringComparison.CurrentCultureIgnoreCase) < 0 :\n                        !node.Text.Contains(searchTextArray[i]))\n                        return false;\n                }\n\n                node = node.Parent;\n            }\n            return true;\n        }\n\n        private void textBoxItemSearchWz_KeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.KeyCode == Keys.Enter)\n            {\n                buttonItemSearchWz_Click(buttonItemSearchWz, EventArgs.Empty);\n            }\n        }\n\n        private void buttonItemSearchString_Click(object sender, EventArgs e)\n        {\n            if (string.IsNullOrEmpty(textBoxItemSearchString.Text))\n                return;\n            QueryPerformance.Start();\n            if (!this.stringLinker.HasValues)\n            {\n                if (!this.TryLoadStringWz())\n                {\n                    MessageBoxEx.Show(\"没有初始化string链接，请手动指定一个String.wz。\", \"喵~~\");\n                    return;\n                }\n                QueryPerformance.End();\n                double ms = (Math.Round(QueryPerformance.GetLastInterval(), 4) * 1000);\n                labelItemStatus.Text = \"初始化StringLinker成功, 用时\" + ms + \"ms.\";\n            }\n            if (comboBoxItem2.SelectedIndex < 0)\n                comboBoxItem2.SelectedIndex = 0;\n\n            List<Dictionary<int, StringResult>> dicts = new List<Dictionary<int, StringResult>>();\n            switch (comboBoxItem2.SelectedIndex)\n            {\n                case 0:\n                    dicts.Add(stringLinker.StringEqp);\n                    dicts.Add(stringLinker.StringItem);\n                    dicts.Add(stringLinker.StringMap);\n                    dicts.Add(stringLinker.StringMob);\n                    dicts.Add(stringLinker.StringNpc);\n                    dicts.Add(stringLinker.StringSkill);\n                    break;\n                case 1:\n                    dicts.Add(stringLinker.StringEqp);\n                    break;\n                case 2:\n                    dicts.Add(stringLinker.StringItem);\n                    break;\n                case 3:\n                    dicts.Add(stringLinker.StringMap);\n                    break;\n                case 4:\n                    dicts.Add(stringLinker.StringMob);\n                    break;\n                case 5:\n                    dicts.Add(stringLinker.StringNpc);\n                    break;\n                case 6:\n                    dicts.Add(stringLinker.StringSkill);\n                    break;\n            }\n\n            listViewExString.BeginUpdate();\n            try\n            {\n                listViewExString.Items.Clear();\n                IEnumerable<KeyValuePair<int, StringResult>> results = searchStringLinker(dicts, textBoxItemSearchString.Text, checkBoxItemExact2.Checked, checkBoxItemRegex2.Checked);\n                foreach (KeyValuePair<int, StringResult> kv in results)\n                {\n                    string[] item = new string[] { kv.Key.ToString(), kv.Value.Name, kv.Value.Desc, kv.Value.FullPath };\n                    listViewExString.Items.Add(new ListViewItem(item));\n                }\n            }\n            catch (Exception ex)\n            {\n                MessageBoxEx.Show(ex.Message, \"错误\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n            }\n            finally\n            {\n                listViewExString.EndUpdate();\n            }            \n        }\n\n        private bool TryLoadStringWz()\n        {\n            foreach (Wz_Structure wz in openedWz)\n            {\n                foreach (Wz_File file in wz.wz_files)\n                {\n                    if (file.Type == Wz_Type.String && this.stringLinker.Load(file))\n                    {\n                        return true;\n                    }\n                }\n            }\n            return false;\n        }\n\n        private IEnumerable<KeyValuePair<int, StringResult>> searchStringLinker(IEnumerable<Dictionary<int, StringResult>> dicts, string key, bool exact, bool isRegex)\n        {\n            string[] match = key.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);\n            Regex re = null;\n            if (isRegex)\n            {\n                re = new Regex(key, RegexOptions.IgnoreCase);\n            }\n\n            foreach (Dictionary<int, StringResult> dict in dicts)\n            {\n                foreach (KeyValuePair<int, StringResult> kv in dict)\n                {\n                    if (exact)\n                    {\n                        if (kv.Key.ToString() == key || kv.Value.Name == key)\n                            yield return kv;\n                    }\n                    else if (isRegex)\n                    {\n                        if (re.IsMatch(kv.Key.ToString()) || (!string.IsNullOrEmpty(kv.Value.Name) && re.IsMatch(kv.Value.Name)))\n                        {\n                            yield return kv;\n                        }\n                    }\n                    else\n                    {\n                        string id = kv.Key.ToString();\n                        bool r = true;\n                        foreach (string str in match)\n                        {\n                            if (!(id.Contains(str) || (!string.IsNullOrEmpty(kv.Value.Name) && kv.Value.Name.Contains(str))))\n                            {\n                                r = false;\n                                break;\n                            }\n                        }\n                        if (r)\n                        {\n                            yield return kv;\n                        }\n                    }\n                }\n            }\n        }\n\n        private void textBoxItemSearchString_KeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.KeyCode == Keys.Enter)\n            {\n                buttonItemSearchString_Click(buttonItemSearchString, EventArgs.Empty);\n            }\n        }\n\n        private void buttonItemSelectStringWz_Click(object sender, EventArgs e)\n        {\n            Wz_File stringWzFile = advTree1.SelectedNode?.AsWzNode()?.GetNodeWzFile();\n            if (stringWzFile == null)\n            {\n                MessageBoxEx.Show(\"没有选择一个用于初始化StringLinker的WzFile。\", \"喵...\");\n                return;\n            }\n            QueryPerformance.Start();\n            bool r = stringLinker.Load(stringWzFile);\n            QueryPerformance.End();\n            if (r)\n            {\n                double ms = (Math.Round(QueryPerformance.GetLastInterval(), 4) * 1000);\n                labelItemStatus.Text = \"初始化StringLinker成功, 用时\" + ms + \"ms.\";\n            }\n            else\n            {\n                MessageBoxEx.Show(\"初始化StringLinker失败。\", \"喵..\");\n            }\n        }\n\n        private void buttonItemClearStringWz_Click(object sender, EventArgs e)\n        {\n            stringLinker.Clear();\n            labelItemStatus.Text = \"StringLinker已经清空...\";\n        }\n\n        private void buttonItemPatcher_Click(object sender, EventArgs e)\n        {\n            foreach (Form form in Application.OpenForms)\n            {\n                if (form is FrmPatcher && !form.IsDisposed)\n                {\n                    form.Show();\n                    form.BringToFront();\n                    return;\n                }\n            }\n            FrmPatcher patcher = new FrmPatcher();\n            var config = WcR2Config.Default;\n            var defaultEnc = config?.WzEncoding?.Value ?? 0;\n            if (defaultEnc != 0)\n            {\n                patcher.PatcherNoticeEncoding = Encoding.GetEncoding(defaultEnc);\n            }\n            patcher.Owner = this;\n            patcher.Show();\n        }\n        #endregion\n\n        #region soundPlayer相关事件\n        private void preLoadSound(Wz_Sound sound, string soundName)\n        {\n            byte[] data = sound.ExtractSound();\n            if (data == null || data.Length <= 0)\n            {\n                return;\n            }\n            soundPlayer.PreLoad(data);\n            labelItemSoundTitle.Text = soundName;\n\n            switch (sound.SoundType)\n            {\n                case Wz_SoundType.Mp3: soundName += \".mp3\"; break;\n                case Wz_SoundType.Pcm: soundName += \".wav\"; break;\n            }\n            soundPlayer.PlayingSoundName = soundName;\n            labelItemSoundTitle.Tooltip = soundName;\n        }\n\n        private void sliderItemSoundTime_ValueChanged(object sender, EventArgs e)\n        {\n            if (!timerChangeValue)\n                soundPlayer.SoundPosition = sliderItemSoundTime.Value;\n        }\n\n        private void sliderItemSoundVol_ValueChanged(object sender, EventArgs e)\n        {\n            soundPlayer.Volume = sliderItemSoundVol.Value;\n        }\n\n        private void buttonItemLoadSound_Click(object sender, EventArgs e)\n        {\n            using (OpenFileDialog dlg = new OpenFileDialog())\n            {\n                List<string> supportExt = new List<string>();\n                supportExt.Add(\"Sound File(*.mp3;*.ogg;*.wav)|*.mp3;*.ogg;*.wav\");\n                foreach (string ext in this.soundPlayer.GetPluginSupportedExt())\n                {\n                    supportExt.Add(ext);\n                }\n                supportExt.Add(\"Any File|*.*\");\n\n                dlg.Title = \"请选择一个声音文件...\";\n                dlg.Filter = string.Join(\"|\", supportExt.ToArray());\n                dlg.Multiselect = false;\n\n                if (DialogResult.OK == dlg.ShowDialog())\n                {\n                    loadCostumSoundFile(dlg.FileName);\n                }\n            }\n        }\n\n        private void buttonItemSoundPlay_Click(object sender, EventArgs e)\n        {\n            if (soundPlayer.State == PlayState.Playing)\n            {\n                soundPlayer.Pause();\n                buttonItemSoundPlay.Image = WzComparerR2.Properties.Resources.Play;\n                //buttonItemSoundPlay.Text = \" Play\";\n            }\n            else if (soundPlayer.State == PlayState.Paused)\n            {\n                soundPlayer.Resume();\n                //buttonItemSoundPlay.Text = \"Pause\";\n                buttonItemSoundPlay.Image = WzComparerR2.Properties.Resources.Pause;\n            }\n            else\n            {\n                soundPlayer.Play();\n                //buttonItemSoundPlay.Text = \"Pause\";\n                buttonItemSoundPlay.Image = WzComparerR2.Properties.Resources.Pause;\n            }\n        }\n\n        private void buttonItemSoundStop_Click(object sender, EventArgs e)\n        {\n            soundPlayer.Stop();\n            //buttonItemSoundPlay.Text = \" Play\";\n            buttonItemSoundPlay.Image = WzComparerR2.Properties.Resources.Play;\n        }\n\n        private void buttonItemSoundSave_Click(object sender, EventArgs e)\n        {\n            byte[] data = soundPlayer.Data;\n            if (data == null)\n                return;\n\n            using (SaveFileDialog dlg = new SaveFileDialog())\n            {\n                dlg.AddExtension = true;\n                dlg.Title = \"请选择保存路径...\";\n                dlg.Filter = \"*.*|*.*\";\n                dlg.AddExtension = false;\n                dlg.FileName = soundPlayer.PlayingSoundName;\n                if (dlg.ShowDialog() == DialogResult.OK)\n                {\n                    FileStream fs = null;\n                    try\n                    {\n                        fs = new FileStream(dlg.FileName, FileMode.Create);\n                        fs.Write(data, 0, data.Length);\n\n                        MessageBoxEx.Show(\"保存成功！\");\n                    }\n                    catch (Exception ex)\n                    {\n                        MessageBoxEx.Show(\"保存失败\\r\\n\\r\\n\" + ex.ToString(), \"错误\");\n                    }\n                    finally\n                    {\n                        if (fs != null)\n                        {\n                            fs.Close();\n                        }\n                    }\n                }\n            }\n        }\n\n        private void checkBoxItemSoundLoop_CheckedChanged(object sender, CheckBoxChangeEventArgs e)\n        {\n            soundPlayer.Loop = checkBoxItemSoundLoop.Checked;\n        }\n\n        private void soundTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)\n        {\n            TimeSpan currentTime = TimeSpan.FromSeconds(soundPlayer.SoundPosition);\n            TimeSpan totalTime = TimeSpan.FromSeconds(soundPlayer.SoundLength);\n            labelItemSoundTime.Text = string.Format(\"{0:d2}:{1:d2}:{2:d2}.{3:d3} / {4:d2}:{5:d2}:{6:d2}.{7:d3}\",\n                currentTime.Hours, currentTime.Minutes, currentTime.Seconds, currentTime.Milliseconds,\n                totalTime.Hours, totalTime.Minutes, totalTime.Seconds, totalTime.Milliseconds);\n            timerChangeValue = true;\n            sliderItemSoundTime.Maximum = (int)totalTime.TotalSeconds;\n            sliderItemSoundTime.Value = (int)currentTime.TotalSeconds;\n            timerChangeValue = false;\n        }\n\n        private void ribbonBar3_DragEnter(object sender, DragEventArgs e)\n        {\n            string[] types = e.Data.GetFormats();\n            if (e.Data.GetDataPresent(DataFormats.FileDrop))\n            {\n                e.Effect = DragDropEffects.Move;\n            }\n            else\n            {\n                e.Effect = DragDropEffects.None;\n            }\n        }\n\n        private void ribbonBar3_DragDrop(object sender, DragEventArgs e)\n        {\n            if (e.Data.GetDataPresent(DataFormats.FileDrop))\n            {\n                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);\n                loadCostumSoundFile(files[0]);\n            }\n        }\n\n        private void loadCostumSoundFile(string fileName)\n        {\n            CustomSoundFile soundFile = new CustomSoundFile(fileName, 0, (int)(new FileInfo(fileName).Length));\n            soundPlayer.PreLoad(soundFile);\n            soundPlayer.PlayingSoundName = Path.GetFileName(fileName);\n            labelItemSoundTitle.Text = \"(载入文件)\" + soundPlayer.PlayingSoundName;\n            labelItemSoundTitle.Tooltip = fileName;\n        }\n        #endregion\n\n        #region contextMenuStrip2\n        private void tsmi2SaveAs_Click(object sender, EventArgs e)\n        {\n            object item = advTree3.SelectedNode?.AsWzNode()?.Value;\n\n            if (item == null)\n                return;\n\n            if (item is string str)\n            {\n                SaveFileDialog dlg = new SaveFileDialog();\n                dlg.FileName = advTree3.SelectedNode.Text;\n                if (!dlg.FileName.Contains(\".\"))\n                {\n                    dlg.FileName += \".txt\";\n                }\n                dlg.Filter = \"*.*|*.*\";\n                if (dlg.ShowDialog() == DialogResult.OK)\n                {\n                    try\n                    {\n                        File.WriteAllText(dlg.FileName, str);\n                        this.labelItemStatus.Text = \"保存成功。\";\n                    }\n                    catch (Exception ex)\n                    {\n                        MessageBoxEx.Show(\"文件保存失败。\\r\\n\" + ex.ToString(), \"提示\");\n                    }\n                }\n            }\n            else if (item is IMapleStoryBlob blob)\n            {\n                SaveFileDialog dlg = new SaveFileDialog();\n                dlg.FileName = advTree3.SelectedNode.Text;\n                if (!dlg.FileName.Contains(\".\") && blob is Wz_Sound wzSound)\n                {\n                    switch (wzSound.SoundType)\n                    {\n                        case Wz_SoundType.Mp3: dlg.FileName += \".mp3\"; break;\n                        case Wz_SoundType.Pcm: dlg.FileName += \".pcm\"; break;\n                    }\n                }\n                dlg.Filter = \"*.*|*.*\";\n                if (dlg.ShowDialog() == DialogResult.OK)\n                {\n                    try\n                    {\n                        byte[] data = new byte[blob.Length];\n                        blob.CopyTo(data, 0);\n                        using (var f = File.Create(dlg.FileName))\n                        {\n                            f.Write(data, 0, data.Length);\n                            f.Flush();\n                        }\n                        this.labelItemStatus.Text = \"保存成功。\";\n                    }\n                    catch (Exception ex)\n                    {\n                        MessageBoxEx.Show(\"文件保存失败。\\r\\n\" + ex.ToString(), \"提示\");\n                    }\n                }\n            }\n            else if (item is Wz_Png png)\n            {\n                SaveFileDialog dlg = new SaveFileDialog();\n                dlg.Title = \"保存Canvas的原始数据\";\n                dlg.FileName = advTree3.SelectedNode.Text + \".bin\";\n                dlg.Filter = \"*.*|*.*\";\n                if (dlg.ShowDialog() == DialogResult.OK)\n                {\n                    try\n                    {\n                        using (var dataReader = png.UnsafeOpenRead())\n                        using (var outputFile = dlg.OpenFile())\n                        {\n                            dataReader.CopyTo(outputFile);\n                        }\n                        this.labelItemStatus.Text = \"保存成功。\";\n                    }\n                    catch (Exception ex)\n                    {\n                        MessageBoxEx.Show(\"文件保存失败。\\r\\n\" + ex.ToString(), \"提示\");\n                    }\n                }\n            }\n        }\n\n        private void tsmi2HandleUol_Click(object sender, EventArgs e)\n        {\n            Wz_Uol uol = advTree3.SelectedNode?.AsWzNode()?.Value as Wz_Uol;\n            if (uol == null)\n            {\n                labelItemStatus.Text = \"没有选中适当的uol节点...\";\n                return;\n            }\n\n            Node uolNode = handleUol(advTree3.SelectedNode, uol.Uol);\n            if (uolNode == null)\n            {\n                labelItemStatus.Text = \"没有找到uol对应的节点...请试着载入更底层的父节点重新执行..\";\n                return;\n            }\n            else\n            {\n                advTree3.SelectedNode = uolNode;\n            }\n        }\n\n        private void tsmi2ExpandAll_Click(object sender, EventArgs e)\n        {\n            if (advTree3.SelectedNode == null)\n                return;\n            advTree3.BeginUpdate();\n            advTree3.SelectedNode.ExpandAll();\n            advTree3.SelectedNode.Expand();\n            advTree3.EndUpdate();\n        }\n\n        private void tsmi2CollapseAll_Click(object sender, EventArgs e)\n        {\n            if (advTree3.SelectedNode == null)\n                return;\n            advTree3.BeginUpdate();\n            advTree3.SelectedNode.Collapse();\n            advTree3.SelectedNode.CollapseAll();\n            advTree3.EndUpdate();\n        }\n\n        private void tsmi2ExpandLevel_Click(object sender, EventArgs e)\n        {\n            if (advTree3.SelectedNode == null)\n                return;\n\n            advTree3.BeginUpdate();\n            foreach (Node node in getEqualLevelNode(advTree3.SelectedNode))\n            {\n                node.Expand();\n            }\n            advTree3.EndUpdate();\n        }\n\n        private void tsmi2CollapseLevel_Click(object sender, EventArgs e)\n        {\n            if (advTree3.SelectedNode == null)\n                return;\n\n            advTree3.BeginUpdate();\n            foreach (Node node in getEqualLevelNode(advTree3.SelectedNode))\n            {\n                node.Collapse();\n            }\n            advTree3.EndUpdate();\n        }\n\n        private IEnumerable<Node> getEqualLevelNode(Node currentNode)\n        {\n            if (currentNode == null)\n                yield break;\n            int level = currentNode.Level;\n            Node parent = currentNode;\n            while (parent != null && parent.Parent != null)\n            {\n                parent = parent.Parent;\n            }\n            Queue<Node> nodeList = new Queue<Node>();\n            nodeList.Enqueue(parent);\n            for (int i = 0; i < level; i++)\n            {\n                int count = nodeList.Count;\n                for (int j = 0; j < count; j++)\n                {\n                    Node node = nodeList.Dequeue();\n                    foreach (Node child in node.Nodes)\n                        nodeList.Enqueue(child);\n                }\n            }\n\n            while (nodeList.Count > 0)\n            {\n                yield return nodeList.Dequeue();\n            }\n        }\n\n        private void tsmi2Prev_Click(object sender, EventArgs e)\n        {\n            if (historyNodeList.PrevCount > 0)\n            {\n                historySelecting = true;\n                advTree3.SelectedNode = historyNodeList.MovePrev();\n            }\n        }\n\n        private void tsmi2Next_Click(object sender, EventArgs e)\n        {\n            if (historyNodeList.NextCount > 0)\n            {\n                historySelecting = true;\n                advTree3.SelectedNode = historyNodeList.MoveNext();\n            }\n        }\n\n        private void tsmi2CopyFullPath_Click(object sender, EventArgs e)\n        {\n            var selectedWzNode = advTree3.SelectedNode.AsWzNode();\n            if (selectedWzNode != null)\n            {\n                string fullPath = selectedWzNode.FullPathToFile.Replace('\\\\', '/');\n                Clipboard.SetText(fullPath);\n                ToastNotification.Show(this, \"已经复制当前所选节点完整路径\", 1000, eToastPosition.TopCenter);\n            }\n        }\n\n        private void contextMenuStrip2_Opening(object sender, CancelEventArgs e)\n        {\n            var node = advTree3.SelectedNode.AsWzNode();\n            tsmi2SaveAs.Visible = false;\n            tsmi2HandleUol.Visible = false;\n            if (node != null)\n            {\n                if (node.Value is Wz_Sound || node.Value is Wz_Png || node.Value is string || node.Value is Wz_RawData || node.Value is Wz_Video)\n                {\n                    tsmi2SaveAs.Visible = true;\n                    tsmi2SaveAs.Enabled = true;\n                }\n                else if (node.Value is Wz_Uol)\n                {\n                    tsmi2HandleUol.Visible = true;\n                }\n                else\n                {\n                    tsmi2SaveAs.Visible = true;\n                    tsmi2SaveAs.Enabled = false;\n                }\n            }\n        }\n        #endregion\n\n        #region charaSim相关\n        private void buttonItemQuickView_Click(object sender, EventArgs e)\n        {\n            quickView();\n        }\n\n        private void advTree1_AfterNodeSelect_2(object sender, AdvTreeNodeEventArgs e)\n        {\n            lastSelectedTree = advTree1;\n            if (buttonItemAutoQuickView.Checked)\n            {\n                quickView(advTree1.SelectedNode);\n            }\n        }\n\n        private void advTree2_AfterNodeSelect_2(object sender, AdvTreeNodeEventArgs e)\n        {\n            lastSelectedTree = advTree2;\n            if (buttonItemAutoQuickView.Checked)\n            {\n                quickView(advTree2.SelectedNode);\n            }\n        }\n\n        private void quickView()\n        {\n            if (lastSelectedTree != null)\n            {\n                quickView(lastSelectedTree.SelectedNode);\n            }\n        }\n\n        private void quickView(Node node)\n        {\n            Wz_Node selectedNode = node.AsWzNode();\n            if (selectedNode == null)\n            {\n                return;\n            }\n\n            Wz_Image image;\n\n            Wz_File wzf = selectedNode.GetNodeWzFile();\n            if (wzf == null)\n            {\n                labelItemStatus.Text = \"没有查询到节点所属的wzfile。\";\n                return;\n            }\n\n            if (!this.stringLinker.HasValues)\n            {\n                this.TryLoadStringWz();\n            }\n\n            object obj = null;\n            string fileName = null;\n            switch (wzf.Type)\n            {\n                case Wz_Type.Character:\n                    if ((image = selectedNode.GetValue<Wz_Image>()) == null || !image.TryExtract())\n                        return;\n                    CharaSimLoader.LoadSetItemsIfEmpty();\n                    CharaSimLoader.LoadExclusiveEquipsIfEmpty();\n                    if (selectedNode.FullPathToFile.Contains(\"Familiar\"))\n                    {\n                        var familiar = Familiar.CreateFromNode(image.Node, PluginManager.FindWz);\n                        obj = familiar;\n                        if (familiar != null)\n                        {\n                            fileName = \"familiar_\" + familiar.FamiliarID + \".png\";\n                        }\n                    }\n                    else\n                    {\n                        var gear = Gear.CreateFromNode(image.Node, PluginManager.FindWz);\n                        obj = gear;\n                        if (gear != null)\n                        {\n                            fileName = gear.ItemID + \".png\";\n                        }\n                    }\n                        break;\n                case Wz_Type.Item:\n                    Wz_Node itemNode = selectedNode;\n                    if (Regex.IsMatch(itemNode.FullPathToFile, @\"^Item\\\\(Cash|Consume|Etc|Install|Cash)\\\\\\d{4,6}.img\\\\\\d+$\"))\n                    {\n                        var item = Item.CreateFromNode(itemNode, PluginManager.FindWz);\n                        obj = item;\n                        if (item != null)\n                        {\n                            fileName = item.ItemID + \".png\";\n                        }\n                    }\n                    else if (Regex.IsMatch(itemNode.FullPathToFile, @\"^Item\\\\Pet\\\\\\d{7}.img\"))\n                    {\n                        if (CharaSimLoader.LoadedSetItems.Count == 0) //宠物 预读套装\n                        {\n                            CharaSimLoader.LoadSetItemsIfEmpty();\n                        }\n                        if ((image = selectedNode.GetValue<Wz_Image>()) == null || !image.TryExtract())\n                            return;\n                        var item = Item.CreateFromNode(image.Node, PluginManager.FindWz);\n                        obj = item;\n                        if (item != null)\n                        {\n                            fileName = item.ItemID + \".png\";\n                        }\n                    }\n\n                    break;\n                case Wz_Type.Skill:\n                    Wz_Node skillNode = selectedNode;\n                    //模式路径分析\n                    if (Regex.IsMatch(skillNode.FullPathToFile, @\"^Skill\\d*\\\\Recipe_\\d+.img\\\\\\d+$\"))\n                    {\n                        Recipe recipe = Recipe.CreateFromNode(skillNode);\n                        obj = recipe;\n                        if (recipe != null)\n                        {\n                            fileName = \"recipe_\" + recipe.RecipeID + \".png\";\n                        }\n                    }\n                    else if (Regex.IsMatch(skillNode.FullPathToFile, @\"^Skill\\d*\\\\\\d+.img\\\\skill\\\\\\d+$\"))\n                    {\n                        Skill skill = Skill.CreateFromNode(skillNode, PluginManager.FindWz);\n                        if (skill != null)\n                        {\n                            switch (this.skillDefaultLevel)\n                            {\n                                case DefaultLevel.Level0: skill.Level = 0; break;\n                                case DefaultLevel.Level1: skill.Level = 1; break;\n                                case DefaultLevel.LevelMax: skill.Level = skill.MaxLevel; break;\n                                case DefaultLevel.LevelMaxWithCO: skill.Level = skill.MaxLevel + 2; break;\n                            }\n                            obj = skill;\n                            fileName = \"skill_\" + skill.SkillID + \".png\";\n                        }\n                    }\n                    break;\n\n                case Wz_Type.Mob:\n                    if ((image = selectedNode.GetValue<Wz_Image>()) == null || !image.TryExtract())\n                        return;\n                    var mob = Mob.CreateFromNode(image.Node, PluginManager.FindWz);\n                    obj = mob;\n                    if (mob != null)\n                    {\n                        fileName = mob.ID + \".png\";\n                    }\n                    break;\n\n                case Wz_Type.Npc:\n                    if ((image = selectedNode.GetValue<Wz_Image>()) == null || !image.TryExtract())\n                        return;\n                    var npc = Npc.CreateFromNode(image.Node, PluginManager.FindWz);\n                    obj = npc;\n                    if (npc != null)\n                    {\n                        fileName = npc.ID + \".png\";\n                    }\n                    break;\n            }\n            if (obj != null)\n            {\n                tooltipQuickView.TargetItem = obj;\n                tooltipQuickView.ImageFileName = fileName;\n                tooltipQuickView.Refresh();\n                tooltipQuickView.HideOnHover = false;\n                tooltipQuickView.Show();\n            }\n        }\n\n        private void comboBoxItemLanguage_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            DevComponents.Editors.ComboItem item = comboBoxItemLanguage.SelectedItem as DevComponents.Editors.ComboItem;\n\n            if (item != null)\n            {\n                GearGraphics.SetFontFamily(item.Text);\n                ConfigManager.Reload();\n                CharaSimConfig.Default.SelectedFontIndex = comboBoxItemLanguage.SelectedIndex;\n                ConfigManager.Save();\n            }\n        }\n\n        private void buttonItemClearSetItems_Click(object sender, EventArgs e)\n        {\n            int count = CharaSimLoader.LoadedSetItems.Count;\n            CharaSimLoader.LoadedSetItems.Clear();\n            labelItemStatus.Text = \"已经清空预读套装共\" + count + \"项。\";\n        }\n\n        private void buttonItemCharItem_CheckedChanged(object sender, EventArgs e)\n        {\n            if (buttonItemCharItem.Checked)\n                this.charaSimCtrl.UIItem.Refresh();\n            this.charaSimCtrl.UIItem.Visible = buttonItemCharItem.Checked;\n        }\n\n        private void buttonItemAddItem_Click(object sender, EventArgs e)\n        {\n            bool success;\n\n            success = this.charaSimCtrl.UIItem.AddItem(this.tooltipQuickView.TargetItem as ItemBase);\n            if (!success)\n            {\n                labelItemStatus.Text = \"背包加入物品失败...\";\n            }\n        }\n\n        private void afrm_KeyDown(object sender, KeyEventArgs e)\n        {\n            AfrmTooltip frm = sender as AfrmTooltip;\n            if (frm == null)\n                return;\n\n            switch (e.KeyCode)\n            {\n                case Keys.Escape:\n                    frm.Hide();\n                    return;\n                case Keys.Up:\n                    frm.Top -= 1;\n                    return;\n                case Keys.Down:\n                    frm.Top += 1;\n                    return;\n                case Keys.Left:\n                    frm.Left -= 1;\n                    return;\n                case Keys.Right:\n                    frm.Left += 1;\n                    return;\n            }\n\n            Skill skill = frm.TargetItem as Skill;\n            if (skill != null)\n            {\n                switch (e.KeyCode)\n                {\n                    case Keys.Oemplus:\n                    case Keys.Add:\n                        skill.Level += 1;\n                        break;\n\n                    case Keys.OemMinus:\n                    case Keys.Subtract:\n                        skill.Level -= 1;\n                        break;\n\n                    case Keys.PageDown:\n                        skill.PerJobIndex += 1;\n                        break;\n\n                    case Keys.PageUp:\n                        skill.PerJobIndex -= 1;\n                        break;\n\n                    case Keys.OemOpenBrackets:\n                        skill.Level -= this.skillInterval;\n                        break;\n                    case Keys.OemCloseBrackets:\n                        skill.Level += this.skillInterval;\n                        break;\n                    default:\n                        return;\n                }\n                frm.Refresh();\n            }\n        }\n\n        private void buttonItemCharaStat_CheckedChanged(object sender, EventArgs e)\n        {\n            if (buttonItemCharaStat.Checked)\n            {\n                this.charaSimCtrl.UIStat.Refresh();\n            }\n            this.charaSimCtrl.UIStat.Visible = buttonItemCharaStat.Checked;\n        }\n\n        private void buttonItemCharaEquip_CheckedChanged(object sender, EventArgs e)\n        {\n            if (buttonItemCharaEquip.Checked)\n            {\n                this.charaSimCtrl.UIEquip.Refresh();\n            }\n            this.charaSimCtrl.UIEquip.Visible = buttonItemCharaEquip.Checked;\n        }\n\n        private void buttonItemQuickViewSetting_Click(object sender, EventArgs e)\n        {\n            using (FrmQuickViewSetting frm = new FrmQuickViewSetting())\n            {\n                frm.Load(CharaSimConfig.Default);\n\n                if (frm.ShowDialog() == DialogResult.OK)\n                {\n                    ConfigManager.Reload();\n                    frm.Save(CharaSimConfig.Default);\n                    ConfigManager.Save();\n                    UpdateCharaSimSettings();\n                }\n            }\n        }\n        #endregion\n\n        #region 实现插件接口\n        Office2007RibbonForm PluginContextProvider.MainForm\n        {\n            get { return this; }\n        }\n\n        DotNetBarManager PluginContextProvider.DotNetBarManager\n        {\n            get { return this.dotNetBarManager1; }\n        }\n\n        IList<Wz_Structure> PluginContextProvider.LoadedWz\n        {\n            get { return new System.Collections.ObjectModel.ReadOnlyCollection<Wz_Structure>(this.openedWz); }\n        }\n\n        Wz_Node PluginContextProvider.SelectedNode1\n        {\n            get { return advTree1.SelectedNode.AsWzNode(); }\n        }\n\n        Wz_Node PluginContextProvider.SelectedNode2\n        {\n            get { return advTree2.SelectedNode.AsWzNode(); }\n        }\n\n        Wz_Node PluginContextProvider.SelectedNode3\n        {\n            get { return advTree3.SelectedNode.AsWzNode(); }\n        }\n\n        private EventHandler<WzNodeEventArgs> selectedNode1Changed;\n        private EventHandler<WzNodeEventArgs> selectedNode2Changed;\n        private EventHandler<WzNodeEventArgs> selectedNode3Changed;\n        private EventHandler<WzStructureEventArgs> wzOpened;\n        private EventHandler<WzStructureEventArgs> wzClosing;\n\n        event EventHandler<WzNodeEventArgs> PluginContextProvider.SelectedNode1Changed\n        {\n            add { selectedNode1Changed += value; }\n            remove { selectedNode1Changed -= value; }\n        }\n\n        event EventHandler<WzNodeEventArgs> PluginContextProvider.SelectedNode2Changed\n        {\n            add { selectedNode2Changed += value; }\n            remove { selectedNode2Changed -= value; }\n        }\n\n        event EventHandler<WzNodeEventArgs> PluginContextProvider.SelectedNode3Changed\n        {\n            add { selectedNode3Changed += value; }\n            remove { selectedNode3Changed -= value; }\n        }\n\n        event EventHandler<WzStructureEventArgs> PluginContextProvider.WzOpened\n        {\n            add { wzOpened += value; }\n            remove { wzOpened -= value; }\n        }\n\n        event EventHandler<WzStructureEventArgs> PluginContextProvider.WzClosing\n        {\n            add { wzClosing += value; }\n            remove { wzClosing -= value; }\n        }\n\n        StringLinker PluginContextProvider.DefaultStringLinker\n        {\n            get { return this.stringLinker; }\n        }\n\n        AlphaForm PluginContextProvider.DefaultTooltipWindow\n        {\n            get { return this.tooltipQuickView; }\n        }\n\n        private void RegisterPluginEvents()\n        {\n            advTree1.AfterNodeSelect += advTree1_AfterNodeSelect_Plugin;\n            advTree2.AfterNodeSelect += advTree2_AfterNodeSelect_Plugin;\n            advTree3.AfterNodeSelect += advTree3_AfterNodeSelect_Plugin;\n        }\n\n        private void advTree1_AfterNodeSelect_Plugin(object sender, AdvTreeNodeEventArgs e)\n        {\n            if (selectedNode1Changed != null)\n            {\n                var wzNode = ((PluginContextProvider)(this)).SelectedNode1;\n                var args = new WzNodeEventArgs(wzNode);\n                selectedNode1Changed(this, args);\n            }\n        }\n\n        private void advTree2_AfterNodeSelect_Plugin(object sender, AdvTreeNodeEventArgs e)\n        {\n            if (selectedNode2Changed != null)\n            {\n                var wzNode = ((PluginContextProvider)(this)).SelectedNode2;\n                var args = new WzNodeEventArgs(wzNode);\n                selectedNode2Changed(this, args);\n            }\n        }\n\n        private void advTree3_AfterNodeSelect_Plugin(object sender, AdvTreeNodeEventArgs e)\n        {\n            if (selectedNode3Changed != null)\n            {\n                var wzNode = ((PluginContextProvider)(this)).SelectedNode3;\n                var args = new WzNodeEventArgs(wzNode);\n                selectedNode3Changed(this, args);\n            }\n        }\n\n        protected virtual void OnWzOpened(WzStructureEventArgs e)\n        {\n            if (wzOpened != null)\n            {\n                wzOpened(this, e);\n            }\n        }\n\n        protected virtual void OnWzClosing(WzStructureEventArgs e)\n        {\n            if (wzClosing != null)\n            {\n                wzClosing(this, e);\n            }\n        }\n        #endregion\n\n        private void btnEasyCompare_Click(object sender, EventArgs e)\n        {\n            if (compareThread != null)\n            {\n                compareThread.Suspend();\n                if (DialogResult.Yes == MessageBoxEx.Show(\"正在执行一个对比操作，要中止吗？\", \"Compare\", MessageBoxButtons.YesNoCancel))\n                {\n                    compareThread.Resume();\n                    compareThread.Abort();\n                }\n                else\n                {\n                    compareThread.Resume();\n                }\n                return;\n            }\n\n            if (openedWz.Count < 2)\n            {\n                MessageBoxEx.Show(\"没有成功打开两个WZ。\", \"Compare\");\n                return;\n            }\n\n            FolderBrowserDialog dlg = new FolderBrowserDialog();\n            dlg.Description = \"请选择输出报告的文件夹\";\n\n            if (dlg.ShowDialog() == DialogResult.OK)\n            {\n                compareThread = new Thread(() =>\n                {\n                    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();\n                    EasyComparer comparer = new EasyComparer();\n                    comparer.Comparer.PngComparison = (WzPngComparison)cmbComparePng.SelectedItem;\n                    comparer.Comparer.ResolvePngLink = chkResolvePngLink.Checked;\n                    comparer.OutputPng = chkOutputPng.Checked;\n                    comparer.OutputAddedImg = chkOutputAddedImg.Checked;\n                    comparer.OutputRemovedImg = chkOutputRemovedImg.Checked;\n                    comparer.HashPngFileName = chkHashPngFileName.Checked;\n                    comparer.StateInfoChanged += new EventHandler(comparer_StateInfoChanged);\n                    comparer.StateDetailChanged += new EventHandler(comparer_StateDetailChanged);\n                    comparer.ColorTable = new List<System.Drawing.Color>()\n                    {\n                        CustomCSSConfig.Default.BackgroundColor,\n                        CustomCSSConfig.Default.NormalTextColor,\n                        CustomCSSConfig.Default.ChangedBackgroundColor,\n                        CustomCSSConfig.Default.AddedBackgroundColor,\n                        CustomCSSConfig.Default.RemovedBackgroundColor,\n                        CustomCSSConfig.Default.ChangedTextColor,\n                        CustomCSSConfig.Default.AddedTextColor,\n                        CustomCSSConfig.Default.RemovedTextColor,\n                        CustomCSSConfig.Default.HyperlinkColor\n                    };\n                    try\n                    {\n                        Wz_File fileNew = openedWz[0].wz_files[0];\n                        Wz_File fileOld = openedWz[1].wz_files[0];\n\n                        while (true)\n                        {\n                            string txt = string.Format(\"待比较wz文件：\\r\\n\\r\\n  new : {0} (ver:{1})\\r\\n  old : {2} (ver:{3})\\r\\n\\r\\n如果继续对比请选择Yes，如果想交换文件顺序请选择No。\",\n                                fileNew.Header.FileName,\n                                fileNew.GetMergedVersion(),\n                                fileOld.Header.FileName,\n                                fileOld.GetMergedVersion()\n                                );\n                            switch (MessageBoxEx.Show(txt, \"Easy Compare\", MessageBoxButtons.YesNoCancel))\n                            {\n                                case DialogResult.Yes:\n                                    comparer.EasyCompareWzFiles(fileNew, fileOld, dlg.SelectedPath);\n                                    return;\n\n                                case DialogResult.No:\n                                    Wz_File tmp = fileNew;\n                                    fileNew = fileOld;\n                                    fileOld = tmp;\n                                    break;\n\n                                case DialogResult.Cancel:\n                                default:\n                                    return;\n                            }\n                        }\n\n                    }\n                    catch (ThreadAbortException)\n                    {\n                        MessageBoxEx.Show(this, \"比较已经中止。\", \"Compare\");\n                    }\n                    catch (Exception ex)\n                    {\n                        MessageBoxEx.Show(this, \"出现异常\\r\\n\" + ex.ToString(), \"Compare\");\n                    }\n                    finally\n                    {\n                        sw.Stop();\n                        compareThread = null;\n                        labelXComp1.Text = \"wz对比结束...共用时\" + sw.Elapsed.ToString();\n                        labelXComp2.Text = \"\";\n                    }\n                });\n                compareThread.Priority = ThreadPriority.Highest;\n                compareThread.Start();\n            }\n        }\n\n        void comparer_StateDetailChanged(object sender, EventArgs e)\n        {\n            EasyComparer comp = sender as EasyComparer;\n            if (comp != null)\n            {\n                labelXComp1.Text = comp.StateInfo;\n            }\n        }\n\n        void comparer_StateInfoChanged(object sender, EventArgs e)\n        {\n            EasyComparer comp = sender as EasyComparer;\n            if (comp != null)\n            {\n                labelXComp2.Text = comp.StateDetail;\n            }\n        }\n\n        private void buttonItemAbout_Click(object sender, EventArgs e)\n        {\n            new FrmAbout().ShowDialog();\n        }\n\n        private void btnExportSkill_Click(object sender, EventArgs e)\n        {\n            FolderBrowserDialog dlg = new FolderBrowserDialog();\n            dlg.Description = \"请选择技能列表输出文件夹\";\n            if (dlg.ShowDialog() == DialogResult.OK)\n            {\n                if (!this.stringLinker.HasValues)\n                    this.TryLoadStringWz();\n\n                DBConnection conn = new DBConnection(this.stringLinker);\n                DataSet ds = conn.GenerateSkillTable();\n                foreach (DataTable dt in ds.Tables)\n                {\n                    FileStream fs = new FileStream(Path.Combine(dlg.SelectedPath, dt.TableName + \".csv\"), FileMode.Create);\n                    StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);\n                    conn.OutputCsv(sw, dt);\n                    sw.Close();\n                    fs.Dispose();\n                }\n                MessageBoxEx.Show(\"数据导出完毕。\");\n            }\n        }\n\n        private void btnCustomCSS_Click(object sender, EventArgs e)\n        {\n            ConfigManager.Reload();\n            var Setting = CustomCSSConfig.Default;\n            using (FrmCustomCSS frm = new FrmCustomCSS())\n            {\n                frm.LoadConfig(Setting);\n                if (frm.ShowDialog() == DialogResult.OK)\n                {\n                    frm.SaveConfig(Setting);\n                    ConfigManager.Save();\n                }\n            }\n        }\n\n        private void btnExportSkillOption_Click(object sender, EventArgs e)\n        {\n            FolderBrowserDialog dlg = new FolderBrowserDialog();\n            dlg.Description = \"请选择魂宝珠系统输出文件夹\";\n            if (dlg.ShowDialog() == DialogResult.OK)\n            {\n                if (!this.stringLinker.HasValues)\n                    this.TryLoadStringWz();\n\n                DBConnection conn = new DBConnection(this.stringLinker);\n                conn.ExportSkillOption(dlg.SelectedPath);\n                MessageBoxEx.Show(\"数据导出完毕。\");\n            }\n        }\n\n        private void buttonItemAutoQuickView_Click(object sender, EventArgs e)\n        {\n            ConfigManager.Reload();\n            CharaSimConfig.Default.AutoQuickView = buttonItemAutoQuickView.Checked;\n            ConfigManager.Save();\n        }\n\n        private void panelExLeft_SizeChanged(object sender, EventArgs e)\n        {\n            if (this.WindowState != FormWindowState.Minimized)\n            {\n                if (panelExLeft.Tag is int)\n                {\n                    int oldHeight = (int)panelExLeft.Tag;\n                    advTree1.Height = (int)(1.0 * advTree1.Height / oldHeight * panelExLeft.Height);\n                }\n                panelExLeft.Tag = panelExLeft.Height;\n            }\n        }\n\n        private void buttonItem1_Click(object sender, EventArgs e)\n        {\n        }\n\n        private void labelItemStatus_TextChanged(object sender, EventArgs e)\n        {\n            ribbonBar2.RecalcLayout();\n        }\n\n        private void btnNodeBack_Click(object sender, EventArgs e)\n        {\n\n        }\n\n        private void btnNodeForward_Click(object sender, EventArgs e)\n        {\n\n        }\n\n        private void buttonItemUpdate_Click(object sender, EventArgs e)\n        {\n            var frm = new FrmUpdater();\n            frm.LoadConfig(WcR2Config.Default);\n            frm.ShowDialog();\n        }\n\n        private void btnItemOptions_Click(object sender, System.EventArgs e)\n        {\n            var frm = new FrmOptions();\n            frm.Load(WcR2Config.Default);\n            if (frm.ShowDialog() == DialogResult.OK)\n            {\n                ConfigManager.Reload();\n                frm.Save(WcR2Config.Default);\n                ConfigManager.Save();\n                UpdateWzLoadingSettings();\n            }\n        }\n\n        private void colorPickerPicBoxBgColor_SelectedColorChanged(object sender, EventArgs e)\n        {\n            this.pictureBoxEx1.BackColor = ((ColorPickerDropDown)sender).SelectedColor;\n        }\n\n        private async void MainForm_Shown(object sender, EventArgs e)\n        {\n            await this.AutomaticCheckUpdate();\n        }\n    }\n\n    #region 内部用扩展方法\n    internal static partial class Ext\n    {\n        public static Wz_Node AsWzNode(this Node node)\n        {\n            return (node?.Tag as WeakReference)?.Target as Wz_Node;\n        }\n\n        public static void ClearLayoutCellInfo(this AdvTree advTree)\n        {\n            var bindingPrivateField = BindingFlags.NonPublic | BindingFlags.Instance;\n            {\n                var field1 = advTree.GetType().GetField(\"Ֆ\", bindingPrivateField);\n                var obj1 = field1.GetValue(advTree);\n                if (obj1 != null)\n                {\n                    var field2 = obj1.GetType().BaseType.GetField(\"ӹ\", bindingPrivateField);\n                    var obj2 = field2.GetValue(obj1);\n                    if (obj2 != null)\n                    {\n                        var field3 = obj2.GetType().GetField(\"ܦ\", bindingPrivateField);\n                        var obj3 = field3.GetValue(obj2);\n                        if (obj3 != null)\n                        {\n                            field3.SetValue(obj2, null);\n                        }\n                    }\n                }\n            }\n\n            {\n                var display = advTree.NodeDisplay as NodeTreeDisplay;\n                if (display != null)\n                {\n                    var field4 = display.GetType().GetField(\"☼\", bindingPrivateField);\n                    var obj4 = field4.GetValue(display) as NodeCellRendererEventArgs;\n                    if (obj4 != null)\n                    {\n                        obj4.Node = null;\n                        obj4.Cell = null;\n                    }\n                }\n            }\n        }\n    }\n    #endregion\n}\n"
  },
  {
    "path": "WzComparerR2/MainForm.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <metadata name=\"styleManager1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>455, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader3.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader4.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>309, 17</value>\n  </metadata>\n  <metadata name=\"columnHeader5.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>163, 17</value>\n  </metadata>\n  <metadata name=\"contextMenuStrip2.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>132, 54</value>\n  </metadata>\n  <metadata name=\"imageList1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 54</value>\n  </metadata>\n  <metadata name=\"contextMenuStrip1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>594, 17</value>\n  </metadata>\n  <metadata name=\"dotNetBarManager1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>297, 54</value>\n  </metadata>\n  <metadata name=\"$this.TrayHeight\" type=\"System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\">\n    <value>96</value>\n  </metadata>\n  <assembly alias=\"System.Drawing\" name=\"System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\" />\n  <data name=\"$this.Icon\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n    <value>\n        AAABAAQAICAAAAEACACoCAAARgAAABAQAAABAAgAaAUAAO4IAAAgIAAAAQAgAKgQAABWDgAAEBAAAAEA\n        IABoBAAA/h4AACgAAAAgAAAAQAAAAAEACAAAAAAAgAQAAAAAAAAAAAAAAAEAAAAAAAAAAAAABTgDAAQz\n        OAAFRgIABlkGAAtNGgAnXBkACGETAAltEgAKcBMADHgYAA5TPgAlUSMAKE40ABFmKwATei0AC2A7ADVr\n        NwAENFAABDZaAAU5XQAOOVkABTxkAAk+ZAAGP2kAD0JeAAZlRgA/eFIABkBrAAtCawAUSG0AGUlrAAlE\n        cQAKSXYAC0x7ABdOdwAUT3oAE1J/ACZLZgAuT2cANVJnADRTawAqVncAIlh/ACxZegA/X3YAT21OAFZw\n        VgBbe14ARGJ4AFJqewBPf3oALIRFADWIdABhjm8Ad4t2AIKXcwAZV4MAEFmOABxejQAQXZUAIFiAACxe\n        gwAbYpUAH2aZACdogAAoYowAKXeBADR5gQAka54ALm2dADZyngAUZaAAGmihABZtrQAbbasADHS9ABpy\n        swAUdrwAGne6AB96vwAobaEALXGkACp0qgA1c6AAP3unAD57qAAldrAAKnq1ACN7vAAsf7wANH2yADJ+\n        uQBeeIkAQnqjAEd/qAACd80ADHfLAAN4zQAae8AAEnzNAA550QAjf8MAOoe0ADSCugA5hb0AWYCdAEWY\n        mwBriJ0AdoiWAHmKlwB4jJoAb6GJAEOCrwBbia4AR4a1AEqMvABRiLoAUpe9AHyarwBmk7MAfpuwAHqe\n        uQBUr7EAf6GiAHuhvwAaiswAHYLTAB2F3gAngMIALIPDAC2GyAAticsAMIfHADiDwQAziMcAMYrMADqP\n        zgA+kc0AJYXTACiJ1wAmiNgAMo7QADCN2QA+ldQANpLaADuV3QAIkeQAJYzlAC2U4gAnkewANpPiADmW\n        5QA7mOEAIp30ADOa8QA/oOkAPaDzAECKwQBPlMYAQ5LLAFeXxABfmcUAQpXSAEWY1QBDldgARJrbAEme\n        3ABRm9YAWJ7SAGyeygBIoN8AXaPVAFGg2gBoocsAe6PCAH+oxgBgo9UAaajSAGGn2QB6rNIAcLDfAEaf\n        4ABIneEASqLhAFGm5ABVq+cAWqrkAFGp6ABcq+sAXrHrAGCs6QBisuwAbbXqAG2+7QBxtukAeLrqAGOx\n        9ABrt/UAa7nyAG+5+QByvPQAdL36AFzAxAB53s4AfsfuAHvB9QB8yfIAd8D7AHvD/AB/y/wAcu7uAICQ\n        mwCftosAg6WZAKWkiQCAkqAAgJyxAIuluQCNqr8AnbC/AKW2rADBvaIAk8O8AIKnxQCXtMcAk7POAJm2\n        yACOttcAobXEAKC4xQCqvckAm8DXAJ3e0ACqwc8AuMXLAKnD0wCBw/MAhM/1AIHF+ACDyPoAhdH2AI3V\n        +wCQ1voAlNz9AI7r5ACb4f4AqeX+ALjo/QDEycsAxNPZAM3j6wDG6/oA0ejyANby/QD///8AAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAB4W2haUx8tAABGSUtFLDIAAAAAAAAAAAAAAAAAAAAAAGiHiISIPxNteVlQT01KICgAAAAAAAAA\n        AAAAAAAAAADZgoeOiINXFydehIiBWU1IFDEAAAAAAAAAAAAAAAAAAF+JkKaQo4kkF1iFjlqDgU06E10A\n        AAAAAAAAAAAAAAAAnrirq6Snq1NSiohXJYGEgU0iF2wAAAAAAAAAAAAAAHqmurikOaC4pLmlozsTQIiI\n        ZkocIAAAAAAAAAAAAAAAn7zAvVQUR7mnvKagICZsh46DZjwcIwAAAAAAAAAAAAC7wMCtHhVvrLq8tmkd\n        2ABzjoiESiEgAAAAAAAAAAAAsMfAvnQUKQCwu765USoAANqHhI5QSB0AAAAAAAAAAACuycfvXhMyAACp\n        vq09AAAAAHSIjoFPKwAAAAAAAAAAALHJ7+0+FG4AAH2iegAAAAAA24WEgVN3AAAAAAAAAAAAs87wtR4W\n        1AAAAAAAAAAAAAAAVlNqAAAAAAAAAAAAAADDzvChFxfUAAAAAAAAAAAAAK+ioqp1cgAAAAAAAAAA5e3O\n        8FUWGNgAAAAAAAAAAOKolZud0NC3TQAAAAAAAADc7c7wRxgc2AAAAAAAAADkypeAlpvK0cWLXAAAAAAA\n        ADPEye9UHCDYAAAAAAAA4L7zwI2YxsbGnJqVAAAAAAAbD0NnwUIcIG4AAAAAAACGmfT20siRZGOClbQA\n        AAAAADYJBwtBGRICDQAAAAAA583x9vbSt2JOAAAAAAAAAAAAMAYKCAQDAQMBLgAAAADm9vb29tGLYk0A\n        AAAAAAAAAAB8NQ4ECgQDAwE3AAAAAOr39vb20otgTAAAAAAAAAAAAN3pNRA0ERoFDAAAAAAA+vf29va8\n        lWViTnFbTH4AAAAAAN9wOMzVa0QvAAAAAAD5/Pb28oyUlGFiYX+Pk3YAAAAAAADW9dN7AAAAAAAAAPn9\n        +Pb2uo2Zv729ydCRAAAAAAAAAADey9cAAAAAAAAAAPr+/Pj38cKS8PT08rQAAAAAAAAAAAAAAAAAAAAA\n        AAAA+fv+/v7488/09rqlAAAAAAAAAAAAAAAAAAAAAAAAAAAA+ezo+/78/Pj4sgAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAADn/fvj4esAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//\n        /////////gMD//4AAf/8AAD//AAAf/wAAD/4AAA/+AAAH/gAEB/wEDAf8Bh4H/AY+B/wH/x/8B/4H+Af\n        4A/gH8AH4B+AB8AfgAfAHwB/wA8Af8APAH/AHwAD4B8AAfh/AAP8f4AD//+AB///wA////gf////////\n        ////////KAAAABAAAAAgAAAAAQAIAAAAAABAAQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAFPwkACVcTABBr\n        HwAKPlQAK3RcAA9GbgASRm0AIFN4ACZYfQAldmYAQmZAAD6DUQBcjW4AT49xAFWddQAPV4wAHWOVAB9l\n        lwApYIkAMmGDADRjhQA1ZIYAMG+LAC1nkAAoapoAOW2SABhppAAbcK4AHXOyACt3rwAserIAJX2+ADd+\n        sgAYfsoAPICxADeEvAA+hboAY4SdAFaCowBTirQAboyiAHOSqgAbhNQAH4bdACqDxAAphMcALIXFAC6I\n        ygA6iMEAPonAADiKyAA8jcgALo/QADeP0AA5kNAAPJfhADea5gBAjcUAQ5HKAEKTzgBYlcIAWJjHAE2c\n        1gBHmtgASpnZAFKh2wBXpNwAZqDJAGyhzAB9qssAY6PSAGuo3ABzqtgAQZ3lAEGe7QBMo+IAWK3nAFqs\n        6ABdreoAbLLlAGu37QByteEAdLXlAHq14AB7vOsAfrzqAGW09ABqvPAAdMD0AHzJ9ACAl4IAn8G9AIO0\n        2QCHt9oAj7rYAJS/2wCU0skArc3DAInI6wCFzvoAkdT3AJvW9ACq1OcAv97qAJvi/wCe4/8AteP5ALnp\n        /ADN2d8AytzlANDd4gDD4e8AJi8AAEBQAABacAAAdJAAAI6wAACpzwAAwvAAANH/EQDY/zEA3v9RAOP/\n        cQDp/5EA7/+xAPb/0QD///8AAAAAAC8mAABQQQAAcFsAAJB0AACwjgAAz6kAAPDDAAD/0hEA/9gxAP/d\n        UQD/5HEA/+qRAP/wsQD/9tEA////AAAAAAAvFAAAUCIAAHAwAACQPgAAsE0AAM9bAADwaQAA/3kRAP+K\n        MQD/nVEA/69xAP/BkQD/0rEA/+XRAP///wAAAAAALwMAAFAEAABwBgAAkAkAALAKAADPDAAA8A4AAP8g\n        EgD/PjEA/1xRAP96cQD/l5EA/7axAP/U0QD///8AAAAAAC8ADgBQABcAcAAhAJAAKwCwADYAzwBAAPAA\n        SQD/EVoA/zFwAP9RhgD/cZwA/5GyAP+xyAD/0d8A////AAAAAAAvACAAUAA2AHAATACQAGIAsAB4AM8A\n        jgDwAKQA/xGzAP8xvgD/UccA/3HRAP+R3AD/seUA/9HwAP///wAAAAAALAAvAEsAUABpAHAAhwCQAKUA\n        sADEAM8A4QDwAPAR/wDyMf8A9FH/APZx/wD3kf8A+bH/APvR/wD///8AAAAAABsALwAtAFAAPwBwAFIA\n        kABjALAAdgDPAIgA8ACZEf8ApjH/ALRR/wDCcf8Az5H/ANyx/wDr0f8A////AAAAAAAIAC8ADgBQABUA\n        cAAbAJAAIQCwACYAzwAsAPAAPhH/AFgx/wBxUf8AjHH/AKaR/wC/sf8A2tH/AP///wAAAAAAAAAAAAAA\n        AAAAAAAAAAAAADIkFQAhHhoAAAAAAAAAAD03NhIYLyAcCQAAAAAAAAA/QDE8NB8RLRsUAAAAAABfTiMW\n        Q0wZKTouECYAAAAAVFETAF1CJwBGMB0qAAAAAFNTCAAAAAAAACUoAAAAAABVRwcAAAAAAElBSEUAAAAA\n        Uj4GAAAAAFZKS1c5XgAADAoXBAAAAGBaZDg7XQAAAA0DAgELAABnaWQrRAAAAABiDg8FWwAAaGpYLCIz\n        NQAAAABhXAAAAG9sZU1PWVAAAAAAAAAAAAAAbnBrZmMAAAAAAAAAAAAAAAAAbQAAAAAAAAAAAAAAAAAA\n        AAAAAAAA//////Ef///gD///4Af//8AD///EQ///x+f//8fD///Hgf//hwP//4MH//+DAf//5wH///+D\n        ////7////////ygAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALLDcXFjxTQxU8XEgVPFxIDjFKSAQZJjwADBkUAAAAAQAz\n        MwUJLU04CjFNSAo1VEgEJz80ABEaHQAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSg1EyFjka8tebH0LX249yl7uPckb6j3Cz5i8AMu\n        TMIFHjIyCTJNOBNakeETZJ73F2mm9xJflusJPWTaASI6qwAZJjwAAAABAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASPFpEK3y48TOJx/8yi8z/LYjL/zCI\n        yf8bYpX/BDdb/gUmQIsUSW+LInm5/h96v/8adLb/GHK1/xdtrP8JRXD/ASVAygAaJycAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxZJcIoshMP+MojH/zOM\n        zv8uicz/LofI/yR2sP8HPWT/BCxJ1B9jk9gviMr/MIrM/yZ+v/8gebv/GnO0/xNkoP8EOFz9ASpIuwAc\n        KiQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIjMeI2aY1TaL\n        yv8/k9H/Q5va/z6X1/9BldL/Oo/O/xRPev8JP2b+Knq1/i+IyP8xjtH/KX++/yuDxf8ngML/GXGz/xBZ\n        jv8DNVn9AyxGogAcKhIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACFJ\n        aFM6hr/2SqDg/0mg4P9IoN//RJnX/0mf3P9In+D/LnOn/yxwof89kc//MorK/yd3sf8TUn//KYDA/y2I\n        yv8lf8H/GnKz/wtMe/8FOmD5BjNWlAAfLxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAJCQHKWaRn0Sb2v9Squn/S6Pj/0SW0v8ZV4P/Q5LL/0mh4P9HmNT/TqXk/0SX1v9Ak9H/HF6N/wU3\n        Wv4dZZj8MIrN/zGLzf8kfr//Fm6u/wdCbP8HQWz3Bi5NcwAAJAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAACNBWjM8icDmUqfm/2Gz7v9ZrOf/NnOg/wQ3Wv80cZz8Uabj/0me3P9QqOj/RZzd/z+R\n        zP8NRG3/Ay5O2xRFaaIuhsf8NI3O/y2GyP8hf8X/EF2V/wZAa/8HQm7uBSY+VgAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAABO2iLd1yq4f1gsev/YrLt/1Ke1v8USG7/AzBR8x4/WphUntPxV6rn/1Oq\n        6f9Gn+D/OYW9/wc+aPwDJ0OAEjFKKSdxqdk1j9D/L4nK/yuIzf8Ybq7/Ckl2/whDcP0EJkJwAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAABIkJA5RibK7bLrz/2W07f9esOv/SIy+/wY5Xf8CKUbMBxUkI0d/\n        q7JYquX/XK/r/0yk4v8obaH/BTlg2QYaLiYAAAADGU11gDKIx/wuisz/L4zP/yB5uv8VZqH/Bj1n9wMf\n        NEkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIzhGJFSVxeFwvPT/a7nx/4DG+f9Ee6T/AzZc/wQo\n        RLAAAAAHMVZ3TU+Z0PFesOv/UaDa/xFNd+4FLUhmAAAAAwAAAAANLkMmLXmv2TCLzv8yjtH/J4HC/xp3\n        uv8KRnHlABcjKwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApTGZDWZ/U9HG88/+Axfj/gsPy/yxe\n        g/8EOWD/AyM8hwAAAAESJDYOOXOfqkmOv+A/cZetBSlAVwAAJAcAAAAAAAAAAAAAAAMdV4GALYbH/C6K\n        zf8nf7/+F2ik5wtDaogAFxcLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBSbkpcpdj3ecH1/4XI\n        +f9wsN//FUht/wU8ZP8DIzmAAAAAAAAAAAAbNlEcGjROJwAcHAkAAAAAAAAAAAAAAAAAAAAAAAAAABAx\n        SC4lap3hJnGn+htRebkJKkVVAA8PEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP2aGcHC2\n        6f18wvX/hcj5/1eXxP8JPWT/Bj1l/wMjOYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUq\n        PwwxWXpNOnWlrVeUw/Jel8X8XZTF5jR0r9oLUIerADlfSwAkJAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AABFbYuAgMLy/3vC9f+Cxvj/P3un/wY8ZP8GPmf/AyU9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAxVHEkU4ayn0eU1fEqkOX/MJnx/z2g8/91vvr/dr78/0aY3v8IZqzsAENzZgAAAAMAAAAAAAAAAAAA\n        AAAAAAAAAAAAAklsh4iExPP/fML1/4LG+P82c5//BT5m/wdBbP8FKUOAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAP1xxJGacybt1vff9NpPi/x2F3v8nkez/N5zx/3K8+v96wf7/YLDz/x6E1f8PaK3ZBzFNJAAA\n        AAAAAAAAAAAAAAAaAB0GMBNPKWRd0Xi66v91vPD/gMT2/zVzoP8GP2n/CERy/wUpQ4AAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAJAcubZ+YXa7v/ZDW/f9ftOz/JojY/zmW5f9ms/b/bbf0/2q39v8/oOn/Ip30/yOS\n        4fITRm1BAAAAAAAAAAAAAAAABSIRLApTIscQeCr7KXeB/zqHtP9tter/KGKM/wZAa/8IRXL/Ayc+hwAT\n        AA0AAAAAAAAAAAAAAAAAAAAALVFoOCR3u+c5mOP/kdn7/5ff/v9/y/z/b7n5/zeQ2v8SfM3/D3W98xR1\n        u+MikN/wHXW0lA8vTxAAAAAAAAAAAAAAAAAAKBQZB04doglvEPwIYRP/DlM+/ydogP8PQl7/BDRQ/wQz\n        OP8DMBHZAh8AWAAAAAIAAAAAAAAAAAAAAAJdhZmIfsfu/oXR9v+a4f//meD//4LJ/v9Jm+L/BHnN/wBs\n        ue0AP2lcBihDJgxCaD0PL08QAAAAAAAAAAAAAAAAAAAAAAAlByIVQxmyJlsY/Qx4GP8JbRL/B1kF/wVF\n        B/8DMAf/BDoB/wQ4APoCLgCxACQAIwAAAAAAAAAAAAAABmuQpaKf4Pv/m+L//5vi//+Z4P//fcX7/yWF\n        0/8Bes//AWSq4AAeNiEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABStjZZkwhXX+EWYr/wdf\n        Cf8JcRT/BlQG/wVMAP8FQwD/BD0A/QMoAIkAFwALAAAAAAAAAAAqNTUYlrPEzabk/v+b4v//m+L//5rh\n        //+Byfr/J4TT/wJ3zf8BbrrzAD9sWQAAFwsAGDAVAD9oTABJdFMAMzMFAAAAAAAAAAAAAAAEZIJwk53e\n        0P06i3P/C2A7/yyERf81azf/BmVG/wtNGv8DNgDcAiMAewARAA8AAAAAAAAAAEFBQR+4ys/er+j//5vi\n        //+b4v//l9/9/1Gp6P8pj+b/DnnR/wF4zf8BarXoAFaTvABcncwBbrrzAn7H5gBPekcAAAABAAAAAAAA\n        AABxVDEkaayitmufhviCl3P/ed7O/5+2i/9FmJv/E2Rt2wAoAKkAHgAqAAAAAQAAAAAAAAAAIiIiD6q2\n        usHF7f3/neP//5vi//+N1/r/KInX/yON5v8jiuP/DHfL/wV4zP8HeMz/HIHR/zCN2f8IkeT/AWefrQAA\n        AAcAAAAAAAAAAAAAAAALQk0XZVAtX1OCcreO6+T9cu7u/1KvsfwgYGt3AB4AGQATAA0AAAAAAAAAAAAA\n        AAAAAAACjpebiNHq9P6w6P7/neL//5ff/v9TrOf/JIbV/z2Z4P9grOn/W6jq/1yq6/9yvPf/d8D7/zOT\n        2/wEVId+AAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAOGNcJKmjfrZTvcHxgoBbtko5IB8AAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAABoaG04w9Pa59Px/f/D7f3/uer+/6bj/f+Ez/X/bb7t/zuV3f+Eyfj/ktj//5HZ\n        /f+O0/z/Y53K2RQuSSYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQjchFxxSWj4hNzcXAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAACQkJAeWoKOJzeHq9dnz/f/Z8/3/1fL9/7bl+/+R1/j/fMny/5fd\n        /P+Z4P7/Vqzn/zaO1u4+bpNmAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADY2Ng6UnaGLnbvN3pG60+bG5PL82PP9/8rv\n        /v++6/7/vur+/7Xo/f9fos/uDk6FawAkSAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4uLgsgMTkfHD5ULYSd\n        sKjS5/D+zeTt/H+kusp4nrfDnLO+w3GCjVwkJEgHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAATVNTK36Ehn1+hIZ3RU1NIREiIg8qKjgSAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAA//////4AA//8AAD//AAA//gAAH/4AAA/+AAAH/AAAA/wAAAP4AAAD+AAAA/gABAP4AAwD+AY\n        +B/gH+AH4B/AA8AfgAOAHwADgA8AA4AGAAeABgA/gAYAAYAGAADABgAA4A4AAPg/AAH8fwAB//+AA///\n        wAf///gP//////////8oAAAAEAAAACAAAAABACAAAAAAAEAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAEkhtDiBWfC8ZTHUyByZFIQA4VAkLQWorC0NrLQAhNxcAAAACAAAAAAAAAAAAAAAAAAAAAAAA\n        AAAAAAAAAAAAAiVqnHougLvrKHu37Q5GbtYNPmRyG2yo3xhrqOkMTHnPAydCVAAAAAIAAAAAAAAAAAAA\n        AAAAAAAAAAAAABhIYRUterPKOZDQ/zeP0P8fZZf+GFiF5yyFxf4lfb7/G3Cu/whBa98DLUhDAAAAAQAA\n        AAAAAAAAAAAAAAAAAAAtbJlJRZjV80ea2P86iMH/QpPO/zyNyP8serL/GmGU+yqDxP4YaaT/B0Bp0gU0\n        UzEAAAAAAAAAAAAAAAAzM2YFSpDBnVqs6P88gLH/G1B24VCg2/RMo+L/ImaY+A0/ZJgugsDoKYTH/w9X\n        jP4GO2OfADMzBQAAAAAAAAAANmR/HGKo29drt+3/KF+J/QgvSndOlMiyUKDb/BlVgrsIK0YdKXOqmi6I\n        yv4dc7L+CD9qkAAAAAIAAAAAAAAAAENyljFpsOPsdLbm/xpPdfgDLEdLOW2VOj97pm0WQ2EiAAAAASdi\n        jUEue7XrIGiexAg+ZjkAAAAAAAAAAAAAAABWh6hEdrrr9WOj0v8LQWn3AypKSAAAAAAAAAAAf39/AlCF\n        sj84h8izRJbY9lyf2eYmdbSsAEeCJwAAAAAAHAAJN29ycnC04ftYmMf/CEFq9wcxUUgAAAAAAAAAADh0\n        qDtrs+jeQZ3l/0Ge7f9ltPT/Nprm/Rd0uIMAAAABADQaHQ1kJcsldmb/MG+L/wY7UfoELyJ3ACoABipV\n        VQZXm8ehfMn0/4XP+/88l+H/FHa9yxt4vIoccbM2AAAAAA01KBMsa0PEEGsf/wlXE/8FPwn/BDQBvwAm\n        ABRZcn8Ul8vizpvi//+Fzvn/G4TU/wBippkAOGcbAFiKLgBIbQckSEgHb6iWkEiKa/RVnXX/HmxS8AMx\n        B4AAHwAIf3+JGrPY59ae4///arzw/x+G3f8MeMjyE3S+1hOByuEAZqNDAAAAAER3Zg9rinFafsm+01OQ\n        iY0AJhkUAAAAAEhISAe2y9Kiuen8/pHU9/9Yref/Xa3q/3TA9P9dq+PnGGWZNQAAAAAAAAAAf38AAlJ7\n        ah9IW1sOAAAAAAAAAAAAAAAAmKOoMrvS3sS/3+7uteP5/5vW9P6CxerwPoe+cypVfwYAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFoc4sWhpytTrTH0KqMqrx2h6S0Tz9ffwgAAAAAAAAAAAAA\n        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIkJCQHAAAAAQAAAAAAAAAAAAAAAOAP\n        JgDAByYAwAMmAMADJgCAASYAgAEmAIADJgCDASYAAwAmAAABJgAAACYAAAAmAIIAJgDHACYA/wEmAP/H\n        JgA=\n</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2/MemoryTributary.cs",
    "content": "﻿\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Runtime.InteropServices;\n\nnamespace System.IO\n{\n    /// <summary>\n    /// MemoryTributary is A re-implementation of MemoryStream that uses A dynamic list of byte arrays as A backing store, instead of A single byte array, the allocation\n    /// of which will fail for relatively small streams as it requires contiguous memory.\n    /// </summary>\n    public class MemoryTributary : Stream       /* http://msdn.microsoft.com/en-us/library/system.io.stream.aspx */\n    {\n        #region Constructors\n\n        public MemoryTributary()\n        {\n            Position = 0;\n        }\n\n        public MemoryTributary(byte[] source)\n        {\n            this.Write(source, 0, source.Length);\n            Position = 0;\n        }\n\n        public MemoryTributary(int length)\n        {\n            SetLength(length);\n            Position = length;\n            byte[] d = block;   //access block to prompt the allocation of memory\n            Position = 0;\n        }\n\n        #endregion\n\n        #region Status Properties\n\n        public override bool CanRead\n        {\n            get { return true; }\n        }\n\n        public override bool CanSeek\n        {\n            get { return true; }\n        }\n\n        public override bool CanWrite\n        {\n            get { return true; }\n        }\n\n        #endregion\n\n        #region Public Properties\n\n        public override long Length\n        {\n            get { return length; }\n        }\n\n        public override long Position { get; set; }\n\n        #endregion\n\n        #region Members\n\n        protected long length = 0;\n\n        protected long blockSize = 65536;\n\n        protected List<byte[]> blocks = new List<byte[]>();\n\n        #endregion\n\n        #region Internal Properties\n\n        /* Use these properties to gain access to the appropriate block of memory for the current Position */\n\n        /// <summary>\n        /// The block of memory currently addressed by Position\n        /// </summary>\n        protected byte[] block\n        {\n            get\n            {\n                while (blocks.Count <= blockId)\n                    blocks.Add(new byte[blockSize]);\n                return blocks[(int)blockId];\n            }\n        }\n        /// <summary>\n        /// The id of the block currently addressed by Position\n        /// </summary>\n        protected long blockId\n        {\n            get { return Position / blockSize; }\n        }\n        /// <summary>\n        /// The offset of the byte currently addressed by Position, into the block that contains it\n        /// </summary>\n        protected long blockOffset\n        {\n            get { return Position % blockSize; }\n        }\n\n        #endregion\n\n        #region Public Stream Methods\n\n        public override void Flush()\n        {\n        }\n\n        public override int Read(byte[] buffer, int offset, int count)\n        {\n            long lcount = (long)count;\n\n            if (lcount < 0)\n            {\n                throw new ArgumentOutOfRangeException(\"count\", lcount, \"Number of bytes to copy cannot be negative.\");\n            }\n\n            long remaining = (length - Position);\n            if (lcount > remaining)\n                lcount = remaining;\n\n            if (buffer == null)\n            {\n                throw new ArgumentNullException(\"buffer\", \"Buffer cannot be null.\");\n            }\n            if (offset < 0)\n            {\n                throw new ArgumentOutOfRangeException(\"offset\", offset, \"Destination offset cannot be negative.\");\n            }\n\n            int read = 0;\n            long copysize = 0;\n            do\n            {\n                copysize = Math.Min(lcount, (blockSize - blockOffset));\n                Buffer.BlockCopy(block, (int)blockOffset, buffer, offset, (int)copysize);\n                lcount -= copysize;\n                offset += (int)copysize;\n\n                read += (int)copysize;\n                Position += copysize;\n\n            } while (lcount > 0);\n\n            return read;\n\n        }\n\n        public override long Seek(long offset, SeekOrigin origin)\n        {\n            switch (origin)\n            {\n                case SeekOrigin.Begin:\n                    Position = offset;\n                    break;\n                case SeekOrigin.Current:\n                    Position += offset;\n                    break;\n                case SeekOrigin.End:\n                    Position = Length - offset;\n                    break;\n            }\n            return Position;\n        }\n\n        public override void SetLength(long value)\n        {\n            length = value;\n        }\n\n        public override void Write(byte[] buffer, int offset, int count)\n        {\n            long initialPosition = Position;\n            int copysize;\n            try\n            {\n                do\n                {\n                    copysize = Math.Min(count, (int)(blockSize - blockOffset));\n\n                    EnsureCapacity(Position + copysize);\n\n                    Buffer.BlockCopy(buffer, (int)offset, block, (int)blockOffset, copysize);\n                    count -= copysize;\n                    offset += copysize;\n\n                    Position += copysize;\n\n                } while (count > 0);\n            }\n            catch (Exception e)\n            {\n                Position = initialPosition;\n                throw e;\n            }\n        }\n\n        public override int ReadByte()\n        {\n            if (Position >= length)\n                return -1;\n\n            byte b = block[blockOffset];\n            Position++;\n\n            return b;\n        }\n\n        public override void WriteByte(byte value)\n        {\n            EnsureCapacity(Position + 1);\n            block[blockOffset] = value;\n            Position++;\n        }\n\n        protected void EnsureCapacity(long intended_length)\n        {\n            if (intended_length > length)\n                length = (intended_length);\n        }\n\n        #endregion\n\n        #region IDispose\n\n        /* http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx */\n        protected override void Dispose(bool disposing)\n        {\n            /* We do not currently use unmanaged resources */\n            base.Dispose(disposing);\n        }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/BuildInstruction.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    /// <summary>\n    /// 表示一条重构文件的指令。\n    /// </summary>\n    public class BuildInstruction\n    {\n        public BuildInstruction()\n            : this(BuildType.Unknown)\n        {\n        }\n\n        public BuildInstruction(BuildType type)\n        {\n            this.Type = type;\n            this.Length = 0;\n            this.FillByte = 0;\n            this.OldFilePosition = 0;\n        }\n\n        public BuildType Type { get; set; }\n\n        /// <summary>\n        /// 将要更新到新文件的字节长度。\n        /// </summary>\n        public int Length { get; set; }\n\n        /// <summary>\n        /// 填充固定字节的值，只用于RebuildType.FillBytes。\n        /// </summary>\n        public byte FillByte { get; set; }\n\n        /// <summary>\n        /// 从原文件中读取的起始偏移，只用于RebuildType.FromOldFile。\n        /// </summary>\n        public int OldFilePosition { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/BuildType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public enum BuildType\n    {\n        Unknown = 0,\n        FromPatcher = 1,\n        FillBytes = 2,\n        FromOldFile = 3,\n        Ending = 4\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/CheckSum.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.Threading;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public class CheckSum\n    {\n        static CheckSum()\n        {\n            if (sbox == null)\n            {\n                sbox = new uint[SizeSBox * 8];\n                Generatesbox(sbox);\n            }\n        }\n\n        private CheckSum()\n        {\n\n        }\n\n        private const int PolyNomial = 0x04C11DB7;\n        private const uint TopBit = 0x80000000;\n        private const int SizeSBox = 256;\n        private const int TableNum = 8;\n        private static uint[] sbox;\n\n        private static void Generatesbox(uint[] sbox)\n        {\n            uint remain;\n            uint i;\n            int bit;\n\n            for (i = 0; i < SizeSBox; i++)\n            {\n                remain = i << 0x18;\n                for (bit = 0; bit < 8; bit++)\n                {\n                    if ((remain & TopBit) != 0)\n                    {\n                        remain = (remain << 1) ^ PolyNomial;\n                    }\n                    else\n                    {\n                        remain = (remain << 1);\n                    }\n                }\n                sbox[i] = remain;\n            }\n\n            for (; i < sbox.Length; i++)\n            {\n                uint r = sbox[i - 256];\n                sbox[i] = sbox[r >> 24] ^ (r << 8);\n            }\n        }\n\n        public static uint ComputeHash(Stream stream, long length, CancellationToken cancellationToken = default)\n        {\n            return ComputeHash(stream, length, 0, cancellationToken);\n        }\n\n        public static uint ComputeHash(Stream stream, long length, uint crc, CancellationToken cancellationToken = default)\n        {\n            var buffer = ArrayPool<byte>.Shared.Rent(0x4000);\n            try\n            {\n                while (length > 0)\n                {\n                    cancellationToken.ThrowIfCancellationRequested();\n                    int count = stream.Read(buffer, 0, (int)Math.Min(buffer.Length, length));\n                    if (count == 0)\n                    {\n                        break;\n                    }\n                    crc = ComputeHash(buffer, 0, count, crc);\n                    length -= count;\n                }\n                return crc;\n            }\n            finally\n            {\n                ArrayPool<byte>.Shared.Return(buffer);\n            }\n        }\n\n        public static uint ComputeHash(byte[] data, int startIndex, int count, uint crc)\n        {\n            return ComputeHash(data.AsSpan(startIndex, count), crc);\n        }\n\n        public static uint ComputeHash(ReadOnlySpan<byte> data, uint crc)\n        {\n#if NET6_0_OR_GREATER\n            // reference paper: Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction\n            if (data.Length >= 32 && Sse42.IsSupported && Pclmulqdq.IsSupported)\n            {\n                unsafe\n                {\n                    Vector128<long> k4k3 = Vector128.Create(0xE8A45605, 0xC5B9CD4C);\n                    Vector128<byte> reverseMask = Vector128.Create((byte)15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);\n                    Vector128<byte> x0, x1, x2;\n\n                    fixed (byte* pData = data)\n                        x0 = Sse2.LoadVector128(pData);\n                    x0 = Ssse3.Shuffle(x0, reverseMask);\n                    x0 = Sse2.Xor(x0, Vector128.Create(0, 0, 0, crc).AsByte());\n                    data = data.Slice(16);\n\n                    while (data.Length >= 16)\n                    {\n                        x1 = Pclmulqdq.CarrylessMultiply(x0.AsInt64(), k4k3, 0x00).AsByte();\n                        x0 = Pclmulqdq.CarrylessMultiply(x0.AsInt64(), k4k3, 0x11).AsByte();\n                        fixed (byte* pData = data)\n                            x2 = Sse2.LoadVector128(pData);\n                        x2 = Ssse3.Shuffle(x2, reverseMask);\n                        x0 = Sse2.Xor(x0, x1);\n                        x0 = Sse2.Xor(x0, x2);\n                        data = data.Slice(16);\n                    }\n                    x0 = Ssse3.Shuffle(x0, reverseMask);\n\n                    Span<byte> rollingData = stackalloc byte[16];\n                    fixed (byte* pData = rollingData)\n                        Sse2.Store(pData, x0);\n                    crc = 0;\n                    foreach (var b in rollingData)\n                        crc = (crc << 8) ^ CheckSum.sbox[(crc >> 24) ^ b];\n                }\n            }\n#endif\n\n            if (data.Length >= 8)\n            {\n                Span<uint> pcrc = stackalloc uint[1] { crc };\n                Span<byte> crcBytes = MemoryMarshal.AsBytes(pcrc);\n                ref uint crcRef = ref pcrc[0];\n                ReadOnlySpan<uint> table = sbox.AsSpan();\n\n                while (data.Length >= 8)\n                {\n                    crcRef ^= (uint)((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]);\n                    crcRef = table[crcBytes[3] + 0x700]\n                            ^ table[crcBytes[2] + 0x600]\n                            ^ table[crcBytes[1] + 0x500]\n                            ^ table[crcBytes[0] + 0x400]\n                            ^ table[data[4] + 0x300]\n                            ^ table[data[5] + 0x200]\n                            ^ table[data[6] + 0x100]\n                            ^ table[data[7] + 0x000];\n                    data = data.Slice(8);\n                }\n                crc = crcRef;\n            }\n\n            foreach (var b in data)\n                crc = (crc << 8) ^ CheckSum.sbox[(crc >> 24) ^ b];\n\n            return crc;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/InflateStream.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Compression;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public class InflateStream : Stream\n    {\n        public InflateStream(Stream stream, bool buffered = false, bool leaveOpen = false)\n        {\n            this.BaseStream = stream;\n            this.baseStartPosition = stream.Position;\n            this.buffered = buffered;\n            this.leaveOpen = leaveOpen;\n\n            this.Reset();\n        }\n\n        public Stream BaseStream { get; private set; }\n\n        private readonly long baseStartPosition;\n        private readonly bool buffered;\n        private readonly bool leaveOpen;\n        private Stream deflateStream;\n        private long position;\n\n        public override long Position\n        {\n            get => this.position;\n            set => this.Seek(value, SeekOrigin.Begin);\n        }\n\n        public override long Seek(long offset, SeekOrigin origin)\n        {\n            long offsetFromBegin = origin switch\n            {\n                SeekOrigin.Begin => offset,\n                SeekOrigin.Current => offset + this.position,\n                _ => throw new NotSupportedException(),\n            };\n\n            if (offsetFromBegin < this.position)\n            {\n                this.Reset();\n                this.Skip(offsetFromBegin);\n            }\n            else if (offsetFromBegin > this.position)\n            {\n                this.Skip(offsetFromBegin - this.position);\n            }\n            return this.position;\n        }\n\n#if NET6_0_OR_GREATER\n        public override int Read(Span<byte> buffer)\n        {\n            int length = this.deflateStream.Read(buffer);\n            this.position += length;\n            return length;\n        }\n#endif\n\n        public override int Read(byte[] array, int offset, int count)\n        {\n            int length = this.deflateStream.Read(array, offset, count);\n            this.position += length;\n            return length;\n        }\n\n        public override int ReadByte()\n        {\n            int b = this.deflateStream.ReadByte();\n            if (b > -1)\n            {\n                this.position += 1;\n            }\n            return b;\n        }\n\n        public override void Flush()\n        {\n        }\n\n        public override bool CanSeek => true;\n\n        public override bool CanRead => true;\n\n        public override bool CanWrite => false;\n\n        public override long Length => throw new NotSupportedException();\n\n        public override void SetLength(long value) => throw new NotSupportedException();\n\n        public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();\n\n        public override void WriteByte(byte value) => throw new NotSupportedException();\n\n        public void Reset()\n        {\n            if (this.deflateStream != null && this.position == 0)\n            {\n                return;\n            }\n\n            this.BaseStream.Position = this.baseStartPosition;\n            var deflateStream = new DeflateStream(this.BaseStream, CompressionMode.Decompress, true);\n            if (buffered)\n            {\n                this.deflateStream = new BufferedStream(deflateStream);\n            }\n            else\n            {\n                this.deflateStream = deflateStream;\n            }\n            this.position = 0;\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                this.deflateStream?.Dispose();\n                if (!this.leaveOpen)\n                {\n                    this.BaseStream?.Dispose();\n                }\n            }\n        }\n\n        private void Skip(long length)\n        {\n            if (length < 0)\n            {\n                throw new ArgumentOutOfRangeException(nameof(length));\n            }\n            if (length == 0)\n            {\n                return;\n            }\n\n            var pool = ArrayPool<byte>.Shared;\n            byte[] buffer = pool.Rent(4096);\n            while (length > 0)\n            {\n                int len = this.Read(buffer, 0, (int)Math.Min(length, buffer.Length));\n                if (len == 0)\n                {\n                    break;\n                }\n                length -= len;\n            }\n            pool.Return(buffer);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/PatchPart.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public class PatchPart\n    {\n        public PatchPart()\n        {\n            this.Type = PatchType.Unknown;\n        }\n\n        public string FileName { get; set; }\n        public PatchType Type { get; set; }\n        public int FileLength { get; set; }\n        public uint Checksum { get; set; }\n        public uint OldChecksum { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/PatchType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public enum PatchType\n    {\n        Unknown = -1,\n        Create = 0,\n        Rebuild = 1,\n        Delete = 2,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/StreamUtils.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public class StreamUtils\n    {\n        public static void CopyStream(Stream src, Stream dest)\n        {\n            CopyStream(src, dest, Int32.MaxValue);\n        }\n\n        public static void CopyStream(Stream src, Stream dest, int length)\n        {\n            byte[] buffer = new byte[0x8000];\n            while (length > 0)\n            {\n                int count = src.Read(buffer, 0, Math.Min(buffer.Length, length));\n                if (count == 0)\n                    break;\n                dest.Write(buffer, 0, count);\n                length -= count;\n            }\n        }\n\n        public static void FillStream(Stream stream, int length, byte data)\n        {\n            byte[] buffer = new byte[length];\n            for (int i = 0; i < length; i++)\n            {\n                buffer[i] = data;\n            }\n            stream.Write(buffer, 0, length);\n        }\n\n        public static uint MoveStreamWithCrc32(Stream src, Stream dest, int length, uint crc)\n        {\n            byte[] buffer = new byte[0x8000];\n            while (length > 0)\n            {\n                int count = src.Read(buffer, 0, Math.Min(buffer.Length, length));\n                if (count == 0)\n                    break;\n                crc = CheckSum.ComputeHash(buffer, 0, count, crc);\n                dest.Write(buffer, 0, count);\n                length -= count;\n            }\n            return crc;\n        }\n\n        public static uint FillStreamWithCrc32(Stream stream, int length, byte data, uint crc)\n        {\n            byte[] buffer = new byte[length];\n            for (int i = 0; i < length; i++)\n            {\n                buffer[i] = data;\n            }\n            crc = CheckSum.ComputeHash(buffer, 0, length, crc);\n            stream.Write(buffer, 0, length);\n            return crc;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/WzPatcherReader.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public class WzPatcherReader\n    {\n        public WzPatcherReader(Stream input)\n        {\n            if (input == null)\n            {\n                throw new ArgumentNullException(\"output\");\n            }\n\n            this.BaseStream = input;\n            this.reader = new BinaryReader(input);\n        }\n\n        public Stream BaseStream { get; private set; }\n\n        private BinaryReader reader;\n\n        public PatchPart ReadPart()\n        {\n            PatchPart part = new PatchPart();\n            int switchByte = 0;\n            StringBuilder sb = new StringBuilder();\n\n            while ((switchByte = reader.BaseStream.ReadByte()) > 2)\n            {\n                sb.Append((char)switchByte);\n            }\n\n            if (switchByte == -1) //失败\n            {\n                return null;\n            }\n\n            part.Type = (PatchType)switchByte;\n            part.FileName = sb.ToString();\n\n            switch (part.Type)\n            {\n                case PatchType.Create:\n                    if (Path.HasExtension(part.FileName))\n                    {\n                        part.FileLength = reader.ReadInt32();\n                        part.Checksum = reader.ReadUInt32();\n                    }\n                    break;\n\n                case PatchType.Rebuild:\n                    part.OldChecksum = reader.ReadUInt32();\n                    part.Checksum = reader.ReadUInt32();\n                    break;\n\n                case PatchType.Delete:\n                    break;\n            }\n\n            return part;\n        }\n\n        public BuildInstruction ReadInst()\n        {\n            BuildInstruction inst = new BuildInstruction();\n            inst.Length = 0;\n            inst.FillByte = 0;\n            inst.OldFilePosition = 0;\n\n            uint command = reader.ReadUInt32();\n\n            if (command == 0)\n            {\n                inst.Type = BuildType.Ending;\n            }\n            else\n            {\n                switch (command >> 0x1c)\n                {\n                    case 0x08:\n                        inst.Type = BuildType.FromPatcher;\n                        inst.Length = (int)command & 0x0fffffff;\n                        break;\n\n                    case 0x0c:\n                        inst.Type = BuildType.FillBytes;\n                        inst.Length = (int)(command & 0x0fffff00) >> 8;\n                        inst.FillByte = (byte)(command & 0xff);\n                        break;\n\n                    default:\n                        inst.Type = BuildType.FromOldFile;\n                        inst.Length = (int)command;\n                        inst.OldFilePosition = reader.ReadInt32();\n                        break;\n                }\n            }\n            return inst;\n        }\n\n        public void ReadContent(Stream destStream, int length)\n        {\n            StreamUtils.CopyStream(this.BaseStream, destStream, length);\n        }\n\n        public int ReadContent(byte[] buffer, int offset, int count)\n        {\n            return this.BaseStream.Read(buffer, offset, count);\n        }\n\n        public int ReadInt32()\n        {\n            return this.reader.ReadInt32();\n        }\n\n        public uint ReadUInt32()\n        {\n            return this.reader.ReadUInt32();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/Builder/WzPatcherWriter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\nusing System.IO.Compression;\n\nnamespace WzComparerR2.Patcher.Builder\n{\n    public class WzPatcherWriter\n    {\n        public WzPatcherWriter(Stream output)\n        {\n            if( output == null)\n            {\n                throw new ArgumentNullException(\"output\");\n            }\n\n            this.BaseStream = output;\n            this.writer = new BinaryWriter(output, Encoding.ASCII);\n        }\n\n        public Stream BaseStream { get; private set; }\n\n        private BinaryWriter writer;\n        private DeflateStream deflateStream;\n        private BinaryWriter deflateWriter;\n        private MemoryTributary tempStream;\n        private bool useTempStream;\n        private long baseStreamPosition;\n\n        public void Begin()\n        {\n            this.writer.Write(Encoding.ASCII.GetBytes(\"WzPatch\\x1A\"));\n            this.writer.Write(2); //version\n\n            if (this.BaseStream.CanSeek && this.BaseStream.CanRead)\n            {\n                this.writer.Write(0u); //crc 回头计算\n                this.useTempStream = false;\n                this.deflateStream = new DeflateStream(this.BaseStream, CompressionMode.Compress, true);\n                this.baseStreamPosition = this.BaseStream.Position;\n\n                //zlib header\n                this.BaseStream.WriteByte(0x78);\n                this.BaseStream.WriteByte(0xDA);\n            }\n            else\n            {\n                this.useTempStream = true;\n                this.tempStream = new MemoryTributary();\n                this.deflateStream = new DeflateStream(this.tempStream, CompressionMode.Compress, true);\n                this.baseStreamPosition = 0;\n\n                //zlib header\n                this.tempStream.WriteByte(0x78);\n                this.tempStream.WriteByte(0xDA);\n            }\n\n            this.deflateWriter = new BinaryWriter(this.deflateStream, Encoding.ASCII);\n        }\n\n        public void WritePart(PatchPart part)\n        {\n            this.deflateWriter.Write(part.FileName.ToCharArray());\n            this.deflateWriter.Write((byte)part.Type); //尾部标识\n\n            switch (part.Type)\n            {\n                case PatchType.Create:\n                    if (Path.HasExtension(part.FileName))\n                    {\n                        this.deflateWriter.Write(part.FileLength);\n                        this.deflateWriter.Write(part.Checksum);\n                    }\n                    break;\n\n                case PatchType.Rebuild:\n                    this.deflateWriter.Write(part.OldChecksum);\n                    this.deflateWriter.Write(part.Checksum);\n                    break;\n\n                case PatchType.Delete:\n                    break;\n            }\n        }\n\n        public void WriteInst(BuildInstruction inst)\n        {\n            switch (inst.Type)\n            {\n                case BuildType.FromPatcher:\n                    this.deflateWriter.Write((0x08 << 0x1c) | (inst.Length & 0x0fffffff));\n                    break;\n\n                case BuildType.FillBytes:\n                    this.deflateWriter.Write((0x0c << 0x1c)\n                        | ((inst.Length & 0x000fffff) << 0x08)\n                        | (inst.FillByte & 0xff));\n                    break;\n\n                case BuildType.FromOldFile:\n                    int flag = (inst.Length >> 0x1c);\n                    if (flag == 0x08 || flag == 0x0c)\n                    {\n                        throw new Exception(\"errrrrrrrrr\");\n                    }\n                    this.deflateWriter.Write(inst.Length);\n                    this.deflateWriter.Write(inst.OldFilePosition);\n                    break;\n\n                case BuildType.Ending:\n                    this.deflateWriter.Write(0);\n                    break;\n            }\n        }\n\n        public void WriteContent(Stream contentStream, int length)\n        {\n            StreamUtils.CopyStream(contentStream, this.deflateStream, length);\n        }\n\n        public void WriteContent(byte[] buffer, int offset, int count)\n        {\n            MemoryStream ms = new MemoryStream(buffer, offset, count);\n            WriteContent(ms, (int)ms.Length);\n        }\n\n        public void End()\n        {\n            this.deflateStream.Flush();\n            this.deflateStream.Close();\n            this.deflateWriter.Close();\n\n            if (!this.useTempStream)\n            {\n                long curPos = this.BaseStream.Position;\n                this.BaseStream.Seek(this.baseStreamPosition, SeekOrigin.Begin);\n                uint crc = CheckSum.ComputeHash(this.BaseStream, curPos - this.baseStreamPosition);\n                this.BaseStream.Seek(this.baseStreamPosition - 4, SeekOrigin.Begin);\n                this.writer.Write(crc);\n                this.BaseStream.Seek(curPos, SeekOrigin.Begin);\n            }\n            else\n            {\n                this.tempStream.Seek(0, SeekOrigin.Begin);\n                uint crc = CheckSum.ComputeHash(this.tempStream, this.tempStream.Length);\n                this.writer.Write(crc);\n                this.tempStream.Seek(0, SeekOrigin.Begin);\n                StreamUtils.CopyStream(this.tempStream, this.BaseStream, (int)this.tempStream.Length);\n                this.tempStream.Close();\n                this.tempStream = null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/PatchPartContext.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Patcher\n{\n    public class PatchPartContext\n    {\n        public PatchPartContext(string fileName, long offset, int type)\n        {\n            this.Offset = offset;\n            this.FileName = fileName;\n            this.Type = type;\n        }\n\n        private readonly HashSet<string> dependencyFiles = new HashSet<string>();\n\n        public long Offset { get; set; }\n        public string FileName { get; private set; }\n        public int Type { get; private set; }\n        public int NewFileLength { get; set; }\n        public uint? OldChecksum { get; set; }\n        public uint? OldChecksumActual { get; set; }\n        public uint NewChecksum { get; set; }\n        public string TempFilePath { get; set; }\n        public string OldFilePath { get; set; }\n        public int Action0 { get; set; }\n        public int Action1 { get; set; }\n        public int Action2 { get; set; }\n        public ISet<string> DependencyFiles { get; private set; } = new HashSet<string>();\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/PatcherSetting.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Configuration;\nusing System.Globalization;\nusing System.Linq;\n\nnamespace WzComparerR2.Patcher\n{\n    public class PatcherSetting : ConfigurationElement\n    {\n        public PatcherSetting()\n        {\n\n        }\n\n        public PatcherSetting(string serverName)\n            : this(serverName, null, 1)\n        {\n            this.ServerName = serverName;\n        }\n\n        public PatcherSetting(string serverName, string urlFormat)\n            : this(serverName, urlFormat, 1)\n        {\n            this.UrlFormat = urlFormat;\n        }\n\n        public PatcherSetting(string serverName, string urlFormat, int maxVersion)\n        {\n            this.UrlFormat = urlFormat;\n            this.ServerName = serverName;\n            this.MaxVersion = maxVersion;\n        }\n\n        [ConfigurationProperty(\"serverName\", IsRequired = true)]\n        public string ServerName\n        {\n            get { return (string)this[\"serverName\"]; }\n            set { this[\"serverName\"] = value; }\n        }\n\n        [ConfigurationProperty(\"urlFormat\")]\n        public string UrlFormat\n        {\n            get { return (string)this[\"urlFormat\"]; }\n            set { this[\"urlFormat\"] = value; }\n        }\n\n        [Obsolete(\"Deprecated in favor of 'Versions'\")]\n        [ConfigurationProperty(\"version0\")]\n        public int? Version0\n        {\n            get { return (int?)this[\"version0\"]; }\n            set { this[\"version0\"] = value; }\n        }\n\n        [Obsolete(\"Deprecated in favor of 'Versions'\")]\n        [ConfigurationProperty(\"version1\")]\n        public int? Version1\n        {\n            get { return (int?)this[\"version1\"]; }\n            set { this[\"version1\"] = value; }\n        }\n\n        [ConfigurationProperty(\"maxVersion\")]\n        public int MaxVersion\n        {\n            get { return (int)this[\"maxVersion\"]; }\n            set { this[\"maxVersion\"] = value; }\n        }\n\n        [ConfigurationProperty(\"versions\")]\n        [TypeConverter(typeof(IntArrayToStringConverter))]\n        public int[] Versions\n        {\n            get { return (int[])this[\"versions\"]; }\n            set { this[\"versions\"] = value; }\n        }\n\n        public string Url\n        {\n            get\n            {\n                if (this.UrlFormat != null)\n                {\n                    if (this.Versions != null)\n                    {\n                        return string.Format(this.UrlFormat, this.Versions.Cast<object>().ToArray());\n                    }\n                    else if (this.MaxVersion > 0)\n                    {\n                        return string.Format(this.UrlFormat, Enumerable.Repeat<object>(0, this.MaxVersion).ToArray());\n                    }\n                    else\n                    {\n                        return this.UrlFormat;\n                    }\n                }\n                return null;\n            }\n        }\n\n        public override string ToString()\n        {\n            return this.ServerName;\n        }\n\n        public class IntArrayToStringConverter : TypeConverter\n        {\n            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)\n            {\n                return sourceType == typeof(string);\n            }\n\n            public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)\n            {\n                return destinationType == typeof(int[]);\n            }\n\n            public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)\n            {\n                if (value is string s)\n                {\n                    return s.Split(',').Select(segment => int.Parse(segment, NumberStyles.Integer, CultureInfo.InvariantCulture)).ToArray();\n                }\n                return null;\n            }\n\n            public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)\n            {\n                if (value is int[] array && destinationType == typeof(string))\n                {\n                    return string.Join(\",\", array.Select(v => v.ToString(CultureInfo.InvariantCulture)));\n                }\n                return null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/PatcherSettingCollection.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\nusing WzComparerR2.Config;\n\nnamespace WzComparerR2.Patcher\n{\n    public class PatcherSettingCollection : ConfigItemCollectionBase<PatcherSetting>\n    {\n        public PatcherSettingCollection()\n        {\n        }\n\n        protected override object GetElementKey(ConfigurationElement element)\n        {\n            return (element as PatcherSetting).ServerName;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/PatchingEventArgs.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Patcher\n{\n    public class PatchingEventArgs : EventArgs\n    {\n        public PatchingEventArgs(PatchPartContext part, PatchingState state)\n            : this(part, state, 0)\n        {\n        }\n\n        public PatchingEventArgs(PatchPartContext part, PatchingState state, long currentFileLength)\n        {\n            this.part = part;\n            this.state = state;\n            this.currentFileLen = currentFileLength;\n        }\n\n        private PatchPartContext part;\n        private PatchingState state;\n        private long currentFileLen;\n\n        public PatchPartContext Part\n        {\n            get { return part; }\n        }\n\n        public PatchingState State\n        {\n            get { return state; }\n        }\n\n        public long CurrentFileLength\n        {\n            get { return currentFileLen; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/PatchingState.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Patcher\n{\n    public enum PatchingState\n    {\n        PatchStart,\n        VerifyOldChecksumBegin,\n        VerifyOldChecksumEnd,\n        VerifyNewChecksumBegin,\n        VerifyNewChecksumEnd,\n        TempFileCreated,\n        TempFileBuildProcessChanged,\n        TempFileClosed,\n\n        PrepareVerifyOldChecksumBegin,\n        PrepareVerifyOldChecksumEnd,\n        ApplyFile,\n        FileSkipped,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/ReversePatcherBuilder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.Patcher.Builder;\nusing System.IO;\nusing System.IO.Compression;\n\nnamespace WzComparerR2.Patcher\n{\n    public class ReversePatcherBuilder\n    {\n        public ReversePatcherBuilder()\n        {\n        }\n\n        public string msDir;\n        public string outputFileName;\n        public string patchFileName;\n\n        public void Build()\n        {\n            List<FileReversePart> preReverse = new List<FileReversePart>();\n\n            using (FileStream fs = new FileStream(patchFileName, FileMode.Open, FileAccess.Read))\n            {\n                fs.Position = 18;\n                InflateStream stream = new InflateStream(fs, true);\n                WzPatcherReader reader = new WzPatcherReader(stream);\n                PatchPart filePart;\n\n                PatchPart reversePart;\n\n                while ((filePart = reader.ReadPart()) != null)\n                {\n                    switch (filePart.Type)\n                    {\n                        case PatchType.Create:\n                            if (filePart.FileLength > 0)\n                            {\n                                stream.Seek(filePart.FileLength, SeekOrigin.Current);\n                                //在原文件夹寻找同名文件\n                                string oldFile = Path.Combine(msDir, filePart.FileName);\n                                if (File.Exists(oldFile))\n                                {\n                                    reversePart = new PatchPart() { Type = PatchType.Create };\n                                    reversePart.FileName = filePart.FileName;\n                                    reversePart.FileLength = (int)new FileInfo(oldFile).Length;\n                                    preReverse.Add(new FileReversePart(reversePart));\n                                }\n                                else\n                                {\n                                    reversePart = new PatchPart() { Type = PatchType.Delete };\n                                    reversePart.FileName = filePart.FileName;\n                                    preReverse.Add(new FileReversePart(reversePart));\n                                }\n                            }\n                            break;\n\n                        case PatchType.Rebuild:\n                            reversePart = new PatchPart()\n                            {\n                                Type = PatchType.Rebuild,\n                                OldChecksum = filePart.Checksum,\n                                Checksum = filePart.OldChecksum,\n                                FileName = filePart.FileName\n                            };\n                            List<FileReverseInst> instList = new List<FileReverseInst>();\n\n                            BuildInstruction inst;\n                            int filePos = 0;\n                            while ((inst = reader.ReadInst())?.Type != null)\n                            {\n                                if (inst.Type == BuildType.Ending)\n                                {\n                                    break;\n                                }\n\n                                switch (inst.Type)\n                                {\n                                    case BuildType.FromPatcher:\n                                        stream.Seek(inst.Length, SeekOrigin.Current);\n\n                                        break;\n\n                                    case BuildType.FillBytes:\n\n                                        break;\n\n                                    case BuildType.FromOldFile:\n                                        instList.Add(new FileReverseInst() { Inst = inst, NewFilePosition = filePos });\n                                        break;\n                                }\n                                filePos += inst.Length;\n                            }\n\n                            preReverse.Add(new FileReversePart(reversePart) { InstList = instList });\n                            break;\n\n                        case PatchType.Delete:\n                            {\n                                string oldFile = Path.Combine(msDir, filePart.FileName);\n                                if (File.Exists(oldFile))\n                                {\n                                    reversePart = new PatchPart() { Type = PatchType.Create };\n                                    reversePart.FileName = filePart.FileName;\n                                    reversePart.FileLength = (int)new FileInfo(oldFile).Length;\n                                    preReverse.Add(new FileReversePart(reversePart));\n                                }\n                            }\n\n                            break;\n                    }\n                }//end while\n            }//end using\n\n            preReverse.Sort();\n\n            using (FileStream dest = new FileStream(outputFileName, FileMode.Create))\n            {\n                WzPatcherWriter writer = new WzPatcherWriter(dest);\n\n                writer.Begin();\n                foreach (var part in preReverse)\n                {\n                    string oldFileName = Path.Combine(msDir, part.Part.FileName);\n                    switch (part.Part.Type)\n                    {\n                        case PatchType.Create:\n                            //计算hash copy文件\n                            using (FileStream oldFs = new FileStream(oldFileName, FileMode.Open, FileAccess.Read, FileShare.Read))\n                            {\n                                part.Part.FileLength = (int)oldFs.Length;\n                                part.Part.Checksum = CheckSum.ComputeHash(oldFs, part.Part.FileLength);\n                                oldFs.Position = 0;\n                                writer.WritePart(part.Part);\n                                writer.WriteContent(oldFs, part.Part.FileLength);\n                            }\n                            break;\n\n                        case PatchType.Rebuild:\n                            writer.WritePart(part.Part);\n\n                            using (FileStream oldFs = new FileStream(oldFileName, FileMode.Open, FileAccess.Read, FileShare.Read))\n                            {\n                                //计算指令\n                                var instList = Work(part.InstList, (int)oldFs.Length);\n                                //开始执行\n                                foreach (var inst in instList)\n                                {\n                                    switch (inst.Inst.Type)\n                                    {\n                                        case BuildType.FromOldFile: //参数反转\n                                            inst.Inst.OldFilePosition = inst.NewFilePosition;\n                                            writer.WriteInst(inst.Inst);\n                                            break;\n\n                                        case BuildType.FromPatcher: //go 待优化\n                                            writer.WriteInst(inst.Inst);\n                                            oldFs.Position = inst.NewFilePosition;\n                                            writer.WriteContent(oldFs, inst.Inst.Length);\n                                            break;\n                                    }\n                                }\n                                //结束执行\n                                writer.WriteInst(new BuildInstruction(BuildType.Ending));\n                            }\n                            break;\n\n                        case PatchType.Delete:\n                            writer.WritePart(part.Part);\n                            break;\n                    }\n                }\n                writer.End();\n            }\n        }\n\n\n        private IEnumerable<FileReverseInst> Work(List<FileReverseInst> instList, int oldFileLength)\n        {\n            //筛选两个文件共同部分\n            List<FileReverseInst> temp = new List<FileReverseInst>(instList);\n            //排序\n            temp.Sort((a, b) =>\n            {\n                BuildInstruction a1 = a.Inst, b1 = b.Inst;\n                int compare = a1.OldFilePosition.CompareTo(b1.OldFilePosition);\n                if (compare == 0)\n                {\n                    compare = -a1.Length.CompareTo(b1.Length);\n                }\n                return compare;\n            });\n\n            //进链表\n            LinkedList<FileReverseInst> reverseList = new LinkedList<FileReverseInst>();\n            foreach (var reverse in temp)\n            {\n                if (reverseList.Count <= 0)\n                {\n                    reverseList.AddFirst(reverse);\n                }\n                else\n                {\n                    BuildInstruction prev = reverseList.Last.Value.Inst;\n                    BuildInstruction cur = reverse.Inst;\n\n                    if (cur.OldFilePosition <= prev.OldFilePosition) //过滤相等\n                    {\n                        continue;\n                    }\n                    if (cur.OldFilePosition < prev.OldFilePosition + prev.Length) //有重叠部分\n                    {\n                        int newLength = (cur.OldFilePosition + cur.Length) - (prev.OldFilePosition + prev.Length);\n                        if (newLength <= 0) //完全包含\n                        {\n                            continue;\n                        }\n                        //调整不重叠\n                        reverse.NewFilePosition += cur.Length - newLength;\n                        cur.OldFilePosition = (cur.OldFilePosition + cur.Length) - newLength;\n                        cur.Length = newLength;\n                    }\n                    reverseList.AddLast(reverse);\n                }\n            }\n\n            //链表填充\n            if (reverseList.Count <= 0)\n            {\n                //怎么可能呢闹呢新建文件吧\n                return null;\n            }\n\n            //填充不存在的区块\n            int totalLength = 0;\n            for (var reverse = reverseList.First; reverse != null; reverse = reverse.Next) //懒得写while\n            {\n                BuildInstruction prev = null;\n                BuildInstruction cur = reverse.Value.Inst;\n                if (reverse.Previous == null) //如果是first 构造一个虚指令\n                {\n                    prev = new BuildInstruction(BuildType.FromOldFile);\n                }\n                else\n                {\n                    prev = reverse.Previous.Value.Inst;\n                }\n                int newLength = cur.OldFilePosition - (prev.OldFilePosition + prev.Length);\n                if (newLength > 0) //如果中间缺失 添加一块原区段\n                {\n                    reverseList.AddBefore(reverse, new FileReverseInst()\n                    {\n                        Inst = new BuildInstruction(BuildType.FromPatcher)\n                        {\n                            Length = newLength\n                        },\n                        NewFilePosition = cur.OldFilePosition - newLength\n                    });\n                    totalLength += newLength;\n                }\n                else if (newLength < 0)\n                {\n                    throw new Exception(\"?????\");\n                }\n\n                totalLength += cur.Length;\n            }\n\n            //补充尾部区块\n            BuildInstruction last = reverseList.Last.Value.Inst;\n            if (last.Type == BuildType.FromOldFile)\n            {\n                int newLength = oldFileLength - (last.OldFilePosition + last.Length);\n                if (newLength > 0)\n                {\n                    reverseList.AddLast(new FileReverseInst()\n                    {\n                        Inst = new BuildInstruction(BuildType.FromPatcher)\n                        {\n                            Length = newLength\n                        },\n                        NewFilePosition = last.OldFilePosition + last.Length\n                    });\n\n                    totalLength += newLength;\n                }\n                else if (newLength < 0)\n                {\n                    throw new Exception(\"?????\");\n                }\n            }\n\n            return reverseList;\n        }\n\n        private class FileReverseInst\n        {\n            public int NewFilePosition { get; set; }\n            public BuildInstruction Inst { get; set; }\n        }\n\n        private class FileReversePart : IComparable<FileReversePart>\n        {\n            public FileReversePart(PatchPart part)\n            {\n                this.Part = part;\n            }\n            public PatchPart Part { get; set; }\n            public List<FileReverseInst> InstList { get; set; }\n\n            int IComparable<FileReversePart>.CompareTo(FileReversePart other)\n            {\n                int comp = ((int)this.Part.Type).CompareTo((int)other.Part.Type);\n                if (comp == 0)\n                {\n                    if (this.Part.Type == PatchType.Create)\n                    {\n                        if (this.Part.FileLength == 0) return -1;\n                        else if (other.Part.FileLength == 0) return 1;\n                    }\n                    comp = this.Part.FileName.CompareTo(other.Part.FileName);\n                }\n                return comp;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Patcher/WzPatcher.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Text;\nusing System.IO.Compression;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing WzComparerR2.Patcher.Builder;\nusing PartialStream = WzComparerR2.WzLib.Utilities.PartialStream;\n\nnamespace WzComparerR2.Patcher\n{\n    public class WzPatcher : IDisposable\n    {\n        public WzPatcher(string fileName)\n        {\n            this.patchFile = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read,\n                0x4000, FileOptions.Asynchronous | FileOptions.RandomAccess);\n            this.NoticeEncoding = Encoding.Default;\n            this.ThrowOnValidationFailed = true;\n        }\n\n        private const int MAX_PATH = 260;\n\n        private FileStream patchFile;\n        private PartialStream patchBlock;\n        private InflateStream inflateStream;\n\n        private string noticeText;\n        private List<PatchPartContext> patchParts;\n        private Dictionary<string, FileHash> oldFileHash;\n\n        public Encoding NoticeEncoding { get; set; }\n\n        public List<PatchPartContext> PatchParts\n        {\n            get { return patchParts; }\n        }\n\n        public string NoticeText\n        {\n            get { return noticeText; }\n        }\n\n        public Dictionary<string, FileHash> OldFileHash\n        {\n            get { return oldFileHash; }\n        }\n\n        public bool? IsKMST1125Format { get; private set; }\n        public bool ThrowOnValidationFailed { get; set; }\n\n        public event EventHandler<PatchingEventArgs> PatchingStateChanged;\n\n\n        /// <summary>\n        /// 验证并初始化补丁解压流。\n        /// </summary>\n        public void OpenDecompress(CancellationToken cancellationToken)\n        {\n            var patchBlock = TrySplit(this.patchFile);\n            if (patchBlock == null)\n            {\n                throw new Exception(\"Decompress Error, cannot find patch block from the stream.\");\n            }\n\n            BinaryReader r = new BinaryReader(patchBlock);\n            patchBlock.Seek(8, SeekOrigin.Begin);\n            int ver = r.ReadInt32();\n            uint checkSum0 = r.ReadUInt32();\n            uint checkSum1 = CheckSum.ComputeHash(patchBlock, patchBlock.Length - 0x10, cancellationToken);\n            VerifyCheckSum(checkSum0, checkSum1, \"PatchFile\", \"0\");\n\n            patchBlock.Seek(16, SeekOrigin.Begin);\n            byte lb = r.ReadByte(), hb = r.ReadByte();\n            if (!(lb == 0x78 && (lb * 0x100 + hb) % 31 == 0)) // zlib头标识 没有就把这两字节都当数据段好了..\n            {\n                patchBlock.Seek(-2, SeekOrigin.Current);\n            }\n\n#if NET6_0_OR_GREATER\n            // wrap InflateStream with BufferedStream for better performance in net6+\n            bool buffered = true;\n#else\n            bool buffered = false;\n#endif\n            this.patchBlock = patchBlock;\n            this.inflateStream = new InflateStream(patchBlock, buffered);\n        }\n\n        private PartialStream TrySplit(Stream metaStream)\n        {\n            metaStream.Seek(0, SeekOrigin.Begin);\n            BinaryReader r = new BinaryReader(metaStream);\n\n            bool TryCheckFileEnding(out PartialStream patchBlock, out string noticeText)\n            {\n                metaStream.Seek(-4, SeekOrigin.End);\n                uint check = r.ReadUInt32();\n                if (check != 0xf2f7fbf3) // f3 fb f7 f2\n                {\n                    patchBlock = null;\n                    noticeText = null;\n                    return false;\n                }\n\n                metaStream.Seek(-12, SeekOrigin.End);\n                long patchBlockLength = r.ReadUInt32();\n                long noticeLength = r.ReadUInt32();\n                metaStream.Seek(-12 - noticeLength - patchBlockLength, SeekOrigin.End);\n                patchBlock = new PartialStream(metaStream, metaStream.Position, patchBlockLength);\n                metaStream.Seek(patchBlockLength, SeekOrigin.Current);\n                noticeText = this.NoticeEncoding.GetString(r.ReadBytes((int)noticeLength));\n                return true;\n            }\n\n            bool TryCheckFileEnding64(out PartialStream patchBlock, out string noticeText)\n            {\n                metaStream.Seek(-8, SeekOrigin.End);\n                ulong check = r.ReadUInt64();\n                if (check != 0xf2f7fbf3) // f3 fb f7 f2 00 00 00 00\n                {\n                    patchBlock = null;\n                    noticeText = null;\n                    return false;\n                }\n\n                metaStream.Seek(-24, SeekOrigin.End);\n                long patchBlockLength = r.ReadInt64();\n                long noticeLength = r.ReadInt64();\n                metaStream.Seek(-24 - noticeLength - patchBlockLength, SeekOrigin.End);\n                patchBlock = new PartialStream(metaStream, metaStream.Position, patchBlockLength);\n                metaStream.Seek(patchBlockLength, SeekOrigin.Current);\n                noticeText = this.NoticeEncoding.GetString(r.ReadBytes((int)noticeLength));\n                return true;\n            }\n\n            PartialStream patchBlock;\n            string noticeText;\n            if (r.ReadUInt16() == 0x5a4d)//\"MZ\"\n            {\n                if (!(TryCheckFileEnding(out patchBlock, out noticeText) || TryCheckFileEnding64(out patchBlock, out noticeText)))\n                {\n                    return null;\n                }\n            }\n            else\n            {\n                // for TMS264 patcher, also check file ending\n                if (!TryCheckFileEnding64(out patchBlock, out noticeText))\n                {\n                    patchBlock = new PartialStream(metaStream, 0, metaStream.Length);\n                    noticeText = null;\n                }\n            }\n\n            // check file header\n            patchBlock.Seek(0, SeekOrigin.Begin);\n            r = new BinaryReader(patchBlock);\n            if (!r.ReadBytes(8).AsSpan().SequenceEqual(\"WzPatch\\x1A\"u8))\n            {\n                return null;\n            }\n\n            this.noticeText = noticeText;\n            patchBlock.Seek(0, SeekOrigin.Begin);\n            return patchBlock;\n        }\n\n        public long PrePatch(CancellationToken cancellationToken)\n        {\n            if (this.inflateStream == null)\n            {\n                this.OpenDecompress(cancellationToken);\n            }\n            else\n            {\n                this.inflateStream.Reset();\n            }\n\n            var patchParts = new List<PatchPartContext>();\n            var r = new BinaryReader(this.inflateStream);\n\n            if (this.TryReadKMST1125FileHashList(r, out var fileHashMap))\n            {\n                this.oldFileHash = fileHashMap;\n                this.IsKMST1125Format = true;\n            }\n            else\n            {\n                this.IsKMST1125Format = false;\n                // reset file cursor\n                this.inflateStream.Reset();\n            }\n\n            while (true)\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n                PatchPartContext part = ReadPatchPart(r);\n\n                if (part == null)\n                {\n                    break;\n                }\n\n                if (this.IsKMST1125Format.Value && this.oldFileHash.TryGetValue(part.FileName, out var fileHash))\n                {\n                    part.OldChecksum = fileHash.Hash;\n                }\n\n                patchParts.Add(part);\n\n                //跳过当前段\n                switch (part.Type)\n                {\n                    case 0:\n                        if (part.NewFileLength > 0)\n                        {\n                            this.inflateStream.Seek(part.NewFileLength, SeekOrigin.Current);\n                        }\n                        break;\n\n                    case 1:\n                        {\n                            part.NewFileLength = CalcNewFileLength(part, r);\n                        }\n                        break;\n\n                    case 2:\n                        break;\n                }\n            }\n\n            this.patchParts = patchParts;\n            return this.inflateStream.Position;\n        }\n\n        private PatchPartContext ReadPatchPart(BinaryReader r)\n        {\n            string fileName;\n            int patchType = GetFileName(r, out fileName);\n            PatchPartContext part;\n\n            switch (patchType)\n            {\n                case 0:\n                    if (string.IsNullOrEmpty(Path.GetExtension(fileName)))\n                    {\n                        part = new PatchPartContext(fileName, -1, 0);\n                    }\n                    else\n                    {\n                        int fileLength = r.ReadInt32();\n                        uint checkSum0 = r.ReadUInt32();\n                        part = new PatchPartContext(fileName, this.inflateStream.Position, patchType);\n                        part.NewChecksum = checkSum0;\n                        part.NewFileLength = fileLength;\n                    }\n                    break;\n\n                case 1:\n                    {\n                        uint? oldCheckSum0 = null;\n                        if (!this.IsKMST1125Format.Value)\n                        {\n                            oldCheckSum0 = r.ReadUInt32();\n                        }\n                        uint newCheckSum0 = r.ReadUInt32();\n                        part = new PatchPartContext(fileName, this.inflateStream.Position, patchType);\n                        part.OldChecksum = oldCheckSum0;\n                        part.NewChecksum = newCheckSum0;\n                    }\n                    break;\n\n                case 2:\n                    {\n                        part = new PatchPartContext(fileName, -1, patchType);\n                    }\n                    break;\n\n                case -1:\n                    return null;\n\n                default:\n                    throw new Exception(\"Unknown patch type \" + patchType + \".\");\n            }\n            return part;\n        }\n\n        /// <summary>\n        /// 对于已经解压的patch文件，向客户端执行更新过程。\n        /// </summary>\n        /// <param Name=\"mapleStoryFolder\">冒险岛客户端所在文件夹。</param>\n        public void Patch(string mapleStoryFolder, CancellationToken cancellationToken = default)\n        {\n            this.Patch(mapleStoryFolder, mapleStoryFolder, cancellationToken);\n        }\n\n        /// <summary>\n        /// 对于已经解压的patch文件，向客户端执行更新过程，可以自己指定临时文件的文件夹。\n        /// </summary>\n        /// <param Name=\"mapleStoryFolder\">冒险岛客户端所在文件夹。</param>\n        /// <param Name=\"tempFileFolder\">生成临时文件的文件夹。</param>\n        public void Patch(string mapleStoryFolder, string tempFileFolder, CancellationToken cancellationToken = default)\n        {\n            string tempDir = CreateRandomDir(tempFileFolder);\n\n            if (this.inflateStream.Position > 0) //重置到初始化\n            {\n                this.inflateStream.Reset();\n            }\n\n            if (this.patchParts == null) //边读取边执行\n            {\n                BinaryReader r = new BinaryReader(this.inflateStream);\n                if (this.TryReadKMST1125FileHashList(r, out var fileHash))\n                {\n                    this.oldFileHash = fileHash;\n                    this.IsKMST1125Format = true;\n                    this.ValidateFileHash(mapleStoryFolder, this.ThrowOnValidationFailed, cancellationToken);\n                }\n                else\n                {\n                    this.IsKMST1125Format = false;\n                    // reset file cursor\n                    this.inflateStream.Reset();\n                    r = new BinaryReader(this.inflateStream);\n                }\n\n                this.patchParts = new List<PatchPartContext>();\n                while (true)\n                {\n                    cancellationToken.ThrowIfCancellationRequested();\n                    PatchPartContext part = ReadPatchPart(r);\n\n                    if (part == null)\n                    {\n                        break;\n                    }\n\n                    patchParts.Add(part);\n\n                    //跳过当前段\n                    switch (part.Type)\n                    {\n                        case 0:\n                            CreateNewFile(part, tempDir);\n                            break;\n                        case 1:\n                            RebuildFile(part, tempDir, mapleStoryFolder);\n                            break;\n                        case 2:\n                            break;\n                    }\n                }\n            }\n            else  //按照调整后顺序执行\n            {\n                this.ValidateFileHash(mapleStoryFolder, this.ThrowOnValidationFailed, cancellationToken);\n\n                foreach (PatchPartContext part in this.patchParts)\n                {\n                    switch (part.Type)\n                    {\n                        case 0:\n                            CreateNewFile(part, tempDir);\n                            break;\n                        case 1:\n                            RebuildFile(part, tempDir, mapleStoryFolder, cancellationToken);\n                            break;\n                        case 2:\n                            break;\n                    }\n                }\n            }\n\n            foreach (PatchPartContext part in this.patchParts)\n            {\n                if (part.Type != 2 && !string.IsNullOrEmpty(part.TempFilePath))\n                {\n                    this.OnApplyFile(part);\n                    SafeMove(part.TempFilePath, Path.Combine(mapleStoryFolder, part.FileName));\n                }\n                else if (part.Type == 2)\n                {\n                    this.OnApplyFile(part);\n                    if (part.FileName.EndsWith(\"\\\\\"))\n                        SafeDeleteDirectory(Path.Combine(mapleStoryFolder, part.FileName));\n                    else\n                        SafeDeleteFile(Path.Combine(mapleStoryFolder, part.FileName));\n                }\n            }\n\n            SafeDeleteDirectory(tempDir);\n        }\n\n        private bool TryReadKMST1125FileHashList(BinaryReader r, out Dictionary<string, FileHash> fileHash)\n        {\n            try\n            {\n                fileHash = new Dictionary<string, FileHash>();\n                int count = r.ReadInt32();\n                for (int i = 0; i < count; i++)\n                {\n                    string fn = this.ReadStringWithLength(r, MAX_PATH);\n                    uint checksum = r.ReadUInt32();\n                    fileHash.Add(fn, new FileHash\n                    {\n                        Hash = checksum,\n                    });\n                }\n                return true;\n            }\n            catch\n            {\n                fileHash = null;\n                return false;\n            }\n        }\n\n        private void ValidateFileHash(string msDir, bool failOnValidationFailed, CancellationToken cancellationToken = default)\n        {\n            if (this.OldFileHash != null && this.OldFileHash.Count > 0)\n            {\n                foreach(var kv in this.OldFileHash)\n                {\n                    cancellationToken.ThrowIfCancellationRequested();\n                    // The temporary context is only used for triggering event.\n                    var part = new PatchPartContext(kv.Key, -1, -1)\n                    {\n                        OldFilePath = Path.Combine(msDir, kv.Key)\n                    };\n\n                    this.OnPrepareVerifyOldChecksumBegin(part);\n                    uint oldCheckSum = 0;\n                    if (!File.Exists(part.OldFilePath))\n                    {\n                        kv.Value.VerifyState = FileHashVerifyState.FileNotFound;\n                    }\n                    else\n                    {\n                        using (var fs = new FileStream(part.OldFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))\n                        {\n                            oldCheckSum = CheckSum.ComputeHash(fs, fs.Length, cancellationToken);\n                        }\n                        part.OldChecksum = kv.Value.Hash;\n                        part.OldChecksumActual = oldCheckSum;\n                        kv.Value.ActualHash = oldCheckSum;\n                        kv.Value.VerifyState = (oldCheckSum == kv.Value.Hash) ? FileHashVerifyState.Verified : FileHashVerifyState.HashNotMatch;\n                    }\n                    this.OnPrepareVerifyOldChecksumEnd(part);\n\n                    if (failOnValidationFailed)\n                    {\n                        VerifyCheckSum(kv.Value.Hash, oldCheckSum, part.FileName, \"origin\");\n                    }\n                }\n            }\n        }\n\n        private string CreateRandomDir(string folder)\n        {\n            string randomDir = null;\n            do\n            {\n                randomDir = Path.Combine(folder, Path.GetRandomFileName());\n            }\n            while (Directory.Exists(randomDir));\n\n            Directory.CreateDirectory(randomDir);\n            return randomDir;\n        }\n\n        public void SafeMove(string srcFile, string dstFile)\n        {\n            if (!File.Exists(srcFile))\n                return;\n\n            DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(dstFile));\n            if (!dir.Exists)\n                dir.Create();\n\n            SafeDeleteFile(dstFile);\n\n            File.Move(srcFile, dstFile);\n        }\n\n        private static void SafeDeleteFile(string fileName)\n        {\n            FileInfo fi = new FileInfo(fileName);\n            if (fi.Exists)\n            {\n                if ((fi.Attributes & FileAttributes.ReadOnly) != 0)\n                {\n                    fi.Attributes = fi.Attributes & (~FileAttributes.ReadOnly);\n                }\n                fi.Delete();\n            }\n        }\n\n        private static void SafeDeleteDirectory(string dirName)\n        {\n            DirectoryInfo di = new DirectoryInfo(dirName);\n            if (di.Exists)\n            {\n                if ((di.Attributes & FileAttributes.ReadOnly) != 0)\n                {\n                    di.Attributes = di.Attributes & (~FileAttributes.ReadOnly);\n                }\n\n                foreach (var f in di.GetFileSystemInfos())\n                {\n                    if ((f.Attributes & FileAttributes.ReadOnly) != 0)\n                    {\n                        f.Attributes = f.Attributes & (~FileAttributes.ReadOnly);\n                    }\n                }\n\n                di.Delete(true);\n            }\n        }\n\n        private int GetFileName(BinaryReader reader, out string fileName)\n        {\n            int switchByte = 0;\n            StringBuilder sb = new StringBuilder();\n\n            while ((switchByte = reader.BaseStream.ReadByte()) > 2)\n            {\n                sb.Append((char)switchByte);\n            }\n\n            fileName = sb.ToString();\n            return switchByte;\n        }\n\n        private string ReadStringWithLength(BinaryReader reader, int? maxLength = null)\n        {\n            int length = reader.ReadInt32();\n            if (length < 0)\n            {\n                throw new Exception($\"Invalid length: {length}\");\n            }\n            if (maxLength != null && length > maxLength)\n            {\n                throw new Exception($\"String length exceed the limit ({length} > {maxLength}).\");\n            }\n            return Encoding.ASCII.GetString(reader.ReadBytes(length));\n        }\n\n        public void CreateNewFile(PatchPartContext part, string tempDir)\n        {\n            this.OnPatchStart(part);\n            string tempFileName = Path.Combine(tempDir, part.FileName);\n            EnsureDirExists(tempFileName);\n\n            if (part.NewFileLength <= 0)\n                return;\n\n            this.inflateStream.Seek(part.Offset, SeekOrigin.Begin);\n            FileStream tempFileStream = new FileStream(tempFileName, FileMode.Create, FileAccess.ReadWrite);\n            part.TempFilePath = tempFileName;\n            this.OnTempFileCreated(part);\n            //创建文件同时计算checksum\n            uint checkSum1 = StreamUtils.MoveStreamWithCrc32(this.inflateStream, tempFileStream, part.NewFileLength, 0U);\n            tempFileStream.Flush();\n\n            this.OnVerifyNewChecksumBegin(part);\n            VerifyCheckSum(part.NewChecksum, checkSum1, part.FileName, \"0\");\n            this.OnVerifyNewChecksumEnd(part);\n\n            tempFileStream.Close();\n            this.OnTempFileClosed(part);\n        }\n\n        public void RebuildFile(PatchPartContext part, string tempDir, string msDir, CancellationToken cancellationToken = default)\n        {\n            this.OnPatchStart(part);\n            string tempFileName = Path.Combine(tempDir, part.FileName);\n            EnsureDirExists(tempFileName);\n            part.OldFilePath = Path.Combine(msDir, part.FileName);\n\n            var oldWzFiles = new Dictionary<string, FileStream>();\n            FileStream tempFileStream = null;\n            bool deferredVerifyFileHash = false;\n\n            FileStream openFile(string fileName)\n            {\n                if (string.IsNullOrEmpty(fileName))\n                {\n                    return null;\n                }\n                if (!oldWzFiles.TryGetValue(fileName, out var fs))\n                {\n                    if (deferredVerifyFileHash && this.oldFileHash != null)\n                    {\n                        verifyDependencyFileHash(fileName);\n                    }\n                    fs = new FileStream(Path.Combine(msDir, fileName), FileMode.Open, FileAccess.Read, FileShare.Read);\n                    oldWzFiles.Add(fileName, fs);\n                }\n                return fs;\n            }\n\n            void closeAllFiles()\n            {\n                foreach(var fs in oldWzFiles.Values)\n                {\n                    fs.Close();\n                }\n                tempFileStream?.Close();\n            }\n\n            void verifyDependencyFileHash(string depFileName)\n            {\n                if (!this.OldFileHash.TryGetValue(depFileName, out var fileHash))\n                {\n                    throw new Exception($\"OldFileHash does not exist. FileName: {depFileName}\");\n                }\n                switch (fileHash.VerifyState)\n                {\n                    case FileHashVerifyState.NotVerified:\n                        throw new Exception($\"OldFile has not verified. FileName: {depFileName}\");\n                    case FileHashVerifyState.FileNotFound:\n                        throw new Exception($\"OldFile not found. FileName: {depFileName}\");\n                    case FileHashVerifyState.HashNotMatch:\n                        throw new Exception($\"OldFile hash not match. FileName: {depFileName}\");\n                }\n            }\n\n            void verifyOldFileHash(out bool skipUpdate)\n            {\n                skipUpdate = false;\n                var oldWzFile = openFile(part.FileName);\n                this.OnVerifyOldChecksumBegin(part);\n                uint oldCheckSumActual = CheckSum.ComputeHash(oldWzFile, oldWzFile.Length);\n                this.OnVerifyOldChecksumEnd(part);\n\n                if (oldCheckSumActual == part.NewChecksum && (part.NewFileLength == 0 || oldWzFile.Length == part.NewFileLength)) // file is updated\n                {\n                    skipUpdate = true;\n                }\n                else if (part.OldChecksum != null)\n                {\n                    VerifyCheckSum(part.OldChecksum.Value, oldCheckSumActual, part.FileName, \"origin\");\n                }\n            }\n\n            void skipIfNeeded()\n            {\n                if (part.NewFileLength == 0)\n                {\n                    this.inflateStream.Seek(part.Offset, SeekOrigin.Begin);\n                    BinaryReader r = new BinaryReader(this.inflateStream);\n                    // Here is a trick that to leverage CalcNewFileLength method to skip this file.\n                    part.NewFileLength = CalcNewFileLength(part, r);\n                }\n                else\n                {\n                    // prepatch executed, patcher can jump to the next part outside.\n                }\n            }\n\n            try\n            {\n                if (this.IsKMST1125Format == true)\n                {\n                    // prepatch enabled\n                    if (this.OldFileHash != null && part.DependencyFiles.Count > 0)\n                    {\n                        // verify self\n                        if (File.Exists(part.OldFilePath))\n                        {\n                            if (this.OldFileHash != null && this.OldFileHash.TryGetValue(part.FileName, out var fileHash))\n                            {\n                                if (fileHash.ActualHash == part.NewChecksum)\n                                {\n                                    // file already updated, skip.\n                                    skipIfNeeded();\n                                    OnFileSkipped(part);\n                                    return;\n                                }\n                            }\n                            else\n                            {\n                                verifyOldFileHash(out bool skipUpdate);\n                                if (skipUpdate)\n                                {\n                                    skipIfNeeded();\n                                    OnFileSkipped(part);\n                                    return;\n                                }\n                            }\n                        }\n\n                        // verify dependencies\n                        foreach (var depFileName in part.DependencyFiles)\n                        {\n                            verifyDependencyFileHash(depFileName);\n                        }\n                    }\n                    else\n                    {\n                        // no DependencyFiles means prepatch is disabled, check old files on-demand.\n                        deferredVerifyFileHash = true;\n                    }\n                }\n                else if (part.OldChecksum != null)\n                {\n                    verifyOldFileHash(out bool skipUpdate);\n                    if (skipUpdate)\n                    {\n                        skipIfNeeded();\n                        OnFileSkipped(part);\n                        return;\n                    }\n                }\n\n                int cmd;\n                //int blockLength;\n                tempFileStream = new FileStream(tempFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 0x4000);\n                part.TempFilePath = tempFileName;\n                if (part.NewFileLength > 0) //预申请硬盘空间 似乎可以加快读写速度\n                {\n                    tempFileStream.SetLength(part.NewFileLength);\n                    tempFileStream.Seek(0, SeekOrigin.Begin);\n                }\n                this.OnTempFileCreated(part);\n                uint newCheckSum1 = 0;\n\n                this.inflateStream.Seek(part.Offset, SeekOrigin.Begin);\n                BinaryReader r = new BinaryReader(this.inflateStream);\n\n                double patchProc = 0;\n                const double patchProcReportInverval = 0.005;\n\n                //v3新增读缓冲\n                List<RebuildFileOperation> operList = new List<RebuildFileOperation>(32768);\n                List<RebuildFileOperation> readFileOperList = new List<RebuildFileOperation>(operList.Capacity);\n                MemoryStream msBuffer = new MemoryStream(1024 * 1024 * 64);\n                int preLoadByteCount = 0;\n                while (true)\n                {\n                    cancellationToken.ThrowIfCancellationRequested();\n                    cmd = r.ReadInt32();\n                    RebuildFileOperation op = null;\n                    if (cmd != 0)\n                    {\n                        switch ((uint)cmd >> 0x1C)\n                        {\n                            case 0x08:\n                                op = new RebuildFileOperation(0);\n                                op.Length = cmd & 0x0fffffff;\n                                break;\n                            case 0x0c:\n                                op = new RebuildFileOperation(1);\n                                op.FillByte = (byte)(cmd & 0xff);\n                                op.Length = (cmd & 0x0fffff00) >> 8;\n                                break;\n                            default:\n                                op = new RebuildFileOperation(2);\n                                op.Length = cmd;\n                                op.StartPosition = r.ReadInt32();\n                                op.FromFileName = this.IsKMST1125Format.Value ? this.ReadStringWithLength(r) : part.FileName;\n                                break;\n                        }\n                    }\n\n                    //如果大于 先处理当前所有预读操作\n                    if (cmd == 0 || (operList.Count >= operList.Capacity - 1)\n                        || (op.OperType != 1 && (op.Length + preLoadByteCount > msBuffer.Capacity)))\n                    {\n                        //排序预读原文件\n                        readFileOperList.Sort((left, right) => {\n                            int cmp;\n                            if ((cmp = string.Compare(left.FromFileName, right.FromFileName, StringComparison.OrdinalIgnoreCase)) != 0) \n                                return cmp;\n                            return left.StartPosition.CompareTo(right.StartPosition);\n                        });\n                        foreach (var readFileOp in readFileOperList)\n                        {\n                            int position = (int)msBuffer.Position;\n                            readFileOp.Flush(openFile(readFileOp.FromFileName), null, null, msBuffer);\n                            readFileOp.bufferStartIndex = position;\n                        }\n\n                        //向新文件输出\n                        foreach (var tempOp in operList)\n                        {\n                            newCheckSum1 = tempOp.Flush(openFile(tempOp.FromFileName), r.BaseStream, msBuffer, tempFileStream, newCheckSum1);\n\n                            //计算更新进度\n                            if (part.NewFileLength > 0)\n                            {\n                                double curProc = 1.0 * tempFileStream.Position / part.NewFileLength;\n                                if (curProc - patchProc >= patchProcReportInverval)// || curProc >= 1 - patchProcReportInverval)\n                                {\n                                    this.OnTempFileUpdated(part, tempFileStream.Position);//更新进度改变\n                                    patchProc = curProc;\n                                }\n                            }\n                            else\n                            {\n                                if (tempFileStream.Position - patchProc > 1024 * 1024 * 10)\n                                {\n                                    this.OnTempFileUpdated(part, tempFileStream.Position);//更新进度改变\n                                    patchProc = tempFileStream.Position;\n                                }\n                            }\n                        }\n\n                        //重置缓冲区\n                        msBuffer.SetLength(0);\n                        preLoadByteCount = 0;\n                        operList.Clear();\n                        readFileOperList.Clear();\n                        if (cmd == 0) // 更新结束 这里是出口无误\n                        {\n                            break;\n                        }\n                    }\n\n                    if (op.OperType != 1 && op.Length >= msBuffer.Capacity) //还是大于的话 单独执行\n                    {\n                        newCheckSum1 = op.Flush(openFile(op.FromFileName), r.BaseStream, null, tempFileStream, newCheckSum1);\n                    }\n                    else //直接放进缓冲区里\n                    {\n                        op.Index = (ushort)operList.Count;\n                        operList.Add(op);\n                        switch (op.OperType)\n                        {\n                            case 0:\n                                int position = (int)msBuffer.Position;\n                                op.Flush(null, r.BaseStream, null, msBuffer);\n                                op.bufferStartIndex = position;\n                                break;\n\n                            case 1:\n                                continue;\n\n                            case 2:\n                                readFileOperList.Add(op);\n                                break;\n                        }\n                        preLoadByteCount += op.Length;\n                    }\n                }\n                msBuffer.Dispose();\n                msBuffer = null;\n                tempFileStream.Flush();\n                tempFileStream.SetLength(tempFileStream.Position);  //设置文件大小为当前长度\n                closeAllFiles();\n\n                this.OnVerifyNewChecksumBegin(part);\n                //tempFileStream.Seek(0, SeekOrigin.Begin);\n                //uint _newCheckSum1 = CheckSum.ComputeHash(tempFileStream, (int)tempFileStream.Length); //新生成文件的hash\n                VerifyCheckSum(part.NewChecksum, newCheckSum1, part.FileName, \"new\");\n                this.OnVerifyNewChecksumEnd(part);\n\n                this.OnTempFileClosed(part);\n            }\n            finally\n            {\n                closeAllFiles();\n            }\n        }\n\n        private class RebuildFileOperation\n        {\n            public RebuildFileOperation(byte operType)\n            {\n                this.OperType = operType;\n                this.bufferStartIndex = -1;\n            }\n            public byte OperType; //0-从补丁文件复制  1-填充字节  2-从原文件复制\n            public byte FillByte; //只有oper1时可用\n            public ushort Index; //操作索引\n            public int StartPosition; //只有oper2时可用 原文件起始坐标\n            public int Length; //输出区块长度\n            public int bufferStartIndex; //输出缓冲流的起始索引 执行后才有值\n            public string FromFileName;\n\n            public void Flush(Stream oldStream, Stream patchFileStream, Stream bufferStream, Stream newStream)\n            {\n                this.Flush(oldStream, patchFileStream, bufferStream, newStream, false, 0U);\n            }\n\n            public uint Flush(Stream oldStream, Stream patchFileStream, Stream bufferStream, Stream newStream, uint crc)\n            {\n                return this.Flush(oldStream, patchFileStream, bufferStream, newStream, true, crc);\n            }\n\n            private uint Flush(Stream oldStream, Stream patchFileStream, Stream bufferStream, Stream newStream, bool withCrc, uint crc)\n            {\n                Stream srcStream = null;\n                if (this.bufferStartIndex > -1) //使用缓冲流\n                {\n                    srcStream = bufferStream;\n                    srcStream.Seek(this.bufferStartIndex, SeekOrigin.Begin);\n                }\n                else //使用原始流\n                {\n                    switch (this.OperType)\n                    {\n                        case 0:\n                            srcStream = patchFileStream;\n                            break;\n\n                        case 2:\n                            srcStream = oldStream;\n                            srcStream.Seek(this.StartPosition, SeekOrigin.Begin);\n                            break;\n                    }\n                }\n\n                //执行更新\n                switch (this.OperType)\n                {\n                    case 0:\n                    case 2:\n                        if (withCrc)\n                        {\n                            crc = StreamUtils.MoveStreamWithCrc32(srcStream, newStream, this.Length, crc);\n                        }\n                        else\n                        {\n                            StreamUtils.CopyStream(srcStream, newStream, this.Length);\n                        }\n                        break;\n                    case 1:\n                        if (withCrc)\n                        {\n                            crc = StreamUtils.FillStreamWithCrc32(newStream, this.Length, this.FillByte, crc);\n                        }\n                        else\n                        {\n                            StreamUtils.FillStream(newStream, this.Length, this.FillByte);\n                        }\n                        break;\n                }\n                return crc;\n            }\n        }\n\n        private int CalcNewFileLength(PatchPartContext patchPart, BinaryReader reader)\n        {\n            patchPart.Action0 = patchPart.Action1 = patchPart.Action2 = 0;\n\n            int length = 0;\n            int cmd;\n            int blockLength;\n            while ((cmd = reader.ReadInt32()) != 0)\n            {\n                switch (((uint)cmd) >> 0x1C)\n                {\n                    case 0x08:\n                        blockLength = cmd & 0x0fffffff;\n                        reader.BaseStream.Seek(blockLength, SeekOrigin.Current); // skip len\n                        patchPart.Action0++;\n                        break;\n\n                    case 0x0c:\n                        blockLength = (cmd & 0x0fffff00) >> 8;\n                        patchPart.Action1++;\n                        break;\n\n                    default:\n                        blockLength = cmd;\n                        reader.BaseStream.Seek(4, SeekOrigin.Current); // skip content\n                        if (this.IsKMST1125Format == true)\n                        {\n                            // skip old file name\n                            var fromFile = ReadStringWithLength(reader, MAX_PATH);\n                            patchPart.DependencyFiles.Add(fromFile);\n                        }\n                        patchPart.Action2++;\n                        break;\n                }\n                length += blockLength;\n            }\n            return length;\n        }\n\n        private void EnsureDirExists(string fileName)\n        {\n            bool isDirectory;\n            EnsureDirExists(fileName, out isDirectory);\n        }\n\n        private void EnsureDirExists(string fileName, out bool isDirectory)\n        {\n            string ext = Path.GetExtension(fileName);\n            string dir;\n            if (string.IsNullOrEmpty(ext))\n            {\n                dir = fileName;\n                isDirectory = true;\n            }\n            else\n            {\n                dir = Path.GetDirectoryName(fileName);\n                isDirectory = false;\n            }\n            if (!Directory.Exists(dir))\n                Directory.CreateDirectory(dir);\n        }\n\n        private void VerifyCheckSum(uint expected, uint actual, string fileName, string reason)\n        {\n            if (expected != actual)\n            {\n                throw new Exception(string.Format(\"CheckSum Error on \\\"{0}\\\"({1}). (expected: 0x{2:x8}, actual: 0x{3:x8})\", fileName, reason, expected, actual));\n            }\n        }\n\n        #region eventhandler\n        protected virtual void OnPatchingStateChanged(PatchingEventArgs e)\n        {\n            if (this.PatchingStateChanged != null)\n            {\n                this.PatchingStateChanged(this, e);\n            }\n        }\n\n        private void OnPatchStart(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.PatchStart);\n            OnPatchingStateChanged(e);\n        }\n        private void OnVerifyOldChecksumBegin(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.VerifyOldChecksumBegin);\n            OnPatchingStateChanged(e);\n        }\n        private void OnVerifyOldChecksumEnd(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.VerifyOldChecksumEnd);\n            OnPatchingStateChanged(e);\n        }\n        private void OnVerifyNewChecksumBegin(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.VerifyNewChecksumBegin);\n            OnPatchingStateChanged(e);\n        }\n        private void OnVerifyNewChecksumEnd(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.VerifyNewChecksumEnd);\n            OnPatchingStateChanged(e);\n        }\n        private void OnTempFileCreated(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.TempFileCreated);\n            OnPatchingStateChanged(e);\n        }\n        private void OnTempFileUpdated(PatchPartContext part, long filelen)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.TempFileBuildProcessChanged, filelen);\n            OnPatchingStateChanged(e);\n        }\n        private void OnTempFileClosed(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.TempFileClosed);\n            OnPatchingStateChanged(e);\n        }\n        private void OnPrepareVerifyOldChecksumBegin(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.PrepareVerifyOldChecksumBegin);\n            OnPatchingStateChanged(e);\n        }\n        private void OnPrepareVerifyOldChecksumEnd(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.PrepareVerifyOldChecksumEnd);\n            OnPatchingStateChanged(e);\n        }\n        private void OnApplyFile(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.ApplyFile);\n            OnPatchingStateChanged(e);\n        }\n        private void OnFileSkipped(PatchPartContext part)\n        {\n            PatchingEventArgs e = new PatchingEventArgs(part, PatchingState.FileSkipped);\n            OnPatchingStateChanged(e);\n        }\n        #endregion\n\n        ~WzPatcher()\n        {\n            this.Dispose(false);\n        }\n\n        public void Close()\n        {\n            this.Dispose(true);\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (this.patchFile != null)\n                {\n                    this.patchFile.Close();\n                }\n            }\n\n            this.patchFile = null;\n            this.patchBlock = null;\n            this.inflateStream = null;\n        }\n\n        public class FileHash\n        {\n            public uint Hash { get; set; }\n            public uint ActualHash { get; set; }\n            public FileHashVerifyState VerifyState { get; set; }\n        }\n\n        public enum FileHashVerifyState\n        {\n            NotVerified = 0,\n            Verified,\n            HashNotMatch,\n            FileNotFound,\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/PictureBoxEx.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing SpineV2 = Spine.V2;\nusing WzComparerR2.Animation;\nusing WzComparerR2.Common;\nusing WzComparerR2.Config;\nusing WzComparerR2.Controls;\nusing WzComparerR2.Encoders;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2\n{\n    public class PictureBoxEx : AnimationControl\n    {\n        public PictureBoxEx() : base()\n        {\n            this.AutoAdjustPosition = true;\n            this.sbInfo = new StringBuilder();\n        }\n\n        public bool AutoAdjustPosition { get; set; }\n        public string PictureName { get; set; }\n        public bool ShowInfo { get; set; }\n\n        public override System.Drawing.Font Font\n        {\n            get { return base.Font; }\n            set\n            {\n                base.Font = value;\n                this.xnaFont?.Dispose();\n                this.xnaFont = new XnaFont(this.GraphicsDevice, value);\n            }\n        }\n\n        public XnaFont XnaFont\n        {\n            get\n            {\n                if (xnaFont == null && this.Font != null)\n                {\n                    this.xnaFont = new XnaFont(this.GraphicsDevice, this.Font);\n                }\n                return this.xnaFont;\n            }\n        }\n\n        private XnaFont xnaFont;\n        private SpriteBatchEx sprite;\n        private StringBuilder sbInfo;\n\n        public void ShowImage(Wz_Png png)\n        {\n            this.ShowImage(png, 0);\n        }\n\n        public void ShowImage(Wz_Png png, int page)\n        {\n            //添加到动画控件\n            var frame = new Animation.Frame()\n            {\n                Texture = png.ToTexture(page, this.GraphicsDevice),\n                Png = png,\n                Page = page,\n                Delay = 0,\n                Origin = Point.Zero,\n            };\n\n            var frameData = new Animation.FrameAnimationData();\n            frameData.Frames.Add(frame);\n\n            this.ShowAnimation(frameData);\n        }\n\n        public FrameAnimationData LoadVideo(Wz_Video wzVideo)\n        {\n            return new MaplestoryCanvasVideoLoader().Load(wzVideo, this.GraphicsDevice);\n        }\n\n        public FrameAnimationData LoadFrameAnimation(Wz_Node node, FrameAnimationCreatingOptions options = default)\n        {\n            return FrameAnimationData.CreateFromNode(node, this.GraphicsDevice, options, PluginBase.PluginManager.FindWz);\n        }\n\n        public ISpineAnimationData LoadSpineAnimation(Wz_Node node)\n        {\n            return this.LoadSpineAnimation(SpineLoader.Detect(node));\n        }\n\n        public ISpineAnimationData LoadSpineAnimation(SpineDetectionResult detectionResult)\n        {\n            if (!detectionResult.Success)\n                return null;\n            var textureLoader = new WzSpineTextureLoader(detectionResult.SourceNode.ParentNode, this.GraphicsDevice, PluginBase.PluginManager.FindWz);\n            // workaround for Map/Back/bossLimbo.img/spine/3/02_Passage_01_BgColor.skel, #266\n            textureLoader.EnableTextureMissingFallback = true;\n            if (detectionResult.Version == SpineVersion.V2)\n                return SpineAnimationDataV2.Create(detectionResult, textureLoader);\n            else if (detectionResult.Version == SpineVersion.V4)\n                return SpineAnimationDataV4.Create(detectionResult, textureLoader);\n            else\n                return null;\n        }\n\n        public void ShowAnimation(FrameAnimationData data)\n        {\n            this.ShowAnimation(new FrameAnimator(data));\n        }\n\n        public void ShowAnimation(ISpineAnimationData data)\n        {\n            this.ShowAnimation(data.CreateAnimator() as AnimationItem);\n        }\n\n        public void ShowAnimation(AnimationItem animator)\n        {\n            if (this.Items.Count > 0)\n            {\n                var itemsCopy = new List<AnimationItem>(this.Items);\n                this.Items.Clear();\n                foreach (var aniItem in itemsCopy)\n                {\n                    this.DisposeAnimationItem(aniItem);\n                }\n            }\n\n            this.Items.Add(animator);\n\n            if (this.AutoAdjustPosition)\n            {\n                this.AdjustPosition();\n            }\n\n            this.Invalidate();\n        }\n\n        public void AdjustPosition()\n        {\n            if (this.Items.Count <= 0)\n                return;\n\n            var animator = this.Items[0];\n\n            if (animator is FrameAnimator)\n            {\n                var aniItem = (FrameAnimator)animator;\n                var rect = aniItem.Data.GetBound();\n                aniItem.Position = new Point(-rect.Left, -rect.Top);\n            }\n            else if (animator is AnimationItem aniItem)\n            {\n                var rect = aniItem.Measure();\n                aniItem.Position = new Point(-rect.Left, -rect.Top);\n            }\n        }\n\n        public bool SaveAsGif(AnimationItem aniItem, string fileName, ImageHandlerConfig config, GifEncoder encoder, bool showOptions)\n        {\n            var rec = new AnimationRecoder(this.GraphicsDevice);\n            var cap = encoder.Compatibility;\n\n            rec.Items.Add(aniItem);\n            int length = rec.GetMaxLength();\n            int delay = Math.Max(cap.MinFrameDelay, config.MinDelay);\n            int[] timeline = null;\n            if (!cap.IsFixedFrameRate)\n            {\n                timeline = rec.GetGifTimeLine(delay, cap.MaxFrameDelay);\n            }\n\n            // calc available canvas area\n            rec.ResetAll();\n            Microsoft.Xna.Framework.Rectangle bounds = aniItem.Measure();\n            if (length > 0)\n            {\n                IEnumerable<int> delays = timeline?.Take(timeline.Length - 1)\n                    ?? Enumerable.Range(0, (int)Math.Ceiling(1.0 * length / delay) - 1);\n\n                foreach (var frameDelay in delays)\n                {\n                    rec.Update(TimeSpan.FromMilliseconds(frameDelay));\n                    var rect = aniItem.Measure();\n                    bounds = Microsoft.Xna.Framework.Rectangle.Union(bounds, rect);\n                }\n            }\n            bounds.Offset(aniItem.Position);\n\n            // customize clip/scale options\n            AnimationClipOptions clipOptions = new AnimationClipOptions()\n            {\n                StartTime = 0,\n                StopTime = length,\n                Left = bounds.Left,\n                Top = bounds.Top,\n                Right = bounds.Right,\n                Bottom = bounds.Bottom,\n                OutputWidth = bounds.Width,\n                OutputHeight = bounds.Height,\n            };\n\n            if (showOptions)\n            {\n                var frmOptions = new FrmGifClipOptions()\n                {\n                    ClipOptions = clipOptions,\n                    ClipOptionsNew = clipOptions,\n                };\n                if (frmOptions.ShowDialog() == DialogResult.OK)\n                {\n                    var clipOptionsNew = frmOptions.ClipOptionsNew;\n                    clipOptions.StartTime = clipOptionsNew.StartTime ?? clipOptions.StartTime;\n                    clipOptions.StopTime = clipOptionsNew.StopTime ?? clipOptions.StopTime;\n\n                    clipOptions.Left = clipOptionsNew.Left ?? clipOptions.Left;\n                    clipOptions.Top = clipOptionsNew.Top ?? clipOptions.Top;\n                    clipOptions.Right = clipOptionsNew.Right ?? clipOptions.Right;\n                    clipOptions.Bottom = clipOptionsNew.Bottom ?? clipOptions.Bottom;\n\n                    clipOptions.OutputWidth = clipOptionsNew.OutputWidth ?? (clipOptions.Right - clipOptions.Left);\n                    clipOptions.OutputHeight = clipOptionsNew.OutputHeight ?? (clipOptions.Bottom - clipOptions.Top);\n                }\n                else\n                {\n                    return false;\n                }\n            }\n\n            // validate params\n            bounds = new Rectangle(\n                clipOptions.Left.Value,\n                clipOptions.Top.Value,\n                clipOptions.Right.Value - clipOptions.Left.Value,\n                clipOptions.Bottom.Value - clipOptions.Top.Value\n                );\n            var targetSize = new Point(clipOptions.OutputWidth.Value, clipOptions.OutputHeight.Value);\n            var startTime = clipOptions.StartTime.Value;\n            var stopTime = clipOptions.StopTime.Value;\n\n            if (bounds.Width <= 0 || bounds.Height <= 0\n                || targetSize.X <= 0 || targetSize.Y <= 0\n                || startTime < 0\n                || stopTime - startTime <= 0)\n            {\n                return false;\n            }\n            length = stopTime - startTime;\n\n            // create output dir\n            string framesDirName = Path.Combine(Path.GetDirectoryName(fileName), Path.GetFileNameWithoutExtension(fileName) + \".frames\");\n            if (config.SavePngFramesEnabled && !Directory.Exists(framesDirName))\n            {\n                Directory.CreateDirectory(framesDirName);\n            }\n\n            // pre-render\n            rec.ResetAll();\n            switch (config.BackgroundType.Value)\n            {\n                default:\n                case ImageBackgroundType.Transparent:\n                    rec.BackgroundColor = Color.Transparent;\n                    break;\n\n                case ImageBackgroundType.Color:\n                    rec.BackgroundColor = System.Drawing.Color.FromArgb(255, config.BackgroundColor.Value).ToXnaColor();\n                    break;\n\n                case ImageBackgroundType.Mosaic:\n                    rec.BackgroundImage = MonogameUtils.CreateMosaic(GraphicsDevice,\n                        config.MosaicInfo.Color0.ToXnaColor(),\n                        config.MosaicInfo.Color1.ToXnaColor(),\n                        Math.Max(1, config.MosaicInfo.BlockSize));\n                    break;\n            }\n\n            // select encoder\n            encoder.Init(fileName, targetSize.X, targetSize.Y);\n\n            // pipeline functions\n            IEnumerable<Tuple<byte[], int>> MergeFrames(IEnumerable<Tuple<byte[], int>> frames)\n            {\n                byte[] prevFrame = null;\n                int prevDelay = 0;\n\n                foreach (var frame in frames)\n                {\n                    byte[] currentFrame = frame.Item1;\n                    int currentDelay = frame.Item2;\n\n                    if (prevFrame == null)\n                    {\n                        prevFrame = currentFrame;\n                        prevDelay = currentDelay;\n                    }\n                    else if (prevFrame.AsSpan().SequenceEqual(currentFrame.AsSpan()))\n                    {\n                        prevDelay += currentDelay;\n                    }\n                    else\n                    {\n                        yield return Tuple.Create(prevFrame, prevDelay);\n                        prevFrame = currentFrame;\n                        prevDelay = currentDelay;\n                    }\n                }\n\n                if (prevFrame != null)\n                {\n                    yield return Tuple.Create(prevFrame, prevDelay);\n                }\n            }\n\n            IEnumerable<int> RenderDelay()\n            {\n                int t = 0;\n                while (t < length)\n                {\n                    int frameDelay = Math.Min(length - t, delay);\n                    t += frameDelay;\n                    yield return frameDelay;\n                }\n            }\n\n            IEnumerable<int> ClipTimeline(int[] _timeline)\n            {\n                int t = 0;\n                for (int i = 0; ; i = (i + 1) % timeline.Length)\n                {\n                    var frameDelay = timeline[i];\n                    if (t < startTime)\n                    {\n                        if (t + frameDelay > startTime)\n                        {\n                            frameDelay = t + frameDelay - startTime;\n                            t = startTime;\n                        }\n                        else\n                        {\n                            t += frameDelay;\n                            continue;\n                        }\n                    }\n\n                    if (t + frameDelay < stopTime)\n                    {\n                        yield return frameDelay;\n                        t += frameDelay;\n                    }\n                    else\n                    {\n                        frameDelay = stopTime - t;\n                        yield return frameDelay;\n                        break;\n                    }\n                }\n            }\n\n            int prevTime = 0;\n            async Task<int> ApplyFrame(byte[] frameData, int frameDelay)\n            {\n                byte[] gifData = null;\n                if (cap.AlphaSupportMode != AlphaSupportMode.FullAlpha && config.BackgroundType.Value == ImageBackgroundType.Transparent)\n                {\n                    using (var rt2 = rec.GetGifTexture(config.BackgroundColor.Value.ToXnaColor(), config.MinMixedAlpha))\n                    {\n                        if (gifData == null)\n                        {\n                            gifData = new byte[frameData.Length];\n                        }\n                        rt2.GetData(gifData);\n                    }\n                }\n                else\n                {\n                    gifData = frameData;\n                }\n\n                var tasks = new List<Task>();\n\n                // save each frame as png\n                if (config.SavePngFramesEnabled)\n                {\n                    tasks.Add(Task.Run(() =>\n                    {\n                        string pngFileName = Path.Combine(framesDirName, $\"{prevTime}_{prevTime + frameDelay}.png\");\n                        GCHandle gcHandle = GCHandle.Alloc(frameData, GCHandleType.Pinned);\n                        try\n                        {\n                            using (var bmp = new System.Drawing.Bitmap(targetSize.X, targetSize.Y, targetSize.X * 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, gcHandle.AddrOfPinnedObject()))\n                            {\n                                bmp.Save(pngFileName, System.Drawing.Imaging.ImageFormat.Png);\n                            }\n                        }\n                        finally\n                        {\n                            gcHandle.Free();\n                        }\n                    }));\n                }\n\n                // append frame data to gif stream\n                tasks.Add(Task.Run(() =>\n                {\n                    // TODO: only for gif here?\n                    frameDelay = Math.Max(10, (int)(Math.Round(frameDelay / 10.0) * 10));\n\n                    GCHandle gcHandle = GCHandle.Alloc(frameData, GCHandleType.Pinned);\n                    try\n                    {\n                        encoder.AppendFrame(gcHandle.AddrOfPinnedObject(), frameDelay);\n                    }\n                    finally\n                    {\n                        gcHandle.Free();\n                    }\n                }));\n\n                await Task.WhenAll(tasks);\n                prevTime += frameDelay;\n                return prevTime;\n            }\n\n            async Task RenderJob(IProgressDialogContext context, CancellationToken cancellationToken)\n            {\n                bool isCompareAndMergeFrames = timeline == null && !cap.IsFixedFrameRate;\n\n                // build pipeline\n                IEnumerable<int> delayEnumerator = timeline == null ? RenderDelay() : ClipTimeline(timeline);\n                var step1 = delayEnumerator.TakeWhile(_ => !cancellationToken.IsCancellationRequested);\n                var frameRenderEnumerator = step1.Select(frameDelay =>\n                {\n                    rec.Draw();\n                    rec.Update(TimeSpan.FromMilliseconds(frameDelay));\n                    return frameDelay;\n                });\n                var step2 = frameRenderEnumerator.TakeWhile(_ => !cancellationToken.IsCancellationRequested);\n                var getFrameData = step2.Select(frameDelay =>\n                {\n                    using (var t2d = rec.GetPngTexture())\n                    {\n                        byte[] frameData = new byte[t2d.Width * t2d.Height * 4];\n                        t2d.GetData(frameData);\n                        return Tuple.Create(frameData, frameDelay);\n                    }\n                });\n                var step3 = getFrameData.TakeWhile(_ => !cancellationToken.IsCancellationRequested);\n                if (isCompareAndMergeFrames)\n                {\n                    var mergedFrameData = MergeFrames(step3);\n                    step3 = mergedFrameData.TakeWhile(_ => !cancellationToken.IsCancellationRequested);\n                }\n\n                var step4 = step3.Select(item => ApplyFrame(item.Item1, item.Item2));\n\n                // run pipeline\n                bool isPlaying = this.IsPlaying;\n                try\n                {\n                    this.IsPlaying = false;\n                    rec.Begin(bounds, targetSize);\n                    if (startTime > 0)\n                    {\n                        rec.Update(TimeSpan.FromMilliseconds(startTime));\n                    }\n                    context.ProgressMin = 0;\n                    context.ProgressMax = length;\n                    foreach (var task in step4)\n                    {\n                        int currentTime = await task;\n                        context.Progress = currentTime;\n                    }\n                }\n                catch (Exception ex)\n                {\n                    if (ex is AggregateException aggrEx && aggrEx.InnerExceptions.Count == 1)\n                    {\n                        context.Message = $\"Error: {aggrEx.InnerExceptions[0].Message}\";\n                    }\n                    else\n                    {\n                        context.Message = $\"Error: {ex.Message}\";\n                    }\n                    context.FullMessage = ex.ToString();\n                    throw;\n                }\n                finally\n                {\n                    rec.End();\n                    this.IsPlaying = isPlaying;\n                }\n            }\n\n            var dialogResult = ProgressDialog.Show(this.FindForm(), \"Exporting...\", \"Save animation file...\", true, false, RenderJob);\n            return dialogResult == DialogResult.OK;\n        }\n\n        public override AnimationItem GetItemAt(int x, int y)\n        {\n            //固定获取当前显示的物件 无论鼠标在哪\n            return this.Items.Count > 0 ? this.Items[0] : null;\n        }\n\n        protected override void Initialize()\n        {\n            base.Initialize();\n            this.sprite = new SpriteBatchEx(this.GraphicsDevice);\n        }\n\n        protected override void Update(TimeSpan elapsed)\n        {\n            base.Update(elapsed);\n        }\n\n        protected override void Draw()\n        {\n            base.Draw();\n\n            if (this.ShowInfo && this.XnaFont != null)\n            {\n                UpdateInfoText();\n                sprite.Begin();\n                sprite.DrawStringEx(this.XnaFont, this.sbInfo, Vector2.Zero, Color.Black);\n                sprite.End();\n            }\n        }\n\n        protected override void OnItemDragSave(AnimationItemEventArgs e)\n        {\n            var fileName = Path.GetTempFileName();\n\n            if ((e.Item as FrameAnimator)?.Data.Frames.Count == 1)\n            {\n                using (var bmp = (e.Item as FrameAnimator).Data.Frames[0].Png.ExtractPng())\n                {\n                    bmp.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);\n                }\n            }\n            else\n            {\n                //this.SaveAsGif(e.Item, fileName, ImageHandlerConfig.Default);\n                // this is too lag so we don't support dragging gifs!\n                return;\n            }\n            \n            var imgObj = new ImageDataObject(null, fileName);\n            this.DoDragDrop(imgObj, System.Windows.Forms.DragDropEffects.Copy);\n            e.Handled = true;\n        }\n\n        private void UpdateInfoText()\n        {\n            this.sbInfo.Clear();\n            if (this.Items.Count > 0)\n            {\n                var aniItem = this.Items[0];\n                int time = 0;\n                if (aniItem is FrameAnimator frameAni)\n                {\n                    time = frameAni.CurrentTime;\n                }\n                else if (aniItem is ISpineAnimator spineAni)\n                {\n                    time = spineAni.CurrentTime;\n                }\n                this.sbInfo.AppendFormat(\"pos: {0}, scale: {1:p0}, play: {2} / {3}\",\n                    aniItem.Position,\n                    base.GlobalScale,\n                    aniItem.Length <= 0 ? 0 : (time % aniItem.Length),\n                    aniItem.Length);\n            }\n        }\n\n        private void DisposeAnimationItem(AnimationItem animationItem)\n        {\n            switch (animationItem)\n            {\n                case FrameAnimator frameAni:\n                    if (frameAni.Data?.Frames != null)\n                    {\n                        foreach (var frame in frameAni.Data.Frames)\n                        {\n                            if (frame.Texture != null && !frame.Texture.IsDisposed)\n                            {\n                                frame.Texture.Dispose();\n                            }\n                        }\n                    }\n                    break;\n                case SpineAnimatorV2 spineV2:\n                    if (spineV2.Skeleton != null)\n                    {\n                        foreach(var slot in spineV2.Skeleton.Slots.Items)\n                        {\n                            var atlasRegion = (slot.Attachment switch\n                            {\n                                SpineV2.MeshAttachment mesh => mesh.RendererObject,\n                                SpineV2.RegionAttachment region => region.RendererObject,\n                                SpineV2.SkinnedMeshAttachment skinnedMesh => skinnedMesh.RendererObject,\n                                _ => null\n                            }) as SpineV2.AtlasRegion;\n                            if (atlasRegion?.page?.rendererObject is Texture2D texture && !texture.IsDisposed)\n                            {\n                                texture.Dispose();\n                            }\n                        }\n                    }\n                    break;\n                case SpineAnimatorV4 spineV4:\n                    if (spineV4.Skeleton != null)\n                    {\n                        foreach (var slot in spineV4.Skeleton.Slots.Items)\n                        {\n                            var atlasRegion = (slot.Attachment switch\n                            {\n                                Spine.MeshAttachment mesh => mesh.Region,\n                                Spine.RegionAttachment region => region.Region,\n                                _ => null\n                            }) as Spine.AtlasRegion;\n                            if (atlasRegion?.page?.rendererObject is Texture2D texture && !texture.IsDisposed)\n                            {\n                                texture.Dispose();\n                            }\n                        }\n                    }\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/PluginLoadContext.cs",
    "content": "﻿#if NET6_0_OR_GREATER\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Reflection;\nusing System.Runtime.Loader;\n\nnamespace WzComparerR2\n{\n    internal class PluginLoadContext : AssemblyLoadContext\n    {\n        public PluginLoadContext(string unmanagedDllFolder, string pluginPath) \n        {\n            this.assemblyResolver = new AssemblyDependencyResolver(pluginPath);\n            this.unmanagedDllResolver = new AssemblyDependencyResolver(unmanagedDllFolder);\n        }\n\n        private AssemblyDependencyResolver assemblyResolver;\n        private AssemblyDependencyResolver unmanagedDllResolver;\n\n        protected override Assembly Load(AssemblyName assemblyName)\n        {\n            string assemblyPath = this.assemblyResolver.ResolveAssemblyToPath(assemblyName);\n            if (assemblyPath != null)\n            {\n                return this.LoadFromAssemblyPath(assemblyPath);\n            }\n\n            return base.Load(assemblyName);\n        }\n\n        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)\n        {\n            string libraryPath = this.unmanagedDllResolver.ResolveUnmanagedDllToPath(unmanagedDllName);\n            if (libraryPath != null)\n            {\n                return this.LoadUnmanagedDllFromPath(libraryPath);\n            }\n\n            return IntPtr.Zero;\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "WzComparerR2/Program.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Windows.Forms;\nusing WzComparerR2.PluginBase;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Loader;\nusing System.Text;\n#endif\n\nnamespace WzComparerR2\n{\n    public class Program\n    {\n        [STAThread]\n        static void Main()\n        {\n            Application.EnableVisualStyles();\n            Application.SetCompatibleTextRenderingDefault(false);\n            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;\n            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;\n            Program.SetDllDirectory();\n#if NET6_0_OR_GREATER\n            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);\n            Dotnet6Patch.Patch();\n#endif\n            Program.StartMainForm();\n        }\n\n        public static string LibPath { get; private set; }\n        private static List<Assembly> loadedPluginAssemblies = new List<Assembly>();\n\n        private\n\n        /// <summary>\n        /// 这是程序入口无雾。\n        /// </summary>\n        static void StartMainForm()\n        {\n            //创建主窗体\n            var frm = new MainForm();\n            //加载插件\n            LoadPlugins(frm);\n            //加载配置文件并初始化插件\n            var cng = Config.ConfigManager.ConfigFile;\n            frm.PluginOnLoad();\n            PluginManager.PluginOnLoad();\n            //走你\n            Application.Run(frm);\n        }\n\n        static void LoadPlugins(PluginContextProvider provider)\n        {\n            var asmList = PluginManager.GetPluginFiles().Select(asmFile =>\n            {\n                try\n                {\n#if NET6_0_OR_GREATER\n                    var ctx = new PluginLoadContext(GetUnmanagedDllDirectory(), asmFile);\n                    return ctx.LoadFromAssemblyPath(asmFile);\n#else\n                    var asmName = AssemblyName.GetAssemblyName(asmFile);\n                    return Assembly.Load(asmName);\n#endif\n                }\n                catch (Exception ex)\n                {\n                    return null;\n                }\n            }).OfType<Assembly>().ToList();\n            loadedPluginAssemblies.AddRange(asmList);\n\n            var context = new PluginContext(provider);\n            foreach (var asm in asmList)\n            {\n                PluginManager.LoadPlugin(asm, context);\n            }\n        }\n\n        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)\n        {\n            Exception ex = e.ExceptionObject as Exception;\n            if (ex != null)\n            {\n                string logFile = Path.Combine(Application.StartupPath, \"error.log\");\n                try\n                {\n                    string content = DateTime.Now.ToString() + \"\\r\\n\" + ex.ToString() + \"\\r\\n\";\n                    File.AppendAllText(logFile, content);\n                }\n                catch\n                {\n                }\n            }\n        }\n\n        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)\n        {\n            foreach (var asm in loadedPluginAssemblies)\n            {\n                if (asm.FullName == args.Name)\n                {\n                    return asm;\n                }\n            }\n\n#if NET6_0_OR_GREATER\n            try\n            {\n                var assemblyName = new AssemblyName(args.Name);\n                string assemblyPath = Path.Combine(GetManagedDllDirectory(), assemblyName.Name + \".dll\");\n                if (File.Exists(assemblyPath))\n                {\n                    return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);\n                }\n            }\n            catch\n            {\n                return null;\n            }\n#endif\n            return null;\n        }\n\n        static void SetDllDirectory()\n        {\n            LibPath = GetUnmanagedDllDirectory();\n            SetDllDirectory(LibPath);\n\n            foreach (var dllName in Directory.GetFiles(LibPath, \"*.dll\"))\n            {\n                var handle = LoadLibrary(dllName);\n            }\n        }\n\n        // System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture requires .netfx 4.7.1, here we use env var instead.\n        static string GetManagedDllDirectory() => Path.Combine(Application.StartupPath, \"Lib\");\n        static string GetUnmanagedDllDirectory() => Path.Combine(Application.StartupPath, \"Lib\", RuntimeInformation.ProcessArchitecture switch\n        {\n            Architecture.X86 => \"x86\",\n            Architecture.X64 => \"x64\",\n            Architecture.Arm64 => \"ARM64\",\n            _ => null,\n        });\n\n        internal static T GetAsmAttr<T>()\n        {\n            object[] attr = typeof(WzComparerR2.Program).Assembly.GetCustomAttributes(typeof(T), true);\n            if (attr != null && attr.Length > 0)\n            {\n                return (T)attr[0];\n            }\n            return default(T);\n        }\n\n        private static string _appVersion;\n        internal static string ApplicationVersion => _appVersion ?? (_appVersion = GetAsmAttr<AssemblyInformationalVersionAttribute>()?.InformationalVersion\n                ?? GetAsmAttr<AssemblyFileVersionAttribute>()?.Version);\n\n        [DllImport(\"kernel32.dll\")]\n        static extern bool SetDllDirectory(string path);\n\n        [DllImport(\"kernel32.dll\")]\n        static extern IntPtr LoadLibrary(string path);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的常规信息通过以下\n// 特性集控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Kagamia Studio\")]\n[assembly: AssemblyProduct(\"WzComparerR2\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2011-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 使此程序集中的类型\n// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型，\n// 则将该类型上的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"5be749fb-87bc-49be-812a-6eababb9c55b\")]\n\n// 程序集的版本信息由下面四个值组成:\n//\n//      主版本\n//      次版本 \n//      内部版本号\n//      修订号\n//\n// 可以指定所有这些值，也可以使用“内部版本号”和“修订号”的默认值，\n// 方法是按如下所示使用“*”:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     此代码由工具生成。\n//     运行时版本:4.0.30319.42000\n//\n//     对此文件的更改可能会导致不正确的行为，并且如果\n//     重新生成代码，这些更改将会丢失。\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace WzComparerR2.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   一个强类型的资源类，用于查找本地化的字符串等。\n    /// </summary>\n    // 此类是由 StronglyTypedResourceBuilder\n    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。\n    // 若要添加或移除成员，请编辑 .ResX 文件，然后重新运行 ResGen\n    // (以 /str 作为命令选项)，或重新生成 VS 项目。\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"17.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   返回此类使用的缓存的 ResourceManager 实例。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"WzComparerR2.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   重写当前线程的 CurrentUICulture 属性，对\n        ///   使用此强类型资源类的所有资源查找执行重写。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap convex {\n            get {\n                object obj = ResourceManager.GetObject(\"convex\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap dir {\n            get {\n                object obj = ResourceManager.GetObject(\"dir\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap img {\n            get {\n                object obj = ResourceManager.GetObject(\"img\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap mp3 {\n            get {\n                object obj = ResourceManager.GetObject(\"mp3\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap num {\n            get {\n                object obj = ResourceManager.GetObject(\"num\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Open {\n            get {\n                object obj = ResourceManager.GetObject(\"Open\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Pause {\n            get {\n                object obj = ResourceManager.GetObject(\"Pause\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Play {\n            get {\n                object obj = ResourceManager.GetObject(\"Play\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap png {\n            get {\n                object obj = ResourceManager.GetObject(\"png\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap rawdata {\n            get {\n                object obj = ResourceManager.GetObject(\"rawdata\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Save {\n            get {\n                object obj = ResourceManager.GetObject(\"Save\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Stop {\n            get {\n                object obj = ResourceManager.GetObject(\"Stop\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap str {\n            get {\n                object obj = ResourceManager.GetObject(\"str\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap uol {\n            get {\n                object obj = ResourceManager.GetObject(\"uol\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap vector {\n            get {\n                object obj = ResourceManager.GetObject(\"vector\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap video {\n            get {\n                object obj = ResourceManager.GetObject(\"video\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"dir\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\dir4.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"img\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\dir3.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"mp3\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\mp3.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"num\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\num2.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Pause\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Pause.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Play\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Play.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"png\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\png.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Stop\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Stop.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"str\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\string.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"uol\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\uol.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"vector\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\vector.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Open\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Save\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Save.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"rawdata\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\rawdata.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"convex\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\convex.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"video\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\video.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2/QueryPerformance.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2\n{\n    public static class QueryPerformance\n    {\n        static QueryPerformance()\n        {\n            counter = 0;\n            IsSupposed = QueryPerformanceFrequency(ref counter);\n            if (!IsSupposed)\n                throw new Exception(\"QueryPerformance无法初始化。\");\n        }\n\n        [DllImport(\"kernel32.dll\")]\n        private extern static bool QueryPerformanceCounter(ref long lPerformanceCounter);\n        [DllImport(\"kernel32.dll\")]\n        private extern static bool QueryPerformanceFrequency(ref long lFrequency);\n\n        public static bool IsSupposed;\n        private static long counter;\n        private static long length;\n        private static long tempStart;\n\n        /// <summary>\n        /// 启动计时器，开始计时。\n        /// </summary>\n        public static void Start()\n        {\n            if (IsSupposed)\n            {\n                length = 0;\n                QueryPerformanceCounter(ref tempStart);\n            }\n        }\n\n        /// <summary>\n        /// 关闭计时器，结束计时。\n        /// </summary>\n        public static void End()\n        {\n            if (IsSupposed && length == 0)\n            {\n                QueryPerformanceCounter(ref length);\n                length -= tempStart;\n            }\n        }\n        /// <summary>\n        /// 返回上次开始结束计时的时间间隔，单位为秒。\n        /// </summary>\n        /// <returns></returns>\n        public static double GetLastInterval()\n        {\n            return (double)length / counter;\n        }\n\n        public static long GetLastCount()\n        {\n            return length;\n        }\n    }\n\n}\n"
  },
  {
    "path": "WzComparerR2/SoundPlayer/BassSoundPlayer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Timers;\nusing ManagedBass;\nusing System.IO;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2\n{\n    public class BassSoundPlayer : ISoundPlayer\n    {\n        public BassSoundPlayer()\n        {\n            volume = 100;\n            autoPlay = true;\n            loadedPlugin = new HashSet<int>();\n        }\n\n        private bool inited;\n        private bool autoPlay;\n        private int volume;\n        private bool loop;\n        \n        private int hStream;\n        private bool isDisposed;\n\n        private string playingSoundName;\n        private byte[] data;\n\n        private HashSet<int> loadedPlugin;\n\n        public string PlayingSoundName\n        {\n            get { return playingSoundName; }\n            set { playingSoundName = value; }\n        }\n\n        public byte[] Data\n        {\n            get { return data; }\n            set { data = value; }\n        }\n\n        public bool Inited\n        {\n            get { return inited; }\n        }\n\n        public bool Init()\n        {\n            if (!inited)\n            {\n                try\n                {\n                    Bass.Configure(Configuration.IncludeDefaultDevice, true);\n                    if (inited = Bass.Init(-1, 44100, DeviceInitFlags.Default, IntPtr.Zero))\n                    {\n                        if (Directory.Exists(Program.LibPath))\n                        {\n                            foreach (string file in Directory.GetFiles(Program.LibPath, \"bass*.dll\", SearchOption.AllDirectories))\n                            {\n                                int p = Bass.PluginLoad(file);\n                                if (p != 0)\n                                {\n                                    loadedPlugin.Add(p);\n                                }\n                            }\n                        }\n                    }\n                }\n                catch\n                {\n                }\n            }\n            return inited;\n        }\n\n        public Errors GetLastError()\n        {\n            return Bass.LastError;\n        }\n\n        public IEnumerable<string> GetPluginSupportedExt()\n        {\n            if (this.loadedPlugin == null || this.loadedPlugin.Count == 0)\n                yield break;\n            foreach (var p in this.loadedPlugin)\n            {\n                PluginInfo info = Bass.PluginGetInfo(p);\n                foreach (PluginFormat form in info.Formats)\n                {\n                    yield return form.Name + \"(\" + form.FileExtensions + \")\" + \"|\" + form.FileExtensions;\n                }\n            }\n        }\n\n        public void PreLoad(ISoundFile sound)\n        {\n            if (sound == null) return;\n            this.UnLoad();\n\n            try\n            {\n                hStream = Bass.CreateStream(sound.FileName, sound.StartPosition, sound.Length, BassFlags.Default);\n            }\n            catch\n            {\n                hStream = 0;\n            }\n\n            if (hStream != 0)\n            {\n                this.Volume = this.Volume;//调节音量到设定值\n                this.Loop = this.Loop;\n                if (this.autoPlay)\n                    this.Play();\n            }\n        }\n\n        public void PreLoad(byte[] data)\n        {\n            if (data == null) return;\n            this.UnLoad();\n\n            try\n            {\n                IntPtr pData = Marshal.UnsafeAddrOfPinnedArrayElement(data,0);\n                hStream = Bass.CreateStream(pData, 0, data.Length, BassFlags.Default);\n            }\n            catch\n            {\n                hStream = 0;\n            }\n\n            if (hStream != 0)\n            {\n                this.Volume = this.Volume;//调节音量到设定值\n                this.Loop = this.Loop;\n                this.data = data;\n                if (this.autoPlay)\n                    this.Play();\n            }\n            else\n            {\n                var lastErr = Bass.LastError;\n            }\n        }\n\n        public void UnLoad()\n        {\n            if (hStream != 0)\n            {\n                Bass.ChannelStop(hStream);\n                Bass.StreamFree(hStream);\n                this.data = null;\n            }\n        }\n\n        public void Play()\n        {\n            bool success = Bass.ChannelPlay(hStream, false);\n        }\n\n        public void Pause()\n        {\n            Bass.ChannelPause(hStream);\n        }\n\n        public void Resume()\n        {\n            Bass.ChannelPlay(hStream, false);\n        }\n\n        public void Stop()\n        {\n            Bass.ChannelStop(hStream);\n            Bass.ChannelSetPosition(hStream, 0);\n        }\n\n        public int Volume\n        {\n            get\n            {\n                return this.volume;\n            }\n            set\n            {\n                this.volume = Math.Min(Math.Max(value, 0), 100);\n                Bass.ChannelSetAttribute(hStream, ChannelAttribute.Volume, this.volume * 0.01f);\n            }\n        }\n\n        public double SoundPosition\n        {\n            get\n            {\n                if (this.hStream != 0)\n                    return Bass.ChannelBytes2Seconds(hStream, Bass.ChannelGetPosition(hStream));\n                else\n                    return 0d;\n            }\n            set\n            {\n                if (this.hStream != 0)\n                {\n                    double totalLen = this.SoundLength;\n                    value = Math.Min(Math.Max(value, 0), totalLen);\n                    Bass.ChannelSetPosition(hStream, Bass.ChannelSeconds2Bytes(hStream, value));\n                }\n            }\n        }\n\n        public double SoundLength\n        {\n            get\n            {\n                if (this.hStream != 0)\n                    return Bass.ChannelBytes2Seconds(hStream, Bass.ChannelGetLength(hStream));\n                else\n                    return 0d;\n            }\n        }\n\n        public void Dispose()\n        {\n            if (!isDisposed)\n            {\n                this.UnLoad();\n                Bass.Free();\n                if (this.loadedPlugin != null && this.loadedPlugin.Count > 0)\n                {\n                    bool success = Bass.PluginFree(0);\n                    this.loadedPlugin.Clear();\n                }\n                isDisposed = true;\n            }\n        }\n\n        public bool AutoPlay\n        {\n            get\n            {\n                return this.autoPlay;\n            }\n            set\n            {\n                this.autoPlay = value;\n            }\n        }\n\n        public bool Loop\n        {\n            get\n            {\n                return this.loop;\n            }\n            set\n            {\n                this.loop = value;\n                if (this.hStream != 0)\n                {\n                    if (this.loop)\n                        Bass.ChannelFlags(hStream, BassFlags.Loop, BassFlags.Loop);\n                    else\n                        Bass.ChannelFlags(hStream, BassFlags.Default, BassFlags.Loop);\n                }\n            }\n        }\n\n        public PlayState State\n        {\n            get\n            {\n                if (this.hStream != 0)\n                {\n                    PlaybackState active = Bass.ChannelIsActive(hStream);\n                    switch (active)\n                    {\n                        case PlaybackState.Stopped: return PlayState.Stopped;\n                        case PlaybackState.Playing: return PlayState.Playing;\n                        case PlaybackState.Paused: return PlayState.Paused;\n                        default: return PlayState.Stopped;\n                    }\n                }\n                else\n                {\n                    return PlayState.Stopped;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/SoundPlayer/CustomSoundFile.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2\n{\n    public class CustomSoundFile : ISoundFile\n    {\n        public CustomSoundFile(string fileName)\n            : this(fileName, 0, 0)\n        {\n        }\n\n        public CustomSoundFile(string fileName, int startPos, int length)\n        {\n            this.fileName = fileName;\n            this.startPosition = startPos;\n            this.length = length;\n        }\n\n        private string fileName;\n        private int startPosition;\n        private int length;\n\n        public string FileName\n        {\n            get { return this.fileName; }\n        }\n\n        public int StartPosition\n        {\n            get { return this.startPosition; }\n        }\n\n        public int Length\n        {\n            get { return this.length; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/SoundPlayer/ISoundFile.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2\n{\n    public interface ISoundFile\n    {\n        string FileName { get; }\n        int StartPosition { get; }\n        int Length { get; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/SoundPlayer/ISoundPlayer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2\n{\n    public interface ISoundPlayer : IDisposable\n    {\n        bool Inited { get; }\n        bool Init();\n        void PreLoad(ISoundFile sound);\n        void UnLoad();\n        void Play();\n        void Pause();\n        void Resume();\n        void Stop();\n        bool AutoPlay { get; set; }\n        int Volume { get; set; }\n        double SoundPosition { get; set; }\n        double SoundLength { get; }\n        PlayState State { get; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/SoundPlayer/PlayState.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2\n{\n    public enum PlayState\n    {\n        Stopped = 0,\n        Playing,\n        Paused\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/Updater.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Net.Http;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2\n{\n    public class Updater\n    {\n        private const string checkUpdateURL = \"https://api.github.com/repos/Kagamia/WzComparerR2/releases/latest\";\n\n        public Updater()\n        {\n            string appVersion = Program.ApplicationVersion;\n            if (TryParseApplicationVersion(appVersion, out var version))\n            {\n                this.CurrentVersion = version;\n                this.CurrentVersionString = $\"{version.Build}.{version.Revision}\";\n            }\n            else\n            {\n                this.CurrentVersion = default;\n                this.CurrentVersionString = appVersion;\n            }\n        }\n\n        public bool LatestReleaseFetched { get; private set; }\n        public bool UpdateAvailable { get; private set; }\n        public Version CurrentVersion { get; private set; }\n        public Version LatestVersion { get; private set; }\n        public string CurrentVersionString { get; private set; }\n        public string LatestVersionString { get; private set; }\n\n        public GithubReleaseResponse Release { get; private set; }\n        public GithubAsset Net462Asset { get; private set; }\n        public GithubAsset Net6Asset { get; private set; }\n        public GithubAsset Net8Asset { get; private set; }\n        public GithubAsset Net10Asset { get; private set; }\n\n        private readonly TimeSpan defaultRequestTimeout = TimeSpan.FromSeconds(15);\n\n        public async Task QueryUpdateAsync(CancellationToken cancellationToken = default)\n        {\n            // send request\n            using var client = new HttpClient();\n            client.Timeout = defaultRequestTimeout;\n            using var request = new HttpRequestMessage(HttpMethod.Get, checkUpdateURL);\n            request.Headers.Accept.ParseAdd(\"application/vnd.github+json\");\n            request.Headers.UserAgent.ParseAdd($\"WzComparerR2/{this.CurrentVersionString ?? \"1.0\"}\");\n            using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);\n            response.EnsureSuccessStatusCode();\n\n            // parse payload\n            using var responseStream = await response.Content.ReadAsStreamAsync();\n            using var jsonTextReader = new JsonTextReader(new StreamReader(responseStream));\n            var serializer = new JsonSerializer();\n            var release = serializer.Deserialize<GithubReleaseResponse>(jsonTextReader);\n\n            // reset all\n            this.LatestReleaseFetched = true;\n            this.Release = release;\n            this.Net462Asset = null;\n            this.Net6Asset = null;\n            this.Net8Asset = null;\n            this.Net10Asset = null;\n\n            // check version\n            if (TryParseBuildVersion(release.Name, out var releaseVer))\n            {\n                this.LatestVersion = releaseVer;\n                this.LatestVersionString = $\"{releaseVer.Build}.{releaseVer.Revision}\";\n                if (this.CurrentVersion != default)\n                {\n                    this.UpdateAvailable = (releaseVer.Build > this.CurrentVersion.Build)\n                        || (releaseVer.Build == this.CurrentVersion.Build && releaseVer.Revision > this.CurrentVersion.Revision);\n                }\n            }\n            else\n            {\n                this.LatestVersion = default;\n                this.LatestVersionString = release.Name;\n            }\n\n            // check assets\n            if (release.Assets != null)\n            {\n                foreach (var asset in release.Assets)\n                {\n                    if (asset.Name != null)\n                    {\n                        if (asset.Name.Contains(\"net6\"))\n                        {\n                            this.Net6Asset = asset;\n                        }\n                        else if (asset.Name.Contains(\"net8\"))\n                        {\n                            this.Net8Asset = asset;\n                        }\n                        else if (asset.Name.Contains(\"net10\"))\n                        {\n                            this.Net10Asset = asset;\n                        }\n                        else\n                        {\n                            this.Net462Asset = asset;\n                        }\n                    }\n                }\n            }\n        }\n\n        public async Task DownloadAssetAsync(GithubAsset asset, string fileName, OnProgressCallback onProgress = null, CancellationToken cancellationToken = default)\n        {\n            // send request\n            using var client = new HttpClient();\n            client.Timeout = defaultRequestTimeout;\n            using var request = new HttpRequestMessage(HttpMethod.Get, asset.BrowserDownloadUrl);\n            if (asset.ContentType != null)\n            {\n                request.Headers.Accept.ParseAdd(asset.ContentType);\n            }\n            request.Headers.UserAgent.ParseAdd($\"WzComparerR2/{this.CurrentVersionString ?? \"1.0\"}\");\n            using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);\n            response.EnsureSuccessStatusCode();\n\n            long fileSize = response.Content.Headers.ContentLength ?? asset.Size;\n            // copy to file\n            bool fileCreated = false;\n            try\n            {\n                using var responseStream = await response.Content.ReadAsStreamAsync();\n                using var fs = File.Create(fileName);\n                fileCreated = true;\n                byte[] buffer = new byte[16 * 1024];\n                long downloadedBytes = 0;\n                while (true)\n                {\n                    int len = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);\n                    if (len <= 0)\n                    {\n                        break;\n                    }\n                    fs.Write(buffer, 0, len);\n                    downloadedBytes += len;\n                    onProgress?.Invoke(downloadedBytes, fileSize);\n                }\n                await responseStream.CopyToAsync(fs, 16 * 1024, cancellationToken).ConfigureAwait(false);\n            }\n            catch\n            {\n                if (fileCreated && File.Exists(fileName))\n                {\n                    File.Delete(fileName);\n                }\n                throw;\n            }\n        }\n\n        private static bool TryParseBuildVersion(string releaseName, out Version result)\n        {\n            var m = Regex.Match(releaseName, @\"(\\d{8})\\.(\\d+)\");\n            if (m.Success \n                && int.TryParse(m.Groups[1].Value, out int build)\n                && int.TryParse(m.Groups[2].Value, out int revision))\n            {\n                result = new Version(0, 0, build, revision);\n                return true;\n            }\n\n            result = default;\n            return false;\n        }\n\n        private static bool TryParseApplicationVersion(string appVersion, out Version result)\n        {\n            return Version.TryParse(appVersion, out result);\n        }\n\n        public delegate void OnProgressCallback(long downloadedBytes, long totalBytes);\n\n        /// <see cref=\"https://docs.github.com/en/enterprise-cloud@latest/rest/releases/releases?apiVersion=2022-11-28#get-the-latest-release\"/>\n        public class GithubReleaseResponse\n        {\n            [JsonProperty(\"name\")]\n            public string Name { get; set; }\n            [JsonProperty(\"body\")]\n            public string Body { get; set; }\n            [JsonProperty(\"created_at\")]\n            public DateTime CreatedAt { get; set; }\n            [JsonProperty(\"assets\")]\n            public GithubAsset[] Assets { get; set; }\n        }\n\n        public class GithubAsset\n        {\n            [JsonProperty(\"name\")]\n            public string Name { get; set; }\n            [JsonProperty(\"label\")]\n            public string Label { get; set; }\n            [JsonProperty(\"content_type\")]\n            public string ContentType { get; set; }\n            [JsonProperty(\"browser_download_url\")]\n            public string BrowserDownloadUrl { get; set; }\n            [JsonProperty(\"size\")]\n            public long Size { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2/WzComparerR2.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <OutputType>WinExe</OutputType>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2</AssemblyName>\n    <RootNamespace>WzComparerR2</RootNamespace>\n    <IsPublishable>True</IsPublishable>\n    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>\n    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n    <LangVersion>latest</LangVersion>\n    <Nullable>disable</Nullable>\n    <ApplicationIcon>wcr2_256.ico</ApplicationIcon>\n    <GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(Platform) == 'AnyCPU'\">\n    <ApplicationManifest>app.manifest</ApplicationManifest>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n    <!-- patching dotnetbar on net6 -->\n    <PackageReference Include=\"Lib.Harmony\" Version=\"2.3.3\" />\n    <PackageReference Include=\"System.Text.Json\" Version=\"8.0.5\" />\n    <!-- upgrade indirect dependency of SharpDX to prevent too many runtime libraries -->\n    <PackageReference Include=\"Microsoft.NETCore.App\" Version=\"2.1.30\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Data\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Configuration\" />\n    <Reference Include=\"System.Net.Http\" />\n    <PackageReference Include=\"System.ValueTuple\" Version=\"4.6.1\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n    <EmbeddedResource Include=\"..\\WzComparerR2.Updater\\bin\\$(Configuration)\\$(TargetFramework)\\WzComparerR2.Updater.deps.json\" />\n    <EmbeddedResource Include=\"..\\WzComparerR2.Updater\\bin\\$(Configuration)\\$(TargetFramework)\\WzComparerR2.Updater.dll\" />\n    <EmbeddedResource Include=\"..\\WzComparerR2.Updater\\bin\\$(Configuration)\\$(TargetFramework)\\WzComparerR2.Updater.dll.config\" />\n    <EmbeddedResource Include=\"..\\WzComparerR2.Updater\\bin\\$(Configuration)\\$(TargetFramework)\\WzComparerR2.Updater.exe\" />\n    <EmbeddedResource Include=\"..\\WzComparerR2.Updater\\bin\\$(Configuration)\\$(TargetFramework)\\WzComparerR2.Updater.runtimeconfig.json\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <EmbeddedResource Include=\"..\\WzComparerR2.Updater\\bin\\$(Configuration)\\$(TargetFramework)\\WzComparerR2.Updater.exe\" />\n    <EmbeddedResource Include=\"..\\WzComparerR2.Updater\\bin\\$(Configuration)\\$(TargetFramework)\\WzComparerR2.Updater.exe.config\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Content Include=\"wcr2_256.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WzComparerR2.Common\\WzComparerR2.Common.csproj\" />\n    <ProjectReference Include=\"..\\WzComparerR2.WzLib\\WzComparerR2.WzLib.csproj\" />\n    <ProjectReference Include=\"..\\WzComparerR2.PluginBase\\WzComparerR2.PluginBase.csproj\" />\n    <ProjectReference Include=\"..\\WzComparerR2.Updater\\WzComparerR2.Updater.csproj\">\n      <Private>false</Private>\n      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\CharaSimResource\\CharaSimResource.csproj\" />\n    <PackageReference Include=\"MonoGame.Framework.WindowsDX\" Version=\"$(MonogameFrameworkVersion)\" />\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.4\" />\n    <PackageReference Include=\"SharpDX\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"ManagedBass\" Version=\"3.1.1\" />\n    <PackageReference Include=\"System.Resources.Extensions\" Version=\"$(SystemResourcesExtensionsVersion)\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Reference Include=\"DevComponents.DotNetBar2\">\n      <HintPath>..\\References\\DevComponents.DotNetBar2.dll</HintPath>\n    </Reference>\n    <Reference Include=\"spine-monogame\">\n      <HintPath>..\\References\\spine-monogame.dll</HintPath>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Update=\"Properties\\Resources.Designer.cs\">\n      <DesignTime>True</DesignTime>\n      <AutoGen>True</AutoGen>\n      <DependentUpon>Resources.resx</DependentUpon>\n    </Compile>\n  </ItemGroup>\n  <ItemGroup>\n    <EmbeddedResource Update=\"Properties\\Resources.resx\">\n      <Generator>ResXFileCodeGenerator</Generator>\n      <LastGenOutput>Resources.Designer.cs</LastGenOutput>\n    </EmbeddedResource>\n  </ItemGroup>\n  <Target Name=\"PostBuild\" AfterTargets=\"PostBuildEvent\" Condition=\"$([MSBuild]::IsOSPlatform('Windows'))\">\n    <Exec Command=\"xcopy &quot;$(ProjectDir)..\\References\\x86\\*&quot; &quot;$(TargetDir)Lib\\x86&quot; /Y /I\" />\n    <Exec Command=\"xcopy &quot;$(ProjectDir)..\\References\\x64\\*&quot; &quot;$(TargetDir)Lib\\x64&quot; /Y /I\" />\n    <Exec Command=\"xcopy &quot;$(ProjectDir)..\\References\\ARM64\\*&quot; &quot;$(TargetDir)Lib\\ARM64&quot; /Y /I\" />\n  </Target>\n</Project>"
  },
  {
    "path": "WzComparerR2/app.config",
    "content": "<?xml version=\"1.0\"?>\n<configuration>\n  <startup>\n    <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.6.2\"/>\n  </startup>\n  <runtime>\n    <assemblyBinding xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n      <probing privatePath=\"Lib;Plugin\\WzComparerR2.MapRender;Plugin\\WzComparerR2.Network\"/>\n    </assemblyBinding>\n  </runtime>\n</configuration>\n"
  },
  {
    "path": "WzComparerR2/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">\n  <asmv3:application>\n    <asmv3:windowsSettings xmlns=\"http://schemas.microsoft.com/SMI/2024/WindowsSettings\">\n      <supportedArchitectures>amd64 arm64</supportedArchitectures>\n    </asmv3:windowsSettings>\n  </asmv3:application>\n</assembly>\n"
  },
  {
    "path": "WzComparerR2.Avatar/Action.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Avatar\n{\n    public class Action\n    {\n        public string Name { get; set; }\n        public int Level { get; set; }\n        public bool Enabled { get; set; }\n        public int Order { get; set; }\n\n        public override string ToString()\n        {\n            return this.Name;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/ActionFrame.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.Avatar\n{\n    public class ActionFrame\n    {\n        public ActionFrame()\n        {\n        }\n\n        public ActionFrame(string action, int frame)\n        {\n            this.Action = action;\n            this.Frame = frame;\n        }\n\n        public string Action { get; set; }\n        public int? Frame { get; set; }\n        public int Delay { get; set; }\n        public int AbsoluteDelay\n        {\n            get { return Math.Abs(this.Delay); }\n        }\n\n        public bool? Face { get; set; }\n        public bool Flip { get; set; }\n        public Point Move { get; set; }\n        public int Rotate { get; set; }\n        public int RotateProp { get; set; }\n\n        //骑宠用特殊属性\n        public string ForceCharacterAction { get; set; }\n        public int? ForceCharacterActionFrameIndex { get; set; }\n        public string ForceCharacterFace { get; set; }\n        public int? ForceCharacterFaceFrameIndex { get; set; }\n        public bool ForceCharacterFaceHide { get; set; }\n        public bool ForceCharacterFlip { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/AvatarCanvas.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Collections.ObjectModel;\nusing System.Drawing;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.CharaSim;\n\nnamespace WzComparerR2.Avatar\n{\n    public class AvatarCanvas\n    {\n        public AvatarCanvas()\n        {\n            this.ZMap = new List<string>();\n            this.Actions = new List<Action>();\n            this.Emotions = new List<string>();\n            this.TamingActions = new List<string>();\n            this.Parts = new AvatarPart[18];\n            this.EarType = 0;\n            this.WeaponIndex = 0;\n        }\n\n        public List<string> ZMap { get; private set; }\n        public List<Action> Actions { get; private set; }\n        public List<string> Emotions { get; private set; }\n        public List<string> TamingActions { get; private set; }\n\n        public AvatarPart[] Parts { get; private set; }\n        public string ActionName { get; set; }\n        public string EmotionName { get; set; }\n        public string TamingActionName { get; set; }\n\n        public bool HairCover { get; set; }\n        public bool ShowHairShade { get; set; }\n\n        public int WeaponIndex { get; set; }\n        public int WeaponType { get; set; }\n        public int EarType { get; set; }\n\n        public bool LoadZ()\n        {\n            return LoadZ(PluginBase.PluginManager.FindWz(\"Base\\\\zmap.img\"));\n        }\n\n        public bool LoadZ(Wz_Node zMapNode)\n        {\n            if (zMapNode == null)\n            {\n                return false;\n            }\n\n            this.ZMap.Clear();\n            this.ZMap.Capacity = zMapNode.Nodes.Count;\n\n            //读取z层顺序\n            foreach (Wz_Node node in zMapNode.Nodes)\n            {\n                this.ZMap.Add(node.Text);\n            }\n\n            return true;\n        }\n\n        /// <summary>\n        /// 从00002000.img中加载全部动作名称。\n        /// </summary>\n        /// <returns></returns>\n        public bool LoadActions()\n        {\n            Wz_Node bodyNode = PluginBase.PluginManager.FindWz(\"Character\\\\00002000.img\");\n            if (bodyNode == null)\n            {\n                return false;\n            }\n\n            this.Actions.Clear();\n\n            foreach (Wz_Node actionNode in bodyNode.Nodes)\n            {\n                if (actionNode.Text != \"info\")\n                {\n                    var action = LoadAction(actionNode);\n                    this.Actions.AddRange(action);\n                }\n            }\n\n            for (int i = 0; i < this.Actions.Count; i++)\n            {\n                this.Actions[i].Order = i;\n            }\n\n            this.Actions.Sort((a0, a1) =>\n            {\n                int comp = a0.Level.CompareTo(a1.Level);\n                if (comp == 0)\n                {\n                    if (a0.Level == 0) //基础动作\n                    {\n                        int idx0 = Array.IndexOf(baseActions, a0.Name),\n                            idx1 = Array.IndexOf(baseActions, a1.Name);\n                        comp = idx0.CompareTo(idx1);\n                    }\n                    else\n                    {\n                        comp = a0.Order.CompareTo(a1.Order);\n                    }\n                }\n                return comp;\n            });\n\n            return true;\n        }\n\n        /// <summary>\n        /// 从00020000.img中加载表情名称。\n        /// </summary>\n        /// <returns></returns>\n        public bool LoadEmotions()\n        {\n            Wz_Node faceNode = this.Face != null ? this.Face.Node : PluginBase.PluginManager.FindWz(\"Character\\\\Face\\\\00020000.img\");\n            if (faceNode == null)\n            {\n                return false;\n            }\n\n            this.Emotions.Clear();\n\n            foreach (Wz_Node emotionNode in faceNode.Nodes)\n            {\n                if (emotionNode.Text != \"info\")\n                {\n                    this.Emotions.Add(emotionNode.Text);\n                }\n            }\n\n            return true;\n        }\n\n        public bool LoadTamingActions()\n        {\n            this.TamingActions.Clear();\n\n            Wz_Node tamingNode = this.Taming == null ? null : this.Taming.Node;\n\n            if (tamingNode == null)\n            {\n                return false;\n            }\n\n            foreach (Wz_Node actionNode in tamingNode.Nodes)\n            {\n                switch (actionNode.Text)\n                {\n                    case \"info\":\n                    case \"characterAction\":\n                    case \"characterEmotion\":\n                    case \"property\":\n                    case \"forcingItem\":\n                        break;\n\n                    default:\n                        this.TamingActions.Add(actionNode.Text);\n                        break;\n                }\n            }\n\n            return true;\n        }\n\n        public List<int> GetCashWeaponTypes()\n        {\n            List<int> types = new List<int>();\n            if (this.Weapon != null && this.Weapon.ID != null && Gear.GetGearType(this.Weapon.ID.Value) == GearType.cashWeapon)\n            {\n                foreach (var node in this.Weapon.Node.Nodes)\n                {\n                    int typeID;\n                    if (Int32.TryParse(node.Text, out typeID))\n                    {\n                        types.Add(typeID);\n                    }\n                }\n            }\n            types.Sort();\n            return types;\n        }\n\n        private IEnumerable<Action> LoadAction(Wz_Node actionNode)\n        {\n            if (actionNode.FindNodeByPath(\"0\") != null)\n            {\n                var action = LoadActionFromNode(actionNode, actionNode.Text);\n                if (action != null)\n                {\n                    action.Name = actionNode.Text;\n                    yield return action;\n                }\n            }\n            else\n            {\n                for (int i = 1; ; i++)\n                {\n                    var subActionNode = actionNode.FindNodeByPath(i.ToString());\n                    if (subActionNode == null)\n                    {\n                        break;\n                    }\n\n                    var action = LoadActionFromNode(subActionNode, actionNode.Text);\n                    if (action != null)\n                    {\n                        action.Name = actionNode.Text + \"\\\\\" + i;\n                        yield return action;\n                    }\n                }\n            }\n        }\n\n        private Action LoadActionFromNode(Wz_Node actionNode, string actionName)\n        {\n            Action act = new Action();\n            act.Name = actionName;\n\n            if (BaseActions.Contains(actionName)) //基础动作\n            {\n                act.Level = 0;\n            }\n            else\n            {\n                Wz_Node frameNode = actionNode.FindNodeByPath(\"0\");\n                if (frameNode == null) //有鬼\n                {\n                    return null;\n                }\n                if (frameNode.FindNodeByPath(\"action\") != null\n                    && frameNode.FindNodeByPath(\"frame\") != null) //引用动作\n                {\n                    act.Level = 2;\n                }\n                else //当成扩展动作\n                {\n                    act.Level = 1;\n                }\n            }\n\n            return act;\n        }\n\n        public AvatarPart AddPart(Wz_Node imgNode)\n        {\n            Wz_Node infoNode = imgNode.FindNodeByPath(\"info\");\n            if (infoNode == null)\n            {\n                return null;\n            }\n            AvatarPart part = new AvatarPart(imgNode);\n\n            var gearType = Gear.GetGearType(part.ID.Value);\n            switch (gearType)\n            {\n                case GearType.body: this.Body = part; break;\n                case GearType.head: this.Head = part; break;\n                case GearType.face:\n                case GearType.face2: this.Face = part; break;\n                case GearType.hair:\n                case GearType.hair2:\n                case GearType.hair3:\n                case GearType.hair4: this.Hair = part; break;\n                case GearType.cap: this.Cap = part; break;\n                case GearType.coat: this.Coat = part; break;\n                case GearType.longcoat: this.Longcoat = part; break;\n                case GearType.pants: this.Pants = part; break;\n                case GearType.shoes: this.Shoes = part; break;\n                case GearType.glove: this.Glove = part; break;\n                case GearType.shield:\n                case GearType.demonShield:\n                case GearType.soulShield:\n                case GearType.katara: this.SubWeapon = part; break;\n                case GearType.cape: this.Cape = part; break;\n                case GearType.shovel:\n                case GearType.pickaxe:\n                case GearType.cashWeapon: this.Weapon = part; break;\n                case GearType.earrings: this.Earrings = part; break;\n                case GearType.faceAccessory: this.FaceAccessory = part; break;\n                case GearType.eyeAccessory: this.EyeAccessory = part; break;\n                case GearType.taming:\n                case GearType.taming2:\n                case GearType.taming3:\n                case GearType.tamingChair: this.Taming = part; break;\n                case GearType.saddle: this.Saddle = part; break;\n                default:\n                    if (Gear.IsWeapon(gearType))\n                    {\n                        this.Weapon = part;\n                    }\n                    break;\n            }\n\n            return part;\n        }\n\n        /// <summary>\n        /// 获取角色动作的动画帧。\n        /// </summary>\n        public ActionFrame[] GetActionFrames(string actionName)\n        {\n            Action action = this.Actions.Find(act => act.Name == actionName);\n            if (action == null)\n            {\n                return new ActionFrame[0];\n            }\n\n            Wz_Node bodyNode = PluginBase.PluginManager.FindWz(\"Character\\\\00002000.img\");\n            if (action.Level == 2)\n            {\n                var actionNode = bodyNode.FindNodeByPath(action.Name);\n                if (actionNode == null)\n                {\n                    return new ActionFrame[0];\n                }\n\n                List<ActionFrame> frames = new List<ActionFrame>();\n                for (int i = 0; ; i++)\n                {\n                    var frameNode = actionNode.FindNodeByPath(i.ToString());\n                    if (frameNode == null)\n                    {\n                        break;\n                    }\n                    ActionFrame frame = new ActionFrame();\n                    frame.Action = frameNode.Nodes[\"action\"].GetValueEx<string>(null);\n                    frame.Frame = frameNode.Nodes[\"frame\"].GetValueEx<int>(0);\n                    LoadActionFrameDesc(frameNode, frame);\n                    frames.Add(frame);\n                }\n                return frames.ToArray();\n            }\n            else\n            {\n                Wz_Node actionNode = null;\n                if (this.Body != null)\n                {\n                    actionNode = this.Body.Node.FindNodeByPath(action.Name);\n                }\n                if (actionNode == null)\n                {\n                    actionNode = bodyNode.FindNodeByPath(action.Name);\n                }\n\n                List<ActionFrame> frames = new List<ActionFrame>();\n                frames.AddRange(LoadStandardFrames(actionNode, action.Name));\n                return frames.ToArray();\n            }\n        }\n\n        private ActionFrame GetActionFrame(string actionName, int frameIndex)\n        {\n            Action action = this.Actions.Find(act => act.Name == actionName);\n            if (action == null)\n            {\n                return null;\n            }\n\n            Wz_Node bodyNode = PluginBase.PluginManager.FindWz(\"Character\\\\00002000.img\");\n            if (action.Level == 2)\n            {\n                var frameNode = bodyNode.FindNodeByPath($@\"{action.Name}\\{frameIndex}\");\n                if (frameNode != null)\n                {\n                    ActionFrame frame = new ActionFrame();\n                    frame.Action = frameNode.Nodes[\"action\"].GetValueEx<string>(null);\n                    frame.Frame = frameNode.Nodes[\"frame\"].GetValueEx<int>(0);\n                    LoadActionFrameDesc(frameNode, frame);\n                    return frame;\n                }\n            }\n            else\n            {\n                Wz_Node actionNode = this.Body?.Node.FindNodeByPath(action.Name)\n                    ?? bodyNode.FindNodeByPath(action.Name);\n\n                var frameNode = actionNode?.Nodes[frameIndex.ToString()];\n                if (frameNode != null)\n                {\n                    var frame = LoadStandardFrame(frameNode);\n                    frame.Action = action.Name;\n                    frame.Frame = frameIndex;\n                    return frame;\n                }\n            }\n\n            return null;\n        }\n\n        public ActionFrame[] GetFaceFrames(string emotion)\n        {\n            List<ActionFrame> frames = new List<ActionFrame>();\n            if (this.Face != null)\n            {\n                if (emotion == \"default\")\n                {\n                    frames.Add(new ActionFrame() { Action = emotion });\n                }\n                else\n                {\n                    var actionNode = this.Face.Node.FindNodeByPath(emotion);\n                    frames.AddRange(LoadStandardFrames(actionNode, emotion));\n                }\n            }\n            return frames.ToArray();\n        }\n\n        private ActionFrame GetFaceFrame(string emotion, int frameIndex)\n        {\n            if (this.Face != null)\n            {\n                if (emotion == \"default\")\n                {\n                    return new ActionFrame() { Action = emotion };\n                }\n                else\n                {\n                    var frameNode = this.Face.Node.FindNodeByPath($@\"{emotion}\\{frameIndex}\");\n                    if (frameNode != null)\n                    {\n                        var frame = LoadStandardFrame(frameNode);\n                        frame.Action = emotion;\n                        frame.Frame = frameIndex;\n                        return frame;\n                    }\n                }\n            }\n\n            return null;\n        }\n\n        public ActionFrame[] GetTamingFrames(string action)\n        {\n            List<ActionFrame> frames = new List<ActionFrame>();\n            if (this.Taming != null)\n            {\n                var actionNode = this.Taming.Node.FindNodeByPath(action);\n                frames.AddRange(LoadStandardFrames(actionNode, action));\n            }\n            return frames.ToArray();\n        }\n\n        private ActionFrame GetTamingFrame(string action, int frameIndex)\n        {\n            var actionNode = this.Taming?.Node.Nodes[action]?.ResolveUol();\n            if (actionNode == null)\n            {\n                return null;\n            }\n\n            var frameNode = actionNode.Nodes[frameIndex.ToString()];\n            if (frameNode != null)\n            {\n                var frame = LoadStandardFrame(frameNode);\n                frame.Action = action;\n                frame.Frame = frameIndex;\n                return frame;\n            }\n\n            return null;\n        }\n\n        /// <summary>\n        /// 读取扩展属性。\n        /// </summary>\n        private void LoadActionFrameDesc(Wz_Node frameNode, ActionFrame actionFrame)\n        {\n            actionFrame.Delay = frameNode.Nodes[\"delay\"].GetValueEx<int>(120);\n            actionFrame.Flip = frameNode.Nodes[\"flip\"].GetValueEx<int>(0) != 0;\n            var faceNode = frameNode.Nodes[\"face\"];\n            if (faceNode != null)\n            {\n                actionFrame.Face = faceNode.GetValue<int>() != 0;\n            }\n            var move = frameNode.Nodes[\"move\"].GetValueEx<Wz_Vector>(null);\n            if (move != null)\n            {\n                actionFrame.Move = move;\n            }\n            actionFrame.RotateProp = frameNode.Nodes[\"rotateProp\"].GetValueEx<int>(0);\n            actionFrame.Rotate = frameNode.Nodes[\"rotate\"].GetValueEx<int>(0);\n\n            actionFrame.ForceCharacterAction = frameNode.Nodes[\"forceCharacterAction\"].GetValueEx<string>(null);\n            actionFrame.ForceCharacterActionFrameIndex = frameNode.Nodes[\"forceCharacterActionFrameIndex\"].GetValueEx<int>();\n            actionFrame.ForceCharacterFace = frameNode.Nodes[\"forceCharacterFace\"].GetValueEx<string>(null);\n            actionFrame.ForceCharacterFaceFrameIndex = frameNode.Nodes[\"forceCharacterFaceFrameIndex\"].GetValueEx<int>();\n            actionFrame.ForceCharacterFaceHide = frameNode.Nodes[\"forceCharacterFaceHide\"].GetValueEx<int>(0) != 0;\n            actionFrame.ForceCharacterFlip = frameNode.Nodes[\"forceCharacterFlip\"].GetValueEx<int>(0) != 0;\n        }\n\n        private IEnumerable<ActionFrame> LoadStandardFrames(Wz_Node actionNode, string action)\n        {\n            if (actionNode == null)\n            {\n                yield break;\n            }\n\n            actionNode = actionNode.ResolveUol();\n\n            if (actionNode == null)\n            {\n                yield break;\n            }\n\n            for (int i = 0; ; i++)\n            {\n                var frameNode = actionNode.FindNodeByPath(i.ToString());\n                if (frameNode == null)\n                {\n                    yield break;\n                }\n                var frame = LoadStandardFrame(frameNode);\n                frame.Action = action;\n                frame.Frame = i;\n                yield return frame;\n            }\n        }\n\n        private ActionFrame LoadStandardFrame(Wz_Node frameNode)\n        {\n            ActionFrame frame = new ActionFrame();\n            while (frameNode.Value is Wz_Uol)\n            {\n                frameNode = frameNode.GetValue<Wz_Uol>().HandleUol(frameNode);\n            }\n            LoadActionFrameDesc(frameNode, frame);\n            return frame;\n        }\n\n        /// <summary>\n        /// 计算角色骨骼层次结构。\n        /// </summary>\n        /// <param name=\"frame\"></param>\n        /// <returns></returns>\n        public Bone CreateFrame(int bodyFrame, int faceFrame, int tamingFrame)\n        {\n            ActionFrame bodyAction = null, faceAction = null, tamingAction = null;\n            string actionName = this.ActionName,\n                emotionName = this.EmotionName,\n                tamingActionName = this.TamingActionName;\n            bool bodyFlip = false;\n\n            //获取骑宠\n            if (this.Taming != null)\n            {\n                tamingAction = GetTamingFrame(tamingActionName, tamingFrame);\n\n                if (tamingAction != null)\n                {\n                    if (!string.IsNullOrEmpty(tamingAction.ForceCharacterAction)) //强制动作\n                    {\n                        actionName = tamingAction.ForceCharacterAction;\n                        bodyFrame = tamingAction.ForceCharacterActionFrameIndex ?? 0;\n                    }\n\n                    if (tamingAction.ForceCharacterFaceHide) //强制表情\n                    {\n                        emotionName = null;\n                    }\n                    else if (!string.IsNullOrEmpty(tamingAction.ForceCharacterFace))\n                    {\n                        emotionName = tamingAction.ForceCharacterFace;\n                        faceFrame = tamingAction.ForceCharacterFaceFrameIndex ?? 0;\n                    }\n\n                    if (tamingAction.ForceCharacterFlip)\n                    {\n                        bodyFlip = true;\n                    }\n                    else if (this.Taming.Node.FindNodeByPath(@\"info\\flip\").GetValueEx(0) != 0)\n                    {\n                        bodyFlip = true;\n                    }\n\n                    if (this.Taming.Node.FindNodeByPath(@\"info\\removeBody\").GetValueEx(0) != 0) //自动适用动作\n                    {\n                        actionName = \"hideBody\";\n                        bodyFrame = 0;\n                    }\n                }\n            }\n\n            if (!string.IsNullOrEmpty(actionName))\n            {\n                bodyAction = GetActionFrame(actionName, bodyFrame);\n\n                if (bodyAction != null && bodyFlip)\n                {\n                    bodyAction.Flip = true;\n                }\n            }\n\n            if (!string.IsNullOrEmpty(emotionName))\n            {\n                faceAction = GetFaceFrame(emotionName, faceFrame);\n            }\n\n            return CreateFrame(bodyAction, faceAction, tamingAction);\n        }\n\n        public Bone CreateFrame(ActionFrame bodyAction, ActionFrame faceAction, ActionFrame tamingAction)\n        {\n            //获取所有部件\n            Wz_Node[] playerNodes = LinkPlayerParts(bodyAction, faceAction);\n            Wz_Node[] tamingNodes = LinkTamingParts(tamingAction);\n\n            //根骨骼 作为角色原点\n            Bone bodyRoot = new Bone(\"@root\");\n            bodyRoot.Position = Point.Empty;\n            CreateBone(bodyRoot, playerNodes, bodyAction.Face);\n            SetBonePoperty(bodyRoot, BoneGroup.Character, bodyAction);\n\n            if (tamingNodes != null && tamingNodes.Length > 0)\n            {\n                //骑宠骨骼\n                Bone tamingRoot = new Bone(\"@root2\");\n                tamingRoot.Position = Point.Empty;\n                CreateBone(tamingRoot, tamingNodes);\n\n                //建立虚拟身体骨骼\n                Bone newRoot = new Bone(\"@rootAll\");\n                newRoot.Position = Point.Empty;\n                bodyRoot.Parent = newRoot;\n\n                //合并骨骼\n                for (int i = tamingRoot.Children.Count - 1; i >= 0; i--)\n                {\n                    var childBone = tamingRoot.Children[i];\n\n                    Bone bone = newRoot.FindChild(childBone.Name);\n                    if (bone != null) //翻转骨骼\n                    {\n                        RotateBone(newRoot, bone);\n                        bone.Name = \"@\" + bone.Name;\n                        childBone.Parent = newRoot;\n                        bone.Parent = childBone;\n                        bone.Position = Point.Empty;\n                    }\n                    else //直接添加\n                    {\n                        childBone.Parent = newRoot;\n                    }\n                }\n                newRoot.Skins.AddRange(tamingRoot.Skins);\n                return newRoot;\n            }\n\n            return bodyRoot;\n        }\n\n        private void SetBonePoperty(Bone bone, BoneGroup group, ActionFrame property)\n        {\n            bone.Group = group;\n            bone.Property = property;\n            foreach (var child in bone.Children)\n            {\n                SetBonePoperty(child, group, property);\n            }\n        }\n\n        private void RotateBone(Bone root, Bone childBone)\n        {\n            while (childBone.Parent != null && childBone.Parent != root)\n            {\n                var p = childBone.Parent;\n                var pp = p.Parent;\n                var cpos = childBone.Position;\n                var ppos = p.Position;\n\n                childBone.Position = new Point(cpos.X + ppos.X, cpos.Y + ppos.Y);\n                p.Position = new Point(-cpos.X, -cpos.Y);\n\n                childBone.Parent = pp;\n                p.Parent = childBone;\n            }\n        }\n\n        private void CreateBone(Bone root, Wz_Node[] frameNodes, bool? bodyFace = null)\n        {\n            bool face = true;\n\n            foreach (Wz_Node partNode in frameNodes)\n            {\n                Wz_Node linkPartNode = partNode;\n                while (linkPartNode.Value is Wz_Uol)\n                {\n                    linkPartNode = linkPartNode.GetValue<Wz_Uol>().HandleUol(linkPartNode);\n                }\n\n                foreach (Wz_Node childNode in linkPartNode.Nodes) //分析部件\n                {\n                    Wz_Node linkNode = childNode;\n                    while (linkNode?.Value is Wz_Uol uol)\n                    {\n                        linkNode = uol.HandleUol(linkNode);\n                    }\n                    if (linkNode == null)\n                    {\n                        continue;\n                    }\n                    if (childNode.Text == \"hairShade\")\n                    {\n                        linkNode = childNode.FindNodeByPath(\"0\");\n                        if (linkNode == null)\n                        {\n                            continue;\n                        }\n                    }\n                    if (linkNode.Value is Wz_Png)\n                    {\n                        //过滤纹理\n                        switch (childNode.Text)\n                        {\n                            case \"face\": if (!(bodyFace ?? face)) continue; break;\n                            case \"ear\": if (this.EarType != 1) continue; break;\n                            case \"lefEar\": if (this.EarType != 2) continue; break;\n                            case \"highlefEar\": if (this.EarType != 3) continue; break;\n                            case \"hairOverHead\":\n                            case \"backHairOverCape\":\n                            case \"backHair\": if (HairCover) continue; break;\n                            case \"hair\":\n                            case \"backHairBelowCap\": if (!HairCover) continue; break;\n                            case \"hairShade\": if (!ShowHairShade) continue; break;\n                            default:\n                                if (childNode.Text.StartsWith(\"weapon\"))\n                                {\n                                    //检查是否多武器颜色\n                                    if (linkNode.ParentNode.FindNodeByPath(\"weapon1\") != null)\n                                    {\n                                        //只追加限定武器\n                                        string weaponName = \"weapon\" + (this.WeaponIndex == 0 ? \"\" : this.WeaponIndex.ToString());\n                                        if (childNode.Text != weaponName)\n                                        {\n                                            continue;\n                                        }\n                                    }\n                                }\n                                break;\n                        }\n\n                        //读取纹理\n                        Skin skin = new Skin();\n                        skin.Name = childNode.Text;\n                        skin.Image = BitmapOrigin.CreateFromNode(linkNode, PluginBase.PluginManager.FindWz);\n                        var zNode = linkNode.FindNodeByPath(\"z\");\n                        if (zNode != null)\n                        {\n                            var val = zNode.Value;\n                            var zIndex = zNode.GetValueEx<int?>(null);\n                            if (zIndex != null)\n                            {\n                                skin.ZIndex = zIndex.Value;\n                            }\n                            else\n                            {\n                                skin.Z = zNode.GetValue<string>();\n                            }\n                        }\n\n                        //读取骨骼\n                        Wz_Node mapNode = linkNode.FindNodeByPath(\"map\");\n                        if (mapNode != null)\n                        {\n                            Bone parentBone = null;\n                            foreach (var map in mapNode.Nodes)\n                            {\n                                string mapName = map.Text;\n                                Point mapOrigin = map.GetValue<Wz_Vector>();\n\n                                if (mapName == \"muzzle\") //特殊处理 忽略\n                                {\n                                    continue;\n                                }\n\n                                if (parentBone == null) //主骨骼\n                                {\n                                    parentBone = AppendBone(root, null, skin, mapName, mapOrigin);\n                                }\n                                else //级联骨骼\n                                {\n                                    AppendBone(root, parentBone, skin, mapName, mapOrigin);\n                                }\n                            }\n                        }\n                        else\n                        {\n                            root.Skins.Add(skin);\n                        }\n                    }\n                    else\n                    {\n                        switch (childNode.Text)\n                        {\n                            case \"face\":\n                                face = Convert.ToInt32(childNode.Value) != 0;\n                                break;\n                        }\n                    }\n                }\n            }\n        }\n\n        private Bone AppendBone(Bone root, Bone parentBone, Skin skin, string mapName, Point mapOrigin)\n        {\n            Bone bone = root.FindChild(mapName);\n            bool exists;\n            if (bone == null) //创建骨骼\n            {\n                exists = false;\n                bone = new Bone(mapName);\n                bone.Position = mapOrigin;\n            }\n            else\n            {\n                exists = true;\n            }\n\n            if (parentBone == null) //主骨骼\n            {\n                if (!exists) //基准骨骼不存在 加到root\n                {\n                    parentBone = root;\n                    bone.Parent = parentBone;\n                    bone.Skins.Add(skin);\n                    skin.Offset = new Point(-mapOrigin.X, -mapOrigin.Y);\n                }\n                else //如果已存在 创建一个关节\n                {\n                    Bone bone0 = new Bone(\"@\" + bone.Name + \"_\" + skin.Name);\n                    bone0.Position = new Point(-mapOrigin.X, -mapOrigin.Y);\n                    bone0.Parent = bone;\n                    parentBone = bone0;\n                    bone0.Skins.Add(skin);\n                    skin.Offset = Point.Empty;\n                }\n                return parentBone;\n            }\n            else //级联骨骼\n            {\n                if (!exists)\n                {\n                    bone.Parent = parentBone;\n                    bone.Position = mapOrigin;\n                }\n                else //如果已存在 替换\n                {\n                    bone.Parent = parentBone;\n                    bone.Position = mapOrigin;\n                }\n\n                return null;\n            }\n        }\n\n        public BitmapOrigin DrawFrame(Bone bone)\n        {\n            var bmpLayers = this.CreateFrameLayers(bone);\n            //计算最大图像范围\n            Rectangle rect = Rectangle.Empty;\n            foreach (var layer in bmpLayers)\n            {\n                var newRect = new Rectangle(layer.OpOrigin, layer.Bitmap.Size);\n                rect = rect.Size.IsEmpty ? newRect : Rectangle.Union(rect, newRect);\n            }\n            rect = rect.Size.IsEmpty ? Rectangle.Empty : rect;\n\n            if (rect.IsEmpty)\n            {\n                return new BitmapOrigin();\n            }\n\n            //绘制图像\n            Bitmap bmp = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);\n            Graphics g = Graphics.FromImage(bmp);\n            foreach (var layer in bmpLayers)\n            {\n                g.DrawImage(layer.Bitmap, layer.OpOrigin.X - rect.X, layer.OpOrigin.Y - rect.Y);\n            }\n\n            g.Dispose();\n\n            return new BitmapOrigin(bmp, -rect.X, -rect.Y);\n        }\n\n        public BitmapOrigin[] CreateFrameLayers(Bone bone)\n        {\n            List<AvatarLayer> layers = GenerateLayer(bone);\n            layers.Sort((l0, l1) => l1.ZIndex.CompareTo(l0.ZIndex));\n\n            var bmpLayers = new BitmapOrigin[layers.Count];\n            for (int i = 0; i < bmpLayers.Length; i++)\n            {\n                var layer = layers[i];\n                bmpLayers[i] = new BitmapOrigin(layer.Bitmap, -layer.Position.X, -layer.Position.Y);\n            }\n            return bmpLayers;\n        }\n\n        private unsafe void TransformPixel(Bitmap src, Bitmap dst, Matrix mt)\n        {\n            var pxData1 = src.LockBits(new Rectangle(0, 0, src.Width, src.Height),\n                System.Drawing.Imaging.ImageLockMode.ReadOnly,\n                System.Drawing.Imaging.PixelFormat.Format32bppArgb);\n            var pxData2 = dst.LockBits(new Rectangle(0, 0, dst.Width, dst.Height),\n                System.Drawing.Imaging.ImageLockMode.WriteOnly,\n                System.Drawing.Imaging.PixelFormat.Format32bppArgb);\n\n            for (int y = 0; y < pxData1.Height; y++)\n            {\n                int* pSrc = (int*)((byte*)pxData1.Scan0 + y * pxData1.Stride);\n                for (int x = 0; x < pxData1.Width; x++, pSrc++)\n                {\n                    Point newPoint = new Point(\n                        x * mt.m11 + y * mt.m21 + mt.m31,\n                        x * mt.m12 + y * mt.m22 + mt.m32\n                    );\n                    int* pDst = (int*)((byte*)pxData2.Scan0 + newPoint.Y * pxData2.Stride + newPoint.X * 4);\n                    *pDst = *pSrc;\n                }\n            }\n\n            dst.UnlockBits(pxData2);\n            src.UnlockBits(pxData1);\n        }\n\n        private List<AvatarLayer> GenerateLayer(Bone bone)\n        {\n            var layers = new List<AvatarLayer>();\n\n            //计算角色原点，用于翻转偏移\n            var rootBone = bone.FindChild(\"@root\");\n            Point rootPos = Point.Empty;\n            {\n                var temp = rootBone;\n                while (temp != null)\n                {\n                    rootPos.Offset(temp.Position);\n                    temp = temp.Parent;\n                }\n            }\n\n            Action<Bone, Point> func = null;\n            func = (parent, pos) =>\n            {\n                pos.Offset(parent.Position);\n                var prop = parent.Property;\n\n                foreach (Skin skin in parent.Skins)\n                {\n                    var layer = new AvatarLayer();\n                    var bmp = skin.Image.Bitmap;\n                    var position = new Point(pos.X + skin.Offset.X - skin.Image.Origin.X,\n                        pos.Y + skin.Offset.Y - skin.Image.Origin.Y);\n\n                    //计算身体旋转和反转\n                    if (parent.Group == BoneGroup.Character && prop != null)\n                    {\n                        Bitmap bmp2;\n                        Rectangle rect2;\n                        if (RotateFlipImage(bmp, new Rectangle(position, bmp.Size),\n                            prop.Flip, prop.Rotate, prop.Move, rootPos,\n                            out bmp2, out rect2))\n                        {\n                            if (bmp2 != null)\n                            {\n                                bmp = bmp2;\n                            }\n                            position = rect2.Location;\n                        }\n                    }\n\n                    layer.Bitmap = bmp;\n                    layer.Position = position;\n                    if (!string.IsNullOrEmpty(skin.Z))\n                    {\n                        layer.ZIndex = this.ZMap.IndexOf(skin.Z);\n                        if (layer.ZIndex < 0)\n                        {\n                            layer.ZIndex = this.ZMap.Count;\n                        }\n                    }\n                    else\n                    {\n                        layer.ZIndex = (skin.ZIndex < 0) ? (this.ZMap.Count - skin.ZIndex) : (-1 - skin.ZIndex);\n                    }\n                    layers.Add(layer);\n                }\n\n                foreach (var child in parent.Children)\n                {\n                    func(child, pos);\n                }\n            };\n\n            func(bone, Point.Empty);\n            return layers;\n        }\n\n        private bool RotateFlipImage(Bitmap bmp, Rectangle rect, bool flip, int rotate, Point move, Point origin, out Bitmap newBmp, out Rectangle newRect)\n        {\n            bool changed = false;\n            newBmp = null;\n            rect.Offset(-origin.X, -origin.Y);\n\n            if (flip || rotate != 0) //重新绘制 旋转和镜像\n            {\n                Matrix mt;\n                switch (rotate)\n                {\n                    case 0:\n                        mt = Matrix.Identity;\n                        break;\n                    case 90:\n                        mt = new Matrix(0, 1, -1, 0, bmp.Height - 1, 0);\n                        rect = new Rectangle(-rect.Bottom, rect.X, bmp.Height, bmp.Width);\n                        break;\n                    case 180:\n                        mt = new Matrix(-1, 0, 0, -1, bmp.Width - 1, bmp.Height - 1);\n                        rect = new Rectangle(-rect.Right, -rect.Bottom, bmp.Width, bmp.Height);\n                        break;\n                    case 270:\n                        mt = new Matrix(0, -1, 1, 0, 0, bmp.Width - 1);\n                        rect = new Rectangle(rect.Y, -rect.Right, bmp.Height, bmp.Width);\n                        break;\n                    default:\n                        goto case 0;\n                }\n\n                if (flip)\n                {\n                    mt *= new Matrix(-1, 0, 0, 1, rect.Width - 1, 0);\n                    rect.X = -rect.Right;\n                }\n\n                newBmp = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);\n                TransformPixel(bmp, newBmp, mt);\n                changed = true;\n            }\n\n            if (move != Point.Empty)\n            {\n                rect.Offset(flip && rotate != 0 ? -move.X : move.X, move.Y);\n                changed = true;\n            }\n\n            if (changed)\n            {\n                rect.Offset(origin.X, origin.Y);\n                newRect = rect;\n                return true;\n            }\n            else\n            {\n                newBmp = null;\n                newRect = Rectangle.Empty;\n                return false;\n            }\n        }\n\n        private Wz_Node[] LinkPlayerParts(ActionFrame bodyAction, ActionFrame faceAction)\n        {\n            //寻找所有部件\n            List<Wz_Node> partNode = new List<Wz_Node>();\n\n            //链接人\n            if (this.Body != null && this.Head != null && bodyAction != null\n                && this.Body.Visible && this.Head.Visible)\n            {\n                //身体\n                Wz_Node bodyNode = FindBodyActionNode(bodyAction);\n                partNode.Add(bodyNode);\n\n                //计算面向\n                bool? face = bodyAction.Face; //扩展动作规定头部\n                if (face == null && bodyNode != null) //链接的body内规定\n                {\n                    Wz_Node propNode = bodyNode.FindNodeByPath(\"face\");\n                    if (propNode != null)\n                    {\n                        face = propNode.GetValue<int>(0) != 0;\n                    }\n                }\n\n                //脸饰附加属性\n                bool invisibleFace = false;\n                if (this.FaceAccessory != null && this.FaceAccessory.Visible)\n                {\n                    invisibleFace = this.FaceAccessory.Node.FindNodeByPath(@\"info\\invisibleFace\").GetValueEx(0) != 0;\n                }\n\n                //头部\n                var headNode = FindActionFrameNode(this.Head.Node, bodyAction);\n                if (headNode == null)\n                {\n                    string actName = this.GetHeadActionName(bodyAction.Action, face);\n                    if (actName != null)\n                    {\n                        ActionFrame headAction = new ActionFrame() { Action = actName };\n                        headNode = FindActionFrameNode(this.Head.Node, headAction);\n                    }\n                }\n                partNode.Add(headNode);\n\n                //脸\n                if (this.Face != null && this.Face.Visible && faceAction != null)\n                {\n                    if ((face ?? true) && !invisibleFace)\n                    {\n                        partNode.Add(FindActionFrameNode(this.Face.Node, faceAction));\n                    }\n                }\n                //毛\n                if (headNode != null && this.Hair != null && this.Hair.Visible)\n                {\n                    var hairNode = FindActionFrameNode(this.Hair.Node, bodyAction);\n                    if (hairNode == null)\n                    {\n                        string actName = this.GetHairActionName(bodyAction.Action, face);\n                        if (actName != null)\n                        {\n                            ActionFrame hairAction = new ActionFrame() { Action = actName, Frame = 0 };\n                            hairNode = FindActionFrameNode(this.Hair.Node, hairAction);\n                        }  \n                    }\n                    partNode.Add(hairNode);\n                }\n                //其他部件\n                for (int i = 4; i < 16; i++)\n                {\n                    var part = this.Parts[i];\n                    if (part != null && part.Visible)\n                    {\n                        if (i == 12 && Gear.GetGearType(part.ID.Value) == GearType.cashWeapon) //点装武器\n                        {\n                            var wpNode = part.Node.FindNodeByPath(this.WeaponType.ToString());\n                            partNode.Add(FindActionFrameNode(wpNode, bodyAction));\n                        }\n                        else if (i == 14) //脸\n                        {\n                            if (face ?? true)\n                            {\n                                partNode.Add(FindActionFrameNode(part.Node, faceAction));\n                            }\n                        }\n                        else //其他部件\n                        {\n                            partNode.Add(FindActionFrameNode(part.Node, bodyAction));\n                        }\n                    }\n                }\n            }\n\n            partNode.RemoveAll(node => node == null);\n\n            return partNode.ToArray();\n        }\n\n        private Wz_Node[] LinkTamingParts(ActionFrame tamingAction)\n        {\n            List<Wz_Node> partNode = new List<Wz_Node>();\n\n            //链接马\n            if (this.Taming != null && this.Taming.Visible && tamingAction != null)\n            {\n                partNode.Add(FindActionFrameNode(this.Taming.Node, tamingAction));\n                if (this.Saddle != null && this.Saddle.Visible)\n                {\n                    var saddleNode = this.Saddle.Node.FindNodeByPath(false, this.Taming.ID.ToString());\n                    partNode.Add(FindActionFrameNode(saddleNode, tamingAction));\n                }\n            }\n\n            partNode.RemoveAll(node => node == null);\n\n            return partNode.ToArray();\n        }\n\n        private Wz_Node FindBodyActionNode(ActionFrame actionFrame)\n        {\n            Wz_Node actionNode = null;\n            if (this.Body != null)\n            {\n                actionNode = this.Body.Node.FindNodeByPath(actionFrame.Action);\n            }\n            if (actionNode == null)\n            {\n                Wz_Node bodyNode = PluginBase.PluginManager.FindWz(\"Character\\\\00002000.img\");\n                actionNode = bodyNode.FindNodeByPath(actionFrame.Action);\n            }\n            if (actionNode != null)\n            {\n                actionNode = actionNode.FindNodeByPath(actionFrame.Frame.ToString());\n            }\n            return actionNode;\n        }\n\n        private Wz_Node FindActionFrameNode(Wz_Node parent, ActionFrame actionFrame)\n        {\n            if (parent == null || actionFrame == null)\n            {\n                return null;\n            }\n            var actionNode = parent;\n            foreach (var path in new[] { actionFrame.Action, actionFrame.Frame.ToString() })\n            {\n                if (actionNode != null && !string.IsNullOrEmpty(path))\n                {\n                    actionNode = actionNode.FindNodeByPath(path);\n\n                    //处理uol\n                    Wz_Uol uol;\n                    while ((uol = actionNode.GetValueEx<Wz_Uol>(null)) != null)\n                    {\n                        actionNode = uol.HandleUol(actionNode);\n                    }\n                }\n            }\n\n            return actionNode;\n        }\n\n        private string GetHeadActionName(string bodyAction, bool? face)\n        {\n            if (bodyAction.StartsWith(\"PB\") && (face ?? false) == false)\n            {\n                return null;\n            }\n\n            if (bodyAction.StartsWith(\"PVPA8\"))\n            {\n                return null;\n            }\n\n            if (face != null)\n            {\n                return (face ?? false) ? \"front\" : \"back\";\n            }\n\n            return null;\n        }\n\n        private string GetHairActionName(string bodyAction, bool? face)\n        {\n            if (bodyAction == \"hide\" || bodyAction == \"blink\" || bodyAction.EndsWith(\"Blink\"))\n            {\n                return null;\n            }\n            if (bodyAction.StartsWith(\"PB\") && (face ?? false) == false)\n            {\n                return null;\n            }\n            if (bodyAction.StartsWith(\"create\"))\n            {\n                return null;\n            }\n            if (bodyAction.EndsWith(\"prone\") && (face ?? false))\n            {\n                return \"prone\";\n            }\n            if (bodyAction.EndsWith(\"proneStab\") && (face ?? false))\n            {\n                return \"proneStab\";\n            }\n            if (face != null)\n            {\n                return face.Value ? \"stand1\" : \"ladder\";\n            }\n            return null;\n        }\n\n        #region parts\n        /// <summary>\n        /// 身体\n        /// </summary>\n        public AvatarPart Body\n        {\n            get { return this.Parts[0]; }\n            set { this.Parts[0] = value; }\n        }\n\n        /// <summary>\n        /// 头部\n        /// </summary>\n        public AvatarPart Head\n        {\n            get { return this.Parts[1]; }\n            set { this.Parts[1] = value; }\n        }\n\n        /// <summary>\n        /// 脸部\n        /// </summary>\n        public AvatarPart Face\n        {\n            get { return this.Parts[2]; }\n            set { this.Parts[2] = value; }\n        }\n\n        /// <summary>\n        /// 头发\n        /// </summary>\n        public AvatarPart Hair\n        {\n            get { return this.Parts[3]; }\n            set { this.Parts[3] = value; }\n        }\n\n        /// <summary>\n        /// 帽子\n        /// </summary>\n        public AvatarPart Cap\n        {\n            get { return this.Parts[4]; }\n            set { this.Parts[4] = value; }\n        }\n\n        /// <summary>\n        /// 上衣\n        /// </summary>\n        public AvatarPart Coat\n        {\n            get { return this.Parts[5]; }\n            set { this.Parts[5] = value; }\n        }\n\n        /// <summary>\n        /// 套装\n        /// </summary>\n        public AvatarPart Longcoat\n        {\n            get { return this.Parts[6]; }\n            set { this.Parts[6] = value; }\n        }\n\n        /// <summary>\n        /// 胖次\n        /// </summary>\n        public AvatarPart Pants\n        {\n            get { return this.Parts[7]; }\n            set { this.Parts[7] = value; }\n        }\n\n        /// <summary>\n        /// 鞋子\n        /// </summary>\n        public AvatarPart Shoes\n        {\n            get { return this.Parts[8]; }\n            set { this.Parts[8] = value; }\n        }\n\n        /// <summary>\n        /// 手套\n        /// </summary>\n        public AvatarPart Glove\n        {\n            get { return this.Parts[9]; }\n            set { this.Parts[9] = value; }\n        }\n\n        /// <summary>\n        /// 盾牌\n        /// </summary>\n        public AvatarPart SubWeapon\n        {\n            get { return this.Parts[10]; }\n            set { this.Parts[10] = value; }\n        }\n\n        /// <summary>\n        /// 披风\n        /// </summary>\n        public AvatarPart Cape\n        {\n            get { return this.Parts[11]; }\n            set { this.Parts[11] = value; }\n        }\n\n        /// <summary>\n        /// 武器\n        /// </summary>\n        public AvatarPart Weapon\n        {\n            get { return this.Parts[12]; }\n            set { this.Parts[12] = value; }\n        }\n\n        /// <summary>\n        /// 耳环\n        /// </summary>\n        public AvatarPart Earrings\n        {\n            get { return this.Parts[13]; }\n            set { this.Parts[13] = value; }\n        }\n\n        /// <summary>\n        /// 脸饰\n        /// </summary>\n        public AvatarPart FaceAccessory\n        {\n            get { return this.Parts[14]; }\n            set { this.Parts[14] = value; }\n        }\n\n        /// <summary>\n        /// 眼饰\n        /// </summary>\n        public AvatarPart EyeAccessory\n        {\n            get { return this.Parts[15]; }\n            set { this.Parts[15] = value; }\n        }\n\n        /// <summary>\n        /// 骑宠\n        /// </summary>\n        public AvatarPart Taming\n        {\n            get { return this.Parts[16]; }\n            set { this.Parts[16] = value; }\n        }\n\n        /// <summary>\n        /// 鞍子\n        /// </summary>\n        public AvatarPart Saddle\n        {\n            get { return this.Parts[17]; }\n            set { this.Parts[17] = value; }\n        }\n\n        #endregion\n\n        #region statics\n\n        private static readonly string[] baseActions = new[]{\n            \"walk1\", \"walk2\", \"stand1\", \"stand2\", \"alert\",\n            \"swingO1\", \"swingO2\", \"swingO3\", \"swingOF\",\n            \"swingT1\", \"swingT2\", \"swingT3\", \"swingTF\",\n            \"swingP1\", \"swingP2\", \"swingPF\",\n            \"stabO1\", \"stabO2\", \"stabOF\", \"stabT1\", \"stabT2\", \"stabTF\",\n            \"shoot1\", \"shoot2\", \"shootF\",\n            \"proneStab\", \"prone\",\n            \"heal\", \"fly\", \"jump\", \"sit\", \"ladder\", \"rope\"\n        };\n\n        public static readonly ReadOnlyCollection<string> BaseActions = new ReadOnlyCollection<string>(baseActions);\n        #endregion\n\n        private class AvatarLayer\n        {\n            public Bitmap Bitmap { get; set; }\n            public Point Position { get; set; }\n            public int ZIndex { get; set; }\n        }\n\n        private struct Matrix\n        {\n            public Matrix(int m11, int m12, int m21, int m22, int m31, int m32)\n            {\n                this.m11 = m11;\n                this.m12 = m12;\n                this.m21 = m21;\n                this.m22 = m22;\n                this.m31 = m31;\n                this.m32 = m32;\n            }\n            public int m11, m12, m21, m22, m31, m32;\n\n            public static Matrix Identity\n            {\n                get { return new Matrix(1, 0, 0, 1, 0, 0); }\n            }\n\n            public static Matrix operator *(Matrix mt1, Matrix mt2)\n            {\n                return new Matrix(\n                    mt1.m11 * mt2.m11 + mt1.m12 * mt2.m21,\n                    mt1.m11 * mt2.m12 + mt1.m12 * mt2.m22,\n                    mt1.m21 * mt2.m11 + mt1.m22 * mt2.m21,\n                    mt1.m21 * mt2.m12 + mt1.m22 * mt2.m22,\n                    mt1.m31 * mt2.m11 + mt1.m32 * mt2.m21 + mt2.m31,\n                    mt1.m31 * mt2.m12 + mt1.m32 * mt2.m22 + mt2.m32);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/AvatarPart.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing WzComparerR2.WzLib;\nusing System.Text.RegularExpressions;\n\nnamespace WzComparerR2.Avatar\n{\n    public class AvatarPart\n    {\n        public AvatarPart(Wz_Node node)\n        {\n            this.Node = node;\n            this.Visible = true;\n            this.LoadInfo();\n        }\n\n        public Wz_Node Node { get; private set; }\n        public string ISlot { get; private set; }\n        public BitmapOrigin Icon { get; private set; }\n        public bool Visible { get; set; }\n        public int? ID { get; private set; }\n\n        private void LoadInfo()\n        {\n            var m = Regex.Match(Node.Text, @\"^(\\d+)\\.img$\");\n            if (m.Success)\n            {\n                this.ID = Convert.ToInt32(m.Result(\"$1\"));\n            }\n\n            Wz_Node infoNode = this.Node.FindNodeByPath(\"info\");\n            if (infoNode == null)\n            {\n                return;\n            }\n\n            foreach (var node in infoNode.Nodes)\n            {\n                switch (node.Text)\n                {\n                    case \"islot\":\n                        this.ISlot = node.GetValue<string>();\n                        break;\n\n                    case \"icon\":\n                        this.Icon = BitmapOrigin.CreateFromNode(node, PluginBase.PluginManager.FindWz);\n                        break;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/Bone.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.Avatar\n{\n    public class Bone\n    {\n        public Bone(string name)\n        {\n            this.Name = name;\n            this.Children = new List<Bone>();\n            this.Skins = new List<Skin>();\n        }\n\n        public string Name { get; set; }\n        public Point Position { get; set; }\n        public List<Bone> Children { get; private set; }\n        public List<Skin> Skins { get; private set; }\n\n        public BoneGroup Group { get; set; }\n        public ActionFrame Property { get; set; }\n\n        private Bone parent;\n        public Bone Parent\n        {\n            get { return this.parent; }\n            set\n            {\n                Bone oldParent = this.parent;\n                if (oldParent != value)\n                {\n                    if (oldParent != null)\n                    {\n                        oldParent.Children.Remove(this);\n                    }\n                    if (value != null)\n                    {\n                        value.Children.Add(this);\n                    }\n\n                    this.parent = value;\n                }\n            }\n        }\n\n        public Bone FindChild(string name)\n        {\n            foreach (Bone bone in Children)\n            {\n                if (bone.Name == name) return bone;\n                if (bone.Children.Count > 0)\n                {\n                    Bone c = bone.FindChild(name);\n                    if (c != null) return c;\n                }\n            }\n            return null;\n        }\n    }\n\n    public enum BoneGroup\n    {\n        Unknown = 0,\n        Character,\n        Taming\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/Entry.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing DevComponents.DotNetBar;\nusing DevComponents.Editors;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.Avatar.UI;\nusing System.Linq;\n\nnamespace WzComparerR2.Avatar\n{\n    public class Entry : PluginEntry\n    {\n        public Entry(PluginContext context)\n            : base(context)\n        {\n        }\n\n        protected override void OnLoad()\n        {\n            var f = new AvatarForm();\n            f.PluginEntry = this;\n            var tabCtrl = f.GetTabPanel();\n            Context.AddTab(f.Text, tabCtrl);\n            Context.SelectedNode1Changed += f.OnSelectedNode1Changed;\n            Context.WzClosing += f.OnWzClosing;\n            this.Tab = tabCtrl.TabItem;\n        }\n\n        public SuperTabItem Tab { get; private set; }\n\n        public void btnSetting_Click(object sender, EventArgs e)\n        {\n            AvatarCanvas canvas = new AvatarCanvas();\n            canvas.LoadZ();\n            canvas.LoadActions();\n            canvas.LoadEmotions();\n\n            /*\n            cmbAction.Items.Clear();\n            foreach (var action in canvas.Actions)\n            {\n                ComboItem cmbItem = new ComboItem(action.Name);\n                switch (action.Level)\n                {\n                    case 0:\n                        cmbItem.FontStyle = System.Drawing.FontStyle.Bold;\n                        cmbItem.ForeColor = Color.Indigo;\n                        break;\n\n                    case 1:\n                        cmbItem.ForeColor = Color.Indigo;\n                        break;\n                }\n                cmbAction.Items.Add(cmbItem);\n            }*/\n\n            canvas.ActionName = \"stand1\";\n            canvas.EmotionName = \"shine\";\n            canvas.TamingActionName = \"stand1\";\n            AddPart(canvas, \"Character\\\\00002000.img\");\n            AddPart(canvas, \"Character\\\\00012000.img\");\n            AddPart(canvas, \"Character\\\\Face\\\\00020000.img\");\n            AddPart(canvas, \"Character\\\\Hair\\\\00030000.img\");\n            AddPart(canvas, \"Character\\\\Coat\\\\01040036.img\");\n            AddPart(canvas, \"Character\\\\Pants\\\\01060026.img\");\n            //AddPart(canvas, \"Character\\\\Weapon\\\\01442000.img\");\n            //AddPart(canvas, \"Character\\\\Weapon\\\\01382007.img\");\n            //AddPart(canvas, \"Character\\\\Weapon\\\\01332000.img\");\n            //AddPart(canvas, \"Character\\\\Weapon\\\\01342000.img\");\n\n            var faceFrames = canvas.GetFaceFrames(canvas.EmotionName);\n\n            //foreach (var action in canvas.Actions)\n            foreach (var action in new[] { \"walk1\", \"jump\", \"stand1\"})\n            {\n                Gif gif = new Gif();\n                var actionFrames = canvas.GetActionFrames(action);\n                foreach (var frame in actionFrames)\n                {\n                    if (frame.Delay != 0)\n                    {\n                        var bone = canvas.CreateFrame(frame, faceFrames[0], null);\n                        var bmp = canvas.DrawFrame(bone);\n\n                        Point pos = bmp.OpOrigin;\n                        pos.Offset(frame.Flip ? new Point(-frame.Move.X, frame.Move.Y) : frame.Move);\n                        GifFrame f = new GifFrame(bmp.Bitmap, new Point(-pos.X, -pos.Y), Math.Abs(frame.Delay));\n                        gif.Frames.Add(f);\n                    }\n                }\n                \n\n                var gifFile = gif.EncodeGif(Color.Transparent);\n                string fileName = \"D:\\\\ms\\\\new_\" + action.Replace('\\\\', '.');\n                gifFile.Save(fileName + (gif.Frames.Count == 1 ? \".png\" : \".gif\"));\n\n                var fd = new System.Drawing.Imaging.FrameDimension(gifFile.FrameDimensionsList[0]);\n                //获取帧数(gif图片可能包含多帧，其它格式图片一般仅一帧)\n                int count = gifFile.GetFrameCount(fd);\n                for (int i = 0; i < count; i++)\n                {\n                    gifFile.SelectActiveFrame(fd, i);\n                    gifFile.Save(fileName + \"_\" + i + \".png\", System.Drawing.Imaging.ImageFormat.Png);\n                }\n\n                gifFile.Dispose();\n            }\n            \n            if (true)\n            {\n\n                Gif gif = CreateChair(canvas);\n                var gifFile = gif.EncodeGif(Color.Transparent, 0);\n                string fileName = \"D:\\\\d16\";\n\n                if (false)\n                {\n                    var fd = new System.Drawing.Imaging.FrameDimension(gifFile.FrameDimensionsList[0]);\n                    //获取帧数(gif图片可能包含多帧，其它格式图片一般仅一帧)\n                    int count = gifFile.GetFrameCount(fd);\n                    for (int i = 0; i < count; i++)\n                    {\n                        gifFile.SelectActiveFrame(fd, i);\n                        gifFile.Save(fileName + \"_\" + i + \".png\", System.Drawing.Imaging.ImageFormat.Png);\n                    }\n                }\n                gifFile.Save(fileName + (gif.Frames.Count == 1 ? \".png\" : \".gif\"));\n                gifFile.Dispose();\n            }\n        }\n\n        private Gif CreateContinueAction(AvatarCanvas canvas)\n        {\n            string afterImage = null;\n            Wz_Node defaultAfterImageNode = null;\n            if (canvas.Weapon != null)\n            {\n                afterImage = canvas.Weapon.Node.FindNodeByPath(false, \"info\", \"afterImage\").GetValueEx<string>(null);\n                if (!string.IsNullOrEmpty(afterImage))\n                {\n                    defaultAfterImageNode = PluginManager.FindWz(\"Character\\\\Afterimage\\\\\" + afterImage + \".img\\\\10\");\n                }\n            }\n\n            GifCanvas gifCanvas = new GifCanvas();\n            gifCanvas.Layers.Add(new GifLayer());\n            int delay = 0;\n            //foreach (string act in new[] { \"alert\", \"swingP1PoleArm\", \"doubleSwing\", \"tripleSwing\" })\n            //foreach (var act in new object[] { \"alert\", \"swingP1PoleArm\", \"overSwingDouble\", \"overSwingTriple\" })\n            var faceFrames = canvas.GetFaceFrames(canvas.EmotionName);\n            //foreach (string act in new[] { \"PBwalk1\", \"PBstand4\", \"PBstand5\" })\n\n            foreach (var act in new object[] {\n                \n                PluginManager.FindWz(\"Skill\\\\2312.img\\\\skill\\\\23121004\"),\n                \"stand1\",\n                PluginManager.FindWz(\"Skill\\\\2312.img\\\\skill\\\\23121052\"),\n                //PluginManager.FindWz(\"Skill\\\\2112.img\\\\skill\\\\21120010\"),\n\n                //PluginManager.FindWz(\"Skill\\\\200.img\\\\skill\\\\2001002\"),\n                //PluginManager.FindWz(\"Skill\\\\230.img\\\\skill\\\\2301003\"),\n                //PluginManager.FindWz(\"Skill\\\\230.img\\\\skill\\\\2301004\"),\n                //PluginManager.FindWz(\"Skill\\\\231.img\\\\skill\\\\2311003\"),\n\n                //PluginManager.FindWz(\"Skill\\\\13100.img\\\\skill\\\\131001010\"),\n                //\"PBwalk1\"\n            })\n            {\n                string actionName = null;\n                Wz_Node afterImageNode = null;\n                List<Gif> effects = new List<Gif>();\n\n                if (act is string)\n                {\n                    actionName = (string)act;\n                }\n                else if (act is Wz_Node)\n                {\n                    Wz_Node skillNode = (Wz_Node)(object)act;\n                    actionName = skillNode.FindNodeByPath(\"action\\\\0\").GetValueEx<string>(null);\n                    if (!string.IsNullOrEmpty(afterImage))\n                    {\n                        afterImageNode = skillNode.FindNodeByPath(\"afterimage\\\\\" + afterImage);\n                    }\n\n                    for (int i = -1; ; i++)\n                    {\n                        Wz_Node effNode = skillNode.FindNodeByPath(\"effect\" + (i > -1 ? i.ToString() : \"\"));\n                        if (effNode == null)\n                            break;\n                        effects.Add(Gif.CreateFromNode(effNode, PluginManager.FindWz));\n                    }\n                }\n\n                if (string.IsNullOrEmpty(actionName))\n                {\n                    continue;\n                }\n\n                //afterImageNode = afterImageNode ?? defaultAfterImageNode;\n\n\n                //添加特效帧\n                foreach (var effGif in effects)\n                {\n                    if (effGif != null && effGif.Frames.Count > 0)\n                    {\n                        var layer = new GifLayer();\n                        if (delay > 0)\n                        {\n                            layer.AddBlank(delay);\n                        }\n                        effGif.Frames.ForEach(af => layer.AddFrame((GifFrame)af));\n                        gifCanvas.Layers.Add(layer);\n                    }\n                }\n\n                //添加角色帧\n                ActionFrame[] actionFrames = canvas.GetActionFrames(actionName);\n                for (int i = 0; i < actionFrames.Length; i++)\n                {\n                    var frame = actionFrames[i];\n\n                    if (frame.Delay != 0)\n                    {\n                        //绘制角色主动作\n                        var bone = canvas.CreateFrame(frame, null, null);\n                        var bmp = canvas.DrawFrame(bone);\n                        GifFrame f = new GifFrame(bmp.Bitmap, bmp.Origin, Math.Abs(frame.Delay));\n                        gifCanvas.Layers[0].Frames.Add(f);\n\n                        //寻找刀光帧\n                        if (afterImageNode != null)\n                        {\n                            var afterImageAction = afterImageNode.FindNodeByPath(false, actionName, i.ToString());\n                            if (afterImageAction != null)\n                            {\n                                Gif aGif = Gif.CreateFromNode(afterImageAction, PluginManager.FindWz);\n                                if (aGif != null && aGif.Frames.Count > 0) //添加新图层\n                                {\n                                    var layer = new GifLayer();\n                                    if (delay > 0)\n                                    {\n                                        layer.AddBlank(delay);\n                                    }\n                                    aGif.Frames.ForEach(af => layer.AddFrame((GifFrame)af));\n                                    gifCanvas.Layers.Add(layer);\n                                }\n                            }\n                        }\n\n                        delay += f.Delay;\n                    }\n\n                }\n\n            }\n\n            return gifCanvas.Combine();\n        }\n\n        private Gif CreateKeyDownAction(AvatarCanvas canvas)\n        {\n            string afterImage = null;\n            Wz_Node defaultAfterImageNode = null;\n            if (canvas.Weapon != null)\n            {\n                afterImage = canvas.Weapon.Node.FindNodeByPath(false, \"info\", \"afterImage\").GetValueEx<string>(null);\n                if (!string.IsNullOrEmpty(afterImage))\n                {\n                    defaultAfterImageNode = PluginManager.FindWz(\"Character\\\\Afterimage\\\\\" + afterImage + \".img\\\\10\");\n                }\n            }\n\n            GifCanvas gifCanvas = new GifCanvas();\n            var layers = new List<Tuple<GifLayer, int>>();\n            var actLayer = new GifLayer();\n\n            //gifCanvas.Layers.Add(new GifLayer());\n            int delay = 0;\n            var faceFrames = canvas.GetFaceFrames(canvas.EmotionName);\n\n            var skillNode = PluginManager.FindWz(\"Skill\\\\2112.img\\\\skill\\\\21120018\");\n            var actionName = skillNode.FindNodeByPath(\"action\\\\0\").GetValueEx<string>(null);\n\n            int keydownCount = 2;\n\n            foreach (var part in new [] {\"prepare\", \"keydown\", \"keydownend\"})\n            {\n                var effects = new List<Tuple<Gif,int>>();\n\n                for (int i = -1; ; i++)\n                {\n                    Wz_Node effNode = skillNode.FindNodeByPath(part + (i > -1 ? i.ToString() : \"\"));\n                    if (effNode == null)\n                        break;\n                    var gif = Gif.CreateFromNode(effNode, PluginManager.FindWz);\n                    var z = effNode.FindNodeByPath(\"z\").GetValueEx(0);\n                    effects.Add(new Tuple<Gif, int>(gif, z));\n                }\n\n                int effDelay = 0;\n                //添加特效帧\n                foreach (var effGif in effects)\n                {\n                    if (effGif.Item1 != null && effGif.Item1.Frames.Count > 0)\n                    {\n                        var layer = new GifLayer();\n                        if (delay > 0)\n                        {\n                            layer.AddBlank(delay);\n                        }\n\n                        int fDelay = 0;\n\n                        for(int i = 0, i0 = part == \"keydown\" ? keydownCount : 1; i < i0; i++)\n                        {\n                            effGif.Item1.Frames.ForEach(af => layer.AddFrame((GifFrame)af));\n                            layers.Add(new Tuple<GifLayer, int>(layer,effGif.Item2));\n                            fDelay+= effGif.Item1.Frames.Select(f => f.Delay).Sum();\n                        }\n\n                        effDelay = Math.Max(fDelay, effDelay);\n                    }\n                }\n\n                delay += effDelay;\n            }\n\n\n            //添加角色帧\n            ActionFrame[] actionFrames = canvas.GetActionFrames(actionName);\n            int adelay = 0;\n            while (adelay < delay)\n            {\n                for (int i = 0; i < actionFrames.Length; i++)\n                {\n                    var frame = actionFrames[i];\n\n                    if (frame.Delay != 0)\n                    {\n                        //绘制角色主动作\n                        var bone = canvas.CreateFrame(frame, null, null);\n                        var bmp = canvas.DrawFrame(bone);\n                        GifFrame f = new GifFrame(bmp.Bitmap, bmp.Origin, Math.Abs(frame.Delay));\n                        actLayer.Frames.Add(f);\n                        adelay += f.Delay;\n                        //delay += f.Delay;\n                    }\n                }\n            }\n\n            layers.Add(new Tuple<GifLayer, int>(actLayer, 0));\n            //按照z排序\n            layers.Sort((a, b) => a.Item2.CompareTo(b.Item2));\n            gifCanvas.Layers.AddRange(layers.Select(t => t.Item1));\n\n            return gifCanvas.Combine();\n        }\n\n        private Gif CreateChair(AvatarCanvas canvas)\n        {\n            GifCanvas gifCanvas = new GifCanvas();\n            var layers = new List<Tuple<GifLayer, int>>();\n            var actLayer = new GifLayer();\n\n            //gifCanvas.Layers.Add(new GifLayer());\n            int delay = 0;\n            var faceFrames = canvas.GetFaceFrames(canvas.EmotionName);\n\n            var ChairNode = PluginManager.FindWz(@\"Item\\Install\\0301.img\\03015660\");\n            var actionName = \"sit\";\n            var pos = ChairNode.FindNodeByPath(@\"info\\bodyRelMove\").GetValueEx<Wz_Vector>(null);\n            \n            Point browPos = new Point(-5, -48);\n\n            //添加特效帧\n            {\n                var effects = new List<Tuple<Gif, int>>();\n\n                for (int i = 1; ; i++)\n                {\n                    Wz_Node effNode = ChairNode.FindNodeByPath(\"effect\"+( i > 1 ? i.ToString() : \"\"));\n                    if (effNode == null)\n                        break;\n                    var gif = Gif.CreateFromNode(effNode, PluginManager.FindWz);\n                    var z = effNode.FindNodeByPath(\"z\").GetValueEx(0);\n                    var isPos = effNode.Nodes[\"pos\"].GetValueEx(0);\n\n\n                    delay = Math.Max(delay, gif.Frames.Sum(f => f.Delay));\n\n                    var layer = new GifLayer();\n                    if (isPos == 1)\n                    {\n                        layer.Frames.AddRange(gif.Frames.Select(f =>\n                        {\n                            GifFrame frame = (GifFrame)f;\n                            frame.Origin = new Point(frame.Origin.X - browPos.X, frame.Origin.Y - browPos.Y);\n                            return frame;\n                        }));\n                    }\n                    else\n                    {\n                        layer.Frames.AddRange(gif.Frames.Select(f => (GifFrame)f));\n                    }\n                   \n                    layers.Add(new Tuple<GifLayer, int>(layer, z));\n                }\n            }\n\n            //添加角色帧\n            ActionFrame[] actionFrames = canvas.GetActionFrames(actionName);\n            int adelay = 0;\n            var bodyMove = pos == null ? Point.Empty : new Point(pos.X, pos.Y);\n            while (adelay < delay)\n            {\n                for (int i = 0; i < actionFrames.Length; i++)\n                {\n                    var frame = actionFrames[i];\n\n                    if (frame.Delay != 0)\n                    {\n                        //绘制角色主动作\n                        var bone = canvas.CreateFrame(frame, faceFrames[0], null);\n                        bone.Position = bodyMove;\n                        var bmp = canvas.DrawFrame(bone);\n                        \n                        GifFrame f = new GifFrame(bmp.Bitmap, bmp.Origin, Math.Abs(frame.Delay));\n                        actLayer.Frames.Add(f);\n                        adelay += f.Delay;\n                        //delay += f.Delay;\n                    }\n                }\n            }\n\n            layers.Add(new Tuple<GifLayer, int>(actLayer, 0));\n            //按照z排序\n            layers.Sort((a, b) => a.Item2.CompareTo(b.Item2));\n            gifCanvas.Layers.AddRange(layers.Select(t => t.Item1));\n\n            return gifCanvas.Combine();\n        }\n\n        void AddPart(AvatarCanvas canvas, string imgPath)\n        {\n            Wz_Node imgNode = PluginManager.FindWz(imgPath);\n            if (imgNode != null)\n            {\n                canvas.AddPart(imgNode);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的常规信息通过以下\n// 特性集控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2.Avatar\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Kagamia Studio\")]\n[assembly: AssemblyProduct(\"WzComparerR2.Avatar\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2015-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 使此程序集中的类型\n// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型，\n// 则将该类型上的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"1311079c-100f-4dd5-9957-3064078a67de\")]\n\n// 程序集的版本信息由下面四个值组成:\n//\n//      主版本\n//      次版本 \n//      生成号\n//      修订号\n//\n// 可以指定所有这些值，也可以使用“生成号”和“修订号”的默认值，\n// 方法是按如下所示使用“*”:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2.Avatar/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     此代码由工具生成。\n//     运行时版本:4.0.30319.42000\n//\n//     对此文件的更改可能会导致不正确的行为，并且如果\n//     重新生成代码，这些更改将会丢失。\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace WzComparerR2.Avatar.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   一个强类型的资源类，用于查找本地化的字符串等。\n    /// </summary>\n    // 此类是由 StronglyTypedResourceBuilder\n    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。\n    // 若要添加或移除成员，请编辑 .ResX 文件，然后重新运行 ResGen\n    // (以 /str 作为命令选项)，或重新生成 VS 项目。\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"16.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   返回此类使用的缓存的 ResourceManager 实例。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"WzComparerR2.Avatar.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   重写当前线程的 CurrentUICulture 属性，对\n        ///   使用此强类型资源类的所有资源查找执行重写。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap _lock {\n            get {\n                object obj = ResourceManager.GetObject(\"_lock\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap arrow_in {\n            get {\n                object obj = ResourceManager.GetObject(\"arrow_in\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap disk {\n            get {\n                object obj = ResourceManager.GetObject(\"disk\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap script_code {\n            get {\n                object obj = ResourceManager.GetObject(\"script_code\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Drawing.Bitmap 类型的本地化资源。\n        /// </summary>\n        internal static System.Drawing.Bitmap user {\n            get {\n                object obj = ResourceManager.GetObject(\"user\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"arrow_in\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\arrow_in.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"disk\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\disk.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"script_code\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\script_code.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"user\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\user.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"_lock\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\lock.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2.Avatar/Skin.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Avatar\n{\n    public class Skin\n    {\n        public string Name { get; set; }\n        public BitmapOrigin Image { get; set; }\n        public Point Offset { get; set; }\n        public string Z { get; set; }\n        public int ZIndex { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarCodeForm.Designer.cs",
    "content": "﻿namespace WzComparerR2.Avatar.UI\n{\n    partial class AvatarCodeForm\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.buttonX1 = new DevComponents.DotNetBar.ButtonX();\n            this.buttonX2 = new DevComponents.DotNetBar.ButtonX();\n            this.textBoxX1 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.checkBoxX1 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.checkBoxX2 = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.SuspendLayout();\n            // \n            // buttonX1\n            // \n            this.buttonX1.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX1.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX1.DialogResult = System.Windows.Forms.DialogResult.OK;\n            this.buttonX1.Location = new System.Drawing.Point(107, 126);\n            this.buttonX1.Name = \"buttonX1\";\n            this.buttonX1.Size = new System.Drawing.Size(75, 23);\n            this.buttonX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX1.TabIndex = 0;\n            this.buttonX1.Text = \"确定\";\n            // \n            // buttonX2\n            // \n            this.buttonX2.AccessibleRole = System.Windows.Forms.AccessibleRole.PushButton;\n            this.buttonX2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));\n            this.buttonX2.ColorTable = DevComponents.DotNetBar.eButtonColor.OrangeWithBackground;\n            this.buttonX2.DialogResult = System.Windows.Forms.DialogResult.Cancel;\n            this.buttonX2.Location = new System.Drawing.Point(197, 126);\n            this.buttonX2.Name = \"buttonX2\";\n            this.buttonX2.Size = new System.Drawing.Size(75, 23);\n            this.buttonX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.buttonX2.TabIndex = 1;\n            this.buttonX2.Text = \"取消\";\n            // \n            // textBoxX1\n            // \n            this.textBoxX1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.textBoxX1.Border.Class = \"TextBoxBorder\";\n            this.textBoxX1.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.textBoxX1.Location = new System.Drawing.Point(12, 12);\n            this.textBoxX1.Multiline = true;\n            this.textBoxX1.Name = \"textBoxX1\";\n            this.textBoxX1.PreventEnterBeep = true;\n            this.textBoxX1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;\n            this.textBoxX1.Size = new System.Drawing.Size(260, 85);\n            this.textBoxX1.TabIndex = 2;\n            this.textBoxX1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBoxX1_KeyDown);\n            // \n            // checkBoxX1\n            // \n            this.checkBoxX1.AutoSize = true;\n            // \n            // \n            // \n            this.checkBoxX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX1.CheckBoxStyle = DevComponents.DotNetBar.eCheckBoxStyle.RadioButton;\n            this.checkBoxX1.Checked = true;\n            this.checkBoxX1.CheckState = System.Windows.Forms.CheckState.Checked;\n            this.checkBoxX1.CheckValue = \"Y\";\n            this.checkBoxX1.Location = new System.Drawing.Point(71, 103);\n            this.checkBoxX1.Name = \"checkBoxX1\";\n            this.checkBoxX1.Size = new System.Drawing.Size(51, 18);\n            this.checkBoxX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX1.TabIndex = 3;\n            this.checkBoxX1.Text = \"覆盖\";\n            // \n            // checkBoxX2\n            // \n            this.checkBoxX2.AutoSize = true;\n            // \n            // \n            // \n            this.checkBoxX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.checkBoxX2.CheckBoxStyle = DevComponents.DotNetBar.eCheckBoxStyle.RadioButton;\n            this.checkBoxX2.Location = new System.Drawing.Point(125, 103);\n            this.checkBoxX2.Name = \"checkBoxX2\";\n            this.checkBoxX2.Size = new System.Drawing.Size(51, 18);\n            this.checkBoxX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.checkBoxX2.TabIndex = 4;\n            this.checkBoxX2.Text = \"合并\";\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(10, 103);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(56, 18);\n            this.labelX1.TabIndex = 5;\n            this.labelX1.Text = \"加载方式\";\n            // \n            // AvatarCodeForm\n            // \n            this.ClientSize = new System.Drawing.Size(284, 161);\n            this.Controls.Add(this.labelX1);\n            this.Controls.Add(this.checkBoxX2);\n            this.Controls.Add(this.checkBoxX1);\n            this.Controls.Add(this.textBoxX1);\n            this.Controls.Add(this.buttonX2);\n            this.Controls.Add(this.buttonX1);\n            this.DoubleBuffered = true;\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"AvatarCodeForm\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n            this.Text = \"读取代码\";\n            this.ResumeLayout(false);\n            this.PerformLayout();\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.ButtonX buttonX1;\n        private DevComponents.DotNetBar.ButtonX buttonX2;\n        private DevComponents.DotNetBar.Controls.TextBoxX textBoxX1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX checkBoxX2;\n        private DevComponents.DotNetBar.LabelX labelX1;\n    }\n}"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarCodeForm.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Text;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\n\nnamespace WzComparerR2.Avatar.UI\n{\n    public partial class AvatarCodeForm : DevComponents.DotNetBar.OfficeForm\n    {\n        public AvatarCodeForm()\n        {\n            InitializeComponent();\n        }\n\n        public string CodeText\n        {\n            get { return textBoxX1.Text; }\n            set { textBoxX1.Text = value; }\n        }\n\n        public int LoadType\n        {\n            get\n            {\n                if (this.checkBoxX1.Checked)\n                {\n                    return 0;\n                }\n                else if (this.checkBoxX2.Checked)\n                {\n                    return 1;\n                }\n                else\n                {\n                    return 0;\n                }\n            }\n        }\n\n        private void textBoxX1_KeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.Control && e.KeyCode == Keys.A)\n            {\n                textBoxX1.SelectAll();\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarCodeForm.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarContainer.Designer.cs",
    "content": "﻿namespace WzComparerR2.Avatar.UI\n{\n    partial class AvatarContainer\n    {\n        /// <summary>\n        /// 必需的设计器变量。\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// 清理所有正在使用的资源。\n        /// </summary>\n        /// <param name=\"disposing\">如果应释放托管资源，为 true；否则为 false。</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region 组件设计器生成的代码\n\n        /// <summary>\n        /// 设计器支持所需的方法 - 不要\n        /// 使用代码编辑器修改此方法的内容。\n        /// </summary>\n        private void InitializeComponent()\n        {\n            components = new System.ComponentModel.Container();\n        }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarContainer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Drawing.Drawing2D;\nusing System.Text;\nusing System.Windows.Forms;\n\nnamespace WzComparerR2.Avatar.UI\n{\n    public partial class AvatarContainer : Control\n    {\n        public AvatarContainer()\n        {\n            InitializeComponent();\n            SetStyle(ControlStyles.UserPaint, true);\n            SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景.  \n            SetStyle(ControlStyles.DoubleBuffer, true); // 双缓冲  \n            bmpCache = new Dictionary<string, BitmapOrigin[]>();\n        }\n\n        /// <summary>\n        /// 获取或设置纸娃娃的绘图原点。\n        /// </summary>\n        public Point Origin { get; set; }\n\n        //绘图相关\n        Dictionary<string, BitmapOrigin[]> bmpCache;\n        string currentKey;\n        Bitmap bg;\n\n        //事件相关\n        bool isDragging;\n        Point lastPressingPoint;\n\n        public void ClearAllCache()\n        {\n            this.bmpCache.Clear();\n        }\n\n        public void AddCache(string key, BitmapOrigin[] layers)\n        {\n            this.bmpCache[key] = layers;\n        }\n\n        public bool HasCache(string key)\n        {\n            return this.bmpCache.ContainsKey(key);\n        }\n\n        public void SetKey(string key)\n        {\n            this.currentKey = key;\n            this.Invalidate();\n        }\n\n        protected override void OnPaint(PaintEventArgs pe)\n        {\n            Graphics g = pe.Graphics;\n            DrawBackgrnd(g);\n\n            BitmapOrigin[] layers;\n            if (currentKey != null && this.bmpCache.TryGetValue(currentKey, out layers) && layers != null)\n            {\n                foreach(var bmp in layers)\n                {\n                    if (bmp.Bitmap != null)\n                    {\n                        Point point = new Point(this.Origin.X - bmp.Origin.X, this.Origin.Y - bmp.Origin.Y);\n                        g.DrawImage(bmp.Bitmap, point);\n                    }\n                }\n               \n            }\n\n            base.OnPaint(pe);\n        }\n\n        private void DrawBackgrnd(Graphics g)\n        {\n            if (bg == null)\n            {\n                bg = new Bitmap(16, 16, System.Drawing.Imaging.PixelFormat.Format32bppArgb);\n                Graphics g1 = Graphics.FromImage(bg);\n                g1.FillRectangle(Brushes.LightGray, bg.Width / 2, 0, bg.Width / 2, bg.Height / 2);\n                g1.FillRectangle(Brushes.LightGray, 0, bg.Height / 2, bg.Width / 2, bg.Height / 2);\n                g1.Dispose();\n            }\n            TextureBrush b = new TextureBrush(bg, WrapMode.Tile);\n            g.FillRectangle(b, new Rectangle(Point.Empty, this.Size));\n            b.Dispose();\n        }\n\n        protected override void OnMouseDown(MouseEventArgs e)\n        {\n            if ((e.Button & System.Windows.Forms.MouseButtons.Left) == System.Windows.Forms.MouseButtons.Left)\n            {\n                isDragging = true;\n                lastPressingPoint = e.Location;\n            }\n            base.OnMouseDown(e);\n        }\n\n        protected override void OnMouseMove(MouseEventArgs e)\n        {\n            if (isDragging)\n            {\n                int dx = e.X - lastPressingPoint.X;\n                int dy = e.Y - lastPressingPoint.Y;\n                this.Origin = new Point(Origin.X + dx, Origin.Y + dy);\n                this.lastPressingPoint = new Point(e.X, e.Y);\n                this.Invalidate();\n            }\n            base.OnMouseMove(e);\n        }\n\n        protected override void OnMouseUp(MouseEventArgs e)\n        {\n            if ((e.Button & System.Windows.Forms.MouseButtons.Left) == System.Windows.Forms.MouseButtons.Left)\n            {\n                isDragging = false;\n            }\n            base.OnMouseUp(e);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarForm.Designer.cs",
    "content": "﻿namespace WzComparerR2.Avatar.UI\n{\n    partial class AvatarForm\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.components = new System.ComponentModel.Container();\n            this.dotNetBarManager1 = new DevComponents.DotNetBar.DotNetBarManager(this.components);\n            this.dockSite4 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite1 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite2 = new DevComponents.DotNetBar.DockSite();\n            this.bar1 = new DevComponents.DotNetBar.Bar();\n            this.panelDockContainer1 = new DevComponents.DotNetBar.PanelDockContainer();\n            this.itemPanel1 = new DevComponents.DotNetBar.ItemPanel();\n            this.dockContainerItem1 = new DevComponents.DotNetBar.DockContainerItem();\n            this.bar2 = new DevComponents.DotNetBar.Bar();\n            this.panelDockContainer2 = new DevComponents.DotNetBar.PanelDockContainer();\n            this.cmbWeaponIdx = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.cmbWeaponType = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.labelX4 = new DevComponents.DotNetBar.LabelX();\n            this.chkTamingPlay = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkEmotionPlay = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkBodyPlay = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.cmbTamingFrame = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.cmbEmotionFrame = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.cmbBodyFrame = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.cmbActionTaming = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.cmbEmotion = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.labelX3 = new DevComponents.DotNetBar.LabelX();\n            this.labelX2 = new DevComponents.DotNetBar.LabelX();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.cmbActionBody = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.chkHairShade = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.chkHairCover = new DevComponents.DotNetBar.Controls.CheckBoxX();\n            this.dockContainerItem2 = new DevComponents.DotNetBar.DockContainerItem();\n            this.dockSite8 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite5 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite6 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite7 = new DevComponents.DotNetBar.DockSite();\n            this.bar3 = new DevComponents.DotNetBar.Bar();\n            this.btnCode = new DevComponents.DotNetBar.ButtonItem();\n            this.btnCharac = new DevComponents.DotNetBar.ButtonItem();\n            this.btnMale = new DevComponents.DotNetBar.ButtonItem();\n            this.btnFemale = new DevComponents.DotNetBar.ButtonItem();\n            this.btnReset = new DevComponents.DotNetBar.ButtonItem();\n            this.btnLock = new DevComponents.DotNetBar.ButtonItem();\n            this.btnSaveAsGif = new DevComponents.DotNetBar.ButtonItem();\n            this.dockSite3 = new DevComponents.DotNetBar.DockSite();\n            this.timer1 = new System.Windows.Forms.Timer(this.components);\n            this.avatarContainer1 = new WzComparerR2.Avatar.UI.AvatarContainer();\n            this.labelX5 = new DevComponents.DotNetBar.LabelX();\n            this.cmbEar = new DevComponents.DotNetBar.Controls.ComboBoxEx();\n            this.dockSite2.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).BeginInit();\n            this.bar1.SuspendLayout();\n            this.panelDockContainer1.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.bar2)).BeginInit();\n            this.bar2.SuspendLayout();\n            this.panelDockContainer2.SuspendLayout();\n            this.dockSite7.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.bar3)).BeginInit();\n            this.SuspendLayout();\n            // \n            // dotNetBarManager1\n            // \n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.F1);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlC);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlA);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlV);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlX);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlZ);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlY);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.Del);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.Ins);\n            this.dotNetBarManager1.BottomDockSite = this.dockSite4;\n            this.dotNetBarManager1.EnableFullSizeDock = false;\n            this.dotNetBarManager1.LeftDockSite = this.dockSite1;\n            this.dotNetBarManager1.ParentForm = this;\n            this.dotNetBarManager1.RightDockSite = this.dockSite2;\n            this.dotNetBarManager1.ShowCustomizeContextMenu = false;\n            this.dotNetBarManager1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.dotNetBarManager1.ToolbarBottomDockSite = this.dockSite8;\n            this.dotNetBarManager1.ToolbarLeftDockSite = this.dockSite5;\n            this.dotNetBarManager1.ToolbarRightDockSite = this.dockSite6;\n            this.dotNetBarManager1.ToolbarTopDockSite = this.dockSite7;\n            this.dotNetBarManager1.TopDockSite = this.dockSite3;\n            // \n            // dockSite4\n            // \n            this.dockSite4.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite4.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.dockSite4.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite4.Location = new System.Drawing.Point(0, 411);\n            this.dockSite4.Name = \"dockSite4\";\n            this.dockSite4.Size = new System.Drawing.Size(584, 0);\n            this.dockSite4.TabIndex = 3;\n            this.dockSite4.TabStop = false;\n            // \n            // dockSite1\n            // \n            this.dockSite1.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite1.Dock = System.Windows.Forms.DockStyle.Left;\n            this.dockSite1.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite1.Location = new System.Drawing.Point(0, 25);\n            this.dockSite1.Name = \"dockSite1\";\n            this.dockSite1.Size = new System.Drawing.Size(0, 386);\n            this.dockSite1.TabIndex = 0;\n            this.dockSite1.TabStop = false;\n            // \n            // dockSite2\n            // \n            this.dockSite2.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite2.Controls.Add(this.bar1);\n            this.dockSite2.Controls.Add(this.bar2);\n            this.dockSite2.Dock = System.Windows.Forms.DockStyle.Right;\n            this.dockSite2.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer(new DevComponents.DotNetBar.DocumentBaseContainer[] {\n            ((DevComponents.DotNetBar.DocumentBaseContainer)(new DevComponents.DotNetBar.DocumentBarContainer(this.bar1, 213, 212))),\n            ((DevComponents.DotNetBar.DocumentBaseContainer)(new DevComponents.DotNetBar.DocumentBarContainer(this.bar2, 213, 171)))}, DevComponents.DotNetBar.eOrientation.Vertical);\n            this.dockSite2.Location = new System.Drawing.Point(368, 25);\n            this.dockSite2.Name = \"dockSite2\";\n            this.dockSite2.Size = new System.Drawing.Size(216, 386);\n            this.dockSite2.TabIndex = 1;\n            this.dockSite2.TabStop = false;\n            // \n            // bar1\n            // \n            this.bar1.AccessibleDescription = \"DotNetBar Bar (bar1)\";\n            this.bar1.AccessibleName = \"DotNetBar Bar\";\n            this.bar1.AccessibleRole = System.Windows.Forms.AccessibleRole.Grouping;\n            this.bar1.AutoSyncBarCaption = true;\n            this.bar1.CloseSingleTab = true;\n            this.bar1.Controls.Add(this.panelDockContainer1);\n            this.bar1.Font = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.bar1.GrabHandleStyle = DevComponents.DotNetBar.eGrabHandleStyle.Caption;\n            this.bar1.IsMaximized = false;\n            this.bar1.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.dockContainerItem1});\n            this.bar1.LayoutType = DevComponents.DotNetBar.eLayoutType.DockContainer;\n            this.bar1.Location = new System.Drawing.Point(3, 0);\n            this.bar1.Name = \"bar1\";\n            this.bar1.Size = new System.Drawing.Size(213, 212);\n            this.bar1.Stretch = true;\n            this.bar1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.bar1.TabIndex = 0;\n            this.bar1.TabStop = false;\n            this.bar1.Text = \"零件\";\n            // \n            // panelDockContainer1\n            // \n            this.panelDockContainer1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelDockContainer1.Controls.Add(this.itemPanel1);\n            this.panelDockContainer1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelDockContainer1.Location = new System.Drawing.Point(3, 23);\n            this.panelDockContainer1.Name = \"panelDockContainer1\";\n            this.panelDockContainer1.Size = new System.Drawing.Size(207, 186);\n            this.panelDockContainer1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelDockContainer1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.panelDockContainer1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.panelDockContainer1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.panelDockContainer1.Style.GradientAngle = 90;\n            this.panelDockContainer1.TabIndex = 0;\n            // \n            // itemPanel1\n            // \n            this.itemPanel1.AutoScroll = true;\n            // \n            // \n            // \n            this.itemPanel1.BackgroundStyle.Class = \"ItemPanel\";\n            this.itemPanel1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.itemPanel1.ContainerControlProcessDialogKey = true;\n            this.itemPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.itemPanel1.DragDropSupport = true;\n            this.itemPanel1.LayoutOrientation = DevComponents.DotNetBar.eOrientation.Vertical;\n            this.itemPanel1.Location = new System.Drawing.Point(0, 0);\n            this.itemPanel1.Name = \"itemPanel1\";\n            this.itemPanel1.Size = new System.Drawing.Size(207, 186);\n            this.itemPanel1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.itemPanel1.TabIndex = 8;\n            this.itemPanel1.Text = \"itemPanel1\";\n            // \n            // dockContainerItem1\n            // \n            this.dockContainerItem1.Control = this.panelDockContainer1;\n            this.dockContainerItem1.Name = \"dockContainerItem1\";\n            this.dockContainerItem1.Text = \"零件\";\n            // \n            // bar2\n            // \n            this.bar2.AccessibleDescription = \"DotNetBar Bar (bar2)\";\n            this.bar2.AccessibleName = \"DotNetBar Bar\";\n            this.bar2.AccessibleRole = System.Windows.Forms.AccessibleRole.Grouping;\n            this.bar2.AutoSyncBarCaption = true;\n            this.bar2.CloseSingleTab = true;\n            this.bar2.Controls.Add(this.panelDockContainer2);\n            this.bar2.Font = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.bar2.GrabHandleStyle = DevComponents.DotNetBar.eGrabHandleStyle.Caption;\n            this.bar2.IsMaximized = false;\n            this.bar2.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.dockContainerItem2});\n            this.bar2.LayoutType = DevComponents.DotNetBar.eLayoutType.DockContainer;\n            this.bar2.Location = new System.Drawing.Point(3, 215);\n            this.bar2.Name = \"bar2\";\n            this.bar2.Size = new System.Drawing.Size(213, 171);\n            this.bar2.Stretch = true;\n            this.bar2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.bar2.TabIndex = 1;\n            this.bar2.TabStop = false;\n            this.bar2.Text = \"动作\";\n            // \n            // panelDockContainer2\n            // \n            this.panelDockContainer2.AutoScroll = true;\n            this.panelDockContainer2.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelDockContainer2.Controls.Add(this.cmbEar);\n            this.panelDockContainer2.Controls.Add(this.cmbWeaponIdx);\n            this.panelDockContainer2.Controls.Add(this.cmbWeaponType);\n            this.panelDockContainer2.Controls.Add(this.labelX4);\n            this.panelDockContainer2.Controls.Add(this.chkTamingPlay);\n            this.panelDockContainer2.Controls.Add(this.chkEmotionPlay);\n            this.panelDockContainer2.Controls.Add(this.chkBodyPlay);\n            this.panelDockContainer2.Controls.Add(this.cmbTamingFrame);\n            this.panelDockContainer2.Controls.Add(this.cmbEmotionFrame);\n            this.panelDockContainer2.Controls.Add(this.cmbBodyFrame);\n            this.panelDockContainer2.Controls.Add(this.cmbActionTaming);\n            this.panelDockContainer2.Controls.Add(this.cmbEmotion);\n            this.panelDockContainer2.Controls.Add(this.labelX3);\n            this.panelDockContainer2.Controls.Add(this.labelX2);\n            this.panelDockContainer2.Controls.Add(this.labelX1);\n            this.panelDockContainer2.Controls.Add(this.cmbActionBody);\n            this.panelDockContainer2.Controls.Add(this.chkHairShade);\n            this.panelDockContainer2.Controls.Add(this.chkHairCover);\n            this.panelDockContainer2.Controls.Add(this.labelX5);\n            this.panelDockContainer2.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelDockContainer2.Location = new System.Drawing.Point(3, 23);\n            this.panelDockContainer2.Name = \"panelDockContainer2\";\n            this.panelDockContainer2.Size = new System.Drawing.Size(207, 145);\n            this.panelDockContainer2.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelDockContainer2.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.panelDockContainer2.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.panelDockContainer2.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.panelDockContainer2.Style.GradientAngle = 90;\n            this.panelDockContainer2.TabIndex = 0;\n            // \n            // cmbWeaponIdx\n            // \n            this.cmbWeaponIdx.DisplayMember = \"Text\";\n            this.cmbWeaponIdx.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbWeaponIdx.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbWeaponIdx.FormattingEnabled = true;\n            this.cmbWeaponIdx.ItemHeight = 15;\n            this.cmbWeaponIdx.Location = new System.Drawing.Point(89, 103);\n            this.cmbWeaponIdx.Name = \"cmbWeaponIdx\";\n            this.cmbWeaponIdx.Size = new System.Drawing.Size(50, 21);\n            this.cmbWeaponIdx.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbWeaponIdx.TabIndex = 13;\n            this.cmbWeaponIdx.SelectedIndexChanged += new System.EventHandler(this.cmbWeaponIdx_SelectedIndexChanged);\n            // \n            // cmbWeaponType\n            // \n            this.cmbWeaponType.DisplayMember = \"Text\";\n            this.cmbWeaponType.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbWeaponType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbWeaponType.FormattingEnabled = true;\n            this.cmbWeaponType.ItemHeight = 15;\n            this.cmbWeaponType.Location = new System.Drawing.Point(35, 103);\n            this.cmbWeaponType.Name = \"cmbWeaponType\";\n            this.cmbWeaponType.Size = new System.Drawing.Size(50, 21);\n            this.cmbWeaponType.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbWeaponType.TabIndex = 12;\n            this.cmbWeaponType.SelectedIndexChanged += new System.EventHandler(this.cmbWeaponType_SelectedIndexChanged);\n            // \n            // labelX4\n            // \n            this.labelX4.AutoSize = true;\n            this.labelX4.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX4.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX4.Location = new System.Drawing.Point(3, 106);\n            this.labelX4.Name = \"labelX4\";\n            this.labelX4.Size = new System.Drawing.Size(31, 18);\n            this.labelX4.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.labelX4.TabIndex = 13;\n            this.labelX4.Text = \"武器\";\n            // \n            // chkTamingPlay\n            // \n            this.chkTamingPlay.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.chkTamingPlay.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkTamingPlay.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkTamingPlay.Location = new System.Drawing.Point(184, 57);\n            this.chkTamingPlay.Name = \"chkTamingPlay\";\n            this.chkTamingPlay.Size = new System.Drawing.Size(15, 21);\n            this.chkTamingPlay.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkTamingPlay.TabIndex = 8;\n            this.chkTamingPlay.TextVisible = false;\n            this.chkTamingPlay.CheckedChanged += new System.EventHandler(this.chkTamingPlay_CheckedChanged);\n            // \n            // chkEmotionPlay\n            // \n            this.chkEmotionPlay.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.chkEmotionPlay.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkEmotionPlay.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkEmotionPlay.Location = new System.Drawing.Point(184, 30);\n            this.chkEmotionPlay.Name = \"chkEmotionPlay\";\n            this.chkEmotionPlay.Size = new System.Drawing.Size(15, 21);\n            this.chkEmotionPlay.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkEmotionPlay.TabIndex = 7;\n            this.chkEmotionPlay.TextVisible = false;\n            this.chkEmotionPlay.CheckedChanged += new System.EventHandler(this.chkEmotionPlay_CheckedChanged);\n            // \n            // chkBodyPlay\n            // \n            this.chkBodyPlay.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.chkBodyPlay.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkBodyPlay.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkBodyPlay.Location = new System.Drawing.Point(184, 3);\n            this.chkBodyPlay.Name = \"chkBodyPlay\";\n            this.chkBodyPlay.Size = new System.Drawing.Size(15, 21);\n            this.chkBodyPlay.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkBodyPlay.TabIndex = 6;\n            this.chkBodyPlay.TextVisible = false;\n            this.chkBodyPlay.CheckedChanged += new System.EventHandler(this.chkBodyPlay_CheckedChanged);\n            // \n            // cmbTamingFrame\n            // \n            this.cmbTamingFrame.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.cmbTamingFrame.DisplayMember = \"Text\";\n            this.cmbTamingFrame.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbTamingFrame.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbTamingFrame.FormattingEnabled = true;\n            this.cmbTamingFrame.ItemHeight = 15;\n            this.cmbTamingFrame.Location = new System.Drawing.Point(128, 57);\n            this.cmbTamingFrame.Name = \"cmbTamingFrame\";\n            this.cmbTamingFrame.Size = new System.Drawing.Size(50, 21);\n            this.cmbTamingFrame.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbTamingFrame.TabIndex = 5;\n            this.cmbTamingFrame.SelectedIndexChanged += new System.EventHandler(this.cmbTamingFrame_SelectedIndexChanged);\n            // \n            // cmbEmotionFrame\n            // \n            this.cmbEmotionFrame.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.cmbEmotionFrame.DisplayMember = \"Text\";\n            this.cmbEmotionFrame.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbEmotionFrame.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbEmotionFrame.FormattingEnabled = true;\n            this.cmbEmotionFrame.ItemHeight = 15;\n            this.cmbEmotionFrame.Location = new System.Drawing.Point(128, 30);\n            this.cmbEmotionFrame.Name = \"cmbEmotionFrame\";\n            this.cmbEmotionFrame.Size = new System.Drawing.Size(50, 21);\n            this.cmbEmotionFrame.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbEmotionFrame.TabIndex = 4;\n            this.cmbEmotionFrame.SelectedIndexChanged += new System.EventHandler(this.cmbEmotionFrame_SelectedIndexChanged);\n            // \n            // cmbBodyFrame\n            // \n            this.cmbBodyFrame.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));\n            this.cmbBodyFrame.DisplayMember = \"Text\";\n            this.cmbBodyFrame.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbBodyFrame.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbBodyFrame.FormattingEnabled = true;\n            this.cmbBodyFrame.ItemHeight = 15;\n            this.cmbBodyFrame.Location = new System.Drawing.Point(128, 3);\n            this.cmbBodyFrame.Name = \"cmbBodyFrame\";\n            this.cmbBodyFrame.Size = new System.Drawing.Size(50, 21);\n            this.cmbBodyFrame.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbBodyFrame.TabIndex = 3;\n            this.cmbBodyFrame.SelectedIndexChanged += new System.EventHandler(this.cmbBodyFrame_SelectedIndexChanged);\n            // \n            // cmbActionTaming\n            // \n            this.cmbActionTaming.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.cmbActionTaming.DisplayMember = \"Text\";\n            this.cmbActionTaming.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbActionTaming.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbActionTaming.FormattingEnabled = true;\n            this.cmbActionTaming.ItemHeight = 15;\n            this.cmbActionTaming.Location = new System.Drawing.Point(35, 57);\n            this.cmbActionTaming.Name = \"cmbActionTaming\";\n            this.cmbActionTaming.Size = new System.Drawing.Size(87, 21);\n            this.cmbActionTaming.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbActionTaming.TabIndex = 2;\n            this.cmbActionTaming.SelectedIndexChanged += new System.EventHandler(this.cmbActionTaming_SelectedIndexChanged);\n            // \n            // cmbEmotion\n            // \n            this.cmbEmotion.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.cmbEmotion.DisplayMember = \"Text\";\n            this.cmbEmotion.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbEmotion.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbEmotion.FormattingEnabled = true;\n            this.cmbEmotion.ItemHeight = 15;\n            this.cmbEmotion.Location = new System.Drawing.Point(35, 30);\n            this.cmbEmotion.Name = \"cmbEmotion\";\n            this.cmbEmotion.Size = new System.Drawing.Size(87, 21);\n            this.cmbEmotion.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbEmotion.TabIndex = 1;\n            this.cmbEmotion.SelectedIndexChanged += new System.EventHandler(this.cmbEmotion_SelectedIndexChanged);\n            // \n            // labelX3\n            // \n            this.labelX3.AutoSize = true;\n            this.labelX3.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX3.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX3.Location = new System.Drawing.Point(3, 60);\n            this.labelX3.Name = \"labelX3\";\n            this.labelX3.Size = new System.Drawing.Size(31, 18);\n            this.labelX3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.labelX3.TabIndex = 3;\n            this.labelX3.Text = \"骑兽\";\n            // \n            // labelX2\n            // \n            this.labelX2.AutoSize = true;\n            this.labelX2.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX2.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX2.Location = new System.Drawing.Point(3, 33);\n            this.labelX2.Name = \"labelX2\";\n            this.labelX2.Size = new System.Drawing.Size(31, 18);\n            this.labelX2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.labelX2.TabIndex = 2;\n            this.labelX2.Text = \"表情\";\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            this.labelX1.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(3, 6);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(31, 18);\n            this.labelX1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.labelX1.TabIndex = 1;\n            this.labelX1.Text = \"身体\";\n            // \n            // cmbActionBody\n            // \n            this.cmbActionBody.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            this.cmbActionBody.DisplayMember = \"Text\";\n            this.cmbActionBody.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbActionBody.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbActionBody.FormattingEnabled = true;\n            this.cmbActionBody.ItemHeight = 15;\n            this.cmbActionBody.Location = new System.Drawing.Point(35, 3);\n            this.cmbActionBody.Name = \"cmbActionBody\";\n            this.cmbActionBody.Size = new System.Drawing.Size(87, 21);\n            this.cmbActionBody.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbActionBody.TabIndex = 0;\n            this.cmbActionBody.SelectedIndexChanged += new System.EventHandler(this.cmbActionBody_SelectedIndexChanged);\n            // \n            // chkHairShade\n            // \n            this.chkHairShade.AutoSize = true;\n            this.chkHairShade.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkHairShade.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkHairShade.Location = new System.Drawing.Point(82, 84);\n            this.chkHairShade.Name = \"chkHairShade\";\n            this.chkHairShade.Size = new System.Drawing.Size(85, 19);\n            this.chkHairShade.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkHairShade.TabIndex = 10;\n            this.chkHairShade.Text = \"hairShade\";\n            this.chkHairShade.CheckedChanged += new System.EventHandler(this.chkHairShade_CheckedChanged);\n            // \n            // chkHairCover\n            // \n            this.chkHairCover.AutoSize = true;\n            this.chkHairCover.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.chkHairCover.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.chkHairCover.Location = new System.Drawing.Point(5, 84);\n            this.chkHairCover.Name = \"chkHairCover\";\n            this.chkHairCover.Size = new System.Drawing.Size(83, 19);\n            this.chkHairCover.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.chkHairCover.TabIndex = 9;\n            this.chkHairCover.Text = \"hairCover\";\n            this.chkHairCover.CheckedChanged += new System.EventHandler(this.chkHairCover_CheckedChanged);\n            // \n            // dockContainerItem2\n            // \n            this.dockContainerItem2.Control = this.panelDockContainer2;\n            this.dockContainerItem2.Name = \"dockContainerItem2\";\n            this.dockContainerItem2.Text = \"动作\";\n            // \n            // dockSite8\n            // \n            this.dockSite8.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite8.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.dockSite8.Location = new System.Drawing.Point(0, 411);\n            this.dockSite8.Name = \"dockSite8\";\n            this.dockSite8.Size = new System.Drawing.Size(584, 0);\n            this.dockSite8.TabIndex = 7;\n            this.dockSite8.TabStop = false;\n            // \n            // dockSite5\n            // \n            this.dockSite5.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite5.Dock = System.Windows.Forms.DockStyle.Left;\n            this.dockSite5.Location = new System.Drawing.Point(0, 25);\n            this.dockSite5.Name = \"dockSite5\";\n            this.dockSite5.Size = new System.Drawing.Size(0, 386);\n            this.dockSite5.TabIndex = 4;\n            this.dockSite5.TabStop = false;\n            // \n            // dockSite6\n            // \n            this.dockSite6.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite6.Dock = System.Windows.Forms.DockStyle.Right;\n            this.dockSite6.Location = new System.Drawing.Point(584, 25);\n            this.dockSite6.Name = \"dockSite6\";\n            this.dockSite6.Size = new System.Drawing.Size(0, 386);\n            this.dockSite6.TabIndex = 5;\n            this.dockSite6.TabStop = false;\n            // \n            // dockSite7\n            // \n            this.dockSite7.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite7.Controls.Add(this.bar3);\n            this.dockSite7.Dock = System.Windows.Forms.DockStyle.Top;\n            this.dockSite7.Location = new System.Drawing.Point(0, 0);\n            this.dockSite7.Name = \"dockSite7\";\n            this.dockSite7.Size = new System.Drawing.Size(584, 25);\n            this.dockSite7.TabIndex = 6;\n            this.dockSite7.TabStop = false;\n            // \n            // bar3\n            // \n            this.bar3.AccessibleDescription = \"DotNetBar Bar (bar3)\";\n            this.bar3.AccessibleName = \"DotNetBar Bar\";\n            this.bar3.AccessibleRole = System.Windows.Forms.AccessibleRole.ToolBar;\n            this.bar3.CanCustomize = false;\n            this.bar3.DockSide = DevComponents.DotNetBar.eDockSide.Top;\n            this.bar3.Font = new System.Drawing.Font(\"Microsoft YaHei UI\", 9F);\n            this.bar3.GrabHandleStyle = DevComponents.DotNetBar.eGrabHandleStyle.Office2003;\n            this.bar3.IsMaximized = false;\n            this.bar3.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.btnCode,\n            this.btnCharac,\n            this.btnReset,\n            this.btnLock,\n            this.btnSaveAsGif});\n            this.bar3.Location = new System.Drawing.Point(0, 0);\n            this.bar3.Name = \"bar3\";\n            this.bar3.Size = new System.Drawing.Size(142, 25);\n            this.bar3.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.bar3.TabIndex = 0;\n            this.bar3.TabStop = false;\n            this.bar3.Text = \"工具\";\n            // \n            // btnCode\n            // \n            this.btnCode.Image = global::WzComparerR2.Avatar.Properties.Resources.script_code;\n            this.btnCode.Name = \"btnCode\";\n            this.btnCode.Tooltip = \"代码\";\n            this.btnCode.Click += new System.EventHandler(this.btnCode_Click);\n            // \n            // btnCharac\n            // \n            this.btnCharac.AutoExpandOnClick = true;\n            this.btnCharac.Image = global::WzComparerR2.Avatar.Properties.Resources.user;\n            this.btnCharac.Name = \"btnCharac\";\n            this.btnCharac.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.btnMale,\n            this.btnFemale});\n            this.btnCharac.Tooltip = \"初始化\";\n            // \n            // btnMale\n            // \n            this.btnMale.Name = \"btnMale\";\n            this.btnMale.Text = \"男性角色\";\n            this.btnMale.Click += new System.EventHandler(this.btnMale_Click);\n            // \n            // btnFemale\n            // \n            this.btnFemale.Name = \"btnFemale\";\n            this.btnFemale.Text = \"女性角色\";\n            this.btnFemale.Click += new System.EventHandler(this.btnFemale_Click);\n            // \n            // btnReset\n            // \n            this.btnReset.Image = global::WzComparerR2.Avatar.Properties.Resources.arrow_in;\n            this.btnReset.Name = \"btnReset\";\n            this.btnReset.Tooltip = \"坐标重置\";\n            this.btnReset.Click += new System.EventHandler(this.btnReset_Click);\n            // \n            // btnLock\n            // \n            this.btnLock.AutoCheckOnClick = true;\n            this.btnLock.Image = global::WzComparerR2.Avatar.Properties.Resources._lock;\n            this.btnLock.Name = \"btnLock\";\n            this.btnLock.Tooltip = \"锁定\";\n            // \n            // btnSaveAsGif\n            // \n            this.btnSaveAsGif.Image = global::WzComparerR2.Avatar.Properties.Resources.disk;\n            this.btnSaveAsGif.Name = \"btnSaveAsGif\";\n            this.btnSaveAsGif.Tooltip = \"保存\";\n            this.btnSaveAsGif.Click += new System.EventHandler(this.btnSaveAsGif_Click);\n            // \n            // dockSite3\n            // \n            this.dockSite3.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite3.Dock = System.Windows.Forms.DockStyle.Top;\n            this.dockSite3.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite3.Location = new System.Drawing.Point(0, 25);\n            this.dockSite3.Name = \"dockSite3\";\n            this.dockSite3.Size = new System.Drawing.Size(584, 0);\n            this.dockSite3.TabIndex = 2;\n            this.dockSite3.TabStop = false;\n            // \n            // timer1\n            // \n            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);\n            // \n            // avatarContainer1\n            // \n            this.avatarContainer1.BackColor = System.Drawing.Color.White;\n            this.avatarContainer1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.avatarContainer1.Location = new System.Drawing.Point(0, 25);\n            this.avatarContainer1.Name = \"avatarContainer1\";\n            this.avatarContainer1.Origin = new System.Drawing.Point(0, 0);\n            this.avatarContainer1.Size = new System.Drawing.Size(368, 386);\n            this.avatarContainer1.TabIndex = 8;\n            this.avatarContainer1.Text = \"avatarContainer1\";\n            // \n            // labelX5\n            // \n            this.labelX5.AutoSize = true;\n            this.labelX5.BackColor = System.Drawing.Color.Transparent;\n            // \n            // \n            // \n            this.labelX5.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX5.Location = new System.Drawing.Point(139, 106);\n            this.labelX5.Name = \"labelX5\";\n            this.labelX5.Size = new System.Drawing.Size(25, 16);\n            this.labelX5.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.labelX5.TabIndex = 14;\n            this.labelX5.Text = \"ear\";\n            // \n            // cmbEar\n            // \n            this.cmbEar.DisplayMember = \"Text\";\n            this.cmbEar.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;\n            this.cmbEar.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;\n            this.cmbEar.FormattingEnabled = true;\n            this.cmbEar.ItemHeight = 15;\n            this.cmbEar.Location = new System.Drawing.Point(163, 103);\n            this.cmbEar.Name = \"cmbEar\";\n            this.cmbEar.Size = new System.Drawing.Size(39, 21);\n            this.cmbEar.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.cmbEar.TabIndex = 15;\n            this.cmbEar.SelectedIndexChanged += new System.EventHandler(this.cmbEar_SelectedIndexChanged);\n            // \n            // AvatarForm\n            // \n            this.ClientSize = new System.Drawing.Size(584, 411);\n            this.Controls.Add(this.avatarContainer1);\n            this.Controls.Add(this.dockSite2);\n            this.Controls.Add(this.dockSite1);\n            this.Controls.Add(this.dockSite3);\n            this.Controls.Add(this.dockSite4);\n            this.Controls.Add(this.dockSite5);\n            this.Controls.Add(this.dockSite6);\n            this.Controls.Add(this.dockSite7);\n            this.Controls.Add(this.dockSite8);\n            this.DoubleBuffered = true;\n            this.Name = \"AvatarForm\";\n            this.Text = \"Avatar\";\n            this.dockSite2.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).EndInit();\n            this.bar1.ResumeLayout(false);\n            this.panelDockContainer1.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.bar2)).EndInit();\n            this.bar2.ResumeLayout(false);\n            this.panelDockContainer2.ResumeLayout(false);\n            this.panelDockContainer2.PerformLayout();\n            this.dockSite7.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.bar3)).EndInit();\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.DotNetBarManager dotNetBarManager1;\n        private DevComponents.DotNetBar.DockSite dockSite4;\n        private DevComponents.DotNetBar.DockSite dockSite1;\n        private DevComponents.DotNetBar.DockSite dockSite2;\n        private DevComponents.DotNetBar.Bar bar1;\n        private DevComponents.DotNetBar.PanelDockContainer panelDockContainer1;\n        private DevComponents.DotNetBar.DockContainerItem dockContainerItem1;\n        private DevComponents.DotNetBar.Bar bar2;\n        private DevComponents.DotNetBar.PanelDockContainer panelDockContainer2;\n        private DevComponents.DotNetBar.DockContainerItem dockContainerItem2;\n        private DevComponents.DotNetBar.DockSite dockSite3;\n        private DevComponents.DotNetBar.DockSite dockSite5;\n        private DevComponents.DotNetBar.DockSite dockSite6;\n        private DevComponents.DotNetBar.DockSite dockSite7;\n        private DevComponents.DotNetBar.DockSite dockSite8;\n        private DevComponents.DotNetBar.ItemPanel itemPanel1;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbActionTaming;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbEmotion;\n        private DevComponents.DotNetBar.LabelX labelX3;\n        private DevComponents.DotNetBar.LabelX labelX2;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbActionBody;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkTamingPlay;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkEmotionPlay;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkBodyPlay;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbTamingFrame;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbEmotionFrame;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbBodyFrame;\n        private System.Windows.Forms.Timer timer1;\n        private AvatarContainer avatarContainer1;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkHairCover;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbWeaponIdx;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbWeaponType;\n        private DevComponents.DotNetBar.LabelX labelX4;\n        private DevComponents.DotNetBar.Bar bar3;\n        private DevComponents.DotNetBar.ButtonItem btnCode;\n        private DevComponents.DotNetBar.ButtonItem btnCharac;\n        private DevComponents.DotNetBar.ButtonItem btnReset;\n        private DevComponents.DotNetBar.Controls.CheckBoxX chkHairShade;\n        private DevComponents.DotNetBar.ButtonItem btnLock;\n        private DevComponents.DotNetBar.ButtonItem btnMale;\n        private DevComponents.DotNetBar.ButtonItem btnFemale;\n        private DevComponents.DotNetBar.ButtonItem btnSaveAsGif;\n        private DevComponents.DotNetBar.Controls.ComboBoxEx cmbEar;\n        private DevComponents.DotNetBar.LabelX labelX5;\n    }\n}"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarForm.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing DevComponents.Editors;\nusing DevComponents.DotNetBar.Controls;\nusing WzComparerR2.Common;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.Config;\n\nnamespace WzComparerR2.Avatar.UI\n{\n    internal partial class AvatarForm : DevComponents.DotNetBar.OfficeForm\n    {\n        public AvatarForm()\n        {\n            InitializeComponent();\n            this.avatar = new AvatarCanvas();\n            this.animator = new Animator();\n            btnReset_Click(btnReset, EventArgs.Empty);\n            FillWeaponIdx();\n            FillEarSelection();\n        }\n\n        public SuperTabControlPanel GetTabPanel()\n        {\n            this.TopLevel = false;\n            this.Dock = DockStyle.Fill;\n            this.FormBorderStyle = FormBorderStyle.None;\n            this.DoubleBuffered = true;\n            var pnl = new SuperTabControlPanel();\n            pnl.Controls.Add(this);\n            pnl.Padding = new System.Windows.Forms.Padding(1);\n            this.Visible = true;\n            return pnl;\n        }\n\n        public Entry PluginEntry { get; set; }\n\n        AvatarCanvas avatar;\n        bool inited;\n        string partsTag;\n        bool suspendUpdate;\n        bool needUpdate;\n        Animator animator;\n\n        /// <summary>\n        /// wz1节点选中事件。\n        /// </summary>\n        public void OnSelectedNode1Changed(object sender, WzNodeEventArgs e)\n        {\n            if (PluginEntry.Context.SelectedTab != PluginEntry.Tab || e.Node == null\n                || this.btnLock.Checked)\n            {\n                return;\n            }\n\n            Wz_File file = e.Node.GetNodeWzFile();\n            if (file == null)\n            {\n                return;\n            }\n\n            switch (file.Type)\n            {\n                case Wz_Type.Character: //读取装备\n                    Wz_Image wzImg = e.Node.GetValue<Wz_Image>();\n                    if (wzImg != null && wzImg.TryExtract())\n                    {\n                        this.SuspendUpdateDisplay();\n                        LoadPart(wzImg.Node);\n                        this.ResumeUpdateDisplay();\n                    }\n                    break;\n            }\n        }\n\n        public void OnWzClosing(object sender, WzStructureEventArgs e)\n        {\n            bool hasChanged = false;\n            for (int i = 0; i < avatar.Parts.Length; i++)\n            {\n                var part = avatar.Parts[i];\n                if (part != null)\n                {\n                    var wzFile = part.Node.GetNodeWzFile();\n                    if (wzFile != null && e.WzStructure.wz_files.Contains(wzFile))//将要关闭文件 移除\n                    {\n                        avatar.Parts[i] = null;\n                        hasChanged = true;\n                    }\n                }\n            }\n\n            if (hasChanged)\n            {\n                this.FillAvatarParts();\n                UpdateDisplay();\n            }\n        }\n\n        /// <summary>\n        /// 初始化纸娃娃资源。\n        /// </summary>\n        private bool AvatarInit()\n        {\n            this.inited = this.avatar.LoadZ()\n                && this.avatar.LoadActions()\n                && this.avatar.LoadEmotions();\n\n            if (this.inited)\n            {\n                this.FillBodyAction();\n                this.FillEmotion();\n            }\n            return this.inited;\n        }\n\n        /// <summary>\n        /// 加载装备部件。\n        /// </summary>\n        /// <param name=\"imgNode\"></param>\n        private void LoadPart(Wz_Node imgNode)\n        {\n            if (!this.inited && !this.AvatarInit() && imgNode == null)\n            {\n                return;\n            }\n\n            AvatarPart part = this.avatar.AddPart(imgNode);\n            if (part != null)\n            {\n                OnNewPartAdded(part);\n                FillAvatarParts();\n                UpdateDisplay();\n            }\n        }\n\n        private void OnNewPartAdded(AvatarPart part)\n        {\n            if (part == null)\n            {\n                return;\n            }\n\n            if (part == avatar.Body) //同步head\n            {\n                int headID = 10000 + part.ID.Value % 10000;\n                if (avatar.Head == null || avatar.Head.ID != headID)\n                {\n                    var headImgNode = PluginBase.PluginManager.FindWz(string.Format(\"Character\\\\{0:D8}.img\", headID));\n                    if (headImgNode != null)\n                    {\n                        this.avatar.AddPart(headImgNode);\n                    }\n                }\n            }\n            else if (part == avatar.Head) //同步body\n            {\n                int bodyID = part.ID.Value % 10000;\n                if (avatar.Body == null || avatar.Body.ID != bodyID)\n                {\n                    var bodyImgNode = PluginBase.PluginManager.FindWz(string.Format(\"Character\\\\{0:D8}.img\", bodyID));\n                    if (bodyImgNode != null)\n                    {\n                        this.avatar.AddPart(bodyImgNode);\n                    }\n                }\n            }\n            else if (part == avatar.Face) //同步表情\n            {\n                this.avatar.LoadEmotions();\n                FillEmotion();\n            }\n            else if (part == avatar.Taming) //同步座驾动作\n            {\n                this.avatar.LoadTamingActions();\n                FillTamingAction();\n                SetTamingDefaultBodyAction();\n                SetTamingDefault();\n            }\n            else if (part == avatar.Weapon) //同步武器类型\n            {\n                FillWeaponTypes();\n            }\n            else if (part == avatar.Pants || part == avatar.Coat) //隐藏套装\n            {\n                if (avatar.Longcoat != null)\n                {\n                    avatar.Longcoat.Visible = false;\n                }\n            }\n            else if (part == avatar.Longcoat) //还是。。隐藏套装\n            {\n                if (avatar.Pants != null && avatar.Pants.Visible\n                    || avatar.Coat != null && avatar.Coat.Visible)\n                {\n                    avatar.Longcoat.Visible = false;\n                }\n            }\n        }\n\n        private void SuspendUpdateDisplay()\n        {\n            this.suspendUpdate = true;\n            this.needUpdate = false;\n        }\n\n        private void ResumeUpdateDisplay()\n        {\n            if (this.suspendUpdate)\n            {\n                this.suspendUpdate = false;\n                if (this.needUpdate)\n                {\n                    this.UpdateDisplay();\n                }\n            }\n        }\n\n        /// <summary>\n        /// 更新画布。\n        /// </summary>\n        private void UpdateDisplay()\n        {\n            if (suspendUpdate)\n            {\n                this.needUpdate = true;\n                return;\n            }\n\n            string newPartsTag = GetAllPartsTag();\n            if (this.partsTag != newPartsTag)\n            {\n                this.partsTag = newPartsTag;\n                this.avatarContainer1.ClearAllCache();\n            }\n\n            ComboItem selectedItem;\n            //同步角色动作\n            selectedItem = this.cmbActionBody.SelectedItem as ComboItem;\n            this.avatar.ActionName = selectedItem != null ? selectedItem.Text : null;\n            //同步表情\n            selectedItem = this.cmbEmotion.SelectedItem as ComboItem;\n            this.avatar.EmotionName = selectedItem != null ? selectedItem.Text : null;\n            //同步骑宠动作\n            selectedItem = this.cmbActionTaming.SelectedItem as ComboItem;\n            this.avatar.TamingActionName = selectedItem != null ? selectedItem.Text : null;\n\n            //获取动作帧\n            this.GetSelectedBodyFrame(out int bodyFrame, out _);\n            this.GetSelectedEmotionFrame(out int emoFrame, out _);\n            this.GetSelectedTamingFrame(out int tamingFrame, out _);\n\n            //获取武器状态\n            selectedItem = this.cmbWeaponType.SelectedItem as ComboItem;\n            this.avatar.WeaponType = selectedItem != null ? Convert.ToInt32(selectedItem.Text) : 0;\n\n            selectedItem = this.cmbWeaponIdx.SelectedItem as ComboItem;\n            this.avatar.WeaponIndex = selectedItem != null ? Convert.ToInt32(selectedItem.Text) : 0;\n\n            //获取耳朵状态\n            selectedItem = this.cmbEar.SelectedItem as ComboItem;\n            this.avatar.EarType = selectedItem != null ? Convert.ToInt32(selectedItem.Text) : 0;\n\n            if (bodyFrame < 0 && emoFrame < 0 && tamingFrame < 0)\n            {\n                return;\n            }\n\n            string actionTag = string.Format(\"{0}:{1},{2}:{3},{4}:{5},{6},{7},{8},{9},{10}\",\n                this.avatar.ActionName,\n                bodyFrame,\n                this.avatar.EmotionName,\n                emoFrame,\n                this.avatar.TamingActionName,\n                tamingFrame,\n                this.avatar.HairCover ? 1 : 0,\n                this.avatar.ShowHairShade ? 1 : 0,\n                this.avatar.EarType,\n                this.avatar.WeaponType,\n                this.avatar.WeaponIndex);\n\n            if (!avatarContainer1.HasCache(actionTag))\n            {\n                try\n                {\n                    var actionFrames = avatar.GetActionFrames(avatar.ActionName);\n                    var bone = avatar.CreateFrame(bodyFrame, emoFrame, tamingFrame);\n                    var layers = avatar.CreateFrameLayers(bone);\n                    avatarContainer1.AddCache(actionTag, layers);\n                }\n                catch\n                {\n                }\n            }\n\n            avatarContainer1.SetKey(actionTag);\n        }\n\n        private string GetAllPartsTag()\n        {\n            string[] partsID = new string[avatar.Parts.Length];\n            for (int i = 0; i < avatar.Parts.Length; i++)\n            {\n                var part = avatar.Parts[i];\n                if (part != null && part.Visible)\n                {\n                    partsID[i] = part.ID.ToString();\n                }\n            }\n            return string.Join(\",\", partsID);\n        }\n\n        void AddPart(string imgPath)\n        {\n            Wz_Node imgNode = PluginManager.FindWz(imgPath);\n            if (imgNode != null)\n            {\n                this.avatar.AddPart(imgNode);\n            }\n        }\n\n        private void SelectBodyAction(string actionName)\n        {\n            for (int i = 0; i < cmbActionBody.Items.Count; i++)\n            {\n                ComboItem item = cmbActionBody.Items[i] as ComboItem;\n                if (item != null && item.Text == actionName)\n                {\n                    cmbActionBody.SelectedIndex = i;\n                    return;\n                }\n            }\n        }\n\n        private void SelectEmotion(string emotionName)\n        {\n            for (int i = 0; i < cmbEmotion.Items.Count; i++)\n            {\n                ComboItem item = cmbEmotion.Items[i] as ComboItem;\n                if (item != null && item.Text == emotionName)\n                {\n                    cmbEmotion.SelectedIndex = i;\n                    return;\n                }\n            }\n        }\n\n        #region 同步界面\n        private void FillBodyAction()\n        {\n            var oldSelection = cmbActionBody.SelectedItem as ComboItem;\n            int? newSelection = null;\n            cmbActionBody.BeginUpdate();\n            cmbActionBody.Items.Clear();\n            foreach (var action in this.avatar.Actions)\n            {\n                ComboItem cmbItem = new ComboItem(action.Name);\n                switch (action.Level)\n                {\n                    case 0:\n                        cmbItem.FontStyle = FontStyle.Bold;\n                        cmbItem.ForeColor = Color.Indigo;\n                        break;\n\n                    case 1:\n                        cmbItem.ForeColor = Color.Indigo;\n                        break;\n                }\n                cmbItem.Tag = action;\n                cmbActionBody.Items.Add(cmbItem);\n\n                if (newSelection == null && oldSelection != null)\n                {\n                    if (cmbItem.Text == oldSelection.Text)\n                    {\n                        newSelection = cmbActionBody.Items.Count - 1;\n                    }\n                }\n            }\n\n            if (cmbActionBody.Items.Count > 0)\n            {\n                cmbActionBody.SelectedIndex = newSelection ?? 0;\n            }\n\n            cmbActionBody.EndUpdate();\n        }\n\n        private void FillEmotion()\n        {\n            FillComboItems(cmbEmotion, avatar.Emotions);\n        }\n\n        private void FillTamingAction()\n        {\n            FillComboItems(cmbActionTaming, avatar.TamingActions);\n        }\n\n        private void FillWeaponTypes()\n        {\n            List<int> weaponTypes = avatar.GetCashWeaponTypes();\n            FillComboItems(cmbWeaponType, weaponTypes.ConvertAll(i => i.ToString()));\n        }\n\n        private void SetTamingDefaultBodyAction()\n        {\n            string actionName;\n            var tamingAction = (this.cmbActionTaming.SelectedItem as ComboItem)?.Text;\n            switch (tamingAction)\n            {\n                case \"ladder\":\n                case \"rope\":\n                    actionName = tamingAction;\n                    break;\n                default:\n                    actionName = \"sit\";\n                    break;\n            }\n            SelectBodyAction(actionName);\n        }\n\n        private void SetTamingDefault()\n        {\n            if (this.avatar.Taming != null)\n            {\n                var tamingAction =  (this.cmbActionTaming.SelectedItem as ComboItem)?.Text;\n                if (tamingAction != null)\n                {\n                    string forceAction = this.avatar.Taming.Node.FindNodeByPath($@\"characterAction\\{tamingAction}\").GetValueEx<string>(null);\n                    if (forceAction != null)\n                    {\n                        this.SelectBodyAction(forceAction);\n                    }\n\n                    string forceEmotion = this.avatar.Taming.Node.FindNodeByPath($@\"characterEmotion\\{tamingAction}\").GetValueEx<string>(null);\n                    if (forceEmotion != null)\n                    {\n                        this.SelectEmotion(forceEmotion);\n                    }\n                }\n            }\n        }\n\n        /// <summary>\n        /// 更新当前显示部件列表。\n        /// </summary>\n        private void FillAvatarParts()\n        {\n            itemPanel1.BeginUpdate();\n            itemPanel1.Items.Clear();\n            foreach (var part in avatar.Parts)\n            {\n                if (part != null)\n                {\n                    var btn = new AvatarPartButtonItem();\n                    var stringLinker = this.PluginEntry.Context.DefaultStringLinker;\n                    StringResult sr;\n                    string text;\n                    if (part.ID != null && stringLinker.StringEqp.TryGetValue(part.ID.Value, out sr))\n                    {\n                        text = string.Format(\"{0}\\r\\n{1}\", sr.Name, part.ID);\n                    }\n                    else\n                    {\n                        text = string.Format(\"{0}\\r\\n{1}\", \"(null)\", part.ID == null ? \"-\" : part.ID.ToString());\n                    }\n                    btn.Text = text;\n                    btn.SetIcon(part.Icon.Bitmap);\n                    btn.Tag = part;\n                    btn.Checked = part.Visible;\n                    btn.btnItemShow.Click += BtnItemShow_Click;\n                    btn.btnItemDel.Click += BtnItemDel_Click;\n                    btn.CheckedChanged += Btn_CheckedChanged;\n                    itemPanel1.Items.Add(btn);\n                }\n            }\n            itemPanel1.EndUpdate();\n        }\n\n        private void BtnItemShow_Click(object sender, EventArgs e)\n        {\n            var btn = (sender as BaseItem).Parent as AvatarPartButtonItem;\n            if (btn != null)\n            {\n                btn.Checked = !btn.Checked;\n            }\n        }\n\n        private void BtnItemDel_Click(object sender, EventArgs e)\n        {\n            var btn = (sender as BaseItem).Parent as AvatarPartButtonItem;\n            if (btn != null)\n            {\n                var part = btn.Tag as AvatarPart;\n                if (part != null)\n                {\n                    int index = Array.IndexOf(this.avatar.Parts, part);\n                    if (index > -1)\n                    {\n                        this.avatar.Parts[index] = null;\n                        this.FillAvatarParts();\n                        this.UpdateDisplay();\n                    }\n                }\n            }\n        }\n\n        private void Btn_CheckedChanged(object sender, EventArgs e)\n        {\n            var btn = sender as AvatarPartButtonItem;\n            if (btn != null)\n            {\n                var part = btn.Tag as AvatarPart;\n                if (part != null)\n                {\n                    part.Visible = btn.Checked;\n                    this.UpdateDisplay();\n                }\n            }\n        }\n\n        private void FillBodyActionFrame()\n        {\n            ComboItem actionItem = cmbActionBody.SelectedItem as ComboItem;\n            if (actionItem != null)\n            {\n                var frames = avatar.GetActionFrames(actionItem.Text);\n                FillComboItems(cmbBodyFrame, frames);\n            }\n            else\n            {\n                cmbBodyFrame.Items.Clear();\n            }\n        }\n\n        private void FillEmotionFrame()\n        {\n            ComboItem emotionItem = cmbEmotion.SelectedItem as ComboItem;\n            if (emotionItem != null)\n            {\n                var frames = avatar.GetFaceFrames(emotionItem.Text);\n                FillComboItems(cmbEmotionFrame, frames);\n            }\n            else\n            {\n                cmbEmotionFrame.Items.Clear();\n            }\n        }\n\n        private void FillTamingActionFrame()\n        {\n            ComboItem actionItem = cmbActionTaming.SelectedItem as ComboItem;\n            if (actionItem != null)\n            {\n                var frames = avatar.GetTamingFrames(actionItem.Text);\n                FillComboItems(cmbTamingFrame, frames);\n            }\n            else\n            {\n                cmbTamingFrame.Items.Clear();\n            }\n        }\n\n        private void FillWeaponIdx()\n        {\n            FillComboItems(cmbWeaponIdx, 0, 4);\n        }\n\n        private void FillEarSelection()\n        {\n            FillComboItems(cmbEar, 0, 4);\n        }\n\n        private void FillComboItems(ComboBoxEx comboBox, int start, int count)\n        {\n            List<ComboItem> items = new List<ComboItem>(count);\n            for (int i = 0; i < count; i++)\n            {\n                ComboItem item = new ComboItem();\n                item.Text = (start + i).ToString();\n                items.Add(item);\n            }\n            FillComboItems(comboBox, items);\n        }\n\n        private void FillComboItems(ComboBoxEx comboBox, IEnumerable<string> items)\n        {\n            List<ComboItem> _items = new List<ComboItem>();\n            foreach (var itemText in items)\n            {\n                ComboItem item = new ComboItem();\n                item.Text = itemText;\n                _items.Add(item);\n            }\n            FillComboItems(comboBox, _items);\n        }\n\n        private void FillComboItems(ComboBoxEx comboBox, IEnumerable<ActionFrame> frames)\n        {\n            List<ComboItem> items = new List<ComboItem>();\n            int i = 0;\n            foreach (var f in frames)\n            {\n                ComboItem item = new ComboItem();\n                item.Text = (i++).ToString();\n                item.Tag = f;\n                items.Add(item);\n            }\n            FillComboItems(comboBox, items);\n        }\n\n        private void FillComboItems(ComboBoxEx comboBox, IEnumerable<ComboItem> items)\n        {\n            //保持原有选项\n            var oldSelection = comboBox.SelectedItem as ComboItem;\n            int? newSelection = null;\n            comboBox.BeginUpdate();\n            comboBox.Items.Clear();\n\n            foreach (var item in items)\n            {\n                comboBox.Items.Add(item);\n\n                if (newSelection == null && oldSelection != null)\n                {\n                    if (item.Text == oldSelection.Text)\n                    {\n                        newSelection = comboBox.Items.Count - 1;\n                    }\n                }\n            }\n\n            //恢复原有选项\n            if (comboBox.Items.Count > 0)\n            {\n                comboBox.SelectedIndex = newSelection ?? 0;\n            }\n\n            comboBox.EndUpdate();\n        }\n\n        private bool GetSelectedActionFrame(ComboBoxEx comboBox, out int frameIndex, out ActionFrame actionFrame)\n        {\n            var selectedItem = comboBox.SelectedItem as ComboItem;\n            if (selectedItem != null\n                && int.TryParse(selectedItem.Text, out frameIndex)\n                && selectedItem?.Tag is ActionFrame _actionFrame)\n            {\n                actionFrame = _actionFrame;\n                return true;\n            }\n            else\n            {\n                frameIndex = -1;\n                actionFrame = null;\n                return false;\n            }\n        }\n\n        private bool GetSelectedBodyFrame(out int frameIndex, out ActionFrame actionFrame)\n        {\n            return this.GetSelectedActionFrame(this.cmbBodyFrame, out frameIndex, out actionFrame);\n        }\n\n        private bool GetSelectedEmotionFrame(out int frameIndex, out ActionFrame actionFrame)\n        {\n            return this.GetSelectedActionFrame(this.cmbEmotionFrame, out frameIndex, out actionFrame);\n        }\n\n        private bool GetSelectedTamingFrame(out int frameIndex, out ActionFrame actionFrame)\n        {\n            return this.GetSelectedActionFrame(this.cmbTamingFrame, out frameIndex, out actionFrame);\n        }\n        #endregion\n\n        private void cmbActionBody_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            this.SuspendUpdateDisplay();\n            FillBodyActionFrame();\n            this.ResumeUpdateDisplay();\n            UpdateDisplay();\n        }\n\n        private void cmbEmotion_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            this.SuspendUpdateDisplay();\n            FillEmotionFrame();\n            this.ResumeUpdateDisplay();\n            UpdateDisplay();\n        }\n\n        private void cmbActionTaming_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            this.SuspendUpdateDisplay();\n            FillTamingActionFrame();\n            SetTamingDefaultBodyAction();\n            SetTamingDefault();\n            this.ResumeUpdateDisplay();\n            UpdateDisplay();\n        }\n\n        private void cmbBodyFrame_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            UpdateDisplay();\n        }\n\n        private void cmbEmotionFrame_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            UpdateDisplay();\n        }\n\n        private void cmbTamingFrame_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            UpdateDisplay();\n        }\n\n        private void cmbWeaponType_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            UpdateDisplay();\n        }\n\n        private void cmbWeaponIdx_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            UpdateDisplay();\n        }\n\n        private void cmbEar_SelectedIndexChanged(object sender, EventArgs e)\n        {\n            UpdateDisplay();\n        }\n\n        private void chkBodyPlay_CheckedChanged(object sender, EventArgs e)\n        {\n            if (chkBodyPlay.Checked)\n            {\n                if (!this.timer1.Enabled)\n                {\n                    AnimateStart();\n                }\n\n                if (this.GetSelectedBodyFrame(out _, out var actionFrame) && actionFrame.AbsoluteDelay > 0)\n                {\n                    this.animator.BodyDelay = actionFrame.AbsoluteDelay;\n                }\n            }\n            else\n            {\n                this.animator.BodyDelay = -1;\n                TimerEnabledCheck();\n            }\n        }\n\n        private void chkEmotionPlay_CheckedChanged(object sender, EventArgs e)\n        {\n            if (chkEmotionPlay.Checked)\n            {\n                if (!this.timer1.Enabled)\n                {\n                    AnimateStart();\n                }\n\n                if (this.GetSelectedEmotionFrame(out _, out var actionFrame) && actionFrame.AbsoluteDelay > 0)\n                {\n                    this.animator.EmotionDelay = actionFrame.AbsoluteDelay;\n                }\n            }\n            else\n            {\n                this.animator.EmotionDelay = -1;\n                TimerEnabledCheck();\n            }\n        }\n\n        private void chkTamingPlay_CheckedChanged(object sender, EventArgs e)\n        {\n            if (chkTamingPlay.Checked)\n            {\n                if (!this.timer1.Enabled)\n                {\n                    AnimateStart();\n                }\n\n                if (this.GetSelectedTamingFrame(out _, out var actionFrame) && actionFrame.AbsoluteDelay > 0)\n                {\n                    this.animator.TamingDelay = actionFrame.AbsoluteDelay;\n                }\n            }\n            else\n            {\n                this.animator.TamingDelay = -1;\n                TimerEnabledCheck();\n            }\n        }\n\n        private void chkHairCover_CheckedChanged(object sender, EventArgs e)\n        {\n            avatar.HairCover = chkHairCover.Checked;\n            UpdateDisplay();\n        }\n\n        private void chkHairShade_CheckedChanged(object sender, EventArgs e)\n        {\n            avatar.ShowHairShade = chkHairShade.Checked;\n            UpdateDisplay();\n        }\n\n        private void timer1_Tick(object sender, EventArgs e)\n        {\n            this.animator.Elapse(timer1.Interval);\n            this.AnimateUpdate();\n            int interval = this.animator.NextFrameDelay;\n\n            if (interval <= 0)\n            {\n                this.timer1.Stop();\n            }\n            else\n            {\n                this.timer1.Interval = interval;\n            }\n        }\n\n        private void AnimateUpdate()\n        {\n            this.SuspendUpdateDisplay();\n\n            if (this.animator.BodyDelay == 0 && FindNextFrame(cmbBodyFrame) && this.GetSelectedBodyFrame(out _, out var bodyFrame))\n            {\n                this.animator.BodyDelay = bodyFrame.AbsoluteDelay;\n            }\n\n            if (this.animator.EmotionDelay == 0 && FindNextFrame(cmbEmotionFrame) && this.GetSelectedEmotionFrame(out _, out var emoFrame))\n            {\n                this.animator.EmotionDelay = emoFrame.AbsoluteDelay;\n            }\n\n            if (this.animator.TamingDelay == 0 && FindNextFrame(cmbTamingFrame) && this.GetSelectedTamingFrame(out _, out var tamingFrame))\n            {\n                this.animator.TamingDelay = tamingFrame.AbsoluteDelay;\n            }\n\n            this.ResumeUpdateDisplay();\n        }\n\n        private void AnimateStart()\n        {\n            TimerEnabledCheck();\n            if (timer1.Enabled)\n            {\n                AnimateUpdate();\n            }\n        }\n\n        private void TimerEnabledCheck()\n        {\n            if (chkBodyPlay.Checked || chkEmotionPlay.Checked || chkTamingPlay.Checked)\n            {\n                if (!this.timer1.Enabled)\n                {\n                    this.timer1.Interval = 1;\n                    this.timer1.Start();\n                }\n            }\n            else\n            {\n                AnimateStop();\n            }\n        }\n\n        private void AnimateStop()\n        {\n            chkBodyPlay.Checked = false;\n            chkEmotionPlay.Checked = false;\n            chkTamingPlay.Checked = false;\n            this.timer1.Stop();\n        }\n\n        private bool FindNextFrame(ComboBoxEx cmbFrames)\n        {\n            ComboItem item = cmbFrames.SelectedItem as ComboItem;\n            if (item == null)\n            {\n                if (cmbFrames.Items.Count > 0)\n                {\n                    cmbFrames.SelectedIndex = 0;\n                    return true;\n                }\n                else\n                {\n                    return false;\n                }\n            }\n\n            int selectedIndex = cmbFrames.SelectedIndex;\n            int i = selectedIndex;\n            do\n            {\n                i = (++i) % cmbFrames.Items.Count;\n                item = cmbFrames.Items[i] as ComboItem;\n                if (item != null && item.Tag is ActionFrame actionFrame && actionFrame.AbsoluteDelay > 0)\n                {\n                    cmbFrames.SelectedIndex = i;\n                    return true;\n                }\n            }\n            while (i != selectedIndex);\n\n            return false;\n        }\n\n        private void btnCode_Click(object sender, EventArgs e)\n        {\n            var dlg = new AvatarCodeForm();\n            string code = GetAllPartsTag();\n            dlg.CodeText = code;\n            if (dlg.ShowDialog() == DialogResult.OK)\n            {\n                if (dlg.CodeText != code && !string.IsNullOrEmpty(dlg.CodeText))\n                {\n                    LoadCode(dlg.CodeText, dlg.LoadType);\n                }\n            }\n        }\n\n        private void btnMale_Click(object sender, EventArgs e)\n        {\n            if (this.avatar.Parts.All(part => part == null) \n                || MessageBoxEx.Show(\"初始化为男性角色？\", \"提示\") == DialogResult.OK)\n            {\n                LoadCode(\"2000,12000,20000,30000,1040036,1060026\", 0);\n            }\n        }\n\n        private void btnFemale_Click(object sender, EventArgs e)\n        {\n            if (this.avatar.Parts.All(part => part == null)\n                || MessageBoxEx.Show(\"初始化为女性角色？\", \"提示\") == DialogResult.OK)\n            {\n                LoadCode(\"2000,12000,21000,31000,1041046,1061039\", 0);\n            }\n        }\n\n        private void btnReset_Click(object sender, EventArgs e)\n        {\n            this.avatarContainer1.Origin = new Point(this.avatarContainer1.Width / 2, this.avatarContainer1.Height / 2 + 40);\n            this.avatarContainer1.Invalidate();\n        }\n\n        private void btnSaveAsGif_Click(object sender, EventArgs e)\n        {\n            bool bodyPlaying = chkBodyPlay.Checked && cmbBodyFrame.Items.Count > 1;\n            bool emoPlaying = chkEmotionPlay.Checked && cmbEmotionFrame.Items.Count > 1;\n            bool tamingPlaying = chkTamingPlay.Checked && cmbTamingFrame.Items.Count > 1;\n\n            int aniCount = new[] { bodyPlaying, emoPlaying, tamingPlaying }.Count(b => b);\n\n            if (aniCount == 0)\n            {\n                // no animation is playing, save as png\n                var dlg = new SaveFileDialog()\n                {\n                    Title = \"Save avatar frame\",\n                    Filter = \"*.png|*.png|*.*|*.*\",\n                    FileName = \"avatar.png\"\n                };\n\n                if (dlg.ShowDialog() != DialogResult.OK)\n                {\n                    return;\n                }\n\n                this.GetSelectedBodyFrame(out int bodyFrame, out _);\n                this.GetSelectedEmotionFrame(out int emoFrame, out _);\n                this.GetSelectedTamingFrame(out int tamingFrame, out _);\n\n                var bone = this.avatar.CreateFrame(bodyFrame, emoFrame, tamingFrame);\n                var frame = this.avatar.DrawFrame(bone);\n                frame.Bitmap.Save(dlg.FileName, System.Drawing.Imaging.ImageFormat.Png);\n            }\n            else\n            {\n                // get default encoder\n                var config = ImageHandlerConfig.Default;\n                using var encoder = AnimateEncoderFactory.CreateEncoder(config);\n                var cap = encoder.Compatibility;\n\n                string extensionFilter = string.Join(\";\", cap.SupportedExtensions.Select(ext => $\"*{ext}\"));\n\n                var dlg = new SaveFileDialog()\n                {\n                    Title = \"Save avatar\",\n                    Filter = string.Format(\"{0} Supported Files ({1})|{1}|All files (*.*)|*.*\", encoder.Name, extensionFilter),\n                    FileName = string.Format(\"avatar{0}\", cap.DefaultExtension)\n                };\n\n                if (dlg.ShowDialog() != DialogResult.OK)\n                {\n                    return;\n                }\n\n                string outputFileName = dlg.FileName;\n                var actPlaying = new[] { bodyPlaying, emoPlaying, tamingPlaying };\n                var actFrames = new[] { cmbBodyFrame, cmbEmotionFrame, cmbTamingFrame }\n                    .Select((cmb, i) =>\n                    {\n                        if (actPlaying[i])\n                        {\n                            return cmb.Items.OfType<ComboItem>().Select(cmbItem => new\n                            {\n                                index = int.Parse(cmbItem.Text),\n                                actionFrame = cmbItem.Tag as ActionFrame,\n                            }).ToArray();\n                        }\n                        else if (this.GetSelectedActionFrame(cmb, out var index, out var actionFrame))\n                        {\n                            return new[] { new { index, actionFrame } };\n                        }\n                        else\n                        {\n                            return null;\n                        }\n                    }).ToArray();\n\n                var gifLayer = new GifLayer();\n\n                if (aniCount == 1 && !cap.IsFixedFrameRate)\n                {\n                    int aniActIndex = Array.FindIndex(actPlaying, b => b);\n                    for (int fIdx = 0, fCnt = actFrames[aniActIndex].Length; fIdx < fCnt; fIdx++)\n                    {\n                        int[] actionIndices = new int[] { -1, -1, -1 };\n                        int delay = 0;\n                        for (int i = 0; i < actFrames.Length; i++)\n                        {\n                            var act = actFrames[i];\n                            if (i == aniActIndex)\n                            {\n                                actionIndices[i] = act[fIdx].index;\n                                delay = act[fIdx].actionFrame.AbsoluteDelay;\n                            }\n                            else if (act != null)\n                            {\n                                actionIndices[i] = act[0].index;\n                            }\n                        }\n                        var bone = this.avatar.CreateFrame(actionIndices[0], actionIndices[1], actionIndices[2]);\n                        var frameData = this.avatar.DrawFrame(bone);\n                        gifLayer.AddFrame(new GifFrame(frameData.Bitmap, frameData.Origin, delay));\n                    }\n                }\n                else\n                {\n                    // more than 2 animating action parts, for simplicity, we use fixed frame delay.\n                    int aniLength = actFrames.Max(layer => layer == null ? 0 : layer.Sum(f => f.actionFrame.AbsoluteDelay));\n                    int aniDelay = config.MinDelay;\n\n                    // pipeline functions\n                    IEnumerable<int> RenderDelay()\n                    {\n                        int t = 0;\n                        while (t < aniLength)\n                        {\n                            int frameDelay = Math.Min(aniLength - t, aniDelay);\n                            t += frameDelay;\n                            yield return frameDelay;\n                        }\n                    }\n\n                    IEnumerable<Tuple<int[], int>> GetFrameActionIndices(IEnumerable<int> delayEnumerator)\n                    {\n                        int[] time = new int[actFrames.Length];\n                        int[] actionState = new int[actFrames.Length];\n                        for (int i = 0; i < actionState.Length; i++)\n                        {\n                            actionState[i] = actFrames[i] != null ? 0 : -1;\n                        }\n\n                        foreach (int delay in delayEnumerator)\n                        {\n                            // return state\n                            int[] actIndices = new int[actionState.Length];\n                            for (int i = 0; i < actionState.Length; i++)\n                            {\n                                actIndices[i] = actionState[i] > -1 ? actFrames[i][actionState[i]].index : -1;\n                            }\n                            yield return Tuple.Create(actIndices, delay);\n\n                            // update state\n                            for (int i = 0; i < actionState.Length; i++)\n                            {\n                                if (actPlaying[i])\n                                {\n                                    var act = actFrames[i];\n                                    time[i] += delay;\n                                    int frameIndex = actionState[i];\n                                    while (time[i] >= act[frameIndex].actionFrame.AbsoluteDelay)\n                                    {\n                                        time[i] -= act[frameIndex].actionFrame.AbsoluteDelay;\n                                        frameIndex = (frameIndex + 1) % act.Length;\n                                    }\n                                    actionState[i] = frameIndex;\n                                }\n                            }\n                        }\n                    }\n\n                    IEnumerable<Tuple<int[], int>> MergeFrames(IEnumerable<Tuple<int[], int>> frames)\n                    {\n                        int[] prevFrame = null;\n                        int prevDelay = 0;\n\n                        foreach (var frame in frames)\n                        {\n                            int[] currentFrame = frame.Item1;\n                            int currentDelay = frame.Item2;\n\n                            if (prevFrame == null)\n                            {\n                                prevFrame = currentFrame;\n                                prevDelay = currentDelay;\n                            }\n                            else if (prevFrame.SequenceEqual(currentFrame))\n                            {\n                                prevDelay += currentDelay;\n                            }\n                            else\n                            {\n                                yield return Tuple.Create(prevFrame, prevDelay);\n                                prevFrame = currentFrame;\n                                prevDelay = currentDelay;\n                            }\n                        }\n\n                        if (prevFrame != null)\n                        {\n                            yield return Tuple.Create(prevFrame, prevDelay);\n                        }\n                    }\n\n                    GifFrame ApplyFrame(int[] actionIndices, int delay)\n                    {\n                        var bone = this.avatar.CreateFrame(actionIndices[0], actionIndices[1], actionIndices[2]);\n                        var frameData = this.avatar.DrawFrame(bone);\n                        return new GifFrame(frameData.Bitmap, frameData.Origin, delay);\n                    }\n\n                    // build pipeline\n                    var step1 = RenderDelay();\n                    var step2 = GetFrameActionIndices(step1);\n                    var step3 = cap.IsFixedFrameRate ? step2 : MergeFrames(step2);\n                    var step4 = step3.Select(tp => ApplyFrame(tp.Item1, tp.Item2));\n\n                    // run pipeline\n                    foreach(var gifFrame in step4)\n                    {\n                        gifLayer.AddFrame(gifFrame);\n                    }\n                }\n\n                if (gifLayer.Frames.Count <= 0)\n                {\n                    MessageBoxEx.Show(this, \"计算动画数据失败。\", \"Error\");\n                    return;\n                }\n\n                Rectangle clientRect = gifLayer.Frames\n                    .Select(f => new Rectangle(-f.Origin.X, -f.Origin.Y, f.Bitmap.Width, f.Bitmap.Height))\n                    .Aggregate((rect1, rect2) =>\n                    {\n                        int left = Math.Min(rect1.X, rect2.X);\n                        int top = Math.Min(rect1.Y, rect2.Y);\n                        int right = Math.Max(rect1.Right, rect2.Right);\n                        int bottom = Math.Max(rect1.Bottom, rect2.Bottom);\n                        return new Rectangle(left, top, right - left, bottom - top);\n                    });\n\n                Brush CreateBackgroundBrush()\n                {\n                    switch (config.BackgroundType.Value)\n                    {\n                        default:\n                        case ImageBackgroundType.Transparent:\n                            return null;\n                        case ImageBackgroundType.Color:\n                            return new SolidBrush(config.BackgroundColor.Value);\n                        case ImageBackgroundType.Mosaic:\n                            int blockSize = Math.Max(1, config.MosaicInfo.BlockSize);\n                            var texture = new Bitmap(blockSize * 2, blockSize * 2);\n                            using (var g = Graphics.FromImage(texture))\n                            using (var brush0 = new SolidBrush(config.MosaicInfo.Color0))\n                            using (var brush1 = new SolidBrush(config.MosaicInfo.Color1))\n                            {\n                                g.FillRectangle(brush0, 0, 0, blockSize, blockSize);\n                                g.FillRectangle(brush0, blockSize, blockSize, blockSize, blockSize);\n                                g.FillRectangle(brush1, 0, blockSize, blockSize, blockSize);\n                                g.FillRectangle(brush1, blockSize, 0, blockSize, blockSize);\n                            }\n                            return new TextureBrush(texture);\n                    }\n                }\n\n                using var bgBrush = CreateBackgroundBrush();\n                encoder.Init(outputFileName, clientRect.Width, clientRect.Height);\n                foreach (IGifFrame gifFrame in gifLayer.Frames)\n                {\n                    using (var bmp = new Bitmap(clientRect.Width, clientRect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))\n                    {\n                        using (var g = Graphics.FromImage(bmp))\n                        {\n                            // draw background\n                            if (bgBrush != null)\n                            {\n                                g.FillRectangle(bgBrush, 0, 0, bmp.Width, bmp.Height);\n                            }\n                            gifFrame.Draw(g, clientRect);\n                        }\n                        encoder.AppendFrame(bmp, Math.Max(cap.MinFrameDelay, gifFrame.Delay));\n                    }\n                }\n            }\n        }\n\n        private void LoadCode(string code, int loadType)\n        {\n            //解析\n            var matches = Regex.Matches(code, @\"(\\d+)([,\\s]|$)\");\n            if (matches.Count <= 0)\n            {\n                MessageBoxEx.Show(\"无法解析的装备代码。\", \"错误\");\n                return;\n            }\n\n            if (PluginManager.FindWz(Wz_Type.Base) == null)\n            {\n                MessageBoxEx.Show(\"没有打开Base.Wz。\", \"错误\");\n                return;\n            }\n\n            var characWz = PluginManager.FindWz(Wz_Type.Character);\n\n            //试图初始化\n            if (!this.inited && !this.AvatarInit())\n            {\n                MessageBoxEx.Show(\"Avatar初始化失败。\", \"错误\");\n                return;\n            }\n            var sl = this.PluginEntry.Context.DefaultStringLinker;\n            if (!sl.HasValues) //生成默认stringLinker\n            {\n                sl.Load(PluginManager.FindWz(Wz_Type.String).GetValueEx<Wz_File>(null));\n            }\n\n            if (loadType == 0) //先清空。。\n            {\n                Array.Clear(this.avatar.Parts, 0, this.avatar.Parts.Length);\n            }\n\n            List<int> failList = new List<int>();\n\n            foreach (Match m in matches)\n            {\n                int gearID;\n                if (Int32.TryParse(m.Result(\"$1\"), out gearID))\n                {\n                    Wz_Node imgNode = FindNodeByGearID(characWz, gearID);\n                    if (imgNode != null)\n                    {\n                        var part = this.avatar.AddPart(imgNode);\n                        OnNewPartAdded(part);\n                    }\n                    else\n                    {\n                        failList.Add(gearID);\n                    }\n                }\n            }\n\n            //刷新\n            this.FillAvatarParts();\n            this.UpdateDisplay();\n\n            //其他提示\n            if (failList.Count > 0)\n            {\n                StringBuilder sb = new StringBuilder();\n                sb.AppendLine(\"以下部件没有找到：\");\n                foreach (var gearID in failList)\n                {\n                    sb.Append(\"  \").AppendLine(gearID.ToString(\"D8\"));\n                }\n                MessageBoxEx.Show(sb.ToString(), \"嗯..\");\n            }\n\n        }\n\n        private Wz_Node FindNodeByGearID(Wz_Node characWz, int id)\n        {\n            string imgName = id.ToString(\"D8\") + \".img\";\n            Wz_Node imgNode = null;\n\n            foreach (var node1 in characWz.Nodes)\n            {\n                if (node1.Text == imgName)\n                {\n                    imgNode = node1;\n                    break;\n                }\n                else if (node1.Nodes.Count > 0)\n                {\n                    foreach (var node2 in node1.Nodes)\n                    {\n                        if (node2.Text == imgName)\n                        {\n                            imgNode = node2;\n                            break;\n                        }\n                    }\n                    if (imgNode != null)\n                    {\n                        break;\n                    }\n                }\n            }\n\n            if (imgNode != null)\n            {\n                Wz_Image img = imgNode.GetValue<Wz_Image>();\n                if (img != null && img.TryExtract())\n                {\n                    return img.Node;\n                }\n            }\n\n            return null;\n        }\n\n        private class Animator\n        {\n            public Animator()\n            {\n                this.delays = new int[3] { -1, -1, -1 };\n            }\n\n            private int[] delays;\n\n            public int NextFrameDelay { get; private set; }\n\n            public int BodyDelay\n            {\n                get { return this.delays[0]; }\n                set\n                {\n                    this.delays[0] = value;\n                    Update();\n                }\n            }\n\n            public int EmotionDelay\n            {\n                get { return this.delays[1]; }\n                set\n                {\n                    this.delays[1] = value;\n                    Update();\n                }\n            }\n\n            public int TamingDelay\n            {\n                get { return this.delays[2]; }\n                set\n                {\n                    this.delays[2] = value;\n                    Update();\n                }\n            }\n\n            public void Elapse(int millisecond)\n            {\n                for (int i = 0; i < delays.Length; i++)\n                {\n                    if (delays[i] >= 0)\n                    {\n                        delays[i] = delays[i] > millisecond ? (delays[i] - millisecond) : 0;\n                    }\n                }\n            }\n\n            private void Update()\n            {\n                int nextFrame = 0;\n                foreach (int delay in this.delays)\n                {\n                    if (delay > 0)\n                    {\n                        nextFrame = nextFrame <= 0 ? delay : Math.Min(nextFrame, delay);\n                    }\n                }\n                this.NextFrameDelay = nextFrame;\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarForm.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <metadata name=\"dotNetBarManager1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 17</value>\n  </metadata>\n  <metadata name=\"timer1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>190, 17</value>\n  </metadata>\n</root>"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarPartButtonItem.Designer.cs",
    "content": "﻿namespace WzComparerR2.Avatar.UI\n{\n    partial class AvatarPartButtonItem\n    {\n        /// <summary>\n        /// 必需的设计器变量。\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// 清理所有正在使用的资源。\n        /// </summary>\n        /// <param name=\"disposing\">如果应释放托管资源，为 true；否则为 false。</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows 窗体设计器生成的代码\n\n        /// <summary>\n        /// 设计器支持所需的方法 - 不要\n        /// 使用代码编辑器修改此方法的内容。\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.btnItemShow = new DevComponents.DotNetBar.ButtonItem();\n            this.btnItemDel = new DevComponents.DotNetBar.ButtonItem();\n            // \n            // btnItemShow\n            // \n            this.btnItemShow.Name = \"btnItemShow\";\n            this.btnItemShow.Text = \"显示/隐藏\";\n            // \n            // btnItemDel\n            // \n            this.btnItemDel.Name = \"btnItemDel\";\n            this.btnItemDel.Text = \"移除\";\n            // \n            // AvatarPartButtonItem\n            // \n            this.AutoCheckOnClick = true;\n            this.ButtonStyle = DevComponents.DotNetBar.eButtonStyle.ImageAndText;\n            this.GlobalItem = false;\n            this.ImageFixedSize = new System.Drawing.Size(32, 32);\n            this.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.btnItemShow,\n            this.btnItemDel});\n            this.SubItemsExpandWidth = 16;\n\n        }\n\n        #endregion\n\n        public DevComponents.DotNetBar.ButtonItem btnItemShow;\n        public DevComponents.DotNetBar.ButtonItem btnItemDel;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarPartButtonItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Text;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing System.Drawing.Imaging;\n\nnamespace WzComparerR2.Avatar.UI\n{\n    internal partial class AvatarPartButtonItem : ButtonItem\n    {\n        public AvatarPartButtonItem()\n        {\n            InitializeComponent();\n        }\n\n        public void SetIcon(Bitmap icon)\n        {\n            if (icon != null)\n            {\n                if (!this.ImageFixedSize.IsEmpty && icon.Size != this.ImageFixedSize)\n                {\n                    Bitmap newIcon = new Bitmap(this.ImageFixedSize.Width, this.ImageFixedSize.Height, PixelFormat.Format32bppArgb);\n                    Graphics g = Graphics.FromImage(newIcon);\n                    int x = (newIcon.Width - icon.Width) / 2;\n                    int y = (newIcon.Height - icon.Height) / 2;\n                    g.DrawImage(icon, x, y);\n                    g.Dispose();\n                    this.Image = newIcon;\n                }\n                else\n                {\n                    this.Image = icon;\n                }\n            }\n            else\n            {\n                this.Image = null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Avatar/UI/AvatarPartButtonItem.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <metadata name=\"$this.TrayLargeIcon\" type=\"System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\">\n    <value>False</value>\n  </metadata>\n</root>"
  },
  {
    "path": "WzComparerR2.Avatar/WzComparerR2.Avatar.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2.Avatar</AssemblyName>\n    <RootNamespace>WzComparerR2.Avatar</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>\n    <WcR2Plugin>true</WcR2Plugin>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n    \n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    \n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n\n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Configuration\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WzComparerR2.Common\\WzComparerR2.Common.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2.PluginBase\\WzComparerR2.PluginBase.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2.WzLib\\WzComparerR2.WzLib.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2\\WzComparerR2.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <Reference Include=\"DevComponents.DotNetBar2\">\n      <HintPath>..\\References\\DevComponents.DotNetBar2.dll</HintPath>\n      <Private>false</Private>\n    </Reference>\n    <PackageReference Include=\"System.Resources.Extensions\" Version=\"$(SystemResourcesExtensionsVersion)\" ExcludeAssets=\"runtime\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\WcR2Plugin.targets\" />\n</Project>"
  },
  {
    "path": "WzComparerR2.Common/Animation/Frame.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.Animation\n{\n    public class Frame\n    {\n        public Frame()\n        {\n            this.A0 = 255;\n            this.A1 = 255;\n        }\n\n        public Frame(Texture2D texture) : this(texture, null)\n        {\n        }\n\n        public Frame(Texture2D atlasPage, Rectangle? atlasRect) : this()\n        {\n            this.Texture = atlasPage;\n            this.AtlasRect = atlasRect;\n        }\n\n        public Texture2D Texture { get; set; }\n        public Rectangle? AtlasRect { get; set; }\n        public Wz_Png Png { get; set; }\n        public int Page { get; set; }\n        public Point Origin { get; set; }\n        public int Z { get; set; }\n        public int Delay { get; set; }\n        public int A0 { get; set; }\n        public int A1 { get; set; }\n        public bool Blend { get; set; }\n\n        public Rectangle Rectangle\n        {\n            get\n            {\n                if (AtlasRect != null)\n                {\n                    return new Rectangle(-Origin.X, -Origin.Y, AtlasRect.Value.Width, AtlasRect.Value.Height);\n                }\n                else if (Texture != null)\n                {\n                    return new Rectangle(-Origin.X, -Origin.Y, Texture.Width, Texture.Height);\n                }\n                else\n                {\n                    return Rectangle.Empty;\n                }   \n            }\n        }\n\n        public static Frame CreateFromNode(Wz_Node frameNode, GraphicsDevice graphicsDevice, GlobalFindNodeFunction findNode)\n        {\n            if (frameNode == null || frameNode.Value == null)\n            {\n                return null;\n            }\n\n            while (frameNode.Value is Wz_Uol)\n            {\n                Wz_Uol uol = frameNode.Value as Wz_Uol;\n                Wz_Node uolNode = uol.HandleUol(frameNode);\n                if (uolNode != null)\n                {\n                    frameNode = uolNode;\n                }\n                else\n                {\n                    break;\n                }\n            }\n            if (frameNode.Value is Wz_Png)\n            {\n                var linkNode = frameNode.GetLinkedSourceNode(findNode);\n                Wz_Png png = linkNode?.GetValue<Wz_Png>() ?? (Wz_Png)frameNode.Value;\n\n                var frame = new Frame(png.ToTexture(graphicsDevice))\n                {\n                    Png = png,\n                };\n\n                foreach (Wz_Node propNode in frameNode.Nodes)\n                {\n                    switch (propNode.Text)\n                    {\n                        case \"origin\":\n                            frame.Origin = (propNode.Value as Wz_Vector).ToPoint();\n                            break;\n                        case \"delay\":\n                            frame.Delay = propNode.GetValue<int>();\n                            break;\n                        case \"z\":\n                            frame.Z = propNode.GetValue<int>();\n                            break;\n                        case \"a0\":\n                            frame.A0 = propNode.GetValue<int>();\n                            break;\n                        case \"a1\":\n                            frame.A1 = propNode.GetValue<int>();\n                            break;\n                    }\n                }\n\n                if (frame.Delay == 0)\n                {\n                    frame.Delay = 120;//给予默认delay\n                }\n                return frame;\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/FrameAnimationData.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Animation\n{\n    public class FrameAnimationData \n    {\n        public FrameAnimationData()\n        {\n            this.Frames = new List<Frame>();\n        }\n\n        public FrameAnimationData(IEnumerable<Frame> frames)\n        {\n            this.Frames = new List<Frame>(frames);\n        }\n\n        public List<Frame> Frames { get; private set; }\n\n        public Rectangle GetBound()\n        {\n            Rectangle? bound = null;\n            foreach (var frame in this.Frames)\n            {\n                bound = bound == null ? frame.Rectangle : Rectangle.Union(frame.Rectangle, bound.Value);\n            }\n            return bound ?? Rectangle.Empty;\n        }\n\n        public static FrameAnimationData CreateFromNode(Wz_Node node, GraphicsDevice graphicsDevice, FrameAnimationCreatingOptions options, GlobalFindNodeFunction findNode)\n        {\n            if (node == null)\n                return null;\n            var anime = new FrameAnimationData();\n            if (options.HasFlag(FrameAnimationCreatingOptions.ScanAllChildrenFrames))\n            {\n                foreach(var frameNode in node.Nodes)\n                {\n                    Frame frame = Frame.CreateFromNode(frameNode, graphicsDevice, findNode);\n                    if (frame != null)\n                    {\n                        anime.Frames.Add(frame);\n                    }\n                }\n            }\n            else\n            {\n                for (int i = 0; ; i++)\n                {\n                    Wz_Node frameNode = node.FindNodeByPath(i.ToString());\n\n                    if (frameNode == null || frameNode.Value == null)\n                        break;\n                    Frame frame = Frame.CreateFromNode(frameNode, graphicsDevice, findNode);\n\n                    if (frame == null)\n                        break;\n                    anime.Frames.Add(frame);\n                }\n            }\n\n            if (anime.Frames.Count > 0)\n                return anime;\n            else\n                return null;\n        }\n    }\n\n    [Flags]\n    public enum FrameAnimationCreatingOptions\n    {\n        None = 0,\n        FindFrameNameInOrdinalNumber = 1 << 0,\n        ScanAllChildrenFrames = 1 << 1,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/FrameAnimator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Collections.ObjectModel;\nusing WzComparerR2.Controls;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Animation\n{\n    public class FrameAnimator : AnimationItem\n    {\n        public FrameAnimator(FrameAnimationData data)\n        {\n            this.Data = data;\n            this.Load();\n        }\n\n        public FrameAnimationData Data { get; private set; }\n\n        public Frame CurrentFrame { get; protected set; }\n\n        public override int Length\n        {\n            get { return this._length; }\n        }\n\n        public int CurrentTime\n        {\n            get { return _timeOffset; }\n            protected set { _timeOffset = value; }\n        }\n\n        private int _timeOffset;\n        private int _length;\n        private int[] _timeline;\n\n        public override void Update(TimeSpan elapsedTime)\n        {\n            if (_length <= 0)\n            {\n                _timeOffset = 0;\n            }\n            else\n            {\n                _timeOffset += (int)elapsedTime.TotalMilliseconds;\n                _timeOffset %= _length;\n            }\n            this.UpdateFrame();\n        }\n\n        public override void Reset()\n        {\n            _timeOffset = 0;\n            this.UpdateFrame();\n        }\n\n        public override Rectangle Measure()\n        {\n            return CurrentFrame?.Rectangle ?? Rectangle.Empty;\n        }\n\n        public KeyFrame[] GetKeyFrames()\n        {\n            return this.Data.Frames.Select(f =>\n                new KeyFrame() { Length = f.Delay, Animated = f.A0 != f.A1 }\n                ).ToArray();\n        }\n\n        protected virtual void Load()\n        {\n            _timeline = CreateTimeline(this.Data.Frames.Select(f => f.Delay));\n            _length = _timeline.Last();\n            _timeOffset = 0;\n            this.UpdateFrame();\n        }\n\n        protected virtual void UpdateFrame()\n        {\n            if (this.Data.Frames.Count <= 0)\n            {\n                this.CurrentFrame = null;\n                return;\n            }\n\n            float progress;\n            int index = GetProcessFromTimeline(_timeline, _timeOffset, out progress);\n\n            var frame = this.Data.Frames[index];\n            if (this.CurrentFrame == null)\n            {\n                this.CurrentFrame = new Frame();\n            }\n            this.CurrentFrame.Texture = frame.Texture;\n            this.CurrentFrame.AtlasRect = frame.AtlasRect;\n            this.CurrentFrame.Z = frame.Z;\n            this.CurrentFrame.Origin = frame.Origin;\n            this.CurrentFrame.A0 = (int)MathHelper.Lerp(frame.A0, frame.A1, progress);\n            this.CurrentFrame.Blend = frame.Blend;\n        }\n\n        public override object Clone()\n        {\n            return new FrameAnimator(this.Data);\n        }\n\n        public static int[] CreateTimeline(IEnumerable<int> delays)\n        {\n            var timeLine = new List<int>() { 0 };\n            foreach (var ms in delays)\n            {\n                timeLine.Add(timeLine[timeLine.Count - 1] + ms);\n            }\n            return timeLine.ToArray();\n        }\n\n        public static int GetProcessFromTimeline(int[] timeline, int timeOffset, out float progress)\n        {\n            int index = Array.BinarySearch(timeline, timeOffset);\n            progress = 0;\n            if (index < 0)\n            {\n                index = ~index - 1;\n                progress = (float)(timeOffset - timeline[index]) / (timeline[index + 1] - timeline[index]);\n            }\n            return index;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/ISpineAnimationData.cs",
    "content": "﻿using System;\nusing WzComparerR2.Common;\n\nnamespace WzComparerR2.Animation\n{\n    public interface ISpineAnimationData\n    {\n        bool PremultipliedAlpha { get; }\n        object SkeletonData { get; }\n        SpineVersion SpineVersion { get; }\n        ISpineAnimator CreateAnimator();\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/ISpineAnimator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\n\nnamespace WzComparerR2.Animation\n{\n    public interface ISpineAnimator\n    {\n        ISpineAnimationData Data { get; }\n        object Skeleton { get; }\n        ReadOnlyCollection<string> Animations { get; }\n        ReadOnlyCollection<string> Skins { get; }\n        int SelectedAnimationIndex { get; set; }\n        string SelectedAnimationName { get; set; }\n        string SelectedSkin { get; set; }\n        Queue<string> NextAnimationName { get; set; }\n        int CurrentTime { get; }\n        void Render(Spine.SkeletonRenderer renderer);\n        Microsoft.Xna.Framework.Rectangle GetSlotBounds(string slot);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/KeyFrame.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.Animation\n{\n    public struct KeyFrame\n    {\n        public int Length { get; set; }\n        public bool Animated { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/MaplestoryCanvasVideoLoader.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.InteropServices;\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.Animation\n{\n    public class MaplestoryCanvasVideoLoader\n    {\n        public FrameAnimationData Load(Wz_Video wzVideo, GraphicsDevice graphicsDevice)\n        {\n            var wzFile = wzVideo.WzFile;\n            var stream = wzVideo.WzImage.OpenRead();\n            var mcvHeader = wzVideo.ReadVideoFileHeader();\n\n            Span<byte> fourCC = stackalloc byte[4];\n            MemoryMarshal.Cast<byte, uint>(fourCC)[0] = mcvHeader.FourCC;\n\n            bool separateAlphaChannel = (mcvHeader.DataFlag & McvDataFlags.AlphaMap) != 0;\n            using VpxVideoDecoder dataDecoder = new VpxVideoDecoder(fourCC, mcvHeader.Width, mcvHeader.Height, 0);\n            using VpxVideoDecoder alphaMapDecoder = separateAlphaChannel ? new VpxVideoDecoder(fourCC, mcvHeader.Width, mcvHeader.Height, 0) : null;\n\n            var frames = new List<Frame>(mcvHeader.FrameCount);\n            byte[] textureBuffer = new byte[mcvHeader.Width * mcvHeader.Height * 4];\n            byte[] alphaMapBuffer = separateAlphaChannel ? new byte[mcvHeader.Width * mcvHeader.Height * 4] : null;\n\n            // shared function to decode frame\n            void readAndDecodeFrame(VpxVideoDecoder decoder, long videoDataOffset, int count, byte[] outputBuffer)\n            {\n                byte[] dataBuffer = ArrayPool<byte>.Shared.Rent(count);\n                try\n                {\n                    lock (wzFile.ReadLock)\n                    {\n                        stream.Position = wzVideo.Offset + videoDataOffset;\n                        stream.ReadExactly(dataBuffer, 0, count);\n                    }\n                    // read and decode frame data\n                    decoder.DecodeData(dataBuffer.AsSpan(0, count));\n                }\n                finally\n                {\n                    ArrayPool<byte>.Shared.Return(dataBuffer);\n                }\n\n                // get raw frames and convert format to bgra.\n                // expected that we should only get single frame.\n                if (decoder.GetNextFrame(out VpxFrame vpxFrame))\n                {\n                    if (vpxFrame.DisplayWidth != mcvHeader.Width || vpxFrame.DisplayHeight != mcvHeader.Height)\n                    {\n                        throw new Exception(string.Format(\"Decoded frame size ({0}*{1}) does not match the video size({2}*{3}).\",\n                            vpxFrame.DisplayWidth, vpxFrame.DisplayHeight, mcvHeader.Width, mcvHeader.Height));\n                    }\n\n                    // TODO: in future we can support rendering yuv420 in pixel shader instead of soft decoding.\n                    switch (vpxFrame.Format)\n                    {\n                        case VpxVideoDecoder.Interop.vpx_img_fmt.VPX_IMG_FMT_I420:\n                            I420ToARGB(vpxFrame.PlanesY, vpxFrame.StrideY, vpxFrame.PlanesU, vpxFrame.StrideU, vpxFrame.PlanesV, vpxFrame.StrideV,\n                                outputBuffer, mcvHeader.Width * 4, mcvHeader.Width, mcvHeader.Height);\n                            break;\n                        default:\n                            throw new Exception($\"Unsupported frame format: {vpxFrame.Format}\");\n                    }\n\n                    if (decoder.GetNextFrame(out vpxFrame))\n                    {\n                        throw new Exception($\"Unexpectedly read more than one frame.\");\n                    }\n                }\n                else\n                {\n                    throw new Exception($\"Failed to get vpx frame.\");\n                }\n            }\n\n            foreach (var fi in mcvHeader.Frames)\n            {\n                readAndDecodeFrame(dataDecoder, fi.DataOffset, fi.DataCount, textureBuffer);\n                if (separateAlphaChannel && fi.AlphaDataOffset > -1 && fi.AlphaDataCount > 0)\n                {\n                    readAndDecodeFrame(alphaMapDecoder, fi.AlphaDataOffset, fi.AlphaDataCount, alphaMapBuffer);\n                    MergeAlphaMap(textureBuffer, alphaMapBuffer);\n                }\n                var texture = new Texture2D(graphicsDevice, mcvHeader.Width, mcvHeader.Height, false, SurfaceFormat.Bgra32);\n                texture.SetData(textureBuffer);\n                var frame = new Frame();\n                frame.Texture = texture;\n                frame.Delay = (int)fi.Delay.TotalMilliseconds;\n                frames.Add(frame);\n            }\n\n            return new FrameAnimationData(frames);\n        }\n\n        private static unsafe void I420ToARGB(IntPtr src_y, int src_stride_y, IntPtr src_u, int src_stride_u, IntPtr src_v, int src_stride_v,\n                                                Span<byte> dst_bgra, int dst_stride_bgra, int width, int height)\n        {\n            fixed (byte* pDest = dst_bgra)\n            {\n                I420ToARGB((byte*)src_y, src_stride_y, (byte*)src_u, src_stride_u, (byte*)src_v, src_stride_v, pDest, dst_stride_bgra, width, height);\n            }\n        }\n\n        private static unsafe void MergeAlphaMap(Span<byte> textureData, ReadOnlySpan<byte> alphaMapData)\n        {\n            if ((textureData.Length & 3) != 0)\n            {\n                throw new ArgumentException(\"The length of TextureData must be a multiple of 4.\", nameof(textureData));\n            }\n            if ((alphaMapData.Length & 3) != 0)\n            {\n                throw new ArgumentException(\"The length of AlphaMapData must be a multiple of 4.\", nameof(alphaMapData));\n            }\n            if (textureData.Length != alphaMapData.Length)\n            {\n                throw new ArgumentException(\"The length of TextureData should be equal to the length of AlphaMapData.\");\n            }\n\n#if NET6_0_OR_GREATER\n            if (textureData.Length >= 32 && Avx2.IsSupported)\n            {\n                Vector256<byte> blendMask = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255,\n                                                            0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);\n                Vector256<byte> ymm0, ymm1;\n                while (textureData.Length >= 32)\n                {\n                    fixed (byte* pData = textureData)\n                        ymm0 = Avx.LoadVector256(pData);\n                    fixed (byte* pData = alphaMapData)\n                        ymm1 = Avx.LoadVector256(pData);\n\n                    //    b g r a  b g r a  b g r a  b g r a\n                    // => 0 b g r  a b g r  a b g r  a b g r\n                    ymm1 = Avx2.ShiftLeftLogical128BitLane(ymm1, 1);\n                    //    b g r _  b g r _  b g r _  b g r _\n                    //    _ _ _ r  _ _ _ r  _ _ _ r  _ _ _ r \n                    ymm0 = Avx2.BlendVariable(ymm0, ymm1, blendMask);\n                    fixed (byte* pData = textureData)\n                        Avx.Store(pData, ymm0);\n\n                    textureData = textureData.Slice(32);\n                    alphaMapData = alphaMapData.Slice(32);\n                }\n            }\n\n            if (textureData.Length >= 16 && Sse41.IsSupported)\n            {\n                Vector128<byte> blendMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255);\n                Vector128<byte> xmm0, xmm1;\n                while (textureData.Length >= 16)\n                {\n                    fixed (byte* pData = textureData)\n                        xmm0 = Sse2.LoadVector128(pData);\n                    fixed (byte* pData = alphaMapData)\n                        xmm1 = Sse2.LoadVector128(pData);\n                    xmm1 = Sse2.ShiftLeftLogical128BitLane(xmm0, 1);\n                    xmm0 = Sse41.BlendVariable(xmm0, xmm1, blendMask);\n                    fixed (byte* pData = textureData)\n                        Sse2.Store(pData, xmm0);\n\n                    textureData = textureData.Slice(16);\n                    alphaMapData = alphaMapData.Slice(16);\n                }\n            }\n#endif\n\n            for (int i = 0; i < textureData.Length; i += 4)\n            {\n                textureData[i + 3] = alphaMapData[i + 2];\n            }\n        }\n\n        #region Interop\n        private const string libYuv = @\"libyuv\";\n\n        [DllImport(libYuv)]\n        private static unsafe extern int I420ToARGB([In] byte* src_y, int src_stride_y,\n                                                    [In] byte* src_u, int src_stride_u,\n                                                    [In] byte* src_v, int src_stride_v,\n                                                    [Out] byte* dst_bgra, int dst_stride_bgra,\n                                                    int width, int height);\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/ModelBound.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Animation\n{\n    public struct ModelBound\n    {\n        public float minX, minY, maxX, maxY;\n\n        public bool IsEmpty\n        {\n            get { return minX >= maxX || minY >= maxY; }\n        }\n\n        public void Update(float[] vertices, int count)\n        {\n            int i = 0;\n            if (count % 4 != 0)\n            {\n                if (vertices[0] > maxX) maxX = vertices[0];\n                if (vertices[0] < minX) minX = vertices[0];\n                if (vertices[1] > maxY) maxY = vertices[1];\n                if (vertices[1] < minY) minY = vertices[1];\n                i += 2;\n            }\n\n            while (i < count)\n            {\n                if (vertices[i] > vertices[i + 2])\n                {\n                    if (vertices[i] > maxX) maxX = vertices[i];\n                    if (vertices[i + 2] < minX) minX = vertices[i + 2];\n                }\n                else\n                {\n                    if (vertices[i + 2] > maxX) maxX = vertices[i + 2];\n                    if (vertices[i] < minX) minX = vertices[i];\n                }\n\n                if (vertices[i + 1] > vertices[i + 3])\n                {\n                    if (vertices[i + 1] > maxY) maxY = vertices[i + 1];\n                    if (vertices[i + 3] < minY) minY = vertices[i + 3];\n                }\n                else\n                {\n                    if (vertices[i + 3] > maxY) maxY = vertices[i + 3];\n                    if (vertices[i + 1] < minY) minY = vertices[i + 1];\n                }\n\n                i += 4;\n            }\n        }\n\n        public Rectangle GetBound()\n        {\n            if (IsEmpty)\n            {\n                return new Rectangle();\n            }\n\n            return new Rectangle((int)Math.Round(minX),\n                (int)Math.Round(minY),\n                (int)Math.Round(maxX - minX),\n                (int)Math.Round(maxY - minY));\n        }\n\n        public static ModelBound Empty\n        {\n            get\n            {\n                var b = new ModelBound();\n                b.minX = b.minY = float.MaxValue;\n                b.maxX = b.maxY = float.MinValue;\n                return b;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/SpineAnimationDataV2.cs",
    "content": "﻿using System;\nusing WzComparerR2.Common;\nusing WzComparerR2.WzLib;\n\nusing Microsoft.Xna.Framework.Graphics;\nusing Spine.V2;\n\nnamespace WzComparerR2.Animation\n{\n    public class SpineAnimationDataV2 : ISpineAnimationData\n    {\n        private SpineAnimationDataV2()\n        {\n        }\n\n        public bool PremultipliedAlpha { get; set; }\n        public SkeletonData SkeletonData { get; private set; }\n\n        public static SpineAnimationDataV2 CreateFromNode(Wz_Node atlasOrSkelNode, GraphicsDevice graphicsDevice, GlobalFindNodeFunction findNode)\n        {\n            var textureLoader = new WzSpineTextureLoader(atlasOrSkelNode.ParentNode, graphicsDevice, findNode);\n            return CreateFromNode(atlasOrSkelNode, textureLoader);\n        }\n\n        public static SpineAnimationDataV2 CreateFromNode(Wz_Node atlasOrSkelNode, TextureLoader textureLoader)\n        {\n            return Create(SpineLoader.Detect(atlasOrSkelNode), textureLoader);\n        }\n\n        public static SpineAnimationDataV2 Create(SpineDetectionResult detectionResult, TextureLoader textureLoader)\n        {\n            var skeletonData = SpineLoader.LoadSkeletonV2(detectionResult, textureLoader);\n\n            if (skeletonData == null)\n            {\n                return null;\n            }\n\n            bool pma = detectionResult.SourceNode.ParentNode.FindNodeByPath(\"PMA\").GetValueEx<int>(0) != 0;\n\n            var anime = new SpineAnimationDataV2();\n            anime.SkeletonData = skeletonData;\n            anime.PremultipliedAlpha = pma;\n            return anime;\n        }\n\n        #region ISpineAnimationData\n        bool ISpineAnimationData.PremultipliedAlpha => this.PremultipliedAlpha;\n        object ISpineAnimationData.SkeletonData => this.SkeletonData;\n        SpineVersion ISpineAnimationData.SpineVersion => SpineVersion.V2;\n        ISpineAnimator ISpineAnimationData.CreateAnimator() => new SpineAnimatorV2(this);\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/SpineAnimationDataV4.cs",
    "content": "﻿using System;\nusing WzComparerR2.Common;\nusing WzComparerR2.WzLib;\n\nusing Microsoft.Xna.Framework.Graphics;\nusing Spine;\n\nnamespace WzComparerR2.Animation\n{\n    public class SpineAnimationDataV4 : ISpineAnimationData\n    {\n        private SpineAnimationDataV4()\n        {\n        }\n\n        public bool PremultipliedAlpha { get; set; }\n        public SkeletonData SkeletonData { get; private set; }\n\n        public static SpineAnimationDataV4 CreateFromNode(Wz_Node atlasOrSkelNode, GraphicsDevice graphicsDevice, GlobalFindNodeFunction findNode)\n        {\n            var textureLoader = new WzSpineTextureLoader(atlasOrSkelNode.ParentNode, graphicsDevice, findNode);\n            return CreateFromNode(atlasOrSkelNode, textureLoader);\n        }\n\n        public static SpineAnimationDataV4 CreateFromNode(Wz_Node atlasOrSkelNode, TextureLoader textureLoader)\n        {\n            return Create(SpineLoader.Detect(atlasOrSkelNode), textureLoader);\n        }\n\n        public static SpineAnimationDataV4 Create(SpineDetectionResult detectionResult, TextureLoader textureLoader)\n        {\n            var skeletonData = SpineLoader.LoadSkeletonV4(detectionResult, textureLoader);\n\n            if (skeletonData == null)\n            {\n                return null;\n            }\n\n            bool pma = detectionResult.SourceNode.ParentNode.FindNodeByPath(\"PMA\").GetValueEx<int>(0) != 0;\n\n            var anime = new SpineAnimationDataV4();\n            anime.SkeletonData = skeletonData;\n            anime.PremultipliedAlpha = pma;\n            return anime;\n        }\n\n        #region ISpineAnimationData\n        bool ISpineAnimationData.PremultipliedAlpha => this.PremultipliedAlpha;\n        object ISpineAnimationData.SkeletonData => this.SkeletonData;\n        SpineVersion ISpineAnimationData.SpineVersion => SpineVersion.V4;\n        ISpineAnimator ISpineAnimationData.CreateAnimator() => new SpineAnimatorV4(this);\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/SpineAnimatorV2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing WzComparerR2.Controls;\n\nusing Microsoft.Xna.Framework;\nusing Spine.V2;\n\nnamespace WzComparerR2.Animation\n{\n    public class SpineAnimatorV2 : AnimationItem, ISpineAnimator\n    {\n        public SpineAnimatorV2(SpineAnimationDataV2 data)\n        {\n            this.Data = data;\n            this._selectedAniIndex = -1;\n            this.Load();\n        }\n\n        public SpineAnimationDataV2 Data { get; private set; }\n\n        public Skeleton Skeleton { get; private set; }\n\n        public ReadOnlyCollection<string> Animations { get; private set; }\n\n        public ReadOnlyCollection<string> Skins { get; private set; }\n\n        public int SelectedAnimationIndex\n        {\n            get\n            {\n                return this._selectedAniIndex;\n            }\n            set\n            {\n                if (value > -1)\n                {\n                    string aniName = this.Animations[value];\n                    var ani = this.Data.SkeletonData.FindAnimation(aniName);\n                    this._animationState.SetAnimation(0, ani, true);\n                    this._selectedAniIndex = value;\n                }\n                else\n                {\n                    this._animationState.ClearTracks();\n                    this._selectedAniIndex = -1;\n                }\n                \n                this.Skeleton.SetToSetupPose();\n                this._animationState.Apply(this.Skeleton);\n                this.Skeleton.UpdateWorldTransform();\n            }\n        }\n\n        public string SelectedAnimationName\n        {\n            get\n            {\n                if( this._selectedAniIndex > -1)\n                {\n                    return this.Animations[this._selectedAniIndex];\n                }\n                return null;\n            }\n            set\n            {\n                if (value != null)\n                {\n                    this.SelectedAnimationIndex = this.Animations.IndexOf(value);\n                }\n                else\n                {\n                    this.SelectedAnimationIndex = -1;\n                }\n            }\n        }\n\n        public string SelectedSkin\n        {\n            get { return this.Skeleton.Skin?.Name; }\n            set { this.Skeleton.SetSkin(value); }\n        }\n\n        public int CurrentTime\n        {\n            get { return (int)((this._animationState?.GetCurrent(0)?.Time ?? 0) * 1000); }\n        }\n\n        internal Spine.V2.Animation SelectedAnimation\n        {\n            get\n            {\n                if (this._selectedAniIndex > -1)\n                {\n                    return this.Data.SkeletonData.FindAnimation(SelectedAnimationName);\n                }\n                return null;\n            }\n        }\n\n        public override void Reset()\n        {\n            this.SelectedAnimationIndex = this.SelectedAnimationIndex;\n        }\n\n        public override int Length\n        {\n            get\n            {\n                return (int)((this.SelectedAnimation?.Duration ?? 0f) * 1000);\n            }\n        }\n\n        public Queue<string> NextAnimationName { get; set; } = new Queue<string>();\n\n        private int _selectedAniIndex;\n        private AnimationState _animationState;\n\n        public override void Update(TimeSpan elapsedTime)\n        {\n            if (this.NextAnimationName.Count() > 0 && CurrentTime > Length)\n            {\n                this.SelectedAnimationName = this.NextAnimationName.Dequeue();\n            }\n            this._animationState.Update((float)elapsedTime.TotalSeconds);\n            this._animationState.Apply(Skeleton);\n            this.Skeleton.UpdateWorldTransform();\n        }\n\n        public override Rectangle Measure()\n        {\n            ModelBound bound = ModelBound.Empty;\n            UpdateBounds(ref bound, this.Skeleton);\n            return bound.GetBound();\n        }\n\n        public Rectangle GetSlotBounds(string slotName)\n        {\n            Skeleton skeleton = this.Skeleton;\n            if (!string.IsNullOrEmpty(slotName))\n            {\n                Slot slot = skeleton.FindSlot(slotName);\n                if (slot != null)\n                    return GetSlotBoundingBox(slot);\n            }\n            ModelBound bound = ModelBound.Empty;\n            UpdateBounds(ref bound, skeleton);\n            return bound.GetBound();\n        }\n\n        private void UpdateBounds(ref ModelBound bound, Skeleton skeleton)\n        {\n            float[] vertices = new float[8];\n            var drawOrder = skeleton.DrawOrder;\n            for (int i = 0, n = drawOrder.Count; i < n; i++)\n            {\n                Slot slot = drawOrder.Items[i];\n                Attachment attachment = slot.Attachment;\n                if (attachment is RegionAttachment)\n                {\n                    RegionAttachment region = (RegionAttachment)attachment;\n                    region.ComputeWorldVertices(slot.Bone, vertices);\n                    bound.Update(vertices, 8);\n                }\n                else if (attachment is MeshAttachment)\n                {\n                    MeshAttachment mesh = (MeshAttachment)attachment;\n                    int vertexCount = mesh.Vertices.Length;\n                    if (vertices.Length < vertexCount) vertices = new float[vertexCount];\n                    mesh.ComputeWorldVertices(slot, vertices);\n                    bound.Update(vertices, vertexCount);\n                }\n                else if (attachment is SkinnedMeshAttachment)\n                {\n                    SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment;\n                    int vertexCount = mesh.UVs.Length;\n                    if (vertices.Length < vertexCount) vertices = new float[vertexCount];\n                    mesh.ComputeWorldVertices(slot, vertices);\n                    bound.Update(vertices, vertexCount);\n                }\n            }\n        }\n\n        private Rectangle GetSlotBoundingBox(Slot slot)\n        {\n            ModelBound bound = ModelBound.Empty;\n\n            if (slot.Attachment is BoundingBoxAttachment)\n            {\n                BoundingBoxAttachment bb = (BoundingBoxAttachment)slot.Attachment;\n                int vertexCount = bb.Vertices.Length;\n                float[] vertices = new float[vertexCount];\n                bb.ComputeWorldVertices(slot.Bone, vertices);\n                bound.Update(vertices, vertexCount);\n            }\n            return bound.GetBound();\n        }\n\n        public override object Clone()\n        {\n            var clonedAnimator = new SpineAnimatorV2(this.Data);\n            clonedAnimator.SelectedAnimationIndex = this.SelectedAnimationIndex;\n            if (this.SelectedSkin != null)\n            {\n                clonedAnimator.SelectedSkin = this.SelectedSkin;\n            }\n            return clonedAnimator;\n        }\n\n        private void Load()\n        {\n            var skeletonData = this.Data.SkeletonData;\n            this.Skeleton = new Skeleton(skeletonData);\n            this.Animations = new ReadOnlyCollection<string>(skeletonData.Animations.Select(ani => ani.Name).ToList());\n            this.Skins = new ReadOnlyCollection<string>(skeletonData.Skins.Select(skin => skin.Name).ToList());\n            this._animationState = new AnimationState(new AnimationStateData(skeletonData));\n\n            if (this.Animations.Count > 0)\n            {\n                this.SelectedAnimationIndex = 0;\n            }\n            else\n            {\n                this.SelectedAnimationIndex = -1;\n            }\n        }\n\n        #region ISpineAnimator\n        ISpineAnimationData ISpineAnimator.Data => this.Data;\n        object ISpineAnimator.Skeleton => this.Skeleton;\n        ReadOnlyCollection<string> ISpineAnimator.Animations => this.Animations;\n        ReadOnlyCollection<string> ISpineAnimator.Skins => this.Skins;\n        int ISpineAnimator.SelectedAnimationIndex { get => this.SelectedAnimationIndex; set => this.SelectedAnimationIndex = value; }\n        string ISpineAnimator.SelectedAnimationName { get => this.SelectedAnimationName; set => this.SelectedAnimationName = value; }\n        string ISpineAnimator.SelectedSkin { get => this.SelectedSkin; set => this.SelectedSkin = value; }\n        int ISpineAnimator.CurrentTime { get => this.CurrentTime; }\n        void ISpineAnimator.Render(Spine.SkeletonRenderer renderer) => renderer.Draw(this.Skeleton);\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/SpineAnimatorV4.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing WzComparerR2.Controls;\n\nusing Microsoft.Xna.Framework;\nusing Spine;\n\nnamespace WzComparerR2.Animation\n{\n    public class SpineAnimatorV4 : AnimationItem, ISpineAnimator\n    {\n        public SpineAnimatorV4(SpineAnimationDataV4 data)\n        {\n            this.Data = data;\n            this._selectedAniIndex = -1;\n            this.Load();\n        }\n\n        public SpineAnimationDataV4 Data { get; private set; }\n\n        public Skeleton Skeleton { get; private set; }\n\n        public ReadOnlyCollection<string> Animations { get; private set; }\n\n        public ReadOnlyCollection<string> Skins { get; private set; }\n\n        public int SelectedAnimationIndex\n        {\n            get\n            {\n                return this._selectedAniIndex;\n            }\n            set\n            {\n                if (value > -1)\n                {\n                    string aniName = this.Animations[value];\n                    var ani = this.Data.SkeletonData.FindAnimation(aniName);\n                    this._animationState.SetAnimation(0, ani, true);\n                    this._selectedAniIndex = value;\n                }\n                else\n                {\n                    this._animationState.ClearTracks();\n                    this._selectedAniIndex = -1;\n                }\n\n                this.Skeleton.SetToSetupPose();\n                this._animationState.Apply(this.Skeleton);\n                this.Skeleton.UpdateWorldTransform();\n            }\n        }\n\n        public string SelectedAnimationName\n        {\n            get\n            {\n                if (this._selectedAniIndex > -1)\n                {\n                    return this.Animations[this._selectedAniIndex];\n                }\n                return null;\n            }\n            set\n            {\n                if (value != null)\n                {\n                    this.SelectedAnimationIndex = this.Animations.IndexOf(value);\n                }\n                else\n                {\n                    this.SelectedAnimationIndex = -1;\n                }\n            }\n        }\n\n        public string SelectedSkin\n        {\n            get { return this.Skeleton.Skin?.Name; }\n            set { this.Skeleton.SetSkin(value); }\n        }\n\n        public int CurrentTime\n        {\n            get { return (int)((this._animationState?.GetCurrent(0)?.TrackTime ?? 0) * 1000); }\n        }\n\n        internal Spine.Animation SelectedAnimation\n        {\n            get\n            {\n                if (this._selectedAniIndex > -1)\n                {\n                    return this.Data.SkeletonData.FindAnimation(SelectedAnimationName);\n                }\n                return null;\n            }\n        }\n\n        public override void Reset()\n        {\n            this.SelectedAnimationIndex = this.SelectedAnimationIndex;\n        }\n\n        public override int Length\n        {\n            get\n            {\n                return (int)((this.SelectedAnimation?.Duration ?? 0f) * 1000);\n            }\n        }\n\n        public Queue<string> NextAnimationName { get; set; } = new Queue<string>();  \n\n        private int _selectedAniIndex;\n        private AnimationState _animationState;\n\n        public override void Update(TimeSpan elapsedTime)\n        {\n            if (this.NextAnimationName.Count() > 0 && CurrentTime > Length)\n            {\n                this.SelectedAnimationName = this.NextAnimationName.Dequeue();\n            }\n            this._animationState.Update((float)elapsedTime.TotalSeconds);\n            this._animationState.Apply(Skeleton);\n            this.Skeleton.UpdateWorldTransform();\n        }\n\n        public override Rectangle Measure()\n        {\n            ModelBound bound = ModelBound.Empty;\n            UpdateBounds(ref bound, this.Skeleton);\n            return bound.GetBound();\n        }\n\n        public Rectangle GetSlotBounds(string slotName)\n        {\n            Skeleton skeleton = this.Skeleton;\n            if (!string.IsNullOrEmpty(slotName))\n            {\n                Slot slot = skeleton.FindSlot(slotName);\n                if (slot != null)\n                    return GetSlotBoundingBox(slot);\n            }\n            ModelBound bound = ModelBound.Empty;\n            UpdateBounds(ref bound, skeleton);\n            return bound.GetBound();\n        }\n\n        private void UpdateBounds(ref ModelBound bound, Skeleton skeleton)\n        {\n            float[] vertices = new float[8];\n            var drawOrder = skeleton.DrawOrder;\n            for (int i = 0, n = drawOrder.Count; i < n; i++)\n            {\n                Slot slot = drawOrder.Items[i];\n                Attachment attachment = slot.Attachment;\n                if (attachment is RegionAttachment)\n                {\n                    RegionAttachment region = (RegionAttachment)attachment;\n                    region.ComputeWorldVertices(slot, vertices, 0);\n                    bound.Update(vertices, 8);\n                }\n                else if (attachment is MeshAttachment)\n                {\n                    MeshAttachment mesh = (MeshAttachment)attachment;\n                    int vertexCount = mesh.WorldVerticesLength;\n                    if (vertices.Length < vertexCount) vertices = new float[vertexCount];\n                    mesh.ComputeWorldVertices(slot, vertices);\n                    bound.Update(vertices, vertexCount);\n                }\n                else if (attachment is ClippingAttachment)\n                {\n                    // ignore, don't know how it works\n                }\n            }\n        }\n\n        private Rectangle GetSlotBoundingBox(Slot slot)\n        {\n            ModelBound bound = ModelBound.Empty;\n\n            if (slot.Attachment is BoundingBoxAttachment)\n            {\n                BoundingBoxAttachment bb = (BoundingBoxAttachment)slot.Attachment;\n                int vertexCount = bb.WorldVerticesLength;\n                float[] vertices = new float[vertexCount];\n                bb.ComputeWorldVertices(slot, vertices);\n                bound.Update(vertices, vertexCount);\n            }\n            return bound.GetBound();\n        }\n\n        public override object Clone()\n        {\n            var clonedAnimator = new SpineAnimatorV4(this.Data);\n            clonedAnimator.SelectedAnimationIndex = this.SelectedAnimationIndex;\n            if (this.SelectedSkin != null)\n            {\n                clonedAnimator.SelectedSkin = this.SelectedSkin;\n            }\n            return clonedAnimator;\n        }\n\n        private void Load()\n        {\n            var skeletonData = this.Data.SkeletonData;\n            this.Skeleton = new Skeleton(skeletonData);\n            this.Animations = new ReadOnlyCollection<string>(skeletonData.Animations.Select(ani => ani.Name).ToList());\n            this.Skins = new ReadOnlyCollection<string>(skeletonData.Skins.Select(skin => skin.Name).ToList());\n            this._animationState = new AnimationState(new AnimationStateData(skeletonData));\n\n            if (this.Animations.Count > 0)\n            {\n                this.SelectedAnimationIndex = 0;\n            }\n            else\n            {\n                this.SelectedAnimationIndex = -1;\n            }\n        }\n\n        #region ISpineAnimator\n        ISpineAnimationData ISpineAnimator.Data => this.Data;\n        object ISpineAnimator.Skeleton => this.Skeleton;\n        ReadOnlyCollection<string> ISpineAnimator.Animations => this.Animations;\n        ReadOnlyCollection<string> ISpineAnimator.Skins => this.Skins;\n        int ISpineAnimator.SelectedAnimationIndex { get => this.SelectedAnimationIndex; set => this.SelectedAnimationIndex = value; }\n        string ISpineAnimator.SelectedAnimationName { get => this.SelectedAnimationName; set => this.SelectedAnimationName = value; }\n        string ISpineAnimator.SelectedSkin { get => this.SelectedSkin; set => this.SelectedSkin = value; }\n        int ISpineAnimator.CurrentTime { get => this.CurrentTime; }\n        void ISpineAnimator.Render(Spine.SkeletonRenderer renderer) => renderer.Draw(this.Skeleton);\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Animation/WzSpineTextureLoader.cs",
    "content": "﻿using System;\nusing WzComparerR2.Common;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.WzLib;\n\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Animation\n{\n    public class WzSpineTextureLoader : Spine.TextureLoader, Spine.V2.TextureLoader\n    {\n        public WzSpineTextureLoader(Wz_Node topNode, GraphicsDevice graphicsDevice)\n            : this(topNode, graphicsDevice, null)\n        {\n        }\n\n        public WzSpineTextureLoader(Wz_Node topNode, GraphicsDevice graphicsDevice, GlobalFindNodeFunction findNodeFunc)\n        {\n            this.TopNode = topNode;\n            this.GraphicsDevice = graphicsDevice;\n            this.FindNodeFunction = findNodeFunc;\n        }\n\n        public Wz_Node TopNode { get; private set; }\n\n        public GraphicsDevice GraphicsDevice { get; private set; }\n\n        public GlobalFindNodeFunction FindNodeFunction { get; set; }\n\n        public bool EnableTextureMissingFallback { get; set; }\n\n        public void Load(Spine.AtlasPage page, string path)\n        {\n            if (this.TryLoadTexture(path, out var texture))\n            {\n                page.rendererObject = texture;\n                page.width = texture.Width;\n                page.height = texture.Height;\n            }\n            else if (this.EnableTextureMissingFallback && page.width > 0 && page.height > 0)\n            {\n                page.rendererObject = this.CreateEmptyTexture(page.width, page.height);\n            }\n        }\n\n        public void Load(Spine.V2.AtlasPage page, string path)\n        {\n            if (this.TryLoadTexture(path, out var texture))\n            {\n                page.rendererObject = texture;\n                page.width = texture.Width;\n                page.height = texture.Height;\n            }\n            else if (this.EnableTextureMissingFallback && page.width > 0 && page.height > 0)\n            {\n                page.rendererObject = this.CreateEmptyTexture(page.width, page.height);\n            }\n        }\n\n        public void Unload(object texture)\n        {\n            (texture as Texture2D)?.Dispose();\n        }\n\n        private bool TryLoadTexture(string path, out Texture2D texture)\n        {\n            texture = null;\n            var frameNode = this.TopNode.FindNodeByPath(path);\n            frameNode = frameNode.ResolveUol();\n\n            if (frameNode?.Value is Wz_Png)\n            {\n                var linkNode = frameNode.GetLinkedSourceNode(FindNodeFunction);\n                Wz_Png png = (linkNode ?? frameNode).GetValue<Wz_Png>();\n                texture = png.ToTexture(this.GraphicsDevice);\n                return true;\n            }\n\n            return false;\n        }\n\n        private Texture2D CreateEmptyTexture(int width, int height)\n        {\n            return new Texture2D(this.GraphicsDevice, width, height, false, SurfaceFormat.Alpha8);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/BitmapOrigin.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\n\nnamespace WzComparerR2\n{\n    public struct BitmapOrigin\n    {\n        public BitmapOrigin(Bitmap bitmap)\n            : this(bitmap, new Point(0, 0))\n        {\n        }\n\n        public BitmapOrigin(Bitmap bitmap, int x, int y)\n            : this(bitmap, new Point(x, y))\n        {\n        }\n\n        public BitmapOrigin(Bitmap bitmap, Point origin)\n        {\n            this.bitmap = bitmap;\n            this.origin = origin;\n        }\n\n        private Bitmap bitmap;\n        private Point origin;\n\n        /// <summary>\n        /// 获取图片。\n        /// </summary>\n        public Bitmap Bitmap\n        {\n            get { return bitmap; }\n            set { bitmap = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置图片的原点坐标。\n        /// </summary>\n        public Point Origin\n        {\n            get { return origin; }\n            set { origin = value; }\n        }\n\n        /// <summary>\n        /// 获取图片原点的相反数，一般为绘图坐标区域的实际绘图原点。\n        /// </summary>\n        public Point OpOrigin\n        {\n            get { return new Point(-origin.X, -origin.Y); }\n        }\n\n        /// <summary>\n        /// 获取图片的实际绘图区域，它由图片大小和原点的相反数组成。\n        /// </summary>\n        public Rectangle Rectangle\n        {\n            get\n            {\n                if (this.bitmap == null)\n                    return new Rectangle(this.OpOrigin, new Size());\n                else\n                    return new Rectangle(this.OpOrigin, this.bitmap.Size);\n            }\n        }\n\n        public static BitmapOrigin CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            BitmapOrigin bp = new BitmapOrigin();\n            Wz_Uol uol;\n            while ((uol = node.GetValue<Wz_Uol>(null)) != null)\n            {\n                node = uol.HandleUol(node);\n            }\n\n            //获取linkNode\n            var linkNode = node.GetLinkedSourceNode(findNode);\n            Wz_Png png = linkNode?.GetValue<Wz_Png>() ?? (Wz_Png)node.Value;\n\n            bp.Bitmap = png?.ExtractPng();\n            Wz_Node originNode = node.FindNodeByPath(\"origin\");\n            Wz_Vector vec = (originNode == null) ? null : originNode.GetValue<Wz_Vector>();\n            bp.Origin = (vec == null) ? new Point() : new Point(vec.X, vec.Y);\n\n            return bp;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Calculator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Text.RegularExpressions;\n\nnamespace WzComparerR2\n{\n    public static class Calculator\n    {\n        public static decimal Parse(string mathExpression, params decimal[] args)\n        {\n            var tokens = Lexer(mathExpression);\n            var inst = Suffix(tokens);\n\n            var paramList = new Dictionary<string, object>();\n\n            if (args != null)\n            {\n                for (int i = 0; i < args.Length; i++)\n                {\n                    switch (i)\n                    {\n                        case 0: paramList[\"x\"] = args[0]; paramList[\"X\"] = args[0]; break;\n                        case 1: paramList[\"y\"] = args[1]; break;\n                        case 2: paramList[\"z\"] = args[2]; break;\n                        case 3: paramList[\"w\"] = args[3]; break;\n                    }\n                }\n            }\n\n            return Execute(inst, new EvalContext(paramList));\n        }\n\n        private static List<Token> Lexer(string expr)\n        {\n            var tokens = new List<Token>();\n            if (string.IsNullOrEmpty(expr))\n                return tokens;\n\n            int begin;\n            for (int i = 0; i < expr.Length; i++)\n            {\n                switch (expr[i])\n                {\n                    case '+':\n                    case '-':\n                    case '*':\n                    case '/': tokens.Add(new Token(TokenType.Operator, expr[i].ToString())); break;\n                    case '.': tokens.Add(new Token(TokenType.Dot, null)); break;\n                    case '(': tokens.Add(new Token(TokenType.BracketStart, null)); break;\n                    case ')': tokens.Add(new Token(TokenType.BracketEnd, null)); break;\n                    case ',': tokens.Add(new Token(TokenType.Comma, null)); break;\n                    case ' ': break; //whitespace当不存在\n                    case '%': break; //ignore\n                    default:\n                        if (char.IsDigit(expr[i]))\n                        { //尽力读取number\n                            begin = i;\n                            bool dot = false;\n                            while (++i < expr.Length)\n                            {\n                                if (char.IsDigit(expr[i]))\n                                {\n                                    //继续读\n                                }\n                                else if (expr[i] == '.' && !dot)\n                                {\n                                    dot = true;\n                                }\n                                else\n                                {\n                                    break;\n                                }\n                            }\n                            tokens.Add(new Token(TokenType.Number, expr.Substring(begin, i - begin)));\n                            i--;\n                        }\n                        else if (char.IsLetter(expr[i]))\n                        { //尽力读取id\n                            begin = i;\n                            while (++i < expr.Length)\n                            {\n                                if (!char.IsLetterOrDigit(expr[i]))\n                                {\n                                    break;\n                                }\n                            }\n                            tokens.Add(new Token(TokenType.ID, expr.Substring(begin, i - begin)));\n                            i--;\n                        }\n                        else if (char.IsWhiteSpace(expr[i]))\n                        {\n                            //空白字符跳过\n                        }\n                        else\n                        { //无效字符\n                            throw new Exception(\"Unknown char '\" + expr[i] + \"'.\");\n                        }\n                        break;\n                }\n            }\n            return tokens;\n        }\n\n        //suffix 逆波兰表达式\n        private static List<Token> Suffix(List<Token> tokens)\n        {\n            var value = new List<Token>();\n            var stack = new Stack<Token>();\n            for (int i = 0; i < tokens.Count; i++)\n            {\n                var token = tokens[i];\n                switch (token.Type)\n                {\n                    case TokenType.BracketStart: //括号 推进stack\n                        stack.Push(token);\n                        if (token.Tag == Tag.Call)\n                            value.Add(new Token(TokenType.CallStart, \"\"));\n                        break;\n\n                    case TokenType.BracketEnd: //括号结束 弹出到上一个括号\n                        {\n                            bool foundBracketEnd = false;\n                            while (stack.Count > 0)\n                            {\n                                Token t = stack.Pop();\n                                if (t.Type != TokenType.BracketStart)\n                                {\n                                    value.Add(t);\n                                }\n                                else\n                                {\n                                    if (t.Tag == Tag.Call)\n                                        value.Add(new Token(TokenType.CallEnd, \"\"));\n                                    foundBracketEnd = true;\n                                    break;\n                                }\n                            }\n                            // ignore redundant bracketEnd at the end of expression, workaround for skill 80003671,80003672,80003677\n                            if (!foundBracketEnd && i != tokens.Count - 1)\n                            {\n                                throw new ArgumentException(\"Brackets are not paired.\");\n                            }\n                        }\n                        break;\n\n                    case TokenType.Operator: //运算符\n                        if (i == 0 || tokens[i - 1].Type == TokenType.BracketStart || tokens[i - 1].Type == TokenType.Operator)\n                        { //独立判定一元运算符\n                            token.Tag = Tag.Unary;\n                        }\n                        goto case TokenType.Dot;\n                    case TokenType.Dot: //取成员\n                        while (stack.Count > 0)\n                        { //比较优先级\n                            Token t = stack.Peek();\n                            if (Priority(token) > Priority(t)\n                                || (token.Tag == Tag.Unary && t.Tag == Tag.Unary))\n                            { //优先级比上个高\n                                break;\n                            }\n                            else\n                            {\n                                value.Add(stack.Pop());\n                            }\n                        }\n                        stack.Push(token);\n                        break;\n\n                    case TokenType.ID: //预判如果后面是括号 当成函数处理\n                        value.Add(token);\n                        if (i + 1 < tokens.Count && tokens[i + 1].Type == TokenType.BracketStart)\n                        {\n                            while (stack.Count > 0)\n                            {\n                                Token t = stack.Peek();\n                                if (t.Type == TokenType.Dot)\n                                {\n                                    value.Add(stack.Pop());\n                                }\n                                else\n                                {\n                                    break;\n                                }\n                            }\n                            //标记下一个括号为call\n                            tokens[i + 1].Tag = Tag.Call;\n                        }\n                        break;\n\n                    case TokenType.Number:\n                        value.Add(token);\n                        break;\n\n                    case TokenType.Comma: //逗号 忽略好像也没事..感觉像卖萌的..\n                        break;\n                }\n            }\n\n            value.AddRange(stack);\n            return value;\n        }\n\n        private static decimal Execute(List<Token> inst, EvalContext param)\n        {\n            var stack = new Stack<object>();\n            object obj;\n            decimal d1, d2;\n            foreach (var token in inst)\n            {\n                switch (token.Type)\n                {\n                    case TokenType.Number: stack.Push(Convert.ToDecimal(token.Value)); break;\n                    case TokenType.ID:\n                        if (param.TryGetValue(token.Value, out obj))\n                        {\n                            stack.Push(obj);\n                        }\n                        else\n                        {\n                            throw new Exception(\"ID '\" + token.Value + \"' not found.\");\n                        }\n                        break;\n                    case TokenType.Operator:\n                        if (token.Tag == Tag.Unary)\n                        {\n                            d1 = Convert.ToDecimal(stack.Pop());\n                            switch (token.Value)\n                            {\n                                case \"+\": stack.Push(d1); break;\n                                case \"-\": stack.Push(-d1); break;\n                            }\n                        }\n                        else\n                        {\n                            d2 = Convert.ToDecimal(stack.Pop());\n                            d1 = Convert.ToDecimal(stack.Pop());\n                            switch (token.Value)\n                            {\n                                case \"+\": stack.Push(d1 + d2); break;\n                                case \"-\": stack.Push(d1 - d2); break;\n                                case \"*\": stack.Push(d1 * d2); break;\n                                case \"/\": stack.Push(d1 / d2); break;\n                            }\n                        }\n                        break;\n                    case TokenType.Dot:\n                        throw new NotSupportedException();\n                    case TokenType.CallStart: stack.Push(TokenType.CallStart); break;\n                    case TokenType.CallEnd:\n                        var p = new Stack<object>();\n                        while (!TokenType.CallStart.Equals(obj = stack.Pop()))\n                        {\n                            p.Push(obj);\n                        }\n                        obj = (stack.Pop() as Delegate).DynamicInvoke(p.ToArray());\n                        stack.Push(obj);\n                        break;\n                }\n            }\n            return stack.Count <= 0 ? 0 : Convert.ToDecimal(stack.Pop());\n        }\n\n\n        private class Token\n        {\n            public Token(TokenType type, String value)\n            {\n                this.Type = type;\n                this.Value = value;\n            }\n            public TokenType Type;\n            public string Value;\n            public Tag Tag;\n        }\n\n        //优先级\n        private static int Priority(Token token)\n        {\n            if (token.Tag == Tag.Unary) return 4;\n            switch (token.Value)\n            {\n                case \"+\":\n                case \"-\": return 1;\n                case \"*\":\n                case \"/\": return 2;\n                case \".\": return 3;\n                default: return 0;\n            }\n        }\n\n        private enum TokenType\n        {\n            ID, //x,funcName\n            Number, //123.45\n            BracketStart, //(\n            BracketEnd, //)\n            Dot, //.成员运算符\n            Operator, //+-*/\n            Comma, //,逗号 函数参数分隔符\n\n            CallStart = 100, //标记用 参数开始\n            CallEnd, //标记用\n        }\n\n        private enum Tag\n        {\n            None = 0,\n            Call,\n            Unary,\n        }\n\n        private class EvalContext\n        {\n            public EvalContext()\n                : this(null)\n            {\n            }\n\n            public EvalContext(Dictionary<string, object> parameters)\n            {\n                this._dict = new Dictionary<string, object>();\n\n                if (parameters != null && parameters.Count > 0)\n                {\n                    foreach (var kv in parameters)\n                    {\n                        _dict.Add(kv.Key, kv.Value);\n                    }\n                }\n            }\n\n            public Dictionary<string, object> _dict;\n\n            public bool TryGetValue(string key, out object value)\n            {\n                return _dict.TryGetValue(key, out value)\n                    || TryGetFunction(key, out value);\n            }\n\n            private bool TryGetFunction(string key, out object value)\n            {\n                Match m;\n\n                if (key == \"u\")\n                {\n                    value = (Func<decimal, decimal>)Math.Ceiling;\n                    return true;\n                }\n                else if (key == \"d\")\n                {\n                    value = (Func<decimal, decimal>)Math.Floor;\n                    return true;\n                }\n                else if (key == \"min\")\n                {\n                    value = (Func<decimal, decimal, decimal>)Math.Min;\n                    return true;\n                }\n                else if ((m = Regex.Match(key, @\"^log(\\d+)$\")).Success)\n                {\n                    var logBase = int.Parse(m.Result(\"$1\"));\n                    value = (Func<decimal, decimal>)(x => x <= 0 ? 0 : (decimal)Math.Floor(Math.Log(decimal.ToDouble(x), logBase)));\n                    return true;\n                }\n\n                value = null;\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Addition.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Addition\n    {\n        public Addition()\n        {\n            Props = new Dictionary<string, string>();\n            ConValue = new List<int>();\n        }\n\n        public AdditionType Type { get; set; }\n        public GearPropType ConType { get; set; }\n        public List<int> ConValue { get; private set; }\n        public Dictionary<string, string> Props { get; private set; }\n\n        public string GetPropString()\n        {\n            StringBuilder sb;\n            switch (this.Type)\n            {\n                case AdditionType.boss:\n                    sb = new StringBuilder();\n                    sb.Append(\"攻击BOSS时，\");\n                    {\n                        string v1;\n                        if (this.Props.TryGetValue(\"prob\", out v1))\n                            sb.Append(\"有\" + v1 + \"的几率\");\n                        sb.Append(\"造成\" + Props[\"damage\"] + \"%的额外伤害\");\n                    }\n                    return sb.ToString();\n                case AdditionType.critical:\n                    sb = new StringBuilder();\n                    {\n                        string val;\n                        if (this.Props.TryGetValue(\"prob\", out val))\n                        {\n                            sb.AppendFormat(\"爆击率{0}%\\r\\n\", val);\n                        }\n                        if (this.Props.TryGetValue(\"damage\", out val))\n                        {\n                            sb.AppendFormat(\"爆击伤害增加{0}%\\r\\n\", val);\n                        }\n                        if (sb.Length > 2)\n                        {\n                            sb.Remove(sb.Length - 2, 2);\n                        }\n                    }\n                    return sb.ToString();\n                case AdditionType.elemboost:\n                    {\n                        string v1, elem;\n                        if (this.Props.TryGetValue(\"elemVol\", out v1))\n                        {\n                            switch (v1[0])\n                            {\n                                case 'I': elem = \"冰\"; break;\n                                case 'F': elem = \"火\"; break;\n                                case 'L': elem = \"雷\"; break;\n                                default: elem = v1[0].ToString(); break;\n                            }\n                            return elem + \"属性效果强化\" + v1.Substring(1) + \"%\";\n                        }\n                    }\n                    break;\n                case AdditionType.hpmpchange:\n                    sb = new StringBuilder();\n                    sb.Append(\"每10秒恢复\");\n                    {\n                        string v1;\n                        if (this.Props.TryGetValue(\"hpChangePerTime\", out v1))\n                        {\n                            sb.Append(\"HP \" + v1);\n                        }\n                    }\n                    return sb.ToString();\n                case AdditionType.mobcategory:\n                    return \"攻击\" + ItemStringHelper.GetMobCategoryName(Convert.ToInt32(this.Props[\"category\"])) + \"怪物时，造成\" + this.Props[\"damage\"] + \"%额外伤害\";\n                case AdditionType.mobdie:\n                    sb = new StringBuilder();\n                    {\n                        string v1;\n                        if (this.Props.TryGetValue(\"hpIncOnMobDie\", out v1))\n                        {\n                            sb.AppendLine(\"怪物死亡时 HP恢复\" + v1);\n                        }\n                        if (this.Props.TryGetValue(\"hpIncRatioOnMobDie\", out v1))\n                        {\n                            sb.AppendLine(\"怪物死亡时 有\" + Props[\"hpRatioProp\"] + \"%的几率 伤害的\" + v1 + \"%转换为HP (但不超过最大HP的10%。)\");\n                        }\n                        if (this.Props.TryGetValue(\"mpIncOnMobDie\", out v1))\n                        {\n                            sb.AppendLine(\"怪物死亡时 HP恢复\" + v1);\n                        }\n                        if (this.Props.TryGetValue(\"mpIncRatioOnMobDie\", out v1))\n                        {\n                            sb.AppendLine(\"怪物死亡时 有\" + Props[\"mpRatioProp\"] + \"%的几率 伤害的\" + v1 + \"%转换为MP (但不超过最大MP的10%。)\");\n                        }\n                    }\n                    if (sb.Length > 0)\n                    {\n                        sb.Append(\"在部分地区功能可能会受到限制。\");\n                        return sb.ToString();\n                    }\n                    break;\n                case AdditionType.skill:\n                    switch (Convert.ToInt32(this.Props[\"id\"]))\n                    {\n                        case 90000000: return \"有一定几率增加必杀效果\";\n                        case 90001001: return \"有一定几率增加眩晕效果\";\n                        case 90001002: return \"有一定几率增加缓速术效果\";\n                        case 90001003: return \"有一定几率增加毒效果\";\n                        case 90001004: return \"有一定几率增加暗黑效果\";\n                        case 90001005: return \"有一定几率增加封印效果\";\n                        case 90001006: return \"有一定几率增加结冰效果\";\n                    }\n                    break;\n                case AdditionType.statinc:\n                    sb = new StringBuilder();\n                    {\n                        foreach (var kv in Props)\n                        {\n                            try\n                            {\n                                GearPropType propType = (GearPropType)Enum.Parse(typeof(GearPropType), kv.Key);\n                                sb.AppendLine(ItemStringHelper.GetGearPropString(propType, Convert.ToInt32(kv.Value)));\n                            }\n                            catch\n                            {\n                            }\n                        }\n                    }\n                    if (sb.Length > 0)\n                    {\n                        return sb.ToString();\n                    }\n                    break;\n                default: return null;\n            }\n            return null;\n        }\n\n        public string GetConString()\n        {\n            switch (this.ConType)\n            {\n                case GearPropType.reqJob:\n                    string[] reqJobs = new string[this.ConValue.Count];\n                    for (int i = 0; i < reqJobs.Length; i++)\n                    {\n                        reqJobs[i] = ItemStringHelper.GetJobName(this.ConValue[i]) ?? this.ConValue[i].ToString();\n                    }\n                    return \"职业为\" + string.Join(\" 或者 \", reqJobs) + \"时\";\n                case GearPropType.reqLevel:\n                    return this.ConValue[0] + \"级以上时\";\n                case GearPropType.reqCraft:\n                    int lastExp;\n                    return \"手技经验值在\" + this.ConValue[0] + \"(\" + getPersonalityLevel(this.ConValue[0], out lastExp) + \"级\" + lastExp + \"点)以上时\";\n                case GearPropType.reqWeekDay:\n                    string[] weekdays = new string[this.ConValue.Count];\n                    for (int i = 0; i < this.ConValue.Count; i++)\n                    {\n                        weekdays[i] = GetWeekDayString(this.ConValue[i]);\n                    }\n                    return string.Join(\", \", weekdays) + \"时\";\n                default:\n                    return null;\n            }\n        }\n\n        private int getPersonalityLevel(int totalExp, out int lastExp)\n        {\n            int curExp = 0;\n            for (int level = 0; ; level++)\n            {\n                if (level == 0)\n                {\n                    curExp = 20;\n                }\n                else if (level < 10)\n                {\n                    curExp = (int)Math.Round(curExp * 1.3, MidpointRounding.AwayFromZero);\n                }\n                else if (level < 20)\n                {\n                    curExp = (int)Math.Round(curExp * 1.1, MidpointRounding.AwayFromZero);\n                }\n                else if (level < 30)\n                {\n                    curExp = (int)Math.Round(curExp * 1.03, MidpointRounding.AwayFromZero);\n                }\n                else if (level < 70)\n                {\n                    curExp = (int)Math.Round(curExp * 1.015, MidpointRounding.AwayFromZero);\n                }\n                else if (level < 100)\n                {\n                    curExp = (int)Math.Round(curExp * 1.003, MidpointRounding.AwayFromZero);\n                }\n                else\n                {\n                    lastExp = 0;\n                    return 100;\n                }\n                if (totalExp - curExp <= 0)\n                {\n                    lastExp = totalExp;\n                    return level;\n                }\n                else\n                {\n                    totalExp -= curExp;\n                }\n            }\n        }\n\n        private static string GetWeekDayString(int weekDay)\n        {\n            switch (weekDay)\n            {\n                case 0: return \"周日\";\n                case 1: return \"周一\";\n                case 2: return \"周二\";\n                case 3: return \"周三\";\n                case 4: return \"周四\";\n                case 5: return \"周五\";\n                case 6: return \"周六\";\n                default: return \"周\" + weekDay; //这怎么可能...\n            }\n        }\n\n        public static Addition CreateFromNode(Wz_Node node)\n        {\n            if (node == null)\n                return null;\n            foreach (AdditionType type in Enum.GetValues(typeof(AdditionType)))\n            {\n                if (type.ToString() == node.Text)\n                {\n                    Addition addition = new Addition();\n                    addition.Type = type;\n                    Action<Wz_Node> addInt32 = n => addition.ConValue.Add(n.GetValue<int>());\n                    Action<Wz_Node> addWeekDay = n =>\n                    {\n                        try\n                        {\n                            DayOfWeek weekday = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), n.GetValue<string>(), true);\n                            addition.ConValue.Add((int)weekday);\n                        }\n                        catch { }\n                    };\n\n                    foreach (Wz_Node subNode in node.Nodes)\n                    {\n                        if (subNode.Text == \"con\")\n                        {\n                            Action<Wz_Node> addValueFunc = addInt32;\n                            foreach (Wz_Node conNode in subNode.Nodes)\n                            {\n                                switch (conNode.Text)\n                                {\n                                    case \"job\":\n                                        addition.ConType = GearPropType.reqJob;\n                                        break;\n                                    //case \"lv\": //已不被官方识别了\n                                    case \"level\":\n                                        addition.ConType = GearPropType.reqLevel;\n                                        break;\n                                    case \"craft\":\n                                        addition.ConType = GearPropType.reqCraft;\n                                        break;\n                                    case \"weekDay\":\n                                        addition.ConType = GearPropType.reqWeekDay;\n                                        addValueFunc = addWeekDay; //改变解析方法\n                                        break;\n                                    default: //不识别的东西\n                                        addition.ConType = (GearPropType)0;\n                                        continue;\n                                }\n\n                                if (conNode.Nodes.Count > 0)\n                                {\n                                    foreach (Wz_Node conValNode in conNode.Nodes)\n                                    {\n                                        addValueFunc(conValNode);\n                                    }\n                                }\n                                else\n                                {\n                                    addValueFunc(conNode);\n                                }\n                            }\n                        }\n                        else\n                        {\n                            addition.Props.Add(subNode.Text, Convert.ToString(subNode.Value));\n                        }\n                    }\n                    return addition;\n                }\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/AdditionType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum AdditionType\n    {\n        skill = 1,\n        mobcategory,\n        elemboost,\n        hpmpchange,\n        critical,\n        mobdie,\n        boss,\n        statinc = 8\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/AlienStone.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class AlienStone\n    {\n        public AlienStoneGrade Grade { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/AlienStoneGrade.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum AlienStoneGrade\n    {\n        /// <summary>\n        /// 表示D级星岩。\n        /// </summary>\n        Normal = 0,\n        /// <summary>\n        /// 表示C级星岩。\n        /// </summary>\n        Rare = 1,\n        /// <summary>\n        /// 表示B级星岩。\n        /// </summary>\n        Epic = 2,\n        /// <summary>\n        /// 表示A级星岩。\n        /// </summary>\n        Unique = 3,\n        /// <summary>\n        /// 表示S级星岩。\n        /// </summary>\n        Legendary = 4\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/DamageSkin.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing WzComparerR2.WzLib;\nusing System.Linq;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class DamageSkin\n    {\n        public DamageSkin()\n        {\n            MiniDigit = new Dictionary<string, BitmapOrigin>();\n            BigDigit = new Dictionary<string, BitmapOrigin>();\n            MiniCriticalDigit = new Dictionary<string, BitmapOrigin>();\n            BigCriticalDigit = new Dictionary<string, BitmapOrigin>();\n            MiniUnit = new Dictionary<string, BitmapOrigin>();\n            BigUnit = new Dictionary<string, BitmapOrigin>();\n            MiniCriticalUnit = new Dictionary<string, BitmapOrigin>();\n            BigCriticalUnit = new Dictionary<string, BitmapOrigin>();\n            EtcBitmap = new Dictionary<string, BitmapOrigin>();\n            MiniDigitSpacing = 0;\n            BigDigitSpacing = 0;\n            MiniCriticalDigitSpacing = 0;\n            BigCriticalDigitSpacing = 0;\n            MiniUnitSpacing = 0;\n            BigUnitSpacing = 0;\n            MiniCriticalUnitSpacing = 0;\n            BigCriticalUnitSpacing = 0;\n        }\n\n        public int DamageSkinID { get; set; }\n        public int ExtractItemID { get; set; }\n        public Dictionary<string, BitmapOrigin> MiniDigit { get; set; }\n        public int MiniDigitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> BigDigit { get; set; }\n        public int BigDigitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> MiniCriticalDigit { get; set; }\n        public int MiniCriticalDigitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> BigCriticalDigit { get; set; }\n        public int BigCriticalDigitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> MiniUnit { get; set; }\n        public int MiniUnitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> BigUnit { get; set; }\n        public int BigUnitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> MiniCriticalUnit { get; set; }\n        public int MiniCriticalUnitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> BigCriticalUnit { get; set; }\n        public int BigCriticalUnitSpacing { get; set; }\n        public Dictionary<string, BitmapOrigin> EtcBitmap { get; set; }\n        public BitmapOrigin Sample { get; set; }\n        public string Desc { get; set; }\n        public string CustomType { get; set; }\n\n        public static DamageSkin CreateFromNode(Wz_Node damageSkinNode, GlobalFindNodeFunction findNode)\n        {\n            if (damageSkinNode == null)\n                return null;\n\n            DamageSkin damageSkin = new DamageSkin();\n\n            damageSkin.DamageSkinID = Convert.ToInt32(damageSkinNode.Text);\n\n            foreach (Wz_Node subNode in damageSkinNode.Nodes)\n            {\n                switch (subNode.Text)\n                {\n                    case \"desc\":\n                        damageSkin.Desc = subNode.GetValue<string>();\n                        break;\n                    case \"extractID\":\n                        damageSkin.ExtractItemID = subNode.GetValue<int>();\n                        break;\n                    case \"sample\":\n                        damageSkin.Sample = BitmapOrigin.CreateFromNode(subNode, findNode);\n                        break;\n                    case \"effect\":\n                        foreach (Wz_Node effectNode in subNode.Nodes)\n                        {\n                            switch (effectNode.Text)\n                            {\n                                case \"NoRed0\":\n                                    foreach (Wz_Node digitNode in effectNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.MiniDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.MiniDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.MiniDigitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"NoRed1\":\n                                    foreach (Wz_Node digitNode in effectNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.BigDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.BigDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.BigDigitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"NoCri0\":\n                                    foreach (Wz_Node digitNode in effectNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.MiniCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.MiniCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.MiniCriticalDigitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"NoCri1\":\n                                    foreach (Wz_Node digitNode in effectNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.BigCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.BigCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.BigCriticalDigitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"NoCustom\":\n                                    foreach (Wz_Node customSubNode in effectNode.Nodes)\n                                    {\n                                        switch (customSubNode.Text)\n                                        {\n                                            case \"NoRed0\":\n                                                foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                                {\n                                                    if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                                    {\n                                                        damageSkin.MiniUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                                    }\n                                                    else if (digitNode.Nodes.Count > 1)\n                                                    {\n                                                        foreach (Wz_Node node in digitNode.Nodes)\n                                                        {\n                                                            if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                            {\n                                                                damageSkin.MiniUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                                break;\n                                                            }\n                                                        }\n                                                    }\n                                                    else if (digitNode.Text == \"numberSpace\")\n                                                    {\n                                                        damageSkin.MiniUnitSpacing = digitNode.GetValue<int>();\n                                                    }\n                                                }\n                                                break;\n                                            case \"NoRed1\":\n                                                foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                                {\n                                                    if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                                    {\n                                                        damageSkin.BigUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                                    }\n                                                    else if (digitNode.Nodes.Count > 1)\n                                                    {\n                                                        foreach (Wz_Node node in digitNode.Nodes)\n                                                        {\n                                                            if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                            {\n                                                                damageSkin.BigUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                                break;\n                                                            }\n                                                        }\n                                                    }\n                                                    else if (digitNode.Text == \"numberSpace\")\n                                                    {\n                                                        damageSkin.BigUnitSpacing = digitNode.GetValue<int>();\n                                                    }\n                                                }\n                                                break;\n                                            case \"NoCri0\":\n                                                foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                                {\n                                                    if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                                    {\n                                                        damageSkin.MiniCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                                    }\n                                                    else if (digitNode.Nodes.Count > 1)\n                                                    {\n                                                        foreach (Wz_Node node in digitNode.Nodes)\n                                                        {\n                                                            if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                            {\n                                                                damageSkin.MiniCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                                break;\n                                                            }\n                                                        }\n                                                    }\n                                                    else if (digitNode.Text == \"numberSpace\")\n                                                    {\n                                                        damageSkin.MiniCriticalUnitSpacing = digitNode.GetValue<int>();\n                                                    }\n                                                }\n                                                break;\n                                            case \"NoCri1\":\n                                                foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                                {\n                                                    if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                                    {\n                                                        damageSkin.BigCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                                    }\n                                                    else if (digitNode.Nodes.Count > 1)\n                                                    {\n                                                        foreach (Wz_Node node in digitNode.Nodes)\n                                                        {\n                                                            if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                            {\n                                                                damageSkin.BigCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                                break;\n                                                            }\n                                                        }\n                                                    }\n                                                    else if (digitNode.Text == \"numberSpace\")\n                                                    {\n                                                        damageSkin.BigCriticalUnitSpacing = digitNode.GetValue<int>();\n                                                    }\n                                                }\n                                                break;\n                                            case \"customType\":\n                                                damageSkin.CustomType = customSubNode.GetValue<string>();\n                                                break;\n                                        }\n                                    }\n                                    break;\n                            }\n                        }\n                        break;\n                    // Legacy below\n                    case \"NoRed0\":\n                        foreach (Wz_Node digitNode in subNode.Nodes)\n                        {\n                            if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                            {\n                                damageSkin.MiniDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                            }\n                            else if (digitNode.Nodes.Count > 1)\n                            {\n                                foreach (Wz_Node node in digitNode.Nodes)\n                                {\n                                    if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                    {\n                                        damageSkin.MiniDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                        break;\n                                    }\n                                }\n                            }\n                            else if (digitNode.Text == \"numberSpace\")\n                            {\n                                damageSkin.MiniDigitSpacing = digitNode.GetValue<int>();\n                            }\n                        }\n                        break;\n                    case \"NoRed1\":\n                        foreach (Wz_Node digitNode in subNode.Nodes)\n                        {\n                            if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                            {\n                                damageSkin.BigDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                            }\n                            else if (digitNode.Nodes.Count > 1)\n                            {\n                                foreach (Wz_Node node in digitNode.Nodes)\n                                {\n                                    if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                    {\n                                        damageSkin.BigDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                        break;\n                                    }\n                                }\n                            }\n                            else if (digitNode.Text == \"numberSpace\")\n                            {\n                                damageSkin.BigDigitSpacing = digitNode.GetValue<int>();\n                            }\n                        }\n                        break;\n                    case \"NoCri0\":\n                        foreach (Wz_Node digitNode in subNode.Nodes)\n                        {\n                            if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                            {\n                                damageSkin.MiniCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                            }\n                            else if (digitNode.Nodes.Count > 1)\n                            {\n                                foreach (Wz_Node node in digitNode.Nodes)\n                                {\n                                    if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                    {\n                                        damageSkin.MiniCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                        break;\n                                    }\n                                }\n                            }\n                            else if (digitNode.Text == \"numberSpace\")\n                            {\n                                damageSkin.MiniCriticalDigitSpacing = digitNode.GetValue<int>();\n                            }\n                        }\n                        break;\n                    case \"NoCri1\":\n                        foreach (Wz_Node digitNode in subNode.Nodes)\n                        {\n                            if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                            {\n                                damageSkin.BigCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                            }\n                            else if (digitNode.Nodes.Count > 1)\n                            {\n                                foreach (Wz_Node node in digitNode.Nodes)\n                                {\n                                    if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                    {\n                                        damageSkin.BigCriticalDigit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                        break;\n                                    }\n                                }\n                            }\n                            else if (digitNode.Text == \"numberSpace\")\n                            {\n                                damageSkin.BigCriticalDigitSpacing = digitNode.GetValue<int>();\n                            }\n                        }\n                        break;\n                    case \"NoCustom\":\n                        foreach (Wz_Node customSubNode in subNode.Nodes)\n                        {\n                            switch (customSubNode.Text)\n                            {\n                                case \"NoRed0\":\n                                    foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.MiniUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.MiniUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.MiniUnitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"NoRed1\":\n                                    foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.BigUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.BigUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.BigUnitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"NoCri0\":\n                                    foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.MiniCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.MiniCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.MiniCriticalUnitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"NoCri1\":\n                                    foreach (Wz_Node digitNode in customSubNode.Nodes)\n                                    {\n                                        if (digitNode.Value is Wz_Uol || digitNode.Value is Wz_Png)\n                                        {\n                                            damageSkin.BigCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(digitNode, findNode));\n                                        }\n                                        else if (digitNode.Nodes.Count > 1)\n                                        {\n                                            foreach (Wz_Node node in digitNode.Nodes)\n                                            {\n                                                if (node.Value is Wz_Uol || node.Value is Wz_Png)\n                                                {\n                                                    damageSkin.BigCriticalUnit.Add(digitNode.Text, BitmapOrigin.CreateFromNode(node, findNode));\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                        else if (digitNode.Text == \"numberSpace\")\n                                        {\n                                            damageSkin.BigCriticalUnitSpacing = digitNode.GetValue<int>();\n                                        }\n                                    }\n                                    break;\n                                case \"customType\":\n                                    damageSkin.CustomType = customSubNode.GetValue<string>();\n                                    break;\n                            }\n                        }\n                        break;\n                }\n            }\n\n            return damageSkin;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/ExclusiveEquip.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class ExclusiveEquip\n    {\n        public ExclusiveEquip()\n        {\n            this.Items = new List<int>();\n        }\n        public string Info { get; set; }\n        public List<int> Items { get; private set; }\n        public string Msg { get; set; }\n\n        public static ExclusiveEquip CreateFromNode(Wz_Node exclusiveEquipNode)\n        {\n            if (exclusiveEquipNode == null)\n                return null;\n\n            ExclusiveEquip exclusiveEquip = new ExclusiveEquip();\n\n            foreach (Wz_Node subNode in exclusiveEquipNode.Nodes)\n            {\n                switch (subNode.Text)\n                {\n                    case \"info\":\n                        exclusiveEquip.Info = Convert.ToString(subNode.Value);\n                        break;\n                    case \"item\":\n                        foreach (Wz_Node itemNode in subNode.Nodes)\n                        {\n                            int itemID = Convert.ToInt32(itemNode.Value);\n                            exclusiveEquip.Items.Add(itemID);\n                        }\n                        break;\n                    case \"msg\":\n                        exclusiveEquip.Msg = Convert.ToString(subNode.Value);\n                        break;\n                }\n            }\n\n            return exclusiveEquip;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Familiar.cs",
    "content": "﻿using System;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Familiar\n    {\n        public Familiar()\n        {\n            FamiliarAttribute = \"N\";\n            FamiliarCategory = 1;\n        }\n\n        public int FamiliarID { get; set; }\n        public int MobID { get; set; }\n        public int MonsterCardID { get; set; }\n        public int FamiliarCategory { get; set; }\n        public int SkillID { get; set; }\n        public int SkillEffectAfter { get; set; }\n        public int Range { get; set; }\n        public string FamiliarAttribute { get; set; }\n        public BitmapOrigin FamiliarCover { get; set; }\n\n        public static Familiar CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            if (node == null)\n                return null;\n            int familiarID;\n            Match m = Regex.Match(node.Text, @\"^(\\d+)\\.img$\");\n            if (!(m.Success && Int32.TryParse(m.Result(\"$1\"), out familiarID)))\n            {\n                return null;\n            }\n\n            Familiar familiar = new Familiar();\n\n            familiar.FamiliarID = familiarID;\n\n            Wz_Node standNode = node.FindNodeByPath(\"stand\\\\0\").ResolveUol();\n            if (standNode != null)\n            {\n                familiar.FamiliarCover = BitmapOrigin.CreateFromNode(standNode, findNode);\n            }\n\n            Wz_Node infoNode = node.FindNodeByPath(\"info\").ResolveUol();\n\n            if (infoNode != null)\n            {\n                foreach (Wz_Node subNode in infoNode.Nodes)\n                {\n                    switch (subNode.Text)\n                    {\n                        case \"FAttribute\":\n                            familiar.FamiliarAttribute = subNode.GetValue<string>();\n                            break;\n                        case \"FCategory\":\n                            familiar.FamiliarCategory = subNode.GetValue<int>();\n                            break;\n                        case \"MobID\":\n                            familiar.MobID = subNode.GetValue<int>();\n                            break;\n                        case \"monsterCardID\":\n                            familiar.MonsterCardID = subNode.GetValue<int>();\n                            break;\n                        case \"range\":\n                            familiar.Range = subNode.GetValue<int>();\n                            break;\n                        case \"skill\":\n                            foreach (Wz_Node skillNode in subNode.Nodes)\n                            {\n                                switch (skillNode.Text)\n                                {\n                                    case \"id\":\n                                        familiar.SkillID = skillNode.GetValue<int>();\n                                        break;\n                                    case \"effectAfter\":\n                                        familiar.SkillEffectAfter = skillNode.GetValue<int>();\n                                        break;\n                                }\n                            }\n                            break;\n                        case \"portrait\":\n                            familiar.FamiliarCover = BitmapOrigin.CreateFromNode(subNode, findNode);\n                            break;\n                    }\n                }\n            }\n\n            return familiar;\n        }\n    }\n}\n\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/FormulaVersion.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum FormulaVersion\n    {\n        Bigbang = 0,\n        Chaos = 1\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Gear.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Drawing;\nusing System.Linq;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Gear : ItemBase\n    {\n        public Gear()\n        {\n            Props = new Dictionary<GearPropType, int>();\n            VariableStat = new Dictionary<GearPropType, float>();\n            AbilityTimeLimited = new Dictionary<GearPropType, int>();\n            ReqSpecJobs = new List<int>();\n            Options = new Potential[3];\n            AdditionalOptions = new Potential[3];\n            Additions = new List<Addition>();\n        }\n        public GearGrade Grade { get; set; }\n        public GearGrade AdditionGrade { get; set; }\n        public GearType type;\n        public GearState State { get; set; }\n\n        public int diff;\n\n        public Potential[] Options { get; private set; }\n        public Potential[] AdditionalOptions { get; private set; }\n        public AlienStone AlienStoneSlot { get; set; }\n\n        public int Star { get; set; }\n        public int ScrollUp { get; set; }\n        public int Hammer { get; set; }\n        public bool HasTuc { get; internal set; }\n        public int PlatinumHammer { get; set; }\n        public bool CanPotential { get; internal set; }\n        public string EpicHs { get; internal set; }\n\n        public bool FixLevel { get; internal set; }\n        public List<GearLevelInfo> Levels { get; internal set; }\n        public List<GearSealedInfo> Seals { get; internal set; }\n\n        public List<Addition> Additions { get; private set; }\n        public bool AdditionHideDesc { get; set; }\n        public Dictionary<GearPropType, int> Props { get; private set; }\n        public Dictionary<GearPropType, float> VariableStat { get; private set; }\n        public Dictionary<GearPropType, int> AbilityTimeLimited { get; private set; }\n        public List<int> ReqSpecJobs { get; private set; }\n\n        /// <summary>\n        /// 获取或设置装备的标准属性。\n        /// </summary>\n        public Dictionary<GearPropType, int> StandardProps { get; private set; }\n\n\n        public bool Epic\n        {\n            get { return GetBooleanValue(GearPropType.epicItem); }\n        }\n\n        public bool TimeLimited\n        {\n            get { return GetBooleanValue(GearPropType.timeLimited); }\n        }\n\n        public bool Cash\n        {\n            get { return GetBooleanValue(GearPropType.cash); }\n        }\n\n        public bool GetBooleanValue(GearPropType type)\n        {\n            int value;\n            return this.Props.TryGetValue(type, out value) && value != 0;\n        }\n\n        public IEnumerable<KeyValuePair<GearPropType, int>> PropsV5\n        {\n            get\n            {\n                return this.Props.Where(kv => IsV5SupportPropType(kv.Key));\n            }\n        }\n\n        public int GetMaxStar()\n        {\n            if (!this.HasTuc)\n            {\n                return 0;\n            }\n            if (this.Cash)\n            {\n                return 0;\n            }\n            if (this.GetBooleanValue(GearPropType.onlyUpgrade))\n            {\n                return 0;\n            }\n            if (this.type == GearType.machineEngine || this.type == GearType.machineArms || this.type == GearType.machineLegs || this.type == GearType.machineBody || this.type == GearType.machineTransistors || this.type == GearType.dragonMask || this.type == GearType.dragonPendant || this.type == GearType.dragonWings || this.type == GearType.dragonTail)\n            {\n                return 0;\n            }\n\n            int reqLevel;\n            this.Props.TryGetValue(GearPropType.reqLevel, out reqLevel);\n            int[] data = null;\n            foreach (int[] item in starData)\n            {\n                if (reqLevel >= item[0])\n                {\n                    data = item;\n                }\n                else\n                {\n                    break;\n                }\n            }\n            if (data == null)\n            {\n                return 0;\n            }\n\n            return data[this.GetBooleanValue(GearPropType.superiorEqp) ? 2 : 1];\n        }\n\n        private static readonly int[][] starData = new int[][] {\n            new[]{ 0, 5, 3 }, \n            new[]{ 95, 8, 5 }, \n            new[]{ 110, 10, 8 }, \n            new[]{ 120, 15, 10 }, \n            new[]{ 130, 20, 12 }, \n            new[]{ 140, 25, 15 }, \n        };\n\n        public override object Clone()\n        {\n            Gear gear = (Gear)this.MemberwiseClone();\n            gear.Props = new Dictionary<GearPropType, int>(this.Props.Count);\n            foreach (KeyValuePair<GearPropType, int> p in this.Props)\n            {\n                gear.Props.Add(p.Key, p.Value);\n            }\n            gear.Options = (Potential[])this.Options.Clone();\n            gear.Additions = new List<Addition>(this.Additions);\n            return gear;\n        }\n\n        public void MakeTimeLimitedPropAvailable()\n        {\n            if (AbilityTimeLimited.Count > 0 && !this.GetBooleanValue(GearPropType.abilityTimeLimited))\n            {\n                int diff = 0;\n                foreach(var kv in AbilityTimeLimited)\n                {\n                    this.Props.TryGetValue(kv.Key, out int oldValue);\n                    this.Props[kv.Key] = oldValue + kv.Value;\n                    diff += kv.Value / Gear.GetPropTypeWeight(kv.Key);\n                }\n                this.Props[GearPropType.abilityTimeLimited] = 1;\n                this.diff += diff;\n            }\n        }\n\n        public void RestoreStandardProperties()\n        {\n            if (this.StandardProps != null)\n            {\n                this.Props.Clear();\n                foreach (var kv in this.StandardProps)\n                {\n                    this.Props[kv.Key] = kv.Value;\n                }\n                this.diff = 0;\n            }\n        }\n\n        public bool IsGenesisWeapon\n        {\n            get\n            {\n                // There's no better way to determine if a weapon is a Genesis weapon, the game itself also uses a hard-coded list to check it.\n                if (IsWeapon(this.type)\n                    && this.Props.TryGetValue(GearPropType.setItemID, out var setItemID)\n                    && 886 <= setItemID && setItemID <= 890)\n                {\n                    return true;\n                }\n                return false;\n            }\n        }\n\n        public static bool IsWeapon(GearType type)\n        {\n            return IsLeftWeapon(type)\n                || IsDoubleHandWeapon(type);\n        }\n\n        /// <summary>\n        /// 获取一个值，指示装备类型是否为主手武器。\n        /// </summary>\n        /// <param name=\"type\">装备类型。</param>\n        /// <returns></returns>\n        public static bool IsLeftWeapon(GearType type)\n        {\n            return (int)type >= 121 && (int)type <= 139 && type != GearType.katara\n                || ((int)type / 10) == 121 || ((int)type / 10) == 125;\n        }\n\n        public static bool IsSubWeapon(GearType type)\n        {\n            switch (type)\n            {\n                case GearType.katara:\n                //case GearType.shield:\n                case GearType.demonShield:\n                case GearType.soulShield:\n                    return true;\n\n                default:\n                    if ((int)type / 1000 == 135)\n                    {\n                        return true;\n                    }\n                    return false;\n            }\n        }\n\n        /// <summary>\n        /// 获取一个值，指示装备类型是否为双手武器。\n        /// </summary>\n        /// <param name=\"type\">装备类型。</param>\n        /// <returns></returns>\n        public static bool IsDoubleHandWeapon(GearType type)\n        {\n            int _type = (int)type;\n            return (_type >= 140 && _type <= 149)\n                || (_type >= 152 && _type <= 159)\n                || type == GearType.boxingCannon\n                || type == GearType.chakram;\n        }\n\n        public static bool IsMechanicGear(GearType type)\n        {\n            return (int)type >= 161 && (int)type <= 165;\n        }\n\n        public static bool IsDragonGear(GearType type)\n        {\n            return (int)type >= 194 && (int)type <= 197;\n        }\n\n        public static int Compare(Gear gear, Gear originGear)\n        {\n            if (gear.ItemID != originGear.ItemID)\n                return 0;\n            int diff = 0;\n            int tempValue;\n            foreach (KeyValuePair<GearPropType, int> prop in gear.Props)\n            {\n                originGear.Props.TryGetValue(prop.Key, out tempValue);//在原装备中寻找属性 若没有找到 视为0\n                diff += (prop.Value - tempValue) / GetPropTypeWeight(prop.Key);\n            }\n            foreach (KeyValuePair<GearPropType, int> prop in originGear.Props)\n            {\n                if (!gear.Props.TryGetValue(prop.Key, out tempValue))//寻找装备原属性里新装备没有的\n                {\n                    diff -= prop.Value / GetPropTypeWeight(prop.Key);\n                }\n            }\n            return diff;\n        }\n\n        private static int GetPropTypeWeight(GearPropType type)\n        {\n            if ((int)type < 100)\n            {\n                switch (type)\n                {\n                    case GearPropType.incMHP:\n                    case GearPropType.incMMP:\n                    case GearPropType.incACC:\n                    case GearPropType.incEVA:\n                        return 10;\n                }\n                return 1;\n            }\n            return int.MaxValue;\n        }\n\n        public static bool IsEpicPropType(GearPropType type)\n        {\n            switch (type)\n            {\n                case GearPropType.incPAD:\n                case GearPropType.incMAD:\n                case GearPropType.incSTR:\n                case GearPropType.incDEX:\n                case GearPropType.incINT:\n                case GearPropType.incLUK:\n                    return true;\n                default:\n                    return false;\n            }\n        }\n\n        public static bool IsV5SupportPropType(GearPropType type)\n        {\n            switch (type)\n            {\n                case GearPropType.incMDD:\n                case GearPropType.incMDDr:\n                case GearPropType.incACC:\n                case GearPropType.incACCr:\n                case GearPropType.incEVA:\n                case GearPropType.incEVAr:\n                    return false;\n                default:\n                    return true;\n            }\n        }\n\n        /// <summary>\n        /// 获取装备类型。\n        /// </summary>\n        /// <param Name=\"gearCode\"></param>\n        /// <returns></returns>\n        public static GearType GetGearType(int code)\n        {\n            switch (code / 1000)\n            {\n                case 1098:\n                    return GearType.soulShield;\n                case 1099:\n                    return GearType.demonShield;\n                case 1212:\n                    return GearType.shiningRod;\n                case 1213:\n                    return GearType.tuner;\n                case 1214:\n                    return GearType.breathShooter;\n                case 1252:\n                    return GearType.memorialStaff;\n                case 1253:\n                    return GearType.celestialLight;\n                case 1254:\n                    return GearType.onmyoSen;\n                case 1259:\n                    return GearType.magicStick;\n                case 1403:\n                    return GearType.boxingCannon;\n                case 1404:\n                    return GearType.chakram;\n            }\n            if (code / 10000 == 135)\n            {\n                switch (code / 100)\n                {\n                    case 13522:\n                    case 13528:\n                    case 13529:\n                    case 13540:\n                        return (GearType)(code / 10);\n\n                    default:\n                        return (GearType)(code / 100 * 10);\n                }\n            }\n            if (code / 10000 == 119)\n            {\n                switch(code / 100)\n                {\n                    case 11902:\n                        return (GearType)(code / 10);\n                }\n            }\n            // MSN support\n            if (code / 10000 == 179)\n            {\n                switch (code / 1000)\n                {\n                    case 1790:\n                    case 1791:\n                    case 1792:\n                    case 1793:\n                        return (GearType)(code / 1000);\n                    default:\n                        return (GearType)(code / 100 * 10);\n                }\n            }\n            return (GearType)(code / 10000);\n        }\n\n        public static int GetGender(int code)\n        {\n            GearType type = GetGearType(code);\n            switch (type)\n            {\n                case GearType.emblem:\n                case GearType.bit:\n                case GearType.jewel:\n                case GearType.astra:\n                case (GearType)3: //发型\n                    return 2;\n            }\n\n            return code / 1000 % 10;\n        }\n\n        public static bool SpecialCanPotential(GearType type)\n        {\n            switch (type)\n            {\n                case GearType.soulShield:\n                case GearType.demonShield:\n                case GearType.katara:\n                case GearType.magicArrow:\n                case GearType.card:\n                case GearType.box:\n                case GearType.orb:\n                case GearType.novaMarrow:\n                case GearType.soulBangle:\n                case GearType.mailin:\n                case GearType.emblem:\n                    return true;\n                default:\n                    return false;\n            }\n        }\n\n        public static IEnumerable<KeyValuePair<GearPropType, int>> CombineProperties(IEnumerable<KeyValuePair<GearPropType, int>> props)\n        {\n            var wrappedProp = props.Select(kv => new KeyValuePair<GearPropType, object>(kv.Key, kv.Value));\n            var combinedProp = CombineProperties(wrappedProp);\n            return combinedProp.Select(kv => new KeyValuePair<GearPropType, int>(kv.Key, Convert.ToInt32(kv.Value)));\n        }\n\n        public static IEnumerable<KeyValuePair<GearPropType, object>> CombineProperties(IEnumerable<KeyValuePair<GearPropType, object>> props)\n        {\n            var combinedProps = new SortedDictionary<GearPropType, object>();\n            var propCache = new SortedDictionary<GearPropType, object>();\n            foreach (var kv in props)\n            {\n                propCache.Add(kv.Key, kv.Value);\n            }\n\n            object obj;\n            foreach (var prop in propCache)\n            {\n                switch (prop.Key)\n                {\n                    case GearPropType.incMHP:\n                    case GearPropType.incMMP:\n                        if (combinedProps.ContainsKey(GearPropType.incMHP_incMMP))\n                        {\n                            break;\n                        }\n                        else if (propCache.TryGetValue(prop.Key == GearPropType.incMHP ? GearPropType.incMMP : GearPropType.incMHP, out obj)\n                            && object.Equals(prop.Value, obj))\n                        {\n                            combinedProps.Add(GearPropType.incMHP_incMMP, prop.Value);\n                            break;\n                        }\n                        goto default;\n\n                    case GearPropType.incMHPr:\n                    case GearPropType.incMMPr:\n                        if (combinedProps.ContainsKey(GearPropType.incMHPr_incMMPr))\n                        {\n                            break;\n                        }\n                        else if (propCache.TryGetValue(prop.Key == GearPropType.incMHPr ? GearPropType.incMMPr : GearPropType.incMHPr, out obj)\n                            && object.Equals(prop.Value, obj))\n                        {\n                            combinedProps.Add(GearPropType.incMHPr_incMMPr, prop.Value);\n                            break;\n                        }\n                        goto default;\n\n                    case GearPropType.incPAD:\n                    case GearPropType.incMAD:\n                        if (combinedProps.ContainsKey(GearPropType.incPAD_incMAD))\n                        {\n                            break;\n                        }\n                        else if (propCache.TryGetValue(prop.Key == GearPropType.incPAD ? GearPropType.incMAD : GearPropType.incPAD, out obj)\n                            && object.Equals(prop.Value, obj))\n                        {\n                            combinedProps.Add(GearPropType.incPAD_incMAD, prop.Value);\n                            break;\n                        }\n                        goto default;\n\n                    case GearPropType.incPDD:\n                    case GearPropType.incMDD:\n                        if (combinedProps.ContainsKey(GearPropType.incPDD_incMDD))\n                        {\n                            break;\n                        }\n                        else if (propCache.TryGetValue(prop.Key == GearPropType.incPDD ? GearPropType.incMDD : GearPropType.incPDD, out obj)\n                            && object.Equals(prop.Value, obj))\n                        {\n                            combinedProps.Add(GearPropType.incPDD_incMDD, prop.Value);\n                            break;\n                        }\n                        goto default;\n\n                    case GearPropType.incACC:\n                    case GearPropType.incEVA:\n                        if (combinedProps.ContainsKey(GearPropType.incACC_incEVA))\n                        {\n                            break;\n                        }\n                        else if (propCache.TryGetValue(prop.Key == GearPropType.incACC ? GearPropType.incEVA : GearPropType.incACC, out obj)\n                            && object.Equals(prop.Value, obj))\n                        {\n                            combinedProps.Add(GearPropType.incACC_incEVA, prop.Value);\n                            break;\n                        }\n                        goto default;\n\n                    default:\n                        combinedProps.Add(prop.Key, prop.Value);\n                        break;\n                }\n            }\n            return combinedProps;\n        }\n\n        public static Gear CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            int gearID;\n            Match m = Regex.Match(node.Text, @\"^(\\d{8})\\.img$\");\n            if (!(m.Success && Int32.TryParse(m.Result(\"$1\"), out gearID)))\n            {\n                return null;\n            }\n            Gear gear = new Gear();\n            gear.ItemID = gearID;\n            gear.type = Gear.GetGearType(gear.ItemID);\n            Wz_Node infoNode = node.FindNodeByPath(\"info\");\n\n            if (infoNode != null)\n            {\n                foreach (Wz_Node subNode in infoNode.Nodes)\n                {\n                    switch (subNode.Text)\n                    {\n                        case \"icon\":\n                            if (subNode.Value is Wz_Uol || subNode.Value is Wz_Png)\n                            {\n                                gear.Icon = BitmapOrigin.CreateFromNode(subNode, findNode);\n                            }\n                            break;\n\n                        case \"iconRaw\":\n                            if (subNode.Value is Wz_Uol || subNode.Value is Wz_Png)\n                            {\n                                gear.IconRaw = BitmapOrigin.CreateFromNode(subNode, findNode);\n                            }\n                            break;\n\n                        case \"sample\":\n                            if (subNode.Value is Wz_Uol || subNode.Value is Wz_Png)\n                            {\n                                gear.Sample = BitmapOrigin.CreateFromNode(subNode, findNode);\n                            }\n                            break;\n\n                        case \"addition\": //附加属性信息\n                            foreach (Wz_Node addiNode in subNode.Nodes)\n                            {\n                                if (addiNode.Text == \"hideDesc\")\n                                {\n                                    gear.AdditionHideDesc = true;\n                                }\n                                else\n                                {\n                                    Addition addi = Addition.CreateFromNode(addiNode);\n                                    if (addi != null)\n                                        gear.Additions.Add(addi);\n                                }\n                            }\n                            gear.Additions.Sort((add1, add2) => (int)add1.Type - (int)add2.Type);\n                            break;\n\n                        case \"option\": //附加潜能信息\n                            Wz_Node itemWz = findNode !=null? findNode(\"Item\\\\ItemOption.img\"):null;\n                            if (itemWz == null)\n                                break;\n                            int optIdx = 0;\n                            foreach (Wz_Node optNode in subNode.Nodes)\n                            {\n                                int optId = 0, optLevel = 0;\n                                foreach (Wz_Node optArgNode in optNode.Nodes)\n                                {\n                                    switch (optArgNode.Text)\n                                    {\n                                        case \"option\": optId = Convert.ToInt32(optArgNode.Value); break;\n                                        case \"level\": optLevel = Convert.ToInt32(optArgNode.Value); break;\n                                    }\n                                }\n\n                                Potential opt = Potential.CreateFromNode(itemWz.FindNodeByPath(optId.ToString(\"d6\")), optLevel);\n                                if (opt != null)\n                                    gear.Options[optIdx++] = opt;\n                            }\n                            break;\n\n                        case \"level\": //可升级信息\n                            if (subNode.Nodes[\"fixLevel\"].GetValueEx<int>(0) != 0)\n                            {\n                                gear.FixLevel = true;\n                            }\n\n                            Wz_Node levelInfo = subNode.Nodes[\"info\"];\n                            gear.Levels = new List<GearLevelInfo>();\n                            if (levelInfo != null)\n                            {\n                                for (int i = 1; ; i++)\n                                {\n                                    Wz_Node levelInfoNode = levelInfo.Nodes[i.ToString()];\n                                    if (levelInfoNode != null)\n                                    {\n                                        GearLevelInfo info = GearLevelInfo.CreateFromNode(levelInfoNode);\n                                        int lv;\n                                        Int32.TryParse(levelInfoNode.Text, out lv);\n                                        info.Level = lv;\n                                        gear.Levels.Add(info);\n                                    }\n                                    else\n                                    {\n                                        break;\n                                    }\n                                }\n                            }\n\n                            Wz_Node levelCase = subNode.Nodes[\"case\"];\n                            if (levelCase != null)\n                            {\n                                int probTotal = 0;\n                                foreach (Wz_Node caseNode in levelCase.Nodes)\n                                {\n                                    int prob = caseNode.Nodes[\"prob\"].GetValueEx(0);\n                                    probTotal += prob;\n                                    for (int i = 0; i < gear.Levels.Count; i++)\n                                    {\n                                        GearLevelInfo info = gear.Levels[i];\n                                        Wz_Node caseLevel = caseNode.Nodes[info.Level.ToString()];\n                                        if (caseLevel != null)\n                                        {\n                                            //desc\n                                            Wz_Node caseHS = caseLevel.Nodes[\"hs\"];\n                                            if (caseHS != null)\n                                            {\n                                                info.HS = caseHS.GetValue<string>();\n                                            }\n\n                                            //随机技能\n                                            Wz_Node caseSkill = caseLevel.Nodes[\"Skill\"];\n                                            if (caseSkill != null)\n                                            {\n                                                foreach (Wz_Node skillNode in caseSkill.Nodes)\n                                                {\n                                                    int id = skillNode.Nodes[\"id\"].GetValueEx(-1);\n                                                    int level = skillNode.Nodes[\"level\"].GetValueEx(-1);\n                                                    if (id >= 0 && level >= 0)\n                                                    {\n                                                        info.Skills[id] = level;\n                                                    }\n                                                }\n                                            }\n\n                                            //装备技能\n                                            Wz_Node equipSkill = caseLevel.Nodes[\"EquipmentSkill\"];\n                                            if (equipSkill != null)\n                                            {\n                                                foreach (Wz_Node skillNode in equipSkill.Nodes)\n                                                {\n                                                    int id = skillNode.Nodes[\"id\"].GetValueEx(-1);\n                                                    int level = skillNode.Nodes[\"level\"].GetValueEx(-1);\n                                                    if (id >= 0 && level >= 0)\n                                                    {\n                                                        info.EquipmentSkills[id] = level;\n                                                    }\n                                                }\n                                            }\n                                            info.Prob = prob;\n                                        }\n                                    }\n                                }\n\n                                foreach (var info in gear.Levels)\n                                {\n                                    info.ProbTotal = probTotal;\n                                }\n                            }\n                            gear.Props.Add(GearPropType.level, 1);\n                            break;\n\n                        case \"sealed\": //封印解除信息\n                            Wz_Node sealedInfo = subNode.Nodes[\"info\"];\n                            gear.Seals = new List<GearSealedInfo>();\n                            if (sealedInfo != null)\n                            {\n                                foreach (Wz_Node levelInfoNode in sealedInfo.Nodes)\n                                {\n                                    GearSealedInfo info = GearSealedInfo.CreateFromNode(levelInfoNode, findNode);\n                                    int lv;\n                                    Int32.TryParse(levelInfoNode.Text, out lv);\n                                    info.Level = lv;\n                                    gear.Seals.Add(info);\n                                }\n                            }\n                            gear.Props.Add(GearPropType.@sealed, 1);\n                            break;\n\n                        case \"variableStat\": //升级奖励属性\n                            foreach (Wz_Node statNode in subNode.Nodes)\n                            {\n                                GearPropType type;\n                                if (Enum.TryParse(statNode.Text, out type))\n                                {\n                                    try\n                                    {\n                                        gear.VariableStat.Add(type, Convert.ToSingle(statNode.Value));\n                                    }\n                                    finally\n                                    {\n                                    }\n                                }\n                            }\n                            break;\n\n                        case \"abilityTimeLimited\": //限时属性\n                            foreach (Wz_Node statNode in subNode.Nodes)\n                            {\n                                GearPropType type;\n                                if (Enum.TryParse(statNode.Text, out type))\n                                {\n                                    try\n                                    {\n                                        gear.AbilityTimeLimited.Add(type, Convert.ToInt32(statNode.Value));\n                                    }\n                                    finally\n                                    {\n                                    }\n                                }\n                            }\n                            break;\n\n                        case \"onlyUpgrade\":\n                            int upgradeItemID = subNode.Nodes[\"0\"]?.GetValueEx(0) ?? 0;\n                            gear.Props.Add(GearPropType.onlyUpgrade, upgradeItemID);\n                            break;\n\n                        case \"epic\":\n                            Wz_Node hsNode = subNode.Nodes[\"hs\"];\n                            if (hsNode != null)\n                            {\n                                gear.EpicHs = Convert.ToString(hsNode.Value);\n                            }\n                            break;\n\n                        case \"gatherTool\":\n                            foreach (Wz_Node gatherNode in subNode.Nodes)\n                            {\n                                GearPropType type;\n                                if (Enum.TryParse(subNode.Text + \"_\" + gatherNode.Text, out type))\n                                {\n                                    try\n                                    {\n                                        gear.Props.Add(type, Convert.ToInt32(gatherNode.Value));\n                                    }\n                                    finally\n                                    {\n                                    }\n                                }\n                            }\n                            break;\n\n                        case \"reqSpecJobs\":\n                            foreach (Wz_Node jobNode in subNode.Nodes)\n                            {\n                                gear.ReqSpecJobs.Add(jobNode.GetValue<int>());\n                            }\n                            break;\n\n                        default:\n                            {\n                                GearPropType type;\n                                if (!int.TryParse(subNode.Text, out _) && Enum.TryParse(subNode.Text, out type))\n                                {\n                                    try\n                                    {\n                                        gear.Props.Add(type, Convert.ToInt32(subNode.Value));\n                                    }\n                                    finally\n                                    {\n                                    }\n                                }\n                            }\n                            break;\n                    }\n                }\n            }\n            int value;\n\n            //读取默认可升级状态\n            if (gear.Props.TryGetValue(GearPropType.tuc, out value) && value > 0)\n            {\n                gear.HasTuc = true;\n                gear.CanPotential = true;\n            }\n            else if (Gear.SpecialCanPotential(gear.type))\n            {\n                gear.CanPotential = true;\n            }\n\n            //读取默认gearGrade\n            if (gear.Props.TryGetValue(GearPropType.fixedGrade, out value))\n            {\n                gear.Grade = (GearGrade)(value - 1);\n            }\n\n            //自动填充Grade\n            if (gear.Options.Any(opt => opt != null) && gear.Grade == GearGrade.C)\n            {\n                gear.Grade = GearGrade.B;\n            }\n\n            //添加默认装备要求\n            GearPropType[] types = new GearPropType[]{\n                GearPropType.reqJob,GearPropType.reqLevel,GearPropType.reqSTR,GearPropType.reqDEX,\n            GearPropType.reqINT,GearPropType.reqLUK};\n            foreach (GearPropType type in types)\n            {\n                if (!gear.Props.ContainsKey(type))\n                {\n                    gear.Props.Add(type, 0);\n                }\n            }\n\n            //修复恶魔盾牌特殊属性\n            if (gear.type == GearType.demonShield)\n            {\n                if (gear.Props.TryGetValue(GearPropType.incMMP, out value))\n                {\n                    gear.Props.Remove(GearPropType.incMMP);\n                    gear.Props.Add(GearPropType.incMDF, value);\n                }\n            }\n\n            //检查道具默认的剪刀次数\n            var cuttableCountOverride = findNode?.Invoke(@$\"Etc\\KarmaScissor_WZ2.img\\ItemList\\{gear.ItemID}\")?.GetValueEx<int>();\n            if (cuttableCountOverride != null && cuttableCountOverride > 0)\n            {\n                gear.Props[GearPropType.CuttableCount] = cuttableCountOverride.Value;\n            }\n\n            //备份标准属性\n            gear.StandardProps = new Dictionary<GearPropType, int>(gear.Props);\n\n            //追加限时属性\n            gear.MakeTimeLimitedPropAvailable();\n\n            return gear;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/GearGrade.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum GearGrade\n    {\n        /// <summary>\n        /// C级(一般物品)\n        /// </summary>\n        C = 0,\n        /// <summary>\n        /// B级(高级物品)\n        /// </summary>\n        B = 1,\n        /// <summary>\n        /// A级(史诗物品)\n        /// </summary>\n        A = 2,\n        /// <summary>\n        /// S级(传说物品)\n        /// </summary>\n        S = 3,\n        /// <summary>\n        /// SS级(垃圾物品)\n        /// </summary>\n        SS = 4,\n        /// <summary>\n        /// (特殊物品)\n        /// </summary>\n        Special=5,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/GearLevelInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class GearLevelInfo\n    {\n        public GearLevelInfo()\n        {\n            this.BonusProps = new Dictionary<GearPropType, Range>();\n            this.Skills = new Dictionary<int, int>();\n            this.EquipmentSkills = new Dictionary<int, int>();\n        }\n\n        public int Level { get; set; }\n        public Dictionary<GearPropType, Range> BonusProps { get; private set; }\n        public int Exp { get; set; }\n\n        public string HS { get; set; }\n        public int Prob { get; set; }\n        public int ProbTotal { get; set; }\n        public Dictionary<int, int> Skills { get; private set; }\n        public Dictionary<int, int> EquipmentSkills { get; private set; }\n\n        public static GearLevelInfo CreateFromNode(Wz_Node node)\n        {\n            GearLevelInfo info = new GearLevelInfo();\n\n            foreach (Wz_Node child in node.Nodes)\n            {\n                if (child.Text == \"exp\")\n                {\n                    info.Exp = child.GetValue(0);\n                }\n                else \n                {\n                    string prefix;\n                    if (child.Text.EndsWith(\"Min\") || child.Text.EndsWith(\"Max\"))\n                    {\n                        prefix = child.Text.Substring(0, child.Text.Length - 3);\n                    }\n                    else\n                    {\n                        prefix = child.Text;\n                    }\n                    \n                    Range range;\n                    try\n                    {\n                        GearPropType propType = (GearPropType)Enum.Parse(typeof(GearPropType), prefix, true);\n                        info.BonusProps.TryGetValue(propType, out range);\n                        if (child.Text.EndsWith(\"Min\"))\n                        {\n                            range.Min = child.GetValue(0);\n                            info.BonusProps[propType] = range;\n                        }\n                        else if (child.Text.EndsWith(\"Max\"))\n                        {\n                            range.Max = child.GetValue(0);\n                            info.BonusProps[propType] = range;\n                        }\n                        else\n                        {\n                            range.Min = range.Max = child.GetValue(0);\n                            info.BonusProps[propType] = range;\n                        }\n                    }\n                    catch\n                    {\n                    }\n                }\n            }\n            return info;\n        }\n\n        public struct Range\n        {\n            public Range(int min, int max)\n            {\n                this.min = min;\n                this.max = max;\n            }\n\n            private int min;\n            private int max;\n            public int Min\n            {\n                get { return min; }\n                set { min = value; }\n            }\n            public int Max\n            {\n                get { return max; }\n                set { max = value; }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/GearPropType.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.Text;\r\n\r\nnamespace WzComparerR2.CharaSim\r\n{\r\n    public enum GearPropType\r\n    {\r\n        //普通装备属性\r\n        incSTR = 1,\r\n        incSTRr,\r\n        incDEX,\r\n        incDEXr,\r\n        incINT,\r\n        incINTr,\r\n        incLUK,\r\n        incLUKr,\r\n        incAllStat,\r\n        incMHP_incMMP,\r\n        incMHPr_incMMPr,\r\n        incMHP,\r\n        incMHPr,\r\n        incMMP,\r\n        incMMPr,\r\n        incMDF,\r\n        incARC,\r\n        incAUT,\r\n        incPAD_incMAD,\r\n        incPAD,\r\n        incMAD,\r\n        incPDD_incMDD,\r\n        incPDD,\r\n        incMDD,\r\n        incACC_incEVA,\r\n        incACC,\r\n        incEVA,\r\n        incSpeed,\r\n        incJump,\r\n        incCraft,\r\n        knockback,\r\n        incPVPDamage,\r\n        bdR,\r\n        incBDR,\r\n        imdR,\r\n        incIMDR,\r\n        damR,\r\n        nbdR,\r\n\r\n        //潜能属性\r\n        incPADr = 100,\r\n        incMADr,\r\n        incPDDr,\r\n        incMDDr,\r\n        incACCr,\r\n        incEVAr,\r\n        incCr,\r\n        incCDr,\r\n        incDAMr,\r\n        RecoveryHP,\r\n        RecoveryMP,\r\n        face,\r\n        prop,\r\n        time,\r\n        HP,\r\n        MP,\r\n        attackType,\r\n        ignoreTargetDEF,\r\n        ignoreDAM,\r\n        ignoreDAMr,\r\n        DAMreflect,\r\n        mpconReduce,\r\n        mpRestore,\r\n        incMesoProp,\r\n        incRewardProp,\r\n        incAllskill,\r\n        RecoveryUP,\r\n        boss,\r\n        level,\r\n        incTerR,\r\n        incAsrR,\r\n        incEXPr,\r\n        reduceCooltime,\r\n        incCriticaldamageMax,\r\n        incCriticaldamageMin,\r\n        @sealed,\r\n        incSTRlv,\r\n        incDEXlv,\r\n        incINTlv,\r\n        incLUKlv,\r\n        incMaxDamage,\r\n        incPADlv,\r\n        incMADlv,\r\n        incCriticaldamage,\r\n\r\n        Option,\r\n        OptionToMob,\r\n        activeSkill,\r\n        bonusByTime,\r\n\r\n        //特殊装备属性\r\n        attackSpeed = 200,\r\n        tuc,\r\n        setItemID,\r\n        durability,\r\n        reqCraft,\r\n        cash,\r\n        royalSpecial,\r\n        masterSpecial,\r\n        reduceReq,\r\n\r\n        //技能特有属性\r\n        mastery = 300,\r\n        criticaldamageMin,\r\n        criticaldamageMax,\r\n        epad,\r\n        emad,\r\n        epdd,\r\n        emdd,\r\n        emhp,\r\n        emmp,\r\n        smartpad,\r\n        smartacc,\r\n        smarteva,\r\n\r\n        //装备特有属性\r\n        reqLevel = 1000,\r\n        reqSTR,\r\n        reqDEX,\r\n        reqINT,\r\n        reqLUK,\r\n        reqJob,\r\n        reqPOP,\r\n        reqSpecJob,\r\n        reqWeekDay, //要求日子\r\n        grade,\r\n\r\n        only = 1100,\r\n        //notSale,\r\n        //dropBlock,\r\n        tradeBlock,\r\n        accountSharable,\r\n        accountSharableAfterExchange,\r\n        onlyEquip,\r\n        tradeAvailable,\r\n        equipTradeBlock,\r\n        sharableOnce,\r\n        notExtend,\r\n        epicItem,\r\n        charismaEXP,\r\n        senseEXP,\r\n        insightEXP,\r\n        willEXP,\r\n        craftEXP,\r\n        charmEXP,\r\n        accountShareTag,\r\n        noPotential,\r\n        fixedPotential,\r\n        timeLimited,\r\n        specialGrade,\r\n        fixedGrade,\r\n        unchangeable,\r\n        superiorEqp,\r\n        incPQEXPr,\r\n        limitBreak,\r\n        nActivatedSocket,\r\n        jokerToSetItem,\r\n        medalTag,\r\n        ringOptionSkill,\r\n        ringOptionSkillLv,\r\n        abilityTimeLimited,\r\n        blockGoldHammer,\r\n        exceptUpgrade,\r\n        colorvar,\r\n        noMoveToLocker,\r\n        onlyUpgrade,\r\n        cantRepair,\r\n        noPetEquipStatMoveItem,\r\n        BTSLabel,\r\n        BLACKPINKLabel,\r\n        android,\r\n        noLookChange,\r\n        tucIgnoreForPotential,\r\n        Etuc,\r\n        CuttableCount,\r\n        //MSN专属属性\r\n        blockUpgradeExtraOption,\r\n        blockUpgradeStarforce,\r\n        mintable,\r\n\r\n        gatherTool_incSkillLevel = 2000,\r\n        gatherTool_incSpeed,\r\n        gatherTool_incNum,\r\n        gatherTool_reqSkillLevel,\r\n    }\r\n}\r\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/GearSealedInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class GearSealedInfo\n    {\n        public GearSealedInfo()\n        {\n            this.BonusProps = new Dictionary<GearPropType, int>();\n        }\n\n        public int Level { get; set; }\n        public Dictionary<GearPropType, int> BonusProps { get; private set; }\n        public int Exp { get; set; }\n        public bool HasIcon { get; set; }\n        public BitmapOrigin Icon { get; set; }\n        public BitmapOrigin IconRaw { get; set; }\n\n        public static GearSealedInfo CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            GearSealedInfo info = new GearSealedInfo();\n\n            foreach (Wz_Node child in node.Nodes)\n            {\n                switch (child.Text)\n                {\n                    case \"exp\":\n                        info.Exp = child.GetValue(0);\n                        break;\n\n                    case \"icon\":\n                        info.Icon = BitmapOrigin.CreateFromNode(child, findNode);\n                        info.HasIcon = true;\n                        break;\n\n                    case \"iconRaw\":\n                        info.IconRaw = BitmapOrigin.CreateFromNode(child, findNode);\n                        info.HasIcon = true;\n                        break;\n\n                    default:\n                        try\n                        {\n                            GearPropType propType = (GearPropType)Enum.Parse(typeof(GearPropType), child.Text, true);\n                            info.BonusProps[propType] = child.GetValue(0);\n                        }\n                        finally\n                        {\n                        }\n                        break;\n                }\n            }\n            return info;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/GearState.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum GearState\n    {\n        itemList = 0,\n        enable = 1,\n        disable = 2\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/GearType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum GearType\n    {\n        body = 0,\n        head = 1,\n        face = 2,\n        hair = 3,\n        hair2 = 4,\n        face2 = 5,\n        hair3 = 6,\n        hair4 = 7,\n\n        /// <summary>\n        /// 脸饰 101\n        /// </summary>\n        faceAccessory = 101,\n        /// <summary>\n        /// 眼饰 102\n        /// </summary>\n        eyeAccessory = 102,\n        /// <summary>\n        /// 耳环 103\n        /// </summary>\n        earrings = 103,\n        /// <summary>\n        /// 坠子 112\n        /// </summary>\n        pendant = 112,\n        /// <summary>\n        /// 腰带 113\n        /// </summary>\n        belt = 113,\n        /// <summary>\n        /// 勋章 114\n        /// </summary>\n        medal = 114,\n        /// <summary>\n        /// 肩饰 115\n        /// </summary>\n        shoulderPad = 115,\n        /// <summary>\n        /// 头盔 100\n        /// </summary>\n        cap = 100,\n        /// <summary>\n        /// 披风 110\n        /// </summary>\n        cape = 110,\n        /// <summary>\n        /// 上衣 104\n        /// </summary>\n        coat = 104,\n        /// <summary>\n        /// 龙神帽子 194\n        /// </summary>\n        dragonMask = 194,\n        /// <summary>\n        /// 龙神吊坠 195\n        /// </summary>\n        dragonPendant = 195,\n        /// <summary>\n        /// 龙神翅膀 196\n        /// </summary>\n        dragonWings = 196,\n        /// <summary>\n        /// 龙神尾巴 197\n        /// </summary>\n        dragonTail = 197,\n        /// <summary>\n        /// 手套 108\n        /// </summary>\n        glove = 108,\n        /// <summary>\n        /// 套服 105\n        /// </summary>\n        longcoat = 105,\n        /// <summary>\n        /// 机甲引擎 161\n        /// </summary>\n        machineEngine = 161,\n        /// <summary>\n        /// 机甲机械臂 162\n        /// </summary>\n        machineArms = 162,\n        /// <summary>\n        /// 机甲机械腿 163\n        /// </summary>\n        machineLegs = 163,\n        /// <summary>\n        /// 机甲机身材质 164\n        /// </summary>\n        machineBody = 164,\n        /// <summary>\n        /// 机甲晶体管 165\n        /// </summary>\n        machineTransistors = 165,\n        /// <summary>\n        /// 安卓 166\n        /// </summary>\n        android = 166,\n        /// <summary>\n        /// 心脏 167\n        /// </summary>\n        machineHeart = 167,\n        /// <summary>\n        /// 口袋物品 116\n        /// </summary>\n        pocket = 116,\n        /// <summary>\n        /// 徽章 118\n        /// </summary>\n        badge = 118,\n        /// <summary>\n        /// 纹章 119\n        /// </summary>\n        emblem = 119,\n        powerSource = 119020,\n        /// <summary>\n        /// 裤/裙 106\n        /// </summary>\n        pants = 106,\n        /// <summary>\n        /// 戒指 111\n        /// </summary>\n        ring = 111,\n        /// <summary>\n        /// 盾牌 109\n        /// </summary>\n        shield = 109,\n        /// <summary>\n        /// 灵魂盾 1098xxx\n        /// </summary>\n        soulShield = 1098,\n        /// <summary>\n        /// 精气盾 1099xxx\n        /// </summary>\n        demonShield = 1099,\n        /// <summary>\n        /// 鞋子 107\n        /// </summary>\n        shoes = 107,\n        /// <summary>\n        /// 双头杖 1212\n        /// </summary>\n        shiningRod = 1212,\n        /// <summary>\n        /// 调谐器 1213\n        /// </summary>\n        tuner = 1213,\n        /// <summary>\n        /// 龙息臂箭 1214\n        /// </summary>\n        breathShooter = 1214,\n        /// <summary>\n        /// 长剑 1215\n        /// </summary>\n        longSword = 1215,\n        /// <summary>\n        /// 灵魂手铳 122\n        /// </summary>\n        soulShooter = 122,\n        /// <summary>\n        /// 亡命剑 123\n        /// </summary>\n        desperado = 123,\n        /// <summary>\n        /// 能量剑 124\n        /// </summary>\n        energySword = 124,\n        /// <summary>\n        /// 记忆长杖 1252\n        /// </summary>\n        memorialStaff = 1252,\n        /// <summary>\n        /// 星灵权杖 1253\n        /// </summary>\n        celestialLight = 1253,\n        /// <summary>\n        /// 阴阳扇 1254\n        /// </summary>\n        onmyoSen = 1254,\n        /// <summary>\n        /// 驯兽魔法棒 1259\n        /// </summary>\n        magicStick = 1259,\n        /// <summary>\n        /// ESP限制器\n        /// </summary>\n        espLimiter = 126,\n        /// <summary>\n        /// 锁链 127\n        /// </summary>\n        chain2 = 127,\n        /// <summary>\n        /// 魔力手套 128\n        /// </summary>\n        magicGauntlet = 128,\n        /// <summary>\n        /// 扇子 129\n        /// </summary>\n        handFan = 129,\n        /// <summary>\n        /// 单手剑 130\n        /// </summary>\n        ohSword = 130,\n        /// <summary>\n        /// 单手斧 131\n        /// </summary>\n        ohAxe = 131,\n        /// <summary>\n        /// 单手钝器 132\n        /// </summary>\n        ohBlunt = 132,\n        /// <summary>\n        /// 短刀 133\n        /// </summary>\n        dagger = 133,\n        /// <summary>\n        /// 刀 134\n        /// </summary>\n        katara = 134,\n        /// <summary>\n        /// 魔法箭矢 135_00\n        /// </summary>\n        magicArrow = 135200,\n        /// <summary>\n        /// 卡片 135_10\n        /// </summary>\n        card = 135210,\n        /// <summary>\n        /// 吊坠 135_20\n        /// </summary>\n        heroMedal = 135220,\n        /// <summary>\n        /// 念珠 135_21\n        /// </summary>\n        rosario = 135221,\n        /// <summary>\n        /// 铁链 135_22\n        /// </summary>\n        chain = 135222,\n        /// <summary>\n        /// 魔导书(火毒) 135_23\n        /// </summary>\n        book1 = 135223,\n        /// <summary>\n        /// 魔导书(冰雷) 135_24\n        /// </summary>\n        book2 = 135224,\n        /// <summary>\n        /// 魔导书(牧师) 135_25\n        /// </summary>\n        book3 = 135225,\n        /// <summary>\n        /// 箭羽 135_26\n        /// </summary>\n        bowMasterFeather = 135226,\n        /// <summary>\n        /// 扳指 135_27\n        /// </summary>\n        crossBowThimble = 135227,\n        /// <summary>\n        /// 短剑剑鞘 135_28\n        /// </summary>\n        shadowerSheath = 135228,\n        /// <summary>\n        /// 护身符 135_29\n        /// </summary>\n        nightLordPoutch = 135229,\n        /// <summary>\n        /// 宝盒 135_30\n        /// </summary>\n        box = 135230,\n        /// <summary>\n        /// 宝珠 135_40\n        /// </summary>\n        orb = 135240,\n        /// <summary>\n        /// 龙之精髓 135_50\n        /// </summary>\n        novaMarrow = 135250,\n        /// <summary>\n        /// 灵魂戒指 135_60\n        /// </summary>\n        soulBangle = 135260,\n        /// <summary>\n        /// 麦林 135_70\n        /// </summary>\n        mailin = 135270,\n        /// <summary>\n        /// 小太刀 135_80\n        /// </summary>\n        katana2 = 135280,\n        /// <summary>\n        /// 哨子 135_81\n        /// </summary>\n        whistle = 135281,\n        /// <summary>\n        /// 拳爪 135_82\n        /// </summary>\n        boxingClaw = 135282,\n        /// <summary>\n        /// 拳天 135_86\n        /// </summary>\n        boxingSky = 135286,\n        /// <summary>\n        /// 罗盘 135_87\n        /// </summary>\n        compass = 135287,\n        /// <summary>\n        /// 手腕护带 135_90\n        /// </summary>\n        viperWristband = 135290,\n        /// <summary>\n        /// 望远镜 135_91\n        /// </summary>\n        captainSight = 135291,\n        /// <summary>\n        /// 火药桶 135_92\n        /// </summary>\n        connonGunPowder = 135292,\n        /// <summary>\n        /// 砝码 135_93\n        /// </summary>\n        aranPendulum = 135293,\n        /// <summary>\n        /// 文件 135_94\n        /// </summary>\n        evanPaper = 135294,\n        /// <summary>\n        /// 魔法球 135_95\n        /// </summary>\n        battlemageBall = 135295,\n        /// <summary>\n        /// 箭轴 135_96\n        /// </summary>\n        wildHunterArrowHead = 135296,\n        /// <summary>\n        /// 珠宝 135_97\n        /// </summary>\n        cygnusGem = 135297,\n        /// <summary>\n        /// 火药桶 135_98\n        /// </summary>\n        connonGunPowder2 = 135298,\n        /// <summary>\n        /// 控制器 135300\n        /// </summary>\n        controller = 135300,\n        /// <summary>\n        /// 狐狸珠 135310\n        /// </summary>\n        foxPearl = 135310,\n        /// <summary>\n        /// 棋子 135320\n        /// </summary>\n        chess = 135320,\n        /// <summary>\n        /// 武器传送装置 135330\n        /// </summary>\n        transmitter = 135330,\n        /// <summary>\n        /// 装弹 135340\n        /// </summary>\n        ExplosivePill = 135340,\n        /// <summary>\n        /// 魔力翅膀 135350\n        /// </summary>\n        magicWing = 135350,\n        /// <summary>\n        /// 精气珠 135360\n        /// </summary>\n        pathOfAbyss = 135360,\n        /// <summary>\n        /// 遗物 135370x\n        /// </summary>\n        relic = 135370,\n        /// <summary>\n        /// 扇坠 135380x\n        /// </summary>\n        fanTassel = 135380,\n        /// <summary>\n        /// 手链 135400x\n        /// </summary>\n        bracelet = 135400,\n        /// <summary>\n        /// 武器腰带 135401x\n        /// </summary>\n        weaponBelt = 135401,\n        /// <summary>\n        /// 饰品 135402x\n        /// </summary>\n        ornament = 135402,\n        /// <summary>\n        /// 索魂器 135403x\n        /// </summary>\n        hexSeeker = 135403,\n        /// <summary>\n        /// 如意宝珠 135404\n        /// </summary>\n        yeouiGem = 135404,\n        /// <summary>\n        /// 灵符 135430\n        /// </summary>\n        kannaReifu = 135430,\n        /// <summary>\n        /// 手杖\n        /// </summary>\n        cane = 136,\n        /// <summary>\n        /// 短杖 137\n        /// </summary>\n        wand = 137,\n        /// <summary>\n        /// 长杖 138\n        /// </summary>\n        staff = 138,\n        /// <summary>\n        /// 空手 139\n        /// </summary>\n        barehand = 139,\n        /// <summary>\n        /// 双手剑 140\n        /// </summary>\n        thSword = 140,\n        /// <summary>\n        /// 拳封 140_3xxx\n        /// </summary>\n        boxingCannon = 1403,\n        /// <summary>\n        /// 环刃 140_4xxx\n        /// </summary>\n        chakram = 1404,\n        /// <summary>\n        /// 双手斧 141\n        /// </summary>\n        thAxe = 141,\n        /// <summary>\n        /// 双手钝器 142\n        /// </summary>\n        thBlunt = 142,\n        /// <summary>\n        /// 枪 143\n        /// </summary>\n        spear = 143,\n        /// <summary>\n        /// 矛 144\n        /// </summary>\n        polearm = 144,\n        /// <summary>\n        /// 弓 145\n        /// </summary>\n        bow = 145,\n        /// <summary>\n        /// 弩 146\n        /// </summary>\n        crossbow = 146,\n        /// <summary>\n        /// 拳套 147\n        /// </summary>\n        throwingGlove = 147,\n        /// <summary>\n        /// 指节 148\n        /// </summary>\n        knuckle = 148,\n        /// <summary>\n        /// 短枪 149\n        /// </summary>\n        gun = 149,\n        /// <summary>\n        /// 采药工具 150\n        /// </summary>\n        shovel = 150,\n        /// <summary>\n        /// 采矿工具 151\n        /// </summary>\n        pickaxe = 151,\n        /// <summary>\n        /// 双弓 152\n        /// </summary>\n        dualBow = 152,\n        /// <summary>\n        /// 手持火炮 153\n        /// </summary>\n        handCannon = 153,\n        /// <summary>\n        /// 太刀 154\n        /// </summary>\n        katana = 154,\n        /// <summary>\n        /// 扇 155\n        /// </summary>\n        fan = 155,\n\n        /// <summary>\n        /// 大剑 156\n        /// </summary>\n        swordZB = 156,\n        /// <summary>\n        /// 太刀 157\n        /// </summary>\n        swordZL = 157,\n        /// <summary>\n        /// 机甲手枪 158\n        /// </summary>\n        GauntletBuster = 158,\n        /// <summary>\n        /// 远古弓 159\n        /// </summary>\n        ancientBow = 159,\n        /// <summary>\n        /// Astra 172\n        /// </summary>\n        astra = 172,\n        /// <summary>\n        /// 拼图 168\n        /// </summary>\n        bit = 168,\n        /// <summary>\n        /// 点装武器 170\n        /// </summary>\n        cashWeapon = 170,\n        /// <summary>\n        /// 武器 -1\n        /// </summary>\n        weapon = -1,\n        /// <summary>\n        /// 武器 -1\n        /// </summary>\n        subWeapon = -2,\n        /// <summary>\n        /// 图腾 120\n        /// </summary>\n        totem = 120,\n        /// <summary>\n        /// 珠宝 178\n        /// </summary>\n        jewel = 178,\n        /// <summary>\n        /// MSN纸娃娃 179\n        /// </summary>\n        face_n = 1790,\n        hair_n = 1791,\n        head_n = 1792,\n        hair2_n = 1793,\n        /// <summary>\n        /// 宠物装备 180\n        /// </summary>\n        petEquip = 180,\n        /// <summary>\n        /// 骑兽 190\n        /// </summary>\n        taming = 190,\n        /// <summary>\n        /// 鞍子 191\n        /// </summary>\n        saddle = 191,\n        /// <summary>\n        /// 骑兽 193\n        /// </summary>\n        taming2 = 193,\n         /// <summary>\n        /// 椅子用骑兽 198\n        /// </summary>\n        tamingChair = 198,\n        /// <summary>\n        /// 骑兽 199\n        /// </summary>\n        taming3 = 199\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/HyperSkillType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum HyperSkillType\n    {\n        None = 0,\n        S = 1,\n        P = 2,\n        A = 3\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Item.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Item : ItemBase\n    {\n        public Item()\n        {\n            this.Props = new Dictionary<ItemPropType, long>();\n            this.Specs = new Dictionary<ItemSpecType, long>();\n            this.Recipes = new List<int>();\n        }\n\n        public int Level { get; set; }\n        public int? DamageSkinID { get; set; }\n        public int? FamiliarID { get; set; }\n        public int Grade { get; set; }\n\n        public Dictionary<ItemPropType, long> Props { get; private set; }\n        public Dictionary<ItemSpecType, long> Specs { get; private set; }\n        public List<int> Recipes { get; private set; }\n\n        public bool Cash\n        {\n            get { return GetBooleanValue(ItemPropType.cash); }\n        }\n\n        public bool TimeLimited\n        {\n            get { return GetBooleanValue(ItemPropType.timeLimited); }\n        }\n\n        public bool GetBooleanValue(ItemPropType type)\n        {\n            return this.Props.TryGetValue(type, out long value) && value != 0;\n        }\n\n        public static Item CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            Item item = new Item();\n            int value;\n            if (node == null\n                || !Int32.TryParse(node.Text, out value)\n                && !((value = node.Text.IndexOf(\".img\")) > -1 && Int32.TryParse(node.Text.Substring(0, value), out value)))\n            {\n                return null;\n            }\n            item.ItemID = value;\n\n            // in msn the node could be UOL.\n            if (node.Value is Wz_Uol)\n            {\n                if ((node = node.ResolveUol()) == null)\n                {\n                    return item;\n                }\n            }\n\n            Wz_Node infoNode = node.FindNodeByPath(\"info\");\n            if (infoNode != null)\n            {\n                foreach (Wz_Node subNode in infoNode.Nodes)\n                {\n                    switch (subNode.Text)\n                    {\n                        case \"icon\":\n                            if (subNode.Value is Wz_Uol || subNode.Value is Wz_Png)\n                            {\n                                item.Icon = BitmapOrigin.CreateFromNode(subNode, findNode);\n                            }\n                            break;\n\n                        case \"iconRaw\":\n                            if (subNode.Value is Wz_Uol || subNode.Value is Wz_Png)\n                            {\n                                item.IconRaw = BitmapOrigin.CreateFromNode(subNode, findNode);\n                            }\n                            break;\n\n                        case \"sample\":\n                            if (subNode.Value is Wz_Uol || subNode.Value is Wz_Png)\n                            {\n                                item.Sample = BitmapOrigin.CreateFromNode(subNode, findNode);\n                            }\n                            break;\n\n                        case \"lv\":\n                            item.Level = Convert.ToInt32(subNode.Value);\n                            break;\n\n                        case \"damageSkinID\":\n                            item.DamageSkinID = Convert.ToInt32(subNode.Value);\n                            break;\n\n                        case \"familiarID\":\n                            item.FamiliarID = Convert.ToInt32(subNode.Value);\n                            break;\n\n                        case \"grade\":\n                            if (int.TryParse(Convert.ToString(subNode.Value), out _))\n                            {\n                                item.Grade = Convert.ToInt32(subNode.Value);\n                            }\n                            else\n                            {\n                                switch (Convert.ToString(subNode.Value))\n                                {\n                                    default:\n                                    case \"normal\":\n                                        item.Grade = 0;\n                                        break;\n                                    case \"rare\":\n                                        item.Grade = 1;\n                                        break;\n                                    case \"epic\":\n                                        item.Grade = 2;\n                                        break;\n                                    case \"unique\":\n                                        item.Grade = 3;\n                                        break;\n                                    case \"legendary\":\n                                        item.Grade = 4;\n                                        break;\n                                }\n                            }\n                            break;\n\n                        default:\n                            if (!int.TryParse(subNode.Text, out _) && Enum.TryParse(subNode.Text, out ItemPropType type))\n                            {\n                                try\n                                {\n                                    item.Props.Add(type, Convert.ToInt64(subNode.Value));\n                                }\n                                finally\n                                {\n                                }\n                            }\n                            break;\n                    }\n                }\n            }\n\n            Wz_Node specNode = node.FindNodeByPath(\"spec\");\n            if (specNode != null)\n            {\n                foreach (Wz_Node subNode in specNode.Nodes)\n                {\n                    if (subNode.Text == \"recipe\")\n                    {\n                        if (subNode.Value == null && subNode.Nodes.Count > 0)\n                        {\n                            foreach (var recipeNode in subNode.Nodes)\n                            {\n                                item.Recipes.Add(recipeNode.GetValue<int>());\n                            }\n                        }\n                        else\n                        {\n                            item.Recipes.Add(subNode.GetValue<int>());\n                        }\n                    }\n                    else if(Enum.TryParse(subNode.Text, out ItemSpecType type))\n                    {\n                        try\n                        {\n                            item.Specs.Add(type, Convert.ToInt64(subNode.Value));\n                        }\n                        finally\n                        {\n                        }\n                    }\n                }\n            }\n            return item;\n        }\n\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/ItemBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public abstract class ItemBase : ICloneable\n    {\n        public ItemBase()\n        {\n        }\n\n        public int ItemID { get; set; }\n        public BitmapOrigin Icon { get; set; }\n        public BitmapOrigin IconRaw { get; set; }\n        public BitmapOrigin Sample { get; set; }\n\n        public virtual ItemBaseType Type\n        {\n            get { return (ItemBaseType)(this.ItemID / 1000000); }\n        }\n\n        public virtual object Clone()\n        {\n            return this.MemberwiseClone();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/ItemBaseType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum ItemBaseType\n    {\n        Unknown = 0,\n        Equip = 1,\n        Consume = 2,\n        Install = 3,\n        Etc = 4,\n        Cash = 5\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/ItemPropType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum ItemPropType\n    {\n        price = 1,\n        lv,\n        reqLevel,\n        cash,\n        quest,\n        pquest,\n        tradeBlock,\n        notSale,\n        only,\n        tradeAvailable,\n        accountSharable,\n        accountSharableAfterExchange,\n        timeLimited,\n        setItemID,\n        nickTag,\n        wonderGrade,\n        life,\n        permanent,\n        //MSN专属属性\n        mintable,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/ItemSpecType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum ItemSpecType\n    {\n        recipe = 1,\n        reqSkill,\n        reqSkillLevel,\n        reqSkillProficiency,\n        recipeValidDay,\n        recipeUseCount,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/ItemStringHelper.cs",
    "content": "﻿using System;\r\nusing System.Collections.Generic;\r\nusing System.Text;\r\n\r\nnamespace WzComparerR2.CharaSim\r\n{\r\n    public static class ItemStringHelper\r\n    {\r\n        /// <summary>\r\n        /// 获取怪物category属性对应的类型说明。\r\n        /// </summary>\r\n        /// <param Name=\"category\">怪物的category属性的值。</param>\r\n        /// <returns></returns>\r\n        public static string GetMobCategoryName(int category)\r\n        {\r\n            switch (category)\r\n            {\r\n                case 0: return \"无形态\";\r\n                case 1: return \"动物型\";\r\n                case 2: return \"植物型\";\r\n                case 3: return \"鱼类型\";\r\n                case 4: return \"爬虫类型\";\r\n                case 5: return \"精灵型\";\r\n                case 6: return \"恶魔型\";\r\n                case 7: return \"不死型\";\r\n                case 8: return \"无机物型\";\r\n                default: return null;\r\n            }\r\n        }\r\n\r\n        public static string GetGearPropString(GearPropType propType, long value)\r\n        {\r\n            return GetGearPropString(propType, value, 0);\r\n        }\r\n\r\n        /// <summary>\r\n        /// 获取GearPropType所对应的文字说明。\r\n        /// </summary>\r\n        /// <param Name=\"propType\">表示装备属性枚举GearPropType。</param>\r\n        /// <param Name=\"Value\">表示propType属性所对应的值。</param>\r\n        /// <returns></returns>\r\n        public static string GetGearPropString(GearPropType propType, long value, int signFlag)\r\n        {\r\n\r\n            string sign;\r\n            switch (signFlag)\r\n            {\r\n                default:\r\n                case 0: //默认处理符号\r\n                    sign = value > 0 ? \"+\" : null;\r\n                    break;\r\n\r\n                case 1: //固定加号\r\n                    sign = \"+\";\r\n                    break;\r\n\r\n                case 2: //无特别符号\r\n                    sign = \"\";\r\n                    break;\r\n            }\r\n            switch (propType)\r\n            {\r\n                case GearPropType.incSTR: return \"力量 : \" + sign + value;\r\n                case GearPropType.incSTRr: return \"力量 : \" + sign + value + \"%\";\r\n                case GearPropType.incDEX: return \"敏捷 : \" + sign + value;\r\n                case GearPropType.incDEXr: return \"敏捷 : \" + sign + value + \"%\";\r\n                case GearPropType.incINT: return \"智力 : \" + sign + value;\r\n                case GearPropType.incINTr: return \"智力 : \" + sign + value + \"%\";\r\n                case GearPropType.incLUK: return \"运气 : \" + sign + value;\r\n                case GearPropType.incLUKr: return \"运气 : \" + sign + value + \"%\";\r\n                case GearPropType.incAllStat: return \"所有属性 : \" + sign + value;\r\n                case GearPropType.incMHP: return \"最大血量： \" + sign + value;\r\n                case GearPropType.incMHPr: return \"最大血量： \" + sign + value + \"%\";\r\n                case GearPropType.incMMP: return \"最大魔量： \" + sign + value;\r\n                case GearPropType.incMMPr: return \"最大魔量： \" + sign + value + \"%\";\r\n                case GearPropType.incMDF: return \"MaxDF : \" + sign + value;\r\n                case GearPropType.incPAD: return \"攻击力 : \" + sign + value;\r\n                case GearPropType.incPADr: return \"攻击力 : \" + sign + value + \"%\";\r\n                case GearPropType.incMAD: return \"魔法力 : \" + sign + value;\r\n                case GearPropType.incMADr: return \"魔法力 : \" + sign + value + \"%\";\r\n                case GearPropType.incPDD: return \"防御力 : \" + sign + value;\r\n                case GearPropType.incPDDr: return \"物理防御力 : \" + sign + value + \"%\";\r\n                case GearPropType.incMDD: return \"魔法防御力 : \" + sign + value;\r\n                case GearPropType.incMDDr: return \"魔法防御力 : \" + sign + value + \"%\";\r\n                case GearPropType.incACC: return \"命中值 : \" + sign + value;\r\n                case GearPropType.incACCr: return \"命中值 : \" + sign + value + \"%\";\r\n                case GearPropType.incEVA: return \"回避值 : \" + sign + value;\r\n                case GearPropType.incEVAr: return \"回避值 : \" + sign + value + \"%\";\r\n                case GearPropType.incSpeed: return \"移动速度 : \" + sign + value;\r\n                case GearPropType.incJump: return \"跳跃力 : \" + sign + value;\r\n                case GearPropType.incCraft: return \"手技 : \" + sign + value;\r\n                case GearPropType.damR:\r\n                case GearPropType.incDAMr: return \"总伤害 : \" + sign + value + \"%\";\r\n                case GearPropType.incCr: return \"爆击率 : \" + sign + value + \"%\";\r\n                case GearPropType.incCDr: return \"爆击伤害 : \" + sign + value + \"%\";\r\n                case GearPropType.knockback: return \"直接攻击时\" + value + \"的比率发生后退现象。\";\r\n                case GearPropType.incPVPDamage: return \"大乱斗时追加攻击力\" + sign + value;\r\n                case GearPropType.incPQEXPr: return \"组队任务经验值增加\" + value + \"%\";\r\n                case GearPropType.incEXPr: return \"经验值增加\" + value + \"%\";\r\n                case GearPropType.incBDR:\r\n                case GearPropType.bdR: return \"攻击首领怪时，伤害+\" + value + \"%\";\r\n                case GearPropType.incIMDR:\r\n                case GearPropType.imdR: return \"无视怪物防御率：+\" + value + \"%\";\r\n                case GearPropType.limitBreak:return \"伤害上限突破至\" + ToChineseNumberExpr(value) + \"。\";\r\n                case GearPropType.reduceReq: return \"装备等级降低：- \" + value;\r\n                case GearPropType.nbdR: return \"攻击普通怪物时，伤害+\" + value + \"%\";\r\n\r\n                case GearPropType.only: return value == 0 ? null : \"固有道具\";\r\n                case GearPropType.tradeBlock: return value == 0 ? null : \"不可交换\";\r\n                case GearPropType.equipTradeBlock: return value == 0 ? null : \"装备后无法交换\";\r\n                case GearPropType.accountSharable: return value == 0 ? null : \"服务器内只有我的角色之间可以移动\";\r\n                case GearPropType.onlyEquip: return value == 0 ? null : \"固有装备物品\";\r\n                case GearPropType.notExtend: return value == 0 ? null : \"无法延长有效时间。\";\r\n                case GearPropType.accountSharableAfterExchange: return value == 0 ? null : \"可交换1次\\n（交易后只能在世界内我的角色之间移动）\";\r\n                case GearPropType.mintable: return value == 0 ? null : \"可铸造\";\r\n                case GearPropType.tradeAvailable:\r\n                    switch (value)\r\n                    {\r\n                        case 1: return \" #c使用宿命剪刀，可以使物品交易1次。#\";\r\n                        case 2: return \" #c使用白金宿命剪刀，可以使物品交易1次。#\";\r\n                        default: return null;\r\n                    }\r\n                case GearPropType.accountShareTag:\r\n                    switch (value)\r\n                    {\r\n                        case 1: return \" #c使用物品共享牌，可以在同一账号内的角色间移动1次。#\";\r\n                        default: return null;\r\n                    }\r\n                case GearPropType.noPotential: return value == 0 ? null : \"无法设置潜能。\";\r\n                case GearPropType.fixedPotential: return value == 0 ? null : \"无法重设潜能\";\r\n                case GearPropType.superiorEqp: return value == 0 ? null : \"道具强化成功时，可以获得更高的效果。\";\r\n                case GearPropType.nActivatedSocket: return value == 0 ? null : \"#c可以镶嵌星岩#\";\r\n                case GearPropType.jokerToSetItem: return value == 0 ? null : \" #c当前装备3个以上的所有套装道具中包含的幸运物品！#\";\r\n                case GearPropType.abilityTimeLimited: return value == 0 ? null : \"限期能力值\";\r\n                case GearPropType.blockGoldHammer: return value == 0 ? null : \"无法使用黄金锤\";\r\n                case GearPropType.colorvar: return value == 0 ? null : \"#c该装备可通过染色颜料来变更颜色.#\";\r\n\r\n                case GearPropType.incMHP_incMMP: return \"最大血量/最大魔量：\" + sign + value;\r\n                case GearPropType.incMHPr_incMMPr: return \"最大血量/最大魔量：\" + sign + value + \"%\";\r\n                case GearPropType.incPAD_incMAD: return \"攻击力/魔力：\" + sign + value;\r\n                case GearPropType.incPDD_incMDD: return \"物理/魔法防御力：\" + sign + value;\r\n                case GearPropType.incACC_incEVA: return \"命中值/回避值：\" + sign + value;\r\n\r\n                case GearPropType.incARC: return \"神秘之力 : \" + sign + value;\r\n                case GearPropType.incAUT: return \"原初之力 : \" + sign + value;\r\n\r\n                case GearPropType.Etuc: return \"可进行卓越强化。（最多：\" + value + \"次）\";\r\n                case GearPropType.CuttableCount: return \"可使用剪刀：\" + value + \"次\";\r\n                default: return null;\r\n            }\r\n        }\r\n\r\n\r\n        public static string GetGearPropDiffString(GearPropType propType, int value, int standardValue)\r\n        {\r\n            var propStr = GetGearPropString(propType, value);\r\n            if (value > standardValue)\r\n            {\r\n                string suffix = null;\r\n                switch (propType)\r\n                {\r\n                    case GearPropType.incSTR:\r\n                    case GearPropType.incDEX:\r\n                    case GearPropType.incINT:\r\n                    case GearPropType.incLUK:\r\n                    case GearPropType.incMHP:\r\n                    case GearPropType.incMMP:\r\n                    case GearPropType.incMDF:\r\n                    case GearPropType.incARC:\r\n                    case GearPropType.incAUT:\r\n                    case GearPropType.incPAD:\r\n                    case GearPropType.incMAD:\r\n                    case GearPropType.incPDD:\r\n                    case GearPropType.incMDD:\r\n                    case GearPropType.incSpeed:\r\n                    case GearPropType.incJump:\r\n                        suffix = $\"({standardValue} #$e+{value - standardValue}#)\"; break;\r\n                    case GearPropType.bdR:\r\n                    case GearPropType.incBDR:\r\n                    case GearPropType.imdR:\r\n                    case GearPropType.incIMDR:\r\n                    case GearPropType.damR:\r\n                    case GearPropType.incDAMr:\r\n                        suffix = $\"({standardValue}% #$y+{value - standardValue}%#)\"; break;\r\n                }\r\n                propStr = \"#$y\" + propStr + \"# \" + suffix;\r\n            }\r\n            return propStr;\r\n        }\r\n\r\n        /// <summary>\r\n        /// 获取gearGrade所对应的字符串。\r\n        /// </summary>\r\n        /// <param Name=\"rank\">表示装备的潜能等级GearGrade。</param>\r\n        /// <returns></returns>\r\n        public static string GetGearGradeString(GearGrade rank)\r\n        {\r\n            switch (rank)\r\n            {\r\n                case GearGrade.C: return \"C级(一般物品)\";\r\n                case GearGrade.B: return \"B级(高级物品)\";\r\n                case GearGrade.A: return \"A级(史诗物品)\";\r\n                case GearGrade.S: return \"S级(传说物品)\";\r\n                case GearGrade.SS: return \"SS级(传说极品)\";\r\n                case GearGrade.Special: return \"(特殊物品)\";\r\n                default: return null;\r\n            }\r\n        }\r\n\r\n        /// <summary>\r\n        /// 获取gearType所对应的字符串。\r\n        /// </summary>\r\n        /// <param Name=\"Type\">表示装备类型GearType。</param>\r\n        /// <returns></returns>\r\n        public static string GetGearTypeString(GearType type)\r\n        {\r\n            switch (type)\r\n            {\r\n                case GearType.body: return \"纸娃娃(身体)\";\r\n                case GearType.head:\r\n                case GearType.head_n: return \"纸娃娃(头部)\";\r\n                case GearType.face:\r\n                case GearType.face2:\r\n                case GearType.face_n: return \"纸娃娃(脸型)\";\r\n                case GearType.hair:\r\n                case GearType.hair2:\r\n                case GearType.hair3:\r\n                case GearType.hair4:\r\n                case GearType.hair_n:\r\n                case GearType.hair2_n: return \"纸娃娃(发型)\";\r\n                case GearType.faceAccessory: return \"脸饰\";\r\n                case GearType.eyeAccessory: return \"眼饰\";\r\n                case GearType.earrings: return \"耳环\";\r\n                case GearType.pendant: return \"坠子\";\r\n                case GearType.belt: return \"腰带\";\r\n                case GearType.medal: return \"勋章\";\r\n                case GearType.shoulderPad: return \"肩饰\";\r\n                case GearType.cap: return \"帽子\";\r\n                case GearType.cape: return \"披风\";\r\n                case GearType.coat: return \"上衣\";\r\n                case GearType.dragonMask: return \"龙神帽子\";\r\n                case GearType.dragonPendant: return \"龙神吊坠\";\r\n                case GearType.dragonWings: return \"龙神翅膀\";\r\n                case GearType.dragonTail: return \"龙神尾巴\";\r\n                case GearType.glove: return \"手套\";\r\n                case GearType.longcoat: return \"套服\";\r\n                case GearType.machineEngine: return \"机甲引擎\";\r\n                case GearType.machineArms: return \"机甲机械臂\";\r\n                case GearType.machineLegs: return \"机甲机械腿\";\r\n                case GearType.machineBody: return \"机甲机身材质\";\r\n                case GearType.machineTransistors: return \"机甲晶体管\";\r\n                case GearType.pants: return \"裤/裙\";\r\n                case GearType.ring: return \"戒指\";\r\n                case GearType.shield: return \"盾牌\";\r\n                case GearType.shoes: return \"鞋子\";\r\n                case GearType.shiningRod: return \"双头杖\";\r\n                case GearType.soulShooter: return \"灵魂手铳\";\r\n                case GearType.ohSword: return \"单手剑\";\r\n                case GearType.ohAxe: return \"单手斧\";\r\n                case GearType.ohBlunt: return \"单手钝器\";\r\n                case GearType.dagger: return \"短刀\";\r\n                case GearType.katara: return \"刀\";\r\n                case GearType.magicArrow: return \"魔法箭矢\";\r\n                case GearType.card: return \"卡片\";\r\n                case GearType.box: return \"宝盒\";\r\n                case GearType.orb: return \"宝珠\";\r\n                case GearType.novaMarrow: return \"龙之精髓\";\r\n                case GearType.soulBangle: return \"灵魂手镯\";\r\n                case GearType.mailin: return \"麦林\";\r\n                case GearType.cane: return \"手杖\";\r\n                case GearType.wand: return \"短杖\";\r\n                case GearType.staff: return \"长杖\";\r\n                case GearType.thSword: return \"双手剑\";\r\n                case GearType.thAxe: return \"双手斧\";\r\n                case GearType.thBlunt: return \"双手钝器\";\r\n                case GearType.spear: return \"枪\";\r\n                case GearType.polearm: return \"矛\";\r\n                case GearType.bow: return \"弓\";\r\n                case GearType.crossbow: return \"弩\";\r\n                case GearType.throwingGlove: return \"拳套\";\r\n                case GearType.knuckle: return \"指节\";\r\n                case GearType.gun: return \"短枪\";\r\n                case GearType.android: return \"智能机器人\";\r\n                case GearType.machineHeart: return \"机械心脏\";\r\n                case GearType.pickaxe: return \"采矿工具\";\r\n                case GearType.shovel: return \"采药工具\";\r\n                case GearType.pocket: return \"口袋物品\";\r\n                case GearType.dualBow: return \"双弩枪\";\r\n                case GearType.handCannon: return \"手持火炮\";\r\n                case GearType.badge: return \"徽章\";\r\n                case GearType.emblem: return \"纹章\";\r\n                case GearType.soulShield: return \"灵魂盾\";\r\n                case GearType.demonShield: return \"精气盾\";\r\n                case GearType.totem: return \"图腾\";\r\n                case GearType.petEquip: return \"宠物装备\";\r\n                case GearType.taming:\r\n                case GearType.taming2:\r\n                case GearType.taming3: \r\n                case GearType.tamingChair: return \"骑兽\";\r\n                case GearType.saddle: return \"鞍子\";\r\n                case GearType.katana: return \"武士刀\";\r\n                case GearType.fan: return \"折扇\";\r\n                case GearType.swordZB: return \"大剑\";\r\n                case GearType.swordZL: return \"太刀\";\r\n                case GearType.weapon: return \"武器\";\r\n                case GearType.subWeapon: return \"辅助武器\";\r\n                case GearType.heroMedal: return \"吊坠\";\r\n                case GearType.rosario: return \"念珠\";\r\n                case GearType.chain: return \"铁链\";\r\n                case GearType.book1:\r\n                case GearType.book2:\r\n                case GearType.book3: return \"魔导书\";\r\n                case GearType.bowMasterFeather: return \"箭羽\";\r\n                case GearType.crossBowThimble: return \"扳指\";\r\n                case GearType.shadowerSheath: return \"短剑剑鞘\";\r\n                case GearType.nightLordPoutch: return \"护身符\";\r\n                case GearType.viperWristband: return \"手腕护带\";\r\n                case GearType.captainSight: return \"瞄准器\";\r\n                case GearType.connonGunPowder: \r\n                case GearType.connonGunPowder2: return \"火药桶\";\r\n                case GearType.aranPendulum: return \"砝码\";\r\n                case GearType.evanPaper: return \"文件\";\r\n                case GearType.battlemageBall: return \"魔法球\";\r\n                case GearType.wildHunterArrowHead: return \"箭轴\";\r\n                case GearType.cygnusGem: return \"宝石\";\r\n                case GearType.controller: return \"控制器\";\r\n                case GearType.foxPearl: return \"狐狸珠\";\r\n                case GearType.chess: return \"棋子\";\r\n                case GearType.powerSource: return \"能源\";\r\n\r\n                case GearType.energySword: return \"能量剑\";\r\n                case GearType.desperado: return \"亡命剑\";\r\n                case GearType.memorialStaff: return \"记忆长杖\";\r\n                case GearType.magicStick: return \"驯兽魔法棒\";\r\n                case GearType.whistle: return \"飞越\";\r\n                case GearType.boxingClaw: return \"拳爪\";\r\n                case GearType.katana2: return \"小太刀\";\r\n                case GearType.espLimiter: return \"ESP限制器\";\r\n\r\n                case GearType.GauntletBuster: return \"机甲手枪\";\r\n                case GearType.ExplosivePill: return \"装弹\";\r\n\r\n                case GearType.chain2: return \"锁链\";\r\n                case GearType.magicGauntlet: return \"魔力手套\";\r\n                case GearType.transmitter: return \"武器传送装置\";\r\n                case GearType.magicWing: return \"魔法之翼\";\r\n                case GearType.pathOfAbyss: return \"深渊精气珠\";\r\n\r\n                case GearType.relic: return \"遗物\";\r\n                case GearType.ancientBow: return \"远古弓\";\r\n\r\n                case GearType.handFan: return \"扇子\";\r\n                case GearType.fanTassel: return \"扇坠\";\r\n\r\n                case GearType.tuner: return \"调谐器\";\r\n                case GearType.bracelet: return \"手链\";\r\n\r\n                case GearType.boxingCannon: return \"拳封\";\r\n                case GearType.boxingSky: return \"拳天\";\r\n\r\n                case GearType.breathShooter: return \"龙息臂箭\";\r\n                case GearType.weaponBelt: return \"武器腰带\";\r\n\r\n                case GearType.ornament: return \"饰品\";\r\n\r\n                case GearType.chakram: return \"环刃\";\r\n                case GearType.hexSeeker: return \"索魂器\";\r\n\r\n                case GearType.jewel: return \"珠宝\";\r\n\r\n                case GearType.celestialLight: return \"星光权杖\";\r\n                case GearType.compass: return \"罗盘\";\r\n\r\n                case GearType.longSword: return \"长剑\";\r\n                case GearType.yeouiGem: return \"如意宝珠\";\r\n\r\n                case GearType.onmyoSen: return \"阴阳扇\";\r\n                case GearType.kannaReifu: return \"灵符\";\r\n\r\n                default: return null;\r\n            }\r\n        }\r\n\r\n        /// <summary>\r\n        /// 获取武器攻击速度所对应的字符串。\r\n        /// </summary>\r\n        /// <param Name=\"attackSpeed\">表示武器的攻击速度，通常为2~9的数字。</param>\r\n        /// <returns></returns>\r\n        public static string GetAttackSpeedString(int attackSpeed)\r\n        {\r\n            switch (attackSpeed)\r\n            {\r\n                case 2:\r\n                case 3: return \"极快\";\r\n                case 4:\r\n                case 5: return \"快\";\r\n                case 6: return \"普通\";\r\n                case 7:\r\n                case 8: return \"缓慢\";\r\n                case 9: return \"较慢\";\r\n                default:\r\n                    return attackSpeed.ToString();\r\n            }\r\n        }\r\n\r\n        /// <summary>\r\n        /// 获取套装装备类型的字符串。\r\n        /// </summary>\r\n        /// <param Name=\"Type\">表示套装装备类型的GearType。</param>\r\n        /// <returns></returns>\r\n        public static string GetSetItemGearTypeString(GearType type)\r\n        {\r\n            return GetGearTypeString(type);\r\n        }\r\n\r\n        /// <summary>\r\n        /// 获取装备额外职业要求说明的字符串。\r\n        /// </summary>\r\n        /// <param Name=\"Type\">表示装备类型的GearType。</param>\r\n        /// <returns></returns>\r\n        public static string GetExtraJobReqString(GearType type)\r\n        {\r\n            switch (type)\r\n            {\r\n                //0xxx\r\n                case GearType.heroMedal: return \"英雄职业群可穿戴装备\";\r\n                case GearType.rosario: return \"圣骑士职业群可穿戴装备\";\r\n                case GearType.chain: return \"黑骑士职业群可穿戴装备\";\r\n                case GearType.book1: return \"火毒系列魔法师可穿戴装备\";\r\n                case GearType.book2: return \"冰雷系列魔法师可穿戴装备\";\r\n                case GearType.book3: return \"主教系列魔法师可穿戴装备\";\r\n                case GearType.bowMasterFeather: return \"神射手职业群可穿戴装备\";\r\n                case GearType.crossBowThimble: return \"箭神职业群可穿戴装备\";\r\n                case GearType.shadowerSheath: return \"侠盗职业群可穿戴装备\";\r\n                case GearType.nightLordPoutch: return \"隐士职业群可穿戴装备\";\r\n                case GearType.katara: return \"暗影双刀可穿戴装备\";\r\n                case GearType.viperWristband: return \"冲锋队长职业群可穿戴装备\";\r\n                case GearType.captainSight: return \"船长职业群可穿戴装备\";\r\n                case GearType.connonGunPowder: \r\n                case GearType.connonGunPowder2: return \"火炮手职业群可穿戴装备\";\r\n                case GearType.box:\r\n                case GearType.boxingClaw: return \"龙的传人可穿戴装备\";\r\n                case GearType.relic: return \"古迹猎人职业群可穿戴装备\";\r\n\r\n                //1xxx\r\n                case GearType.cygnusGem: return \"冒险骑士团可穿戴装备\";\r\n\r\n                //2xxx\r\n                case GearType.aranPendulum: return GetExtraJobReqString(21);\r\n                case GearType.evanPaper: return GetExtraJobReqString(22);\r\n                case GearType.magicArrow: return GetExtraJobReqString(23);\r\n                case GearType.card: return GetExtraJobReqString(24);\r\n                case GearType.foxPearl: return GetExtraJobReqString(25);\r\n                case GearType.orb:\r\n                case GearType.shiningRod: return GetExtraJobReqString(27);\r\n\r\n                //3xxx\r\n                case GearType.demonShield: return GetExtraJobReqString(31);\r\n                case GearType.desperado: return \"恶魔复仇者可穿戴装备\";\r\n                case GearType.battlemageBall: return \"唤灵斗师职业群可穿戴装备\";\r\n                case GearType.wildHunterArrowHead: return \"豹弩游侠职业群可穿戴装备\";\r\n                case GearType.mailin: return \"机械师可穿戴装备\";\r\n                case GearType.controller:\r\n                case GearType.energySword: return GetExtraJobReqString(36);\r\n                case GearType.GauntletBuster:\r\n                case GearType.ExplosivePill: return GetExtraJobReqString(37);\r\n\r\n                //4xxx\r\n                case GearType.katana:\r\n                case GearType.katana2: return \"剑豪可穿戴装备\";\r\n                case GearType.onmyoSen:\r\n                case GearType.kannaReifu:\r\n                case GearType.fan: return \"阴阳师可穿戴装备\";\r\n\r\n                //5xxx\r\n                case GearType.soulShield: return \"米哈尔可穿戴装备\";\r\n\r\n                //6xxx\r\n                case GearType.novaMarrow: return GetExtraJobReqString(61);\r\n                case GearType.weaponBelt:\r\n                case GearType.breathShooter: return GetExtraJobReqString(63);\r\n                case GearType.chain2:\r\n                case GearType.transmitter: return GetExtraJobReqString(64);\r\n                case GearType.soulBangle:\r\n                case GearType.soulShooter: return GetExtraJobReqString(65);\r\n\r\n                //10xxx\r\n                case GearType.swordZB:\r\n                case GearType.swordZL: return GetExtraJobReqString(101);\r\n\r\n                case GearType.whistle:\r\n                case GearType.memorialStaff: return GetExtraJobReqString(172);\r\n\r\n                case GearType.magicStick: return GetExtraJobReqString(112);\r\n\r\n                case GearType.espLimiter:\r\n                case GearType.chess: return GetExtraJobReqString(142);\r\n\r\n                case GearType.magicGauntlet: \r\n                case GearType.magicWing: return GetExtraJobReqString(152);\r\n\r\n                case GearType.pathOfAbyss: return GetExtraJobReqString(155);\r\n                case GearType.handFan:\r\n                case GearType.fanTassel: return GetExtraJobReqString(164);\r\n\r\n                case GearType.tuner:\r\n                case GearType.bracelet: return GetExtraJobReqString(151);\r\n\r\n                case GearType.boxingCannon:\r\n                case GearType.boxingSky: return GetExtraJobReqString(175);\r\n\r\n                case GearType.ornament: return GetExtraJobReqString(162);\r\n\r\n                case GearType.chakram:\r\n                case GearType.hexSeeker: return GetExtraJobReqString(154);\r\n\r\n                case GearType.celestialLight:\r\n                case GearType.compass: return GetExtraJobReqString(182);\r\n\r\n                case GearType.longSword:\r\n                case GearType.yeouiGem: return GetExtraJobReqString(161);\r\n                default: return null;\r\n            }\r\n        }\r\n\r\n        /// <summary>\r\n        /// 获取装备额外职业要求说明的字符串。\r\n        /// </summary>\r\n        /// <param Name=\"specJob\">表示装备属性的reqSpecJob的值。</param>\r\n        /// <returns></returns>\r\n        public static string GetExtraJobReqString(int specJob)\r\n        {\r\n            switch (specJob)\r\n            {\r\n                case 21: return \"战神可穿戴装备\";\r\n                case 22: return \"龙神职业群可穿戴装备\";\r\n                case 23: return \"双弩精灵可穿戴装备\";\r\n                case 24: return \"幻影可穿戴装备\";\r\n                case 25: return \"隐月可穿戴装备\";\r\n                case 27: return \"夜光法师可穿戴装备\";\r\n                case 31: return \"恶魔猎手可穿戴装备\";\r\n                case 36: return \"尖兵可穿戴装备\";\r\n                case 37: return \"爆破手可使用\";\r\n                case 41: return \"剑豪可穿戴装备\";\r\n                case 42: return \"阴阳师可穿戴装备\";\r\n                case 51: return \"米哈尔可穿戴装备\";\r\n                case 61: return \"狂龙战士可穿戴装备\";\r\n                case 63: return \"炼狱黑客可穿戴装备\";\r\n                case 64: return \"魔链影士可穿戴装备\";\r\n                case 65: return \"爆莉萌天使可穿戴装备\";\r\n                case 101: return \"神之子可穿戴装备\";\r\n                case 112: return \"林之灵可穿戴装备\";\r\n                case 142: return \"超能力者可穿戴装备\";\r\n                case 151: return \"御剑骑士可穿戴装备\";\r\n                case 152: return \"圣晶使徒可穿戴装备\";\r\n                case 154: return \"飞刃沙士可穿戴装备\";\r\n                case 155: return \"影魂异人可穿戴装备\";\r\n                case 161: return \"莲可穿戴装备\";\r\n                case 162: return \"元素师可穿戴装备\";\r\n                case 164: return \"虎影可穿戴装备\";\r\n                case 172: return \"琳可穿戴装备\";\r\n                case 175: return \"墨玄可穿戴装备\";\r\n                case 182: return \"施亚可穿戴装备\";\r\n                default: return null;\r\n            }\r\n        }\r\n\r\n        public static string GetExtraJobReqString(IEnumerable<int> specJobs)\r\n        {\r\n            List<string> extraJobNames = new List<string>();\r\n            foreach (int specJob in specJobs)\r\n            {\r\n                switch (specJob)\r\n                {\r\n                    case 1: extraJobNames.AddRange(new[] { \"英雄\", \"圣骑士\" }); break;\r\n                    case 2: extraJobNames.AddRange(new[] { \"冰雷魔导师\", \"火毒魔导师\", \"主教\" }); break;\r\n                    case 4: extraJobNames.Add(\"侠盗\"); break;\r\n                    case 11: extraJobNames.Add(\"魂骑士\"); break;\r\n                    case 12: extraJobNames.Add(\"炎术士\"); break;\r\n                    case 22: extraJobNames.Add(\"龙神\"); break;\r\n                    case 32: extraJobNames.Add(\"唤灵斗师\"); break;\r\n                    case 172: extraJobNames.Add(\"森林小主\"); break;\r\n                    default: extraJobNames.Add(specJob.ToString()); break;\r\n                }\r\n            }\r\n            if (extraJobNames.Count == 0)\r\n            {\r\n                return null;\r\n            }\r\n            return string.Join(\"、\", extraJobNames) + \"可穿戴装备\";\r\n        }\r\n\r\n        public static string GetItemPropString(ItemPropType propType, long value)\r\n        {\r\n            switch (propType)\r\n            {\r\n                case ItemPropType.tradeBlock:\r\n                    return GetGearPropString(GearPropType.tradeBlock, value);\r\n                case ItemPropType.tradeAvailable:\r\n                    return GetGearPropString(GearPropType.tradeAvailable, value);\r\n                case ItemPropType.only:\r\n                    return GetGearPropString(GearPropType.only, value);\r\n                case ItemPropType.accountSharable:\r\n                    return GetGearPropString(GearPropType.accountSharable, value);\r\n                case ItemPropType.accountSharableAfterExchange:\r\n                    return GetGearPropString(GearPropType.accountSharableAfterExchange, value);\r\n                case ItemPropType.quest:\r\n                    return value == 0 ? null : \"任务道具\";\r\n                case ItemPropType.pquest:\r\n                    return value == 0 ? null : \"组队任务道具\";\r\n                case ItemPropType.permanent:\r\n                    return value == 0 ? null : \"可以一直使用魔法的神奇宠物。\";\r\n                case ItemPropType.mintable:\r\n                    return GetGearPropString(GearPropType.mintable, value);\r\n                default:\r\n                    return null;\r\n            }\r\n        }\r\n\r\n        public static string GetSkillReqAmount(int skillID, int reqAmount)\r\n        {\r\n            switch (skillID / 10000)\r\n            {\r\n                case 11200: return \"[需要巨熊技能点: \" + reqAmount + \"]\";\r\n                case 11210: return \"[需要雪豹技能点: \" + reqAmount + \"]\";\r\n                case 11211: return \"[需要猛禽技能点: \" + reqAmount + \"]\";\r\n                case 11212: return \"[需要猫咪技能点: \" + reqAmount + \"]\";\r\n                default: return \"[需要？？技能点: \" + reqAmount + \"]\";\r\n            }\r\n        }\r\n\r\n        public static string GetJobName(int jobCode)\r\n        {\r\n            switch (jobCode)\r\n            {\r\n                case 0: return \"新手\";\r\n                case 100: return \"战士\";\r\n                case 110: return \"剑客\";\r\n                case 111: return \"勇士\";\r\n                case 112: return \"英雄\";\r\n                case 114: return \"英雄(6次)\";\r\n                case 120: return \"准骑士\";\r\n                case 121: return \"骑士\";\r\n                case 122: return \"圣骑士\";\r\n                case 124: return \"圣骑士(6次)\";\r\n                case 130: return \"枪战士\";\r\n                case 131: return \"狂战士\";\r\n                case 132: return \"黑骑士\";\r\n                case 134: return \"黑骑士(6次)\";\r\n                case 200: return \"魔法师\";\r\n                case 210: return \"法师（火，毒）\";\r\n                case 211: return \"巫师（火，毒）\";\r\n                case 212: return \"魔导师（火，毒）\";\r\n                case 214: return \"魔导师（火，毒）(6次)\";\r\n                case 220: return \"法师（冰，雷）\";\r\n                case 221: return \"巫师（冰，雷）\";\r\n                case 222: return \"魔导师（冰，雷）\";\r\n                case 224: return \"魔导师（冰，雷）(6次)\";\r\n                case 230: return \"牧师\";\r\n                case 231: return \"祭司\";\r\n                case 232: return \"主教\";\r\n                case 234: return \"主教(6次)\";\r\n                case 300: return \"弓箭手\";\r\n                case 301: return \"弓箭手(古迹猎人)\";\r\n                case 310: return \"猎人\";\r\n                case 311: return \"射手\";\r\n                case 312: return \"神射手\";\r\n                case 314: return \"神射手(6次)\";\r\n                case 320: return \"弩弓手\";\r\n                case 321: return \"游侠\";\r\n                case 322: return \"箭神\";\r\n                case 324: return \"箭神(6次)\";\r\n                case 330: return \"上古射手\";\r\n                case 331: return \"追逐者\";\r\n                case 332: return \"古迹猎人\";\r\n                case 334: return \"古迹猎人(6次)\";\r\n                case 400: return \"飞侠\";\r\n                case 410: return \"刺客\";\r\n                case 411: return \"无影人\";\r\n                case 412: return \"隐士\";\r\n                case 414: return \"隐士(6次)\";\r\n                case 420: return \"侠客\";\r\n                case 421: return \"独行客\";\r\n                case 422: return \"侠盗\";\r\n                case 424: return \"侠盗(6次)\";\r\n                case 430: return \"见习刀客\";\r\n                case 431: return \"双刀客\";\r\n                case 432: return \"双刀侠\";\r\n                case 433: return \"血刀\";\r\n                case 434: return \"暗影双刀\";\r\n                case 436: return \"暗影双刀(6次)\";\r\n                case 500: return \"海盗\";\r\n                case 501: return \"海盗(炮手)\";\r\n                case 510: return \"拳手\";\r\n                case 511: return \"斗士\";\r\n                case 512: return \"冲锋队长\";\r\n                case 514: return \"冲锋队长(6次)\";\r\n                case 520: return \"火枪手\";\r\n                case 521: return \"大副\";\r\n                case 522: return \"船长\";\r\n                case 524: return \"船长(6次)\";\r\n                case 530: return \"火炮手\";\r\n                case 531: return \"毁灭炮手\";\r\n                case 532: return \"神炮王\";\r\n                case 534: return \"神炮王(6次)\";\r\n\r\n                case 800: return \"管理员\";\r\n                case 900: return \"管理员\";\r\n\r\n\r\n                case 1000: return \"初心者\";\r\n                case 1100: return \"魂骑士(1次)\";\r\n                case 1110: return \"魂骑士(2次)\";\r\n                case 1111: return \"魂骑士(3次)\";\r\n                case 1112: return \"魂骑士(4次)\";\r\n                case 1114: return \"魂骑士(6次)\";\r\n                case 1200: return \"炎术士(1次)\";\r\n                case 1210: return \"炎术士(2次)\";\r\n                case 1211: return \"炎术士(3次)\";\r\n                case 1212: return \"炎术士(4次)\";\r\n                case 1214: return \"炎术士(6次)\";\r\n                case 1300: return \"风灵使者(1次)\";\r\n                case 1310: return \"风灵使者(2次)\";\r\n                case 1311: return \"风灵使者(3次)\";\r\n                case 1312: return \"风灵使者(4次)\";\r\n                case 1314: return \"风灵使者(6次)\";\r\n                case 1400: return \"夜行者(1次)\";\r\n                case 1410: return \"夜行者(2次)\";\r\n                case 1411: return \"夜行者(3次)\";\r\n                case 1412: return \"夜行者(4次)\";\r\n                case 1414: return \"夜行者(6次)\";\r\n                case 1500: return \"奇袭者(1次)\";\r\n                case 1510: return \"奇袭者(2次)\";\r\n                case 1511: return \"奇袭者(3次)\";\r\n                case 1512: return \"奇袭者(4次)\";\r\n                case 1514: return \"奇袭者(6次)\";\r\n\r\n                case 2000: return \"战童\";\r\n                case 2001: return \"小不点\";\r\n                case 2002: return \"双弩精灵\";\r\n                case 2003: return \"幻影\";\r\n                case 2004: return \"夜光法师\";\r\n                case 2005: return \"隐月\";\r\n                case 2100: return \"战神(1次)\";\r\n                case 2110: return \"战神(2次)\";\r\n                case 2111: return \"战神(3次)\";\r\n                case 2112: return \"战神(4次)\";\r\n                case 2114: return \"战神(6次)\";\r\n                case 2200:\r\n                case 2210: return \"龙神(1次)\";\r\n                case 2211:\r\n                case 2212:\r\n                case 2213: return \"龙神(2次)\";\r\n                case 2214:\r\n                case 2215:\r\n                case 2216: return \"龙神(3次)\";\r\n                case 2217:\r\n                case 2218: return \"龙神(4次)\";\r\n                case 2220: return \"龙神(6次)\";\r\n                case 2300: return \"双弩精灵(1次)\";\r\n                case 2310: return \"双弩精灵(2次)\";\r\n                case 2311: return \"双弩精灵(3次)\";\r\n                case 2312: return \"双弩精灵(4次)\";\r\n                case 2314: return \"双弩精灵(6次)\";\r\n                case 2400: return \"幻影(1次)\";\r\n                case 2410: return \"幻影(2次)\";\r\n                case 2411: return \"幻影(3次)\";\r\n                case 2412: return \"幻影(4次)\";\r\n                case 2414: return \"幻影(6次)\";\r\n                case 2500: return \"隐月(1次)\";\r\n                case 2510: return \"隐月(2次)\";\r\n                case 2511: return \"隐月(3次)\";\r\n                case 2512: return \"隐月(4次)\";\r\n                case 2514: return \"隐月(6次)\";\r\n                case 2700: return \"夜光法师(1次)\";\r\n                case 2710: return \"夜光法师(2次)\";\r\n                case 2711: return \"夜光法师(3次)\";\r\n                case 2712: return \"夜光法师(4次)\";\r\n                case 2714: return \"夜光法师(6次)\";\r\n\r\n\r\n                case 3000: return \"市民\";\r\n                case 3001: return \"恶魔\";\r\n                case 3100: return \"恶魔猎手(1次)\";\r\n                case 3110: return \"恶魔猎手(2次)\";\r\n                case 3111: return \"恶魔猎手(3次)\";\r\n                case 3112: return \"恶魔猎手(4次)\";\r\n                case 3114: return \"恶魔猎手(6次)\";\r\n                case 3101: return \"恶魔复仇者(1次)\";\r\n                case 3120: return \"恶魔复仇者(2次)\";\r\n                case 3121: return \"恶魔复仇者(3次)\";\r\n                case 3122: return \"恶魔复仇者(4次)\";\r\n                case 3124: return \"恶魔复仇者(6次)\";\r\n                case 3200: return \"唤灵斗师(1次)\";\r\n                case 3210: return \"唤灵斗师(2次)\";\r\n                case 3211: return \"唤灵斗师(3次)\";\r\n                case 3212: return \"唤灵斗师(4次)\";\r\n                case 3214: return \"唤灵斗师(6次)\";\r\n                case 3300: return \"豹弩游侠(1次)\";\r\n                case 3310: return \"豹弩游侠(2次)\";\r\n                case 3311: return \"豹弩游侠(3次)\";\r\n                case 3312: return \"豹弩游侠(4次)\";\r\n                case 3314: return \"豹弩游侠(6次)\";\r\n                case 3500: return \"机械师(1次)\";\r\n                case 3510: return \"机械师(2次)\";\r\n                case 3511: return \"机械师(3次)\";\r\n                case 3512: return \"机械师(4次)\";\r\n                case 3514: return \"机械师(6次)\";\r\n                case 3002: return \"尖兵\";\r\n                case 3600: return \"尖兵(1次)\";\r\n                case 3610: return \"尖兵(2次)\";\r\n                case 3611: return \"尖兵(3次)\";\r\n                case 3612: return \"尖兵(4次)\";\r\n                case 3614: return \"尖兵(6次)\";\r\n                case 3700: return \"爆破手\";\r\n                case 3710: return \"爆破手(2次)\";\r\n                case 3711: return \"爆破手(3次)\";\r\n                case 3712: return \"爆破手(4次)\";\r\n                case 3714: return \"爆破手(6次)\";\r\n\r\n                case 4001: return \"阴阳师\";\r\n                case 4002: return \"阴阳师\";\r\n                case 4100: return \"剑豪(1次)\";\r\n                case 4110: return \"剑豪(2次)\";\r\n                case 4111: return \"剑豪(3次)\";\r\n                case 4112: return \"剑豪(4次)\";\r\n                case 4114: return \"剑豪(6次)\";\r\n                case 4200: return \"阴阳师(1次)\";\r\n                case 4210: return \"阴阳师(2次)\";\r\n                case 4211: return \"阴阳师(3次)\";\r\n                case 4212: return \"阴阳师(4次)\";\r\n                case 4214: return \"阴阳师(6次)\";\r\n\r\n\r\n                case 5000: return \"米哈尔\";\r\n                case 5100: return \"米哈尔(1次)\";\r\n                case 5110: return \"米哈尔(2次)\";\r\n                case 5111: return \"米哈尔(3次)\";\r\n                case 5112: return \"米哈尔(4次)\";\r\n                case 5114: return \"米哈尔(6次)\";\r\n\r\n\r\n                case 6000: return \"狂龙战士\";\r\n                case 6001: return \"爆莉萌天使\";\r\n                case 6002: return \"魔链影士\";\r\n                case 6003: return \"炼狱黑客\";\r\n                case 6100: return \"狂龙战士(1次)\";\r\n                case 6110: return \"狂龙战士(2次)\";\r\n                case 6111: return \"狂龙战士(3次)\";\r\n                case 6112: return \"狂龙战士(4次)\";\r\n                case 6114: return \"狂龙战士(6次)\";\r\n                case 6300: return \"炼狱黑客(1次)\";\r\n                case 6310: return \"炼狱黑客(2次)\";\r\n                case 6311: return \"炼狱黑客(3次)\";\r\n                case 6312: return \"炼狱黑客(4次)\";\r\n                case 6314: return \"炼狱黑客(6次)\";\r\n                case 6400: return \"魔链影士(1次)\";\r\n                case 6410: return \"魔链影士(2次)\";\r\n                case 6411: return \"魔链影士(3次)\";\r\n                case 6412: return \"魔链影士(4次)\";\r\n                case 6414: return \"魔链影士(6次)\";\r\n                case 6500: return \"爆莉萌天使(1次)\";\r\n                case 6510: return \"爆莉萌天使(2次)\";\r\n                case 6511: return \"爆莉萌天使(3次)\";\r\n                case 6512: return \"爆莉萌天使(4次)\";\r\n                case 6514: return \"爆莉萌天使(6次)\";\r\n\r\n\r\n                case 7000: return \"内在能力\";\r\n                case 7100: return \"联盟\";\r\n                case 7200: return \"怪物农庄\";\r\n\r\n\r\n                case 9100: return \"公会\";\r\n                case 9200: \r\n                case 9201: \r\n                case 9202: \r\n                case 9203: \r\n                case 9204: return \"专业技术\";\r\n\r\n\r\n                case 10000: return \"神之子\";\r\n                case 10100: return \"神之子\";\r\n                case 10110: return \"神之子\";\r\n                case 10111: return \"神之子\";\r\n                case 10112: return \"神之子\";\r\n                case 10114: return \"神之子(6次)\";\r\n\r\n                case 11000: return \"林之灵\";\r\n                case 11200: return \"林之灵(1次)\";\r\n                case 11210: return \"林之灵(2次)\";\r\n                case 11211: return \"林之灵(3次)\";\r\n                case 11212: return \"林之灵(4次)\";\r\n                case 11214: return \"林之灵(6次)\";\r\n\r\n                case 12000:\r\n                case 12005:\r\n                case 12100: return \"灶门炭治郎\";\r\n\r\n                case 12006:\r\n                case 12200: return \"埼玉\";\r\n\r\n\r\n                case 13000: \r\n                case 13100: return \"品克缤\";\r\n                case 13001:\r\n                case 13500: return \"白雪人\";\r\n\r\n                case 14000: return \"超能力者\";\r\n                case 14200: return \"超能力者(1次)\";\r\n                case 14210: return \"超能力者(2次)\";\r\n                case 14211: return \"超能力者(3次)\";\r\n                case 14212: return \"超能力者(4次)\";\r\n                case 14214: return \"超能力者(6次)\";\r\n\r\n                case 15000: return \"圣晶使徒\";\r\n                case 15001: return \"影魂异人\";\r\n                case 15002: return \"御剑骑士\";\r\n                case 15003: return \"飞刃沙士\";\r\n                case 15100: return \"御剑骑士(1次)\";\r\n                case 15110: return \"御剑骑士(2次)\";\r\n                case 15111: return \"御剑骑士(3次)\";\r\n                case 15112: return \"御剑骑士(4次)\";\r\n                case 15114: return \"御剑骑士(6次)\";\r\n                case 15200: return \"圣晶使徒(1次)\";\r\n                case 15210: return \"圣晶使徒(2次)\";\r\n                case 15211: return \"圣晶使徒(3次)\";\r\n                case 15212: return \"圣晶使徒(4次)\";\r\n                case 15214: return \"圣晶使徒(6次)\";\r\n                case 15400: return \"飞刃沙士(1次)\";\r\n                case 15410: return \"飞刃沙士(2次)\";\r\n                case 15411: return \"飞刃沙士(3次)\";\r\n                case 15412: return \"飞刃沙士(4次)\";\r\n                case 15414: return \"飞刃沙士(6次)\";\r\n                case 15500: return \"影魂异人(1次)\";\r\n                case 15510: return \"影魂异人(2次)\";\r\n                case 15511: return \"影魂异人(3次)\";\r\n                case 15512: return \"影魂异人(4次)\";\r\n                case 15514: return \"影魂异人(6次)\";\r\n\r\n                case 16000: return \"虎影\";\r\n                case 16001: return \"软萌宝\";\r\n                case 16002: return \"莲\";\r\n                case 16100: return \"莲(1次)\";\r\n                case 16110: return \"莲(2次)\";\r\n                case 16111: return \"莲(3次)\";\r\n                case 16112: return \"莲(4次)\";\r\n                case 16114: return \"莲(6次)\";\r\n                case 16200: return \"元素师(1次)\";\r\n                case 16210: return \"元素师(2次)\";\r\n                case 16211: return \"元素师(3次)\";\r\n                case 16212: return \"元素师(4次)\";\r\n                case 16214: return \"元素师(6次)\";\r\n                case 16400: return \"虎影(1次)\";\r\n                case 16410: return \"虎影(2次)\";\r\n                case 16411: return \"虎影(3次)\";\r\n                case 16412: return \"虎影(4次)\";\r\n                case 16414: return \"虎影(6次)\";\r\n\r\n                case 17000: return \"墨玄\";\r\n                case 17001: return \"琳\";\r\n                case 17200: return \"琳(1次)\";\r\n                case 17210: return \"琳(2次)\";\r\n                case 17211: return \"琳(3次)\";\r\n                case 17212: return \"琳(4次)\";\r\n                case 17214: return \"琳(5次)\";\r\n                case 17500: return \"墨玄(1次)\";\r\n                case 17510: return \"墨玄(2次)\";\r\n                case 17511: return \"墨玄(3次)\";\r\n                case 17512: return \"墨玄(4次)\";\r\n                case 17514: return \"墨玄(6次)\";\r\n\r\n                case 18000: return \"施亚\";\r\n                case 18200: return \"施亚(1次)\";\r\n                case 18210: return \"施亚(2次)\";\r\n                case 18211: return \"施亚(3次)\";\r\n                case 18212: return \"施亚(4次)\";\r\n                case 18214: return \"施亚(6次)\";\r\n\r\n\r\n                case 40000: return \"5转\";\r\n                case 40001: return \"5转(战士)\";\r\n                case 40002: return \"5转(魔法师)\";\r\n                case 40003: return \"5转(弓箭手)\";\r\n                case 40004: return \"5转(飞侠)\";\r\n                case 40005: return \"5转(海盗)\";\r\n                case 50000: return \"6转\";\r\n                case 50006: return \"6转(强化核心)\";\r\n                case 50007: return \"6转(HEXA属性)\";\r\n            }\r\n            return null;\r\n        }\r\n\r\n        public static string ToChineseNumberExpr(long value, bool detailedExpr = false)\r\n        {\r\n            var sb = new StringBuilder(32);\r\n            bool firstPart = true;\r\n            if (value < 0)\r\n            {\r\n                sb.Append(\"-\");\r\n                value = -value; // just ignore the exception -2147483648\r\n            }\r\n            if (detailedExpr)\r\n            {\r\n                string[] smallUnits = { \"\", \"十\", \"百\", \"千\" };\r\n                string[] bigUnits = { \"\", \"万\", \"亿\", \"兆\", \"京\" };\r\n\r\n                string digits = value.ToString();\r\n                int len = digits.Length;\r\n\r\n                bool blockHasValue = false;\r\n                int zeroCount = 0;\r\n\r\n                for (int i = 0; i < len; i++)\r\n                {\r\n                    int posFromRight = len - i - 1;\r\n                    int smallUnitIndex = posFromRight % 4;\r\n                    int bigUnitIndex = posFromRight / 4;\r\n\r\n                    char d = digits[i];\r\n\r\n                    if (d == '0')\r\n                    {\r\n                        zeroCount++;\r\n                    }\r\n                    else\r\n                    {\r\n                        if (zeroCount > 0 && zeroCount <= 3)\r\n                        {\r\n                            sb.Append('0');\r\n                        }\r\n\r\n                        zeroCount = 0;\r\n\r\n                        sb.Append(d);\r\n                        if (smallUnitIndex > 0)\r\n                            sb.Append(smallUnits[smallUnitIndex]);\r\n\r\n                        blockHasValue = true;\r\n                    }\r\n\r\n                    if (smallUnitIndex == 0)\r\n                    {\r\n                        if (blockHasValue && bigUnitIndex > 0 && bigUnitIndex < bigUnits.Length)\r\n                            sb.Append(bigUnits[bigUnitIndex]);\r\n\r\n                        blockHasValue = false;\r\n                        zeroCount = 0;\r\n                    }\r\n                }\r\n            }\r\n            else\r\n            {\r\n                if (value >= 1_0000_0000_0000_0000)\r\n                {\r\n                    long part = value / 1_0000_0000_0000_0000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}京\", part);\r\n                    value -= part * 1_0000_0000_0000_0000;\r\n                    firstPart = false;\r\n                }\r\n                if (value >= 1_0000_0000_0000)\r\n                {\r\n                    long part = value / 1_0000_0000_0000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}兆\", part);\r\n                    value -= part * 1_0000_0000_0000;\r\n                    firstPart = false;\r\n                }\r\n                if (value >= 1_0000_0000)\r\n                {\r\n                    long part = value / 1_0000_0000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}亿\", part);\r\n                    value -= part * 1_0000_0000;\r\n                    firstPart = false;\r\n                }\r\n                if (value >= 1_0000)\r\n                {\r\n                    long part = value / 1_0000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}万\", part);\r\n                    value -= part * 1_0000;\r\n                    firstPart = false;\r\n                }\r\n                if (value > 0)\r\n                {\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}\", value);\r\n                }\r\n            }\r\n\r\n            return sb.Length > 0 ? sb.ToString() : \"0\";\r\n        }\r\n\r\n        public static string ToThousandsNumberExpr(long value, bool isMsea = false)\r\n        {\r\n            var sb = new StringBuilder(32);\r\n            bool firstPart = true;\r\n            if (isMsea)\r\n            {\r\n                if (value < 0)\r\n                {\r\n                    sb.Append(\"-\");\r\n                    value = -value; // just ignore the exception -2147483648\r\n                }\r\n                if (value >= 1_0000_0000_0000_0000)\r\n                {\r\n                    long part = value / 1_0000_0000_0000_0000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}Q\", part);\r\n                    value -= part * 1_0000_0000_0000_0000;\r\n                    firstPart = false;\r\n                }\r\n                if (value >= 1_000_000_000_000)\r\n                {\r\n                    long part = value / 1_000_000_000_000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}T\", part);\r\n                    value -= part * 1_000_000_000_000;\r\n                    firstPart = false;\r\n                }\r\n                if (value >= 1_000_000_000)\r\n                {\r\n                    long part = value / 1_000_000_000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}B\", part);\r\n                    value -= part * 1_000_000_000;\r\n                    firstPart = false;\r\n                }\r\n                if (value >= 1_000_000)\r\n                {\r\n                    long part = value / 1_000_000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}M\", part);\r\n                    value -= part * 1_000_000;\r\n                    firstPart = false;\r\n                }\r\n                if (value >= 1_000)\r\n                {\r\n                    long part = value / 1_000;\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}K\", part);\r\n                    value -= part * 1_000;\r\n                    firstPart = false;\r\n                }\r\n                if (value > 0)\r\n                {\r\n                    sb.Append(firstPart ? null : \" \");\r\n                    sb.AppendFormat(\"{0}\", value);\r\n                }\r\n            }\r\n            else\r\n            {\r\n                if (value < 0)\r\n                {\r\n                    sb.Append(\"-\");\r\n                    value = -value; // just ignore the exception -2147483648\r\n                }\r\n                /* if (value >= 1_000_000_000_000) // For future proofing\r\n                {\r\n                    double part = Math.Round((double)value / 1_000_000_000_000, 1);\r\n                    sb.AppendFormat(\"{0}T\", part);\r\n                } */\r\n                if (value >= 1_000_000_000)\r\n                {\r\n                    double part = Math.Round((double)value / 1_000_000_000, 1);\r\n                    sb.AppendFormat(\"{0}B\", part);\r\n                }\r\n                else if (value >= 1_000_000)\r\n                {\r\n                    double part = Math.Round((double)value / 1_000_000, 1);\r\n                    sb.AppendFormat(\"{0}M\", part);\r\n                }\r\n                else if (value >= 1_000)\r\n                {\r\n                    double part = Math.Round((double)value / 1_000, 1);\r\n                    sb.AppendFormat(\"{0}K\", part);\r\n                }\r\n                else if (value > 0)\r\n                {\r\n                    sb.AppendFormat(\"{0}\", value);\r\n                }\r\n            }\r\n\r\n            return sb.Length > 0 ? sb.ToString() : \"0\";\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Mob.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Mob\n    {\n        public Mob()\n        {\n            this.ID = -1;\n            this.ElemAttr = new MobElemAttr(null);\n            this.Revive = new List<int>();\n            //this.Animates = new LifeAnimateCollection();\n\n            this.FirstAttack = false;\n            this.BodyAttack = false;\n            this.DamagedByMob = false;\n        }\n\n        public int ID { get; set; }\n        public int Level { get; set; }\n        public string DefaultHP { get; set; }\n        public string DefaultMP { get; set; }\n        public string FinalMaxHP { get; set; }\n        public string FinalMaxMP { get; set; }\n        public long MaxHP { get; set; }\n        public long MaxMP { get; set; }\n        public int HPRecovery { get; set; }\n        public int MPRecovery { get; set; }\n        public int? Speed { get; set; }\n        public int? FlySpeed { get; set; }\n        public int PADamage { get; set; }\n        public int MADamage { get; set; }\n        public int PDRate { get; set; }\n        public int MDRate { get; set; }\n        public int Acc { get; set; }\n        public int Eva { get; set; }\n        public int Pushed { get; set; }\n        public int Exp { get; set; }\n        public bool Boss { get; set; }\n        public bool Undead { get; set; }\n        public int Category { get; set; }\n        public bool FirstAttack { get; set; }\n        public bool BodyAttack { get; set; }\n        public int RemoveAfter { get; set; }\n        public bool DamagedByMob { get; set; }\n        public bool Invincible { get; set; }\n        public bool NotAttack { get; set; }\n        public int FixedDamage { get; set; }\n        public MobElemAttr ElemAttr { get; set; }\n\n        public int? Link { get; set; }\n        public bool Skeleton { get; set; }\n        public bool JsonLoad { get; set; }\n\n        public List<int> Revive { get; private set; }\n\n        public BitmapOrigin Default { get; set; }\n        //public LifeAnimateCollection Animates { get; private set; }\n\n\n        public static Mob CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            int mobID;\n            Match m = Regex.Match(node.Text, @\"^(\\d{7})\\.img$\");\n            if (!(m.Success && Int32.TryParse(m.Result(\"$1\"), out mobID)))\n            {\n                return null;\n            }\n\n            Mob mobInfo = new Mob();\n            mobInfo.ID = mobID;\n            Wz_Node infoNode = node.FindNodeByPath(\"info\");\n            //加载基础属性\n            if (infoNode != null)\n            {\n                foreach (var propNode in infoNode.Nodes)\n                {\n                    switch (propNode.Text)\n                    {\n                        case \"level\": mobInfo.Level = propNode.GetValueEx<int>(0); break;\n                        case \"defaultHP\": mobInfo.DefaultHP = propNode.GetValueEx<string>(null); break;\n                        case \"defaultMP\": mobInfo.DefaultMP = propNode.GetValueEx<string>(null); break;\n                        case \"finalmaxHP\": mobInfo.FinalMaxHP = propNode.GetValueEx<string>(null); break;\n                        case \"finalmaxMP\": mobInfo.FinalMaxMP = propNode.GetValueEx<string>(null); break;\n                        case \"maxHP\": mobInfo.MaxHP = propNode.GetValueEx<long>(0); break;\n                        case \"maxMP\": mobInfo.MaxMP = propNode.GetValueEx<long>(0); break;\n                        case \"hpRecovery\": mobInfo.HPRecovery = propNode.GetValueEx<int>(0); break;\n                        case \"mpRecovery\": mobInfo.MPRecovery = propNode.GetValueEx<int>(0); break;\n                        case \"speed\": mobInfo.Speed = propNode.GetValueEx<int>(0); break;\n                        case \"flySpeed\": mobInfo.FlySpeed = propNode.GetValueEx<int>(0); break;\n\n                        case \"PADamage\": mobInfo.PADamage = propNode.GetValueEx<int>(0); break;\n                        case \"MADamage\": mobInfo.MADamage = propNode.GetValueEx<int>(0); break;\n                        case \"PDRate\": mobInfo.PDRate = propNode.GetValueEx<int>(0); break;\n                        case \"MDRate\": mobInfo.MDRate = propNode.GetValueEx<int>(0); break;\n                        case \"acc\": mobInfo.Acc = propNode.GetValueEx<int>(0); break;\n                        case \"eva\": mobInfo.Eva = propNode.GetValueEx<int>(0); break;\n                        case \"pushed\": mobInfo.Pushed = propNode.GetValueEx<int>(0); break;\n                        case \"exp\": mobInfo.Exp = propNode.GetValueEx<int>(0); break;\n\n                        case \"boss\": mobInfo.Boss = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"undead\": mobInfo.Undead = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"firstAttack\": mobInfo.FirstAttack = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"bodyAttack\": mobInfo.BodyAttack = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"category\": mobInfo.Category = propNode.GetValueEx<int>(0); break;\n                        case \"removeAfter\": mobInfo.RemoveAfter = propNode.GetValueEx<int>(0); break;\n                        case \"damagedByMob\": mobInfo.DamagedByMob = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"invincible\": mobInfo.Invincible = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"notAttack\": mobInfo.NotAttack = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"fixedDamage\": mobInfo.FixedDamage = propNode.GetValueEx<int>(0); break;\n                        case \"elemAttr\": mobInfo.ElemAttr = new MobElemAttr(propNode.GetValueEx<string>(null)); break;\n\n                        case \"link\": mobInfo.Link = propNode.GetValueEx<int>(0); break;\n                        case \"skeleton\": mobInfo.Skeleton = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"jsonLoad\": mobInfo.JsonLoad = propNode.GetValueEx<int>(0) != 0; break;\n\n                        //case \"skill\": LoadSkill(mobInfo, propNode); break;\n                        //case \"attack\": LoadAttack(mobInfo, propNode); break;\n                        //case \"buff\": LoadBuff(mobInfo, propNode); break;\n                        case \"revive\":\n                            for (int i = 0; ; i++)\n                            {\n                                var reviveNode = propNode.FindNodeByPath(i.ToString());\n                                if (reviveNode == null)\n                                {\n                                    break;\n                                }\n                                mobInfo.Revive.Add(reviveNode.GetValue<int>());\n                            }\n                            break;\n                    }\n                }\n            }\n\n            //读取怪物默认动作\n            {\n                Wz_Node linkNode = null;\n                if (mobInfo.Link != null && findNode != null)\n                {\n                    linkNode = findNode(string.Format(\"Mob\\\\{0:d7}.img\", mobInfo.Link));\n                }\n                if (linkNode == null)\n                {\n                    linkNode = node;\n                }\n\n                var imageFrame = new BitmapOrigin();\n\n                foreach (var action in new[] { \"stand\", \"move\", \"fly\" })\n                {\n                    var actNode = linkNode.FindNodeByPath(action + @\"\\0\");\n                    if (actNode != null)\n                    {\n                        imageFrame = BitmapOrigin.CreateFromNode(actNode, findNode);\n                        if (imageFrame.Bitmap != null)\n                        {\n                            break;\n                        }\n                    }\n                }\n\n                mobInfo.Default = imageFrame;\n            }\n\n            return mobInfo;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/MobElemAttr.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class MobElemAttr\n    {\n        public MobElemAttr(string elemAttr)\n        {\n            this.StringValue = elemAttr;\n            if (string.IsNullOrEmpty(elemAttr))\n            {\n                return;\n            }\n\n            for (int i = 0; i < elemAttr.Length; i += 2)\n            {\n                ElemResistance resist = (ElemResistance)(elemAttr[i + 1] - 48);\n                switch (elemAttr[i])\n                {\n                    case 'I': this.I = resist; break;\n                    case 'L': this.L = resist; break;\n                    case 'F': this.F = resist; break;\n                    case 'S': this.S = resist; break;\n                    case 'H': this.H = resist; break;\n                    case 'D': this.D = resist; break;\n                    case 'P': this.P = resist; break;\n                }\n            }\n        }\n        public string StringValue { get; private set; }\n        public ElemResistance I { get; private set; }\n        public ElemResistance L { get; private set; }\n        public ElemResistance F { get; private set; }\n        public ElemResistance S { get; private set; }\n        public ElemResistance H { get; private set; }\n        public ElemResistance D { get; private set; }\n        public ElemResistance P { get; private set; }\n    }\n\n    public enum ElemResistance : byte\n    {\n        Normal = 0,\n        Immune = 1,\n        Resist = 2,\n        Weak = 3,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Npc.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Npc\n    {\n        public Npc()\n        {\n            this.ID = -1;\n            //this.Animates = new LifeAnimateCollection();\n        }\n\n        public int ID { get; set; }\n        public bool Shop { get; set; }\n\n        public int? Link { get; set; }\n\n        public BitmapOrigin Default { get; set; }\n\n        //public LifeAnimateCollection Animates { get; private set; }\n\n        public static Npc CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            int npcID;\n            Match m = Regex.Match(node.Text, @\"^(\\d{7})\\.img$\");\n            if (!(m.Success && Int32.TryParse(m.Result(\"$1\"), out npcID)))\n            {\n                return null;\n            }\n\n            Npc npcInfo = new Npc();\n            npcInfo.ID = npcID;\n            Wz_Node infoNode = node.FindNodeByPath(\"info\");\n\n            //加载基础属性\n            if (infoNode != null)\n            {\n                foreach (var propNode in infoNode.Nodes)\n                {\n                    switch (propNode.Text)\n                    {\n                        case \"shop\": npcInfo.Shop = propNode.GetValueEx<int>(0) != 0; break;\n                        case \"link\": npcInfo.Link = propNode.GetValueEx<int>(0); break;\n                        case \"default\": npcInfo.Default = BitmapOrigin.CreateFromNode(propNode, findNode); break;\n                    }\n                }\n            }\n\n            //读取默认图片\n            if (npcInfo.Default.Bitmap == null)\n            {\n                Wz_Node linkNode = null;\n                if (npcInfo.Link != null && findNode != null)\n                {\n                    linkNode = findNode(string.Format(\"Npc\\\\{0:d7}.img\", npcInfo.Link));\n                }\n                if (linkNode == null)\n                {\n                    linkNode = node;\n                }\n\n                var imageFrame = new BitmapOrigin();\n\n                foreach (var action in new[] { \"stand\", \"move\", \"fly\" })\n                {\n                    var actNode = linkNode.FindNodeByPath(action + @\"\\0\");\n                    if (actNode != null)\n                    {\n                        imageFrame = BitmapOrigin.CreateFromNode(actNode, findNode);\n                        if (imageFrame.Bitmap != null)\n                        {\n                            break;\n                        }\n                    }\n                }\n\n                npcInfo.Default = imageFrame;\n            }\n\n            return npcInfo;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Potential.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Potential\n    {\n        public Potential()\n        {\n            props = new Dictionary<GearPropType, int>();\n        }\n        public int code;\n        public int optionType;\n        public int reqLevel;\n        public Dictionary<GearPropType, int> props;\n        public int weight;\n        public string stringSummary;\n\n        /// <summary>\n        /// 指示潜能是否是附加潜能。\n        /// </summary>\n        public bool IsPotentialEx\n        {\n            get { return this.code / 1000 % 10 == 2; }\n        }\n\n        public override string ToString()\n        {\n            return this.code.ToString(\"d6\") + \" \" + ConvertSummary()\n                + (weight > 0 ? (\" - \" + weight) : null);\n        }\n\n        public string ConvertSummary()\n        {\n            if (string.IsNullOrEmpty(this.stringSummary))\n                return null;\n            List<string> types = new List<string>(this.props.Keys.Count);\n            foreach (GearPropType k in this.props.Keys)\n                types.Add(k.ToString());\n            types.Sort((a, b) => b.Length.CompareTo(a.Length));\n            string str = this.stringSummary;\n            foreach (string s in types)\n            {\n                GearPropType t = (GearPropType)Enum.Parse(typeof(GearPropType), s);\n                str = str.Replace(\"#\" + s, this.props[t].ToString());\n            }\n            return str;\n        }\n\n        public static int GetPotentialLevel(int gearReqLevel)\n        {\n            if (gearReqLevel <= 0) return 1;\n            else if (gearReqLevel >= 200) return 20;\n            else return (gearReqLevel + 9) / 10;\n        }\n\n        public static bool CheckOptionType(int optionType, GearType gearType)\n        {\n            switch (optionType)\n            {\n                case 0: return true;\n                case 10: return Gear.IsWeapon(gearType) || \n                    (Gear.IsSubWeapon(gearType) && gearType != GearType.shield);\n                case 11:\n                    return !CheckOptionType(10, gearType);\n                case 20: return gearType == GearType.pants\n                    || gearType == GearType.shoes\n                    || gearType == GearType.cap\n                    || gearType == GearType.coat\n                    || gearType == GearType.longcoat\n                    || gearType == GearType.glove\n                    || gearType == GearType.cape;\n                case 40: return gearType == GearType.ring\n                    || gearType == GearType.earrings\n                    || gearType == GearType.pendant\n                    || gearType == GearType.belt;\n                case 51: return gearType == GearType.cap;\n                case 52: return gearType == GearType.coat || gearType == GearType.longcoat;\n                case 53: return gearType == GearType.pants || gearType == GearType.longcoat;\n                case 54: return gearType == GearType.glove;\n                case 55: return gearType == GearType.shoes;\n                default: return false;\n            }\n        }\n\n        public static Potential CreateFromNode(Wz_Node potentialNode, int pLevel)\n        {\n            Potential potential = new Potential();\n            if (potentialNode == null || !Int32.TryParse(potentialNode.Text, out potential.code))\n                return null;\n            foreach (Wz_Node subNode in potentialNode.Nodes)\n            {\n                if (subNode.Text == \"info\")\n                {\n                    foreach (Wz_Node infoNode in subNode.Nodes)\n                    {\n                        switch (infoNode.Text)\n                        {\n                            case \"optionType\":\n                                potential.optionType = Convert.ToInt32(infoNode.Value);\n                                break;\n                            case \"reqLevel\":\n                                potential.reqLevel = Convert.ToInt32(infoNode.Value);\n                                break;\n                            case \"weight\":\n                                potential.weight = Convert.ToInt32(infoNode.Value);\n                                break;\n                            case \"string\":\n                                potential.stringSummary = Convert.ToString(infoNode.Value);\n                                break;\n                        }\n                    }\n                }\n                else if (subNode.Text == \"level\")\n                {\n                    Wz_Node levelNode = subNode.FindNodeByPath(pLevel.ToString());\n                    if (levelNode != null)\n                    {\n                        foreach (Wz_Node propNode in levelNode.Nodes)\n                        {\n                            try\n                            {\n                                GearPropType propType = (GearPropType)Enum.Parse(typeof(GearPropType), propNode.Text);\n                                int value = (propType == GearPropType.face ? 0 : Convert.ToInt32(propNode.Value));\n                                potential.props.Add(propType, value);\n                            }\n                            catch\n                            {\n                            }\n                        }\n                    }\n                    else\n                    {\n                        return null;\n                    }\n                }\n            }\n            return potential;\n        }\n\n        public static Potential LoadFromWz(int optID, int optLevel, GlobalFindNodeFunction findNode)\n        {\n            Wz_Node itemWz = findNode(\"Item\\\\ItemOption.img\");\n            if (itemWz == null)\n                return null;\n\n            Potential opt = Potential.CreateFromNode(itemWz.FindNodeByPath(optID.ToString(\"d6\")), optLevel);\n            return opt;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Recipe.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Recipe\n    {\n        public Recipe()\n        {\n            this.TargetItems = new List<RecipeItemInfo>();\n            this.RecipeItems = new List<RecipeItemInfo>();\n            this.Props = new Dictionary<RecipePropType, int>();\n        }\n\n        public int RecipeID { get; set; }\n        public List<RecipeItemInfo> TargetItems { get; private set; }\n        public List<RecipeItemInfo> RecipeItems { get; private set; }\n        public Dictionary<RecipePropType, int> Props { get; private set; }\n\n        public int MainTargetItemID\n        {\n            get\n            {\n                if (this.TargetItems.Count > 0)\n                {\n                    return this.TargetItems[0].ItemID;\n                }\n                return 0;\n            }\n        }\n\n        public static Recipe CreateFromNode(Wz_Node node)\n        {\n            Recipe recipe = new Recipe();\n            int recipeID;\n            if (!Int32.TryParse(node.Text, out recipeID))\n                return null;\n            recipe.RecipeID = recipeID;\n\n            foreach (Wz_Node subNode in node.Nodes)\n            {\n                switch (subNode.Text)\n                {\n                    case \"target\":\n                        for (int i = 0; ; i++)\n                        {\n                            Wz_Node itemNode = subNode.FindNodeByPath(i.ToString());\n                            if (itemNode == null)\n                            {\n                                break;\n                            }\n\n                            RecipeItemInfo itemInfo = new RecipeItemInfo();\n                            foreach (var itemPropNode in itemNode.Nodes)\n                            {\n                                switch (itemPropNode.Text)\n                                {\n                                    case \"item\":\n                                        itemInfo.ItemID = itemPropNode.GetValue<int>();\n                                        break;\n                                    case \"count\":\n                                        itemInfo.Count = itemPropNode.GetValue<int>();\n                                        break;\n                                    case \"probWeight\":\n                                        itemInfo.ProbWeight = itemPropNode.GetValue<int>();\n                                        break;\n                                }\n                            }\n                            recipe.TargetItems.Add(itemInfo);\n                        }\n                        break;\n\n                    case \"recipe\":\n                        for (int i = 0; ; i++)\n                        {\n                            Wz_Node itemNode = subNode.FindNodeByPath(i.ToString());\n                            if (itemNode == null)\n                            {\n                                break;\n                            }\n                            RecipeItemInfo itemInfo = new RecipeItemInfo();\n                            foreach (var itemPropNode in itemNode.Nodes)\n                            {\n                                switch (itemPropNode.Text)\n                                {\n                                    case \"item\":\n                                        itemInfo.ItemID = itemPropNode.GetValue<int>();\n                                        break;\n                                    case \"count\":\n                                        itemInfo.Count = itemPropNode.GetValue<int>();\n                                        break;\n                                }\n                            }\n                            recipe.RecipeItems.Add(itemInfo);\n                        }\n                        break;\n\n                    default:\n                        RecipePropType type;\n                        if (Enum.TryParse(subNode.Text, out type))\n                        {\n                            try\n                            {\n                                recipe.Props.Add(type, Convert.ToInt32(subNode.Value));\n                            }\n                            finally\n                            {\n                            }\n                        }\n                        break;\n                }\n            }\n\n            return recipe;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/RecipeItemInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class RecipeItemInfo\n    {\n        public RecipeItemInfo()\n        {\n        }\n\n        public int ItemID { get; set; }\n        public int Count { get; set; }\n        public int ProbWeight { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/RecipePropType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public enum RecipePropType\n    {\n        reqSkillLevel = 1,\n        reqSkillProficiency,\n        coolTimeSec,\n        intFatigability,\n        incSkillMasterProficiency,\n        probMod,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SetItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SetItem\n    {\n        public SetItem()\n        {\n            ItemIDs = new SetItemIDList();\n            Effects = new Dictionary<int, SetItemEffect>();\n        }\n\n        public int SetItemID { get; set; }\n        public int CompleteCount { get; set; }\n        public int currentCount;\n        public bool Parts { get; set; }\n        public bool ExpandToolTip { get; set; }\n        public SetItemIDList ItemIDs { get; private set; }\n        public string SetItemName { get; set; }\n        public Dictionary<int, SetItemEffect> Effects { get; private set; }\n\n        public static SetItem CreateFromNode(Wz_Node setItemNode, Wz_Node optionNode)\n        {\n            if (setItemNode == null)\n                return null;\n\n            SetItem setItem = new SetItem();\n            int setItemID;\n            if (int.TryParse(setItemNode.Text, out setItemID))\n            {\n                setItem.SetItemID = setItemID;\n            }\n\n            Dictionary<string, string> desc = new Dictionary<string, string>();\n\n            foreach (Wz_Node subNode in setItemNode.Nodes)\n            {\n                switch (subNode.Text)\n                {\n                    case \"setItemName\":\n                        setItem.SetItemName = Convert.ToString(subNode.Value);\n                        break;\n                    case \"completeCount\":\n                        setItem.CompleteCount = Convert.ToInt32(subNode.Value);\n                        break;\n                    case \"parts\":\n                        setItem.Parts = subNode.GetValue<int>() != 0;\n                        break;\n                    case \"expandToolTip\":\n                        setItem.ExpandToolTip = subNode.GetValue<int>() != 0;\n                        break;\n                    case \"ItemID\":\n                        foreach (Wz_Node itemNode in subNode.Nodes)\n                        {\n                            int idx = Convert.ToInt32(itemNode.Text);\n                            if (itemNode.Nodes.Count == 0)\n                            {\n                                int itemID = Convert.ToInt32(itemNode.Value);\n                                setItem.ItemIDs.Add(idx, new SetItemIDPart(itemID));\n                            }\n                            else\n                            {\n                                SetItemIDPart part = new SetItemIDPart();\n                                foreach (Wz_Node itemNode2 in itemNode.Nodes)\n                                {\n                                    switch (itemNode2.Text)\n                                    {\n                                        case \"representName\":\n                                            part.RepresentName = Convert.ToString(itemNode2.Value);\n                                            break;\n                                        case \"typeName\":\n                                            part.TypeName = Convert.ToString(itemNode2.Value);\n                                            break;\n                                        default:\n                                            if (Int32.TryParse(itemNode2.Text, out int index) && index >= 0) // the key can start from 0\n                                            {\n                                                part.ItemIDs[Convert.ToInt32(itemNode2.Value)] = false;\n                                            }\n                                            break;\n                                    }\n                                }\n                                setItem.ItemIDs.Add(idx, part);\n                            }\n                        }\n                        break;\n                    case \"Effect\":\n                        foreach (Wz_Node effectNode in subNode.Nodes)\n                        {\n                            int count = Convert.ToInt32(effectNode.Text);\n                            SetItemEffect effect = new SetItemEffect();\n                            foreach (Wz_Node propNode in effectNode.Nodes)\n                            {\n                                switch (propNode.Text)\n                                {\n                                    case \"Option\":\n                                        if (optionNode != null)\n                                        {\n                                            List<Potential> potens = new List<Potential>();\n                                            foreach (Wz_Node pNode in propNode.Nodes)\n                                            {\n                                                string optText = Convert.ToString(pNode.FindNodeByPath(\"option\").Value).PadLeft(6, '0');\n                                                Wz_Node opn = optionNode.FindNodeByPath(optText);\n                                                if (opn == null)\n                                                    continue;\n                                                Potential p = Potential.CreateFromNode(opn, Convert.ToInt32(pNode.FindNodeByPath(\"level\").Value));\n                                                if (p != null)\n                                                {\n                                                    potens.Add(p);\n                                                }\n                                            }\n                                            effect.Props.Add(GearPropType.Option, potens);\n                                        }\n                                        break;\n\n                                    case \"OptionToMob\":\n                                        List<SetItemOptionToMob> opToMobList = new List<SetItemOptionToMob>();\n                                        for (int i = 1; ; i++)\n                                        {\n                                            Wz_Node optNode = propNode.FindNodeByPath(i.ToString());\n                                            if (optNode == null)\n                                            {\n                                                break;\n                                            }\n\n                                            SetItemOptionToMob option = new SetItemOptionToMob();\n\n                                            foreach (Wz_Node pNode in optNode.Nodes)\n                                            {\n                                                switch (pNode.Text)\n                                                {\n                                                    case \"mob\":\n                                                        foreach (Wz_Node mobNode in pNode.Nodes)\n                                                        {\n                                                            option.Mobs.Add(mobNode.GetValue<int>());\n                                                        }\n                                                        break;\n\n                                                    case \"mobName\":\n                                                        option.MobName = pNode.GetValue<string>();\n                                                        break;\n\n                                                    default:\n                                                        {\n                                                            GearPropType type;\n                                                            if (Enum.TryParse(pNode.Text, out type))\n                                                            {\n                                                                option.Props.Add(type, pNode.GetValue<int>());\n                                                            }\n                                                        }\n                                                        break;\n                                                }\n                                            }\n\n                                            opToMobList.Add(option);\n                                        }\n                                        effect.Props.Add(GearPropType.OptionToMob, opToMobList);\n                                        break;\n\n                                    case \"activeSkill\":\n                                        List<SetItemActiveSkill> activeSkillList = new List<SetItemActiveSkill>();\n                                        for (int i = 0; ; i++)\n                                        {\n                                            Wz_Node optNode = propNode.FindNodeByPath(i.ToString());\n                                            if (optNode == null)\n                                            {\n                                                break;\n                                            }\n\n                                            SetItemActiveSkill activeSkill = new SetItemActiveSkill();\n                                            foreach (Wz_Node pNode in optNode.Nodes)\n                                            {\n                                                switch (pNode.Text)\n                                                {\n                                                    case \"id\":\n                                                        activeSkill.SkillID = pNode.GetValue<int>();\n                                                        break;\n\n                                                    case \"level\":\n                                                        activeSkill.Level= pNode.GetValue<int>();\n                                                        break;\n                                                }\n                                            }\n                                            activeSkillList.Add(activeSkill);\n                                        }\n                                        effect.Props.Add(GearPropType.activeSkill, activeSkillList);\n                                        break;\n\n                                    case \"bonusByTime\":\n                                        var bonusByTimeList = new List<SetItemBonusByTime>();\n                                        for (int i = 0; ; i++)\n                                        {\n                                            Wz_Node optNode = propNode.FindNodeByPath(i.ToString());\n                                            if (optNode == null)\n                                            {\n                                                break;\n                                            }\n\n                                            var bonusByTime = new SetItemBonusByTime();\n                                            foreach (Wz_Node pNode in optNode.Nodes)\n                                            {\n                                                switch (pNode.Text)\n                                                {\n                                                    case \"termStart\":\n                                                        bonusByTime.TermStart = pNode.GetValue<int>();\n                                                        break;\n\n                                                    default:\n                                                        {\n                                                            GearPropType type;\n                                                            if (Enum.TryParse(pNode.Text, out type))\n                                                            {\n                                                                bonusByTime.Props.Add(type, pNode.GetValue<int>());\n                                                            }\n                                                        }\n                                                        break;\n                                                }\n                                            }\n                                            bonusByTimeList.Add(bonusByTime);\n                                        }\n                                        effect.Props.Add(GearPropType.bonusByTime, bonusByTimeList);\n                                        break;\n\n                                    default:\n                                        {\n                                            GearPropType type;\n                                            if (Enum.TryParse(propNode.Text, out type))\n                                            {\n                                                effect.Props.Add(type, Convert.ToInt32(propNode.Value));\n                                            }\n                                        }\n                                        break;\n                                }\n                            }\n                            setItem.Effects.Add(count, effect);\n                        }\n                        break;\n                    case \"Desc\":\n                        foreach (var descNode in subNode.Nodes)\n                        {\n                            desc[descNode.Text] = Convert.ToString(descNode.Value);\n                        }\n                        break;\n                }\n            }\n\n            //处理额外分组\n            if (desc.Count > 0)\n            {\n                foreach (var kv in desc)\n                {\n                    SetItemIDPart combinePart = null;\n                    string combineTypeName = null;\n                    switch (kv.Key)\n                    {\n                        case \"weapon\":\n                            combinePart = CombinePart(setItem, gearID => Gear.IsWeapon(Gear.GetGearType(gearID)));\n                            combineTypeName = ItemStringHelper.GetSetItemGearTypeString(GearType.weapon);\n                            break;\n\n                        case \"subweapon\":\n                            combinePart = CombinePart(setItem, gearID => Gear.IsSubWeapon(Gear.GetGearType(gearID)));\n                            combineTypeName = ItemStringHelper.GetSetItemGearTypeString(GearType.subWeapon);\n                            break;\n\n                        case \"pocket\":\n                            combinePart = CombinePart(setItem, gearID => Gear.GetGearType(gearID) == GearType.pocket);\n                            combineTypeName = ItemStringHelper.GetSetItemGearTypeString(GearType.pocket);\n                            break;\n\n                        case \"emblem\":\n                            combinePart = CombinePart(setItem, gearID => Gear.GetGearType(gearID) == GearType.emblem);\n                            combineTypeName = ItemStringHelper.GetSetItemGearTypeString(GearType.emblem);\n                            break;\n                    }\n\n                    if (combinePart != null)\n                    {\n                        combinePart.RepresentName = kv.Value;\n                        combinePart.TypeName = combineTypeName; ItemStringHelper.GetSetItemGearTypeString(GearType.weapon);\n                    }\n                }\n            }\n\n          \n            return setItem;\n        }\n\n        /// <summary>\n        /// 按一定条件合并装备部件的分组。\n        /// </summary>\n        /// <param name=\"predicate\">装备id符合条件的判断方法。</param>\n        /// <returns></returns>\n        private static SetItemIDPart CombinePart(SetItem setItem, Predicate<int> predicate)\n        {\n            List<int> itemIDList = new List<int>();\n            List<int> preRemovedPartIdx = new List<int>();\n            int? idx = null;\n            foreach (var part in setItem.ItemIDs.Parts)\n            {\n                bool add = false;\n                foreach (var itemID in part.Value.ItemIDs.Keys)\n                {\n                    if (predicate(itemID)) //id满足条件\n                    {\n                        itemIDList.Add(itemID);\n                        add = true;\n                    }\n                }\n                \n                if (add) //提取出被合并项的最大partID\n                {\n                    //idx = idx == null ? part.Key : Math.Max(part.Key, idx.Value);\n                    if (!preRemovedPartIdx.Contains(part.Key))\n                        preRemovedPartIdx.Add(part.Key);\n                }\n\n                idx = idx == null ? part.Key : Math.Max(part.Key, idx.Value);\n            }\n            if (itemIDList.Count > 0)\n            {\n                SetItemIDPart part = new SetItemIDPart(itemIDList);\n                foreach (int i in preRemovedPartIdx)\n                {\n                    setItem.ItemIDs.Remove(i);\n                }\n                setItem.ItemIDs.Add(idx.Value + 1, part);\n                return part;\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SetItemActiveSkill.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SetItemActiveSkill\n    {\n        public SetItemActiveSkill()\n        {\n        }\n\n        public int SkillID { get; set; }\n        public int Level { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SetItemBonusByTime.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SetItemBonusByTime\n    {\n        public SetItemBonusByTime()\n        {\n            this.Props = new Dictionary<GearPropType, int>();\n        }\n\n        public int TermStart { get; set; }\n        public Dictionary<GearPropType, int> Props { get; private set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SetItemEffect.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SetItemEffect\n    {\n        public SetItemEffect()\n        {\n            props = new SortedDictionary<GearPropType, object>();\n            enabled = false;\n        }\n        private SortedDictionary<GearPropType, object> props;\n        private bool enabled;\n\n        public SortedDictionary<GearPropType, object> Props\n        {\n            get { return props; }\n        }\n\n        public IEnumerable<KeyValuePair<GearPropType, object>> PropsV5\n        {\n            get { return props.Where(kv => Gear.IsV5SupportPropType(kv.Key)); }\n        }\n\n        public bool Enabled\n        {\n            get { return enabled; }\n            set { enabled = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SetItemIDList.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SetItemIDList\n    {\n        public SetItemIDList()\n        {\n            this.Parts = new List<KeyValuePair<int, SetItemIDPart>>();\n        }\n\n        public List<KeyValuePair<int, SetItemIDPart>> Parts {get;private set;}\n\n        public void Add(int partID, SetItemIDPart part)\n        {\n            this.Parts.Add(new KeyValuePair<int, SetItemIDPart>(partID, part));\n        }\n\n        public void Remove(int partID)\n        {\n            this.Parts.RemoveAll(kv => kv.Key == partID);\n        }\n\n        /// <summary>\n        /// 获取或设置装备是否有效。\n        /// </summary>\n        /// <param Name=\"ItemID\">装备ID。</param>\n        /// <returns></returns>\n        public bool this[int itemID]\n        {\n            get\n            {\n                foreach (var kv in Parts)\n                {\n                    bool enabled;\n                    kv.Value.ItemIDs.TryGetValue(itemID, out enabled);\n                    if (enabled)\n                        return true;\n                }\n                return false;\n            }\n            set\n            {\n                foreach (var kv in Parts)\n                {\n                    bool enabled;\n                    if (kv.Value.ItemIDs.TryGetValue(itemID, out enabled) && (enabled ^ value))\n                    {\n                        kv.Value.ItemIDs[itemID] = value;\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SetItemIDPart.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SetItemIDPart\n    {\n        public SetItemIDPart()\n        {\n            itemIDs = new Dictionary<int, bool>();\n        }\n\n        /// <summary>\n        /// 通过一件装备ID初始化SetItemIDPart的实例。\n        /// </summary>\n        /// <param Name=\"ItemID\">要初始化的装备ID。</param>\n        public SetItemIDPart(int itemID)\n            : this()\n        {\n            itemIDs[itemID] = false;\n        }\n\n        /// <summary>\n        /// 通过一个装备ID集合初始化SetItemIDPart的实例。\n        /// </summary>\n        /// <param Name=\"itemIDList\">要初始化的装备ID集合。</param>\n        public SetItemIDPart(IEnumerable<int> itemIDList)\n            : this()\n        {\n            foreach (int itemID in itemIDList)\n            {\n                itemIDs[itemID] = false;\n            }\n        }\n\n        private Dictionary<int, bool> itemIDs;\n        private string representName;\n        private string typeName;\n\n        public Dictionary<int, bool> ItemIDs\n        {\n            get { return itemIDs; }\n        }\n\n        /// <summary>\n        /// 获取一个值，它表示是否当前的装备ID中，至少有一个是生效的。\n        /// </summary>\n        public bool Enabled\n        {\n            get\n            {\n                foreach (var kv in itemIDs)\n                {\n                    if (kv.Value)\n                        return true;\n                }\n                return false;\n            }\n        }\n\n        /// <summary>\n        /// 获取或设置套装部件的显示名称。\n        /// </summary>\n        public string RepresentName\n        {\n            get { return representName; }\n            set { representName = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置套装部件的类型显示名称。\n        /// </summary>\n        public string TypeName\n        {\n            get { return typeName; }\n            set { typeName = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SetItemOptionToMob.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SetItemOptionToMob\n    {\n        public SetItemOptionToMob()\n        {\n            this.Mobs = new List<int>();\n            this.Props = new Dictionary<GearPropType, int>();\n        }\n\n        public List<int> Mobs { get; private set; }\n        public string MobName { get; set; }\n        public Dictionary<GearPropType, int> Props { get; private set; }\n\n        public string ConvertSummary()\n        {\n            StringBuilder sb = new StringBuilder();\n\n            string mobStr = null;\n            if (MobName != null)\n            {\n                mobStr = MobName;\n            }\n            else if (Mobs.Count > 0)\n            {\n                mobStr = Mobs[0].ToString();\n            }\n            sb.AppendFormat(\"攻击{0}时，\", mobStr);\n\n            foreach (var kv in this.Props)\n            {\n                if (kv.Key == GearPropType.damR)\n                {\n                    sb.AppendFormat(\"伤害增加{0}%，\", kv.Value);\n                }\n                else\n                {\n                    sb.Append(ItemStringHelper.GetGearPropString(kv.Key, kv.Value));\n                }\n            }\n\n            return sb.ToString(0, sb.Length - 1);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/Skill.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class Skill\n    {\n        public Skill()\n        {\n            this.level = 0;\n            this.levelCommon = new List<Dictionary<string, string>>();\n            this.common = new Dictionary<string, string>();\n            this.PVPcommon = new Dictionary<string, string>();\n            this.ReqSkill = new Dictionary<int, int>();\n            this.Action = new List<string>();\n            this.PerJobAttackInfo = new Dictionary<int, Dictionary<string, string>>();\n            this.perJobIndex = 0;\n        }\n\n        private int level;\n        private int perJobIndex;\n        internal List<Dictionary<string, string>> levelCommon;\n        internal Dictionary<string, string> common;\n\n        public Dictionary<string, string> Common\n        {\n            get\n            {\n                if (PreBBSkill && this.level > 0 && this.level <= levelCommon.Count)\n                    return levelCommon[this.level - 1];\n                else\n                    return common;\n            }\n        }\n\n        public Dictionary<string, string> PVPcommon { get; private set; }\n        public int SkillID { get; set; }\n        public BitmapOrigin Icon { get; set; }\n        public BitmapOrigin IconMouseOver { get; set; }\n        public BitmapOrigin IconDisabled { get; set; }\n\n        public HyperSkillType Hyper { get; set; }\n\n        public int Level\n        {\n            get { return level; }\n            set\n            {\n                bool canBreakLevel = this.CombatOrders || this.VSkill\n                    || this.SkillID / 100000 == 4000; //fix for evan\n                int maxLevel = canBreakLevel ? 100 : this.MaxLevel;\n                level = Math.Max(0, Math.Min(value, maxLevel));\n            }\n        }\n\n        public int PerJobIndex\n        {\n            get { return perJobIndex; }\n            set\n            {\n                perJobIndex = Math.Max(0, Math.Min(value, this.PerJobAttackInfo.Count - 1));\n            }\n        }\n\n        public int ReqLevel { get; set; }\n        public int ReqAmount { get; set; }\n        public bool PreBBSkill { get; set; }\n        public bool Invisible { get; set; }\n        public bool CombatOrders { get; set; }\n        public bool NotRemoved { get; set; }\n        public bool VSkill { get; set; }\n        public bool Origin { get; set; }\n        public int MasterLevel { get; set; }\n        public Dictionary<int, int> ReqSkill { get; private set; }\n        public List<string> Action { get; private set; }\n        public Dictionary<int, Dictionary<string, string>> PerJobAttackInfo { get; private set; }\n\n        public int MaxLevel\n        {\n            get\n            {\n                string v;\n                if (this.PreBBSkill)\n                    return levelCommon.Count;\n                else if (common.TryGetValue(\"maxLevel\", out v))\n                    return Convert.ToInt32(v);\n                return 0;\n            }\n        }\n\n        public static Skill CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            Skill skill = new Skill();\n            int skillID;\n            if (!Int32.TryParse(node.Text, out skillID))\n                return null;\n            skill.SkillID = skillID;\n\n            foreach (Wz_Node childNode in node.Nodes)\n            {\n                switch (childNode.Text)\n                {\n                    case \"icon\":\n                        skill.Icon = BitmapOrigin.CreateFromNode(childNode, findNode);\n                        break;\n                    case \"iconMouseOver\":\n                        skill.IconMouseOver = BitmapOrigin.CreateFromNode(childNode, findNode);\n                        break;\n                    case \"iconDisabled\":\n                        skill.IconDisabled = BitmapOrigin.CreateFromNode(childNode, findNode);\n                        break;\n                    case \"common\":\n                        foreach (Wz_Node commonNode in childNode.Nodes)\n                        {\n                            if (commonNode.Value != null && !(commonNode.Value is Wz_Vector))\n                            {\n                                skill.common[commonNode.Text] = commonNode.Value.ToString();\n                            }\n                            else if (commonNode.Text == \"attackInfo\")\n                            {\n                                if (commonNode.Nodes.Count > 0)\n                                {\n                                    foreach (Wz_Node jobNode in commonNode.Nodes)\n                                    {\n                                        int jobID;\n                                        if (Int32.TryParse(jobNode.Text, out jobID))\n                                        {\n                                            Dictionary<string, string> attackInfo = new Dictionary<string, string>();\n                                            foreach (Wz_Node infoNode in jobNode.Nodes)\n                                            {\n                                                attackInfo[infoNode.Text] = infoNode.Value.ToString();\n                                            }\n                                            skill.PerJobAttackInfo[jobID] = attackInfo;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                        break;\n                    case \"PVPcommon\":\n                        foreach (Wz_Node commonNode in childNode.Nodes)\n                        {\n                            if (commonNode.Value != null && !(commonNode.Value is Wz_Vector))\n                            {\n                                skill.PVPcommon[commonNode.Text] = commonNode.Value.ToString();\n                            }\n                        }\n                        break;\n                    case \"level\":\n                        for (int i = 1; ; i++)\n                        {\n                            Wz_Node levelNode = childNode.FindNodeByPath(i.ToString());\n                            if (levelNode == null)\n                                break;\n                            Dictionary<string, string> levelInfo = new Dictionary<string, string>();\n\n                            foreach (Wz_Node commonNode in levelNode.Nodes)\n                            {\n                                if (commonNode.Value != null && !(commonNode.Value is Wz_Vector))\n                                {\n                                    levelInfo[commonNode.Text] = commonNode.Value.ToString();\n                                }\n                            }\n\n                            skill.levelCommon.Add(levelInfo);\n                        }\n                        break;\n                    case \"hyper\":\n                        skill.Hyper = (HyperSkillType)childNode.GetValue<int>();\n                        break;\n                    case \"invisible\":\n                        skill.Invisible = childNode.GetValue<int>() != 0;\n                        break;\n                    case \"combatOrders\":\n                        skill.CombatOrders = childNode.GetValue<int>() != 0;\n                        break;\n                    case \"notRemoved\":\n                        skill.NotRemoved = childNode.GetValue<int>() != 0;\n                        break;\n                    case \"vSkill\":\n                        skill.VSkill = childNode.GetValue<int>() != 0;\n                        break;\n                    case \"origin\":\n                        skill.Origin = childNode.GetValue<int>() != 0;\n                        break;\n                    case \"masterLevel\":\n                        skill.MasterLevel = childNode.GetValue<int>();\n                        break;\n                    case \"reqLev\":\n                        skill.ReqLevel = childNode.GetValue<int>();\n                        break;\n                    case \"req\":\n                        foreach (Wz_Node reqNode in childNode.Nodes)\n                        {\n                            if (reqNode.Text == \"level\")\n                            {\n                                skill.ReqLevel = reqNode.GetValue<int>();\n                            }\n                            else if (reqNode.Text == \"reqAmount\")\n                            {\n                                skill.ReqAmount = reqNode.GetValue<int>();\n                            }\n                            else\n                            {\n                                int reqSkill;\n                                if (Int32.TryParse(reqNode.Text, out reqSkill))\n                                {\n                                    skill.ReqSkill[reqSkill] = reqNode.GetValue<int>();\n                                }\n                            }\n                        }\n                        break;\n                    case \"action\":\n                        for (int i = 0; ; i++)\n                        {\n                            Wz_Node idxNode = childNode.FindNodeByPath(i.ToString());\n                            if (idxNode == null)\n                                break;\n                            skill.Action.Add(idxNode.GetValue<string>());\n                        }\n                        break;\n                }\n            }\n\n            //判定技能声明版本\n            skill.PreBBSkill = false;\n            if (skill.levelCommon.Count > 0)\n            {\n                if (skill.common.Count <= 0 || skill.common.ContainsKey(\"maxLevel\"))\n                {\n                    skill.PreBBSkill = true;\n                }\n            }\n\n            return skill;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SummaryParams.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.CharaSim\n{\n    public struct SummaryParams\n    {\n        private string r;\n        private string n;\n        private string cStart;\n        private string cEnd;\n        private string gStart;\n        private string gEnd;\n\n        /// <summary>\n        /// 获取或设置回车符(\\r)的替换字符串。\n        /// </summary>\n        public string R\n        {\n            get { return r; }\n            set { r = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置换行符(\\n)的替换字符串。\n        /// </summary>\n        public string N\n        {\n            get { return n; }\n            set { n = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置高亮起始符(#c)的替换字符串。\n        /// </summary>\n        public string CStart\n        {\n            get { return cStart; }\n            set { cStart = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置高亮结束符(#)的替换字符串\n        /// </summary>\n        public string CEnd\n        {\n            get { return cEnd; }\n            set { cEnd = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置自定义高亮起始符(#g)的替换字符串。\n        /// </summary>\n        public string GStart\n        {\n            get { return gStart; }\n            set { gStart = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置自定义高亮结束符(#)的替换字符串\n        /// </summary>\n        public string GEnd\n        {\n            get { return gEnd; }\n            set { gEnd = value; }\n        }\n\n        /// <summary>\n        /// 获取默认的替换字符串组合。\n        /// </summary>\n        public static SummaryParams Default\n        {\n            get\n            {\n                return new SummaryParams()\n                {\n                    R = @\"\\r\",\n                    N = @\"\\n\",\n                    cStart = @\"#c\",\n                    cEnd = @\"#\",\n                    gStart = @\"#$g\",\n                    gEnd = @\"#\"\n                };\n            }\n        }\n\n        public static SummaryParams Text\n        {\n            get\n            {\n                return new SummaryParams()\n                {\n                    R = \"\\r\",\n                    N = \"\\n\",\n                    cStart = @\"#c\",\n                    cEnd = @\"#\",\n                    gStart = @\"#$g\",\n                    gEnd = @\"#\"\n                };\n            }\n        }\n\n        public static SummaryParams Html\n        {\n            get\n            {\n                return new SummaryParams()\n                {\n                    R = null,\n                    N = \"<br />\",\n                    cStart = @\"<span style=\"\"font-weight:bold; color:orange;\"\">\",\n                    cEnd = @\"</span>\",\n                    gStart = @\"<span style=\"\"font-weight:bold; color:#3f0;\"\">\",\n                    gEnd = @\"</span>\"\n                };\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/CharaSim/SummaryParser.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Runtime.CompilerServices;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.Common;\n\nnamespace WzComparerR2.CharaSim\n{\n    public class SummaryParser\n    {\n        static SummaryParser()\n        {\n            GlobalVariableMapping = new Dictionary<string, string>();\n            GlobalVariableMapping[\"comboConAran\"] = \"aranComboCon\";\n        }\n\n        public static string GetSkillSummary(string H, int Level, Dictionary<string, string> CommonProps, SummaryParams param, SkillSummaryOptions options = default)\n        {\n            if (H == null) return null;\n\n            int idx = 0;\n            StringBuilder sb = new StringBuilder();\n            bool beginC = false;\n            while (idx < H.Length)\n            {\n                if (H[idx] == '#')\n                {\n                    int end = idx, len = 0;\n                    while ((++end) < H.Length)\n                    {\n                        if (H[end] == '_' ||\n                            ('a' <= H[end] && H[end] <= 'z') ||\n                            ('A' <= H[end] && H[end] <= 'Z') ||\n                            (end - idx > 1 && '0' <= H[end] && H[end] <= '9')) //^[_A-Za-z][_A-Za-z0-9]*$\n                        {\n                            len++;\n                        }\n                        else\n                        {\n                            break;\n                        }\n                    }\n                    //优先匹配common\n                    string prop = null;\n                    string propKey = null;\n                    if (CommonProps != null)\n                    {\n                        for (int i = len; i > 0; i--)\n                        {\n                            propKey = H.Substring(idx + 1, i);\n                            if (GetValueIgnoreCase(CommonProps, propKey, out prop))\n                            {\n                                len = i;\n                                break;\n                            }\n                        }\n                    }\n                    if (prop != null)\n                    {\n                        try\n                        {\n                            decimal val = Calculator.Parse(prop, Level);\n                            if (options.ConvertCooltimeMS && propKey == \"cooltimeMS\")\n                            {\n                                sb.AppendFormat(\"{0:f2}\", val / 1000);\n                            }\n                            else if (options.ConvertPerM && propKey.EndsWith(\"PerM\", StringComparison.Ordinal))\n                            {\n                                sb.AppendFormat(\"{0:f1}\", val / 100);\n                            }\n                            else\n                            {\n                                sb.Append(val);\n                            }\n                        }\n                        catch\n                        {\n                            if (options.IgnoreEvalError)\n                            {\n                                sb.Append(\"NaN\");\n                            }\n                            else\n                            {\n                                throw;\n                            }\n                        }\n\n                        idx += len + 1;\n                        continue;\n                    }\n                    else //试图匹配全局变量\n                    {\n                        string key = null;\n                        for (int i = len; i > 0; i--)\n                        {\n                            key = H.Substring(idx + 1, i);\n                            if (GlobalVariableMapping.TryGetValue(key, out prop))\n                            {\n                                break;\n                            }\n                        }\n                        if (prop != null)\n                        {\n                            if (prop != \"\" && GetValueIgnoreCase(CommonProps, prop, out prop))\n                            {\n                                try\n                                {\n                                    decimal val = Calculator.Parse(prop, Level);\n                                    sb.Append(val);\n                                }\n                                catch\n                                {\n                                    if (options.IgnoreEvalError)\n                                    {\n                                        sb.Append(\"NaN\");\n                                    }\n                                    else\n                                    {\n                                        throw;\n                                    }\n                                }\n                            }\n                            else\n                            {\n                                sb.Append(param.GStart).Append(\"[\").Append(key).Append(\"]\").Append(param.GEnd);\n                            }\n                            idx += len + 1;\n                            continue;\n                        }\n                    }\n                    //匹配#c...#段落\n                    if (beginC)\n                    {\n                        beginC = false;\n                        sb.Append(param.CEnd);\n                        idx++;\n                    }\n                    else if (idx + 1 < H.Length && H[idx + 1] == 'c')\n                    {\n                        beginC = true;\n                        sb.Append(param.CStart);\n                        idx += 2;\n                    }\n                    else if (idx + 1 < H.Length && len == 0)//匹配省略c的段落\n                    {\n                        beginC = true;\n                        sb.Append(param.CStart);\n                        idx++;\n                    }\n                    else if (len > 0)//无法匹配 取最长的common段\n                    {\n                        string key = H.Substring(idx + 1, len);\n                        if (Regex.IsMatch(key, @\"^\\d+$\"))\n                        {\n                            sb.Append(key);\n                        }\n                        else\n                        {\n                            sb.Append(0);//默认值\n                        }\n                        idx += len + 1;\n                    }\n                    else // skip last #\n                    {\n                        idx++;\n                    }\n                }\n                else if (H[idx] == '\\\\')\n                {\n                    if (idx + 1 < H.Length)\n                    {\n                        switch (H[idx + 1])\n                        {\n                            case 'c': break; // \\c忽略掉 原因不明\n                            case 'r': sb.Append(param.R); break;\n                            case 'n':\n                                if (beginC && options.EndColorOnNewLine)\n                                {\n                                    beginC = false;\n                                    sb.Append(param.CEnd);\n                                }\n                                sb.Append(param.N);\n                                break;\n                            case '\\\\': sb.Append('\\\\'); break;\n                            default: sb.Append(H[idx + 1]); break;\n                        }\n                        idx += 2;\n                    }\n                    else //转义失败\n                    {\n                        idx++;\n                    }\n                }\n                else\n                {\n                    sb.Append(H[idx++]);\n                }\n            }\n            return sb.ToString();\n        }\n\n        private static bool GetValueIgnoreCase(Dictionary<string,string> dict, string key, out string value)\n        {\n            //bool find = false;\n            foreach (var kv in dict)\n            {\n                if (kv.Key.Equals(key, StringComparison.OrdinalIgnoreCase))\n                {\n                    value = kv.Value;\n                    return true;\n                }\n            }\n            value = null;\n            return false;\n        }\n\n        public static string GetSkillSummary(Skill skill, StringResultSkill sr, SummaryParams param)\n        {\n            if (skill == null)\n                return null;\n            return GetSkillSummary(skill, skill.Level, sr, param);\n        }\n\n        public static string GetSkillSummary(Skill skill, int level, StringResultSkill sr, SummaryParams param, SkillSummaryOptions options = default, Dictionary<string, string> overrideSkillCommon = null)\n        {\n            if (skill == null || sr == null)\n                return null;\n\n            string h = null;\n            if (skill.PreBBSkill) //用level声明的技能\n            {\n                string hsSummary;\n                if (skill.Level == level && skill.Common.TryGetValue(\"hs\", out string hs)\n                    && (hsSummary = sr[hs]) != null) // fix for skill 170001005, 170011005\n                {\n                    h = hsSummary;\n                }\n                else if (sr.SkillH.Count >= level)\n                {\n                    h = sr.SkillH[level - 1];\n                }\n                else if (sr.SkillH.Count == 1)\n                {\n                    h = sr.SkillH[0];\n                }\n                var levelCommon = level <= skill.levelCommon.Count ? skill.levelCommon[level - 1] : skill.common;\n                return GetSkillSummary(h, level, levelCommon, param, options);\n            }\n            else\n            {\n                if (sr.SkillH.Count > 0)\n                {\n                    h = sr.SkillH[0];\n                }\n                if (sr.SkillExtraH.Count > 0)\n                {\n                    // SkillExtraH is always sorted\n                    foreach (var kv in sr.SkillExtraH)\n                    {\n                        if (level < kv.Key)\n                        {\n                            break;\n                        }\n                        h = kv.Value;\n                    }\n                }\n                return GetSkillSummary(h, level, overrideSkillCommon ?? skill.Common, param, options);\n            }\n        }\n\n        public static Dictionary<string,string> GlobalVariableMapping { get; private set; }\n    }\n\n    public struct SkillSummaryOptions\n    {\n        public bool ConvertCooltimeMS { get; set; }\n        public bool ConvertPerM { get; set; }\n        public bool IgnoreEvalError { get; set; }\n        public bool EndColorOnNewLine { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Config/ConfigArrayList.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class ConfigArrayList<T> : ConfigurationElementCollection, IEnumerable<T>\n    {\n        public T this[int index]\n        {\n            get { return ((ItemElement)base.BaseGet(index)).Value; }\n            set\n            {\n                base.BaseRemoveAt(index);\n                base.BaseAdd(index, new ItemElement() { Value = value });\n            }\n        }\n\n        public void Add(T item)\n        {\n            base.BaseAdd(new ItemElement() { Value = item }, false);\n        }\n\n        public void Insert(int index, T item)\n        {\n            base.BaseAdd(index, new ItemElement() { Value = item });\n        }\n\n        public void RemoveAt(int index)\n        {\n            base.BaseRemoveAt(index);\n        }\n\n        public bool Remove(T item)\n        {\n            int index = this.IndexOf(item);\n            if (index > -1)\n            {\n                base.BaseRemoveAt(index);\n                return true;\n            }\n            return false;\n        }\n\n        public int IndexOf(T item)\n        {\n            var elem = this.OfType<ItemElement>().FirstOrDefault(e => object.Equals(e.Value, item));\n            int index = elem == null ? -1 : base.BaseIndexOf(elem);\n            return index;\n        }\n\n        public void Clear()\n        {\n            base.BaseClear();\n        }\n\n        protected override ConfigurationElement CreateNewElement()\n        {\n            return new ItemElement();\n        }\n\n        protected override object GetElementKey(ConfigurationElement element)\n        {\n            return element;\n        }\n\n        public new IEnumerator<T> GetEnumerator()\n        {\n            return this.OfType<ItemElement>().Select(elem => elem.Value).GetEnumerator();\n        }\n\n        protected override string ElementName\n        {\n            get { return \"item\"; }\n        }\n\n        public class ItemElement : ConfigurationElement\n        {\n            public ItemElement()\n            {\n                this.Hash = Guid.NewGuid();\n            }\n\n            [ConfigurationProperty(\"hash\", IsRequired = true)]\n            public Guid Hash\n            {\n                get { return (Guid)this[\"hash\"]; }\n                set { this[\"hash\"] = value; }\n            }\n\n            [ConfigurationProperty(\"value\", IsRequired = true)]\n            public T Value\n            {\n                get { return (T)this[\"value\"]; }\n                set { this[\"value\"] = value; }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Config/ConfigItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class ConfigItem<T> : ConfigurationElement\n    {\n        [ConfigurationProperty(\"value\", IsRequired = true)]\n        public T Value\n        {\n            get { return (T)this[\"value\"]; }\n            set { this[\"value\"] = value; }\n        }\n\n        public static implicit operator T(ConfigItem<T> item)\n        {\n            return item.Value;\n        }\n\n        public static implicit operator ConfigItem<T>(T item)\n        {\n            return new ConfigItem<T>() { Value = item };\n        }\n\n        public override string ToString()\n        {\n            return nameof(ConfigItem<T>) + \" [\" + this.Value + \"]\";\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Config/ConfigItemCollectionBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\n\nnamespace WzComparerR2.Config\n{\n    public class ConfigItemCollectionBase<T> : ConfigurationElementCollection\n        where T : ConfigurationElement, new()\n    {\n        \n        public T this[int index]\n        {\n            get { return (T)base.BaseGet(index); }\n            set\n            {\n                base.BaseRemoveAt(index);\n                base.BaseAdd(index, value);\n            }\n        }\n\n        public void Add(T item)\n        {\n            base.BaseAdd(item, false);\n        }\n\n        public void Insert(int index, T item)\n        {\n            base.BaseAdd(index, item);\n        }\n\n        public void RemoveAt(int index)\n        {\n            base.BaseRemoveAt(index);\n        }\n\n        public void Remove(T item)\n        {\n            int index = base.BaseIndexOf(item);\n            if (index > -1)\n            {\n                base.BaseRemoveAt(index);\n            }\n        }\n\n        public void Clear()\n        {\n            base.BaseClear();\n        }\n\n        protected override ConfigurationElement CreateNewElement()\n        {\n            return new T();\n        }\n\n        protected override object GetElementKey(ConfigurationElement element)\n        {\n            return element;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Config/ConfigManager.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\nusing System.IO;\nusing System.Windows.Forms;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\n\nnamespace WzComparerR2.Config\n{\n    public static class ConfigManager\n    {\n        public static string ConfigFileName\n        {\n            get { return Path.Combine(Application.StartupPath, \"Setting.config\"); }\n        }\n\n        public static Configuration ConfigFile\n        {\n            get { return _configFile = (_configFile ?? Open()); }\n        }\n\n        private static Configuration _configFile;\n\n        private static Configuration Open()\n        {\n            var configFile = ConfigurationManager.OpenMappedMachineConfiguration(\n                new ConfigurationFileMap()\n                {\n                    MachineConfigFilename = ConfigFileName\n                });\n\n            return configFile;\n        }\n\n        public static void Reload()\n        {\n            _configFile = Open();\n        }\n\n        public static void Save()\n        {\n            _configFile?.Save(ConfigurationSaveMode.Full);\n        }\n\n        public static bool RegisterSection<T>() where T : ConfigSectionBase<T>, new()\n        {\n            return RegisterSection(typeof(T));\n        }\n\n        public static bool RegisterSection(Type type)\n        {\n            if (type == null || !type.IsSubclassOf(typeof(ConfigSectionBase<>).MakeGenericType(type)))\n            {\n                throw new ArgumentException($\"类型{type}没有继承于{typeof(ConfigSectionBase<>)}。\");\n            }\n\n            string secName = GetSectionName(type);\n          \n            if (ConfigFile.GetSection(secName) == null)\n            {\n                ConfigFile.Sections.Add(secName, Activator.CreateInstance(type) as ConfigurationSection);\n                var section = ConfigFile.GetSection(secName);\n                return true;\n            }\n            return false;\n        }\n\n        /// <summary>\n        /// 对此方法的调用不应为尾调用, 否则<see cref=\"Assembly.GetCallingAssembly\"/>会因尾调用优化出错.\n        /// </summary>\n        /// <seealso cref=\"https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getcallingassembly?redirectedfrom=MSDN&view=netcore-3.1#System_Reflection_Assembly_GetCallingAssembly\"/>\n        public static void RegisterAllSection()\n        {\n            var asm = Assembly.GetCallingAssembly();\n            RegisterAllSection(asm);\n        }\n\n       public static void RegisterAllSection(Assembly assembly)\n        {\n            var secTypes = assembly.GetExportedTypes().Where(type => {\n                try\n                {\n                    var baseType = type.BaseType;\n                    return baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(ConfigSectionBase<>);\n                }\n                catch\n                {\n                    return false;\n                }\n            });\n            bool needSave = false;\n            foreach (var type in secTypes)\n            {\n                needSave |= RegisterSection(type);\n            }\n\n            if (needSave)\n            {\n                Save();\n            }\n        }\n\n        public static string GetSectionName(Type type)\n        {\n            var attrList = type.GetCustomAttributes(typeof(SectionNameAttribute), false).OfType<SectionNameAttribute>();\n            return attrList.Select(attr => attr.Name).FirstOrDefault(secName => !string.IsNullOrEmpty(secName)) ?? type.Name;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Config/ConfigSectionBase.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Configuration;\nusing System.Reflection;\n\nnamespace WzComparerR2.Config\n{\n    public abstract class ConfigSectionBase<T> : ConfigurationSection\n        where T : ConfigSectionBase<T>, new()\n    {\n        public static T Default\n        {\n            get\n            {\n                string secName = ConfigManager.GetSectionName(typeof(T));\n                try\n                {\n                    return ConfigManager.ConfigFile.GetSection(secName) as T;\n                }\n                catch (ConfigurationErrorsException e)\n                {\n                    ConfigManager.Reload();\n                    return ConfigManager.ConfigFile.GetSection(secName) as T;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Config/SectionNameAttribute.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.Config\n{\n    public sealed class SectionNameAttribute : Attribute\n    {\n        public SectionNameAttribute(string sectionName)\n        {\n            this.Name = sectionName;\n        }\n\n        public string Name { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/AlphaForm.cs",
    "content": "﻿using System;\nusing System.Windows.Forms;\nusing System.Runtime.InteropServices;\nusing System.Drawing;\nusing System.Drawing.Imaging;\n\nnamespace WzComparerR2.Controls\n{\n    public class AlphaForm : PerPixelAlphaForm\n    {\n        public AlphaForm()\n        {\n            TopMost = true;\n            //TopLevel = false;\n            ShowInTaskbar = false;\n            Visible = true;\n            AutoSize = false;\n            MaximizeBox = false;\n            MinimizeBox = false;\n            HideOnHover = false;\n        }\n\n        private bool hideOnHover;\n        private Rectangle captionRectangle;\n        private Bitmap bitmap;\n\n        /// <summary>\n        /// 获取或设置一个值，表示窗体是否在鼠标滑过时自动隐藏。\n        /// </summary>\n        public bool HideOnHover\n        {\n            get { return hideOnHover; }\n            set { hideOnHover = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置一个Rectangle，表示窗体的虚拟标题栏区域。\n        /// </summary>\n        public Rectangle CaptionRectangle\n        {\n            get { return captionRectangle; }\n            set { captionRectangle = value; }\n        }\n\n        protected override void WndProc(ref Message m)\n        {\n            switch (m.Msg)\n            {\n                case 0x0084: /*WM_NCHITTEST*/\n                    if (!hideOnHover)\n                    {\n                        Point hitPoint = this.PointToClient(new Point((int)m.LParam));\n                        if (captionHitTest(hitPoint))\n                        {\n                            m.Result = (IntPtr)2; //HTCAPTION\n                            return;\n                        }\n                    }\n                    else\n                    {\n                        m.Msg = 0x0018; /*WM_SHOWWINDOW*/\n                        m.WParam = (IntPtr)0;\n                        break;\n                    }\n                    break;\n\n                case 0x00A5: /*WM_NCRBUTTONUP*/\n                    {\n                        Point hitPoint = this.PointToClient(new Point((int)m.LParam));\n                        this.OnMouseClick(new MouseEventArgs(System.Windows.Forms.MouseButtons.Right, 1, hitPoint.X, hitPoint.Y, 0));\n                    }\n                    return;\n\n                case 0x00A3: /*WM_NCLBUTTONDBLCLK*/ //防止双击最大化\n                    return;\n            }\n            \n            base.WndProc(ref m);\n        }\n\n        protected override void OnKeyDown(KeyEventArgs e)\n        {\n            base.OnKeyDown(e);\n        }\n\n        protected override void OnFormClosing(FormClosingEventArgs e)\n        {\n            if (e.CloseReason == System.Windows.Forms.CloseReason.UserClosing)\n            {\n                e.Cancel = true;\n                this.Hide();\n                return;\n            }\n            base.OnFormClosing(e);\n        }\n\n        protected virtual bool captionHitTest(Point point)\n        {\n            return this.captionRectangle.Contains(point);\n        }\n\n        public Bitmap Bitmap\n        {\n            get { return bitmap; }\n            set { bitmap = value; }\n        }\n    }\n\n    public class PerPixelAlphaForm : Form\n    {\n        public PerPixelAlphaForm()\n        {\n            // This form should not have A border or else Windows will clip it.\n            FormBorderStyle = FormBorderStyle.None;\n        }\n\n        /// <para>Changes the current Bitmap.</para>\n        public void SetBitmap(Bitmap bitmap)\n        {\n            SetBitmap(bitmap, 255);\n        }\n\n        /// <para>Changes the current Bitmap with A custom opacity Level.  Here is where all happens!</para>\n        public void SetBitmap(Bitmap bitmap, byte opacity)\n        {\n            if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)\n                throw new ApplicationException(\"The bitmap must be 32ppp with alpha-channel.\");\n\n            // The ideia of this is very simple,\n            // 1. Create A compatible DC with screen;\n            // 2. Select the Bitmap with 32bpp with alpha-channel in the compatible DC;\n            // 3. Call the UpdateLayeredWindow.\n\n            IntPtr screenDc = Win32.GetDC(IntPtr.Zero);\n            IntPtr memDc = Win32.CreateCompatibleDC(screenDc);\n            IntPtr hBitmap = IntPtr.Zero;\n            IntPtr oldBitmap = IntPtr.Zero;\n\n            try\n            {\n                hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));  // grab A GDI handle from this GDI+ Bitmap\n                oldBitmap = Win32.SelectObject(memDc, hBitmap);\n\n                Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);\n                Win32.Point pointSource = new Win32.Point(0, 0);\n                Win32.Point topPos = new Win32.Point(Left, Top);\n                Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();\n                blend.BlendOp = Win32.AC_SRC_OVER;\n                blend.BlendFlags = 0;\n                blend.SourceConstantAlpha = opacity;\n                blend.AlphaFormat = Win32.AC_SRC_ALPHA;\n\n                Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);\n                //if (this.AutoSize)\n                //  this.Size = bitmap.Size;\n            }\n            finally\n            {\n                Win32.ReleaseDC(IntPtr.Zero, screenDc);\n                if (hBitmap != IntPtr.Zero)\n                {\n                    Win32.SelectObject(memDc, oldBitmap);\n                    //Windows.DeleteObject(hBitmap); // The documentation says that we have to use the Windows.DeleteObject... but since there is no such method I use the Normal DeleteObject from Win32 GDI and it's working fine without any resource leak.\n                    Win32.DeleteObject(hBitmap);\n                }\n                Win32.DeleteDC(memDc);\n            }\n        }\n\n\n        protected override CreateParams CreateParams\n        {\n            get\n            {\n                CreateParams cp = base.CreateParams;\n                cp.ExStyle |= 0x00080000; // This form has to have the WS_EX_LAYERED extended style\n                return cp;\n            }\n        }\n    }\n\n    class Win32\n    {\n        public enum Bool\n        {\n            False = 0,\n            True\n        };\n\n        [StructLayout(LayoutKind.Sequential)]\n        public struct Point\n        {\n            public Int32 x;\n            public Int32 y;\n\n            public Point(Int32 x, Int32 y) { this.x = x; this.y = y; }\n        }\n\n        [StructLayout(LayoutKind.Sequential)]\n        public struct Size\n        {\n            public Int32 cx;\n            public Int32 cy;\n\n            public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; }\n        }\n\n        [StructLayout(LayoutKind.Sequential, Pack = 1)]\n        public struct BLENDFUNCTION\n        {\n            public byte BlendOp;\n            public byte BlendFlags;\n            public byte SourceConstantAlpha;\n            public byte AlphaFormat;\n        }\n\n        public const Int32 ULW_COLORKEY = 0x00000001;\n        public const Int32 ULW_ALPHA = 0x00000002;\n        public const Int32 ULW_OPAQUE = 0x00000004;\n\n        public const byte AC_SRC_OVER = 0x00;\n        public const byte AC_SRC_ALPHA = 0x01;\n\n        [DllImport(\"user32.dll\", ExactSpelling = true, SetLastError = true)]\n        public static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);\n\n        [DllImport(\"user32.dll\", ExactSpelling = true, SetLastError = true)]\n        public static extern IntPtr GetDC(IntPtr hWnd);\n\n        [DllImport(\"user32.dll\", ExactSpelling = true)]\n        public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);\n\n        [DllImport(\"gdi32.dll\", ExactSpelling = true, SetLastError = true)]\n        public static extern IntPtr CreateCompatibleDC(IntPtr hDC);\n\n        [DllImport(\"gdi32.dll\", ExactSpelling = true, SetLastError = true)]\n        public static extern Bool DeleteDC(IntPtr hdc);\n\n        [DllImport(\"gdi32.dll\", ExactSpelling = true)]\n        public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);\n\n        [DllImport(\"gdi32.dll\", ExactSpelling = true, SetLastError = true)]\n        public static extern Bool DeleteObject(IntPtr hObject);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/AnimationClipOptions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Controls\n{\n    public class AnimationClipOptions\n    {\n        public int? StartTime { get; set; }\n        public int? StopTime { get; set; }\n\n        public int? Left { get; set; }\n        public int? Top { get; set; }\n        public int? Right { get; set; }\n        public int? Bottom { get; set; }\n\n        public int? OutputWidth { get; set; }\n        public int? OutputHeight { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/AnimationControl.Designer.cs",
    "content": "﻿namespace WzComparerR2.Controls\n{\n    partial class AnimationControl\n    {\n        /// <summary> \n        /// 必需的设计器变量。\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary> \n        /// 清理所有正在使用的资源。\n        /// </summary>\n        /// <param name=\"disposing\">如果应释放托管资源，为 true；否则为 false。</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region 组件设计器生成的代码\n\n        /// <summary> \n        /// 设计器支持所需的方法 - 不要修改\n        /// 使用代码编辑器修改此方法的内容。\n        /// </summary>\n        private void InitializeComponent()\n        {\n            components = new System.ComponentModel.Container();\n        }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/AnimationControl.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Text;\nusing System.Diagnostics;\nusing System.Windows.Forms;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Animation;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.Controls\n{\n    public partial class AnimationControl : GraphicsDeviceControl\n    {\n        public AnimationControl()\n        {\n            InitializeComponent();\n            this.MouseDown += AnimationControl_MouseDown;\n            this.MouseUp += AnimationControl_MouseUp;\n            this.MouseMove += AnimationControl_MouseMove;\n            this.MouseWheel += AnimationControl_MouseWheel;\n\n            this.Items = new List<AnimationItem>();\n            this.MouseDragEnabled = true;\n            this.GlobalScale = 1f;\n\n            this.timer = new Timer();\n            timer.Interval = 30;\n            timer.Tick += Timer_Tick;\n            timer.Enabled = true;\n            this.sw = Stopwatch.StartNew();\n        }\n\n        public List<AnimationItem> Items { get; private set; }\n        public bool MouseDragEnabled { get; set; }\n        public bool MouseDragSaveEnabled { get; set; }\n        public bool ShowPositionGridOnDrag { get; set; }\n\n        public float GlobalScale\n        {\n            get { return this.globalScale; }\n            set { this.globalScale = MathHelper.Clamp(value, 0.1f, 10f); }\n        }\n\n        public bool IsPlaying\n        {\n            get { return this.timer.Enabled; }\n            set\n            {\n                if (value)\n                {\n                    this.lastUpdateTime = TimeSpan.Zero;\n                    this.sw.Restart();\n                }\n                this.timer.Enabled = value;\n            }\n        }\n\n        public int FrameInterval\n        {\n            get { return this.timer.Interval; }\n            set { this.timer.Interval = value; }\n        }\n\n        private float globalScale;\n        private Timer timer;\n        private Stopwatch sw;\n        private TimeSpan lastUpdateTime;\n\n        private SpriteBatchEx sprite;\n        private AnimationGraphics graphics;\n\n        //拖拽相关\n        private MouseDragContext mouseDragContext;\n\n        //离屏绘制相关\n\n        protected override void Initialize()\n        {\n            sprite = new SpriteBatchEx(this.GraphicsDevice);\n            graphics = new AnimationGraphics(this.GraphicsDevice, sprite);\n        }\n\n        protected virtual void Update(TimeSpan elapsed)\n        {\n            foreach (var animation in this.Items)\n            {\n                if (animation != null)\n                {\n                    animation.Update(elapsed);\n                }\n            }\n        }\n\n        public virtual void DrawBackground()\n        {\n            this.GraphicsDevice.Clear(this.BackColor.ToXnaColor());\n        }\n\n        protected override void Draw()\n        {\n            //绘制背景色\n            this.DrawBackground();\n            //绘制场景\n            Matrix mtViewport = Matrix.CreateTranslation(this.Padding.Left, this.Padding.Top, 0);\n            Matrix mtAnimation = Matrix.CreateScale(GlobalScale, GlobalScale, 1) * mtViewport;\n\n            foreach (var animation in this.Items)\n            {\n                if (animation != null)\n                {\n                    if (animation is FrameAnimator frameAni)\n                    {\n                        graphics.Draw(frameAni, mtAnimation);\n                    }\n                    else if (animation is ISpineAnimator spineAni)\n                    {\n                        graphics.Draw(spineAni, mtAnimation);\n                    }\n                }\n            }\n\n            //绘制辅助内容\n            if (ShowPositionGridOnDrag && this.mouseDragContext.IsDragging && this.mouseDragContext.DraggingItem != null)\n            {\n                var pos = this.mouseDragContext.DraggingItem.Position;\n                this.sprite.Begin(transformMatrix: mtViewport);\n                this.sprite.DrawLine(new Point(0, pos.Y), new Point(this.Width, pos.Y), 1, Color.Indigo);\n                this.sprite.DrawLine(new Point(pos.X, 0), new Point(pos.X, this.Height), 1, Color.Indigo);\n                this.sprite.End();\n            }\n        }\n\n        public virtual AnimationItem GetItemAt(int x, int y)\n        {\n            for(int i = this.Items.Count - 1; i >= 0; i--)\n            {\n                var item = this.Items[i];\n                var bound = item.Measure();\n                var rect = new Rectangle(\n                    (int)Math.Round(item.Position.X + bound.X* this.GlobalScale),\n                    (int)Math.Round(item.Position.Y + bound.Y * this.GlobalScale),\n                    (int)Math.Round(bound.Width * this.GlobalScale),\n                    (int)Math.Round(bound.Height * this.GlobalScale));\n                if (rect.Contains(x, y))\n                {\n                    return item;\n                }\n            }\n            return null;\n        }\n\n        #region EVENTS\n        protected virtual void OnItemDragSave(AnimationItemEventArgs e)\n        {\n\n        }\n\n\n        private void AnimationControl_MouseDown(object sender, MouseEventArgs e)\n        {\n            this.Focus();\n\n            if (this.MouseDragEnabled && e.Button == MouseButtons.Left)\n            {\n                var item = GetItemAt(e.X, e.Y);\n                if (item != null)\n                {\n                    this.mouseDragContext.IsDragging = true;\n                    this.mouseDragContext.MouseDownPoint = new Point(e.X, e.Y);\n                    this.mouseDragContext.DraggingItem = item;\n                    this.mouseDragContext.StartPosition = item.Position;\n                }\n            }\n            if ((Control.ModifierKeys & Keys.Control) != 0 && e.Button == MouseButtons.Middle)\n            {\n                this.GlobalScale = 1f;\n            }\n        }\n\n        private void AnimationControl_MouseUp(object sender, MouseEventArgs e)\n        {\n            if (this.MouseDragEnabled && e.Button == MouseButtons.Left)\n            {\n                this.mouseDragContext.IsDragging = false;\n            }\n        }\n\n        private void AnimationControl_MouseMove(object sender, MouseEventArgs e)\n        {\n            if (this.MouseDragEnabled && this.mouseDragContext.IsDragging && this.mouseDragContext.DraggingItem != null)\n            {\n                this.mouseDragContext.DraggingItem.Position = new Point(\n                    e.X - mouseDragContext.MouseDownPoint.X + mouseDragContext.StartPosition.X,\n                    e.Y - mouseDragContext.MouseDownPoint.Y + mouseDragContext.StartPosition.Y);\n\n                //处理拖拽保存\n                if (this.MouseDragSaveEnabled && (Control.ModifierKeys & Keys.Control) != 0)\n                {\n                    var dragSize = SystemInformation.DragSize;\n                    var dragBox = new Rectangle(mouseDragContext.MouseDownPoint, new Point(dragSize.Width, dragSize.Height));\n                    if (!dragBox.Contains(new Point(e.X, e.Y)))\n                    {\n                        var e2 = new AnimationItemEventArgs(this.mouseDragContext.DraggingItem);\n                        this.OnItemDragSave(e2);\n                        if (e2.Handled)\n                        {\n                            this.mouseDragContext.IsDragging = false;\n                        }\n                    }\n                }\n            }\n        }\n\n        private void AnimationControl_MouseWheel(object sender, MouseEventArgs e)\n        {\n            const int WHEEL_DELTA = 120;\n            if ((Control.ModifierKeys & Keys.Control) != 0)\n            {\n                float wheelTicks = e.Delta / WHEEL_DELTA;\n                float oldScale = this.GlobalScale;\n                float newScale = oldScale * (1 + 0.1f * wheelTicks);\n                if (oldScale.CompareTo(1f) * newScale.CompareTo(1f) == -1) // scaling cross 100%\n                {\n                    newScale = 1f;\n                }\n                this.GlobalScale = newScale;\n            }\n        }\n\n        private void Timer_Tick(object sender, EventArgs e)\n        {\n            var curTime = sw.Elapsed;\n            var elapsed = curTime - lastUpdateTime;\n            lastUpdateTime = curTime;\n\n            if (this.Visible)\n            {\n                this.Update(elapsed);\n                this.Invalidate();\n            }\n        }\n        #endregion\n\n        private struct MouseDragContext\n        {\n            public bool IsDragging;\n            public Point MouseDownPoint;\n            public Point StartPosition;\n            public AnimationItem DraggingItem;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/AnimationItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Controls\n{\n    public abstract class AnimationItem : ICloneable\n    {\n        public Point Position { get; set; }\n\n        public virtual int Length\n        {\n            get { return 0; }\n        }\n\n        public abstract void Update(TimeSpan elapsed);\n\n        public virtual Rectangle Measure()\n        {\n            return Rectangle.Empty;\n        }\n\n        public virtual void Reset()\n        {\n\n        }\n\n        public virtual object Clone()\n        {\n            var aniItem = (AnimationItem)base.MemberwiseClone();\n            aniItem.Reset();\n            return aniItem;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/AnimationItemEventArgs.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.Controls\n{\n    public class AnimationItemEventArgs\n    {\n        public AnimationItemEventArgs(AnimationItem item)\n        {\n            this.Item = item;\n        }\n\n        /// <summary>\n        /// 事件相关联的AnimationItem。\n        /// </summary>\n        public AnimationItem Item { get; private set; }\n        public bool Handled { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/AnimationRecoder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.Animation;\n\nnamespace WzComparerR2.Controls\n{\n    public class AnimationRecoder\n    {\n        public AnimationRecoder(GraphicsDevice graphicsDevice)\n        {\n            this._device = graphicsDevice;\n            this._graphics = new AnimationGraphics(_device);\n            this.Items = new List<AnimationItem>();\n            this.BackgroundColor = Color.Transparent;\n        }\n\n        public List<AnimationItem> Items { get; private set; }\n\n        public System.Drawing.Color GdipBackgroundColor\n        {\n            get\n            {\n                var c = this.BackgroundColor;\n                return System.Drawing.Color.FromArgb(c.A, c.R, c.G, c.B);\n            }\n            set\n            {\n                this.BackgroundColor = value.ToXnaColor();\n            }\n        }\n\n        public Color BackgroundColor { get; set; }\n\n        public Texture2D BackgroundImage { get; set; }\n\n        private GraphicsDevice _device;\n        private RenderTargetBinding[] _oldBuffer;\n        private RenderTarget2D _rt2d;\n        private Rectangle _viewport;\n        private Point _targetSize;\n        private AnimationGraphics _graphics;\n        private SpriteBatch _sb;\n        private PngEffect _eff;\n\n        public void Begin(Rectangle viewport, Point? targetSize = null)\n        {\n            this._targetSize = targetSize ?? viewport.Size;\n            _rt2d = new RenderTarget2D(_device, _targetSize.X, _targetSize.Y, false, SurfaceFormat.Bgra32, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);\n            var binding = _device.GetRenderTargets();\n            _device.SetRenderTarget(_rt2d);\n\n            this._viewport = viewport;\n            this._oldBuffer = binding;\n\n            this._sb = new SpriteBatch(_device);\n            this._eff = new PngEffect(_device);\n        }\n\n        public void Update(TimeSpan elapsed)\n        {\n            foreach (var aniItem in this.Items)\n            {\n                aniItem.Update(elapsed);\n            }\n        }\n\n        public void Draw()\n        {\n            System.Threading.Monitor.Enter(this._device);\n            try\n            {\n                _device.SetRenderTarget(_rt2d);\n                this.DrawAnimation();\n            }\n            finally\n            {\n                _device.SetRenderTargets(_oldBuffer);\n                System.Threading.Monitor.Exit(this._device);\n            }\n        }\n\n        private void DrawAnimation()\n        {\n            if (this.BackgroundImage != null)\n            {\n                this._device.Clear(Color.Black);\n                var rect = new Rectangle(0, 0, _targetSize.X, _targetSize.Y);\n                _sb.Begin(blendState: BlendState.Opaque, samplerState: SamplerState.PointWrap);\n                _sb.Draw(this.BackgroundImage, Vector2.Zero, rect, Color.White);\n                _sb.End();\n            }\n            else\n            {\n                this._device.Clear(this.BackgroundColor);\n            }\n\n            Matrix world = Matrix.CreateTranslation(-this._viewport.Left, -this._viewport.Top, 0);\n            if (_targetSize != _viewport.Size)\n            {\n                world *= Matrix.CreateScale(\n                    1f * _targetSize.X / _viewport.Width,\n                    1f * _targetSize.Y / _viewport.Height,\n                    1f);\n            }\n\n            foreach (var animation in this.Items.Where(_ani => _ani != null))\n            {\n                if (animation is FrameAnimator framAni)\n                {\n                    _graphics.Draw(framAni, world);\n                }\n                else if (animation is ISpineAnimator spineAni)\n                {\n                    _graphics.Draw(spineAni, world);\n                }\n            }\n        }\n\n        public Texture2D GetPngTexture()\n        {\n            System.Threading.Monitor.Enter(this._device);\n            try\n            {\n                var texture = new RenderTarget2D(_device, _rt2d.Width, _rt2d.Height, false, SurfaceFormat.Bgra32, DepthFormat.None);\n                _device.SetRenderTarget(texture);\n                _eff.AlphaMixEnabled = false;\n\n                _device.Clear(Color.Transparent);\n                _sb.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, null, null, _eff, null);\n                _sb.Draw(_rt2d, Vector2.Zero, Color.White);\n                _sb.End();\n                return texture;\n            }\n            finally\n            {\n                _device.SetRenderTargets(_oldBuffer);\n                System.Threading.Monitor.Exit(this._device);\n            }\n        }\n\n        public Texture2D GetGifTexture(Color mixColor, int minMixedAlpha)\n        {\n            System.Threading.Monitor.Enter(this._device);\n            try\n            {\n                var texture = new RenderTarget2D(_device, _rt2d.Width, _rt2d.Height, false, SurfaceFormat.Bgra32, DepthFormat.None);\n                _device.SetRenderTarget(texture);\n\n                _eff.AlphaMixEnabled = true;\n                _eff.MixedColor = mixColor;\n                _eff.MinMixedAlpha = minMixedAlpha;\n\n                _device.Clear(Color.Transparent);\n                _sb.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, null, null, _eff, null);\n                _sb.Draw(_rt2d, Vector2.Zero, Color.White);\n                _sb.End();\n\n                return texture;\n            }\n            finally\n            {\n                _device.SetRenderTargets(_oldBuffer);\n                System.Threading.Monitor.Exit(this._device);\n            }\n        }\n\n        public void ResetAll()\n        {\n            foreach (var aniItem in this.Items)\n            {\n                aniItem.Reset();\n            }\n        }\n\n        public int GetMaxLength()\n        {\n            return this.Items.Select(aniItem => Math.Max(0, aniItem.Length)).Max();\n        }\n\n        public int[] GetGifTimeLine(int preferredFrameDelay, int? maxFrameDelay = null)\n        {\n            if (preferredFrameDelay <= 0)\n            {\n                preferredFrameDelay = 1;\n            }\n            if (maxFrameDelay != null && maxFrameDelay < preferredFrameDelay)\n            {\n                maxFrameDelay = preferredFrameDelay;\n            }\n\n            // only calculate the first layer\n            foreach (var animation in this.Items.Where(_ani => _ani != null))\n            {\n                if (animation is FrameAnimator frameAni)\n                {\n                    // we won't skip any frame even frame delay is greater than preferred delay\n                    var timeline = new List<int>();\n                    int totalLength = 0;\n                    foreach (var frame in frameAni.GetKeyFrames())\n                    {\n                        totalLength += frame.Length;\n                        if (frame.Animated)\n                        {\n                            for (int ms = frame.Length; ms > 0;)\n                            {\n                                if (ms >= preferredFrameDelay)\n                                {\n                                    timeline.Add(preferredFrameDelay);\n                                    ms -= preferredFrameDelay;\n                                }\n                                else\n                                {\n                                    if (timeline.Count > 0)\n                                    {\n                                        timeline[timeline.Count - 1] += ms;\n                                    }\n                                    else\n                                    {\n                                        // duration of the first frame less than minFrameDelay, but we can't simply ignore it.\n                                        timeline.Add(ms);\n                                    }\n                                    ms = 0;\n                                }\n                            }\n                        }\n                        else\n                        {\n                            if (maxFrameDelay != null)\n                            {\n                                for (int ms = frame.Length; ms > 0;)\n                                {\n                                    if (ms >= maxFrameDelay.Value)\n                                    {\n                                        timeline.Add(maxFrameDelay.Value);\n                                        ms -= maxFrameDelay.Value;\n                                    }\n                                    else\n                                    {\n                                        timeline.Add(ms);\n                                        ms = 0;\n                                    }\n                                }\n                            }\n                            else\n                            {\n                                timeline.Add(frame.Length);\n                            }\n                        }\n                    }\n\n                    return timeline.ToArray();\n                }\n                else if (animation is ISpineAnimator)\n                {\n                    return null;\n                }\n            }\n\n            return null;\n        }\n\n        public void End()\n        {\n            var binding = _device.GetRenderTargets();\n            _device.SetRenderTargets(_oldBuffer);\n            this._device = null;\n            this._oldBuffer = null;\n\n            for (int i = 0; i < binding.Length; i++)\n            {\n                binding[i].RenderTarget.Dispose();\n            }\n\n            _eff.Dispose();\n            _sb.Dispose();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/FrmProgressDialog.Designer.cs",
    "content": "﻿\nnamespace WzComparerR2.Controls\n{\n    partial class FrmProgressDialog\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.progressBarX1 = new DevComponents.DotNetBar.Controls.ProgressBarX();\n            this.tableLayoutPanel1.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // tableLayoutPanel1\n            // \n            this.tableLayoutPanel1.ColumnCount = 1;\n            this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));\n            this.tableLayoutPanel1.Controls.Add(this.labelX1, 0, 0);\n            this.tableLayoutPanel1.Controls.Add(this.progressBarX1, 0, 1);\n            this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);\n            this.tableLayoutPanel1.Name = \"tableLayoutPanel1\";\n            this.tableLayoutPanel1.RowCount = 2;\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));\n            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));\n            this.tableLayoutPanel1.Size = new System.Drawing.Size(284, 51);\n            this.tableLayoutPanel1.TabIndex = 0;\n            // \n            // labelX1\n            // \n            this.labelX1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(3, 3);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(278, 19);\n            this.labelX1.TabIndex = 0;\n            this.labelX1.Text = \"Message\";\n            this.labelX1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.labelX1_MouseClick);\n            // \n            // progressBarX1\n            // \n            this.progressBarX1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \n            | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.progressBarX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.progressBarX1.Location = new System.Drawing.Point(3, 28);\n            this.progressBarX1.Name = \"progressBarX1\";\n            this.progressBarX1.Size = new System.Drawing.Size(278, 20);\n            this.progressBarX1.TabIndex = 1;\n            this.progressBarX1.Text = \"progressBarX1\";\n            // \n            // FrmProgressDialog\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(284, 51);\n            this.Controls.Add(this.tableLayoutPanel1);\n            this.DoubleBuffered = true;\n            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;\n            this.MaximizeBox = false;\n            this.MinimizeBox = false;\n            this.Name = \"FrmProgressDialog\";\n            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;\n            this.Text = \"Processing\";\n            this.tableLayoutPanel1.ResumeLayout(false);\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.ProgressBarX progressBarX1;\n    }\n}"
  },
  {
    "path": "WzComparerR2.Common/Controls/FrmProgressDialog.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\n\nnamespace WzComparerR2.Controls\n{\n    public partial class FrmProgressDialog : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmProgressDialog()\n        {\n            InitializeComponent();\n        }\n\n        public string Message\n        {\n            get { return this.labelX1.Text; }\n            set { this.labelX1.Text = value; }\n        }\n\n        public int Progress\n        {\n            get { return this.progressBarX1.Value; }\n            set { this.progressBarX1.Value = value; }\n        }\n\n        public int ProgressMin\n        {\n            get { return this.progressBarX1.Minimum; }\n            set { this.progressBarX1.Minimum = value; }\n        }\n\n        public int ProgressMax\n        {\n            get { return this.progressBarX1.Maximum; }\n            set { this.progressBarX1.Maximum = value; }\n        }\n\n        public string FullMessage { get; set; }\n\n        private void labelX1_MouseClick(object sender, MouseEventArgs e)\n        {\n            if (e.Button == MouseButtons.Right)\n            {\n                Clipboard.SetText(this.FullMessage ?? this.Message);\n                ToastNotification.Show(this, \"已复制到剪切板。\", 1000, eToastPosition.TopCenter);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/FrmProgressDialog.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2.Common/Controls/GraphicsDeviceControl.cs",
    "content": "﻿#region File Description\n//-----------------------------------------------------------------------------\n// GraphicsDeviceControl.cs\n//\n// Microsoft XNA Community Game Platform\n// Copyright (C) Microsoft Corporation. All rights reserved.\n//-----------------------------------------------------------------------------\n#endregion\n\n#region Using Statements\nusing System;\nusing System.Drawing;\nusing System.Reflection;\nusing System.Windows.Forms;\nusing Microsoft.Xna.Framework.Graphics;\n#endregion\n\nnamespace WzComparerR2.Controls\n{\n    // System.Drawing and the XNA Framework both define Color and Rectangle\n    // types. To avoid conflicts, we specify exactly which ones to use.\n    using Color = System.Drawing.Color;\n    using Rectangle = Microsoft.Xna.Framework.Rectangle;\n\n\n    /// <summary>\n    /// Custom control uses the XNA Framework GraphicsDevice to render onto\n    /// a Windows Form. Derived classes can override the Initialize and Draw\n    /// methods to add their own drawing code.\n    /// </summary>\n    abstract public class GraphicsDeviceControl : Control\n    {\n        #region Fields\n\n\n        // However many GraphicsDeviceControl instances you have, they all share\n        // the same underlying GraphicsDevice, managed by this helper service.\n        GraphicsDeviceService graphicsDeviceService;\n        SwapChainRenderTarget swapChainRT;\n\n        #endregion\n\n        #region Properties\n\n\n        /// <summary>\n        /// Gets a GraphicsDevice that can be used to draw onto this control.\n        /// </summary>\n        public GraphicsDevice GraphicsDevice\n        {\n            get { return graphicsDeviceService.GraphicsDevice; }\n        }\n\n\n        /// <summary>\n        /// Gets an IServiceProvider containing our IGraphicsDeviceService.\n        /// This can be used with components such as the ContentManager,\n        /// which use this service to look up the GraphicsDevice.\n        /// </summary>\n        public ServiceContainer Services\n        {\n            get { return services; }\n        }\n\n        ServiceContainer services = new ServiceContainer();\n\n\n        #endregion\n\n        #region Initialization\n\n\n        /// <summary>\n        /// Initializes the control.\n        /// </summary>\n        protected override void OnCreateControl()\n        {\n            // Don't initialize the graphics device if we are running in the designer.\n            if (!DesignMode)\n            {\n                this.graphicsDeviceService = GraphicsDeviceService.AddRef(Handle,\n                                                                     ClientSize.Width,\n                                                                     ClientSize.Height);\n                this.swapChainRT = new SwapChainRenderTarget(graphicsDeviceService.GraphicsDevice, this.Handle, ClientSize.Width, ClientSize.Height);\n                this.swapChainRT.Disposing += SwapChainRT_Disposing;\n                // Register the service, so components like ContentManager can find it.\n                services.AddService<IGraphicsDeviceService>(graphicsDeviceService);\n\n                // Give derived classes a chance to initialize themselves.\n                Initialize();\n            }\n\n            base.OnCreateControl();\n        }\n\n\n        /// <summary>\n        /// Disposes the control.\n        /// </summary>\n        protected override void Dispose(bool disposing)\n        {\n            if (this.swapChainRT != null && !this.swapChainRT.IsDisposed)\n            {\n                this.swapChainRT.Dispose();\n                this.swapChainRT = null;\n            }\n\n            if (this.graphicsDeviceService != null)\n            {\n                this.graphicsDeviceService.Release(disposing);\n                this.graphicsDeviceService = null;\n            }\n\n            base.Dispose(disposing);\n        }\n\n        private void SwapChainRT_Disposing(object sender, EventArgs e)\n        {\n            // fix monogame memory leak bug.\n            if (sender is SwapChainRenderTarget swapChainRT)\n            {\n                var backBufferField = sender.GetType().GetField(\"_backBuffer\", BindingFlags.Instance | BindingFlags.NonPublic);\n                if (backBufferField != null && backBufferField.GetValue(swapChainRT) is SharpDX.Direct3D11.Resource d3dResource)\n                {\n                    SharpDX.Utilities.Dispose(ref d3dResource);\n                    backBufferField.SetValue(swapChainRT, null);\n                }\n            }\n        }\n        #endregion\n\n        #region Paint\n\n\n        /// <summary>\n        /// Redraws the control in response to a WinForms paint message.\n        /// </summary>\n        protected override void OnPaint(PaintEventArgs e)\n        {\n            try\n            {\n                if (!DesignMode)\n                {\n                    System.Threading.Monitor.Enter(this.GraphicsDevice);\n                }\n\n                string beginDrawError = this.BeginDraw();\n\n                if (string.IsNullOrEmpty(beginDrawError))\n                {\n                    // Draw the control using the GraphicsDevice.\n                    Draw();\n                    EndDraw();\n                }\n                else\n                {\n                    // If BeginDraw failed, show an error message using System.Drawing.\n                    PaintUsingSystemDrawing(e.Graphics, beginDrawError);\n                }\n            }\n            finally\n            {\n                if (!DesignMode)\n                {\n                    System.Threading.Monitor.Exit(this.GraphicsDevice);\n                }\n            }\n        }\n\n        /// <summary>\n        /// Attempts to begin drawing the control. Returns an error message string\n        /// if this was not possible, which can happen if the graphics device is\n        /// lost, or if we are running inside the Form designer.\n        /// </summary>\n        string BeginDraw()\n        {\n            // If we have no graphics device, we must be running in the designer.\n            if (graphicsDeviceService == null)\n            {\n                return Text + \"\\n\\n\" + GetType();\n            }\n\n            // Make sure the graphics device is big enough, and is not lost.\n            string deviceResetError = HandleDeviceReset();\n\n            if (!string.IsNullOrEmpty(deviceResetError))\n            {\n                return deviceResetError;\n            }\n\n            // Many GraphicsDeviceControl instances can be sharing the same\n            // GraphicsDevice. The device backbuffer will be resized to fit the\n            // largest of these controls. But what if we are currently drawing\n            // a smaller control? To avoid unwanted stretching, we set the\n            // viewport to only use the top left portion of the full backbuffer.\n            Viewport viewport = new Viewport();\n\n            viewport.X = 0;\n            viewport.Y = 0;\n\n            viewport.Width = ClientSize.Width;\n            viewport.Height = ClientSize.Height;\n\n            viewport.MinDepth = 0;\n            viewport.MaxDepth = 1;\n\n            GraphicsDevice.Viewport = viewport;\n            GraphicsDevice.PresentationParameters.BackBufferWidth = viewport.Width;\n            GraphicsDevice.PresentationParameters.BackBufferHeight = viewport.Height;\n            GraphicsDevice.SetRenderTarget(swapChainRT);\n\n            return null;\n        }\n\n\n        /// <summary>\n        /// Ends drawing the control. This is called after derived classes\n        /// have finished their Draw method, and is responsible for presenting\n        /// the finished image onto the screen, using the appropriate WinForms\n        /// control handle to make sure it shows up in the right place.\n        /// </summary>\n        void EndDraw()\n        {\n            try\n            {\n                Rectangle sourceRectangle = new Rectangle(0, 0, ClientSize.Width,\n                                                                ClientSize.Height);\n\n                //GraphicsDevice.Present(sourceRectangle, null, this.Handle);\n                this.swapChainRT.Present();\n                GraphicsDevice.SetRenderTarget(null);\n            }\n            catch\n            {\n                // Present might throw if the device became lost while we were\n                // drawing. The lost device will be handled by the next BeginDraw,\n                // so we just swallow the exception.\n            }\n        }\n\n\n        /// <summary>\n        /// Helper used by BeginDraw. This checks the graphics device status,\n        /// making sure it is big enough for drawing the current control, and\n        /// that the device is not lost. Returns an error string if the device\n        /// could not be reset.\n        /// </summary>\n        string HandleDeviceReset()\n        {\n            bool deviceNeedsReset = false;\n            var clientSize = this.ClientSize;\n\n            switch (GraphicsDevice.GraphicsDeviceStatus)\n            {\n                case GraphicsDeviceStatus.Lost:\n                    // If the graphics device is lost, we cannot use it at all.\n                    return \"Graphics device lost\";\n\n                case GraphicsDeviceStatus.NotReset:\n                    // If device is in the not-reset state, we should try to reset it.\n                    deviceNeedsReset = true;\n                    break;\n\n                default:\n                    // If the device state is ok, check whether it is big enough.\n                    PresentationParameters pp = GraphicsDevice.PresentationParameters;\n\n                    deviceNeedsReset = (clientSize.Width != pp.BackBufferWidth) ||\n                                       (clientSize.Height != pp.BackBufferHeight);\n                    break;\n            }\n\n            // Do we need to reset the device?\n            if (deviceNeedsReset)\n            {\n                try\n                {\n                    this.swapChainRT.Dispose();\n                    this.swapChainRT = new SwapChainRenderTarget(\n                        this.graphicsDeviceService.GraphicsDevice,\n                        this.Handle,\n                        clientSize.Width,\n                        clientSize.Height);\n                    this.swapChainRT.Disposing += SwapChainRT_Disposing;\n                }\n                catch (Exception e)\n                {\n                    return \"Graphics device reset failed\\n\\n\" + e;\n                }\n            }\n\n            return null;\n        }\n\n\n        /// <summary>\n        /// If we do not have a valid graphics device (for instance if the device\n        /// is lost, or if we are running inside the Form designer), we must use\n        /// regular System.Drawing method to display a status message.\n        /// </summary>\n        protected virtual void PaintUsingSystemDrawing(Graphics graphics, string text)\n        {\n            graphics.Clear(Color.CornflowerBlue);\n\n            using (Brush brush = new SolidBrush(Color.Black))\n            {\n                using (StringFormat format = new StringFormat())\n                {\n                    format.Alignment = StringAlignment.Center;\n                    format.LineAlignment = StringAlignment.Center;\n\n                    graphics.DrawString(text, Font, brush, ClientRectangle, format);\n                }\n            }\n        }\n\n\n        /// <summary>\n        /// Ignores WinForms paint-background messages. The default implementation\n        /// would clear the control to the current background color, causing\n        /// flickering when our OnPaint implementation then immediately draws some\n        /// other color over the top using the XNA Framework GraphicsDevice.\n        /// </summary>\n        protected override void OnPaintBackground(PaintEventArgs pevent)\n        {\n        }\n\n\n        #endregion\n\n        #region Abstract Methods\n\n\n        /// <summary>\n        /// Derived classes override this to initialize their drawing code.\n        /// </summary>\n        protected abstract void Initialize();\n\n\n        /// <summary>\n        /// Derived classes override this to draw themselves using the GraphicsDevice.\n        /// </summary>\n        protected abstract void Draw();\n\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/GraphicsDeviceService.cs",
    "content": "﻿#region File Description\n//-----------------------------------------------------------------------------\n// GraphicsDeviceService.cs\n//\n// Microsoft XNA Community Game Platform\n// Copyright (C) Microsoft Corporation. All rights reserved.\n//-----------------------------------------------------------------------------\n#endregion\n\n#region Using Statements\nusing System;\nusing System.Threading;\nusing Microsoft.Xna.Framework.Graphics;\n#endregion\n\n// The IGraphicsDeviceService interface requires a DeviceCreated event, but we\n// always just create the device inside our constructor, so we have no place to\n// raise that event. The C# compiler warns us that the event is never used, but\n// we don't care so we just disable this warning.\n#pragma warning disable 67\n\nnamespace WzComparerR2.Controls\n{\n    /// <summary>\n    /// Helper class responsible for creating and managing the GraphicsDevice.\n    /// All GraphicsDeviceControl instances share the same GraphicsDeviceService,\n    /// so even though there can be many controls, there will only ever be a single\n    /// underlying GraphicsDevice. This implements the standard IGraphicsDeviceService\n    /// interface, which provides notification events for when the device is reset\n    /// or disposed.\n    /// </summary>\n    class GraphicsDeviceService : IGraphicsDeviceService\n    {\n        #region Fields\n\n\n        // Singleton device service instance.\n        static GraphicsDeviceService singletonInstance;\n\n\n        // Keep track of how many controls are sharing the singletonInstance.\n        static int referenceCount;\n\n\n        #endregion\n\n\n        /// <summary>\n        /// Constructor is private, because this is a singleton class:\n        /// client controls should use the public AddRef method instead.\n        /// </summary>\n        GraphicsDeviceService(IntPtr windowHandle, int width, int height)\n        {\n            parameters = new PresentationParameters();\n\n            parameters.BackBufferWidth = Math.Max(width, 1);\n            parameters.BackBufferHeight = Math.Max(height, 1);\n            parameters.BackBufferFormat = SurfaceFormat.Color;\n            parameters.DepthStencilFormat = DepthFormat.Depth24;\n            parameters.DeviceWindowHandle = windowHandle;\n            parameters.PresentationInterval = PresentInterval.Immediate;\n            parameters.IsFullScreen = false;\n\n            graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter,\n                                                GraphicsProfile.HiDef,\n                                                parameters);\n        }\n\n\n        /// <summary>\n        /// Gets a reference to the singleton instance.\n        /// </summary>\n        public static GraphicsDeviceService AddRef(IntPtr windowHandle,\n                                                   int width, int height)\n        {\n            // Increment the \"how many controls sharing the device\" reference count.\n            if (Interlocked.Increment(ref referenceCount) == 1)\n            {\n                // If this is the first control to start using the\n                // device, we must create the singleton instance.\n                singletonInstance = new GraphicsDeviceService(IntPtr.Zero, 1, 1);\n            }\n\n            return singletonInstance;\n        }\n\n\n        /// <summary>\n        /// Releases a reference to the singleton instance.\n        /// </summary>\n        public void Release(bool disposing)\n        {\n            // Decrement the \"how many controls sharing the device\" reference count.\n            if (Interlocked.Decrement(ref referenceCount) == 0)\n            {\n                // If this is the last control to finish using the\n                // device, we should dispose the singleton instance.\n                if (disposing)\n                {\n                    if (DeviceDisposing != null)\n                        DeviceDisposing(this, EventArgs.Empty);\n\n                    graphicsDevice.Dispose();\n                }\n\n                graphicsDevice = null;\n            }\n        }\n\n\n        /// <summary>\n        /// Resets the graphics device to whichever is bigger out of the specified\n        /// resolution or its current size. This behavior means the device will\n        /// demand-grow to the largest of all its GraphicsDeviceControl clients.\n        /// </summary>\n        public void ResetDevice(int width, int height)\n        {\n            if (DeviceResetting != null)\n                DeviceResetting(this, EventArgs.Empty);\n\n            parameters.BackBufferWidth = Math.Max(1, width);\n            parameters.BackBufferHeight = Math.Max(1, height);\n\n            graphicsDevice.Reset(parameters);\n\n            if (DeviceReset != null)\n                DeviceReset(this, EventArgs.Empty);\n        }\n\n\n        /// <summary>\n        /// Gets the current graphics device.\n        /// </summary>\n        public GraphicsDevice GraphicsDevice\n        {\n            get { return graphicsDevice; }\n        }\n\n        GraphicsDevice graphicsDevice;\n\n\n        // Store the current device settings.\n        PresentationParameters parameters;\n\n\n        // IGraphicsDeviceService events.\n        public event EventHandler<EventArgs> DeviceCreated;\n        public event EventHandler<EventArgs> DeviceDisposing;\n        public event EventHandler<EventArgs> DeviceReset;\n        public event EventHandler<EventArgs> DeviceResetting;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/ProgressDialog.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\n\nnamespace WzComparerR2.Controls\n{\n    public static class ProgressDialog\n    {\n        public static DialogResult Show(IWin32Window owner, string text, string caption, bool closeOnComplete, bool closeOnError, Func<IProgressDialogContext, CancellationToken, Task> factory)\n        {\n            return new ProgressDialogContext(text, caption, closeOnComplete, closeOnError, factory).ShowDialog(owner);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/ProgressDialogContext.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Windows.Forms;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.Controls\n{\n    public interface IProgressDialogContext\n    {\n        string Message { get; set; }\n        string FullMessage { get; set; }\n        int Progress { get; set; }\n        int ProgressMin { get; set; }\n        int ProgressMax { get; set; }\n    }\n\n    internal class ProgressDialogContext : IProgressDialogContext\n    {\n        internal ProgressDialogContext(\n            string text,\n            string caption,\n            bool closeOnComplete,\n            bool closeOnError,\n            Func<ProgressDialogContext, CancellationToken, Task> factory)\n        {\n            this.dialog = new FrmProgressDialog();\n            if (caption != null)\n            {\n                this.dialog.Text = caption;\n            }\n            if (text != null)\n            {\n                this.dialog.Message = text;\n            }\n            this.cancellationTokenSource = new CancellationTokenSource();\n            this.closeOnComplete = closeOnComplete;\n            this.closeOnError = closeOnError;\n            this.factory = factory;\n        }\n\n        public string Message\n        {\n            get { return this.dialog.Message; }\n            set { this.dialog.Message = value; }\n        }\n\n        public string FullMessage\n        {\n            get { return this.dialog.FullMessage; }\n            set { this.dialog.FullMessage = value; }\n        }\n\n        public int Progress\n        {\n            get { return this.dialog.Progress; }\n            set { this.dialog.Progress = value; }\n        }\n\n        public int ProgressMin\n        {\n            get { return this.dialog.ProgressMin; }\n            set { this.dialog.ProgressMin = value; }\n        }\n\n        public int ProgressMax\n        {\n            get { return this.dialog.ProgressMax; }\n            set { this.dialog.ProgressMax = value; }\n        }\n\n        private readonly FrmProgressDialog dialog;\n        private readonly CancellationTokenSource cancellationTokenSource;\n        private readonly bool closeOnComplete;\n        private readonly bool closeOnError;\n        private readonly Func<ProgressDialogContext, CancellationToken, Task> factory;\n\n        private DialogResult dialogResult;\n\n        internal DialogResult ShowDialog(IWin32Window owner)\n        {\n            this.dialog.Load += Dialog_Load;\n            this.dialog.FormClosing += Dialog_FormClosing;\n            this.dialogResult = DialogResult.None;\n            dialog.ShowDialog(owner);\n            return this.dialogResult;\n        }\n\n        private async void Dialog_Load(object sender, EventArgs e)\n        {\n            try\n            {\n                if (this.factory != null)\n                {\n                    await this.factory(this, this.cancellationTokenSource.Token);\n                }\n                this.dialog.FormClosing -= Dialog_FormClosing;\n                this.dialogResult = DialogResult.OK;\n                if (this.closeOnComplete)\n                {\n                    this.dialog.Close();\n                }\n            }\n            catch\n            {\n                this.OnCancel();\n                if (this.closeOnError)\n                {\n                    this.dialog.Close();\n                }\n            }\n        }\n\n        private void Dialog_FormClosing(object sender, FormClosingEventArgs e)\n        {\n            this.OnCancel();\n        }\n\n        private void OnCancel()\n        {\n            this.dialogResult = DialogResult.Cancel;\n            if (!this.cancellationTokenSource.IsCancellationRequested)\n            {\n                this.cancellationTokenSource.Cancel();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Controls/ServiceContainer.cs",
    "content": "﻿#region File Description\n//-----------------------------------------------------------------------------\n// ServiceContainer.cs\n//\n// Microsoft XNA Community Game Platform\n// Copyright (C) Microsoft Corporation. All rights reserved.\n//-----------------------------------------------------------------------------\n#endregion\n\n#region Using Statements\nusing System;\nusing System.Collections.Generic;\n#endregion\n\nnamespace WzComparerR2.Controls\n{\n    /// <summary>\n    /// Container class implements the IServiceProvider interface. This is used\n    /// to pass shared services between different components, for instance the\n    /// ContentManager uses it to locate the IGraphicsDeviceService implementation.\n    /// </summary>\n    public class ServiceContainer : IServiceProvider\n    {\n        Dictionary<Type, object> services = new Dictionary<Type, object>();\n\n\n        /// <summary>\n        /// Adds a new service to the collection.\n        /// </summary>\n        public void AddService<T>(T service)\n        {\n            services.Add(typeof(T), service);\n        }\n\n\n        /// <summary>\n        /// Looks up the specified service.\n        /// </summary>\n        public object GetService(Type serviceType)\n        {\n            object service;\n\n            services.TryGetValue(serviceType, out service);\n\n            return service;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Encoders/BuildInApngEncoder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Drawing;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.Encoders\n{\n    public class BuildInApngEncoder : GifEncoder\n    {\n        public BuildInApngEncoder()\n        {\n        }\n\n        public bool OptimizeEnabled { get; set; }\n\n        private IntPtr handle;\n\n        public override GifEncoderCompatibility Compatibility => new GifEncoderCompatibility()\n        {\n            IsFixedFrameRate = false,\n            MinFrameDelay = 1,\n            MaxFrameDelay = 655350,\n            FrameDelayStep = 1,\n            AlphaSupportMode = AlphaSupportMode.FullAlpha,\n            DefaultExtension = \".png\",\n            SupportedExtensions = new[] { \".png\" },\n        };\n\n        public override void Init(string fileName, int width, int height)\n        {\n            base.Init(fileName, width, height);\n\n            var err = apng_init(fileName, width, height, out handle);\n            if (err != ApngError.Success)\n            {\n                throw new Exception($\"Apng error: {err}.\");\n            }\n        }\n\n        public override void AppendFrame(IntPtr pBuffer, int delay)\n        {\n            var err = apng_append_frame(handle, pBuffer, 0, 0, Width, Height, Width * 4, delay, OptimizeEnabled);\n            if (err != ApngError.Success)\n            {\n                throw new Exception($\"Apng error: {err}.\");\n            }\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (handle != IntPtr.Zero)\n                {\n                    apng_write_end(handle);\n                    apng_destroy(ref handle);\n                    handle = IntPtr.Zero;\n                }\n            }\n            base.Dispose(disposing);\n        }\n\n        enum ApngError : int\n        {\n            Success = 0,\n            ContextCreateFailed = 1,\n            FileError = 2,\n            ArgumentError = 3,\n            MemoryError = 4,\n        };\n\n        [DllImport(\"libapng.dll\")]\n        static extern ApngError apng_init([MarshalAs(UnmanagedType.LPWStr)] string fileName, int width, int height, out IntPtr ppEnc);\n        [DllImport(\"libapng.dll\")]\n        static extern ApngError apng_append_frame(IntPtr pEnc, IntPtr pData, int x, int y, int width, int height, int stride, int delay_ms, bool optimize);\n        [DllImport(\"libapng.dll\")]\n        static extern void apng_write_end(IntPtr pEnc);\n        [DllImport(\"libapng.dll\")]\n        static extern void apng_destroy(ref IntPtr ppEnc);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Encoders/BuildInGifEncoder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.IO;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing ImageManipulation;\n\nnamespace WzComparerR2.Encoders\n{\n    public class BuildInGifEncoder : GifEncoder\n    {\n        public BuildInGifEncoder()\n        {\n            mStream = new MemoryStream();\n            quantizer = new OctreeQuantizer(255, 8);\n        }\n\n        public override GifEncoderCompatibility Compatibility => new GifEncoderCompatibility()\n        {\n            IsFixedFrameRate = false,\n            MinFrameDelay = 10,\n            MaxFrameDelay = 655350,\n            FrameDelayStep = 10,\n            AlphaSupportMode = AlphaSupportMode.OneBitAlpha,\n            DefaultExtension = \".gif\",\n            SupportedExtensions = new[] { \".gif\" },\n        };\n\n        private BinaryWriter bWriter;\n        private MemoryStream mStream;\n        private Quantizer quantizer;\n\n        private static readonly byte[] gifHeader = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };//GIF89a\n        private static readonly byte[] logicalScreen = new byte[] { 0x70, 0x00, 0x00 };//无全局色彩表 无视背景色 无视像素纵横比\n        private static readonly byte[] appExtension = new byte[] { 0x21,0xff,0x0b, //块标志\n                0x4e,0x45,0x54,0x53,0x43,0x41,0x50,0x45,0x32,0x2e,0x30, //NETSCAPE2.0\n                0x03,0x01,0x00,0x00,0x00};//循环信息 其他信息\n        private static readonly byte[] gifEnd = new byte[] { 0x3b };//结束信息\n\n        public override void Init(string fileName, int width, int height)\n        {\n            base.Init(fileName, width, height);\n\n            bWriter = new BinaryWriter(File.Create(fileName));\n            WriteHeader();\n        }\n\n        public override void AppendFrame(Bitmap image, int delay)\n        {\n            mStream.SetLength(0);\n            mStream.Position = 0;\n            using (var tempGif = quantizer.Quantize(image))\n            {\n                tempGif.Save(mStream, ImageFormat.Gif);\n            }\n\n            byte[] tempArray = mStream.GetBuffer();\n            // 781开始为Graphic Control Extension块 标志为21 F9 04 \n            tempArray[784] = 0x09; //图像刷新时屏幕返回初始帧 貌似不打会bug 意味不明 测试用\n            delay = delay / 10;\n            tempArray[785] = (byte)(delay & 0xff);\n            tempArray[786] = (byte)(delay >> 8 & 0xff); //写入2字节的帧delay \n                                                        // 787为透明色索引  788为块结尾0x00\n            tempArray[787] = 0xff;\n            // 789开始为Image Descriptor块 标志位2C\n            // 790~793为帧偏移大小 默认为0\n            // 794~797为帧图像大小 默认他\n            tempArray[798] = (byte)(tempArray[798] | 0X87); //本地色彩表标志\n\n            //写入到gif文件\n            bWriter.Write(tempArray, 781, 18);\n            bWriter.Write(tempArray, 13, 768);\n            bWriter.Write(tempArray, 799, (int)mStream.Length - 800);\n        }\n\n        public override void AppendFrame(IntPtr pBuffer, int delay)\n        {\n            using (var bmp = new Bitmap(Width, Height, Width * 4, PixelFormat.Format32bppArgb, pBuffer))\n            {\n                AppendFrame(bmp, delay);\n            }\n        }\n\n        private void WriteHeader()\n        {\n            //写入gif头信息\n            bWriter.Write(gifHeader);\n            bWriter.Write((ushort)Width);\n            bWriter.Write((ushort)Height);\n            bWriter.Write(logicalScreen);\n            //写入循环标记\n            bWriter.Write(appExtension);\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (bWriter != null)\n                {\n                    bWriter.Write(gifEnd);\n                    bWriter.Close();\n                    bWriter = null;\n                }\n            }\n\n            if (mStream != null)\n            {\n                mStream.Dispose();\n                mStream = null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Encoders/FFmpegEncoder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.IO;\nusing System.IO.Pipes;\n\nnamespace WzComparerR2.Encoders\n{\n    public class FFmpegEncoder : GifEncoder\n    {\n        public static readonly string DefaultExecutionFileName = \"ffmpeg\";\n        /// <summary>\n        /// Default ffmpeg argument format to encode mp4 with avc H.264 video format.\n        /// </summary>\n        public static readonly string DefaultArgumentFormat = @$\"-y -f rawvideo -pixel_format bgra -s %w*%h -r 1000/%t -i \"\"%i\"\" -vf \"\"crop=trunc(iw/2)*2:trunc(ih/2)*2\"\" -vcodec libx264 -pix_fmt yuv420p \"\"%o\"\"\";\n\n        public static readonly string DefaultOutputFileExtension = \".mp4\";\n\n        public FFmpegEncoder()\n        {\n        }\n\n        public string FFmpegBinPath { get; set; }\n        public string FFmpegArgumentFormat { get; set; }\n        public string OutputFileExtension { get; set; }\n\n        private Process ffmpegProc;\n        private NamedPipeServerStream server;\n        private CancellationTokenSource ffmpegProcExitedCts;\n        private StringBuilder ffmpegStdout = new StringBuilder();\n        private StringBuilder ffmpegStderr = new StringBuilder();\n        private bool disposed;\n\n        public override GifEncoderCompatibility Compatibility => new GifEncoderCompatibility()\n        {\n            IsFixedFrameRate = true,\n            MinFrameDelay = 1,\n            MaxFrameDelay = int.MaxValue,\n            FrameDelayStep = 1,\n            AlphaSupportMode = AlphaSupportMode.NoAlpha,\n            DefaultExtension = string.IsNullOrEmpty(OutputFileExtension) ? DefaultOutputFileExtension : OutputFileExtension,\n            SupportedExtensions = new[] { \".*\" },\n        };\n\n        public unsafe override void AppendFrame(IntPtr pBuffer, int delay)\n        {\n            if (server == null && ffmpegProc == null)\n            {\n                StartFFmpeg(delay).Wait();\n            }\n\n            int frameDataLen = Width * Height * 4;\n            using UnmanagedMemoryStream ms = new((byte*)pBuffer.ToPointer(), frameDataLen, frameDataLen, FileAccess.Read);\n            ms.CopyToAsync(server, 32768, ffmpegProcExitedCts?.Token ?? default).ConfigureAwait(false).GetAwaiter().GetResult();\n        }\n\n        private async Task StartFFmpeg(int delay)\n        {\n            // create random pipe name\n            string pipeName = $\"{nameof(FFmpegEncoder)}-{Process.GetCurrentProcess().Id}-{(uint)Environment.TickCount}\";\n\n            // create named-pipe server and listen\n            NamedPipeServerStream server = new(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte);\n            var task1 = server.WaitForConnectionAsync();\n\n            // start ffmpeg\n            ProcessStartInfo psi = new()\n            {\n                FileName = string.IsNullOrEmpty(FFmpegBinPath) ? DefaultExecutionFileName : FFmpegBinPath,\n                Arguments = SubstituteParams(string.IsNullOrEmpty(FFmpegArgumentFormat) ? DefaultArgumentFormat : FFmpegArgumentFormat,\n                    @$\"\\\\.\\pipe\\{pipeName}\", Width, Height, delay, FileName),\n                WorkingDirectory = Environment.CurrentDirectory,\n                UseShellExecute = false,\n                RedirectStandardOutput = true,\n                RedirectStandardError = true,\n                CreateNoWindow = true,\n            };\n            Process ffmpegProc = new()\n            {\n                StartInfo = psi,\n            };\n            ffmpegProc.OutputDataReceived += FFmpegProc_OutputDataReceived;\n            ffmpegProc.ErrorDataReceived += FFmpegProc_ErrorDataReceived;\n            ffmpegProc.Exited += FFmpegProc_Exited;\n            ffmpegProc.Start();\n            ffmpegProc.BeginOutputReadLine();\n            ffmpegProc.BeginErrorReadLine();\n            await task1.ConfigureAwait(false);\n\n            this.server = server;\n            this.ffmpegProc = ffmpegProc;\n            ffmpegProcExitedCts = new CancellationTokenSource();\n        }\n\n        private void FFmpegProc_OutputDataReceived(object sender, DataReceivedEventArgs e)\n        {\n            ffmpegStdout?.AppendLine(e.Data);\n        }\n\n        private void FFmpegProc_ErrorDataReceived(object sender, DataReceivedEventArgs e)\n        {\n            ffmpegStderr?.AppendLine(e.Data);\n        }\n\n        private void FFmpegProc_Exited(object sender, EventArgs e)\n        {\n            ffmpegProcExitedCts?.Cancel();\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            base.Dispose(disposing);\n            if (!disposed)\n            {\n                if (disposing)\n                {\n                    if (server != null)\n                    {\n                        if (server.IsConnected)\n                        {\n                            server.Flush();\n                            server.Disconnect();\n                        }\n                        server.Dispose();\n                    }\n                    if (ffmpegProc != null)\n                    {\n                        if (!ffmpegProc.HasExited)\n                        {\n                            ffmpegProc.WaitForExit();\n                        }\n                        ffmpegProc.Dispose();\n                    }\n                    if (ffmpegProcExitedCts != null)\n                    {\n                        ffmpegProcExitedCts.Dispose();\n                    }\n                }\n\n                ffmpegStdout = null;\n                ffmpegStderr = null;\n                disposed = true;\n            }\n        }\n\n        private string SubstituteParams(string format, string inputFileName, int width, int height, int frameDelay, string outputFileName)\n        {\n            // %i: inputFileName\n            // %w: width\n            // %h: height\n            // %t: frameDelay\n            // %o: outputFileName\n            // %%: escape '%' char\n            return Regex.Replace(format, \"%[iwhto%]\", match => match.Value switch\n            {\n                \"%i\" => inputFileName,\n                \"%w\" => width.ToString(),\n                \"%h\" => height.ToString(),\n                \"%t\" => frameDelay.ToString(),\n                \"%o\" => outputFileName,\n                \"%%\" => \"%\",\n                _ => throw new FormatException($\"Unknown format: {match.Value}\")\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Encoders/GifEncoder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Imaging;\n\nnamespace WzComparerR2.Encoders\n{\n    public abstract class GifEncoder : IDisposable\n    {\n        protected GifEncoder()\n        {\n        }\n\n        public string FileName { get; private set; }\n        public int Width { get; private set; }\n        public int Height { get; private set; }\n\n        public virtual string Name\n        {\n            get\n            {\n                return GetType().Name;\n            }\n        }\n        public abstract GifEncoderCompatibility Compatibility { get; }\n\n        public virtual void Init(string fileName, int width, int height)\n        {\n            FileName = fileName;\n            Width = width;\n            Height = height;\n        }\n\n        public virtual void AppendFrame(Bitmap image, int delay)\n        {\n            BitmapData data = image.LockBits(new Rectangle(Point.Empty, image.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);\n            AppendFrame(data.Scan0, delay);\n            image.UnlockBits(data);\n        }\n\n        public abstract void AppendFrame(IntPtr pBuffer, int delay);\n\n        public void Dispose()\n        {\n            Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n\n        }\n\n        ~GifEncoder()\n        {\n            Dispose(false);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Encoders/GifEncoderCompatibility.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.Encoders\n{\n    public class GifEncoderCompatibility\n    {\n        public GifEncoderCompatibility()\n        {\n        }\n\n        public bool IsFixedFrameRate { get; set; }\n        public int MinFrameDelay { get; set; }\n        public int MaxFrameDelay { get; set; }\n        public int FrameDelayStep { get; set; }\n        public AlphaSupportMode AlphaSupportMode { get; set; }\n        public string DefaultExtension { get; set; }\n        public IReadOnlyList<string> SupportedExtensions { get; set; }\n    }\n\n    public enum AlphaSupportMode\n    {\n        NoAlpha,\n        OneBitAlpha,\n        FullAlpha\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Encoders/IndexGifEncoder.cs",
    "content": "﻿using System;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.Encoders\n{\n    public class IndexGifEncoder : GifEncoder\n    {\n        public IndexGifEncoder()\n        {\n        }\n\n        private IntPtr encoder_pointer;\n\n        public override GifEncoderCompatibility Compatibility => new GifEncoderCompatibility()\n        {\n            IsFixedFrameRate = false,\n            MinFrameDelay = 10,\n            MaxFrameDelay = 655350,\n            FrameDelayStep = 10,\n            AlphaSupportMode = AlphaSupportMode.OneBitAlpha,\n            DefaultExtension = \".gif\",\n            SupportedExtensions = new[] { \".gif\" },\n        };\n\n        public override void Init(string fileName, int width, int height)\n        {\n            base.Init(fileName, width, height);\n\n            encoder_pointer = construct(fileName, width, height, 255, 0);\n        }\n\n        public override void AppendFrame(IntPtr pBuffer, int delay)\n        {\n            encoder.append_frame(pBuffer, delay, encoder_pointer);\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (encoder_pointer != IntPtr.Zero)\n                {\n                    encoder.destruct(encoder_pointer);\n                    encoder_pointer = IntPtr.Zero;\n                }\n            }\n        }\n\n        private gif_encoder_structure encoder\n        {\n            get\n            {\n                return (gif_encoder_structure)Marshal.PtrToStructure(encoder_pointer, typeof(gif_encoder_structure));\n            }\n        }\n\n        [DllImport(\"libgif.dll\", EntryPoint = \"#1\", CharSet = CharSet.Unicode)]\n        private extern static IntPtr construct(string location, int width, int height, int maxColor, int backColor);\n\n        private delegate void gif_encoder_destruct(IntPtr encoder_pointer);\n        private delegate void gif_encoder_append_frame(IntPtr pixels, int delay, IntPtr encoder_pointer);\n\n        [StructLayout(LayoutKind.Sequential)]\n        private struct gif_encoder_structure\n        {\n            public gif_encoder_destruct destruct;\n            public gif_encoder_append_frame append_frame;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Gif.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.IO;\nusing WzComparerR2.Encoders;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Common\n{\n    public class Gif\n    {\n        public Gif()\n        {\n            this.Frames = new List<IGifFrame>();\n        }\n\n        public List<IGifFrame> Frames { get; private set; }\n\n        public Bitmap EncodeGif(Color backgrnd)\n        {\n            return EncodeGif(backgrnd, 0x00);\n        }\n\n        public Bitmap EncodeGif(Color backgrnd, int minAlpha)\n        {\n            return EncodeGif(backgrnd, minAlpha, 0, this.Frames.Count);\n        }\n\n        public Bitmap EncodeGif(Color backgrnd, int minAlpha, int startIndex, int frameCount)\n        {\n            if (frameCount <= 0)\n            {\n                return null;\n            }\n\n            return EncodeGif<BuildInGifEncoder>(backgrnd, minAlpha, startIndex, frameCount);\n        }\n\n        public Bitmap EncodeGif2(Color backgrnd, int minAlpha)\n        {\n            return EncodeGif2(backgrnd, minAlpha, 0, this.Frames.Count);\n        }\n\n        public Bitmap EncodeGif2(Color backgrnd, int minAlpha, int startIndex, int frameCount)\n        {\n            if (frameCount <= 0)\n            {\n                return null;\n            }\n\n            return EncodeGif<IndexGifEncoder>(backgrnd, minAlpha, startIndex, frameCount);\n        }\n\n        private Bitmap EncodeGif<T>(Color backgrnd, int minAlpha, int startIndex, int frameCount)\n            where T : GifEncoder\n        {\n            //预判大小\n            Rectangle rect = this.GetRect();\n            Bitmap canvas = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);\n            string tempFileName = Path.GetTempFileName();\n            GifEncoder enc = (GifEncoder)Activator.CreateInstance(typeof(T), tempFileName, rect.Width, rect.Height);\n\n            //写入帧信息\n            for (int i = startIndex, j = startIndex + frameCount; i < j; i++)\n            {\n                if (i >= this.Frames.Count)\n                    break;\n                IGifFrame frame = this.Frames[i];\n                if (frame == null)\n                {\n                    continue;\n                }\n\n                PrepareFrame(canvas, frame, rect, backgrnd, minAlpha);\n                enc.AppendFrame(canvas, frame.Delay);\n            }\n\n            enc.Dispose();\n            return Image.FromFile(tempFileName) as Bitmap;\n        }\n\n        public Rectangle GetRect()\n        {\n            Rectangle rect = Rectangle.Empty;\n            foreach (var f in this.Frames)\n            {\n                var newRect = ((IGifFrame)f).Region;\n                rect = rect.Size.IsEmpty ? newRect : Rectangle.Union(rect, newRect);\n            }\n            return rect.Size.IsEmpty ? Rectangle.Empty : rect;\n        }\n\n        public Bitmap GetFrame(int i)\n        {\n            var iFrame = this.Frames[i];\n            var rect = iFrame.Region;\n            return GetFrame(i, rect);\n        }\n\n        private Bitmap GetFrame(int i, Rectangle canvasRect)\n        {\n            var iFrame = this.Frames[i];\n            Bitmap bmp = new Bitmap(canvasRect.Width, canvasRect.Height, PixelFormat.Format32bppArgb);\n            Graphics g = Graphics.FromImage(bmp);\n            iFrame.Draw(g, canvasRect);\n            g.Dispose();\n            return bmp;\n        }\n\n        private static Bitmap PrepareFrame(IGifFrame frame, Rectangle canvasRect, Color backgrnd, int minAlpha)\n        {\n            Bitmap gifFrame = new Bitmap(canvasRect.Width, canvasRect.Height, PixelFormat.Format32bppArgb);\n            PrepareFrame(gifFrame, frame, canvasRect, backgrnd, minAlpha);\n            return gifFrame;\n        }\n\n        /// <summary>\n        /// 预处理帧坐标，生成新的图片。\n        /// </summary>\n        private static void PrepareFrame(Bitmap canvas, IGifFrame frame, Rectangle canvasRect, Color backgrnd, int minAlpha)\n        {\n            Graphics g = Graphics.FromImage(canvas);\n\n            if (backgrnd.A == 0xff) //背景色\n            {\n                g.Clear(backgrnd);\n                frame.Draw(g, canvasRect);\n            }\n            else //透明混合色\n            {\n                g.Clear(Color.Transparent);\n                Rectangle frameRect = frame.Region;\n                frameRect.Offset(-canvasRect.X, -canvasRect.Y);\n                frame.Draw(g, canvasRect);\n\n                BitmapData data = canvas.LockBits(new Rectangle(Point.Empty, canvas.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);\n                unsafe\n                {\n                    byte* buffer = (byte*)data.Scan0.ToPointer();\n\n                    for (int y = frameRect.Top; y < frameRect.Bottom; y++)\n                    {\n                        for (int x = frameRect.Left; x < frameRect.Right; x++)\n                        {\n                            int i = 4 * x + y * data.Stride;\n\n                            byte a = buffer[i + 3];\n                            if (a <= minAlpha)\n                            {\n                                buffer[i] = buffer[i + 1] = buffer[i + 2] = buffer[i + 3] = 0;\n                            }\n                            else if (a < 0xff)\n                            {\n                                float al = a / 255f;\n                                float be = (1 - al);\n                                buffer[i] = (byte)(buffer[i] * al + backgrnd.B * be);\n                                buffer[i + 1] = (byte)(buffer[i + 1] * al + backgrnd.G * be);\n                                buffer[i + 2] = (byte)(buffer[i + 2] * al + backgrnd.R * be);\n                                buffer[i + 3] = 0xff;\n                            }\n                        }\n                    }\n                }\n                canvas.UnlockBits(data);\n            }\n        }\n\n        public static Gif CreateFromNode(Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            if (node == null)\n                return null;\n            Gif gif = new Gif();\n            for (int i = 0; ; i++)\n            {\n                Wz_Node frameNode = node.FindNodeByPath(i.ToString());\n\n                if (frameNode == null || frameNode.Value == null)\n                    break;\n                GifFrame gifFrame = CreateFrameFromNode(frameNode, findNode);\n\n                if (gifFrame == null)\n                    break;\n                gif.Frames.Add(gifFrame);\n            }\n            if (gif.Frames.Count > 0)\n                return gif;\n            else\n                return null;\n        }\n\n        public static GifFrame CreateFrameFromNode(Wz_Node frameNode, GlobalFindNodeFunction findNode)\n        {\n            if (frameNode == null || frameNode.Value == null)\n            {\n                return null;\n            }\n\n            while (frameNode.Value is Wz_Uol)\n            {\n                Wz_Uol uol = frameNode.Value as Wz_Uol;\n                Wz_Node uolNode = uol.HandleUol(frameNode);\n                if (uolNode != null)\n                {\n                    frameNode = uolNode;\n                }\n            }\n            if (frameNode.Value is Wz_Png)\n            {\n                var linkNode = frameNode.GetLinkedSourceNode(findNode);\n                Wz_Png png = linkNode?.GetValue<Wz_Png>() ?? (Wz_Png)frameNode.Value;\n\n                var gifFrame = new GifFrame(png.ExtractPng());\n                foreach (Wz_Node propNode in frameNode.Nodes)\n                {\n                    switch (propNode.Text)\n                    {\n                        case \"origin\":\n                            gifFrame.Origin = (propNode.Value as Wz_Vector);\n                            break;\n                        case \"delay\":\n                            gifFrame.Delay = propNode.GetValue<int>();\n                            break;\n                        case \"a0\":\n                            gifFrame.A0 = propNode.GetValue<int>();\n                            break;\n                        case \"a1\":\n                            gifFrame.A1 = propNode.GetValue<int>();\n                            break;\n                    }\n                }\n                if (gifFrame.Delay == 0)\n                {\n                    gifFrame.Delay = 120;//给予默认delay\n                }\n                return gifFrame;\n            }\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/GifCanvas.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.Common\n{\n    public class GifCanvas\n    {\n        public GifCanvas()\n        {\n            this.Layers = new List<GifLayer>();\n            this.AlphaGradientDelay = 30;\n        }\n\n        public List<GifLayer> Layers { get; private set; }\n\n        public int AlphaGradientDelay { get; set; }\n\n        public Gif Combine()\n        {\n            //获取全部关键帧延时\n            List<int> delays = new List<int>();\n            delays.Add(0);\n            foreach (var layer in this.Layers)\n            {\n                int delay = 0;\n                foreach (var frame in layer.Frames)\n                {\n                    delay += frame.Delay;\n                    int idx = delays.BinarySearch(delay);\n                    if (idx < 0)\n                    {\n                        delays.Insert(~idx, delay);\n                    }\n                }\n            }\n\n            //构建关键帧\n            LinkedList<KeyFrame> keyFrames = new LinkedList<KeyFrame>();\n            for (int i = 1; i < delays.Count; i++)\n            {\n                keyFrames.AddLast(new KeyFrame() { Delay = delays[i] - delays[i - 1] });\n            }\n\n            //开始填充\n            foreach (var layer in this.Layers)\n            {\n                var node = keyFrames.First;\n                foreach (var frame in layer.Frames) //把图层按关键帧拆分\n                {\n                    var frame0 = frame;\n                    int delay = frame.Delay;\n                    while (delay > 0)\n                    {\n                        if (frame.Bitmap != null)\n                        {\n                            if (node.Value.Delay == frame0.Delay) //直接加入\n                            {\n                                node.Value.Frames.Add(frame0);\n                            }\n                            else if (node.Value.Delay < frame0.Delay) //拆分\n                            {\n                                GifFrame f1, f2;\n                                SplitGifFrame(frame0, node.Value.Delay, out f1, out f2);\n                                node.Value.Frames.Add(f1);\n                                frame0 = f2;\n                            }\n                            else\n                            {\n                                throw new Exception(\"key frame delay error.\");\n                            }\n                        }\n\n                        delay -= node.Value.Delay;\n                        node = node.Next;\n                    }\n                }\n            }\n\n            //开始合并\n            Gif gif = new Gif();\n            {\n                var node = keyFrames.First;\n                while (node != null)\n                {\n                    if (AlphaGradientDelay > 0 && node.Value.HasAlphaGradient && AlphaGradientDelay < node.Value.Delay) //分离渐变帧\n                    {\n                        KeyFrame f1, f2;\n                        node.Value.Split(AlphaGradientDelay, out f1, out f2);\n                        node.Value = f1;\n                        keyFrames.AddAfter(node, f2);\n                    }\n\n                    gif.Frames.Add(node.Value);\n                    node = node.Next;\n                }\n            }\n            return gif;\n        }\n\n        private static void SplitGifFrame(GifFrame frame, int time, out GifFrame frame1, out GifFrame frame2)\n        {\n            double p = (double)time / frame.Delay;\n            int a = frame.A0 == frame.A1 ? frame.A0 : (int)Math.Round(frame.A0 * (1 - p) + frame.A1 * p);\n            frame1 = new GifFrame(frame.Bitmap, frame.Origin, time) { A0 = frame.A0, A1 = a };\n            frame2 = new GifFrame(frame.Bitmap, frame.Origin, frame.Delay - time) { A0 = a, A1 = frame.A1 };\n        }\n\n        private class KeyFrame : IGifFrame\n        {\n            public KeyFrame()\n            {\n                this.Frames = new List<GifFrame>();\n            }\n\n            public List<GifFrame> Frames { get; private set; }\n            public int Delay { get; set; }\n\n            public bool HasAlphaGradient\n            {\n                get\n                {\n                    return !this.Frames.TrueForAll(f => f.A0 == f.A1);\n                }\n            }\n\n            public void Split(int time, out KeyFrame keyFrame1, out KeyFrame keyFrame2)\n            {\n                keyFrame1 = new KeyFrame();\n                keyFrame2 = new KeyFrame();\n                double p = (double)time / this.Delay;\n                foreach (var f in this.Frames)\n                {\n                    GifFrame f1, f2;\n                    SplitGifFrame(f, time, out f1, out f2);\n                    keyFrame1.Frames.Add(f1);\n                    keyFrame2.Frames.Add(f2);\n                }\n                keyFrame1.Delay = time;\n                keyFrame2.Delay = this.Delay - time;\n            }\n\n            int IGifFrame.Delay\n            {\n                get { return this.Delay; }\n            }\n\n            Rectangle IGifFrame.Region\n            {\n                get\n                {\n                    Rectangle rect = Rectangle.Empty;\n                    foreach (var f in this.Frames)\n                    {\n                        var newRect = ((IGifFrame)f).Region;\n                        rect = rect.Size.IsEmpty ? newRect : Rectangle.Union(rect, newRect);\n                    }\n                    return rect.Size.IsEmpty ? Rectangle.Empty : rect;\n                }\n            }\n\n            void IGifFrame.Draw(Graphics g, Rectangle canvasRect)\n            {\n                foreach (var f in this.Frames)\n                {\n                    ((IGifFrame)f).Draw(g, canvasRect);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/GifFrame.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Imaging;\n\nnamespace WzComparerR2.Common\n{\n    public class GifFrame : IGifFrame\n    {\n        public GifFrame()\n        {\n            this.A0 = 255;\n            this.A1 = 255;\n        }\n\n        public GifFrame(Bitmap bitmap)\n            : this(bitmap, Point.Empty, 0)\n        {\n        }\n\n        public GifFrame(Bitmap bitmap, int delay)\n            : this(bitmap, Point.Empty, delay)\n        {\n        }\n\n        public GifFrame(Bitmap bitmap, Point origin, int delay)\n            : this()\n        {\n            this.Bitmap = bitmap;\n            this.Origin = origin;\n            this.Delay = delay;\n        }\n\n        public Bitmap Bitmap { get; set; }\n        public Point Origin { get; set; }\n        public int Delay { get; set; }\n        public int A0 { get; set; }\n        public int A1 { get; set; }\n\n        int IGifFrame.Delay\n        {\n            get { return this.Delay; }\n        }\n\n        Rectangle IGifFrame.Region\n        {\n            get\n            {\n                var size = this.Bitmap == null ? Size.Empty : this.Bitmap.Size;\n                return new Rectangle(-this.Origin.X, -this.Origin.Y, size.Width, size.Height);\n            }\n        }\n\n        void IGifFrame.Draw(Graphics g, Rectangle canvasRect)\n        {\n            if (this.Bitmap == null)\n            {\n                return;\n            }\n\n            Point pos = new Point(-this.Origin.X - canvasRect.X, -this.Origin.Y - canvasRect.Y);\n\n            if (A0 >= 255)\n            {\n                g.DrawImage(this.Bitmap, pos);\n            }\n            else if (A0 > 0)\n            {\n                var imageAttr = new ImageAttributes();\n                float a = A0 / 255f;\n                var mt = new ColorMatrix(new[]{\n                        new float[]{1,0,0,0,0},\n                        new float[]{0,1,0,0,0},\n                        new float[]{0,0,1,0,0},\n                        new float[]{0,0,0,a,0},\n                        new float[]{0,0,0,0,1},\n                    });\n                imageAttr.SetColorMatrix(mt);\n                g.DrawImage(this.Bitmap,\n                    new Rectangle(pos, this.Bitmap.Size),\n                    0, 0, this.Bitmap.Width, this.Bitmap.Height, GraphicsUnit.Pixel,\n                    imageAttr);\n                imageAttr.Dispose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/GifLayer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Common\n{\n    public class GifLayer\n    {\n        public GifLayer()\n        {\n            this.Frames = new List<GifFrame>();\n        }\n\n        public List<GifFrame> Frames { get; private set; }\n\n        public void AddFrame(GifFrame frame)\n        {\n            this.Frames.Add(frame);\n        }\n\n        public void AddBlank(int delay)\n        {\n            this.Frames.Add(new GifFrame(null, delay));\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/GlobalFindNodeFunction.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2\n{\n    public delegate Wz_Node GlobalFindNodeFunction(string fullPath);\n}\n"
  },
  {
    "path": "WzComparerR2.Common/IGifFrame.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.Common\n{\n    public interface IGifFrame\n    {\n        Rectangle Region { get; }\n        int Delay { get; }\n        void Draw(Graphics g, Rectangle canvasRect);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/ImageDataObject.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Windows.Forms;\nusing System.Drawing;\nusing System.IO;\n\nnamespace WzComparerR2.Common\n{\n    public class ImageDataObject : DataObject\n    {\n        public ImageDataObject(Image image, string fileName)\n        {\n            this.Image = image;\n            this.FileName = fileName;\n\n            this.SetData(DataFormats.Bitmap, fileName);\n            this.SetData(DataFormats.FileDrop, fileName);\n            this.SetData(QQ_RichEdit_Format, new MemoryStream(new byte[0]));\n            this.SetData(QQ_Unicode_RichEdit_Format, new MemoryStream(new byte[0]));\n        }\n\n        public Image Image { get; private set; }\n        public string FileName { get; private set; }\n\n        private static readonly string QQ_RichEdit_Format = \"QQ_RichEdit_Format\";\n        private static readonly string QQ_Unicode_RichEdit_Format = \"QQ_Unicode_RichEdit_Format\";\n\n        public override object GetData(string format, bool autoConvert)\n        {\n            if (format == DataFormats.Bitmap\n                || format == typeof(Bitmap).FullName)\n            {\n                PrepareImageFile();\n                base.SetData(DataFormats.Bitmap, this.FileName);\n            }\n            else if (format == DataFormats.FileDrop\n                || format == \"FileName\"\n                || format == \"FileNameW\")\n            {\n                PrepareImageFile();\n                base.SetData(DataFormats.FileDrop, new string[] { this.FileName });\n            }\n            else if (format == QQ_RichEdit_Format)\n            {\n                PrepareImageFile();\n                byte[] buffer = Encoding.Default.GetBytes(GetQQRichFormatString());\n                this.SetData(QQ_RichEdit_Format, new MemoryStream(buffer));\n            }\n            else if (format == QQ_Unicode_RichEdit_Format)\n            {\n                PrepareImageFile();\n                byte[] buffer = Encoding.Unicode.GetBytes(GetQQRichFormatString());\n                this.SetData(QQ_Unicode_RichEdit_Format, new MemoryStream(buffer));\n            }\n\n            return base.GetData(format, autoConvert);\n        }\n\n        private void PrepareImageFile()\n        {\n            string fileName = this.FileName;\n            string tempDir = new DirectoryInfo(Environment.GetEnvironmentVariable(\"TEMP\")).FullName;\n            bool willSaveImage = false;\n            if (string.IsNullOrEmpty(fileName))\n            {\n                fileName = Path.Combine(tempDir, Path.GetRandomFileName());\n                willSaveImage = true;\n            }\n            else\n            {\n                if (string.IsNullOrEmpty(Path.GetDirectoryName(fileName)))//没有文件夹 保存文件\n                {\n                    fileName = Path.Combine(tempDir, fileName);\n                    if (File.Exists(fileName))\n                    {\n                        string fileNameNoExt = Path.GetFileNameWithoutExtension(fileName);\n                        string ext = Path.GetExtension(fileName);\n                        for (int i = 1; ; i++)\n                        {\n                            fileName = Path.Combine(tempDir, string.Format(\"{0}({1}){2}\", fileNameNoExt, i, ext));\n                            if (!File.Exists(fileName))\n                            {\n                                break;\n                            }\n                        }\n                    }\n                    willSaveImage = true;\n                }\n            }\n\n            if (willSaveImage)\n            {\n                Image.Save(fileName, Image.RawFormat);\n                this.FileName = fileName;\n            }\n        }\n\n        private string GetQQRichFormatString()\n        {\n            return string.Format(@\"<QQRichEditFormat><Info version=\"\"1001\"\"></Info><EditElement type=\"\"1\"\" filepath=\"\"{0}\"\" shortcut=\"\"\"\"></EditElement></QQRichEditFormat>\", FileName);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的常规信息通过以下\n// 特性集控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2.Common\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"WzComparerR2.Common\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2015-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 使此程序集中的类型\n// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型，\n// 则将该类型上的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"b7285749-1724-4f18-9824-7d067ac26402\")]\n\n// 程序集的版本信息由下面四个值组成:\n//\n//      主版本\n//      次版本 \n//      生成号\n//      修订号\n//\n// 可以指定所有这些值，也可以使用“生成号”和“修订号”的默认值，\n// 方法是按如下所示使用“*”:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/AnimationGraphics.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.Animation;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.Rendering\n{\n    public class AnimationGraphics\n    {\n        public AnimationGraphics(GraphicsDevice graphicsDevice)\n            : this (graphicsDevice, new SpriteBatch(graphicsDevice))\n        {\n        }\n\n        public AnimationGraphics(GraphicsDevice graphicsDevice, SpriteBatch sprite)\n        {\n            this.GraphicsDevice = graphicsDevice;\n            this.sprite = sprite;\n            this.spineRenderer = new Spine.SkeletonRenderer(graphicsDevice);\n            this.blendState = StateEx.NonPremultipled_Hidef();\n        }\n\n        public GraphicsDevice GraphicsDevice { get; private set; }\n\n        private SpriteBatch sprite;\n        private Spine.SkeletonRenderer spineRenderer;\n        private BlendState blendState;\n\n        public void Draw(FrameAnimator animator, Matrix world)\n        {\n            Frame frame = animator.CurrentFrame;\n            if (frame != null && frame.Texture != null)\n            {\n                if (animator.Position != Point.Zero)\n                {\n                    world *= Matrix.CreateTranslation(animator.Position.X, animator.Position.Y, 0);\n                }\n\n                sprite.Begin(SpriteSortMode.Deferred, this.blendState, transformMatrix: world);\n                sprite.Draw(frame.Texture,\n                    Vector2.Zero,\n                    frame.AtlasRect,\n                    new Color(Color.White, frame.A0),\n                    0,\n                    frame.Origin.ToVector2(),\n                    1,\n                    SpriteEffects.None,\n                    0);\n                sprite.End();\n            }\n        }\n\n        public void Draw(ISpineAnimator animator, Matrix world)\n        {\n            if (animator is AnimationItem aniItem && aniItem.Position != Point.Zero)\n            {\n                world *= Matrix.CreateTranslation(aniItem.Position.X, aniItem.Position.Y, 0);\n            }\n\n            spineRenderer.PremultipliedAlpha = animator.Data.PremultipliedAlpha;\n            if (spineRenderer.Effect is BasicEffect basicEff)\n            {\n                basicEff.World = world;\n                basicEff.Projection = Matrix.CreateOrthographicOffCenter(0, this.GraphicsDevice.Viewport.Width, this.GraphicsDevice.Viewport.Height, 0, 1, 0);\n            }\n\n            spineRenderer.Begin();\n            animator.Render(spineRenderer);\n            spineRenderer.End();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/BlendEx.cs",
    "content": "﻿using System;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering\n{\n    public static class StateEx\n    {\n        public static BlendState NonPremultipled_Hidef() => new BlendState()\n        {\n            AlphaSourceBlend = Blend.One,\n            AlphaDestinationBlend = Blend.InverseSourceAlpha,\n            AlphaBlendFunction = BlendFunction.Add,\n            ColorSourceBlend = Blend.SourceAlpha,\n            ColorDestinationBlend = Blend.InverseSourceAlpha,\n            ColorBlendFunction = BlendFunction.Add,\n        };\n\n        public static BlendState SrcAlphaMask() => new BlendState()\n        {\n            AlphaSourceBlend = Blend.Zero,\n            AlphaDestinationBlend = Blend.InverseSourceAlpha,\n            AlphaBlendFunction = BlendFunction.Add,\n            ColorSourceBlend = Blend.Zero,\n            ColorDestinationBlend = Blend.InverseSourceAlpha,\n            ColorBlendFunction = BlendFunction.Add,\n        };\n\n        public static BlendState MultiplyRGB() => new BlendState()\n        {\n            AlphaSourceBlend = Blend.Zero,\n            AlphaDestinationBlend = Blend.One,\n            AlphaBlendFunction = BlendFunction.Add,\n            ColorSourceBlend = Blend.Zero,\n            ColorDestinationBlend = Blend.SourceColor,\n            ColorBlendFunction = BlendFunction.Add,\n        };\n\n        public static RasterizerState Scissor() => new RasterizerState()\n        {\n            ScissorTestEnable = true,\n            CullMode = CullMode.None,\n            MultiSampleAntiAlias = false,\n            FillMode = FillMode.Solid\n        };\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/D2DContext.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing SharpDX.Direct2D1;\nusing SharpDX.DirectWrite;\nusing SharpDX.DXGI;\n\nnamespace WzComparerR2.Rendering\n{\n    public sealed class D2DContext : IDisposable\n    {\n        public Surface DxgiSurface { get; internal set; }\n        public RenderTarget D2DRenderTarget { get; internal set; }\n        \n        internal bool IsBeginEndPair { get; private set; }\n        private SolidColorBrush cachedBrush;\n\n\n        public Brush GetBrush(Microsoft.Xna.Framework.Color color)\n        {\n            return this.GetBrush(color.XnaToDxColor());\n        }\n\n        public Brush GetBrush(SharpDX.Color4 color)\n        {\n            if (this.cachedBrush == null || this.cachedBrush.IsDisposed)\n            {\n                this.cachedBrush = new SolidColorBrush(this.D2DRenderTarget, color);\n            }\n            else\n            {\n                this.cachedBrush.Color = color;\n            }\n            return this.cachedBrush;\n        }\n\n        public void Dispose()\n        {\n            this.cachedBrush?.Dispose();\n            this.DxgiSurface?.Dispose();\n            this.D2DRenderTarget?.Dispose();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/D2DFactory.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Runtime.CompilerServices;\nusing System.Reflection;\nusing Microsoft.Xna.Framework.Graphics;\nusing SharpDX.Direct2D1;\nusing SharpDX.DirectWrite;\n\nnamespace WzComparerR2.Rendering\n{\n    public class D2DFactory : IDisposable\n    {\n        private static D2DFactory _instance;\n\n        public static D2DFactory Instance\n        {\n            get\n            {\n                if (_instance == null || _instance.IsDisposed)\n                {\n                    _instance = new D2DFactory();\n                }\n                return _instance;\n            }\n        }\n\n        private D2DFactory()\n        {\n            this.factory2D = new SharpDX.Direct2D1.Factory();\n            this.factoryDWrite = new SharpDX.DirectWrite.Factory();\n            this.dictContext = new ConditionalWeakTable<SharpDX.DisposeBase, D2DContext>();\n            this.deviceSwapChainField = typeof(GraphicsDevice)\n                    .GetField(\"_swapChain\", BindingFlags.Instance | BindingFlags.NonPublic);\n            this.textureResourceField = typeof(Texture)\n                    .GetField(\"_texture\", BindingFlags.Instance | BindingFlags.NonPublic);\n        }\n\n        public bool IsDisposed { get; private set; }\n\n        internal readonly SharpDX.Direct2D1.Factory factory2D;\n        internal readonly SharpDX.DirectWrite.Factory factoryDWrite;\n        private ConditionalWeakTable<SharpDX.DisposeBase, D2DContext> dictContext;\n        private readonly FieldInfo deviceSwapChainField;\n        private readonly FieldInfo textureResourceField;\n\n        public D2DContext GetContext(GraphicsDevice graphicsDevice)\n        {\n            SharpDX.ComObject obj = GetRenderTargetResource(graphicsDevice);\n            D2DContext context = GetOrCreateContext(obj);\n\n            if (context == null)\n            {\n                return null;\n            }\n\n            AlphaMode alphaMode = AlphaMode.Ignore;\n            if (context.DxgiSurface == null || context.DxgiSurface.IsDisposed)\n            {\n                if (obj is SharpDX.DXGI.SwapChain)\n                {\n                    var swapChain = (SharpDX.DXGI.SwapChain)obj;\n                    context.DxgiSurface = SharpDX.DXGI.Surface.FromSwapChain(swapChain, 0);\n                    alphaMode = AlphaMode.Ignore;\n                }\n                else if (obj is SharpDX.Direct3D11.Resource)\n                {\n                    context.DxgiSurface = obj.QueryInterface<SharpDX.DXGI.Surface>();\n                    alphaMode = AlphaMode.Premultiplied;\n                }\n                else\n                {\n                    return null;\n                }\n            }\n\n            if (context.D2DRenderTarget == null || context.D2DRenderTarget.IsDisposed)\n            {\n                var rtProp = new RenderTargetProperties(new PixelFormat(SharpDX.DXGI.Format.Unknown, alphaMode));\n                var d2drt = new RenderTarget(this.factory2D, context.DxgiSurface, rtProp);\n                d2drt.TextRenderingParams = new RenderingParams(factoryDWrite, 1f, 0f, 0f, PixelGeometry.Flat, RenderingMode.CleartypeGdiClassic);\n                d2drt.TextAntialiasMode = SharpDX.Direct2D1.TextAntialiasMode.Grayscale;\n                context.D2DRenderTarget = d2drt;\n                context.DxgiSurface.Disposing += (o, e) => d2drt.Dispose();\n            }\n            \n            return context;\n        }\n\n        public void ReleaseContext(GraphicsDevice graphicsDevice)\n        {\n            SharpDX.ComObject obj = GetRenderTargetResource(graphicsDevice);\n            D2DContext context;\n            if (this.dictContext.TryGetValue(obj, out context))\n            {\n                context.Dispose();\n            }\n        }\n\n        private SharpDX.ComObject GetRenderTargetResource(GraphicsDevice graphicsDevice)\n        {\n            var rt = graphicsDevice.GetRenderTargets();\n\n            SharpDX.ComObject obj;\n            if (rt.Length <= 0)\n            {\n                obj = (SharpDX.DXGI.SwapChain)this.deviceSwapChainField.GetValue(graphicsDevice);\n            }\n            else\n            {\n                obj = (SharpDX.Direct3D11.Resource)this.textureResourceField.GetValue(rt[0].RenderTarget);\n            }\n\n            return obj;\n        }\n\n        private D2DContext GetOrCreateContext(SharpDX.ComObject comObject)\n        {\n            if (comObject == null)\n            {\n                return null;\n            }\n\n            if (comObject.IsDisposed)\n            {\n                dictContext.Remove(comObject);\n                return null;\n            }\n\n            D2DContext context;\n            if (!this.dictContext.TryGetValue(comObject, out context))\n            {\n                context = new D2DContext();\n                comObject.Disposing += ComObject_Disposing;\n                this.dictContext.Add(comObject, context);\n            }\n            return context;\n        }\n\n        private void ComObject_Disposing(object sender, EventArgs e)\n        {\n            var comObject = sender as SharpDX.ComObject;\n            D2DContext context;\n            if (comObject != null && this.dictContext.TryGetValue(comObject, out context))\n            {\n                context?.Dispose();\n                this.dictContext.Remove(comObject);\n            }\n        }\n\n        ~D2DFactory()\n        {\n            this.Dispose(false);\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing && !this.IsDisposed)\n            {\n                if (this.factory2D != null)\n                {\n                    this.factory2D.Dispose();\n                }\n                if (this.factoryDWrite != null)\n                {\n                    this.factoryDWrite.Dispose();\n                }\n\n                this.dictContext = null;\n                this.IsDisposed = true;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/D2DFont.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Concurrent;\nusing System.Linq;\nusing System.Text;\nusing SharpDX.Direct2D1;\nusing SharpDX.DXGI;\nusing SharpDX.DirectWrite;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Rendering\n{\n    public class D2DFont : IDisposable\n    {\n        public D2DFont(string familyName, float size)\n            : this(familyName, size, false, false)\n        {\n            \n        }\n\n        public D2DFont(string familyName, float size, bool bold, bool italic)\n        {\n            this.FamilyName = familyName;\n            this.Size = size;\n\n            FontWeight weight = bold ? FontWeight.Bold : FontWeight.Normal;\n            FontStyle style = italic ? FontStyle.Italic : FontStyle.Normal;\n            var factory = D2DFactory.Instance.factoryDWrite;\n            this.textFormat = new TextFormat(factory, this.FamilyName, weight, style, this.Size);\n\n            this.CacheFontMetrics();\n            this.Height = this.Height;\n        }\n\n        public D2DFont(System.Drawing.Font font) :\n            this(font.Name, font.SizeInPoints * 96 / 72, font.Bold, font.Italic)\n        {\n\n        }\n\n        public string FamilyName { get; private set; }\n        public float Size { get; private set; }\n\n        public float Height\n        {\n            get\n            {\n                if (this.lineHeight <= 0)\n                {\n                    if (this.metrics.DesignUnitsPerEm > 0)\n                    {\n                        float ratio = this.textFormat.FontSize / this.metrics.DesignUnitsPerEm;\n                        float size = (this.metrics.Ascent + this.metrics.Descent + this.metrics.LineGap) * ratio;\n                        this.lineHeight = (float)Math.Ceiling(size);\n                    }\n                    else\n                    {\n                        this.lineHeight = this.Size;\n                    }\n                }\n\n                return this.lineHeight;\n            }\n            set\n            {\n                LineSpacingMethod method;\n                float lineSpacing;\n                float baseLine;\n                this.textFormat.GetLineSpacing(out method, out lineSpacing, out baseLine);\n                if (method == LineSpacingMethod.Default || baseLine <= 0)\n                {\n                    if (this.metrics.DesignUnitsPerEm > 0)\n                    {\n                        float ratio = this.textFormat.FontSize / metrics.DesignUnitsPerEm;\n                        baseLine = metrics.Ascent * ratio;\n                    }\n                    else\n                    {\n                        baseLine = this.Size;\n                    }\n                }\n                this.textFormat.SetLineSpacing(LineSpacingMethod.Uniform, value, baseLine);\n                this.lineHeight = value;\n            }\n        }\n\n        private readonly TextFormat textFormat;\n        private float lineHeight;\n        private FontMetrics metrics;\n\n        private Font GetMatchingFont()\n        {\n            var fontCollection = this.textFormat.FontCollection;\n            int index;\n            if (fontCollection.FindFamilyName(this.textFormat.FontFamilyName, out index))\n            {\n                using (var family = fontCollection.GetFontFamily(index))\n                {\n                    var font = family.GetFirstMatchingFont(this.textFormat.FontWeight,\n                        this.textFormat.FontStretch,\n                        this.textFormat.FontStyle);\n                    return font;\n                }\n            }\n            return null;\n        }\n\n        private bool CacheFontMetrics()\n        {\n            var font = this.GetMatchingFont();\n            if (font != null)\n            {\n                using (font)\n                {\n                    this.metrics = font.Metrics;\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        internal void DrawText(D2DContext context, string text, Vector2 position, Color color)\n        {\n             this.DrawText(context, text, position, Vector2.Zero, color);\n        }\n\n        internal void DrawText(D2DContext context, string text, Vector2 position, Vector2 size, Color color)\n        {\n            var rt = context.D2DRenderTarget;\n\n            using (var layout = this.LayoutString(text, size.X, size.Y))\n            {\n                rt.DrawTextLayout(new SharpDX.Vector2(position.X, position.Y),\n                    layout,\n                    context.GetBrush(color),\n                    DrawTextOptions.None);\n            }\n        }\n\n        public Vector2 MeasureString(string text)\n        {\n            return this.MeasureString(text, Vector2.Zero);\n        }\n\n        public Vector2 MeasureString(string text, Vector2 size)\n        {\n            using (var layout = this.LayoutString(text, size.X, size.Y))\n            {\n                var metrics = layout.Metrics;\n                if (metrics.LineCount > 0 && this.metrics.DesignUnitsPerEm > 0)\n                {\n                    float ratio = this.textFormat.FontSize / this.metrics.DesignUnitsPerEm;\n                    var gap = this.lineHeight - (this.metrics.Ascent + this.metrics.Descent) * ratio;\n                    if (gap > 0)\n                    {\n                        metrics.Height -= gap;\n                    }\n                }\n\n                return new Vector2(metrics.WidthIncludingTrailingWhitespace, metrics.Height);\n            }\n        }\n\n        private TextLayout LayoutString(string text, float maxWidth, float maxHeight)\n        {\n            if (maxWidth <= 0)\n            {\n                maxWidth = Int16.MaxValue;\n            }\n            var layout = new TextLayout(D2DFactory.Instance.factoryDWrite, text, this.textFormat, maxWidth, 0, 1, false);\n            layout.WordWrapping = WordWrapping.Wrap;\n            layout.TextAlignment = TextAlignment.Leading;\n            layout.ParagraphAlignment = ParagraphAlignment.Near;\n            return layout;\n        }\n\n        ~D2DFont()\n        {\n            this.Dispose(false);\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                this.textFormat.Dispose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/D2DRenderer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering\n{\n    public class D2DRenderer\n    {\n        public D2DRenderer(GraphicsDevice graphicsDevice)\n        {\n            this.GraphicsDevice = graphicsDevice;\n        }\n\n        public GraphicsDevice GraphicsDevice { get; private set; }\n\n        internal bool IsBeginEndPair { get; private set; }\n\n        protected D2DContext context;\n\n        public void Begin()\n        {\n            this.Begin(SharpDX.Matrix3x2.Identity);\n        }\n\n        public void Begin(Microsoft.Xna.Framework.Matrix transform)\n        {\n            var mt3x2 = new SharpDX.Matrix3x2(transform.M11, transform.M12,\n                transform.M21, transform.M22,\n                transform.M41, transform.M42);\n            this.Begin(mt3x2);\n        }\n\n        private void Begin(SharpDX.Matrix3x2 transform)\n        {\n            this.context = D2DFactory.Instance.GetContext(this.GraphicsDevice);\n            if (this.context == null)\n            {\n                throw new Exception(\"Create D2D context failed.\");\n            }\n            this.context.D2DRenderTarget.Transform = transform;\n            this.context.D2DRenderTarget.BeginDraw();\n            this.IsBeginEndPair = true;\n        }\n\n        public void PushClip(Rectangle clipRect)\n        {\n            this.context.D2DRenderTarget.PushAxisAlignedClip(clipRect.XnaToDxRect(), SharpDX.Direct2D1.AntialiasMode.PerPrimitive);\n        }\n\n        public void PopClip()\n        {\n            this.context.D2DRenderTarget.PopAxisAlignedClip();\n        }\n\n        public void DrawString(D2DFont font, string text, Vector2 position, Color color)\n        {\n            font.DrawText(this.context, text, position, color);\n        }\n\n        public void DrawString(D2DFont font, string text, Vector2 position, Vector2 size, Color color)\n        {\n            font.DrawText(this.context, text, position, size, color);\n        }\n\n        public void DrawLine(Vector2 point0, Vector2 point1, float width, Color color)\n        {\n            var rt = this.context.D2DRenderTarget;\n            rt.DrawLine(new SharpDX.Vector2(point0.X, point0.Y),\n                new SharpDX.Vector2(point1.X, point1.Y),\n                this.context.GetBrush(color),\n                width);\n        }\n\n        public void DrawRectangle(Rectangle rectangle, Color color)\n        {\n            var rt = this.context.D2DRenderTarget;\n            rt.DrawRectangle(rectangle.XnaToDxRect(), this.context.GetBrush(color));\n        }\n\n        public void FillRectangle(Rectangle rectangle, Color color)\n        {\n            var rt = this.context.D2DRenderTarget;\n            rt.FillRectangle(rectangle.XnaToDxRect(), this.context.GetBrush(color));\n        }\n\n        public void FillRoundedRectangle(Rectangle rectangle, float cornerRadius, Color color)\n        {\n            var rt = this.context.D2DRenderTarget;\n            var rRect = new SharpDX.Direct2D1.RoundedRectangle()\n            {\n                RadiusX = cornerRadius,\n                RadiusY = cornerRadius,\n                Rect = rectangle.XnaToDxRect()\n            };\n            rt.FillRoundedRectangle(rRect, this.context.GetBrush(color));\n        }\n\n        public void End()\n        {\n            this.context.D2DRenderTarget.EndDraw();\n            this.IsBeginEndPair = false;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/DxExtension.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Rendering\n{\n    public static class DxExtension\n    {\n        public static Color DxToXnaColor(this SharpDX.Color color)\n        {\n            return new Color(color.R, color.G, color.B, color.A);\n        }\n\n        public static SharpDX.Color XnaToDxColor(this Color color)\n        {\n            return SharpDX.Color.FromRgba(color.PackedValue);\n        }\n\n        public static SharpDX.RectangleF XnaToDxRect(this Rectangle rect)\n        {\n            return new SharpDX.RectangleF(rect.X, rect.Y, rect.Width, rect.Height);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/Effect/EffectCompiler.bat",
    "content": "@echo off\nsetlocal\n\nif [%1]==[] goto error\nset monogame_ver=%1\n\n:check\n@echo Check dotnet-mgfxc tool...\ndotnet tool list --global | findstr \"dotnet-mgfxc\" || goto install\n\n:exists\ndotnet tool list --global | findstr \"dotnet-mgfxc.*%monogame_ver%\" >nul 2>&1 || goto uninstall\ngoto build\n\n:uninstall\ndotnet tool uninstall --global dotnet-mgfxc || goto failed\n\n:install\ndotnet tool install --global dotnet-mgfxc --version %monogame_ver% || goto failed\n\n:build\nmgfxc \"PngEffect.fx\" \"PngEffect.%monogame_ver%.mgfxo\" /Profile:DirectX_11 || goto failed\ngoto :eof\n\n:error\n@echo %0 [monogame_ver]\n@echo    monogame_ver: 3.8.0.1641/3.8.1.303\ngoto :failed\n\n:failed\nexit /B 1\n\nendlocal"
  },
  {
    "path": "WzComparerR2.Common/Rendering/Effect/Macros.fxh",
    "content": "//-----------------------------------------------------------------------------\n// Macros.fxh\n//\n// Microsoft XNA Community Game Platform\n// Copyright (C) Microsoft Corporation. All rights reserved.\n//-----------------------------------------------------------------------------\n\n#ifdef SM4\n\n// Macros for targetting shader model 4.0 (DX11)\n\n#define TECHNIQUE(name, vsname, psname ) \\\n\ttechnique name { pass { VertexShader = compile vs_4_0_level_9_1 vsname (); PixelShader = compile ps_4_0_level_9_1 psname(); } }\n\n#define BEGIN_CONSTANTS     cbuffer Parameters : register(b0) {\n#define MATRIX_CONSTANTS\n#define END_CONSTANTS       };\n\n#define _vs(r)\n#define _ps(r)\n#define _cb(r)\n\n#define DECLARE_TEXTURE(Name, index) \\\n    Texture2D<float4> Name : register(t##index); \\\n    sampler Name##Sampler : register(s##index)\n\n#define DECLARE_CUBEMAP(Name, index) \\\n    TextureCube<float4> Name : register(t##index); \\\n    sampler Name##Sampler : register(s##index)\n\n#define SAMPLE_TEXTURE(Name, texCoord)  Name.Sample(Name##Sampler, texCoord)\n#define SAMPLE_CUBEMAP(Name, texCoord)  Name.Sample(Name##Sampler, texCoord)\n\n\n#else\n\n\n// Macros for targetting shader model 2.0 (DX9)\n\n#define TECHNIQUE(name, vsname, psname ) \\\n\ttechnique name { pass { VertexShader = compile vs_2_0 vsname (); PixelShader = compile ps_2_0 psname(); } }\n\n#define BEGIN_CONSTANTS\n#define MATRIX_CONSTANTS\n#define END_CONSTANTS\n\n#define _vs(r)  : register(vs, r)\n#define _ps(r)  : register(ps, r)\n#define _cb(r)\n\n#define DECLARE_TEXTURE(Name, index) \\\n    sampler2D Name : register(s##index);\n\n#define DECLARE_CUBEMAP(Name, index) \\\n    samplerCUBE Name : register(s##index);\n\n#define SAMPLE_TEXTURE(Name, texCoord)  tex2D(Name, texCoord)\n#define SAMPLE_CUBEMAP(Name, texCoord)  texCUBE(Name, texCoord)\n\n\n#endif\n\n\n\n//Extensions\n\n#ifdef SM4\n\n#define TECHNIQUE_SB(name, psname ) \\\n\ttechnique name { pass { PixelShader = compile ps_4_0_level_9_1 psname(); } }\n#else\n\n#define TECHNIQUE_SB(name, psname ) \\\n\ttechnique name { pass { PixelShader = compile ps_2_0 psname(); } }\n#endif\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/Effect/PngEffect.fx",
    "content": "﻿#include \"Macros.fxh\"\n\nDECLARE_TEXTURE(Texture, 0);\n\nBEGIN_CONSTANTS\n\n  float4 mixedColor;\n  float clipAlpha;\n\n  MATRIX_CONSTANTS\n\nEND_CONSTANTS\n\nstruct VSOutput\n{\n\tfloat4 position\t\t: SV_Position;\n\tfloat4 color\t\t: COLOR0;\n\tfloat2 texCoord\t\t: TEXCOORD0;\n};\n\nfloat4 RGBAtoNonPremultiplied(float4 input)\n{\n\tif (input.a <= 1) {\n\t\tinput.rgb /= input.a;\n\t}\n\treturn input;\n}\n\nfloat4 Blend(float4 background, float4 premultipliedColor)\n{\n\treturn float4(background.rgb * (1 - premultipliedColor.a) + premultipliedColor.rgb, 1);\n}\n\nfloat4 PS(VSOutput input) : SV_Target0\n{\n\tfloat4 color = SAMPLE_TEXTURE(Texture, input.texCoord) * input.color;\n\treturn RGBAtoNonPremultiplied(color);\n}\n\nfloat4 PS_AlphaTest(VSOutput input) : SV_Target0\n{\n\tfloat4 color = SAMPLE_TEXTURE(Texture, input.texCoord) * input.color;\n\tclip(color.a <= clipAlpha ? -1 : 1);\n\treturn Blend(mixedColor, color);\n}\n\nTECHNIQUE_SB(tech0, PS);\nTECHNIQUE_SB(tech1, PS_AlphaTest);\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/ConstantBuffer.cs",
    "content": "﻿using Microsoft.Xna.Framework.Graphics;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.Rendering.EffectCompiler\n{\n    public class ConstantBuffer\n    {\n        public string Name { get; set; }\n        public int Slot { get; set; }\n        public int SizeInBytes { get; set; }\n        public List<ShaderParameter> Parameters { get; set; } = new();\n    }\n\n    public class ShaderParameter\n    {\n        public ShaderParameter() \n        { \n        }\n\n        public ShaderParameter(string name, string semantic, int bufferOffset, EffectParameterClass parameterClass, EffectParameterType parameterType, int rowCount, int columnCount, int elementCount)\n        {\n            Name = name;\n            Semantic = semantic;\n            BufferOffset = bufferOffset;\n            ParameterClass = parameterClass;\n            ParameterType = parameterType;\n            RowCount = rowCount;\n            ColumnCount = columnCount;\n            ElementCount = elementCount;\n        }\n\n        public string Name { get; set; }\n        public string Semantic { get; set; }\n        public int BufferOffset { get; set; }\n        public EffectParameterClass ParameterClass { get; set; }\n        public EffectParameterType ParameterType { get; set; }\n        public int RowCount { get; set; }\n        public int ColumnCount { get; set; }\n        public int ElementCount { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/Internal/ConstantBufferData.cs",
    "content": "﻿// Port from https://github.com/MonoGame/MonoGame/blob/develop/Tools/MonoGame.Effect.Compiler/Effect/ConstantBufferData.cs\n\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\n\nnamespace WzComparerR2.Rendering.EffectCompiler.Internal\n{\n    public class ConstantBufferData\n    {\n        public string Name;\n\n        public int Size;\n\n        public List<int> ParameterIndex = new List<int>();\n\n        public List<int> ParameterOffset = new List<int>();\n\n        public List<d3dx_parameter> Parameters = new List<d3dx_parameter>();\n\n        public void Write(BinaryWriter writer)\n        {\n            writer.Write(Name);\n\n            writer.Write((ushort)Size);\n\n            writer.MgfxWriteElementCount(ParameterIndex.Count);\n            for (var i = 0; i < ParameterIndex.Count; i++)\n            {\n                writer.MgfxWriteElementCount(ParameterIndex[i]);\n                writer.Write((ushort)ParameterOffset[i]);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/Internal/D3DXObjects.cs",
    "content": "﻿// Port from https://github.com/MonoGame/MonoGame/blob/develop/Tools/MonoGame.Effect.Compiler/Effect/EffectObject.cs\n\nusing System;\nusing System.Collections.Generic;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering.EffectCompiler.Internal\n{\n    public class d3dx_parameter\n    {\n        public string name;\n        public string semantic;\n        public object data;\n        public EffectParameterClass class_;\n        public EffectParameterType type;\n        public uint rows;\n        public uint columns;\n        public uint element_count;\n        public uint annotation_count = 0;\n        public uint member_count;\n        public uint flags = 0;\n        public uint bytes = 0;\n\n        public int bufferIndex = -1;\n        public int bufferOffset = -1;\n\n        public d3dx_parameter[] annotation_handles = null;\n        public d3dx_parameter[] member_handles;\n\n        public override string ToString()\n        {\n            if (rows > 0 || columns > 0)\n                return string.Format(\"{0} {1}{2}x{3} {4} : cb{5},{6}\", class_, type, rows, columns, name, bufferIndex, bufferOffset);\n            else\n                return string.Format(\"{0} {1} {2}\", class_, type, name);\n        }\n    }\n\n    public class d3dx_state\n    {\n        public uint operation;\n        public uint index;\n        public STATE_TYPE type;\n        public d3dx_parameter parameter;\n    }\n\n    public class d3dx_sampler\n    {\n        public uint state_count = 0;\n        public d3dx_state[] states = null;\n    }\n\n    public enum STATE_TYPE\n    {\n        CONSTANT,\n        PARAMETER,\n        EXPRESSION,\n        EXPRESSIONINDEX,\n    }\n\n    public enum STATE_CLASS\n    {\n        LIGHTENABLE,\n        FVF,\n        LIGHT,\n        MATERIAL,\n        NPATCHMODE,\n        PIXELSHADER,\n        RENDERSTATE,\n        SETSAMPLER,\n        SAMPLERSTATE,\n        TEXTURE,\n        TEXTURESTAGE,\n        TRANSFORM,\n        VERTEXSHADER,\n        SHADERCONST,\n        UNKNOWN,\n    };\n\n    public class d3dx_pass\n    {\n        public string name;\n        public uint state_count;\n        public uint annotation_count = 0;\n\n        public BlendState blendState;\n        public DepthStencilState depthStencilState;\n        public RasterizerState rasterizerState;\n\n        public d3dx_state[] states;\n        public d3dx_parameter[] annotation_handles = null;\n    }\n\n    public class d3dx_technique\n    {\n        public string name;\n        public uint pass_count;\n        public uint annotation_count = 0;\n\n        public d3dx_parameter[] annotation_handles = null;\n        public d3dx_pass[] pass_handles;\n    }\n\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/Internal/EffectObject.cs",
    "content": "﻿// Port from https://github.com/MonoGame/MonoGame/blob/develop/Tools/MonoGame.Effect.Compiler/Effect/EffectObject.writer.cs\n\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Reflection;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering.EffectCompiler.Internal\n{\n    public class EffectObject\n    {\n        static EffectObject()\n        {\n            Type mgfxHeaderType = typeof(GraphicsDevice).Assembly.GetType(\"Microsoft.Xna.Framework.Graphics.Effect+MGFXHeader\", true);\n            MGFXSignature = (int)mgfxHeaderType.GetField(\"MGFXSignature\", BindingFlags.Static | BindingFlags.Public).GetValue(null);\n            MGFXVersion = (int)mgfxHeaderType.GetField(\"MGFXVersion\", BindingFlags.Static | BindingFlags.Public).GetValue(null);\n\n            Type hashType = typeof(GraphicsDevice).Assembly.GetType(\"MonoGame.Framework.Utilities.Hash\", true);\n            var computeHashMethod = hashType.GetMethod(\"ComputeHash\", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(Stream) }, null);\n            ComputeHash = (Func<Stream, int>)computeHashMethod.CreateDelegate(typeof(Func<Stream, int>));\n        }\n\n        public static readonly int MGFXSignature;\n        public static readonly int MGFXVersion;\n        public static readonly int ShaderProfile = 1; //Directx_11\n        public static readonly Func<Stream, int> ComputeHash;\n\n\n        public d3dx_parameter[] Parameters;\n        public d3dx_technique[] Techniques;\n        public List<ShaderData> Shaders;\n        public List<ConstantBufferData> ConstantBuffers;\n\n        /// <summary>\n        /// Writes the effect for loading later.\n        /// </summary>\n        public void Write(BinaryWriter writer)\n        {\n            writer.Write(MGFXSignature);\n            writer.Write((byte)MGFXVersion);\n            writer.Write((byte)ShaderProfile);\n\n            // Write the rest to a memory stream.\n            using (MemoryStream memStream = new MemoryStream())\n            using (BinaryWriter memWriter = new BinaryWriter(memStream))\n            {\n                // Write all the constant buffers.\n                memWriter.MgfxWriteElementCount(ConstantBuffers.Count);\n                foreach (var cbuffer in ConstantBuffers)\n                    cbuffer.Write(memWriter);\n\n                // Write all the shaders.\n                memWriter.MgfxWriteElementCount(Shaders.Count);\n                foreach (var shader in Shaders)\n                    shader.Write(memWriter);\n\n                // Write the parameters.\n                WriteParameters(memWriter, Parameters, Parameters.Length);\n\n                // Write the techniques.\n                memWriter.MgfxWriteElementCount(Techniques.Length);\n                foreach (var technique in Techniques)\n                {\n                    memWriter.Write(technique.name);\n                    WriteAnnotations(memWriter, technique.annotation_handles);\n\n                    // Write the passes.\n                    memWriter.MgfxWriteElementCount((int)technique.pass_count);\n                    for (var p = 0; p < technique.pass_count; p++)\n                    {\n                        var pass = technique.pass_handles[p];\n\n                        memWriter.Write(pass.name);\n                        WriteAnnotations(memWriter, pass.annotation_handles);\n\n                        // Write the index for the vertex and pixel shaders.\n                        memWriter.MgfxWriteElementCount(GetShaderIndex(STATE_CLASS.VERTEXSHADER, pass.states));\n                        memWriter.MgfxWriteElementCount(GetShaderIndex(STATE_CLASS.PIXELSHADER, pass.states));\n\n                        // Write the state objects too!\n                        if (pass.blendState != null)\n                        {\n                            memWriter.Write(true);\n                            memWriter.Write((byte)pass.blendState.AlphaBlendFunction);\n                            memWriter.Write((byte)pass.blendState.AlphaDestinationBlend);\n                            memWriter.Write((byte)pass.blendState.AlphaSourceBlend);\n                            memWriter.Write(pass.blendState.BlendFactor.R);\n                            memWriter.Write(pass.blendState.BlendFactor.G);\n                            memWriter.Write(pass.blendState.BlendFactor.B);\n                            memWriter.Write(pass.blendState.BlendFactor.A);\n                            memWriter.Write((byte)pass.blendState.ColorBlendFunction);\n                            memWriter.Write((byte)pass.blendState.ColorDestinationBlend);\n                            memWriter.Write((byte)pass.blendState.ColorSourceBlend);\n                            memWriter.Write((byte)pass.blendState.ColorWriteChannels);\n                            memWriter.Write((byte)pass.blendState.ColorWriteChannels1);\n                            memWriter.Write((byte)pass.blendState.ColorWriteChannels2);\n                            memWriter.Write((byte)pass.blendState.ColorWriteChannels3);\n                            memWriter.Write(pass.blendState.MultiSampleMask);\n                        }\n                        else\n                            memWriter.Write(false);\n\n                        if (pass.depthStencilState != null)\n                        {\n                            memWriter.Write(true);\n                            memWriter.Write((byte)pass.depthStencilState.CounterClockwiseStencilDepthBufferFail);\n                            memWriter.Write((byte)pass.depthStencilState.CounterClockwiseStencilFail);\n                            memWriter.Write((byte)pass.depthStencilState.CounterClockwiseStencilFunction);\n                            memWriter.Write((byte)pass.depthStencilState.CounterClockwiseStencilPass);\n                            memWriter.Write(pass.depthStencilState.DepthBufferEnable);\n                            memWriter.Write((byte)pass.depthStencilState.DepthBufferFunction);\n                            memWriter.Write(pass.depthStencilState.DepthBufferWriteEnable);\n                            memWriter.Write(pass.depthStencilState.ReferenceStencil);\n                            memWriter.Write((byte)pass.depthStencilState.StencilDepthBufferFail);\n                            memWriter.Write(pass.depthStencilState.StencilEnable);\n                            memWriter.Write((byte)pass.depthStencilState.StencilFail);\n                            memWriter.Write((byte)pass.depthStencilState.StencilFunction);\n                            memWriter.Write(pass.depthStencilState.StencilMask);\n                            memWriter.Write((byte)pass.depthStencilState.StencilPass);\n                            memWriter.Write(pass.depthStencilState.StencilWriteMask);\n                            memWriter.Write(pass.depthStencilState.TwoSidedStencilMode);\n                        }\n                        else\n                            memWriter.Write(false);\n\n                        if (pass.rasterizerState != null)\n                        {\n                            memWriter.Write(true);\n                            memWriter.Write((byte)pass.rasterizerState.CullMode);\n                            memWriter.Write(pass.rasterizerState.DepthBias);\n                            memWriter.Write((byte)pass.rasterizerState.FillMode);\n                            memWriter.Write(pass.rasterizerState.MultiSampleAntiAlias);\n                            memWriter.Write(pass.rasterizerState.ScissorTestEnable);\n                            memWriter.Write(pass.rasterizerState.SlopeScaleDepthBias);\n                        }\n                        else\n                            memWriter.Write(false);\n                    }\n                }\n\n                // Calculate a hash code from memory stream\n                // and write it to the header.\n                \n                var effectKey = ComputeHash(memStream);\n                writer.Write(effectKey);\n\n                //write content from memory stream to final stream.\n                memStream.WriteTo(writer.BaseStream);\n            }\n\n            // Write a tail to be used by the reader for validation.\n            if (MGFXVersion >= 10)\n                writer.Write(MGFXSignature);\n        }\n\n        private static void WriteParameters(BinaryWriter writer, d3dx_parameter[] parameters, int count)\n        {\n            if (MGFXVersion < 10) writer.Write7BitEncodedInt(count);\n            else writer.Write(count);\n            for (var i = 0; i < count; i++)\n                WriteParameter(writer, parameters[i]);\n        }\n\n        private static void WriteParameter(BinaryWriter writer, d3dx_parameter param)\n        {\n            writer.Write((byte)param.class_);\n            writer.Write((byte)param.type);\n\n            writer.Write(param.name);\n            writer.Write(param.semantic ?? string.Empty);\n            WriteAnnotations(writer, param.annotation_handles);\n\n            writer.Write((byte)param.rows);\n            writer.Write((byte)param.columns);\n\n            // Write the elements or struct members.\n            WriteParameters(writer, param.member_handles, (int)param.element_count);\n            WriteParameters(writer, param.member_handles, (int)param.member_count);\n\n            if (param.element_count == 0 && param.member_count == 0)\n            {\n                switch (param.type)\n                {\n                    case EffectParameterType.Bool:\n                    case EffectParameterType.Int32:\n                    case EffectParameterType.Single:\n                        writer.Write((byte[])param.data);\n                        break;\n                }\n            }\n        }\n\n        private static void WriteAnnotations(BinaryWriter writer, d3dx_parameter[] annotations)\n        {\n            // annotation is not supported yet\n            writer.MgfxWriteElementCount(0);\n            //var count = annotations == null ? 0 : annotations.Length;\n            //writer.Write(count);\n            //for (var i = 0; i < count; i++)\n            //\tWriteParameter(writer, annotations[i]);\n        }\n\n        internal static int GetShaderIndex(STATE_CLASS type, d3dx_state[] states)\n        {\n            foreach (var state in states)\n            {\n                var class_ = (STATE_CLASS)state.operation;\n                if (class_ != type)\n                    continue;\n\n                if (state.type != STATE_TYPE.CONSTANT)\n                    throw new NotSupportedException(\"We do not support shader expressions!\");\n\n                return (int)state.parameter.data;\n            }\n\n            return -1;\n        }\n    }\n\n    internal static class EffectWriterExtensions\n    {\n        public static void MgfxWriteElementCount(this BinaryWriter writer, int value)\n        {\n            if (EffectObject.MGFXVersion < 10)\n            {\n                writer.Write((byte)value);\n            }\n            else\n            {\n                writer.Write(value);\n            }\n        }\n\n#if NETFRAMEWORK\n        private static Action<BinaryWriter, int> write7BitEncodedIntFunc;\n        public static void Write7BitEncodedInt(this BinaryWriter writer, int value)\n        {\n            if (write7BitEncodedIntFunc == null)\n            {\n                MethodInfo method = typeof(BinaryWriter).GetMethod(\"Write7BitEncodedInt\", BindingFlags.Instance | BindingFlags.NonPublic);\n                write7BitEncodedIntFunc = (Action<BinaryWriter, int>)method.CreateDelegate(typeof(Action<BinaryWriter, int>));\n            }\n            write7BitEncodedIntFunc(writer, value);\n        }\n#endif\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/Internal/ShaderData.cs",
    "content": "﻿// Port from https://github.com/MonoGame/MonoGame/blob/develop/Tools/MonoGame.Effect.Compiler/Effect/ShaderData.cs\n\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering.EffectCompiler.Internal\n{\n    public class ShaderData\n    {\n        public int[] _cbuffers;\n\n        public Sampler[] _samplers;\n\n        public Attribute[] _attributes;\n\n        public byte[] ShaderCode;\n\n        public bool IsVertexShader;\n\n        public struct Sampler\n        {\n            public SamplerType type;\n            public int textureSlot;\n            public int samplerSlot;\n            public string samplerName;\n            public string parameterName;\n            public int parameter;\n            public SamplerState state;\n        }\n\n        public struct Attribute\n        {\n            public string name;\n            public VertexElementUsage usage;\n            public int index;\n            public int location;\n        }\n\n        public void Write(BinaryWriter writer)\n        {\n            writer.Write(IsVertexShader);\n\n            writer.Write(ShaderCode.Length);\n            writer.Write(ShaderCode);\n\n            writer.Write((byte)_samplers.Length);\n            foreach (var sampler in _samplers)\n            {\n                writer.Write((byte)sampler.type);\n                writer.Write((byte)sampler.textureSlot);\n                writer.Write((byte)sampler.samplerSlot);\n\n                if (sampler.state != null)\n                {\n                    writer.Write(true);\n                    writer.Write((byte)sampler.state.AddressU);\n                    writer.Write((byte)sampler.state.AddressV);\n                    writer.Write((byte)sampler.state.AddressW);\n                    writer.Write(sampler.state.BorderColor.R);\n                    writer.Write(sampler.state.BorderColor.G);\n                    writer.Write(sampler.state.BorderColor.B);\n                    writer.Write(sampler.state.BorderColor.A);\n                    writer.Write((byte)sampler.state.Filter);\n                    writer.Write(sampler.state.MaxAnisotropy);\n                    writer.Write(sampler.state.MaxMipLevel);\n                    writer.Write(sampler.state.MipMapLevelOfDetailBias);\n                }\n                else\n                    writer.Write(false);\n\n                writer.Write(sampler.samplerName);\n\n                writer.Write((byte)sampler.parameter);\n            }\n\n            writer.Write((byte)_cbuffers.Length);\n            foreach (var cb in _cbuffers)\n                writer.Write((byte)cb);\n\n            writer.Write((byte)_attributes.Length);\n            foreach (var attrib in _attributes)\n            {\n                writer.Write(attrib.name);\n                writer.Write((byte)attrib.usage);\n                writer.Write((byte)attrib.index);\n                writer.Write((short)attrib.location);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/SampleInfo.cs",
    "content": "﻿using System;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering.EffectCompiler\n{\n    public class SamplerInfo\n    {\n        public string Name { get; set; }\n        public string TextureName { get; set; }\n        public SamplerType Type { get; set; }\n        public int TextureSlot { get; set; }\n        public int SamplerSlot { get; set; }\n        public SamplerState State { get; set; }\n    }\n\n    public enum SamplerType\n    {\n        Sampler2D,\n        SamplerCube,\n        SamplerVolume,\n        Sampler1D\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/ShaderConverter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Rendering.EffectCompiler.Internal;\n\nnamespace WzComparerR2.Rendering.EffectCompiler\n{\n    public static class ShaderConverter\n    {\n        public static byte[] D3DShaderByteCodeToMgfx(ReadOnlySpan<byte> shaderByteCode, ShaderStage shaderStage, IReadOnlyList<ConstantBuffer> constantBuffers, IReadOnlyList<SamplerInfo> samplers)\n        {\n            var cbuffers = new List<ConstantBufferData>();\n\n            if (constantBuffers != null)\n            {\n                foreach (var constantBuffer in constantBuffers)\n                {\n                    while (cbuffers.Count <= constantBuffer.Slot)\n                    {\n                        cbuffers.Add(null);\n                    }\n\n                    cbuffers[constantBuffer.Slot] = ConvertConstantBuffer(constantBuffer);\n                }\n            }\n\n            // fill default constant buffer\n            for (int i = 0; i < cbuffers.Count; i++)\n            {\n                if (cbuffers[i] == null)\n                {\n                    cbuffers[i] = new ConstantBufferData()\n                    {\n                        Name = $\"dummy_{i}\",\n                        Size = 16,\n                        Parameters = new List<d3dx_parameter>(),\n                    };\n                }\n            }\n\n            var rawSamplers = new List<ShaderData.Sampler>();\n            if (samplers != null)\n            {\n                foreach (var sampler in samplers)\n                {\n                    rawSamplers.Add(ConvertSampler(sampler));\n                }\n            }\n\n            var shaderData = new ShaderData()\n            {\n                _attributes = Array.Empty<ShaderData.Attribute>(),\n                _samplers = rawSamplers.ToArray(),\n                _cbuffers = Enumerable.Range(0, cbuffers.Count).Select(i=>i).ToArray(),\n                ShaderCode = shaderByteCode.ToArray(),\n                IsVertexShader = shaderStage == ShaderStage.Vertex,\n            };\n\n            var effectObject = new EffectObject();\n            effectObject.ConstantBuffers = cbuffers;\n            effectObject.Shaders = new List<ShaderData>() { shaderData };\n            effectObject.Techniques = new d3dx_technique[1]\n            {\n                new d3dx_technique()\n                {\n                    name = \"tech0\",\n                    pass_count = 1,\n                    pass_handles = new d3dx_pass[1]\n                    {\n                        new d3dx_pass()\n                        {\n                            name = \"pass0\",\n                            blendState = null,\n                            depthStencilState = null,\n                            rasterizerState = null,\n                            state_count = 2,\n                            states = new d3dx_state[2]\n                            {\n                                new d3dx_state()\n                                {\n                                    type = STATE_TYPE.CONSTANT,\n                                    operation = (int)STATE_CLASS.VERTEXSHADER,\n                                    parameter = new d3dx_parameter()\n                                    {\n                                        data = shaderStage == ShaderStage.Vertex ? 0 : -1,\n                                    }\n                                },\n                                new d3dx_state()\n                                {\n                                    type = STATE_TYPE.CONSTANT,\n                                    operation = (int)STATE_CLASS.PIXELSHADER,\n                                    parameter = new d3dx_parameter()\n                                    {\n                                        data = shaderStage == ShaderStage.Pixel ? 0 : -1,\n                                    }\n                                }\n                            },\n                        }\n                    },\n                }\n            };\n\n            var parameters = new List<d3dx_parameter>();\n            foreach (var cb in effectObject.ConstantBuffers)\n            {\n                foreach (var param in cb.Parameters)\n                {\n                    var match = parameters.FindIndex(e => e.name == param.name);\n                    if (match == -1)\n                    {\n                        cb.ParameterIndex.Add(parameters.Count);\n                        parameters.Add(param);\n                    }\n                    else\n                    {\n                        if (param.type != parameters[match].type\n                            || param.rows != parameters[match].rows\n                            || param.columns != parameters[match].columns\n                            || param.element_count != parameters[match].element_count)\n                        {\n                            throw new Exception($\"Parameter {param.name} conflicts with existing parameter.\");\n                        }\n\n                        cb.ParameterIndex.Add(match);\n                    }\n                }\n            }\n\n            foreach (var shader in effectObject.Shaders)\n            {\n                for (var s = 0; s < shader._samplers.Length; s++)\n                {\n                    var sampler = shader._samplers[s];\n\n                    var match = parameters.FindIndex(e => e.name == sampler.parameterName);\n                    if (match == -1)\n                    {\n                        shader._samplers[s].parameter = parameters.Count;\n                        parameters.Add(CreateTextureParamater(sampler));\n                    }\n                    else\n                    {\n                        // TODO: Make sure the type and size of the parameter match up!\n                        shader._samplers[s].parameter = match;\n                    }\n                }\n            }\n\n            effectObject.Parameters = parameters.ToArray();\n\n            using var ms = new MemoryStream();\n            using var writer = new BinaryWriter(ms);\n            effectObject.Write(writer);\n            return ms.ToArray();\n        }\n    \n        private static ConstantBufferData ConvertConstantBuffer(ConstantBuffer constantBuffer)\n        {\n            var cb = new ConstantBufferData()\n            {\n                Name = constantBuffer.Name,\n                Size = constantBuffer.SizeInBytes,\n                ParameterIndex = new List<int>(constantBuffer.Parameters.Count),\n                ParameterOffset = new List<int>(constantBuffer.Parameters.Count),\n                Parameters = new List<d3dx_parameter>(constantBuffer.Parameters.Count),\n            };\n\n            for (int i = 0; i < constantBuffer.Parameters.Count; i++)\n            {\n                var d3dxParam = ConvertShaderParameter(constantBuffer.Parameters[i]);\n                cb.ParameterOffset.Add(d3dxParam.bufferOffset);\n                cb.Parameters.Add(d3dxParam);\n            }\n\n            return cb;\n        }\n\n        private static d3dx_parameter ConvertShaderParameter(ShaderParameter parameter)\n        {\n            var d3dxParam = new d3dx_parameter()\n            {\n                name = parameter.Name,\n                semantic = parameter.Semantic,\n                bufferOffset = parameter.BufferOffset,\n                rows = (uint)parameter.RowCount,\n                columns = (uint)parameter.ColumnCount,\n                class_ = parameter.ParameterClass,\n                type = parameter.ParameterType,\n                element_count = 0,\n                member_count = 0,\n            };\n\n            if (parameter.ElementCount > 0) // array\n            {\n                d3dxParam.element_count = (uint)parameter.ElementCount;\n                d3dxParam.member_handles = new d3dx_parameter[parameter.ElementCount];\n                for (int i = 0; i < d3dxParam.member_handles.Length; i++)\n                {\n                    d3dxParam.member_handles[i] = new d3dx_parameter()\n                    {\n                        name = string.Empty,\n                        semantic = string.Empty,\n                        rows = d3dxParam.rows,\n                        columns = d3dxParam.columns,\n                        class_ = parameter.ParameterClass,\n                        type = parameter.ParameterType,\n                        data = new byte[d3dxParam.rows * d3dxParam.columns * 4],\n                    };\n                }\n                d3dxParam.data = new byte[d3dxParam.rows * d3dxParam.columns * 4];\n            }\n            else\n            {\n                d3dxParam.member_handles = Array.Empty<d3dx_parameter>();\n                d3dxParam.data = new byte[d3dxParam.rows * d3dxParam.columns * 4];\n            }\n\n            return d3dxParam;\n        }\n\n        private static ShaderData.Sampler ConvertSampler(SamplerInfo samplerInfo)\n        {\n            var sampler = new ShaderData.Sampler()\n            {\n                samplerName = samplerInfo.Name,\n                textureSlot = samplerInfo.TextureSlot,\n                samplerSlot = samplerInfo.SamplerSlot,\n                parameterName = samplerInfo.TextureName,\n                parameter = -1,\n                type = samplerInfo.Type,\n                state = samplerInfo.State,\n            };\n            return sampler;\n        }\n\n        private static d3dx_parameter CreateTextureParamater(ShaderData.Sampler sampler)\n        {\n            var d3dxParam = new d3dx_parameter();\n            d3dxParam.class_ = EffectParameterClass.Object;\n            d3dxParam.name = sampler.parameterName;\n            d3dxParam.semantic = string.Empty;\n\n            switch (sampler.type)\n            {\n                case SamplerType.Sampler1D:\n                    d3dxParam.type = EffectParameterType.Texture1D;\n                    break;\n\n                case SamplerType.Sampler2D:\n                    d3dxParam.type = EffectParameterType.Texture2D;\n                    break;\n\n                case SamplerType.SamplerVolume:\n                    d3dxParam.type = EffectParameterType.Texture3D;\n                    break;\n\n                case SamplerType.SamplerCube:\n                    d3dxParam.type = EffectParameterType.TextureCube;\n                    break;\n            }\n            return d3dxParam;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/EffectCompiler/ShaderStage.cs",
    "content": "﻿namespace WzComparerR2.Rendering.EffectCompiler\n{\n    public enum ShaderStage\n    {\n        Vertex,\n        Pixel\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/MonogameUtils.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing GdipColor = System.Drawing.Color;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing SharpDX.Direct3D11;\nusing Texture2D = Microsoft.Xna.Framework.Graphics.Texture2D;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Rendering\n{\n    public static class MonogameUtils\n    {\n        public static Color ToXnaColor(this GdipColor color)\n        {\n            return new Color(color.R, color.G, color.B, color.A);\n        }\n\n        public static Color ToXnaColor(int argbPackedValue)\n        {\n            var bgra = BitConverter.GetBytes(argbPackedValue);\n            return new Color(bgra[2], bgra[1], bgra[0], bgra[3]);\n        }\n\n        public static Color GetXnaColor(this Wz_Node node)\n        {\n            var argbColor = node.GetValueEx<int>(0);\n            return ToXnaColor(argbColor);\n        }\n\n        public static Texture2D CreateMosaic(GraphicsDevice device, Color c0, Color c1, int blockSize)\n        {\n            var t2d = new Texture2D(device, blockSize * 2, blockSize * 2, false, SurfaceFormat.Color);\n            Color[] colorData = new Color[blockSize * blockSize * 4];\n            int offset = blockSize * blockSize * 2;\n            for (int i = 0; i < blockSize; i++)\n            {\n                colorData[i] = c0;\n                colorData[blockSize + i] = c1;\n                colorData[offset + i] = c1;\n                colorData[offset + blockSize + i] = c0;\n            }\n            for (int i = 1; i < blockSize; i++)\n            {\n                Array.Copy(colorData, 0, colorData, blockSize * 2 * i, blockSize * 2);\n                Array.Copy(colorData, offset, colorData, offset + blockSize * 2 * i, blockSize * 2);\n            }\n            t2d.SetData(colorData);\n            return t2d;\n        }\n\n        public static Texture2D ToTexture(this System.Drawing.Bitmap bitmap, GraphicsDevice device)\n        {\n            var t2d = new Texture2D(device, bitmap.Width, bitmap.Height, false, SurfaceFormat.Bgra32);\n            bitmap.ToTexture(t2d, Point.Zero);\n            return t2d;\n        }\n\n        public static void ToTexture(this System.Drawing.Bitmap bitmap, Texture2D texture, Point origin)\n        {\n            var rect = new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height);\n            var bmpData = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,\n                System.Drawing.Imaging.PixelFormat.Format32bppArgb);\n            byte[] buffer = new byte[bmpData.Stride * bmpData.Height];\n            Marshal.Copy(bmpData.Scan0, buffer, 0, buffer.Length);\n            bitmap.UnlockBits(bmpData);\n\n            texture.SetData(0, 0, new Rectangle(origin.X, origin.Y, rect.Width, rect.Height), buffer, 0, buffer.Length);\n        }\n\n        public static Device _d3dDevice(this GraphicsDevice device)\n        {\n            return (Device)device.Handle;\n        }\n\n        public static DeviceContext _d3dContext(this GraphicsDevice device)\n        {\n            var d3dContextField = typeof(GraphicsDevice).GetField(\"_d3dContext\", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);\n            return (DeviceContext)d3dContextField.GetValue(device);\n        }\n\n        public static SharpDX.DXGI.SwapChain _swapChain(this GraphicsDevice device)\n        {\n            var _swapChainField = typeof(GraphicsDevice).GetField(\"_swapChain\", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);\n            return (SharpDX.DXGI.SwapChain)_swapChainField.GetValue(device);\n        }\n\n        public static Resource GetTexture(this Texture texture)\n        {\n            var _getTextureFunc = typeof(Texture).GetMethod(\"GetTexture\", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);\n            return (Resource)_getTextureFunc.Invoke(texture, Array.Empty<object>());\n        }\n\n        public static void CopyBackBuffer(this GraphicsDevice graphicsDevice, Texture2D destTexture)\n        {\n            var pp = graphicsDevice.PresentationParameters;\n            if (pp.BackBufferWidth != destTexture.Width || pp.BackBufferHeight != destTexture.Height || pp.BackBufferFormat != destTexture.Format)\n            {\n                throw new Exception(\"Destination texture size or format does not compatible with back buffer.\");\n            }\n\n            var d3dContext = graphicsDevice._d3dContext();\n            var swapChain = graphicsDevice._swapChain();\n            var dest = destTexture.GetTexture();\n            using SharpDX.Direct3D11.Texture2D source = SharpDX.Direct3D11.Resource.FromSwapChain<SharpDX.Direct3D11.Texture2D>(swapChain, 0);\n            lock (d3dContext)\n            {\n                d3dContext.CopyResource(source, dest);\n            }\n        }\n\n        public static bool IsSupportFormat(this GraphicsDevice device, SharpDX.DXGI.Format format)\n        {\n            var d3dDevice = device._d3dDevice();\n            var fmtSupport = d3dDevice.CheckFormatSupport(format);\n            return (fmtSupport & SharpDX.Direct3D11.FormatSupport.Texture2D) != 0;\n        }\n\n        public static bool IsSupportBgra4444(this GraphicsDevice device)\n        {\n            return device.IsSupportFormat(SharpDX.DXGI.Format.B4G4R4A4_UNorm);\n        }\n\n        public static bool IsSupportBgr565(this GraphicsDevice device)\n        {\n            return device.IsSupportFormat(SharpDX.DXGI.Format.B5G6R5_UNorm);\n        }\n\n        public static bool IsSupportBgra5551(this GraphicsDevice device)\n        {\n            return device.IsSupportFormat(SharpDX.DXGI.Format.B5G5R5A1_UNorm);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/PngEffect.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Reflection;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering\n{\n    public class PngEffect : Effect\n    {\n        public PngEffect(GraphicsDevice graphicDevice)\n            :base(graphicDevice, GetEffectCode())\n        {\n            this.AlphaMixEnabled = false;\n            this.MinMixedAlpha = 255;\n            this.MixedColor = Color.White;\n        }\n\n        public bool AlphaMixEnabled\n        {\n            get { return alphaMixed; }\n            set\n            {\n                this.CurrentTechnique = this.Techniques[value ? \"tech1\" : \"tech0\"];\n                this.alphaMixed = value;\n            }\n        }\n\n        public int MinMixedAlpha\n        {\n            get { return (int)(this.Parameters[\"clipAlpha\"].GetValueSingle() * 255); }\n            set { this.Parameters[\"clipAlpha\"].SetValue((float)value / 255); }\n        }\n\n        public Color MixedColor\n        {\n            get { return new Color(this.Parameters[\"mixedColor\"].GetValueVector4()); }\n            set { this.Parameters[\"mixedColor\"].SetValue(value.ToVector4()); }\n        }\n\n        private bool alphaMixed;\n\n        private static byte[] GetEffectCode()\n        {\n            var asm = Assembly.GetAssembly(typeof(PngEffect));\n            \n            using (var input = asm.GetManifestResourceStream(\"WzComparerR2.Rendering.Effect.PngEffect.mgfxo\"))\n            {\n                byte[] code = new byte[input.Length];\n                input.Read(code, 0, code.Length);\n                return code;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/SpriteBatchEx.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.Drawing.Drawing2D;\nusing GDIColor = System.Drawing.Color;\nusing GDIRect = System.Drawing.Rectangle;\nusing Rectangle = Microsoft.Xna.Framework.Rectangle;\nusing Color = Microsoft.Xna.Framework.Color;\nusing Point = Microsoft.Xna.Framework.Point;\n\nnamespace WzComparerR2.Rendering\n{\n    public class SpriteBatchEx : SpriteBatch\n    {\n        public SpriteBatchEx(GraphicsDevice graphicsDevice)\n            : base(graphicsDevice)\n        {\n            this.singlePixel = CreateSinglePixel();\n        }\n\n        Texture2D singlePixel;\n\n        private Texture2D CreateSinglePixel()\n        {\n            Texture2D pixel = new Texture2D(this.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);\n            pixel.SetData(new Color[] { Color.White });\n            return pixel;\n        }\n\n        public void DrawStringEx(XnaFont xnaFont, string text, Vector2 location, Color color)\n        {\n            DrawStringEx(xnaFont, text, 0, text == null ? 0 : text.Length, location, Vector2.Zero, color);\n        }\n\n        public void DrawStringEx(XnaFont xnaFont, string text, Vector2 location, Vector2 size, Color color)\n        {\n            DrawStringEx(xnaFont, text, 0, text == null ? 0 : text.Length, location, size, color);\n        }\n\n        private void DrawStringEx(XnaFont xnaFont, string text, int startIndex, int length, Vector2 location, Vector2 size, Color color)\n        {\n            IEnumerable<char> e = TextUtils.CreateCharEnumerator(text, startIndex, length);\n            DrawStringEx(xnaFont, e, location, size, color, Vector2.Zero, 0);\n        }\n\n        public void DrawStringEx(XnaFont xnaFont, StringBuilder stringBuilder, Vector2 location, Color color)\n        {\n            DrawStringEx(xnaFont, stringBuilder, 0, stringBuilder == null ? 0 : stringBuilder.Length, location, color, Vector2.Zero);\n        }\n\n        public void DrawStringEx(XnaFont xnaFont, StringBuilder stringBuilder, Vector2 location, Color color, Vector2 origin)\n        {\n            DrawStringEx(xnaFont, stringBuilder, 0, stringBuilder == null ? 0 : stringBuilder.Length, location, color, origin);\n        }\n\n        public void DrawStringEx(XnaFont xnaFont, StringBuilder stringBuilder, int startIndex, int length, Vector2 location, Color color)\n        {\n            DrawStringEx(xnaFont, stringBuilder, startIndex, length, location, color, Vector2.Zero);\n        }\n\n        public void DrawStringEx(XnaFont xnaFont, StringBuilder stringBuilder, int startIndex, int length, Vector2 location, Color color, Vector2 origin)\n        {\n            IEnumerable<char> e = TextUtils.CreateCharEnumerator(stringBuilder, startIndex, length);\n            DrawStringEx(xnaFont, e, location, Vector2.Zero, color, origin, 0);\n        }\n\n        private void DrawStringEx(XnaFont font, IEnumerable<char> text, Vector2 location, Vector2 size, Color color, Vector2 origin, float layerDepth)\n        {\n            if (font == null || text == null)\n            {\n                return;\n            }\n\n            float dx = location.X, dy = location.Y;\n\n            foreach (char c in text)\n            {\n                if (c == '\\r')\n                {\n                    continue;\n                }\n                else if (c == '\\n') //换行符\n                {\n                    dy += font.Height;\n                    dx = location.X;\n                    continue;\n                }\n                else\n                {\n                    Rectangle rect = font.TryGetRect(c);\n                    if (size.X > 0 && dx > location.X && dx + rect.Width > location.X + size.X) //强制换行\n                    {\n                        dy += font.Height;\n                        dx = location.X;\n                    }\n                    base.Draw(font.TextureBuffer, new Vector2(dx, dy), rect, color, 0f, origin, 1f, SpriteEffects.None, layerDepth);\n                    dx += rect.Width;\n                }\n            }\n            location.X = dx;\n            location.Y = dy;\n        }\n\n        public void DrawPath(Point[] path, Color color)\n        {\n            Rectangle[] rectPath = new Rectangle[path.Length];\n            for (int i = 0; i < path.Length - 1; i++)\n            {\n                if (path[i].X == path[i + 1].X)\n                {\n                    int dy = path[i + 1].Y - path[i].Y;\n                    if (dy > 0)\n                    {\n                        rectPath[i] = new Rectangle(path[i].X, path[i].Y, 1, dy);\n                    }\n                    else if (dy < 0)\n                    {\n                        rectPath[i] = new Rectangle(path[i].X, path[i + 1].Y + 1, 1, -dy);\n                    }\n                }\n                else if (path[i].Y == path[i + 1].Y)\n                {\n                    int dx = path[i + 1].X - path[i].X;\n                    if (dx > 0)\n                    {\n                        rectPath[i] = new Rectangle(path[i].X, path[i].Y, dx, 1);\n                    }\n                    else if (dx < 0)\n                    {\n                        rectPath[i] = new Rectangle(path[i + 1].X + 1, path[i].Y, -dx, 1);\n                    }\n                }\n            }\n            if (path[0] != path[path.Length - 1] || path.Length == 1)\n            {\n                rectPath[path.Length - 1] = new Rectangle(\n                    path[path.Length - 1].X,\n                    path[path.Length - 1].Y,\n                    1,\n                    1);\n            }\n            for (int i = 0; i < rectPath.Length; i++)\n            {\n                if (rectPath[i].Width > 0 && rectPath[i].Height > 0)\n                {\n                    this.FillRectangle(rectPath[i], color);\n                }\n            }\n        }\n\n        public void FillRectangle(Rectangle rectangle, Color color)\n        {\n            base.Draw(singlePixel, rectangle, color);\n        }\n\n        public void FillRectangle(Rectangle rectangle, Color color, Vector2 origin)\n        {\n            rectangle.X -= (int)origin.X;\n            rectangle.Y -= (int)origin.Y;\n            base.Draw(singlePixel, rectangle, color);\n        }\n\n        public void FillRoundedRectangle(Rectangle rectangle, Color color)\n        {\n            if (rectangle.Width > 2 && rectangle.Height > 2)\n            {\n                base.Draw(singlePixel, new Rectangle(rectangle.X + 1, rectangle.Y, rectangle.Width - 2, 1), color);\n                base.Draw(singlePixel, new Rectangle(rectangle.X, rectangle.Y + 1, rectangle.Width, rectangle.Height - 2), color);\n                base.Draw(singlePixel, new Rectangle(rectangle.X + 1, rectangle.Bottom - 1, rectangle.Width - 2, 1), color);\n            }\n            else\n            {\n                base.Draw(singlePixel, rectangle, color);\n            }\n        }\n\n        public void DrawRectangle(Rectangle rectangle, Color color)\n        {\n            if (!rectangle.IsEmpty)\n            {\n                Point[] path = new Point[5];\n                path[0] = new Point(rectangle.X, rectangle.Y);\n                path[1] = new Point(path[0].X, rectangle.Y + rectangle.Height);\n                path[2] = new Point(rectangle.X + rectangle.Width, path[1].Y);\n                path[3] = new Point(path[2].X, path[0].Y);\n                path[4] = path[0];\n                this.DrawPath(path, color);\n            }\n        }\n\n        public void DrawLine(Point point1, Point point2, int width, Color color)\n        {\n            if (point1 != point2)\n            {\n                float length = Vector2.Distance(new Vector2(point1.X, point1.Y), new Vector2(point2.X, point2.Y));\n                Rectangle dest = new Rectangle(point1.X, point1.Y, (int)length, width);\n                Vector2 origin = new Vector2(0, 0.5f);\n                float rot = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);\n                this.Draw(this.singlePixel, dest, null, color, rot, origin, SpriteEffects.None, 0);\n            }\n        }\n\n        public void Flush()\n        {\n            this.End();\n            this.GetType().BaseType.GetField(\"_beginCalled\", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)\n                .SetValue(this, true);\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (this.singlePixel != null)\n                {\n                    this.singlePixel.Dispose();\n                }\n            }\n            base.Dispose(disposing);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/SurfaceFormatEx.cs",
    "content": "﻿using System;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering\n{\n    public static class SurfaceFormatEx\n    {\n        public const SurfaceFormat BC7 = (SurfaceFormat)0x101;\n        public const SurfaceFormat R16 = (SurfaceFormat)0x102;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/TextUtils.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.Rendering\n{\n    internal static class TextUtils\n    {\n        public static IEnumerable<char> CreateCharEnumerator(string text, int startIndex, int length)\n        {\n            for (int i = 0; i < length; i++)\n            {\n                yield return text[startIndex + i];\n            }\n        }\n\n        public static IEnumerable<char> CreateCharEnumerator(StringBuilder stringBuilder, int startIndex, int length)\n        {\n            for (int i = 0; i < length; i++)\n            {\n                yield return stringBuilder[startIndex + i];\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/Texture2DEx.cs",
    "content": "﻿using System;\nusing System.Reflection;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing SharpDX.Direct3D11;\nusing Texture2D = Microsoft.Xna.Framework.Graphics.Texture2D;\n\nnamespace WzComparerR2.Rendering\n{\n    public static class Texture2DEx\n    {\n        public static Texture2D CreateEx(GraphicsDevice graphicsDevice, int width, int height, SurfaceFormat format)\n        {\n            SharpDX.DXGI.Format dxgiFormat = format switch\n            {\n                SurfaceFormatEx.BC7 => SharpDX.DXGI.Format.BC7_UNorm,\n                SurfaceFormatEx.R16 => SharpDX.DXGI.Format.R16_UNorm,\n                _ => throw new ArgumentException($\"Unsupported texture format {format}\")\n            };\n            var texture = new Texture2D(graphicsDevice, width, height, false, format);\n            TextureInitialize(texture, dxgiFormat);\n            return texture;\n        }\n\n        public static unsafe void SetDataEx(this Texture2D texture, ReadOnlySpan<byte> data, int pitch)\n        {\n            var region = new Rectangle(0, 0, texture.Width, texture.Height);\n            int expectedDataSize = texture.Format switch\n            {\n                SurfaceFormatEx.BC7 => pitch * (region.Height / 4), // (w/4)*(h/4)*16\n                SurfaceFormatEx.R16 => region.Height * pitch,\n                _ => throw new ArgumentException($\"Unsupported texture format {texture.Format}\")\n            };\n\n            if (data.Length < expectedDataSize)\n                throw new ArgumentException($\"Incorrect data length ({data.Length} < {expectedDataSize}).\", nameof(data));\n            TextureSetData(texture, region, data, pitch);\n        }\n\n        private static void TextureInitialize(Texture2D texture2D, SharpDX.DXGI.Format format)\n        {\n            Texture2DDescription description = new Texture2DDescription\n            {\n                Width = texture2D.Width,\n                Height = texture2D.Height,\n                MipLevels = 1,\n                ArraySize = 1,\n                Format = format,\n                BindFlags = BindFlags.ShaderResource,\n                CpuAccessFlags = CpuAccessFlags.None,\n                Usage = ResourceUsage.Default,\n                OptionFlags = ResourceOptionFlags.None,\n                SampleDescription = new SharpDX.DXGI.SampleDescription()\n                {\n                    Count = 1,\n                    Quality = 0,\n                }\n            };\n            var _device = texture2D.GraphicsDevice._d3dDevice();\n            var _textureField = typeof(Texture).GetField(\"_texture\", BindingFlags.Instance | BindingFlags.NonPublic);\n            Resource dx11Texture = new SharpDX.Direct3D11.Texture2D(_device, description);\n            Resource oldTexture = _textureField.GetValue(texture2D) as Resource;\n            _textureField.SetValue(texture2D, dx11Texture);\n            SharpDX.Utilities.Dispose(ref oldTexture);\n        }\n\n        private static unsafe void TextureSetData(Texture2D texture2D, Rectangle region, ReadOnlySpan<byte> data, int pitch)\n        {\n            fixed (byte* pData = data)\n            {\n                var dataPtr = new IntPtr(pData);\n                var resourceRegion = new ResourceRegion()\n                {\n                    Top = region.Top,\n                    Front = 0,\n                    Back = 1,\n                    Bottom = region.Bottom,\n                    Left = region.Left,\n                    Right = region.Right,\n                };\n                var d3dContext = texture2D.GraphicsDevice._d3dContext();\n                lock (d3dContext)\n                    d3dContext.UpdateSubresource(texture2D.GetTexture(), 0, resourceRegion, dataPtr, pitch, 0);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/WzLibExtension.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.WzLib.Utilities;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.Rendering\n{\n    public static class WzLibExtension\n    {\n        public static Texture2D ToTexture(this Wz_Png png, GraphicsDevice graphicsDevice)\n        {\n            return ToTexture(png, 0, graphicsDevice);\n        }\n\n        public static Texture2D ToTexture(this Wz_Png png, int page, GraphicsDevice graphicsDevice)\n        {\n            var format = GetTextureFormatOfPng(png.Format);\n            if (format == SurfaceFormat.Bgra4444)\n            {\n                //检测是否支持 pre-win8\n                if (!graphicsDevice.IsSupportBgra4444())\n                {\n                    format = SurfaceFormat.Bgra32;\n                }\n            }\n            else if (format == SurfaceFormat.Bgr565)\n            {\n                //检测是否支持 pre-win8\n                if (!graphicsDevice.IsSupportBgr565())\n                {\n                    format = SurfaceFormat.Bgra32;\n                }\n            }\n            else if (format == SurfaceFormat.Bgra5551)\n            {\n                //检测是否支持 pre-win8\n                if (!graphicsDevice.IsSupportBgra5551())\n                {\n                    format = SurfaceFormat.Bgra32;\n                }\n            }\n\n            Texture2D t2d = format switch\n            {\n                SurfaceFormatEx.BC7 => Texture2DEx.CreateEx(graphicsDevice, png.Width & ~3, png.Height & ~3, format),\n                SurfaceFormatEx.R16 => Texture2DEx.CreateEx(graphicsDevice, png.Width, png.Height, format),\n                _ => new Texture2D(graphicsDevice, png.Width, png.Height, false, format),\n            };\n            png.ToTexture(page, t2d, Point.Zero);\n            return t2d;\n        }\n\n        public static void ToTexture(this Wz_Png png, int page, Texture2D texture, Point origin)\n        {\n            Rectangle rect = new Rectangle(origin, new Point(png.Width, png.Height));\n            if (png.Format == Wz_TextureFormat.BC7)\n            {\n                rect.Width = png.Width & ~3;\n                rect.Height = png.Height & ~3;\n            }\n\n            //检查大小\n            if (rect.X < 0 || rect.Y < 0 || rect.Right > texture.Width || rect.Bottom > texture.Height)\n            {\n                throw new ArgumentException(\"Png rectangle is out of bounds.\");\n            }\n\n            if (texture.Format == SurfaceFormat.Bgra32 && png.Format != Wz_TextureFormat.ARGB8888)\n            {\n                // soft decoding\n                using (var bmp = png.ExtractPng(page))\n                {\n                    bmp.ToTexture(texture, origin);\n                }\n            }\n            else if (texture.Format != GetTextureFormatOfPng(png.Format))\n            {\n                throw new ArgumentException($\"Texture format({texture.Format}) does not fit the png form({png.Format}).\");\n            }\n            else\n            {\n                int bufferSize = png.GetRawDataSizePerPage();\n                byte[] rawData = ArrayPool<byte>.Shared.Rent(bufferSize);\n                int actualBytes = png.GetRawData(bufferSize * page, rawData.AsSpan(0, bufferSize));\n                if (actualBytes != bufferSize)\n                {\n                    throw new ArgumentException($\"Not enough bytes have been read. (actual:{actualBytes}, expected:{bufferSize})\");\n                }\n\n                switch (png.Format)\n                {\n                    case Wz_TextureFormat.ARGB4444 when png.ActualScale == 1:\n                    case Wz_TextureFormat.ARGB8888 when png.ActualScale == 1:\n                    case Wz_TextureFormat.ARGB1555 when png.ActualScale == 1:\n                    case Wz_TextureFormat.RGB565 when png.ActualScale == 1:\n                    case Wz_TextureFormat.DXT3 when png.ActualScale == 1:\n                    case Wz_TextureFormat.DXT5 when png.ActualScale == 1:\n                    case Wz_TextureFormat.RGBA1010102 when png.ActualScale == 1:\n                        texture.SetData(0, 0, rect, rawData, 0, bufferSize);\n                        break;\n\n                    case Wz_TextureFormat.RGB565 when png.ActualScale == 16:\n                        int textureDataSize = png.Width * png.Height * 2;\n                        byte[] textureData = ArrayPool<byte>.Shared.Rent(textureDataSize);\n                        ImageCodec.ScalePixels(rawData, 2, png.Width / png.ActualScale, png.Width / png.ActualScale * 2, png.Height / png.ActualScale,\n                            png.ActualScale, png.ActualScale, textureData.AsSpan(0, textureDataSize), png.Width * 2);\n                        texture.SetData(0, 0, rect, textureData, 0, textureDataSize);\n                        ArrayPool<byte>.Shared.Return(textureData);\n                        break;\n\n                    case Wz_TextureFormat.BC7 when png.ActualScale == 1:\n                        texture.SetDataEx(rawData.AsSpan(0, bufferSize), png.Width * 4);\n                        break;\n\n                    case Wz_TextureFormat.R16 when png.ActualScale == 1:\n                        texture.SetDataEx(rawData.AsSpan(0, bufferSize), png.Width * 2);\n                        break;\n\n                    default:\n                        throw new Exception($\"Unsupported png format ({png.Format}, scale={png.ActualScale}).\");\n                }\n\n                ArrayPool<byte>.Shared.Return(rawData);\n            }\n        }\n\n        public static SurfaceFormat GetTextureFormatOfPng(Wz_TextureFormat textureFormat)\n        {\n            switch (textureFormat)\n            {\n                case Wz_TextureFormat.ARGB4444: return SurfaceFormat.Bgra4444;\n                case Wz_TextureFormat.ARGB8888: return SurfaceFormat.Bgra32;\n                case Wz_TextureFormat.ARGB1555: return SurfaceFormat.Bgra5551;\n                case Wz_TextureFormat.RGB565: return SurfaceFormat.Bgr565;\n                case Wz_TextureFormat.R16: return SurfaceFormatEx.R16;\n                case Wz_TextureFormat.DXT3: return SurfaceFormat.Dxt3;\n                case Wz_TextureFormat.DXT5: return SurfaceFormat.Dxt5;\n                case Wz_TextureFormat.A8: return SurfaceFormat.Alpha8;\n                case Wz_TextureFormat.RGBA1010102: return SurfaceFormat.Rgba1010102;\n                case Wz_TextureFormat.DXT1: return SurfaceFormat.Dxt1;\n                case Wz_TextureFormat.BC7: return SurfaceFormatEx.BC7;\n                case Wz_TextureFormat.RGBA32Float: return SurfaceFormat.Vector4;\n\n                default: return SurfaceFormat.Bgra32;\n            }\n        }\n\n        public static Point ToPoint(this Wz_Vector vector)\n        {\n            return new Point(vector.X, vector.Y);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/XnaFont.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\nusing System.Drawing.Imaging;\nusing System.Drawing.Drawing2D;\nusing System.Drawing.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing GDIColor = System.Drawing.Color;\nusing GDIRect = System.Drawing.Rectangle;\nusing Rectangle = Microsoft.Xna.Framework.Rectangle;\n\nnamespace WzComparerR2.Rendering\n{\n    public class XnaFont : IDisposable\n    {\n        public XnaFont(GraphicsDevice graphicsDevice, FontFamily fontFamily, float size)\n            : this(graphicsDevice, new Font(fontFamily, size, GraphicsUnit.Pixel))\n        {\n        }\n\n        public XnaFont(GraphicsDevice graphicsDevice, string familyName, float size)\n            : this(graphicsDevice, new Font(familyName, size, GraphicsUnit.Pixel))\n        {\n        }\n\n        public XnaFont(GraphicsDevice graphicsDevice, Font baseFont)\n        {\n            if (graphicsDevice == null || baseFont == null)\n            {\n                throw new ArgumentNullException();\n            }\n            this.baseFont = baseFont;\n            this.Height = baseFont.Height;\n            this.textureBuffer = new Texture2D(graphicsDevice, 2048, 2048, false, SurfaceFormat.Bgra32);\n            RebuildGdiBuffer(this.baseFont.Height);\n            this.charLocation = new Dictionary<char, Rectangle>();\n        }\n\n        Font baseFont;\n        Texture2D textureBuffer;\n        Bitmap gdiBuffer;\n        Graphics g;\n        Dictionary<char, Rectangle> charLocation;\n        int textureSpaceX;\n        int textureSpaceY;\n        int textureCurLineHeight;\n        int gdiBufferX;\n\n        public Texture2D TextureBuffer\n        {\n            get { return textureBuffer; }\n        }\n\n        public Font BaseFont\n        {\n            get { return baseFont; }\n        }\n\n        public int Height { get; set; }\n\n        public Vector2 MeasureString(string text)\n        {\n            return MeasureString(text, Vector2.Zero);\n        }\n\n        public Vector2 MeasureString(StringBuilder stringBuilder)\n        {\n            return MeasureString(stringBuilder, Vector2.Zero);\n        }\n\n        public Vector2 MeasureString(string text, Vector2 size)\n        {\n            var ie = TextUtils.CreateCharEnumerator(text, 0, text.Length);\n            return MeasureString(ie, size);\n        }\n\n        public Vector2 MeasureString(StringBuilder stringBuilder, Vector2 size)\n        {\n            var ie = TextUtils.CreateCharEnumerator(stringBuilder, 0, stringBuilder.Length);\n            return MeasureString(ie, size);\n        }\n\n        public Vector2 MeasureString(string text, int startIndex, int length)\n        {\n            return MeasureString(TextUtils.CreateCharEnumerator(text, startIndex, length), Vector2.Zero);\n        }\n\n        public Vector2 MeasureString(StringBuilder stringBuilder, int startIndex, int length)\n        {\n            return MeasureString(TextUtils.CreateCharEnumerator(stringBuilder, startIndex, length), Vector2.Zero);\n        }\n\n        public Vector2 MeasureString(IEnumerable<char> text, Vector2 layoutSize)\n        {\n            if (text == null)\n                return Vector2.Zero;\n\n            Size size = new Size();\n            int maxWidth = 0;\n            int lineHeight = 0;\n            foreach (char c in text)\n            {\n                if (c == '\\r')\n                {\n                    continue;\n                }\n                if (c == '\\n')\n                {\n                    if (lineHeight <= 0)\n                    {\n                        //lineHeight = this.baseFont.Height;\n                    }\n                    size.Height += this.baseFont.Height;\n                    maxWidth = Math.Max(maxWidth, size.Width);\n                    size.Width = 0;\n                    lineHeight = 0;\n                }\n                else\n                {\n                    Rectangle rect = TryGetRect(c);\n                    if (layoutSize.X > 0 && size.Width > 0 && size.Width + rect.Width > layoutSize.X) //强制换行\n                    {\n                        size.Height += this.baseFont.Height;\n                        maxWidth = Math.Max(maxWidth, size.Width);\n                        size.Width = 0;\n                        lineHeight = 0;\n                    }\n                    size.Width += rect.Width;\n                    lineHeight = Math.Max(rect.Height, lineHeight);\n                }\n            }\n            size.Width = Math.Max(maxWidth, size.Width);\n            size.Height += lineHeight;\n            if (size.Width <= 0)\n                return Vector2.Zero;\n            return new Vector2(size.Width, size.Height);\n        }\n\n        public Rectangle TryGetRect(char c)\n        {\n            Rectangle rect;\n            if (!this.charLocation.TryGetValue(c, out rect))\n            {\n                rect = this.CreateCharBuffer(c);\n                this.charLocation[c] = rect;\n            }\n            return rect;\n        }\n\n        private Rectangle CreateCharBuffer(char c)\n        {\n            string text = c.ToString();\n            SizeF size;\n\n            size = g.MeasureString(text, this.baseFont, byte.MaxValue, StringFormat.GenericTypographic);\n            GDIRect originRect = new GDIRect(gdiBufferX, 0, (int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));\n            if (originRect.Width == 0)\n            {\n                originRect.Width = (int)(this.baseFont.Size / 2);\n            }\n            if (gdiBuffer.Height < originRect.Height)\n            {\n                RebuildGdiBuffer(originRect.Height);\n                originRect.X = 0; //2012-10-3\n            }\n            if (gdiBufferX + originRect.Width > gdiBuffer.Width)\n            {\n                g.Clear(GDIColor.Transparent);\n                gdiBufferX = 0;\n                originRect.X = 0;\n            }\n            g.DrawString(text, baseFont, Brushes.White, originRect.Location, StringFormat.GenericTypographic);\n\n            //计算范围并且复制图像数据到数组\n            byte[] b = new byte[4 * originRect.Width * originRect.Height];\n            BitmapData data = gdiBuffer.LockBits(originRect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);\n            for (int i = 0; i < originRect.Height; i++)\n            {\n                IntPtr source = IntPtr.Add(data.Scan0, data.Stride * i);\n                System.Runtime.InteropServices.Marshal.Copy(source, b, i * 4 * originRect.Width, 4 * originRect.Width);\n            }\n            gdiBuffer.UnlockBits(data);\n            gdiBufferX += originRect.Width;\n\n            //调整xnaTexture的大小并粘贴图像\n            textureBuffer.GraphicsDevice.Textures[0] = null;\n\n            if (textureSpaceX + originRect.Width > textureBuffer.Width)\n            {\n                textureSpaceX = 0;\n                textureSpaceY += textureCurLineHeight;\n                textureCurLineHeight = 0;\n            }\n            textureCurLineHeight = Math.Max(textureCurLineHeight, originRect.Height);\n            if (textureSpaceY + textureCurLineHeight > textureBuffer.Height)\n            {\n                ClearTextureBuffer();\n                textureSpaceX = 0;\n                textureSpaceY = 0;\n                charLocation.Clear();\n            }\n\n            Rectangle rect = new Rectangle(textureSpaceX, textureSpaceY, originRect.Width, originRect.Height);\n\n            textureBuffer.SetData(0, rect, b, 0, b.Length);\n            textureSpaceX += rect.Width;\n            return rect;\n        }\n\n        private void RebuildGdiBuffer(int height)\n        {\n            if (gdiBuffer != null)\n            {\n                g.Dispose();\n                gdiBuffer.Dispose();\n            }\n            gdiBuffer = new Bitmap(textureBuffer.Width, height, PixelFormat.Format32bppArgb);\n            gdiBufferX = 0;\n            g = Graphics.FromImage(gdiBuffer);\n            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;\n            g.SmoothingMode = SmoothingMode.HighQuality;\n            // g.CompositingMode = CompositingMode.SourceCopy; 乱用这句出事故...\n        }\n\n        private void ClearTextureBuffer()\n        {\n            int[] ary = new int[textureBuffer.Width * textureBuffer.Height];\n            textureBuffer.SetData(ary);\n            ary = null;\n        }\n\n        ~XnaFont()\n        {\n            this.Dispose(false);\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                this.textureBuffer.Dispose();\n                this.g.Dispose();\n                this.baseFont.Dispose();\n                this.gdiBuffer.Dispose();\n            }\n        }\n\n        public static FontFamily GdiLoadFontFile(string fontFileName)\n        {\n            try\n            {\n                PrivateFontCollection font = new PrivateFontCollection();\n                font.AddFontFile(fontFileName);\n                gdiFontCache.Add(font);\n                return font.Families[0];\n            }\n            catch\n            {\n                return null;\n            }\n        }\n\n        private static List<PrivateFontCollection> gdiFontCache = new List<PrivateFontCollection>();\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Rendering/XnaFontRenderer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.Text;\n\nnamespace WzComparerR2.Rendering\n{\n    public class XnaFontRenderer : TextRenderer<XnaFont>\n    {\n        public XnaFontRenderer(SpriteBatchEx spriteBatch)\n        {\n            this.SpriteBatch = spriteBatch;\n        }\n\n        public SpriteBatchEx SpriteBatch { get; set; }\n\n\n        protected override void MeasureRuns(List<Run> runs)\n        {\n            int x = 0;\n            foreach (var run in runs)\n            {\n                if (run.IsBreakLine)\n                {\n                    run.X = x;\n                    run.Length = 0;\n                }\n                else\n                {\n                    var size = base.font.MeasureString(base.sb, run.StartIndex, run.Length);\n                    run.X = x;\n                    run.Width = (int)size.X;\n                    x += run.Width;\n                }\n            }\n        }\n\n        protected override Rectangle[] MeasureChars(int startIndex, int length)\n        {\n            var regions = new Rectangle[length];\n            int x = 0;\n            for (int i = 0; i < length; i++)\n            {\n                var rect = this.font.TryGetRect(this.sb[startIndex + i]);\n                regions[i] = new Rectangle(x, 0, rect.Width, rect.Height);\n                x += rect.Width;\n            }\n            return regions;\n        }\n\n        protected override void Flush(StringBuilder sb, int startIndex, int length, int x, int y, string colorID)\n        {\n            var color = this.GetColor(colorID);\n            var pos = new Microsoft.Xna.Framework.Vector2(x, y);\n            this.SpriteBatch.DrawStringEx(this.font, sb, startIndex, length, pos, color);\n        }\n\n        public virtual Microsoft.Xna.Framework.Color GetColor(string colorID)\n        {\n            switch (colorID)\n            {\n                case \"c\":\n                    return new Microsoft.Xna.Framework.Color(255, 153, 0);\n                default:\n                    return Microsoft.Xna.Framework.Color.White;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/SpineLoader.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Common\n{\n    public static class SpineLoader\n    {\n        private const string AtlasExtension = \".atlas\";\n        private const string JsonExtension = \".json\";\n        private const string SkelExtension = \".skel\";\n        private const string SharedAtlasNodeName = \"atlas\";\n\n        public static SpineDetectionResult Detect(Wz_Node wzNode)\n        {\n            if (wzNode == null || wzNode.ParentNode == null)\n            {\n                return SpineDetectionResult.Failed(\"WzNode or its parent cannot be null.\");\n            }\n           \n            Wz_Node parentNode = wzNode.ParentNode;\n            Wz_Node atlasNode = null;\n            Wz_Node skelNode = null;\n            SkeletonLoadType loadType;\n            SpineVersion spineVersion;\n\n            if (wzNode.Text.EndsWith(AtlasExtension)) // detect from atlasNode\n            {\n                atlasNode = wzNode;\n                string spineName = atlasNode.Text.Substring(0, atlasNode.Text.Length - AtlasExtension.Length);\n\n                // find skel node in sibling nodes\n                if ((skelNode = parentNode.Nodes[spineName + JsonExtension]) != null)\n                {\n                    loadType = SkeletonLoadType.Json;\n                }\n                else if ((skelNode = parentNode.Nodes[spineName] ?? parentNode.Nodes[spineName + SkelExtension]) != null)\n                {\n                    loadType = SkeletonLoadType.Binary;\n                }\n                else\n                {\n                    return SpineDetectionResult.Failed(\"Failed to find skel node.\");\n                }\n            }\n            else // detect from skel node\n            {\n                skelNode = wzNode;\n                string spineName = null;\n                if (skelNode.Text.EndsWith(JsonExtension))\n                {\n                    spineName = skelNode.Text.Substring(0, skelNode.Text.Length - JsonExtension.Length);\n                    loadType = SkeletonLoadType.Json;\n                }\n                else if (skelNode.Text.EndsWith(SkelExtension))\n                {\n                    spineName = skelNode.Text.Substring(0, skelNode.Text.Length - SkelExtension.Length);\n                    loadType = SkeletonLoadType.Binary;\n                }\n                else\n                {\n                    switch (skelNode.ResolveUol()?.Value)\n                    {\n                        case Wz_Sound sound when sound.SoundType == Wz_SoundType.Binary:\n                        case Wz_RawData rawData:\n                            spineName = skelNode.Text;\n                            loadType = SkeletonLoadType.Binary;\n                            break;\n\n                        default:\n                            return SpineDetectionResult.Failed(\"Failed to infer the wzNode as atlasNode or skelNode.\");\n                    }\n                }\n\n                if (spineName != null)\n                {\n                    // find atlas node in sibling nodes\n                    // KMST 1172: the atlas node name could be constant\n                    atlasNode = parentNode.Nodes[spineName + AtlasExtension] ?? parentNode.Nodes[SharedAtlasNodeName];\n                    if (atlasNode == null)\n                    {\n                        return SpineDetectionResult.Failed(\"Failed to find atlas node.\");\n                    }\n                }\n            }\n\n            // resolve uols\n            if ((atlasNode = atlasNode.ResolveUol()) == null)\n            {\n                return SpineDetectionResult.Failed(\"Failed to resolve uol for atlasNode.\");\n            }\n            if ((skelNode = skelNode.ResolveUol()) == null)\n            {\n                return SpineDetectionResult.Failed(\"Failed to resolve uol for skelNode.\");\n            }\n\n            // check atlas data type\n            if (atlasNode.Value is not string)\n            {\n                return SpineDetectionResult.Failed(\"AtlasNode does not contain a string value.\");\n            }\n\n            // inference spine version\n            string versionStr = null;\n            switch (loadType)\n            {\n                case SkeletonLoadType.Json when skelNode.Value is string json:\n                    versionStr = ReadSpineVersionFromJson(json);\n                    break;\n\n                case SkeletonLoadType.Binary when skelNode.Value is Wz_Sound wzSound && wzSound.SoundType == Wz_SoundType.Binary:\n                case SkeletonLoadType.Binary when skelNode.Value is Wz_RawData wzRawData:\n                    var blob = skelNode.Value as IMapleStoryBlob;\n                    var data = new byte[blob.Length];\n                    blob.CopyTo(data, 0);\n                    var ms = new MemoryStream(data);\n                    versionStr = ReadSpineVersionFromBinary(ms, 0, blob.Length);\n                    break;\n            }\n\n            if (versionStr == null)\n            {\n                return SpineDetectionResult.Failed($\"Failed to read version string from skel {loadType}.\");\n            }\n            if (!Version.TryParse(versionStr, out var version))\n            {\n                return SpineDetectionResult.Failed($\"Failed to parse version '{versionStr}'.\");\n            }\n\n            switch (version.Major)\n            {\n                case 2: spineVersion = SpineVersion.V2; break;\n                case 4: spineVersion = SpineVersion.V4; break;\n                default: return SpineDetectionResult.Failed($\"Spine version '{versionStr}' is not supported.\"); ;\n            }\n\n            return SpineDetectionResult.Create(wzNode, atlasNode, skelNode, loadType, spineVersion);\n        }\n\n        private static string ReadSpineVersionFromJson(string jsonText)\n        {\n            // { \"skeleton\": { \"spine\": \"2.1.27\" } }\n            using var sr = new StringReader(jsonText);\n            object skelObj = Spine.Json.Deserialize(sr);\n            if (skelObj is IDictionary<string, object> jRootDict\n                && jRootDict.TryGetValue(\"skeleton\", out var jSkeleton)\n                && jSkeleton is IDictionary<string, object> jSkeletonDict\n                && jSkeletonDict.TryGetValue(\"spine\", out var jSpine)\n                && Version.TryParse(jSpine as string, out var spineVer))\n            {\n                return jSpine as string;\n            }\n            return null;\n        }\n\n        private static string ReadSpineVersionFromBinary(Stream stream, uint offset, int length)\n        {\n            /* \n             * v4 format:\n             * 00-07 hash\n             * 08    version len\n             * 09-XX version (len-1 bytes)\n             * \n             * v2 format:\n             * 00        hash len\n             * 01-XX     hash (len-1 bytes)\n             * (XX+1)    version len\n             * (XX+2)-YY version (len-1 bytes) \n             */\n\n            long oldPos = stream.Position;\n            try\n            {\n                stream.Position = offset;\n                // this method can detect version from v4 and pre-v3 file format.\n                string version = Spine.SkeletonBinary.GetVersionString(stream);\n                return version;\n            }\n            catch \n            {\n                // ignore error;\n                return null;\n            }\n            finally \n            { \n                stream.Position = oldPos;\n            }\n        }\n\n        public static Spine.V2.SkeletonData LoadSkeletonV2(Wz_Node wzNode, Spine.V2.TextureLoader textureLoader)\n        {\n            var detectionResult = Detect(wzNode);\n            if (detectionResult.Success && detectionResult.Version == SpineVersion.V2)\n            {\n                return LoadSkeletonV2(detectionResult, textureLoader);\n            }\n            return null;\n        }\n\n        public static Spine.V2.SkeletonData LoadSkeletonV2(SpineDetectionResult detectionResult, Spine.V2.TextureLoader textureLoader)\n        {\n            using var atlasReader = new StringReader((string)detectionResult.ResolvedAtlasNode.Value);\n            var atlas = new Spine.V2.Atlas(atlasReader, \"\", textureLoader);\n\n            switch (detectionResult.LoadType)\n            {\n                case SkeletonLoadType.Json:\n                    using (var skeletonReader = new StringReader((string)detectionResult.ResolvedSkelNode.Value))\n                    {\n                        var skeletonJson = new Spine.V2.SkeletonJson(atlas);\n                        return skeletonJson.ReadSkeletonData(skeletonReader);\n                    }\n\n                case SkeletonLoadType.Binary when detectionResult.ResolvedSkelNode.Value is IMapleStoryBlob blob:\n                    byte[] data = new byte[blob.Length];\n                    blob.CopyTo(data, 0);\n                    var ms = new MemoryStream(data);\n                    var skeletonBinary = new Spine.V2.SkeletonBinary(atlas);\n                    return skeletonBinary.ReadSkeletonData(ms);\n\n                default:\n                    return null;\n            }\n        }\n\n        public static Spine.SkeletonData LoadSkeletonV4(Wz_Node atlasOrSkelNode, Spine.TextureLoader textureLoader)\n        {\n            var detectionResult = Detect(atlasOrSkelNode);\n            if (detectionResult.Success && detectionResult.Version == SpineVersion.V4)\n            {\n                return LoadSkeletonV4(detectionResult, textureLoader);\n            }\n            return null;\n        }\n\n        public static Spine.SkeletonData LoadSkeletonV4(SpineDetectionResult detectionResult, Spine.TextureLoader textureLoader)\n        {\n            using var atlasReader = new StringReader((string)detectionResult.ResolvedAtlasNode.Value);\n            var atlas = new Spine.Atlas(atlasReader, \"\", textureLoader);\n\n            switch (detectionResult.LoadType)\n            {\n                case SkeletonLoadType.Json:\n                    using (var skeletonReader = new StringReader((string)detectionResult.ResolvedSkelNode.Value))\n                    {\n                        var skeletonJson = new Spine.SkeletonJson(atlas);\n                        return skeletonJson.ReadSkeletonData(skeletonReader);\n                    }\n\n                case SkeletonLoadType.Binary when detectionResult.ResolvedSkelNode.Value is IMapleStoryBlob blob:\n                    byte[] data = new byte[blob.Length];\n                    blob.CopyTo(data, 0);\n                    var ms = new MemoryStream(data);\n                    var skeletonBinary = new Spine.SkeletonBinary(atlas);\n                    return skeletonBinary.ReadSkeletonData(ms);\n\n                default:\n                    return null;\n            }\n        }\n    }\n\n    public enum SkeletonLoadType\n    {\n        None = 0,\n        Json = 1,\n        Binary = 2,\n    }\n\n    public enum SpineVersion\n    {\n        Unknown = 0,\n        V2 = 2,\n        V4 = 4\n    }\n\n    public sealed class SpineDetectionResult\n    {\n        internal SpineDetectionResult()\n        {\n        }\n\n        public bool Success { get; internal set; }\n        public string ErrorDetail { get; internal set; }\n        public Wz_Node SourceNode { get; internal set; }\n        public Wz_Node ResolvedAtlasNode { get; internal set; }\n        public Wz_Node ResolvedSkelNode { get; internal set; }\n        public SkeletonLoadType LoadType { get; internal set; }\n        public SpineVersion Version { get; internal set; }\n\n        public static SpineDetectionResult Failed(string error = null) => new SpineDetectionResult\n        {\n            Success = false,\n            ErrorDetail = error,\n        };\n\n        public static SpineDetectionResult Create(Wz_Node sourceNode, Wz_Node atlasNode, Wz_Node skelNode, SkeletonLoadType loadType, SpineVersion version) => new SpineDetectionResult\n        {\n            Success = true,\n            ErrorDetail = null,\n            SourceNode = sourceNode,\n            ResolvedAtlasNode = atlasNode,\n            ResolvedSkelNode = skelNode,\n            LoadType = loadType,\n            Version = version\n        };\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/StringLinker.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Common\n{\n    public class StringLinker\n    {\n        public StringLinker()\n        {\n            stringEqp = new Dictionary<int, StringResult>();\n            stringFamiliarSkill = new Dictionary<int, StringResult>();\n            stringItem = new Dictionary<int, StringResult>();\n            stringMap = new Dictionary<int, StringResult>();\n            stringMob = new Dictionary<int, StringResult>();\n            stringNpc = new Dictionary<int, StringResult>();\n            stringSkill = new Dictionary<int, StringResult>();\n            stringSkill2 = new Dictionary<string, StringResult>();\n        }\n\n        public bool Load(Wz_File stringWz)\n        {\n            if (stringWz == null || stringWz.Node == null)\n                return false;\n            this.Clear();\n            int id;\n            foreach (Wz_Node node in stringWz.Node.Nodes)\n            {\n                Wz_Image image = node.Value as Wz_Image;\n                if (image == null)\n                    continue;\n                switch (node.Text)\n                {\n                    case \"Pet.img\":\n                    case \"Cash.img\":\n                    case \"Ins.img\":\n                    case \"Consume.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree in image.Node.Nodes)\n                        {\n                            if (Int32.TryParse(tree.Text, out id) && tree.ResolveUol() is Wz_Node linkNode)\n                            {\n                                StringResult strResult = new StringResult();\n                                strResult.Name = GetDefaultString(linkNode, \"name\");\n                                strResult.Desc = GetDefaultString(linkNode, \"desc\");\n                                strResult.AutoDesc = GetDefaultString(linkNode, \"autodesc\");\n                                strResult.FullPath = tree.FullPath; // always use the original node path\n\n                                AddAllValue(strResult, linkNode);\n                                stringItem[id] = strResult;\n                            }\n                        }\n                        break;\n                    case \"Etc.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree0 in image.Node.Nodes)\n                        {\n                            foreach (Wz_Node tree in tree0.Nodes)\n                            {\n                                if (Int32.TryParse(tree.Text, out id) && tree.ResolveUol() is Wz_Node linkNode)\n                                {\n                                    StringResult strResult = new StringResult();\n                                    strResult.Name = GetDefaultString(linkNode, \"name\");\n                                    strResult.Desc = GetDefaultString(linkNode, \"desc\");\n                                    strResult.FullPath = tree.FullPath;\n\n                                    AddAllValue(strResult, linkNode);\n                                    stringItem[id] = strResult;\n                                }\n                            }\n                        }\n                        break;\n                    case \"Familiar.img\":\n                    case \"FamiliarSkill.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree0 in image.Node.Nodes)\n                        {\n                            if (tree0.Text == \"skill\")\n                            {\n                                foreach (Wz_Node tree1 in tree0.Nodes)\n                                {\n                                    if (Int32.TryParse(tree1.Text, out id) && tree1.ResolveUol() is Wz_Node linkNode)\n                                    {\n                                        StringResult strResult = null;\n                                        if (strResult == null) strResult = new StringResult();\n\n                                        strResult.Name = GetDefaultString(linkNode, \"name\") ?? strResult.Name ?? string.Empty;\n                                        strResult.Desc = GetDefaultString(linkNode, \"desc\") ?? strResult.Desc;\n                                        strResult.FullPath = tree1.FullPath;\n\n                                        AddAllValue(strResult, linkNode);\n                                        stringFamiliarSkill[id] = strResult;\n                                    }\n                                }\n                            }\n                        }\n                        break;\n                    case \"Mob.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree in image.Node.Nodes)\n                        {\n                            if (Int32.TryParse(tree.Text, out id) && tree.ResolveUol() is Wz_Node linkNode)\n                            {\n                                StringResult strResult = new StringResult();\n                                strResult.Name = GetDefaultString(linkNode, \"name\");\n                                strResult.FullPath = tree.FullPath;\n\n                                AddAllValue(strResult, linkNode);\n                                stringMob[id] = strResult;\n                            }\n                        }\n                        break;\n                    case \"Npc.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree in image.Node.Nodes)\n                        {\n                            if (Int32.TryParse(tree.Text, out id) && tree.ResolveUol() is Wz_Node linkNode)\n                            {\n                                StringResult strResult = new StringResult();\n                                strResult.Name = GetDefaultString(linkNode, \"name\");\n                                strResult.Desc = GetDefaultString(linkNode, \"func\");\n                                strResult.FullPath = tree.FullPath;\n\n                                AddAllValue(strResult, linkNode);\n                                stringNpc[id] = strResult;\n                            }\n                        }\n                        break;\n                    case \"Map.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree0 in image.Node.Nodes)\n                        {\n                            foreach (Wz_Node tree in tree0.Nodes)\n                            {\n                                if (Int32.TryParse(tree.Text, out id) && tree.ResolveUol() is Wz_Node linkNode)\n                                {\n                                    StringResult strResult = new StringResult();\n                                    strResult.Name = string.Format(\"{0}：{1}\",\n                                        GetDefaultString(linkNode, \"streetName\"),\n                                        GetDefaultString(linkNode, \"mapName\"));\n                                    strResult.Desc = GetDefaultString(linkNode, \"mapDesc\");\n                                    strResult.FullPath = tree.FullPath;\n\n                                    AddAllValue(strResult, linkNode);\n                                    stringMap[id] = strResult;\n                                }\n                            }\n                        }\n                        break;\n                    case \"Skill.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree in image.Node.Nodes)\n                        {\n                            if (tree.ResolveUol() is not Wz_Node linkNode)\n                            {\n                                continue;\n                            }\n                            StringResultSkill strResult = new StringResultSkill();\n                            strResult.Name = GetDefaultString(linkNode, \"name\");//?? GetDefaultString(tree, \"bookName\");\n                            strResult.Desc = GetDefaultString(linkNode, \"desc\");\n                            strResult.Pdesc = GetDefaultString(linkNode, \"pdesc\");\n                            strResult.SkillH.Add(GetDefaultString(linkNode, \"h\"));\n                            strResult.SkillpH.Add(GetDefaultString(linkNode, \"ph\"));\n                            strResult.SkillhcH.Add(GetDefaultString(linkNode, \"hch\"));\n                            if (strResult.SkillH[0] == null)\n                            {\n                                strResult.SkillH.RemoveAt(0);\n                                for (int i = 1; ; i++)\n                                {\n                                    string hi = GetDefaultString(linkNode, \"h\" + i);\n                                    if (string.IsNullOrEmpty(hi))\n                                        break;\n                                    strResult.SkillH.Add(hi);\n                                }\n                            }\n                            // KMST1196, add h_ prefix strings\n                            foreach (Wz_Node child in linkNode.Nodes)\n                            {\n                                if (child.Text.StartsWith(\"h_\") && int.TryParse(child.Text.Substring(2), out int level) && level > 0 && child.Value != null)\n                                {\n                                    strResult.SkillExtraH.Add(new KeyValuePair<int, string>(level, child.GetValue<string>()));\n                                }\n                            }\n                            if (strResult.SkillExtraH.Count > 1)\n                            {\n                                strResult.SkillExtraH.Sort((left, right) => left.Key.CompareTo(right.Key));\n                            }\n                            strResult.SkillH.TrimExcess();\n                            strResult.SkillpH.TrimExcess();\n                            strResult.FullPath = tree.FullPath;\n\n                            AddAllValue(strResult, linkNode);\n                            if (tree.Text.Length >= 7 && Int32.TryParse(tree.Text, out id))\n                            {\n                                stringSkill[id] = strResult;\n                            }\n                            stringSkill2[tree.Text] = strResult;\n                        }\n                        break;\n                    case \"Eqp.img\":\n                        if (!image.TryExtract()) break;\n                        foreach (Wz_Node tree0 in image.Node.Nodes)\n                        {\n                            foreach (Wz_Node tree1 in tree0.Nodes)\n                            {\n                                foreach (Wz_Node tree in tree1.Nodes)\n                                {\n                                    if (Int32.TryParse(tree.Text, out id) && tree.ResolveUol() is Wz_Node linkNode)\n                                    {\n                                        StringResult strResult = new StringResult();\n                                        strResult.Name = GetDefaultString(linkNode, \"name\");\n                                        strResult.Desc = GetDefaultString(linkNode, \"desc\");\n                                        strResult.FullPath = tree.FullPath;\n\n                                        AddAllValue(strResult, linkNode);\n                                        stringEqp[id] = strResult;\n                                    }\n                                }\n                            }\n                        }\n                        break;\n                }\n            }\n\n            return this.HasValues;\n        }\n\n        public void Clear()\n        {\n            stringEqp.Clear();\n            stringFamiliarSkill.Clear();\n            stringItem.Clear();\n            stringMob.Clear();\n            stringMap.Clear();\n            stringNpc.Clear();\n            stringSkill.Clear();\n            stringSkill2.Clear();\n        }\n\n        public bool HasValues\n        {\n            get\n            {\n                return (stringEqp.Count + stringItem.Count + stringMap.Count +\n                    stringMob.Count + stringNpc.Count + stringSkill.Count > 0);\n            }\n        }\n\n        private Dictionary<int, StringResult> stringEqp;\n        private Dictionary<int, StringResult> stringFamiliarSkill;\n        private Dictionary<int, StringResult> stringItem;\n        private Dictionary<int, StringResult> stringMap;\n        private Dictionary<int, StringResult> stringMob;\n        private Dictionary<int, StringResult> stringNpc;\n        private Dictionary<int, StringResult> stringSkill;\n        private Dictionary<string, StringResult> stringSkill2;\n\n        private string GetDefaultString(Wz_Node node, string searchNodeText)\n        {\n            node = node.FindNodeByPath(searchNodeText);\n            return node == null ? null : Convert.ToString(node.Value);\n        }\n\n        private void AddAllValue(StringResult sr, Wz_Node node)\n        {\n            foreach (Wz_Node child in node.Nodes)\n            {\n                if (child.Value != null)\n                {\n                    sr[child.Text] = child.GetValue<string>();\n                }\n            }\n        }\n\n        public Dictionary<int, StringResult> StringEqp\n        {\n            get { return stringEqp; }\n        }\n\n        public Dictionary<int, StringResult> StringFamiliarSkill\n        {\n            get { return stringFamiliarSkill; }\n        }\n\n        public Dictionary<int, StringResult> StringItem\n        {\n            get { return stringItem; }\n        }\n\n        public Dictionary<int, StringResult> StringMap\n        {\n            get { return stringMap; }\n        }\n\n        public Dictionary<int, StringResult> StringMob\n        {\n            get { return stringMob; }\n        }\n\n        public Dictionary<int, StringResult> StringNpc\n        {\n            get { return stringNpc; }\n        }\n\n        public Dictionary<int, StringResult> StringSkill\n        {\n            get { return stringSkill; }\n        }\n\n        public Dictionary<string, StringResult> StringSkill2\n        {\n            get { return stringSkill2; }\n        }\n\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/StringResult.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.Common\n{\n    public class StringResult\n    {\n        public StringResult()\n        {\n        }\n\n        public string Name { get; set; }\n        public string Desc { get; set; }\n        public string Pdesc { get; set; }\n        public string AutoDesc { get; set; }\n        public string FullPath { get; set; }\n\n        private List<KeyValuePair<string, string>> allValues;\n\n        public string this[string key]\n        {\n            get\n            {\n                if (this.allValues != null && key != null)\n                {\n                    foreach(var kv in this.allValues)\n                    {\n                        if (kv.Key == key)\n                        {\n                            return kv.Value;\n                        }\n                    }\n                }\n                return null;\n            }\n            set\n            {\n                if (key != null)\n                {\n                    if (this.allValues == null)\n                    {\n                        this.allValues = new List<KeyValuePair<string, string>>();\n                    }\n\n                    for(int i = 0; i < this.allValues.Count; i++)\n                    {\n                        var kv = this.allValues[i];\n                        if (kv.Key == key)\n                        {\n                            this.allValues[i] = new KeyValuePair<string, string>(key, value);\n                            return;\n                        }\n                    }\n                    this.allValues.Add(new KeyValuePair<string, string>(key, value));\n                }\n            }\n        }\n    }\n\n    public sealed class StringResultSkill : StringResult\n    {\n\n        public StringResultSkill()\n        {\n            this.skillH = new List<string>();\n            this.skillpH = new List<string>();\n            this.skillhcH = new List<string>();\n            this.skillExtraH = new List<KeyValuePair<int, string>>();\n        }\n\n        public List<string> SkillH\n        {\n            get { return this.skillH; }\n        }\n\n        public List<string> SkillpH\n        {\n            get { return this.skillpH; }\n        }\n\n        public List<string> SkillhcH\n        {\n            get { return this.skillhcH; }\n        }\n\n        public List<KeyValuePair<int, string>> SkillExtraH\n        {\n            get { return this.skillExtraH; }\n        }\n\n        private readonly List<string> skillH;\n        private readonly List<string> skillpH;\n        private readonly List<string> skillhcH;\n        private readonly List<KeyValuePair<int, string>> skillExtraH;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Text/DocumentElements.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.Text\n{\n    public abstract class DocElement\n    {\n    }\n\n    public sealed class Span : DocElement\n    {\n        public string ColorID { get; set; }\n        public string Text { get; set; }\n    }\n\n    public sealed class LineBreak : DocElement\n    {\n        private LineBreak() { }\n        public static readonly LineBreak Instance = new LineBreak();\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Text/Parser.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.Text\n{\n    public class Parser\n    {\n        private Parser()\n        {\n        }\n\n        public static IList<DocElement> Parse(string format)\n        {\n            var elements = new List<DocElement>();\n            var sb = new StringBuilder();\n            var colorStack = new Stack<string>();\n            colorStack.Push(\"\");\n\n            int strPos = 0;\n            char curChar;\n\n            int offset = 0;\n\n            Action flushRun = () =>\n            {\n                if (offset < format.Length && sb.Length > offset)\n                {\n                    elements.Add(new Span()\n                    {\n                        Text = sb.ToString(offset, sb.Length - offset),\n                        ColorID = colorStack.Peek()\n                    });\n                    offset = sb.Length;\n                }\n            };\n\n            while (strPos < format.Length)\n            {\n                curChar = format[strPos++];\n                if (curChar == '\\\\')\n                {\n                    if (strPos < format.Length)\n                    {\n                        curChar = format[strPos++];\n                        switch (curChar)\n                        {\n                            case 'r': curChar = '\\r'; break;\n                            case 'n': curChar = '\\n'; break;\n                        }\n                    }\n                    else //结束符处理\n                    {\n                        curChar = '#';\n                    }\n                }\n\n                switch (curChar)\n                {\n                    case '#':\n                        if (strPos < format.Length && format[strPos] == 'c')//遇到#c 换橙刷子并flush\n                        {\n                            flushRun();\n                            colorStack.Push(\"c\");\n                            strPos++;\n                        }\n                        else if (strPos < format.Length && format[strPos] == '$'\n                            && strPos + 1 < format.Length )//遇到#$(自定义) 更换为自定义颜色表\n                        {\n                            flushRun();\n                            colorStack.Push(format.Substring(strPos, 2));\n                            strPos += 2;\n                        }\n                        else if (colorStack.Count == 1) //同#c\n                        {\n                            flushRun();\n                            colorStack.Push(\"c\");\n                            //strPos++;\n                        }\n                        else//遇到# 换白刷子并flush\n                        {\n                            flushRun();\n                            colorStack.Pop();\n                        }\n                        break;\n\n                    case '\\r': //忽略\n                        break;\n\n                    case '\\n': //插入换行\n                        flushRun();\n                        elements.Add(LineBreak.Instance);\n                        break;\n\n                    default:\n                        sb.Append(curChar);\n                        break;\n                }\n            }\n\n            flushRun();\n            return elements;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Text/TextAlignment.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.Text\n{\n    public enum TextAlignment\n    {\n        Left = 0,\n        Center = 1,\n        Right = 2,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/Text/TextRenderer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.Text\n{\n    public abstract class TextRenderer<TFont>\n    {\n        public TextRenderer()\n        {\n            sb = new StringBuilder();\n        }\n\n        public bool WordWrapEnabled { get; set; }\n        protected StringBuilder sb;\n        protected TFont font;\n\n        public void DrawFormatString(string s, TFont font, int width, ref int y, int height, TextAlignment alignment = TextAlignment.Left)\n        {\n            //初始化环境\n            this.font = font;\n            this.sb.Clear();\n            this.sb.EnsureCapacity(s.Length);\n\n            //读取格式\n            var doc = Parser.Parse(s);\n            this.DrawRunsInner(this.PrepareRuns(doc), width, ref y, height, alignment);\n        }\n\n        public void DrawPlainText(string s, TFont font, int width, ref int y, int height, TextAlignment alignment = TextAlignment.Left)\n        {\n            this.font = font;\n            this.sb.Clear();\n            this.sb.EnsureCapacity(s.Length);\n            this.DrawRunsInner(this.PrepareRuns(s), width, ref y, height, alignment);\n        }\n\n        private void DrawRunsInner(List<Run> runs, int width, ref int y, int height, TextAlignment alignment)\n        {\n            runs = runs.SelectMany(run => SplitWords(run)).ToList();\n            this.MeasureRuns(runs);\n            var layout = LayoutRuns(runs, width, ref y, height, alignment);\n            this.FlushAll(layout);\n        }\n\n        private List<Run> PrepareRuns(IList<DocElement> doc)\n        {\n            var runs = new List<Run>();\n            foreach (var elem in doc)\n            {\n                if (elem is Span)\n                {\n                    var span = (Span)elem;\n                    int start = sb.Length;\n                    sb.Append(span.Text);\n                    runs.Add(new Run(start, sb.Length - start) { ColorID = span.ColorID });\n                }\n                else if (elem is LineBreak)\n                {\n                    runs.Add(new Run(sb.Length, 0) { IsBreakLine = true });\n                }\n            }\n            return runs;\n        }\n\n        private List<Run> PrepareRuns(string text)\n        {\n            List<Run> runs = new List<Run>();\n            var sr = new System.IO.StringReader(text);\n            for (int row = 0; sr.Peek() > -1; row++)\n            {\n                if (row > 0)\n                {\n                    runs.Add(new Run(sb.Length, 0) { IsBreakLine = true });\n                }\n                var line = sr.ReadLine();\n                if (!string.IsNullOrEmpty(line))\n                {\n                    sb.Append(line);\n                    runs.Add(new Run(sb.Length - line.Length, line.Length));\n                }\n            }\n            return runs;\n        }\n\n        private List<Run> SplitWords(Run run)\n        {\n            List<Run> runs = new List<Run>();\n\n            if (run.IsBreakLine)\n            {\n                runs.Add(run);\n            }\n            else\n            {\n                for (int i = run.StartIndex, i0 = run.StartIndex + run.Length; i < i0; i++)\n                {\n                    int start = i, len;\n                    switch (sb[i])\n                    {\n                        case ' ':\n                        case '\\t':\n                            while (++i < i0)\n                            {\n                                if (!(sb[i] == ' ' || sb[i] == '\\t'))\n                                {\n                                    break;\n                                }\n                            }\n                            len = (i--) - start;\n                            runs.Add(new Run(start, len) { IsWhiteSpace = true });\n                            break;\n\n                        case '\\r':\n                            if (i + 1 < i0 && sb[i + 1] == '\\n')\n                            {\n                                i++;\n                                goto case '\\n';\n                            }\n                            else\n                            {\n                                runs.Add(new Run(start, 1) { IsWhiteSpace = true });\n                            }\n                            break;\n\n                        case '\\n':\n                            len = i - start + 1;\n                            runs.Add(new Run(start, len) { IsBreakLine = true });\n                            break;\n\n                        default:\n                            if (this.WordWrapEnabled)\n                            {\n                                while (++i < i0)\n                                {\n                                    if (sb[i] == ' ' || sb[i] == '\\t' || sb[i] == '\\r' || sb[i] == '\\n')\n                                    {\n                                        break;\n                                    }\n                                }\n\n                                len = (i--) - start;\n                                runs.Add(new Run(start, len) { ColorID = run.ColorID });\n                            }\n                            else\n                            {\n                                runs.Add(new Run(start, 1) { ColorID = run.ColorID });\n                            }\n                            break;\n                    }\n                }\n            }\n            return runs;\n        }\n\n        private float GetFontLineHeight(Font font)\n        {\n            var ff = font.FontFamily;\n            return (float)Math.Ceiling(1.0 * font.Height * ff.GetLineSpacing(font.Style) / ff.GetEmHeight(font.Style));\n        }\n\n        protected abstract void MeasureRuns(List<Run> runs);\n\n        protected abstract Rectangle[] MeasureChars(int startIndex, int length);\n\n        protected abstract void Flush(StringBuilder sb, int startIndex, int length, int x, int y, string ColorID);\n\n        private List<PositionedText> LayoutRuns(List<Run> runs, int width, ref int y, int lineHeight, TextAlignment alignment)\n        {\n            int drawX = 0;\n            int drawY = y;\n            int start = -1, end = -1;\n            int xOffset = 0;\n\n            int curX = drawX;\n            string colorID = null;\n            List<PositionedText> result = new();\n            int lastLineStartIndex = 0;\n\n            bool hasContent() => start > -1 && end > start;\n            void flush(bool isNewLine)\n            {\n                if (hasContent())\n                {\n                    result.Add(new PositionedText()\n                    {\n                        StartIndex = start,\n                        Length = end - start,\n                        X = drawX,\n                        Y = drawY,\n                        ColorID = colorID\n                    });\n                }\n                if (isNewLine)\n                {\n                    if (lastLineStartIndex < result.Count && alignment != TextAlignment.Left)\n                    {\n                        // recalculate offsetX by alignment\n                        int contentWidth = curX;\n                        int adjustOffsetX = alignment switch\n                        {\n                            TextAlignment.Center => (width - contentWidth) / 2,\n                            TextAlignment.Right => (width - contentWidth),\n                            _ => 0,\n                        };\n                        for (int i = lastLineStartIndex; i < result.Count; i++)\n                        {\n                            result[i].X += adjustOffsetX;\n                        }\n                    }\n                    drawX = curX = 0;\n                    drawY += lineHeight;\n                    lastLineStartIndex = result.Count;\n                }\n                else\n                {\n                    drawX = curX;\n                }\n                start = end = -1;\n            };\n\n            for (int r = 0; r < runs.Count; r++)\n            {\n                var run = runs[r];\n                if (run.IsBreakLine)\n                { //强行换行 并且flush\n                    flush(true);\n                    if (r < runs.Count - 1)\n                    {\n                        xOffset = runs[r + 1].X;\n                    }\n                }\n                else\n                {\n                    if (!run.IsWhiteSpace && run.ColorID != colorID)\n                    {\n                        end = run.StartIndex;\n                        curX = run.X - xOffset;\n                        flush(false);\n                        colorID = run.ColorID;\n                    }\n\n                    if (start < 0)\n                    {\n                        start = run.StartIndex;\n                    }\n\n                    if (!(run.IsWhiteSpace && run.Width <= 0))\n                    { //非空 计算宽度\n                        curX = run.X - xOffset;\n                        if (this.WordWrapEnabled ? (width - curX < run.Width) : (curX >= width))  //奇怪的算法 暂定\n                        { //宽度不够\n                            if (curX > 0) //(hasContent())\n                            { //有内容\n                                // 判断行尾标点是否追加\n                                if (run.ColorID == colorID && run.Length == 1 && \",.\".IndexOf(this.sb[run.StartIndex]) > -1)\n                                {\n                                    end = run.StartIndex + run.Length;\n                                    if (++r >= runs.Count)\n                                    {\n                                        break;\n                                    }\n                                    run = runs[r];\n                                }\n                                flush(true);\n                                start = run.StartIndex;\n                                xOffset = run.X;\n                            }\n                            if (width - curX < run.Width)\n                            { //宽度还是不够 按字符拆分\n                                var rects = MeasureChars(run.StartIndex, run.Length);\n\n                                for (int i = 0, ir = run.StartIndex; i < rects.Length; i++, ir++)\n                                {\n                                    rects[i].X += run.X;\n\n                                    if (start < 0)\n                                    {\n                                        start = ir;\n                                        xOffset = run.X;\n                                    }\n\n                                    if (rects[i].Right - xOffset > width)\n                                    { //超宽 flush之前内容\n                                        if (ir - start <= 0)\n                                        { //限定至少输出一个字符\n                                            end = start + 1;\n                                            curX = rects[i].Right - xOffset;\n                                            flush(true);\n                                            xOffset = rects[i].Right;\n                                            continue;\n                                        }\n                                        else\n                                        {\n                                            end = ir;\n                                            curX = rects[i].X - xOffset;\n                                            flush(true);\n                                            start = ir;\n                                            xOffset = rects[i].X;\n                                        }\n                                    }\n                                }\n                                end = run.StartIndex + run.Length;\n                                curX = rects[rects.Length - 1].Right - xOffset;\n                                flush(false);\n\n                                continue;\n                            }\n                        }\n                    }\n\n                    //正常绘制\n                    end = run.StartIndex + run.Length;\n                    curX = run.X + run.Width - xOffset;\n                }\n            }\n\n            //输出结尾\n            flush(true);\n            y = drawY;\n            return result;\n        }\n\n        private void FlushAll(List<PositionedText> texts)\n        {\n            foreach (PositionedText text in texts)\n            {\n                this.Flush(sb, text.StartIndex, text.Length, text.X, text.Y, text.ColorID);\n            }\n        }\n\n        private class PositionedText\n        {\n            public int StartIndex;\n            public int Length;\n            public int X;\n            public int Y;\n            public string ColorID;\n        }\n    }\n\n    public class Run\n    {\n        public Run(int startIndex, int length)\n        {\n            this.StartIndex = startIndex;\n            this.Length = length;\n        }\n\n        public int StartIndex;\n        public int Length;\n        public bool IsWhiteSpace;\n        public bool IsBreakLine;\n        public int X;\n        public int Width;\n        public string ColorID;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/VpxVideoDecoder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing vpx_codec_ctx_t = WzComparerR2.VpxVideoDecoder.Interop.vpx_codec_ctx;\nusing vpx_codec_dec_cfg_t = WzComparerR2.VpxVideoDecoder.Interop.vpx_codec_dec_cfg;\nusing vpx_color_range_t = WzComparerR2.VpxVideoDecoder.Interop.vpx_color_range;\nusing vpx_codec_err_t = WzComparerR2.VpxVideoDecoder.Interop.vpx_codec_err;\nusing vpx_codec_flags_t = WzComparerR2.VpxVideoDecoder.Interop.vpx_codec_flags;\nusing vpx_image_t = WzComparerR2.VpxVideoDecoder.Interop.vpx_image;\nusing vpx_img_fmt_t = WzComparerR2.VpxVideoDecoder.Interop.vpx_img_fmt;\nusing vpx_codec_iface_ptr = nint;\nusing vpx_codec_iter_t = nint;\nusing System.Runtime.CompilerServices;\nusing System.Windows.Forms.VisualStyles;\n\nnamespace WzComparerR2\n{\n    public class VpxVideoDecoder : IDisposable\n    {\n        public static ReadOnlySpan<byte> FourCC_VP8 => \"VP80\"u8;\n        public static ReadOnlySpan<byte> FourCC_VP9 => \"VP90\"u8;\n\n        public VpxVideoDecoder(ReadOnlySpan<byte> fourCC)\n        {\n            this.VpxCodecInit(fourCC, new vpx_codec_dec_cfg_t());\n        }\n\n        public VpxVideoDecoder(ReadOnlySpan<byte> fourCC, int width, int height, int threads)\n        {\n            this.VpxCodecInit(fourCC, new vpx_codec_dec_cfg_t\n            {\n                w = (uint)width,\n                h = (uint)height,\n                threads = (uint)threads,\n            });\n        }\n\n        private IntPtr pVpxCodecCtx;\n        private IntPtr pVpxCodecDecConfig;\n        private vpx_codec_iter_t iter;\n\n        private unsafe void VpxCodecInit(ReadOnlySpan<byte> fourCC, vpx_codec_dec_cfg_t config)\n        {\n            vpx_codec_iface_ptr iface;\n            if (fourCC.Length != 4)\n            {\n                throw new ArgumentException(\"Invalid length.\", nameof(fourCC));\n            }\n\n            if (fourCC.SequenceEqual(FourCC_VP9))\n            {\n                iface = Interop.vpx_codec_vp9_dx();\n            }\n            else if (fourCC.SequenceEqual(FourCC_VP8))\n            {\n                iface = Interop.vpx_codec_vp8_dx();\n            }\n            else\n            {\n                throw new ArgumentException($\"Unknown fourCC value: {MemoryMarshal.Read<int>(fourCC)}\", nameof(fourCC));\n            }\n\n            IntPtr pVpxCodecCtx = Marshal.AllocHGlobal(Marshal.SizeOf<vpx_codec_ctx_t>());\n            IntPtr pVpxCodecDecConfig = Marshal.AllocHGlobal(Marshal.SizeOf<vpx_codec_dec_cfg_t>());\n           \n            try\n            {\n                Unsafe.Copy(pVpxCodecDecConfig.ToPointer(), ref config);\n                vpx_codec_err_t err = Interop.vpx_codec_dec_init_ver((vpx_codec_ctx_t*)pVpxCodecCtx, iface, (vpx_codec_dec_cfg_t*)pVpxCodecDecConfig, 0, Interop.VPX_DECODER_ABI_VERSION);\n                ThrowOnNonSuccessfulError(err, nameof(Interop.vpx_codec_dec_init_ver));\n            }\n            catch\n            {\n                Marshal.FreeHGlobal(pVpxCodecCtx);\n                Marshal.FreeHGlobal(pVpxCodecDecConfig);\n            }\n\n            this.pVpxCodecCtx = pVpxCodecCtx;\n            this.pVpxCodecDecConfig = pVpxCodecDecConfig;\n        }\n\n        public unsafe void DecodeData(ReadOnlySpan<byte> data)\n        {\n            this.ThrowOnObjectDisposed();\n            if (data.IsEmpty)\n            {\n                return;\n            }\n            fixed (byte* pData = data)\n            {\n                this.iter = 0;\n                var err = Interop.vpx_codec_decode((vpx_codec_ctx_t*)this.pVpxCodecCtx, pData, (uint)data.Length, 0, 0);\n                ThrowOnNonSuccessfulError(err, nameof(Interop.vpx_codec_decode));\n            }\n        }\n\n        public unsafe bool GetNextFrame(out VpxFrame frame)\n        {\n            this.ThrowOnObjectDisposed();\n\n            vpx_codec_iter_t iter = this.iter;\n            vpx_image_t* img = Interop.vpx_codec_get_frame((vpx_codec_ctx_t*)this.pVpxCodecCtx, &iter);\n            this.iter = iter;\n\n            if (img != null)\n            {\n                frame = new VpxFrame(img);\n                return true;\n            }\n            else\n            {\n                frame = default;\n                return false;\n            }\n        }\n\n        #region IDisposable\n        private bool isDisposed;\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        ~VpxVideoDecoder()\n        {\n            this.Dispose(false);\n        }\n\n        protected virtual unsafe void Dispose(bool disposing)\n        {\n            if (isDisposed)\n            {\n                return;\n            }\n\n            if (disposing)\n            {\n                if (this.pVpxCodecCtx != IntPtr.Zero)\n                {\n                    Interop.vpx_codec_destroy((vpx_codec_ctx_t*)this.pVpxCodecCtx);\n                    Marshal.FreeHGlobal(this.pVpxCodecCtx);\n                    this.pVpxCodecCtx = IntPtr.Zero;\n                }\n                if (this.pVpxCodecDecConfig != IntPtr.Zero)\n                {\n                    Marshal.FreeHGlobal(this.pVpxCodecDecConfig);\n                    this.pVpxCodecDecConfig = IntPtr.Zero;\n                }\n            }\n            isDisposed = true;\n        }\n\n        private void ThrowOnObjectDisposed()\n        {\n            if (this.isDisposed)\n            {\n                throw new ObjectDisposedException(nameof(VpxVideoDecoder));\n            }\n        }\n\n        private void ThrowOnNonSuccessfulError(vpx_codec_err_t err, string methodName = null)\n        {\n            if (err != vpx_codec_err_t.VPX_CODEC_OK)\n            {\n                throw new Exception($\"{methodName ?? \"libVpx\"} returns error: ${err}\");\n            }\n        }\n        #endregion\n\n        #region interop\n        public static class Interop\n        {\n            private const string libVpx = @\"libvpx\";\n\n            [DllImport(libVpx)]\n            public static unsafe extern vpx_codec_err_t vpx_codec_dec_init_ver([Out] vpx_codec_ctx_t* ctx, vpx_codec_iface_ptr iface, [In] vpx_codec_dec_cfg* cfg, vpx_codec_flags_t flags, int ver);\n            [DllImport(libVpx)]\n            public static extern vpx_codec_iface_ptr vpx_codec_vp8_dx();\n            [DllImport(libVpx)]\n            public static extern vpx_codec_iface_ptr vpx_codec_vp9_dx();\n            [DllImport(libVpx)]\n            public static unsafe extern vpx_codec_err_t vpx_codec_decode([In] vpx_codec_ctx_t* ctx, [In] byte* data, uint data_sz, nint user_priv, long deadline);\n            [DllImport(libVpx)]\n            public static unsafe extern vpx_image_t* vpx_codec_get_frame([In] vpx_codec_ctx_t* ctx, [In][Out] vpx_codec_iter_t* iter);\n            [DllImport(libVpx)]\n            public static unsafe extern vpx_codec_err_t vpx_codec_destroy([In] vpx_codec_ctx_t* ctx);\n\n            public const int VPX_IMAGE_ABI_VERSION = 5;\n            public const int VPX_CODEC_ABI_VERSION = (4 + VPX_IMAGE_ABI_VERSION);\n            public const int VPX_DECODER_ABI_VERSION = (3 + VPX_CODEC_ABI_VERSION);\n\n            public enum vpx_codec_err\n            {\n                VPX_CODEC_OK = 0,\n                VPX_CODEC_ERROR,\n                VPX_CODEC_MEM_ERROR,\n                VPX_CODEC_ABI_MISMATCH,\n                VPX_CODEC_INCAPABLE,\n                VPX_CODEC_UNSUP_BITSTREAM,\n                VPX_CODEC_UNSUP_FEATURE,\n                VPX_CODEC_CORRUPT_FRAME,\n                VPX_CODEC_INVALID_PARAM,\n                VPX_CODEC_LIST_END\n            }\n\n            public struct vpx_codec_dec_cfg\n            {\n                public uint threads;\n                public uint w;\n                public uint h;\n            }\n\n            public unsafe struct vpx_codec_ctx\n            {\n                public nint name;\n                public vpx_codec_iface_ptr iface;\n                public vpx_codec_err err;\n                public nint err_detail;\n                public int init_flags;\n                public vpx_codec_dec_cfg* config;\n                public nint priv;\n            }\n\n            public enum vpx_img_fmt\n            {\n                VPX_IMG_FMT_NONE = 0,\n                VPX_IMG_FMT_PLANAR = 0x100,\n                VPX_IMG_FMT_UV_FLIP = 0x200,\n                VPX_IMG_FMT_HAS_ALPHA = 0x400,\n                VPX_IMG_FMT_HIGHBITDEPTH = 0x800,\n                VPX_IMG_FMT_YV12 = VPX_IMG_FMT_PLANAR | VPX_IMG_FMT_UV_FLIP | 1,\n                VPX_IMG_FMT_I420 = VPX_IMG_FMT_PLANAR | 2,\n                VPX_IMG_FMT_I422 = VPX_IMG_FMT_PLANAR | 5,\n                VPX_IMG_FMT_I444 = VPX_IMG_FMT_PLANAR | 6,\n                VPX_IMG_FMT_I440 = VPX_IMG_FMT_PLANAR | 7,\n                VPX_IMG_FMT_NV12 = VPX_IMG_FMT_PLANAR | 9,\n                VPX_IMG_FMT_I42016 = VPX_IMG_FMT_I420 | VPX_IMG_FMT_HIGHBITDEPTH,\n                VPX_IMG_FMT_I42216 = VPX_IMG_FMT_I422 | VPX_IMG_FMT_HIGHBITDEPTH,\n                VPX_IMG_FMT_I44416 = VPX_IMG_FMT_I444 | VPX_IMG_FMT_HIGHBITDEPTH,\n                VPX_IMG_FMT_I44016 = VPX_IMG_FMT_I440 | VPX_IMG_FMT_HIGHBITDEPTH\n            }\n\n            public enum vpx_codec_flags\n            {\n                VPX_CODEC_USE_POSTPROC = 0x10000,\n                VPX_CODEC_USE_ERROR_CONCEALMENT = 0x20000,\n                VPX_CODEC_USE_INPUT_FRAGMENTS = 0x40000,\n                VPX_CODEC_USE_FRAME_THREADING = 0x80000,\n            }\n\n            public enum vpx_color_space\n            {\n                VPX_CS_UNKNOWN = 0,\n                VPX_CS_BT_601 = 1,\n                VPX_CS_BT_709 = 2,\n                VPX_CS_SMPTE_170 = 3,\n                VPX_CS_SMPTE_240 = 4,\n                VPX_CS_BT_2020 = 5,\n                VPX_CS_RESERVED = 6,\n                VPX_CS_SRGB = 7,\n            }\n\n            public enum vpx_color_range\n            {\n                VPX_CR_STUDIO_RANGE = 0,\n                VPX_CR_FULL_RANGE = 1,\n            }\n\n            public const int VPX_PLANE_PACKED = 0;\n            public const int VPX_PLANE_Y = 0;\n            public const int VPX_PLANE_U = 1;\n            public const int VPX_PLANE_V = 2;\n            public const int VPX_PLANE_ALPHA = 3;\n\n            [StructLayout(LayoutKind.Sequential)]\n            public unsafe struct vpx_image\n            {\n                public vpx_img_fmt fmt;\n                public vpx_color_space cs;\n                public vpx_color_range_t range;\n\n                public uint w;\n                public uint h;\n                public uint bit_depth;\n\n                public uint d_w;\n                public uint d_h;\n\n                public uint r_w;\n                public uint r_h;\n\n                public uint x_chroma_shift;\n                public uint y_chroma_shift;\n\n                // fixed byte* planes[4]; //error: CS1663\n                private byte* planes_0;\n                private byte* planes_1;\n                private byte* planes_2;\n                private byte* planes_3;\n                public byte** planes\n                {\n                    get\n                    {\n                        fixed (vpx_image* pImage = &this)\n                            return &pImage->planes_0;\n                    }\n                }\n                public fixed int stride[4];\n\n                public int bps;\n                public void* user_priv;\n                public byte* img_data;\n                public int img_data_owner;\n                public int self_allocd;\n                public void* fb_priv;\n            }\n        }\n        #endregion\n    }\n\n    public unsafe struct VpxFrame\n    {\n        internal VpxFrame(vpx_image_t* image)\n        {\n            this.image = image;\n        }\n\n        private readonly vpx_image_t* image;\n\n        public vpx_img_fmt_t Format => image->fmt;\n        public int DisplayWidth => (int)image->d_w;\n        public int DisplayHeight => (int)image->d_h;\n        public IntPtr PlanesY => new IntPtr(image->planes[VpxVideoDecoder.Interop.VPX_PLANE_Y]);\n        public IntPtr PlanesU => new IntPtr(image->planes[VpxVideoDecoder.Interop.VPX_PLANE_U]);\n        public IntPtr PlanesV => new IntPtr(image->planes[VpxVideoDecoder.Interop.VPX_PLANE_V]);\n        public IntPtr PlanesAlpha => new IntPtr(image->planes[VpxVideoDecoder.Interop.VPX_PLANE_ALPHA]);\n        public int StrideY => image->stride[VpxVideoDecoder.Interop.VPX_PLANE_Y];\n        public int StrideU => image->stride[VpxVideoDecoder.Interop.VPX_PLANE_U];\n        public int StrideV => image->stride[VpxVideoDecoder.Interop.VPX_PLANE_V];\n        public int StrideAlpha => image->stride[VpxVideoDecoder.Interop.VPX_PLANE_ALPHA];\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Common/WzComparerR2.Common.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2.Common</AssemblyName>\n    <RootNamespace>WzComparerR2</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>\n    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n    <LangVersion>latest</LangVersion>\n    <Nullable>disable</Nullable>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Configuration\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WzComparerR2.WzLib\\WzComparerR2.WzLib.csproj\" />\n    <PackageReference Include=\"MonoGame.Framework.WindowsDX\" Version=\"$(MonogameFrameworkVersion)\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Reference Include=\"ImageManipulation\">\n      <HintPath>..\\References\\ImageManipulation.dll</HintPath>\n    </Reference>\n    <Reference Include=\"spine-monogame\">\n      <HintPath>..\\References\\spine-monogame.dll</HintPath>\n    </Reference>\n    <Reference Include=\"DevComponents.DotNetBar2\">\n      <HintPath>..\\References\\DevComponents.DotNetBar2.dll</HintPath>\n    </Reference>\n    <!-- force upgrading SharpDX -->\n    <PackageReference Include=\"SharpDX\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"SharpDX.Direct2D1\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"SharpDX.Direct3D11\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"SharpDX.DXGI\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"SharpDX.Mathematics\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"SharpDX.MediaFoundation\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"SharpDX.XAudio2\" Version=\"$(SharpDXVersion)\" />\n    <PackageReference Include=\"SharpDX.XInput\" Version=\"$(SharpDXVersion)\" />\n  </ItemGroup>\n  <ItemGroup>\n    <EmbeddedResource Include=\"Rendering\\Effect\\PngEffect.$(MonogameFrameworkVersion).mgfxo\" LogicalName=\"WzComparerR2.Rendering.Effect.PngEffect.mgfxo\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n  <Target Name=\"PreBuild\" BeforeTargets=\"PreBuildEvent\" Condition=\"$([MSBuild]::IsOSPlatform('Windows')) AND !Exists('$(ProjectDir)\\Rendering\\Effect\\PngEffect.$(MonogameFrameworkVersion).mgfxo')\">\n    <Exec Command=\"EffectCompiler.bat $(MonogameFrameworkVersion)\" WorkingDirectory=\"$(ProjectDir)Rendering\\Effect\" />\n  </Target>\n</Project>\n"
  },
  {
    "path": "WzComparerR2.Common/Wz_NodeExtension2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.Common\n{\n    public static class Wz_NodeExtension2\n    {\n        public static Wz_Node GetLinkedSourceNode(this Wz_Node node, GlobalFindNodeFunction findNode)\n        {\n            string path;\n\n            if (!string.IsNullOrEmpty(path = node.Nodes[\"source\"].GetValueEx<string>(null)))\n            {\n                return findNode?.Invoke(path);\n            }\n            else if (!string.IsNullOrEmpty(path = node.Nodes[\"_inlink\"].GetValueEx<string>(null)))\n            {\n                var img = node.GetNodeWzImage();\n                return img?.Node.FindNodeByPath(true, path.Split('/'));\n            }\n            else if (!string.IsNullOrEmpty(path = node.Nodes[\"_outlink\"].GetValueEx<string>(null)))\n            {\n                return findNode?.Invoke(path);\n            }\n            else\n            {\n                return node;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/AppSyntaxModeProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\nusing System.Xml;\nusing ICSharpCode.TextEditor.Document;\n\nnamespace WzComparerR2.LuaConsole\n{\n    public class AppSyntaxModeProvider : ISyntaxModeFileProvider\n    {\n        public AppSyntaxModeProvider()\n        {\n            this.list = new List<SyntaxMode>();\n            UpdateSyntaxModeList();\n        }\n\n        private List<SyntaxMode> list;\n\n        public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode)\n        {\n            try\n            {\n                string resourceName = Path.GetFileNameWithoutExtension(syntaxMode.FileName);\n                byte[] fileContent = Properties.Resources.ResourceManager.GetObject(resourceName) as byte[];\n                if (fileContent != null)\n                {\n                    Stream stream = new MemoryStream(fileContent, false);\n                    return new XmlTextReader(stream);\n                }\n                else\n                {\n                    return null;\n                }\n            }\n            catch (Exception)\n            {\n                throw;\n            }\n        }\n\n        public ICollection<SyntaxMode> SyntaxModes\n        {\n            get { return list; }\n        }\n\n        public void UpdateSyntaxModeList()\n        {\n            this.list.Clear();\n            this.list.Add(new SyntaxMode(\"Lua.xshd\", \"Lua\", \"*.lua\"));\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/Config/LuaConsoleConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Configuration;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing WzComparerR2.Config;\n\nnamespace WzComparerR2.LuaConsole.Config\n{\n    [SectionName(\"WcR2.LuaConsole\")]\n    public class LuaConsoleConfig : ConfigSectionBase<LuaConsoleConfig>\n    {\n        public LuaConsoleConfig()\n        {\n        }\n\n        /// <summary>\n        /// 获取最近打开的文档列表。\n        /// </summary>\n        [ConfigurationProperty(\"recentDocuments\")]\n        [ConfigurationCollection(typeof(ConfigArrayList<string>.ItemElement))]\n        public ConfigArrayList<string> RecentDocuments\n        {\n            get { return (ConfigArrayList<string>)this[\"recentDocuments\"]; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/Entry.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.Config;\nusing WzComparerR2.PluginBase;\nusing DevComponents.DotNetBar;\n\nnamespace WzComparerR2.LuaConsole\n{\n    public class Entry : PluginEntry\n    {\n        public Entry(PluginContext context)\n            : base(context)\n        {\n            Instance = this;\n        }\n\n        internal static Entry Instance { get; private set; }\n\n        protected override void OnLoad()\n        {\n            var bar = this.Context.AddRibbonBar(\"Tools\", \"控制台\");\n            ButtonItem btnItem = new ButtonItem(\"\", \"Lua控制台\");\n\n            btnItem.Click += btnItem_Click;\n            bar.Items.Add(btnItem);\n            ConfigManager.RegisterAllSection(this.GetType().Assembly);\n        }\n\n        FrmConsole frm;\n\n        void btnItem_Click(object sender, EventArgs e)\n        {\n            if (frm == null || frm.IsDisposed)\n            {\n                frm = new FrmConsole();\n                frm.Owner = Context.MainForm;\n                \n            }\n            frm.Show();\n            frm.Focus();\n        }\n\n        protected override void OnUnload()\n        {\n            base.OnUnload();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/Examples/DumpAnimations.lua",
    "content": "import 'WzComparerR2.PluginBase'\nimport 'WzComparerR2.WzLib'\nimport 'WzComparerR2.Common'\nimport 'WzComparerR2.Encoders'\nimport 'System.IO'\nimport 'System.Xml'\nimport 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'\nimport 'System.Drawing'\nimport 'System.Drawing.Imaging'\n\nrequire 'Helper'\n\n------------------------------------------------------------\n\nlocal function isPng(value)\n  return value and type(value) == \"userdata\" and value:GetType().Name == 'Wz_Png'\nend\n\nlocal function isDelay(node)\n  return node.Text == \"delay\" and node:GetType().Name == 'Wz_Node'\nend\n\nlocal function isPngWithDelay(node)\n  if isPng(node.Value) then\n    for n in enumAllWzNodes(node) do\n      if isDelay(n) then\n        return true\n      end\n    end\n  end\n  return false\nend\n\nlocal function enumAllWzPngNodesWithDelay(node) \n  return coroutine.wrap(function()\n    if isPng(node.Value) and isPngWithDelay(node) then\n      coroutine.yield(node)\n    end\n    for _, n in each(node.Nodes) do\n      for child in enumAllWzPngNodesWithDelay(n) do\n        coroutine.yield(child)\n      end\n    end\n  end)\nend\n\nlocal function findNodeFunc(path)\n  return PluginManager.FindWz(path)\nend\n\nlocal t_IGifFrame = {}\nt_IGifFrame.typeRef = luanet.import_type('WzComparerR2.Common.IGifFrame')\nt_IGifFrame.Draw = luanet.get_method_bysig(t_IGifFrame.typeRef, 'Draw', \"System.Drawing.Graphics\", \"System.Drawing.Rectangle\")\n\nlocal function saveAnimatedImage(node, fileName)\n  local gif = Gif.CreateFromNode(node, findNodeFunc)\n  local rect = gif:GetRect()\n  local enc = BuildInApngEncoder()\n  enc:Init(fileName, rect.Width, rect.Height)\n  enc.OptimizeEnabled = false\n  \n  for i,frame in each(gif.Frames) do\n    local bmp = Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb)\n    local g = Graphics.FromImage(bmp)\n    t_IGifFrame.Draw(frame, g, rect)\n    g:Dispose()\n    enc:AppendFrame(bmp, frame.Delay)\n    bmp:Dispose()\n    bmp = nil\n  end\n  \n  enc:Dispose()\n  \n  for i,frame in each(gif.Frames) do\n    frame.Bitmap:Dispose();\n  end\n  gif = nil\nend\n\n------------------------------------------------------------\n\n-- all variables\nlocal topWzPath = 'Skill'\nlocal topNode = PluginManager.FindWz(topWzPath)\nlocal outputDir = \"D:\\\\wzDump\"\n\n------------------------------------------------------------\n-- main function\n\nif not topNode then\n  env:WriteLine('\"{0}\" not loaded.', topWzPath)\n  return\nend\n\nfor n in enumAllWzNodes(topNode) do\n  local img = Wz_NodeExtension.GetNodeWzImage(n)\n  if img then\n    --extract wz image\n    env:WriteLine('(extract) '..(img.Name))\n\n    if img:TryExtract() then\n      local dir = outputDir..\"\\\\\"..(n.FullPathToFile)\n      local dirCreated = false\n      local fullpaths = {}\n\n      for n2 in enumAllWzPngNodesWithDelay(img.Node) do\n        local parentNode = n2.ParentNode\n        local fullpath = parentNode.FullPath\n\n        -- Skip if 'fullpath' already done\n        if (not fullpaths[fullpath]) then\n          local fn = fullpath:sub(img.Name:len()+2):gsub(\"\\\\\", \".\")\n          fn = removeInvalidPathChars(fn)\n          fn = Path.Combine(dir, fn .. \".apng\")\n\n          --ensure dir exists\n          if not dirCreated then\n            if not Directory.Exists(dir) then\n              Directory.CreateDirectory(dir)\n            end\n            dirCreated = true\n          end\n\n          saveAnimatedImage(parentNode, fn)\n          \n          fullpaths[fullpath] = true\n        end\n      end\n      \n      img:Unextract()\n\n    else --error\n      env:WriteLine((img.Name)..' extract failed.')\n      \n    end --end extract\n  end -- end type validate\nend -- end foreach\n\nenv:WriteLine('--------Done.---------')"
  },
  {
    "path": "WzComparerR2.LuaConsole/Examples/DumpImages.lua",
    "content": "﻿import 'WzComparerR2.PluginBase'\nimport 'WzComparerR2.WzLib'\nimport 'System.IO'\nimport 'System.Xml'\n\nrequire 'Helper'\n\n------------------------------------------------------------\n\nlocal function isPng(value)\n  return value and type(value) == \"userdata\" and value:GetType().Name == 'Wz_Png'\nend\n\n------------------------------------------------------------\n\n-- all variables\nlocal topWzPath = 'Character'\nlocal topNode = PluginManager.FindWz(topWzPath)\nlocal outputDir = \"D:\\\\wzDump\"\nlocal errorList = {}  -- collect error messages here\n\n------------------------------------------------------------\n-- main function\n\nif not topNode then\n  env:WriteLine('\"{0}\" not loaded.', topWzPath)\n  return\nend\n\n\n\n-- enum all wz_images\nfor n in enumAllWzNodes(topNode) do\n  local img = Wz_NodeExtension.GetNodeWzImage(n)\n  \n  if img then\n    env:WriteLine('(extract) ' .. (img.Name))\n    local success, extractErr = pcall(function()\n      if img:TryExtract() then\n        local dir = outputDir .. \"\\\\\" .. (n.FullPathToFile)\n        local dirCreated = false\n\n        for n2 in enumAllWzNodes(img.Node) do\n          local png = n2.Value\n          if isPng(png) and (png.Width > 1 or png.Height > 1) then\n            local fn = n2.FullPath:sub(img.Name:len() + 2):gsub(\"\\\\\", \".\")\n            fn = removeInvalidPathChars(fn)\n            fn = Path.Combine(dir, fn .. \".png\")\n\n            if not dirCreated then\n              if not Directory.Exists(dir) then\n                Directory.CreateDirectory(dir)\n              end\n              dirCreated = true\n            end\n\n            -- Try saving PNG\n            local successSave, saveErr = pcall(function()\n              local bmp = png:ExtractPng()\n              if bmp then\n                bmp:Save(fn)\n                bmp:Dispose()\n              else\n                error(\"ExtractPng() returned nil.\")\n              end\n            end)\n\n            if not successSave then\n              local errMsg = string.format(\"Error saving PNG [%s]: %s\", n2.FullPath, saveErr)\n              env:WriteLine(errMsg)\n              table.insert(errorList, errMsg)\n            end\n          end\n        end\n        img:Unextract()\n      else\n        local errMsg = string.format(\"%s extract failed.\", img.Name)\n        env:WriteLine(errMsg)\n        table.insert(errorList, errMsg)\n      end\n    end)\n\n    if not success then\n      local errMsg = string.format(\"Unexpected error in image [%s]: %s\", img.Name, extractErr)\n      env:WriteLine(errMsg)\n      table.insert(errorList, errMsg)\n    end\n  end\nend\n\n-- Final error report\nif #errorList > 0 then\n  env:WriteLine(\"-------- Error Summary --------\")\n  for i, err in ipairs(errorList) do\n    env:WriteLine(err)\n  end\nend\n\nenv:WriteLine('-------- Done. --------')"
  },
  {
    "path": "WzComparerR2.LuaConsole/Examples/DumpSounds.lua",
    "content": "import 'WzComparerR2.PluginBase'\nimport 'WzComparerR2.WzLib'\nimport 'System.IO'\nimport 'System.Xml'\n\nrequire 'Helper'\n\n------------------------------------------------------------\n\nlocal function isSound(value)\n  return value and type(value) == \"userdata\" and value:GetType().Name == 'Wz_Sound'\nend\n\n------------------------------------------------------------\n\n-- all variables\nlocal topWzPath = 'Sound\\\\Bgm00.img'\nlocal topNode = PluginManager.FindWz(topWzPath)\nlocal outputDir = \"D:\\\\wzDump\"\n\n------------------------------------------------------------\n-- main function\n\nif not topNode then\n  env:WriteLine('\"{0}\" not loaded.', topWzPath)\n  return\nend\n\n\n\n-- enum all wz_images\nfor n in enumAllWzNodes(topNode) do\n  local img = Wz_NodeExtension.GetNodeWzImage(n)\n  \n  if img then\n    --extract wz image\n    env:WriteLine('(extract)'..(img.Name))\n    if img:TryExtract() then\n    \n      local dir = outputDir..\"\\\\\"..(n.FullPathToFile)\n      local dirCreated = false\n      \n      --find all sound\n      for n2 in enumAllWzNodes(img.Node) do\n        local sound = n2.Value\n        if isSound(sound) then\n          \n          local fn = n2.FullPath:sub(img.Name:len()+2):gsub(\"\\\\\", \".\")\n          fn = removeInvalidPathChars(fn)\n          if not n2.Text:find(\"\\\\.\") then\n            if sound.SoundType == Wz_SoundType.Mp3 then\n              fn = fn .. \".mp3\"\n            end\n            if sound.SoundType == Wz_SoundType.Pcm then\n              fn = fn .. \".wav\"\n            end\n          end\n          fn = Path.Combine(dir, fn)\n          \n          --ensure dir exists\n          if not dirCreated then\n            if not Directory.Exists(dir) then\n              Directory.CreateDirectory(dir)\n            end\n            dirCreated = true\n          end\n          \n          --save sound\n          env:WriteLine('(output)'..fn)\n          File.WriteAllBytes(fn, sound:ExtractSound())\n          env:WriteLine('(close)'..fn)\n          \n        end\n      end\n      \n      img:Unextract()\n    else --error\n      \n      env:WriteLine((img.Name)..' extract failed.')\n      \n    end --end extract\n  end -- end type validate\nend -- end foreach\n\nenv:WriteLine('--------Done.---------')"
  },
  {
    "path": "WzComparerR2.LuaConsole/Examples/DumpXml.lua",
    "content": "import 'WzComparerR2.PluginBase'\nimport 'WzComparerR2.WzLib'\nimport 'System.IO'\nimport 'System.Xml'\n\nrequire \"helper\"\n\n------------------------------------------------------------\n\n-- all variables\nlocal topNode = PluginManager.FindWz('Etc')\nlocal outputDir = \"D:\\\\wzDump\"\n\n------------------------------------------------------------\n-- main function\n\nif not topNode then\n  env:WriteLine('Base.wz not loaded.')\n  return\nend\n\n-- enum all wz_images\nfor n in enumAllWzNodes(topNode) do\n  local value = n.Value\n  if isWzImage(value) then\n    local img = value\n\n    --extract wz image\n    env:WriteLine('(extract)'..(img.Name))\n    if img:TryExtract() then\n    \n      --dump as Xml\n      local xmlFileName = outputDir..\"\\\\\"..(n.FullPathToFile)..\".xml\"\n      local dir = Path.GetDirectoryName(xmlFileName)\n    \n      --ensure dir exists\n      if not Directory.Exists(dir) then\n        Directory.CreateDirectory(dir)\n      end\n      \n      --create file\n      env:WriteLine('(output)'..xmlFileName)\n      local fs = File.Create(xmlFileName)\n      local xw = XmlWriter.Create(fs)\n      \n      xw:WriteStartDocument(true);\n      Wz_NodeExtension.DumpAsXml(img.Node, xw)\n      xw:WriteEndDocument()\n      \n      xw:Flush()\n      fs:Close()\n      env:WriteLine('(close)'..xmlFileName)\n      \n      img:Unextract()\n      \n    else --error\n      \n      env:WriteLine((img.Name)..' extract failed.')\n      \n    end --end extract\n  end -- end type validate\nend -- end foreach\n\nenv:WriteLine('--------Done.---------')"
  },
  {
    "path": "WzComparerR2.LuaConsole/Examples/Helper.lua",
    "content": "﻿import 'System.IO'\n\nfunction isWzImage(value)\n  return value and type(value) == \"userdata\" \n    and (value:GetType().Name == 'Wz_Image' or value:GetType().Name == 'Ms_Image')\nend\n\nfunction enumAllWzNodes(node) \n  return coroutine.wrap(function()\n    coroutine.yield(node)\n    for _,v in each(node.Nodes) do\n      for child in enumAllWzNodes(v) do\n        coroutine.yield(child)\n      end\n    end\n  end)\nend\n\nlocal p = Path.GetInvalidFileNameChars()\nlocal ivStr = \"\"\nfor i, v in each(p) do\n  if v >= 32 then\n    ivStr = ivStr .. string.char(v)\n  end\nend\nlocal ivPattern = \"[\"..ivStr..\"]\"\n\nfunction removeInvalidPathChars(fileName)\n  return fileName:gsub(ivPattern, \"\")\nend\n\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/FrmConsole.Designer.cs",
    "content": "﻿namespace WzComparerR2.LuaConsole\n{\n    partial class FrmConsole\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.components = new System.ComponentModel.Container();\n            this.dotNetBarManager1 = new DevComponents.DotNetBar.DotNetBarManager(this.components);\n            this.dockSite4 = new DevComponents.DotNetBar.DockSite();\n            this.bar2 = new DevComponents.DotNetBar.Bar();\n            this.panelDockContainer1 = new DevComponents.DotNetBar.PanelDockContainer();\n            this.textBoxX2 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.dockContainerItem1 = new DevComponents.DotNetBar.DockContainerItem();\n            this.dockSite9 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite1 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite2 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite8 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite5 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite6 = new DevComponents.DotNetBar.DockSite();\n            this.dockSite7 = new DevComponents.DotNetBar.DockSite();\n            this.bar1 = new DevComponents.DotNetBar.Bar();\n            this.menuFile = new DevComponents.DotNetBar.ButtonItem();\n            this.menuNew = new DevComponents.DotNetBar.ButtonItem();\n            this.menuOpen = new DevComponents.DotNetBar.ButtonItem();\n            this.menuSave = new DevComponents.DotNetBar.ButtonItem();\n            this.menuSaveAs = new DevComponents.DotNetBar.ButtonItem();\n            this.menuExit = new DevComponents.DotNetBar.ButtonItem();\n            this.menuDebug = new DevComponents.DotNetBar.ButtonItem();\n            this.menuReset = new DevComponents.DotNetBar.ButtonItem();\n            this.menuRun = new DevComponents.DotNetBar.ButtonItem();\n            this.menuStopRun = new DevComponents.DotNetBar.ButtonItem();\n            this.dockSite3 = new DevComponents.DotNetBar.DockSite();\n            this.dockContainerItem3 = new DevComponents.DotNetBar.DockContainerItem();\n            this.tabStrip1 = new DevComponents.DotNetBar.TabStrip();\n            this.menuRecent = new DevComponents.DotNetBar.ButtonItem();\n            this.dockSite4.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.bar2)).BeginInit();\n            this.bar2.SuspendLayout();\n            this.panelDockContainer1.SuspendLayout();\n            this.dockSite7.SuspendLayout();\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).BeginInit();\n            this.SuspendLayout();\n            // \n            // dotNetBarManager1\n            // \n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.F1);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlC);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlA);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlV);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlX);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlZ);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlY);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.Del);\n            this.dotNetBarManager1.AutoDispatchShortcuts.Add(DevComponents.DotNetBar.eShortcut.Ins);\n            this.dotNetBarManager1.BottomDockSite = this.dockSite4;\n            this.dotNetBarManager1.EnableFullSizeDock = false;\n            this.dotNetBarManager1.FillDockSite = this.dockSite9;\n            this.dotNetBarManager1.LeftDockSite = this.dockSite1;\n            this.dotNetBarManager1.MdiSystemItemVisible = false;\n            this.dotNetBarManager1.ParentForm = this;\n            this.dotNetBarManager1.RightDockSite = this.dockSite2;\n            this.dotNetBarManager1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.dotNetBarManager1.ToolbarBottomDockSite = this.dockSite8;\n            this.dotNetBarManager1.ToolbarLeftDockSite = this.dockSite5;\n            this.dotNetBarManager1.ToolbarRightDockSite = this.dockSite6;\n            this.dotNetBarManager1.ToolbarTopDockSite = this.dockSite7;\n            this.dotNetBarManager1.TopDockSite = this.dockSite3;\n            // \n            // dockSite4\n            // \n            this.dockSite4.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite4.Controls.Add(this.bar2);\n            this.dockSite4.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.dockSite4.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer(new DevComponents.DotNetBar.DocumentBaseContainer[] {\n            ((DevComponents.DotNetBar.DocumentBaseContainer)(new DevComponents.DotNetBar.DocumentBarContainer(this.bar2, 488, 95)))}, DevComponents.DotNetBar.eOrientation.Vertical);\n            this.dockSite4.Location = new System.Drawing.Point(0, 282);\n            this.dockSite4.Name = \"dockSite4\";\n            this.dockSite4.Size = new System.Drawing.Size(488, 98);\n            this.dockSite4.TabIndex = 7;\n            this.dockSite4.TabStop = false;\n            // \n            // bar2\n            // \n            this.bar2.AccessibleDescription = \"DotNetBar Bar (bar2)\";\n            this.bar2.AccessibleName = \"DotNetBar Bar\";\n            this.bar2.AccessibleRole = System.Windows.Forms.AccessibleRole.Grouping;\n            this.bar2.AutoSyncBarCaption = true;\n            this.bar2.CloseSingleTab = true;\n            this.bar2.Controls.Add(this.panelDockContainer1);\n            this.bar2.Font = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.bar2.GrabHandleStyle = DevComponents.DotNetBar.eGrabHandleStyle.Caption;\n            this.bar2.IsMaximized = false;\n            this.bar2.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.dockContainerItem1});\n            this.bar2.LayoutType = DevComponents.DotNetBar.eLayoutType.DockContainer;\n            this.bar2.Location = new System.Drawing.Point(0, 3);\n            this.bar2.Name = \"bar2\";\n            this.bar2.Size = new System.Drawing.Size(488, 95);\n            this.bar2.Stretch = true;\n            this.bar2.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.bar2.TabIndex = 0;\n            this.bar2.TabStop = false;\n            this.bar2.Text = \"输出\";\n            // \n            // panelDockContainer1\n            // \n            this.panelDockContainer1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelDockContainer1.Controls.Add(this.textBoxX2);\n            this.panelDockContainer1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelDockContainer1.Location = new System.Drawing.Point(3, 23);\n            this.panelDockContainer1.Name = \"panelDockContainer1\";\n            this.panelDockContainer1.Size = new System.Drawing.Size(482, 69);\n            this.panelDockContainer1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelDockContainer1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.panelDockContainer1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.panelDockContainer1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.panelDockContainer1.Style.GradientAngle = 90;\n            this.panelDockContainer1.TabIndex = 0;\n            // \n            // textBoxX2\n            // \n            // \n            // \n            // \n            this.textBoxX2.Border.Class = \"TextBoxBorder\";\n            this.textBoxX2.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.textBoxX2.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.textBoxX2.Location = new System.Drawing.Point(0, 0);\n            this.textBoxX2.Multiline = true;\n            this.textBoxX2.Name = \"textBoxX2\";\n            this.textBoxX2.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;\n            this.textBoxX2.Size = new System.Drawing.Size(482, 69);\n            this.textBoxX2.TabIndex = 1;\n            this.textBoxX2.Text = \"--调用env:Help() 获取帮助\\r\\n\";\n            // \n            // dockContainerItem1\n            // \n            this.dockContainerItem1.Control = this.panelDockContainer1;\n            this.dockContainerItem1.Name = \"dockContainerItem1\";\n            this.dockContainerItem1.Text = \"输出\";\n            // \n            // dockSite9\n            // \n            this.dockSite9.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite9.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite9.Location = new System.Drawing.Point(0, 26);\n            this.dockSite9.Name = \"dockSite9\";\n            this.dockSite9.Size = new System.Drawing.Size(0, 0);\n            this.dockSite9.TabIndex = 12;\n            this.dockSite9.TabStop = false;\n            // \n            // dockSite1\n            // \n            this.dockSite1.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite1.Dock = System.Windows.Forms.DockStyle.Left;\n            this.dockSite1.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite1.Location = new System.Drawing.Point(0, 26);\n            this.dockSite1.Name = \"dockSite1\";\n            this.dockSite1.Size = new System.Drawing.Size(0, 256);\n            this.dockSite1.TabIndex = 4;\n            this.dockSite1.TabStop = false;\n            // \n            // dockSite2\n            // \n            this.dockSite2.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite2.Dock = System.Windows.Forms.DockStyle.Right;\n            this.dockSite2.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite2.Location = new System.Drawing.Point(488, 26);\n            this.dockSite2.Name = \"dockSite2\";\n            this.dockSite2.Size = new System.Drawing.Size(0, 256);\n            this.dockSite2.TabIndex = 5;\n            this.dockSite2.TabStop = false;\n            // \n            // dockSite8\n            // \n            this.dockSite8.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite8.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.dockSite8.Location = new System.Drawing.Point(0, 380);\n            this.dockSite8.Name = \"dockSite8\";\n            this.dockSite8.Size = new System.Drawing.Size(488, 0);\n            this.dockSite8.TabIndex = 11;\n            this.dockSite8.TabStop = false;\n            // \n            // dockSite5\n            // \n            this.dockSite5.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite5.Dock = System.Windows.Forms.DockStyle.Left;\n            this.dockSite5.Location = new System.Drawing.Point(0, 26);\n            this.dockSite5.Name = \"dockSite5\";\n            this.dockSite5.Size = new System.Drawing.Size(0, 354);\n            this.dockSite5.TabIndex = 8;\n            this.dockSite5.TabStop = false;\n            // \n            // dockSite6\n            // \n            this.dockSite6.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite6.Dock = System.Windows.Forms.DockStyle.Right;\n            this.dockSite6.Location = new System.Drawing.Point(488, 26);\n            this.dockSite6.Name = \"dockSite6\";\n            this.dockSite6.Size = new System.Drawing.Size(0, 354);\n            this.dockSite6.TabIndex = 9;\n            this.dockSite6.TabStop = false;\n            // \n            // dockSite7\n            // \n            this.dockSite7.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite7.Controls.Add(this.bar1);\n            this.dockSite7.Dock = System.Windows.Forms.DockStyle.Top;\n            this.dockSite7.Location = new System.Drawing.Point(0, 0);\n            this.dockSite7.Name = \"dockSite7\";\n            this.dockSite7.Size = new System.Drawing.Size(488, 26);\n            this.dockSite7.TabIndex = 10;\n            this.dockSite7.TabStop = false;\n            // \n            // bar1\n            // \n            this.bar1.AccessibleDescription = \"DotNetBar Bar (bar1)\";\n            this.bar1.AccessibleName = \"DotNetBar Bar\";\n            this.bar1.AccessibleRole = System.Windows.Forms.AccessibleRole.MenuBar;\n            this.bar1.DockSide = DevComponents.DotNetBar.eDockSide.Top;\n            this.bar1.Font = new System.Drawing.Font(\"Microsoft YaHei UI\", 9F);\n            this.bar1.IsMaximized = false;\n            this.bar1.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.menuFile,\n            this.menuDebug});\n            this.bar1.Location = new System.Drawing.Point(0, 0);\n            this.bar1.MenuBar = true;\n            this.bar1.Name = \"bar1\";\n            this.bar1.Size = new System.Drawing.Size(488, 25);\n            this.bar1.Stretch = true;\n            this.bar1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.bar1.TabIndex = 0;\n            this.bar1.TabStop = false;\n            this.bar1.Text = \"bar1\";\n            // \n            // menuFile\n            // \n            this.menuFile.Name = \"menuFile\";\n            this.menuFile.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.menuNew,\n            this.menuOpen,\n            this.menuSave,\n            this.menuSaveAs,\n            this.menuRecent,\n            this.menuExit});\n            this.menuFile.Text = \"文件(&F)\";\n            // \n            // menuNew\n            // \n            this.menuNew.Name = \"menuNew\";\n            this.menuNew.Shortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlN);\n            this.menuNew.Text = \"新建\";\n            this.menuNew.Click += new System.EventHandler(this.menuNew_Click);\n            // \n            // menuOpen\n            // \n            this.menuOpen.Name = \"menuOpen\";\n            this.menuOpen.Shortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlO);\n            this.menuOpen.Text = \"打开\";\n            this.menuOpen.Click += new System.EventHandler(this.menuOpen_Click);\n            // \n            // menuSave\n            // \n            this.menuSave.BeginGroup = true;\n            this.menuSave.Name = \"menuSave\";\n            this.menuSave.Shortcuts.Add(DevComponents.DotNetBar.eShortcut.CtrlS);\n            this.menuSave.Text = \"保存\";\n            this.menuSave.Click += new System.EventHandler(this.menuSave_Click);\n            // \n            // menuSaveAs\n            // \n            this.menuSaveAs.Name = \"menuSaveAs\";\n            this.menuSaveAs.Text = \"另存为...\";\n            this.menuSaveAs.Click += new System.EventHandler(this.menuSaveAs_Click);\n            // \n            // menuExit\n            // \n            this.menuExit.BeginGroup = true;\n            this.menuExit.Name = \"menuExit\";\n            this.menuExit.Shortcuts.Add(DevComponents.DotNetBar.eShortcut.AltF4);\n            this.menuExit.Text = \"退出\";\n            this.menuExit.Click += new System.EventHandler(this.menuExit_Click);\n            // \n            // menuDebug\n            // \n            this.menuDebug.Name = \"menuDebug\";\n            this.menuDebug.SubItems.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.menuReset,\n            this.menuRun,\n            this.menuStopRun});\n            this.menuDebug.Text = \"调试(&D)\";\n            // \n            // menuReset\n            // \n            this.menuReset.Name = \"menuReset\";\n            this.menuReset.Symbol = \"\";\n            this.menuReset.SymbolColor = System.Drawing.Color.Gray;\n            this.menuReset.SymbolSize = 9F;\n            this.menuReset.Text = \"重置\";\n            this.menuReset.Click += new System.EventHandler(this.menuReset_Click);\n            // \n            // menuRun\n            // \n            this.menuRun.BeginGroup = true;\n            this.menuRun.Name = \"menuRun\";\n            this.menuRun.Shortcuts.Add(DevComponents.DotNetBar.eShortcut.F5);\n            this.menuRun.Symbol = \"\";\n            this.menuRun.SymbolColor = System.Drawing.Color.FromArgb(((int)(((byte)(29)))), ((int)(((byte)(127)))), ((int)(((byte)(29)))));\n            this.menuRun.SymbolSize = 9F;\n            this.menuRun.Text = \"运行\";\n            this.menuRun.Tooltip = \"F5\";\n            this.menuRun.Click += new System.EventHandler(this.menuRun_Click);\n            // \n            // menuStopRun\n            // \n            this.menuStopRun.Name = \"menuStopRun\";\n            this.menuStopRun.Shortcuts.Add(DevComponents.DotNetBar.eShortcut.ShiftF5);\n            this.menuStopRun.Symbol = \"\";\n            this.menuStopRun.SymbolColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));\n            this.menuStopRun.SymbolSize = 9F;\n            this.menuStopRun.Text = \"停止运行\";\n            this.menuStopRun.Tooltip = \"Shift+F5\";\n            this.menuStopRun.Click += new System.EventHandler(this.menuStopRun_Click);\n            // \n            // dockSite3\n            // \n            this.dockSite3.AccessibleRole = System.Windows.Forms.AccessibleRole.Window;\n            this.dockSite3.Dock = System.Windows.Forms.DockStyle.Top;\n            this.dockSite3.DocumentDockContainer = new DevComponents.DotNetBar.DocumentDockContainer();\n            this.dockSite3.Location = new System.Drawing.Point(0, 26);\n            this.dockSite3.Name = \"dockSite3\";\n            this.dockSite3.Size = new System.Drawing.Size(488, 0);\n            this.dockSite3.TabIndex = 6;\n            this.dockSite3.TabStop = false;\n            // \n            // dockContainerItem3\n            // \n            this.dockContainerItem3.Name = \"dockContainerItem3\";\n            this.dockContainerItem3.Text = \"dockContainerItem3\";\n            // \n            // tabStrip1\n            // \n            this.tabStrip1.AutoSelectAttachedControl = true;\n            this.tabStrip1.CanReorderTabs = true;\n            this.tabStrip1.CloseButtonVisible = true;\n            this.tabStrip1.Dock = System.Windows.Forms.DockStyle.Top;\n            this.tabStrip1.Location = new System.Drawing.Point(0, 26);\n            this.tabStrip1.MdiForm = this;\n            this.tabStrip1.MdiTabbedDocuments = true;\n            this.tabStrip1.Name = \"tabStrip1\";\n            this.tabStrip1.SelectedTab = null;\n            this.tabStrip1.SelectedTabFont = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Bold);\n            this.tabStrip1.Size = new System.Drawing.Size(488, 23);\n            this.tabStrip1.Style = DevComponents.DotNetBar.eTabStripStyle.Office2007Document;\n            this.tabStrip1.TabAlignment = DevComponents.DotNetBar.eTabStripAlignment.Top;\n            this.tabStrip1.TabIndex = 14;\n            this.tabStrip1.TabLayoutType = DevComponents.DotNetBar.eTabLayoutType.FixedWithNavigationBox;\n            this.tabStrip1.Text = \"tabStrip1\";\n            // \n            // menuRecent\n            // \n            this.menuRecent.BeginGroup = true;\n            this.menuRecent.Name = \"menuRecent\";\n            this.menuRecent.Text = \"最近打开的文件\";\n            // \n            // FrmConsole\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(488, 380);\n            this.Controls.Add(this.tabStrip1);\n            this.Controls.Add(this.dockSite9);\n            this.Controls.Add(this.dockSite2);\n            this.Controls.Add(this.dockSite1);\n            this.Controls.Add(this.dockSite3);\n            this.Controls.Add(this.dockSite4);\n            this.Controls.Add(this.dockSite5);\n            this.Controls.Add(this.dockSite6);\n            this.Controls.Add(this.dockSite7);\n            this.Controls.Add(this.dockSite8);\n            this.DoubleBuffered = true;\n            this.IsMdiContainer = true;\n            this.Name = \"FrmConsole\";\n            this.Text = \"Lua控制台\";\n            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmConsole_FormClosing);\n            this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FrmConsole_FormClosed);\n            this.MdiChildActivate += new System.EventHandler(this.FrmConsole_MdiChildActivate);\n            this.dockSite4.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.bar2)).EndInit();\n            this.bar2.ResumeLayout(false);\n            this.panelDockContainer1.ResumeLayout(false);\n            this.dockSite7.ResumeLayout(false);\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).EndInit();\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private DevComponents.DotNetBar.DotNetBarManager dotNetBarManager1;\n        private DevComponents.DotNetBar.DockSite dockSite4;\n        private DevComponents.DotNetBar.Bar bar2;\n        private DevComponents.DotNetBar.PanelDockContainer panelDockContainer1;\n        private DevComponents.DotNetBar.DockContainerItem dockContainerItem1;\n        private DevComponents.DotNetBar.DockSite dockSite9;\n        private DevComponents.DotNetBar.DockSite dockSite1;\n        private DevComponents.DotNetBar.DockSite dockSite2;\n        private DevComponents.DotNetBar.DockSite dockSite3;\n        private DevComponents.DotNetBar.DockSite dockSite5;\n        private DevComponents.DotNetBar.DockSite dockSite6;\n        private DevComponents.DotNetBar.DockSite dockSite7;\n        private DevComponents.DotNetBar.Bar bar1;\n        private DevComponents.DotNetBar.ButtonItem menuRun;\n        private DevComponents.DotNetBar.DockSite dockSite8;\n        private DevComponents.DotNetBar.Controls.TextBoxX textBoxX2;\n        private DevComponents.DotNetBar.DockContainerItem dockContainerItem3;\n        private DevComponents.DotNetBar.ButtonItem menuStopRun;\n        private DevComponents.DotNetBar.ButtonItem menuFile;\n        private DevComponents.DotNetBar.TabStrip tabStrip1;\n        private DevComponents.DotNetBar.ButtonItem menuNew;\n        private DevComponents.DotNetBar.ButtonItem menuOpen;\n        private DevComponents.DotNetBar.ButtonItem menuSave;\n        private DevComponents.DotNetBar.ButtonItem menuExit;\n        private DevComponents.DotNetBar.ButtonItem menuDebug;\n        private DevComponents.DotNetBar.ButtonItem menuReset;\n        private DevComponents.DotNetBar.ButtonItem menuRecent;\n        private DevComponents.DotNetBar.ButtonItem menuSaveAs;\n    }\n}"
  },
  {
    "path": "WzComparerR2.LuaConsole/FrmConsole.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Text.RegularExpressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing NLua;\n\nusing WzComparerR2.Config;\nusing WzComparerR2.LuaConsole.Config;\nusing WzComparerR2.PluginBase;\n\nnamespace WzComparerR2.LuaConsole\n{\n    public partial class FrmConsole : DevComponents.DotNetBar.Office2007Form\n    {\n        public FrmConsole()\n        {\n            InitializeComponent();\n            this.refreshRecentDocItems();\n            this.env = new LuaEnvironment(this);\n        }\n\n        private FrmLuaEditor SelectedLuaEditor => tabStrip1.SelectedTab?.AttachedControl as FrmLuaEditor;\n\n        private readonly LuaEnvironment env;\n        private Task runningTask;\n        private CancellationTokenSource cancellationTokenSource;\n\n        private void FrmConsole_FormClosing(object sender, FormClosingEventArgs e)\n        {\n            if (e.CloseReason == CloseReason.UserClosing && this.runningTask?.IsCompleted == false)\n            {\n                if (DialogResult.Yes != MessageBoxEx.Show(this, \"任务还在运行中，是否关闭？\", \"提示\", MessageBoxButtons.YesNo, MessageBoxIcon.Warning))\n                {\n                    e.Cancel = true;\n                    return;\n                }\n            }\n        }\n\n        private void FrmConsole_FormClosed(object sender, FormClosedEventArgs e)\n        {\n            if (this.runningTask?.IsCompleted == false && this.cancellationTokenSource != null)\n            {\n                this.cancellationTokenSource.Cancel();\n                try\n                {\n                    this.runningTask.Wait();\n                }\n                catch\n                {\n                    // ignore any error\n                }\n            }\n\n            foreach (var frm in this.MdiChildren)\n            {\n                if (frm is FrmLuaEditor editor && editor.Tag is LuaSandbox sandbox)\n                {\n                    sandbox.Dispose();\n                }\n            }\n        }\n\n        private void FrmConsole_MdiChildActivate(object sender, EventArgs e)\n        {\n        }\n\n        private void menuReset_Click(object sender, EventArgs e)\n        {\n            if (this.runningTask?.IsCompleted == false)\n            {\n                ToastNotification.Show(this, \"已有任务正在运行\", 1000, eToastPosition.TopCenter);\n                return;\n            }\n\n            var selectedEditor = this.SelectedLuaEditor;\n            if (selectedEditor == null || selectedEditor.Tag is not LuaSandbox sandbox)\n            {\n                MessageBoxEx.Show(this, \"获取当前所选窗体失败。\", \"Error\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n                return;\n            }\n\n            string baseDir = null;\n            if (selectedEditor.FileName != null)\n            {\n                baseDir = Path.GetDirectoryName(selectedEditor.FileName);\n            }\n            sandbox.InitLuaEnv(baseDir, true);\n            this.env.WriteLine(\"运行时已重置\");\n        }\n\n        private void menuNew_Click(object sender, EventArgs e)\n        {\n            this.CreateNewTab();\n        }\n\n        private async void menuRun_Click(object sender, EventArgs e)\n        {\n            if (this.runningTask?.IsCompleted == false)\n            {\n                ToastNotification.Show(this, \"已有任务正在运行\", 1000, eToastPosition.TopCenter);\n                return;\n            }\n\n            var selectedEditor = this.SelectedLuaEditor;\n            if (selectedEditor == null || selectedEditor.Tag is not LuaSandbox sandbox)\n            {\n                MessageBoxEx.Show(this, \"获取当前所选窗体失败。\", \"Error\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n                return;\n            }\n\n            try\n            {\n                string baseDir = null;\n                if (selectedEditor.FileName != null)\n                {\n                    baseDir = Path.GetDirectoryName(selectedEditor.FileName);\n                }\n\n                this.env.WriteLine(\"开始执行{0}...\", selectedEditor.BaseFileName);\n                sandbox.InitLuaEnv(baseDir);\n                this.cancellationTokenSource = new CancellationTokenSource();\n                this.runningTask = sandbox.DoStringAsync(selectedEditor.CodeContent, cancellationTokenSource.Token);\n                await this.runningTask;\n            }\n            catch (NLua.Exceptions.LuaScriptException ex)\n            {\n                env.WriteLine(ex);\n                if (ex.IsNetException && ex.InnerException != null)\n                {\n                    env.WriteLine(ex.InnerException);\n                }\n            }\n            catch (Exception ex)\n            {\n                env.WriteLine(ex);\n            }\n        }\n\n        private void menuStopRun_Click(object sender, EventArgs e)\n        {\n            if (this.runningTask?.IsCompleted == false && this.cancellationTokenSource != null)\n            {\n                this.cancellationTokenSource.Cancel();\n                ToastNotification.Show(this, \"正在中止运行...\", 1000, eToastPosition.TopCenter);\n            }\n        }\n\n        private void menuOpen_Click(object sender, EventArgs e)\n        {\n            using OpenFileDialog dlg = new();\n            dlg.Filter = \"*.lua|*.lua|*.*|*.*\";\n            if (dlg.ShowDialog() == DialogResult.OK)\n            {\n                this.OpenFile(dlg.FileName);\n            }\n        }\n\n        private void menuSave_Click(object sender, EventArgs e)\n        {\n            if (tabStrip1.SelectedTab?.AttachedControl is FrmLuaEditor editor)\n            {\n                this.SaveFile(editor, false);\n            }\n        }\n\n        private void menuSaveAs_Click(object sender, EventArgs e)\n        {\n            if (tabStrip1.SelectedTab?.AttachedControl is FrmLuaEditor editor)\n            {\n                this.SaveFile(editor, true);\n            }\n        }\n\n        private void refreshRecentDocItems()\n        {\n            this.menuRecent.SubItems.Clear();\n            foreach (var doc in LuaConsoleConfig.Default.RecentDocuments)\n            {\n                ButtonItem item = new ButtonItem() { Text = \"&\" + (this.menuRecent.SubItems.Count + 1) + \". \" + Path.GetFileName(doc), Tooltip = doc, Tag = doc };\n                item.Click += RecentDocumentItem_Click;\n                this.menuRecent.SubItems.Add(item);\n            }\n        }\n\n        private void RecentDocumentItem_Click(object sender, EventArgs e)\n        {\n            if (sender is ButtonItem item && item.Tag is string fileName)\n            {\n                OpenFile(fileName);\n            }\n        }\n\n        private void menuExit_Click(object sender, EventArgs e)\n        {\n            this.Close();\n        }\n\n        private bool SaveFile(FrmLuaEditor editor, bool saveAs = false)\n        {\n            if (saveAs || string.IsNullOrEmpty(editor.FileName))\n            {\n                using SaveFileDialog dlg = new SaveFileDialog();\n                dlg.Filter = \"*.lua|*.lua|*.*|*.*\";\n                if (editor.BaseFileName != null)\n                {\n                    dlg.FileName = editor.BaseFileName;\n                }\n                if (dlg.ShowDialog() != DialogResult.OK)\n                {\n                    return false;\n                }\n                editor.FileName = dlg.FileName;\n            }\n\n            editor.SaveFile(editor.FileName);\n            this.env.WriteLine($\"====已经保存{editor.FileName}====\");\n            return true;\n        }\n\n        private void OpenFile(string fileName)\n        {\n            try\n            {\n                this.CreateNewTab(fileName);\n            }\n            catch (Exception ex)\n            {\n                MessageBoxEx.Show(this, ex.ToString(), \"Error\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n                return;\n            }\n\n            ConfigManager.Reload();\n            var config = LuaConsoleConfig.Default;\n            config.RecentDocuments.Remove(fileName);\n            config.RecentDocuments.Insert(0, fileName);\n            for (int i = config.RecentDocuments.Count - 1; i >= 10; i--)\n            {\n                config.RecentDocuments.RemoveAt(i);\n            }\n            ConfigManager.Save();\n            refreshRecentDocItems();\n        }\n\n        private void CreateNewTab(string fileName = null)\n        {\n            FrmLuaEditor frm = new FrmLuaEditor();\n            frm.FileNameChanged += this.FrmLuaEditor_FileNameChanged;\n            frm.FormClosing += this.FrmLuaEditor_FormClosing;\n            frm.FormClosed += this.FrmLuaEditor_FormClosed;\n            frm.Tag = new LuaSandbox(this.env);\n            try\n            {\n                if (!string.IsNullOrEmpty(fileName))\n                {\n                    frm.LoadFile(fileName);\n                }\n\n                frm.MdiParent = this;\n                frm.WindowState = FormWindowState.Maximized;\n                frm.Show();\n            }\n            catch\n            {\n                frm.Dispose();\n                throw;\n            }\n        }\n\n        private void FrmLuaEditor_FileNameChanged(object sender, EventArgs e)\n        {\n            if (sender is FrmLuaEditor frm)\n            {\n                foreach (TabItem tab in this.tabStrip1.Tabs)\n                {\n                    if (tab.AttachedControl == frm)\n                    {\n                        tab.Tooltip = frm.FileName;\n                        break;\n                    }\n                }\n            }\n        }\n\n        private void FrmLuaEditor_FormClosing(object sender, FormClosingEventArgs e)\n        {\n            if (sender is FrmLuaEditor editor && editor.IsContentModified)\n            {\n                switch(MessageBoxEx.Show(this, \"关闭窗口将丢失所有未保存的修改，是否保存？\", \"提示\", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning))\n                {\n                    case DialogResult.Yes:\n                        e.Cancel = !this.SaveFile(editor, false);\n                        break;\n                    case DialogResult.No:\n                        e.Cancel = false;\n                        break;\n                    default:\n                        e.Cancel = true;\n                        break;\n                }\n            }\n        }\n\n        private void FrmLuaEditor_FormClosed(object sender, FormClosedEventArgs e)\n        {\n            if (sender is FrmLuaEditor frm && frm.Tag is Lua lua)\n            {\n                lua.Dispose();\n            }\n        }\n\n        private Lua GetOrCreateLuaVM(FrmLuaEditor frm)\n        {\n            if (frm.Tag is not Lua lua)\n            {\n                lua = null;\n            }\n            return lua;\n        }\n\n        public class LuaEnvironment\n        {\n            internal LuaEnvironment(FrmConsole form)\n            {\n                this.form = form;\n            }\n\n            private FrmConsole form;\n\n            public PluginContext Context\n            {\n                get\n                {\n                    return Entry.Instance?.Context;\n                }\n            }\n\n            public void Write(object value)\n            {\n                if (value != null)\n                {\n                    this.AppendText(value.ToString());\n                }\n            }\n\n            public void Write(string format, params object[] args)\n            {\n                if (format != null)\n                {\n                    string content = string.Format(format, args ?? new object[0]);\n                    this.AppendText(content);\n                }\n            }\n\n            public void WriteLine()\n            {\n                this.WriteLine(null);\n            }\n\n            public void WriteLine(object value)\n            {\n                if (value != null)\n                {\n                    this.AppendText(value.ToString());\n                }\n                this.AppendText(Environment.NewLine);\n            }\n\n            public void WriteLine(string format, object arg0)\n            {\n                this.WriteLine(format, new object[] { arg0 });\n            }\n\n            public void WriteLine(string format, object arg0, object arg1)\n            {\n                this.WriteLine(format, new object[] { arg0, arg1 });\n            }\n\n            public void WriteLine(string format, object arg0, object arg1, object arg2)\n            {\n                this.WriteLine(format, new object[] { arg0, arg1, arg2 });\n            }\n\n            public void WriteLine(string format, params object[] args)\n            {\n                if (format != null)\n                {\n                    string content;\n                    if (args == null || args.Length <= 0)\n                    {\n                        content = format;\n                    }\n                    else\n                    {\n                        content = string.Format(format, args ?? new object[0]);\n                    }\n                    this.AppendText(content);\n                }\n                this.AppendText(Environment.NewLine);\n            }\n\n            public void Help()\n            {\n                this.WriteLine(@\"-- 标准输出函数：\nenv:Write(object)\nenv:Write(string format, object[] args)\nenv:WriteLine(object)\nenv:WriteLine(string format, object[] args)\n\");\n            }\n\n            private void AppendText(string text)\n            {\n                if (!this.form.textBoxX2.IsDisposed)\n                {\n                    text = Regex.Replace(text, @\"(?<!\\r)\\n\", \"\\n\", RegexOptions.Multiline);\n                    this.form.textBoxX2.AppendText(text);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/FrmConsole.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <metadata name=\"dotNetBarManager1.TrayLocation\" type=\"System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\">\n    <value>17, 17</value>\n  </metadata>\n</root>"
  },
  {
    "path": "WzComparerR2.LuaConsole/FrmLuaEditor.Designer.cs",
    "content": "namespace WzComparerR2.LuaConsole\n{\n    partial class FrmLuaEditor\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.textEditorControl1 = new ICSharpCode.TextEditor.TextEditorControl();\n            this.SuspendLayout();\n            // \n            // textEditorControl1\n            // \n            this.textEditorControl1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.textEditorControl1.IsReadOnly = false;\n            this.textEditorControl1.Location = new System.Drawing.Point(0, 0);\n            this.textEditorControl1.Name = \"textEditorControl1\";\n            this.textEditorControl1.Size = new System.Drawing.Size(284, 261);\n            this.textEditorControl1.TabIndex = 0;\n            this.textEditorControl1.TextChanged += new System.EventHandler(this.textEditorControl1_TextChanged);\n            this.textEditorControl1.FileNameChanged += new System.EventHandler(this.textEditorControl1_FileNameChanged);\n            // \n            // FrmLuaEditor\n            // \n            this.ClientSize = new System.Drawing.Size(284, 261);\n            this.Controls.Add(this.textEditorControl1);\n            this.DoubleBuffered = true;\n            this.Name = \"FrmLuaEditor\";\n            this.Text = \"FrmLuaEditor\";\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n\n        private ICSharpCode.TextEditor.TextEditorControl textEditorControl1;\n    }\n}"
  },
  {
    "path": "WzComparerR2.LuaConsole/FrmLuaEditor.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.IO;\nusing System.Text;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing ICSharpCode.TextEditor.Document;\n\nnamespace WzComparerR2.LuaConsole\n{\n    public partial class FrmLuaEditor : DevComponents.DotNetBar.OfficeForm\n    {\n        static bool globalInit = false;\n\n        public FrmLuaEditor()\n        {\n            if (!globalInit)\n            {\n                HighlightingManager.Manager.AddSyntaxModeFileProvider(new AppSyntaxModeProvider());\n                globalInit = true;\n            }\n\n            InitializeComponent();\n            textEditorControl1.SetHighlighting(\"Lua\");\n            this.BaseFileName = \"untitled\";\n        }\n\n        private string _baseFileName;\n        private bool _isContentModified;\n\n        public void LoadFile(string fileName)\n        {\n            this.textEditorControl1.LoadFile(fileName, false, true);\n            this.BaseFileName = Path.GetFileName(fileName);\n            this.IsContentModified = false;\n        }\n\n        public void SaveFile(string fileName)\n        {\n            this.textEditorControl1.SaveFile(fileName);\n            this.BaseFileName = Path.GetFileName(fileName);\n            this.IsContentModified = false;\n        }\n\n        public string FileName\n        {\n            get { return this.textEditorControl1.FileName; }\n            set { this.textEditorControl1.FileName = value; }\n        }\n\n        public string CodeContent\n        {\n            get { return this.textEditorControl1.Text; }\n        }\n\n        public bool IsContentModified\n        {\n            get => this._isContentModified;\n            private set\n            {\n                if (this._isContentModified != value)\n                {\n                    this._isContentModified = value;\n                    this.UpdateTitle();\n                }\n            }\n        }\n\n        public string BaseFileName\n        {\n            get => this._baseFileName;\n            private set\n            {\n                if (this._baseFileName != value)\n                {\n                    this._baseFileName = value;\n                    this.UpdateTitle();\n                }\n            }\n        }\n\n        public event EventHandler FileNameChanged;\n\n        private void UpdateTitle()\n        {\n            this.Text = (this._baseFileName ?? \"(null)\") + (this._isContentModified ? \"*\" : null);\n        }\n\n        private void textEditorControl1_TextChanged(object sender, EventArgs e)\n        {\n            this.IsContentModified = true;\n        }\n\n        private void textEditorControl1_FileNameChanged(object sender, EventArgs e)\n        {\n            this.FileNameChanged?.Invoke(this, e);\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.LuaConsole/FrmLuaEditor.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2.LuaConsole/Lua/CLRPackage.lua",
    "content": "---\n--- This lua module provides auto importing of .net classes into a named package.\n--- Makes for super easy use of LuaInterface glue\n---\n--- example:\n---   Threading = CLRPackage(\"System\", \"System.Threading\")\n---   Threading.Thread.Sleep(100)\n---\n--- Extensions:\n--- import() is a version of CLRPackage() which puts the package into a list which is used by a global __index lookup,\n--- and thus works rather like C#'s using statement. It also recognizes the case where one is importing a local\n--- assembly, which must end with an explicit .dll extension.\n\n--- Alternatively, luanet.namespace can be used for convenience without polluting the global namespace:\n---   local sys,sysi = luanet.namespace {'System','System.IO'}\n--    sys.Console.WriteLine(\"we are at {0}\",sysi.Directory.GetCurrentDirectory())\n\n\n-- LuaInterface hosted with stock Lua interpreter will need to explicitly require this...\nif not luanet then require 'luanet' end\n\nlocal import_type, load_assembly = luanet.import_type, luanet.load_assembly\n\nlocal mt = {\n\t--- Lookup a previously unfound class and add it to our table\n\t__index = function(package, classname)\n\t\tlocal class = rawget(package, classname)\n\t\tif class == nil then\n\t\t\tclass = import_type(package.packageName .. \".\" .. classname)\n\t\t\tpackage[classname] = class\t\t-- keep what we found around, so it will be shared\n\t\tend\n\t\treturn class\n\tend\n}\n\nfunction luanet.namespace(ns)\n    if type(ns) == 'table' then\n        local res = {}\n        for i = 1,#ns do\n            res[i] = luanet.namespace(ns[i])\n        end\n        return unpack(res)\n    end\n    -- FIXME - table.packageName could instead be a private index (see Lua 13.4.4)\n    local t = { packageName = ns }\n    setmetatable(t,mt)\n    return t\nend\n\nlocal globalMT, packages\n\nlocal function set_global_mt()\n    packages = {}\n    globalMT = {\n        __index = function(T,classname)\n                for i,package in ipairs(packages) do\n                    local class = package[classname]\n                    if class then\n                        _G[classname] = class\n                        return class\n                    end\n                end\n        end\n    }\n    setmetatable(_G, globalMT)\nend\n\n--- Create a new Package class\nfunction CLRPackage(assemblyName, packageName)\n  -- a sensible default...\n  packageName = packageName or assemblyName\n  local ok = pcall(load_assembly,assemblyName)\t\t\t-- Make sure our assembly is loaded\n  return luanet.namespace(packageName)\nend\n\nfunction import (assemblyName, packageName)\n    if not globalMT then\n        set_global_mt()\n    end\n    if not packageName then\n\t\tlocal i = assemblyName:find('%.dll$')\n\t\tif i then packageName = assemblyName:sub(1,i-1)\n\t\telse packageName = assemblyName end\n\tend\n    local t = CLRPackage(assemblyName,packageName)\n\ttable.insert(packages,t)\n\treturn t\nend\n\n\nfunction luanet.make_array (tp,tbl)\n    local arr = tp[#tbl]\n\tfor i,v in ipairs(tbl) do\n\t    arr:SetValue(v,i-1)\n\tend\n\treturn arr\nend\n\nfunction luanet.each(o)\n   local e = o:GetEnumerator()\n   return function()\n      if e:MoveNext() then\n        return e.Current\n     end\n   end\nend\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/LuaSandbox.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing WzComparerR2.WzLib;\nusing NLua;\n\nnamespace WzComparerR2.LuaConsole\n{\n    public class LuaSandbox : IDisposable\n    {\n        public LuaSandbox(object env)\n        {\n            this.Env = env;\n        }\n\n        public Lua Lua { get; private set; }\n        public object Env { get; private set; }\n        private bool hookEventAttched;\n\n        public void InitLuaEnv(string scriptBaseDir = null, bool forceReset = false)\n        {\n            if (this.Lua != null)\n            {\n                if (forceReset)\n                {\n                    this.Lua.Dispose();\n                    this.Lua = null;\n                }\n                else\n                {\n                    return;\n                }\n            }\n\n            var lua = new Lua();\n            lua.State.Encoding = Encoding.UTF8;\n            lua.LoadCLRPackage();\n            lua[\"env\"] = this.Env;\n\n            // TODO: will move this function to external lua file\n            lua.DoString(@\"\nlocal t_IEnumerable = {}\nt_IEnumerable.typeRef = luanet.import_type('System.Collections.IEnumerable')\nt_IEnumerable.GetEnumerator = luanet.get_method_bysig(t_IEnumerable.typeRef, 'GetEnumerator')\n\nlocal t_IEnumerator = {}\nt_IEnumerator.typeRef = luanet.import_type('System.Collections.IEnumerator')\nt_IEnumerator.MoveNext = luanet.get_method_bysig(t_IEnumerator.typeRef, 'MoveNext')\nt_IEnumerator.get_Current = luanet.get_method_bysig(t_IEnumerator.typeRef, 'get_Current')\n\nfunction each(userData)\n  if type(userData) == 'userdata' then\n    local i = 0;\n    local ienum = t_IEnumerable.GetEnumerator(userData)\n    return function()\n      if ienum and t_IEnumerator.MoveNext(ienum) then\n        i = i + 1\n        return i, t_IEnumerator.get_Current(ienum)\n      end\n      return nil, nil\n    end\n  end\nend\n\");\n\n            // Only set the package path on the first run.\n            // path order:\n            // 1. script file folder\n            // 2. luaConsole plugin folder\n            // 3. wzComparerR2 folder\n            // 4. current folder\n            string pluginBaseDir = Path.GetDirectoryName(typeof(Entry).Assembly.Location);\n            string wcR2Folder = Application.StartupPath;\n\n            List<string> packagePath = new List<string>(16);\n            foreach (var baseDir in new[] { scriptBaseDir, pluginBaseDir, wcR2Folder })\n            {\n                if (!string.IsNullOrEmpty(baseDir))\n                {\n                    packagePath.Add(Path.Combine(baseDir, \"?.lua\"));\n                    packagePath.Add(Path.Combine(baseDir, \"?\", \"init.lua\"));\n                    packagePath.Add(Path.Combine(baseDir, \"lua\", \"?.lua\"));\n                    packagePath.Add(Path.Combine(baseDir, \"lua\", \"?\", \"init.lua\"));\n                }\n            }\n            packagePath.Add(Path.Combine(\".\", \"?.lua\"));\n            packagePath.Add(Path.Combine(\".\", \"?\", \"init.lua\"));\n            lua.SetObjectToPath(\"package.path\", string.Join(\";\", packagePath.Distinct()));\n\n            // Register commonly used delegate types\n            lua.RegisterLuaDelegateType(typeof(WzComparerR2.GlobalFindNodeFunction), typeof(LuaGlobalFindNodeFunctionHandler));\n\n            this.Lua = lua;\n        }\n\n        public async Task<object[]> DoStringAsync(string chunk, CancellationToken cancellationToken = default)\n        {\n            cancellationToken.Register(this.HookCancellationEvent);\n            try\n            {\n                return await Task.Run(() => this.Lua.DoString(chunk), cancellationToken).ConfigureAwait(false);\n            }\n            finally\n            {\n                this.RemoveCancellationEvent();\n            }\n        }\n\n        private void HookCancellationEvent()\n        {\n            if (!this.hookEventAttched)\n            {\n                this.Lua.DebugHook += this.Lua_DebugHook;\n                this.Lua.SetDebugHook(KeraLua.LuaHookMask.Count, 1);\n                this.hookEventAttched = true;\n            }\n        }\n\n        private void RemoveCancellationEvent()\n        {\n            if (this.hookEventAttched)\n            {\n                this.Lua.DebugHook -= this.Lua_DebugHook;\n                this.Lua.RemoveDebugHook();\n                this.hookEventAttched = false;\n            }\n        }\n\n        private void Lua_DebugHook(object sender, NLua.Event.DebugHookEventArgs e)\n        {\n            ((Lua)sender).State.Error(\"Operation cancelled.\");\n        }\n\n        public void Dispose()\n        {\n            if (this.Lua != null)\n            {\n                this.Lua.Dispose();\n                this.Lua = null;\n            }\n        }\n\n        class LuaGlobalFindNodeFunctionHandler : NLua.Method.LuaDelegate\n        {\n            Wz_Node CallFunction(string wzPath)\n            {\n                object[] args = { wzPath };\n                object[] inArgs = { wzPath };\n                int[] outArgs = { };\n\n                object ret = CallFunction(args, inArgs, outArgs);\n\n                return (Wz_Node)ret;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的常规信息通过以下\n// 特性集控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2.LuaConsole\")]\n[assembly: AssemblyDescription(\"用于WzComparerR2的控制台插件。\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Kagamia Studio\")]\n[assembly: AssemblyProduct(\"WzComparerR2.LuaConsole\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2014-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 使此程序集中的类型\n// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型，\n// 则将该类型上的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"8ffb04b2-8c73-4d19-8f2d-3c8de3ecfd27\")]\n\n// 程序集的版本信息由下面四个值组成:\n//\n//      主版本\n//      次版本 \n//      生成号\n//      修订号\n//\n// 可以指定所有这些值，也可以使用“生成号”和“修订号”的默认值，\n// 方法是按如下所示使用“*”:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     此代码由工具生成。\n//     运行时版本:4.0.30319.42000\n//\n//     对此文件的更改可能会导致不正确的行为，并且如果\n//     重新生成代码，这些更改将会丢失。\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace WzComparerR2.LuaConsole.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   一个强类型的资源类，用于查找本地化的字符串等。\n    /// </summary>\n    // 此类是由 StronglyTypedResourceBuilder\n    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。\n    // 若要添加或移除成员，请编辑 .ResX 文件，然后重新运行 ResGen\n    // (以 /str 作为命令选项)，或重新生成 VS 项目。\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"16.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   返回此类使用的缓存的 ResourceManager 实例。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"WzComparerR2.LuaConsole.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   重写当前线程的 CurrentUICulture 属性，对\n        ///   使用此强类型资源类的所有资源查找执行重写。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Byte[] 类型的本地化资源。\n        /// </summary>\n        internal static byte[] Lua {\n            get {\n                object obj = ResourceManager.GetObject(\"Lua\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查找 System.Byte[] 类型的本地化资源。\n        /// </summary>\n        internal static byte[] SharpLua {\n            get {\n                object obj = ResourceManager.GetObject(\"SharpLua\", resourceCulture);\n                return ((byte[])(obj));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"Lua\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\resources\\lua.xshd;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n  <data name=\"SharpLua\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\SharpLua.xshd;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2.LuaConsole/Resources/Lua.xshd",
    "content": "<?xml version=\"1.0\"?>\n<SyntaxDefinition name = \"Lua\" extensions = \".lua\">\n\t\n<!--\t<Properties>\n\t\t<Property name=\"LineComment\" value=\"//\" />\n\t</Properties>-->\n\n\t<Digits name=\"Digits\" bold=\"false\" italic=\"false\" color=\"DarkBlue\"/>\n\n\t<RuleSets>\n\t\t<RuleSet ignorecase=\"false\">\n      <Delimiters>&amp;&lt;&gt;~!%^*()-+=|\\/{}[]:;\"' ,\t.?</Delimiters>\n\n      <Span name=\"BlockComment\" bold=\"false\" italic=\"false\" color=\"Green\" stopateol=\"false\">\n\t\t\t\t<Begin>--[[</Begin>\n\t\t\t\t<End>]]</End>\n\t\t\t</Span>\n\t\t\t\n\t\t\t<Span name=\"LineComment\" bold=\"false\" italic=\"false\" color=\"Green\" stopateol=\"true\">\n\t\t\t  <Begin>--</Begin>\n\t\t\t</Span>\n\t\t\t\n\t\t\t<Span name=\"String\" bold=\"false\" italic=\"false\" color=\"Red\" stopateol=\"false\">\n\t\t\t  <Begin>\"</Begin>\n\t\t\t\t<End>\"</End>\n\t\t\t</Span>\n\t\t\t\n\t\t\t<Span name=\"Char\" bold=\"false\" italic=\"false\" color=\"Red\" stopateol=\"true\">\n\t\t\t  <Begin>'</Begin>\n\t\t\t\t<End>'</End>\n\t\t\t</Span>\n\n      <Span name=\"MultiLineString\" bold=\"false\" italic=\"false\" color=\"Red\" stopateol=\"false\" escapecharacter='\"'>\n        <Begin>[[</Begin>\n        <End>]]</End>\n      </Span>\n\n      <MarkPrevious bold=\"true\" italic=\"false\" color=\"MidnightBlue\">(</MarkPrevious>\n\t\t\t\n\t\t\t<KeyWords name=\"Punctuation\" bold=\"false\" italic=\"false\" color=\"DarkGreen\">\n        <Key word=\"?\" />\n        <Key word=\",\" />\n        <Key word=\".\" />\n        <Key word=\";\" />\n        <Key word=\"(\" />\n        <Key word=\")\" />\n        <Key word=\"[\" />\n        <Key word=\"]\" />\n        <Key word=\"{\" />\n        <Key word=\"}\" />\n        <Key word=\"+\" />\n        <Key word=\"-\" />\n        <Key word=\"/\" />\n        <Key word=\"%\" />\n        <Key word=\"*\" />\n        <Key word=\"&lt;\" />\n        <Key word=\"&gt;\" />\n        <Key word=\"^\" />\n        <Key word=\"=\" />\n        <Key word=\"~\" />\n        <Key word=\"!\" />\n        <Key word=\"|\" />\n        <Key word=\"&amp;\" />\n        <Key word=\"@\" />\n        <Key word=\"$\" />\n      </KeyWords>\n\t\t  \n\t\t\t<KeyWords name=\"AccessKeywords\" bold=\"true\" italic=\"false\" color=\"Blue\">\n\t\t\t\t<Key word=\"self\" />\n\t\t\t</KeyWords>\n\t\t\t\n\t\t\t\n\t\t\t<KeyWords name=\"LanguageKeywords\" bold=\"false\" italic=\"false\" color=\"Blue\">\n\t\t\t  <Key word=\"and\" />\n\t\t\t  <Key word=\"break\" />\n\t\t\t  <Key word=\"do\" />\n\t\t\t  <Key word=\"else\" />\n\t\t\t  <Key word=\"elseif\" />\n\t\t\t  <Key word=\"end\" />\n\t\t\t  <Key word=\"false\" />\n\t\t\t  <Key word=\"for\" />\n\t\t\t  <Key word=\"function\" />\n\t\t\t  <Key word=\"if\" />\n\t\t\t  <Key word=\"in\" />\n\t\t\t  <Key word=\"local\" />\n\t\t\t  <Key word=\"nil\" />\n\t\t\t  <Key word=\"not\" />\n\t\t\t  <Key word=\"or\" />\n\t\t\t  <Key word=\"repeat\" />\n\t\t\t  <Key word=\"return\" />\n\t\t\t  <Key word=\"then\" />\n\t\t\t  <Key word=\"true\" />\n\t\t\t  <Key word=\"until\" />\n\t\t\t  <Key word=\"while\" />\n\t\t\t</KeyWords>\n\n    \n\t\t</RuleSet>\n\t\t\n\t</RuleSets>\n</SyntaxDefinition>\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/Resources/SharpLua.xshd",
    "content": "<?xml version=\"1.0\"?>\n<SyntaxDefinition name=\"SharpLua\" extensions=\".slua;.lua\" xmlns=\"http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008\">\n\t<!-- The named colors 'Comment' and 'String' are used in SharpDevelop to detect if a line is inside a multiline string/comment -->\n\t<Color name=\"Comment\" foreground=\"Green\" exampleText=\"-- comment\" />\n\t<Color name=\"String\" foreground=\"Blue\" />\n\t<Color name=\"Punctuation\" />\n\t<Color name=\"MethodCall\" foreground=\"MidnightBlue\" fontWeight=\"bold\"/>\n\t<Color name=\"NumberLiteral\" foreground=\"DarkBlue\"/>\n\t<Color name=\"NilKeyword\" fontWeight=\"bold\"/>\n\t<Color name=\"Keywords\" fontWeight=\"bold\" foreground=\"Blue\" />\n\t<Color name=\"GotoKeywords\" foreground=\"Navy\" />\n\t<Color name=\"Visibility\" fontWeight=\"bold\" foreground=\"Blue\"/>\n\t<Color name=\"TrueFalse\" fontWeight=\"bold\" foreground=\"DarkCyan\" />\n\t\n\t<RuleSet name=\"CommentMarkerSet\">\n\t\t<Keywords fontWeight=\"bold\" foreground=\"Red\">\n\t\t\t<Word>TODO</Word>\n\t\t\t<Word>FIXME</Word>\n\t\t</Keywords>\n\t\t<Keywords fontWeight=\"bold\" foreground=\"#E0E000\">\n\t\t\t<Word>HACK</Word>\n\t\t\t<Word>UNDONE</Word>\n\t\t</Keywords>\n\t</RuleSet>\n\t\n\t<!-- This is the main ruleset. -->\n\t<RuleSet>\n      \n\t\t<Span color=\"Comment\">\n\t\t\t<Begin color=\"XmlDoc/DocComment\">---</Begin>\n\t\t\t<RuleSet>\n\t\t\t\t<Import ruleSet=\"XmlDoc/DocCommentSet\"/>\n\t\t\t\t<Import ruleSet=\"CommentMarkerSet\"/>\n\t\t\t</RuleSet>\n\t\t</Span>\n\n\n    <Span color=\"Comment\" ruleSet=\"CommentMarkerSet\" multiline=\"true\">\n      <Begin>--\\[[=]*\\[</Begin>\n      <End>\\][=]*]</End>\n    </Span>\n\n      \n    <Span color=\"Comment\" ruleSet=\"CommentMarkerSet\">\n\t\t\t<Begin>--</Begin>\n\t\t</Span>\n\t\t\n\t\t<Span color=\"String\">\n\t\t\t<Begin>\"</Begin>\n\t\t\t<End>\"</End>\n\t\t\t<RuleSet>\n\t\t\t\t<!-- span for escape sequences -->\n\t\t\t\t<Span begin=\"\\\\\" end=\".\"/>\n\t\t\t</RuleSet>\n\t\t</Span>\n\n      <Span color=\"String\">\n          <Begin>'</Begin>\n          <End>'</End>\n          <RuleSet>\n              <!-- span for escape sequences -->\n              <Span begin=\"\\\\\" end=\".\"/>\n          </RuleSet>\n      </Span>\n\t\t\n\t\t<Span color=\"String\" multiline=\"true\">\n\t\t\t<Begin color=\"String\">\\[[=]*\\[</Begin>\n\t\t\t<End>\\][=]*]</End>\n\t\t</Span>\n\t\t\n\t\t<Keywords color=\"TrueFalse\">\n\t\t\t<Word>true</Word>\n\t\t\t<Word>false</Word>\n\t\t</Keywords>\n\t\t\n\t\t<Keywords color=\"Keywords\">\n        <Word>and</Word>\n        <Word>break</Word>\n        <Word>do</Word>\n        <Word>else</Word>\n        <Word>elseif</Word>\n        <Word>end</Word>\n        <Word>false</Word>\n        <Word>for</Word>\n        <Word>function</Word>\n        <Word>if</Word>\n        <Word>in</Word>\n        <Word>local</Word>\n        <!--<Word>nil</Word>-->\n        <Word>not</Word>\n        <Word>or</Word>\n        <Word>repeat</Word>\n        <Word>return</Word>\n        <Word>then</Word>\n        <Word>true</Word>\n        <Word>until</Word>\n        <Word>while</Word>\n        <Word>using</Word>\n\t\t</Keywords>\n\t\t\n\t\t<Keywords color=\"GotoKeywords\">\n\t\t\t<Word>break</Word>\n\t\t\t<Word>return</Word>\n\t\t</Keywords>\n\t\t\n\t\t<Keywords color=\"Visibility\">\n        <Word>local</Word>\n\t\t</Keywords>\n\t\t\n\t\t<Keywords color=\"NilKeyword\">\n\t\t\t<Word>nil</Word>\n\t\t</Keywords>\n\t\t\n\t\t<!-- Mark previous rule-->\n\t\t<Rule color=\"MethodCall\">\n        \\b\n        [\\d\\w_]+  # an identifier\n        (?=\\s*\\() # followed by (\n    </Rule>\n      <Rule color=\"MethodCall\">\n        \\b\n        [\\d\\w_]+  # an identifier\n        (?=\\s*\\\") # followed by \"\n    </Rule>\n    <Rule color=\"MethodCall\">\n        \\b\n        [\\d\\w_]+  # an identifier\n        (?=\\s*\\') # followed by '\n    </Rule>\n      <Rule color=\"MethodCall\">\n        \\b\n        [\\d\\w_]+  # an identifier\n        (?=\\s*\\{) # followed by {\n    </Rule>\n      <Rule color=\"MethodCall\">\n        \\b\n        [\\d\\w_]+  # an identifier\n        (?=\\s*\\[) # followed by [\n    </Rule>\n\t\t\n\t\t<!-- Digits -->\n\t\t<Rule color=\"NumberLiteral\">\n\t\t\t\\b0[xX][0-9a-fA-F]+  # hex number\n\t\t|\t\n\t\t\t(\t\\b\\d+(\\.[0-9]+)?   #number with optional floating point\n\t\t\t|\t\\.[0-9]+           #or just starting with floating point\n\t\t\t)\n\t\t\t([eE][+-]?[0-9]+)? # optional exponent\n\t\t</Rule>\n\t\t\n\t\t<Rule color=\"Punctuation\">\n\t\t\t[?,.;()\\[\\]{}+\\-/%*&lt;&gt;^+~!|&amp;]+\n\t\t</Rule>\n\t</RuleSet>\n</SyntaxDefinition>\n"
  },
  {
    "path": "WzComparerR2.LuaConsole/WzComparerR2.LuaConsole.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2.LuaConsole</AssemblyName>\n    <RootNamespace>WzComparerR2.LuaConsole</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <WcR2Plugin>true</WcR2Plugin>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <!-- manually export native lua54.dll-->\n    <ShouldIncludeNativeLua>false</ShouldIncludeNativeLua>\n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n\n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Configuration\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WzComparerR2.Common\\WzComparerR2.Common.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2.PluginBase\\WzComparerR2.PluginBase.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2.WzLib\\WzComparerR2.WzLib.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <Reference Include=\"DevComponents.DotNetBar2\">\n      <HintPath>..\\References\\DevComponents.DotNetBar2.dll</HintPath>\n      <Private>false</Private>\n    </Reference>\n    <PackageReference Include=\"ICSharpCode.TextEditor\" Version=\"3.2.1.6466\" NoWarn=\"NU1701\" />\n    <PackageReference Include=\"KeraLua\" Version=\"1.4.1\" GeneratePathProperty=\"true\" />\n    <PackageReference Include=\"NLua\" Version=\"1.7.3\" />\n  </ItemGroup>\n  <ItemGroup>\n    <EmbeddedResource Include=\"Resources\\SharpLua.xshd\" />\n    <None Include=\"Lua\\CLRPackage.lua\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n  <Target Name=\"PostBuildNet6\" AfterTargets=\"PostBuildEvent\" Condition=\"$([MSBuild]::IsOSPlatform('Windows'))\">\n    <Exec Command=\"xcopy &quot;$(PkgKeraLua)\\runtimes\\win-x86\\native\\*.dll&quot; &quot;$(TargetDir)x86&quot; /I /Y\" />\n    <Exec Command=\"xcopy &quot;$(PkgKeraLua)\\runtimes\\win-x64\\native\\*.dll&quot; &quot;$(TargetDir)x64&quot; /I /Y\" />\n    <Exec Command=\"xcopy &quot;$(PkgKeraLua)\\runtimes\\win-arm64\\native\\*.dll&quot; &quot;$(TargetDir)ARM64&quot; /I /Y\" />\n  </Target>\n  <Import Project=\"$(ProjectDir)..\\Build\\WcR2Plugin.targets\" />\n</Project>"
  },
  {
    "path": "WzComparerR2.MapRender/Animation/IStateMachineAnimationData.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.Animation\n{\n    public interface IStateMachineAnimationData\n    {\n        ReadOnlyCollection<string> States { get; }\n        int SelectedStateIndex { get; set; }\n        string SelectedState { get; }\n        void Update(TimeSpan elapsedTime);\n        event EventHandler AnimationEnd;\n        object GetMesh();\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Animation/RepeatableFrameAnimationData.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.Animation\n{\n    public class RepeatableFrameAnimationData : FrameAnimationData\n    {\n        public RepeatableFrameAnimationData()\n        {\n\n        }\n\n        public RepeatableFrameAnimationData(IEnumerable<Frame> frames)\n            : base(frames)\n        {\n\n        }\n\n        public bool? Repeat { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Animation/RepeatableFrameAnimator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Animation\n{\n    public class RepeatableFrameAnimator : FrameAnimator\n    {\n        public RepeatableFrameAnimator(RepeatableFrameAnimationData data)\n            : base(data)\n        {\n            this.Data = data;\n            this.IsLoop = this.Data.Repeat ?? true;\n        }\n\n        public new RepeatableFrameAnimationData Data { get; private set; }\n\n        public bool IsStopped { get; private set; }\n        public bool IsLoop { get; set; }\n\n        public override void Update(TimeSpan elapsedTime)\n        {\n            if (!IsLoop)\n            {\n                int _timeOffset = base.CurrentTime;\n                if (this.Length <= 0)\n                {\n                    _timeOffset = 0;\n                }\n                else\n                {\n                    _timeOffset += (int)elapsedTime.TotalMilliseconds;\n                    if (!this.IsStopped)\n                    {\n                        if (_timeOffset >= this.Length)\n                        {\n                            _timeOffset = this.Length;\n                            this.IsStopped = true;\n                        }\n                    }\n                    else\n                    {\n                        _timeOffset = this.Length;\n                    }\n                }\n\n                base.CurrentTime = _timeOffset;\n                this.UpdateFrame();\n            }\n            else\n            {\n                base.Update(elapsedTime);\n            }\n        }\n\n        public override void Reset()\n        {\n            base.Reset();\n            IsStopped = false;\n        }\n\n        protected override void Load()\n        {\n            base.Load();\n        }\n\n        protected override void UpdateFrame()\n        {\n            if (!IsLoop && this.IsStopped)\n            {\n                var frame = this.Data.Frames.Last();\n                if (this.CurrentFrame == null)\n                {\n                    this.CurrentFrame = new Frame();\n                }\n                this.CurrentFrame.Texture = frame.Texture;\n                this.CurrentFrame.AtlasRect = frame.AtlasRect;\n                this.CurrentFrame.Z = frame.Z;\n                this.CurrentFrame.Origin = frame.Origin;\n                this.CurrentFrame.A0 = frame.A1;\n                this.CurrentFrame.Blend = frame.Blend;\n            }\n            else\n            {\n                base.UpdateFrame();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Animation/StateMachineAnimator.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.Animation\n{\n    public class StateMachineAnimator\n    {\n        public StateMachineAnimator(IDictionary<string, RepeatableFrameAnimationData> data)\n            : this(new FrameStateMachineData(data))\n        {\n        }\n\n        public StateMachineAnimator(ISpineAnimationData data)\n        {\n            throw new NotImplementedException();\n        }\n\n        private StateMachineAnimator(IStateMachineAnimationData data)\n        {\n            this.Data = data;\n            this.Data.AnimationEnd += Data_AnimationEnd;\n        }\n\n        public IStateMachineAnimationData Data { get; private set; }\n\n        public event EventHandler<AnimationEndEventArgs> AnimationEnd;\n\n\n        public void SetAnimation(string aniName)\n        {\n            int idx = this.Data.States.IndexOf(aniName);\n            this.Data.SelectedStateIndex = idx;\n        }\n\n        public string GetCurrent()\n        {\n            return this.Data.SelectedState;\n        }\n\n        public void Update(TimeSpan elapsedTime)\n        {\n            this.Data.Update(elapsedTime);\n        }\n\n        protected virtual void OnAnimationEnd(AnimationEndEventArgs e)\n        {\n            this.AnimationEnd?.Invoke(this, e);\n\n            if (!e.IsHandled)\n            {\n                if (e.CurrentState != e.NextState)\n                {\n                    this.SetAnimation(e.NextState);\n                }\n            }\n        }\n\n        private void Data_AnimationEnd(object sender, EventArgs e)\n        {\n            string state = GetCurrent();\n            var ev = new AnimationEndEventArgs(state);\n            ev.NextState = state;\n            this.OnAnimationEnd(ev);\n        }\n\n        public class AnimationEndEventArgs : EventArgs\n        {\n            public AnimationEndEventArgs(string currentState)\n            {\n                this.CurrentState = currentState;\n            }\n\n            public bool IsHandled { get; set; }\n            public string CurrentState { get; }\n            public string NextState { get; set; }\n        }\n\n        /// <summary>\n        /// 用于帧动画的状态机数据。\n        /// </summary>\n        private class FrameStateMachineData : IStateMachineAnimationData\n        {\n            public FrameStateMachineData(IDictionary<string, RepeatableFrameAnimationData> data)\n            {\n                this.data = new Dictionary<string, RepeatableFrameAnimationData>(data);\n                this.States = new ReadOnlyCollection<string>(new List<string>(this.data.Keys));\n                this.SelectedStateIndex = -1;\n            }\n\n            private IDictionary<string, RepeatableFrameAnimationData> data;\n            private int selectedIndex;\n            private RepeatableFrameAnimator selectedData;\n\n            public ReadOnlyCollection<string> States { get; private set; }\n\n            public int SelectedStateIndex\n            {\n                get { return this.selectedIndex; }\n                set\n                {\n                    if (value < 0 || value >= this.States.Count)\n                    {\n                        this.selectedIndex = -1;\n                        this.selectedData = null;\n                    }\n                    else\n                    {\n                        this.selectedIndex = value;\n                        this.selectedData = new RepeatableFrameAnimator(this.data[SelectedState]);\n                    }\n                }\n            }\n\n            public string SelectedState\n            {\n                get { return this.selectedIndex < 0 ? null : this.States[selectedIndex]; }\n            }\n\n            public event EventHandler AnimationEnd;\n\n            protected virtual void OnAnimationEnd(EventArgs e)\n            {\n                this.AnimationEnd?.Invoke(this, e);\n            }\n\n            public void Update(TimeSpan elapsedTime)\n            {\n                if (this.selectedData == null)\n                    return;\n                if (this.selectedData.CurrentTime + elapsedTime.TotalMilliseconds >= selectedData.Length)\n                {\n                    this.selectedData.Update(elapsedTime);\n                    //this.Update(TimeSpan.FromMilliseconds(selectedData.Length - selectedData.CurrentTime));\n                    OnAnimationEnd(EventArgs.Empty);\n                }\n                else\n                {\n                    this.selectedData.Update(elapsedTime);\n                }\n            }\n\n            public object GetMesh()\n            {\n                return this.selectedData?.CurrentFrame;\n            }\n        }\n\n        /// <summary>\n        /// 用于骨骼动画的状态机数据。\n        /// </summary>\n        private class SpineStateMachineData : IStateMachineAnimationData\n        {\n            public string SelectedState\n            {\n                get\n                {\n                    throw new NotImplementedException();\n                }\n            }\n\n            public int SelectedStateIndex\n            {\n                get\n                {\n                    throw new NotImplementedException();\n                }\n\n                set\n                {\n                    throw new NotImplementedException();\n                }\n            }\n\n            public ReadOnlyCollection<string> States\n            {\n                get\n                {\n                    throw new NotImplementedException();\n                }\n            }\n\n            public event EventHandler AnimationEnd;\n\n            public object GetMesh()\n            {\n                throw new NotImplementedException();\n            }\n\n            public void Update(TimeSpan deltaTime)\n            {\n                throw new NotImplementedException();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Camera.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public class Camera\n    {\n        public Camera(GraphicsDeviceManager graphics)\n        {\n            this.graphics = graphics;\n            this.AdjustRectEnabled = true;\n        }\n\n        GraphicsDeviceManager graphics;\n        Rectangle worldRect;\n        Vector2 center;\n        int displayMode;\n        bool useWorldRect;\n\n        public GraphicsDeviceManager Graphics\n        {\n            get { return graphics; }\n            set { graphics = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置摄像机中心对应的世界坐标。\n        /// </summary>\n        public Vector2 Center\n        {\n            get { return center; }\n            set { center = value; }\n        }\n\n        /// <summary>\n        /// 获取摄像机宽度。\n        /// </summary>\n        public int Width\n        {\n            get { return graphics.PreferredBackBufferWidth; }\n        }\n\n        /// <summary>\n        /// 获取摄像机高度。\n        /// </summary>\n        public int Height\n        {\n            get { return graphics.PreferredBackBufferHeight; }\n        }\n\n        /// <summary>\n        /// 获取摄像机矩形的世界坐标。\n        /// </summary>\n        public Rectangle ClipRect\n        {\n            get\n            {\n                if (useWorldRect)\n                    return worldRect;\n                else\n                    return new Rectangle((int)center.X - Width / 2, (int)center.Y - Height / 2, Width, Height);\n            }\n        }\n\n        /// <summary>\n        /// 获取摄像机左上角对应的世界坐标。\n        /// </summary>\n        public Vector2 Origin\n        {\n            get\n            {\n                Rectangle rect = ClipRect;\n                return new Vector2(rect.X, rect.Y);\n            }\n        }\n\n        public Rectangle WorldRect\n        {\n            get { return worldRect; }\n            set { worldRect = value; }\n        }\n\n        public int DisplayMode\n        {\n            get { return displayMode; }\n            set\n            {\n                displayMode = value;\n                ChangeDisplayMode();\n            }\n        }\n\n        public bool UseWorldRect\n        {\n            get { return useWorldRect; }\n            set { useWorldRect = value; }\n        }\n\n        public bool AdjustRectEnabled { get; set; }\n\n        private void ChangeDisplayMode()\n        {\n            switch (this.displayMode)\n            {\n                case 0:\n                    graphics.PreferredBackBufferWidth = 800;\n                    graphics.PreferredBackBufferHeight = 600;\n                    break;\n                case 1:\n                    graphics.PreferredBackBufferWidth = 1024;\n                    graphics.PreferredBackBufferHeight = 768;\n                    break;\n                case 2:\n                    graphics.PreferredBackBufferWidth = 1366;\n                    graphics.PreferredBackBufferHeight = 768;\n                    break;\n                case 3:\n                    graphics.PreferredBackBufferWidth = graphics.GraphicsDevice.DisplayMode.Width;\n                    graphics.PreferredBackBufferHeight = graphics.GraphicsDevice.DisplayMode.Height;\n                    break;\n                default:\n                    goto case 0;\n            }\n            graphics.ApplyChanges();\n        }\n\n        public void AdjustToWorldRect()\n        {\n            if (this.useWorldRect)\n                return;\n\n            if (!this.AdjustRectEnabled)\n                return;\n\n            if (this.Width > worldRect.Width)\n            {\n                this.center.X = worldRect.Center.X;\n            }\n            else\n            {\n                this.center.X = MathHelper.Clamp(this.center.X,\n                    worldRect.Left + this.Width / 2,\n                    worldRect.Right - this.Width / 2);\n            }\n\n            if (this.Height > worldRect.Height)\n            {\n                this.center.Y = worldRect.Center.Y;\n            }\n            else\n            {\n                this.center.Y = MathHelper.Clamp(this.center.Y,\n                    worldRect.Top + this.Height / 2,\n                    worldRect.Bottom - this.Height / 2);\n            }\n        }\n\n        public Rectangle MeasureDrawingRect(int width, int height, Vector2 position, Vector2 origin, bool flipX)\n        {\n            Rectangle drawingRect;\n            drawingRect.Width = width;\n            drawingRect.Height = height;\n            if (flipX)\n            {\n                drawingRect.X = (int)(position.X + origin.X - width);\n                drawingRect.Y = (int)(position.Y - origin.Y);\n            }\n            else\n            {\n                drawingRect.X = (int)(position.X - origin.X);\n                drawingRect.Y = (int)(position.Y - origin.Y);\n            }\n            return drawingRect;\n        }\n\n#if MapRenderV1\n        public bool CheckSpriteVisible(RenderFrame frame, Vector2 position, bool flip)\n        {\n            Rectangle drawingRect;\n            return CheckSpriteVisible(frame, position, flip, out drawingRect);\n        }\n\n        public bool CheckSpriteVisible(RenderFrame frame, Vector2 position, bool flip, out Rectangle drawingRect)\n        {\n            if (frame == null || frame.Texture == null)\n            {\n                drawingRect = Rectangle.Empty;\n                return false;\n            }\n            drawingRect = MeasureDrawingRect(\n                frame.Texture.Width,\n                frame.Texture.Height,\n                position,\n                frame.Origin,\n                flip);\n            return this.ClipRect.Intersects(drawingRect);\n        }\n#endif\n\n        public Point CameraToWorld(Point cameraPoint)\n        {\n            cameraPoint.X += this.ClipRect.X;\n            cameraPoint.Y += this.ClipRect.Y;\n            return cameraPoint;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Chat.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n//using PokeIn.Comet;\n\nnamespace WzComparerR2.MapRender\n{\n    /*\n    public class Chat\n    {\n        public Chat()\n        {\n            this.caller = new APICaller();\n            this.client = new DesktopClient(caller, \"http://kagamia.sitecloud.cytanium.com/wcweb/chat.ashx\", null);\n            client.OnClientConnected += (e) =>\n            {\n                if (this.Connected != null)\n                {\n                    this.Connected(this, EventArgs.Empty);\n                }\n            };\n            client.OnClientDisconnected += (e) =>\n            {\n                if (this.Disconnected != null)\n                {\n                    this.Disconnected(this, EventArgs.Empty);\n                }\n            };\n            client.OnErrorReceived += (o, e) =>\n            {\n                if (this.Error != null)\n                {\n                    this.Error(this, new ChatErrorEventArgs() { Error = e });\n                }\n            };\n            this.caller.MessageReceived += (o, e) =>\n            {\n                if (this.MessageReceived != null)\n                {\n                    this.MessageReceived(this, e);\n                }\n            };\n        }\n\n        private DesktopClient client;\n        private APICaller caller;\n\n        public bool IsConnected\n        {\n            get { return this.client.IsConnected; }\n        }\n\n        public event EventHandler Connected;\n        public event EventHandler Disconnected;\n        public event EventHandler<ChatErrorEventArgs> Error;\n        public event EventHandler<ChatMessageEventArgs> MessageReceived;\n\n        public void Connect()\n        {\n            client.Connect();\n        }\n\n        public void Disconnect()\n        {\n            client.Close();\n            client.Dispose();\n        }\n\n        public void Talk(string message)\n        {\n            if (client.IsConnected)\n            {\n                client.SendAsync(\"Chat.Talk\", message);\n            }\n        }\n\n        public void SetName(string name)\n        {\n            if (client.IsConnected)\n            {\n                client.SendAsync(\"Chat.SetName\", name);\n            }\n        }\n\n        public class APICaller\n        {\n            public event EventHandler<ChatMessageEventArgs> MessageReceived;\n\n            public void OnMessage(int type, string from, string to, string msg)\n            {\n                if (this.MessageReceived != null)\n                {\n                    ChatMessageEventArgs e = new ChatMessageEventArgs();\n                    e.MsgType = type;\n                    e.FromName = from;\n                    e.ToName = to;\n                    e.MessageText = msg;\n                    this.MessageReceived(this, e);\n                }\n            }\n        }\n    }\n\n    public class ChatErrorEventArgs : EventArgs\n    {\n        public string Error { get; set; }\n    }\n\n    public class ChatMessageEventArgs : EventArgs\n    {\n        public int MsgType { get; set; }\n        public string FromName { get; set; }\n        public string ToName { get; set; }\n        public string MessageText { get; set; }\n    }\n     */\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Config/MapRenderConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Configuration;\nusing WzComparerR2.Config;\n\nnamespace WzComparerR2.MapRender.Config\n{\n    [SectionName(\"WcR2.MapRender\")]\n    public sealed class MapRenderConfig : ConfigSectionBase<MapRenderConfig>\n    {\n        public MapRenderConfig()\n        {\n            this.Volume = 1f;\n            this.MuteOnLeaveFocus = true;\n            this.ClipMapRegion = true;\n        }\n\n        [ConfigurationProperty(\"volume\")]\n        public ConfigItem<float> Volume\n        {\n            get { return (ConfigItem<float>)this[\"volume\"]; }\n            set { this[\"volume\"] = value; }\n        }\n\n        [ConfigurationProperty(\"muteOnLeaveFocus\")]\n        public ConfigItem<bool> MuteOnLeaveFocus\n        {\n            get { return (ConfigItem<bool>)this[\"muteOnLeaveFocus\"]; }\n            set { this[\"muteOnLeaveFocus\"] = value; }\n        }\n\n        [ConfigurationProperty(\"defaultFontIndex\")]\n        public ConfigItem<int> DefaultFontIndex\n        {\n            get { return (ConfigItem<int>)this[\"defaultFontIndex\"]; }\n            set { this[\"defaultFontIndex\"] = value; }\n        }\n\n        [ConfigurationProperty(\"clipMapRegion\")]\n        public ConfigItem<bool> ClipMapRegion\n        {\n            get { return (ConfigItem<bool>)this[\"clipMapRegion\"]; }\n            set { this[\"clipMapRegion\"] = value; }\n        }\n\n        [ConfigurationProperty(\"useD2DRenderer\")]\n        public ConfigItem<bool> UseD2dRenderer\n        {\n            get { return (ConfigItem<bool>)this[\"useD2DRenderer\"]; }\n            set { this[\"useD2DRenderer\"] = value; }\n        }\n\n        [ConfigurationProperty(\"topBar.Visible\")]\n        public ConfigItem<bool> TopBarVisible\n        {\n            get { return (ConfigItem<bool>)this[\"topBar.Visible\"]; }\n            set { this[\"topBar.Visible\"] = value; }\n        }\n\n        [ConfigurationProperty(\"minimap.CameraRegionVisible\")]\n        public ConfigItem<bool> Minimap_CameraRegionVisible\n        {\n            get { return (ConfigItem<bool>)this[\"minimap.CameraRegionVisible\"]; }\n            set { this[\"minimap.CameraRegionVisible\"] = value; }\n        }\n\n        [ConfigurationProperty(\"worldMap.UseImageNameAsInfoName\")]\n        public ConfigItem<bool> WorldMap_UseImageNameAsInfoName\n        {\n            get { return (ConfigItem<bool>)this[\"worldMap.UseImageNameAsInfoName\"]; }\n            set { this[\"worldMap.UseImageNameAsInfoName\"] = value; }\n        }\n\n        [ConfigurationProperty(\"screenshotBackgroundColor\")]\n        public ConfigItem<string> ScreenshotBackgroundColor\n        {\n            get { return (ConfigItem<string>)this[\"screenshotBackgroundColor\"]; }\n            set { this[\"screenshotBackgroundColor\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Coroutine.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Diagnostics;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\nusing IE = System.Collections.IEnumerator;\n\nnamespace WzComparerR2.MapRender\n{\n    class CoroutineManager : GameComponent\n    {\n        public CoroutineManager(Game game) : base(game)\n        {\n            this.Enabled = true;\n            this.runList = new LinkedList<Coroutine>();\n            this.preAdd = new List<Coroutine>();\n            this.preRemove = new List<Coroutine>();\n        }\n\n        public GameTime GameTime { get; private set; }\n\n        private LinkedList<Coroutine> runList;\n        private List<Coroutine> preAdd;\n        private List<Coroutine> preRemove;\n        private bool isUpdating;\n   \n\n        public override void Update(GameTime gameTime)\n        {\n            this.GameTime = gameTime;\n            this.isUpdating = true;\n\n            LinkedListNode<Coroutine> node = null;\n            while ((node = (node == null ? runList.First : node.Next)) != null)\n            {\n                preAdd.Clear();\n                preRemove.Clear();\n                bool removeMe = true;\n                var coroutine = node.Value;\n\n                while (coroutine != null)\n                {\n                    if (coroutine.Enumerator != null)\n                    {\n                        bool hasNext = coroutine.Enumerator.MoveNext();\n                        if (hasNext)\n                        {\n                            removeMe = false;\n                            var value = coroutine.Enumerator.Current;\n                            if (value == null)\n                            {\n                                //跳到下次update\n                            }\n                            else if (value is YieldCoroutine)\n                            {\n                                node.Value = ((YieldCoroutine)value);\n                            }\n                            else if (value is Coroutine)\n                            {\n                                //栈执行\n                                var nextCoroutine = (Coroutine)value;\n                                nextCoroutine.Prev = coroutine;\n                                node.Value = nextCoroutine;\n\n                                preAdd.Remove(nextCoroutine);\n                            }\n                            else\n                            {\n                                //其他奇妙类型 忽略\n                            }\n                            break;\n                        }\n                    }\n\n                    coroutine = coroutine.Prev;\n                }\n\n                foreach (var c in preRemove)\n                {\n                    runList.Remove(c);\n                }\n                foreach (var c in preAdd)\n                {\n                    runList.AddLast(c);\n                }\n                if (removeMe)\n                {\n                    var nextNode = node.Previous;\n                    runList.Remove(node);\n                    node = nextNode;\n                }\n            }\n\n            this.isUpdating = false;\n        }\n\n        public Coroutine StartCoroutine(IE ie)\n        {\n            return this.StartCoroutine(new Coroutine() { Enumerator = ie });\n        }\n\n        public Coroutine StartCoroutine(Coroutine coroutine)\n        {\n            lock (this)\n            {\n                if (this.isUpdating)\n                {\n                    preAdd.Add(coroutine);\n                }\n                else\n                {\n                    this.runList.AddLast(coroutine);\n                }\n\n                return coroutine;\n            }\n        }\n\n        public Coroutine Yield(IE ie)\n        {\n            return new YieldCoroutine() { Enumerator = ie };\n        }\n\n        public Coroutine Yield(Coroutine coroutine)\n        {\n            return new YieldCoroutine() { Enumerator = coroutine.Enumerator };\n        }\n\n        public Coroutine Post(Action action)\n        {\n            return new InvokeActionCoroutine(action);\n        }\n\n        public Coroutine Post<T>(Action<T> action, T arg0)\n        {\n            return new InvokeActionCoroutine<T>(action, arg0);\n        }\n\n        public void StopCoroutine(Coroutine coroutine)\n        {\n            if (this.isUpdating)\n            {\n                preRemove.Add(coroutine);\n            }\n            else\n            {\n                this.runList.Remove(coroutine);\n            }\n        }\n    }\n\n    class Coroutine\n    {\n        public IE Enumerator { get; set; } \n        public Coroutine Prev { get; set; }\n    }\n\n    sealed class YieldCoroutine : Coroutine\n    {\n    }\n\n    sealed class WaitTaskCompletedCoroutine : Coroutine\n    {\n        public WaitTaskCompletedCoroutine(Task task)\n        {\n            this.Task = task;\n            this.Enumerator = this.GetEnumerator();\n        }\n        public Task Task { get; set; }\n\n        private IE GetEnumerator()\n        {\n            while (!Task.IsCompleted)\n            {\n                yield return null;\n            }\n            if (Task.IsFaulted)\n            {\n                PluginBase.PluginManager.LogError(\"MapRender\", Task.Exception, \"Coroutine Error: \");\n#if DEBUG\n                if (Debugger.IsAttached)\n                {\n                    var ex = Task.Exception;\n                    Debugger.Break();\n                }\n#endif\n            }\n        }\n    }\n\n    sealed class InvokeActionCoroutine : Coroutine\n    {\n        public InvokeActionCoroutine(Action action)\n        {\n            this.Action = action;\n            this.Enumerator = this.GetEnumerator();\n        }\n\n        public Action Action { get; set; }\n\n        private IE GetEnumerator()\n        {\n            this.Action.Invoke();\n            yield break;\n        }\n    }\n\n    sealed class InvokeActionCoroutine<T> : Coroutine\n    {\n        public InvokeActionCoroutine(Action<T> action, T arg0)\n        {\n            this.Action = action;\n            this.Arg0 = arg0;\n            this.Enumerator = this.GetEnumerator();\n        }\n\n        public Action<T> Action { get; set; }\n        public T Arg0 { get; set; }\n\n        private IE GetEnumerator()\n        {\n            this.Action.Invoke(this.Arg0);\n            yield break;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Effects/EffectResources.cs",
    "content": "﻿using Microsoft.Xna.Framework.Graphics;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\nusing WzComparerR2.Rendering.EffectCompiler;\n\nnamespace WzComparerR2.MapRender.Effects\n{\n    public static class EffectResources\n    {\n        static EffectResources()\n        {\n            nativeShaderDescriptions = new Dictionary<string, NativeShaderDesc>();\n            nativeShaderMgfxCache = new Dictionary<string, byte[]>();\n            nativeShaderDescriptions.Add(\"default\", new NativeShaderDesc()\n            {\n                Name = \"default\",\n                OriginFileName = \"g_module_default_ps\",\n                FileLength = 796,\n                Version = \"KMST1186\",\n                Stage = ShaderStage.Pixel,\n                ConstantBuffers = new List<ConstantBuffer>(),\n                Samplers = new List<SamplerInfo>()\n                {\n                    new SamplerInfo()\n                    {\n                        Name = \"src_sampler_sampler_s\",\n                        SamplerSlot = 0,\n                        TextureName = \"src_sampler\",\n                        TextureSlot = 0,\n                        Type = SamplerType.Sampler2D,\n                    }\n                },\n            });\n            nativeShaderDescriptions.Add(\"light\", new NativeShaderDesc\n            {\n                Name = \"light\",\n                OriginFileName = \"g_module_light_ps\",\n                FileLength = 1828,\n                Version = \"KMST1186\",\n                Stage = ShaderStage.Pixel,\n                ConstantBuffers = new List<ConstantBuffer>()\n                {\n                    new ConstantBuffer\n                    {\n                        Name = \"_2\",\n                        Slot = 2,\n                        SizeInBytes = 16,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"z\", null, 0, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                        }\n                    },\n                    new ConstantBuffer\n                    {\n                        Name = \"_4\",\n                        Slot = 4,\n                        SizeInBytes = 96,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"player_pos\", \"cb1[0].xy\", 0, EffectParameterClass.Vector, EffectParameterType.Single, 1, 2, 0),\n                            new ShaderParameter(\"light_inner_radius\", \"cb1[0].z\", 8, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"light_outer_radius\", \"cb1[0].w\", 12, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"player_light_color\", \"cb1[1].xyzw\", 16, EffectParameterClass.Vector, EffectParameterType.Single, 1, 4, 0),\n                            new ShaderParameter(\"top_color\", \"cb1[2].xyzw\", 32, EffectParameterClass.Vector, EffectParameterType.Single, 1, 4, 0),\n                            new ShaderParameter(\"bottom_color\", \"cb1[3].xyzw\", 48, EffectParameterClass.Vector, EffectParameterType.Single, 1, 4, 0),\n                            new ShaderParameter(\"min_y\", \"cb1[4].x\", 64, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"max_y\", \"cb1[4].y\", 68, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                        }\n                    },\n                },\n                Samplers = new List<SamplerInfo>()\n                {\n                    new SamplerInfo()\n                    {\n                        Name = \"src_tex_sampler_s\",\n                        SamplerSlot = 0,\n                        TextureName = \"src_tex\",\n                        TextureSlot = 0,\n                        Type = SamplerType.Sampler2D,\n                    }\n                },\n            });\n            nativeShaderDescriptions.Add(\"waterBack\", new NativeShaderDesc\n            {\n                Name = \"waterBack\",\n                OriginFileName = \"g_module_water_back_ps\",\n                FileLength = 1536,\n                Version = \"KMST1186\",\n                Stage = ShaderStage.Pixel,\n                ConstantBuffers = new List<ConstantBuffer>()\n                {\n                    new ConstantBuffer\n                    {\n                        Name = \"_2\",\n                        Slot = 2,\n                        SizeInBytes = 176,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"min_y\", null, 0, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"max_y\", null, 4, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color0\", null, 16, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level0\", null, 28, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color1\", null, 32, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level1\", null, 44, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color2\", null, 48, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level2\", null, 60, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color3\", null, 64, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level3\", null, 76, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color4\", null, 80, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level4\", null, 92, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color5\", null, 96, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level5\", null, 108, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color6\", null, 112, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level6\", null, 124, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color7\", null, 128, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level7\", null, 140, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color8\", null, 144, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level8\", null, 156, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"color9\", null, 160, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"level9\", null, 172, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                        }\n                    }\n                }\n            });\n            nativeShaderDescriptions.Add(\"waterFront\", new NativeShaderDesc\n            {\n                Name = \"waterFront\",\n                OriginFileName = \"g_module_water_front_ps\",\n                FileLength = 3872,\n                Version = \"KMST1186\",\n                Stage = ShaderStage.Pixel,\n                ConstantBuffers = new List<ConstantBuffer>()\n                {\n                    new ConstantBuffer()\n                    {\n                        Name = \"_0\",\n                        Slot = 0,\n                        SizeInBytes = 144,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"vp\", null, 0, EffectParameterClass.Matrix, EffectParameterType.Single, 4, 4, 0),\n                            new ShaderParameter(\"vp_inv\", null, 64, EffectParameterClass.Matrix, EffectParameterType.Single, 4, 4, 0),\n                            new ShaderParameter(\"resolution_time\", null, 128, EffectParameterClass.Vector, EffectParameterType.Single, 1, 4, 0),\n                        }\n                    },\n                    new ConstantBuffer()\n                    {\n                        Name = \"_2\",\n                        Slot = 2,\n                        SizeInBytes = 112,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"value\", null, 0, EffectParameterClass.Vector, EffectParameterType.Single, 1, 2, 0),\n                            new ShaderParameter(\"cgrad\", null, 8, EffectParameterClass.Vector, EffectParameterType.Single, 1, 2, 0),\n                            new ShaderParameter(\"octave\", null, 16, EffectParameterClass.Vector, EffectParameterType.Single, 1, 2, 0),\n                            new ShaderParameter(\"strength\", null, 24, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"edge\", null, 28, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"godray_min_y\", null, 32, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"godray_max_y\", null, 36, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"godray_move\", null, 40, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"godray_bright\", null, 44, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"godray_color\", null, 48, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"godray_uv_rot\", null, 60, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"godray_uv_scale\", null, 64, EffectParameterClass.Vector, EffectParameterType.Single, 1, 2, 0),\n                            new ShaderParameter(\"aberration\", null, 72, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"dist_strength\", null, 76, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"diffuse\", null, 80, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                            new ShaderParameter(\"screen_min_y\", null, 92, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"screen_max_y\", null, 96, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"screen_color\", null, 100, EffectParameterClass.Vector, EffectParameterType.Single, 1, 3, 0),\n                        },\n                    },\n                    new ConstantBuffer()\n                    {\n                        Name = \"_4\",\n                        Slot = 4,\n                        SizeInBytes = 96,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"player_pos\", \"cb1[0].xy\", 0, EffectParameterClass.Vector, EffectParameterType.Single, 1, 2, 0),\n                            new ShaderParameter(\"distance_factor1\", \"cb1[1].x\", 16, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"min_y\", \"cb1[4].x\", 64, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"max_y\", \"cb1[4].y\", 68, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                            new ShaderParameter(\"dist_center_pos\", \"cb1[4].zw\", 72, EffectParameterClass.Vector, EffectParameterType.Single, 1, 2, 0),\n                            new ShaderParameter(\"distance_factor2\", \"cb1[5].x\", 80, EffectParameterClass.Scalar, EffectParameterType.Single, 1, 1, 0),\n                        }\n                    },\n                },\n                Samplers = new List<SamplerInfo>()\n                {\n                    new SamplerInfo()\n                    {\n                        Name = \"noise_tex_sampler_s\",\n                        SamplerSlot = 1,\n                        TextureName = \"noise_tex\",\n                        TextureSlot = 1,\n                        Type = SamplerType.Sampler2D,\n                    },\n                    new SamplerInfo()\n                    {\n                        Name = \"godray_noise_tex_sampler_s\",\n                        SamplerSlot = 2,\n                        TextureName = \"godray_noise_tex\",\n                        TextureSlot = 2,\n                        Type = SamplerType.Sampler2D,\n                    },\n                    new SamplerInfo()\n                    {\n                        Name = \"bg_tex_sampler_s\",\n                        SamplerSlot = 3,\n                        TextureName = \"bg_tex\",\n                        TextureSlot = 3,\n                        Type = SamplerType.Sampler2D,\n                    }\n                },\n            });\n            nativeShaderDescriptions.Add(\"vs_position_color_texture\", new NativeShaderDesc\n            {\n                Name = \"vs_position_color_texture\",\n                OriginFileName = \"off_42b030\",\n                FileLength = 1668,\n                Version = \"KMST1186\",\n                Stage = ShaderStage.Vertex,\n                ConstantBuffers = new List<ConstantBuffer>()\n                {\n                    new ConstantBuffer()\n                    {\n                        Name = \"_0\",\n                        Slot = 0,\n                        SizeInBytes = 144,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"vp\", null, 0, EffectParameterClass.Matrix, EffectParameterType.Single, 4, 4, 0),\n                            new ShaderParameter(\"vp_inv\", null, 64, EffectParameterClass.Matrix, EffectParameterType.Single, 4, 4, 0),\n                            new ShaderParameter(\"resolution_time\", null, 128, EffectParameterClass.Vector, EffectParameterType.Single, 1, 4, 0),\n                        }\n                    },\n                    new ConstantBuffer()\n                    {\n                        Name = \"_1\",\n                        Slot = 1,\n                        SizeInBytes = 64,\n                        Parameters = new List<ShaderParameter>()\n                        {\n                            new ShaderParameter(\"world\", null, 0, EffectParameterClass.Matrix, EffectParameterType.Single, 4, 4, 0),\n                        }\n                    },\n                },\n            });\n        }\n\n        private static Dictionary<string, NativeShaderDesc> nativeShaderDescriptions;\n        private static Dictionary<string, byte[]> nativeShaderMgfxCache;\n\n        public static ReadOnlySpan<byte> GetNativeShaderEffectBytes(string shaderName)\n        {\n            return GetNativeShaderEffectBytesInternal(shaderName);\n        }\n\n        public static Effect CreateNativeShader(GraphicsDevice graphicsDevice, string shaderName)\n        {\n            return new Effect(graphicsDevice, GetNativeShaderEffectBytesInternal(shaderName));\n        }\n\n        private static byte[] GetNativeShaderEffectBytesInternal(string shaderName)\n        {\n            if (!nativeShaderMgfxCache.TryGetValue(shaderName, out byte[] effectFile))\n            {\n                effectFile = CompileNativeShader(shaderName);\n                nativeShaderMgfxCache.Add(shaderName, effectFile);\n            }\n\n            return effectFile;\n        }\n\n        private static byte[] CompileNativeShader(string shaderName)\n        {\n            if (!nativeShaderDescriptions.TryGetValue(shaderName, out var nativeShaderDesc))\n            {\n                throw new ArgumentException($\"Can't find shader description of '{shaderName}'.\", nameof(shaderName));\n            }\n\n            var asm = Assembly.GetAssembly(typeof(EffectResources));\n            byte[] shaderByteCode;\n            using (var input = asm.GetManifestResourceStream($\"WzComparerR2.MapRender.Effects.Resources.Native.{shaderName}\"))\n            {\n                shaderByteCode = new byte[input.Length];\n                input.Read(shaderByteCode, 0, shaderByteCode.Length);\n            }\n\n            var effectFile = ShaderConverter.D3DShaderByteCodeToMgfx(shaderByteCode, \n                nativeShaderDesc.Stage, \n                nativeShaderDesc.ConstantBuffers,\n                nativeShaderDesc.Samplers);\n            return effectFile;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Effects/NativeShaderDesc.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing WzComparerR2.Rendering.EffectCompiler;\n\nnamespace WzComparerR2.MapRender.Effects\n{\n    public class NativeShaderDesc\n    {\n        public string Name { get; set; }\n        public string OriginFileName { get; set; }\n        public int FileLength { get; set; }\n        public string Version { get; set; }\n        public ShaderStage Stage { get; set; }\n        public List<ConstantBuffer> ConstantBuffers { get; set; }\n        public List<SamplerInfo> Samplers { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Effects/ShaderMaterials.cs",
    "content": "﻿using System;\nusing System.Linq;\nusing System.Reflection;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender.Effects\n{\n    public static class ShaderMaterialFactory\n    {\n        public static ShaderMaterial Create(MsCustomSpriteData msSprite)\n        {\n            var shaderMaterial = Create(msSprite.Shader.ID);\n            shaderMaterial.LoadFromMsSprite(msSprite);\n            return shaderMaterial;\n        }\n\n        public static ShaderMaterial Create(string shaderID)\n        {\n            return shaderID switch\n            {\n                \"light\" => new LightPixelShaderMaterial(),\n                \"waterBack\" => new WaterBackPixelShaderMaterial(),\n                \"waterFront\" => new WaterFrontPixelShaderMaterial(),\n                _ => throw new Exception($\"Unsupported shader: {shaderID}\"),\n            };\n        }\n    }\n\n    public abstract class ShaderMaterial : IEquatable<ShaderMaterial>\n    {\n        public ShaderMaterial(string shaderID)\n        {\n            this.ShaderID = shaderID;\n        }\n\n        public string ShaderID { get; protected set; }\n        public abstract void ApplyParameters(Effect effect);\n        public virtual void ApplySamplerStates(GraphicsDevice graphicsDevice) { }\n        public virtual void LoadFromMsSprite(MsCustomSpriteData msSprite) { }\n        public virtual bool Equals(ShaderMaterial other)\n        {\n            // TODO: check if all exported shader parameters are identical.\n            return Object.ReferenceEquals(this, other);\n        }\n\n        protected void LoadBindingParameters(MsCustomSpriteData msCustomSprite)\n        {\n            foreach (var propInfo in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))\n            {\n                var bindingAttr = propInfo.GetCustomAttribute<ShaderParameterBindingAttribute>();\n                if (bindingAttr != null)\n                {\n                    if (bindingAttr.TextureIndex > -1 && bindingAttr.TextureIndex < msCustomSprite.Textures.Length)\n                    {\n                        var msCustomTexture = msCustomSprite.Textures[bindingAttr.TextureIndex];\n                        if (propInfo.PropertyType == typeof(Texture2D))\n                        {\n                            propInfo.SetValue(this, msCustomTexture.Texture);\n                        }\n                        else if (propInfo.PropertyType == typeof(SamplerState))\n                        {\n                            var samplerState = new SamplerState();\n                            samplerState.AddressU = msCustomTexture.AddressU switch\n                            {\n                                1 => TextureAddressMode.Wrap,\n                                _ => TextureAddressMode.Clamp,\n                            };\n                            samplerState.AddressV = msCustomTexture.AddressV switch\n                            {\n                                1 => TextureAddressMode.Wrap,\n                                _ => TextureAddressMode.Clamp,\n                            };\n                            propInfo.SetValue(this, samplerState);\n                        }\n                    }\n                    else if (msCustomSprite.Shader.Constants.TryGetValue(bindingAttr.ParameterName, out var shaderConstant))\n                    {\n                        if (propInfo.PropertyType == typeof(float))\n                        {\n                            propInfo.SetValue(this, shaderConstant.ToScalar());\n                        }\n                        else if (propInfo.PropertyType == typeof(Vector2))\n                        {\n                            propInfo.SetValue(this, shaderConstant.ToVector2());\n                        }\n                        else if (propInfo.PropertyType == typeof(Vector3))\n                        {\n                            propInfo.SetValue(this, shaderConstant.ToVector3());\n                        }\n                    }\n                }\n            }\n        }\n\n        protected void ApplyBindingParameters(Effect effect)\n        {\n            foreach (var propInfo in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))\n            {\n                var bindingAttr = propInfo.GetCustomAttribute<ShaderParameterBindingAttribute>();\n                if (bindingAttr != null && !string.IsNullOrEmpty(bindingAttr.ParameterName))\n                {\n                    switch (propInfo.GetValue(this))\n                    {\n                        case float _float:\n                            effect.Parameters[bindingAttr.ParameterName].SetValue(_float);\n                            break;\n                        case Vector2 vec2:\n                            effect.Parameters[bindingAttr.ParameterName].SetValue(vec2);\n                            break;\n                        case Vector3 vec3:\n                            effect.Parameters[bindingAttr.ParameterName].SetValue(vec3);\n                            break;\n                        case Vector4 vec4:\n                            effect.Parameters[bindingAttr.ParameterName].SetValue(vec4);\n                            break;\n                        case Matrix matrix:\n                            effect.Parameters[bindingAttr.ParameterName].SetValue(matrix);\n                            break;\n                        case Texture tex:\n                            effect.Parameters[bindingAttr.ParameterName].SetValue(tex);\n                            break;\n                    }\n                }\n            }\n        }\n\n        protected void ApplyBindingSamplers(GraphicsDevice graphicsDevice)\n        {\n            foreach (var propInfo in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))\n            {\n                var bindingAttr = propInfo.GetCustomAttribute<ShaderParameterBindingAttribute>();\n                if (bindingAttr != null && bindingAttr.TextureIndex > -1)\n                {\n                    switch (propInfo.GetValue(this))\n                    {\n                        case SamplerState samplerState:\n                            graphicsDevice.SamplerStates[bindingAttr.TextureIndex] = samplerState;\n                            break;\n                    }\n                }\n            }\n        }\n    }\n\n    internal class ShaderParameterBindingAttribute : Attribute\n    {\n        public ShaderParameterBindingAttribute(string parameterName, int textureIndex = -1)\n        {\n            this.ParameterName = parameterName;\n            this.TextureIndex = textureIndex;\n        }\n\n        public string ParameterName { get; protected set; }\n        public int TextureIndex { get; protected set; }\n    }\n\n    public interface IMaplestoryEffectMatrices\n    {\n        Matrix ViewProjection { get; set; }\n        Matrix ViewProjectionInverse { get; set; }\n        Vector4 ResolutionTime { get; set; }\n    }\n\n    public interface IBackgroundCaptureEffect\n    {\n        Texture2D BackgroundTexture { get; set; }\n    }\n\n    public class LightPixelShaderMaterial : ShaderMaterial\n    {\n        public LightPixelShaderMaterial() : base(\"light\")\n        {\n        }\n\n        [ShaderParameterBinding(\"z\")]\n        public float Z { get; set; }\n        [ShaderParameterBinding(\"src_tex\", 0)]\n        public Texture2D SrcTexture { get; set; }\n        [ShaderParameterBinding(null, 0)]\n        public SamplerState SrcTextureSamplerState { get; set; }\n\n        [ShaderParameterBinding(\"player_pos\")]\n        public Vector2 PlayerPos { get; set; }\n        [ShaderParameterBinding(\"light_inner_radius\")]\n        public float LightInnerRadius { get; set; }\n        [ShaderParameterBinding(\"light_outer_radius\")]\n        public float LightOuterRadius { get; set; }\n        [ShaderParameterBinding(\"player_light_color\")]\n        public Vector4 PlayerLightColor { get; set; }\n        [ShaderParameterBinding(\"top_color\")]\n        public Vector4 TopColor { get; set; }\n        [ShaderParameterBinding(\"bottom_color\")]\n        public Vector4 BottomColor { get; set; }\n        [ShaderParameterBinding(\"min_y\")]\n        public float MinY { get; set; }\n        [ShaderParameterBinding(\"max_y\")]\n        public float MaxY { get; set; }\n        \n        public override void LoadFromMsSprite(MsCustomSpriteData sprite)\n        {\n            base.LoadBindingParameters(sprite);\n        }\n\n        public override void ApplyParameters(Effect effect)\n        {\n            base.ApplyBindingParameters(effect);\n        }\n\n        public override void ApplySamplerStates(GraphicsDevice graphicsDevice)\n        {\n            base.ApplyBindingSamplers(graphicsDevice);\n        }\n    }\n\n    public class WaterBackPixelShaderMaterial : ShaderMaterial\n    {\n        public WaterBackPixelShaderMaterial() : base(\"waterBack\")\n        {\n        }\n\n        [ShaderParameterBinding(\"min_y\")]\n        public float MinY { get; set; }\n        [ShaderParameterBinding(\"max_y\")]\n        public float MaxY { get; set; }\n        [ShaderParameterBinding(\"color0\")]\n        public Vector3 Color0 { get; set; }\n        [ShaderParameterBinding(\"level0\")]\n        public float Level0 { get; set; }\n        [ShaderParameterBinding(\"color1\")]\n        public Vector3 Color1 { get; set; }\n        [ShaderParameterBinding(\"level1\")]\n        public float Level1 { get; set; }\n        [ShaderParameterBinding(\"color2\")]\n        public Vector3 Color2 { get; set; }\n        [ShaderParameterBinding(\"level2\")]\n        public float Level2 { get; set; }\n        [ShaderParameterBinding(\"color3\")]\n        public Vector3 Color3 { get; set; }\n        [ShaderParameterBinding(\"level3\")]\n        public float Level3 { get; set; }\n        [ShaderParameterBinding(\"color4\")]\n        public Vector3 Color4 { get; set; }\n        [ShaderParameterBinding(\"level4\")]\n        public float Level4 { get; set; }\n        [ShaderParameterBinding(\"color5\")]\n        public Vector3 Color5 { get; set; }\n        [ShaderParameterBinding(\"level5\")]\n        public float Level5 { get; set; }\n        [ShaderParameterBinding(\"color6\")]\n        public Vector3 Color6 { get; set; }\n        [ShaderParameterBinding(\"level6\")]\n        public float Level6 { get; set; }\n        [ShaderParameterBinding(\"color7\")]\n        public Vector3 Color7 { get; set; }\n        [ShaderParameterBinding(\"level7\")]\n        public float Level7 { get; set; }\n        [ShaderParameterBinding(\"color8\")]\n        public Vector3 Color8 { get; set; }\n        [ShaderParameterBinding(\"level8\")]\n        public float Level8 { get; set; }\n        [ShaderParameterBinding(\"color9\")]\n        public Vector3 Color9 { get; set; }\n        [ShaderParameterBinding(\"level9\")]\n        public float Level9 { get; set; }\n\n        public override void LoadFromMsSprite(MsCustomSpriteData sprite)\n        {\n            base.LoadBindingParameters(sprite);\n        }\n\n        public override void ApplyParameters(Effect effect)\n        {\n            base.ApplyBindingParameters(effect);\n        }\n    }\n\n    public class WaterFrontPixelShaderMaterial : ShaderMaterial, IMaplestoryEffectMatrices, IBackgroundCaptureEffect\n    {\n        public WaterFrontPixelShaderMaterial() : base(\"waterFront\")\n        {\n        }\n\n        [ShaderParameterBinding(\"vp\")]\n        public Matrix ViewProjection { get; set; }\n        [ShaderParameterBinding(\"vp_inv\")]\n        public Matrix ViewProjectionInverse { get; set; }\n        [ShaderParameterBinding(\"resolution_time\")]\n        public Vector4 ResolutionTime { get; set; }\n\n        [ShaderParameterBinding(\"value\")]\n        public Vector2 Value { get; set; }\n        [ShaderParameterBinding(\"cgrad\")]\n        public Vector2 Cgrad { get; set; }\n        [ShaderParameterBinding(\"octave\")]\n        public Vector2 Octave { get; set; }\n        [ShaderParameterBinding(\"strength\")]\n        public float Strength { get; set; }\n        [ShaderParameterBinding(\"edge\")]\n        public float Edge { get; set; }\n        [ShaderParameterBinding(\"godray_min_y\")]\n        public float GodrayMinY { get; set; }\n        [ShaderParameterBinding(\"godray_max_y\")]\n        public float GodrayMaxY { get; set; }\n        [ShaderParameterBinding(\"godray_move\")]\n        public float GodrayMove { get; set; }\n        [ShaderParameterBinding(\"godray_bright\")]\n        public float GodrayBright { get; set; }\n        [ShaderParameterBinding(\"godray_color\")]\n        public Vector3 GodrayColor { get; set; }\n        [ShaderParameterBinding(\"godray_uv_rot\")]\n        public float GodrayUVRot { get; set; }\n        [ShaderParameterBinding(\"godray_uv_scale\")]\n        public Vector2 GodrayUVScale { get; set; }\n        [ShaderParameterBinding(\"aberration\")]\n        public float Aberration { get; set; }\n        [ShaderParameterBinding(\"dist_strength\")]\n        public float DistStrength { get; set; }\n        [ShaderParameterBinding(\"diffuse\")]\n        public Vector3 Diffuse { get; set; }\n        [ShaderParameterBinding(\"screen_min_y\")]\n        public float ScreenMinY { get; set; }\n        [ShaderParameterBinding(\"screen_max_y\")]\n        public float ScreenMaxY { get; set; }\n        [ShaderParameterBinding(\"screen_color\")]\n        public Vector3 ScreenColor { get; set; }\n        [ShaderParameterBinding(\"noise_tex\", 1)]\n        public Texture2D NoiseTexture { get; set; }\n        [ShaderParameterBinding(null, 1)]\n        public SamplerState NoiseTextureSamplerState { get; set; }\n        [ShaderParameterBinding(\"godray_noise_tex\", 2)]\n        public Texture2D GodrayNoiseTexture { get; set; }\n        [ShaderParameterBinding(null, 2)]\n        public SamplerState GodrayNoiseTextureSamplerState { get; set; }\n        [ShaderParameterBinding(\"bg_tex\")]\n        public Texture2D BackgroundTexture { get; set; }\n\n        [ShaderParameterBinding(\"player_pos\")]\n        public Vector2 PlayerPos { get; set; }\n        [ShaderParameterBinding(\"distance_factor1\")]\n        public float Factor1 { get; set; }\n        [ShaderParameterBinding(\"min_y\")]\n        public float MinY { get; set; }\n        [ShaderParameterBinding(\"max_y\")]\n        public float MaxY { get; set; }\n        [ShaderParameterBinding(\"dist_center_pos\")]\n        public Vector2 DistNoiseCenterPos { get; set; }\n        [ShaderParameterBinding(\"distance_factor2\")]\n        public float Factor2 { get; set; }\n\n        public override void LoadFromMsSprite(MsCustomSpriteData sprite)\n        {\n            base.LoadBindingParameters(sprite);\n        }\n\n        public override void ApplyParameters(Effect effect)\n        {\n            base.ApplyBindingParameters(effect);\n        }\n\n        public override void ApplySamplerStates(GraphicsDevice graphicsDevice)\n        {\n            base.ApplyBindingSamplers(graphicsDevice);\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.MapRender/Entry.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.Config;\nusing WzComparerR2.PluginBase;\nusing DevComponents.DotNetBar;\nusing System.Threading;\nusing System.Windows.Forms;\nusing Game = Microsoft.Xna.Framework.Game;\n\nnamespace WzComparerR2.MapRender\n{\n    public class Entry : PluginEntry\n    {\n        public Entry(PluginContext context)\n            : base(context)\n        {\n\n        }\n\n        #if MapRenderV1\n        private RibbonBar bar;\n        private ButtonItem btnItemMapRender;\n        private FrmMapRender mapRenderGame1;\n        #endif\n\n        private RibbonBar bar2;\n        private ButtonItem btnItemMapRenderV2;\n        private FrmMapRender2 mapRenderGame2;\n\n        protected override void OnLoad()\n        {\n            #if MapRenderV1\n            this.bar = Context.AddRibbonBar(\"Modules\", \"MapRender\");\n            btnItemMapRender = new ButtonItem(\"\", \"MapRender\");\n            btnItemMapRender.Click += btnItem_Click;\n            bar.Items.Add(btnItemMapRender);\n            #endif\n            this.bar2 = Context.AddRibbonBar(\"Modules\", \"MapRender2\");\n            btnItemMapRenderV2 = new ButtonItem(\"\", \"MapRenderV2\");\n            btnItemMapRenderV2.Click += btnItem_Click;\n            bar2.Items.Add(btnItemMapRenderV2);\n            ConfigManager.RegisterAllSection(this.GetType().Assembly);\n        }\n\n        void btnItem_Click(object sender, EventArgs e)\n        {\n            Wz_Node node = Context.SelectedNode1;\n            if (node != null)\n            {\n                Wz_Image img = node.Value as Wz_Image;\n                Wz_File wzFile = node.GetNodeWzFile();\n\n                if (img != null && img.TryExtract())\n                {\n                    if (wzFile == null || wzFile.Type != Wz_Type.Map)\n                    {\n                        if (MessageBoxEx.Show(\"所选Img不属于Map.wz，是否继续？\", \"提示\", MessageBoxButtons.OKCancel) != DialogResult.OK)\n                        {\n                            goto exit;\n                        }\n                    }\n\n                    StringLinker sl = this.Context.DefaultStringLinker;\n                    if (!sl.HasValues) //生成默认stringLinker\n                    {\n                        sl = new StringLinker();\n                        sl.Load(PluginManager.FindWz(Wz_Type.String).GetValueEx<Wz_File>(null));\n                    }\n\n                    //开始绘制\n                    Thread thread = new Thread(() =>\n                    {\n#if !DEBUG\n                        try\n                        {\n#endif\n#if MapRenderV1\n                        if (sender == btnItemMapRender)\n                        {\n                            if (this.mapRenderGame1 != null)\n                            {\n                                return;\n                            }\n                            this.mapRenderGame1 = new FrmMapRender(img) { StringLinker = sl };\n                            try\n                            {\n                                using (this.mapRenderGame1)\n                                {\n                                    this.mapRenderGame1.Run();\n                                }\n                            }\n                            finally\n                            {\n                                this.mapRenderGame1 = null;\n                            }\n                        }\n                        else\n#endif\n                        {\n                            if (this.mapRenderGame2 != null)\n                            {\n                                // post message to the opening game.\n                                this.mapRenderGame2.LoadMap(img);\n                                return;\n                            }\n                            else\n                            {\n                                this.mapRenderGame2 = new FrmMapRender2() { StringLinker = sl };\n                                this.mapRenderGame2.Window.Title = \"MapRender \" + this.Version;\n                                this.mapRenderGame2.LoadMap(img);\n\n                                try\n                                {\n                                    using (this.mapRenderGame2)\n                                    {\n                                        this.mapRenderGame2.Run();\n                                    }\n                                }\n                                finally\n                                {\n                                    this.mapRenderGame2 = null;\n                                }\n                            }\n                        }\n#if !DEBUG\n                        }\n                        catch (Exception ex)\n                        {\n                            PluginManager.LogError(\"MapRender\", ex, \"MapRender error:\");\n                            MessageBoxEx.Show(ex.ToString(), \"MapRender\");\n                        }\n#endif\n                    });\n                    thread.SetApartmentState(ApartmentState.STA);\n                    thread.IsBackground = true;\n                    thread.Start();\n                    goto exit;\n                }\n            }\n\n            MessageBoxEx.Show(\"没有选择一个map的img\", \"MapRender\");\n\n            exit:\n            return;\n        }\n\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/FpsCounter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Diagnostics;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public class FpsCounter : DrawableGameComponent\n    {\n        public FpsCounter(Game game) : base(game)\n        {\n            this.CountInterval = TimeSpan.FromMilliseconds(1000);\n            this.swUpdate = new Stopwatch();\n            this.swDraw = new Stopwatch();\n        }\n\n        public TimeSpan CountInterval { get; set; }\n        public double UpdatePerSec { get; private set; }\n        public double DrawPerSec { get; private set; }\n        public bool UseStopwatch { get; set; }\n\n        private TimeSpan lastUpdate;\n        private int updateCount;\n        private Stopwatch swUpdate;\n\n        private TimeSpan lastDraw;\n        private int drawCount;\n        private Stopwatch swDraw;\n\n        public override void Update(GameTime gameTime)\n        {\n            this.updateCount++;\n\n            if (UseStopwatch)\n            {\n                if (!this.swUpdate.IsRunning)\n                {\n                    this.swUpdate.Start();\n                    return;\n                }\n                TimeSpan totalElapsed = this.swUpdate.Elapsed;\n                if (totalElapsed >= CountInterval)\n                {\n                    this.swUpdate.Reset();\n                    this.UpdatePerSec = this.updateCount / totalElapsed.TotalSeconds;\n                    this.updateCount = 0;\n                    this.swUpdate.Start();\n                }\n            }\n            else\n            {\n                TimeSpan totalElapsed = gameTime.TotalGameTime - lastUpdate;\n                if (totalElapsed >= this.CountInterval)\n                {\n                    this.UpdatePerSec = this.updateCount / totalElapsed.TotalSeconds;\n                    this.updateCount = 0;\n                    this.lastUpdate = gameTime.TotalGameTime;\n                }\n            }\n        }\n\n        public override void Draw(GameTime gameTime)\n        {\n            this.drawCount++;\n            if (UseStopwatch)\n            {\n                if (!this.swDraw.IsRunning)\n                {\n                    this.swDraw.Start();\n                    return;\n                }\n                TimeSpan totalElapsed = this.swDraw.Elapsed;\n                if (totalElapsed >= CountInterval)\n                {\n                    this.swDraw.Reset();\n                    this.DrawPerSec = this.drawCount / totalElapsed.TotalSeconds;\n                    this.drawCount = 0;\n                    this.swDraw.Start();\n                }\n            }\n            else\n            {\n                TimeSpan totalElapsed = gameTime.TotalGameTime - lastDraw;\n                if (totalElapsed >= this.CountInterval)\n                {\n                    this.DrawPerSec = this.drawCount / totalElapsed.TotalSeconds;\n                    this.drawCount = 0;\n                    this.lastDraw = gameTime.TotalGameTime;\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.MapRender/FrmMapRender.cs",
    "content": "﻿#if MapRenderV1\n\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Runtime.InteropServices;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.PluginBase;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing Microsoft.Xna.Framework.Input;\n\nusing System.IO;\nusing System.IO.Compression;\nusing System.Threading;\nusing Form = System.Windows.Forms.Form;\nusing Un4seen.Bass;\nusing WzComparerR2.MapRender.UI;\n\n//using JLChnToZ.IMEHelper;\nusing WzComparerR2.MapRender.Patches;\nusing WzComparerR2.Common;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender\n{\n    public class FrmMapRender : Game\n    {\n        public FrmMapRender()\n        {\n            graphics = new GraphicsDeviceManager(this);\n            this.loadState = LoadState.NotLoad;\n            this.IsMouseVisible = true;\n            this.renderingList = new List<RenderPatch>();\n            this.willReloadBgm = true;\n            this.profile = new StringBuilder();\n            this.showProfile = true;\n            this.fpsCounter = new FpsCounter(this);\n            this.fpsCounter.UseStopwatch = true;\n            this.cameraInMap = true;\n            this.patchVisibility = new PatchVisibility();\n            this.patchVisibility.FootHoldVisible = false;\n            this.patchVisibility.LadderRopeVisible = false;\n            this.volumeGlobal = 100;\n\n            Form windowForm = (Form)Form.FromHandle(this.Window.Handle);\n            windowForm.GotFocus += windowForm_GotFocus;\n            windowForm.LostFocus += windowForm_LostFocus;\n\n            GameExt.FixKeyboard(this);\n            /*\n            this.chat = new Chat();\n            this.chat.Connected += chat_Connected;\n            this.chat.Error += chat_Error;\n            this.chat.MessageReceived += chat_MessageReceived;*/\n        }\n\n        void chat_Connected(object sender, EventArgs e)\n        {\n            if (this.txtChat.Length > 0)\n            {\n                this.txtChat.AppendLine();\n            }\n            this.txtChat.Append(\"已经连接服务器\");\n        }\n\n        protected override void OnActivated(object sender, EventArgs args)\n        {\n            base.OnActivated(sender, args);\n            GameExt.FixKeyboard(this);\n        }\n\n        protected override void OnDeactivated(object sender, EventArgs args)\n        {\n            base.OnDeactivated(sender, args);\n        }\n        /*\n        void chat_Error(object sender, ChatErrorEventArgs e)\n        {\n            if (this.txtChat.Length > 0)\n            {\n                this.txtChat.AppendLine();\n            }\n            this.txtChat.Append(\"错误：\" + e.Error);\n        }\n\n        void chat_MessageReceived(object sender, ChatMessageEventArgs e)\n        {\n            if (this.txtChat.Length > 0)\n            {\n                this.txtChat.AppendLine();\n            }\n            this.txtChat.AppendFormat(\"[{0}] {1}\", e.FromName ?? \"系统公告\", e.MessageText);\n        }*/\n\n        public FrmMapRender(Wz_Image mapImg)\n            : this()\n        {\n            this.mapImg = mapImg;\n        }\n\n        RenderEnv renderEnv;\n        Tooltip tooltip;\n        GraphicsDeviceManager graphics;\n        LoadState loadState;\n        TimeSpan stateChangedTime;\n        const int EnteringTime = 1500;\n        const int ExitingTime = 1500;\n\n        //双缓冲用...\n        RenderTarget2D target2D;\n\n        //资源...\n        Wz_Image mapImg;\n        int mapID;\n        string mapMark;\n        MiniMap miniMap;\n        List<RenderPatch> renderingList;\n        StringLinker stringLinker;\n        TextureLoader texLoader;\n        PatchVisibility patchVisibility;\n\n        //bgm用\n        IntPtr bgmStream;\n        byte[] bgmFile;\n        string bgmName;\n        float bgmVolume;\n        int volumeGlobal; //0-100\n        bool isSilent;\n        bool silentOnDeactive;\n\n        //和地图切换相关的参数...\n        bool willReloadBgm;\n        string enterPortal;\n\n        //截图用\n        bool prepareCapture;\n        bool snapshotSaving;\n\n        //调试用\n        StringBuilder profile;\n        bool showProfile;\n        FpsCounter fpsCounter;\n        bool cameraInMap;\n\n        //ui\n        UIMiniMap uiMinimap;\n\n        //输入\n        //IMEHandler ime;\n        StringBuilder txtInput = new StringBuilder();\n        bool isOnCommand;\n\n        //Chat chat;\n        StringBuilder txtChat = new StringBuilder();\n\n        public StringLinker StringLinker\n        {\n            get { return stringLinker; }\n            set { stringLinker = value; }\n        }\n\n        protected override void Initialize()\n        {\n            base.Initialize();\n            //this.ime = new IMEHandler(this, true);\n            //this.ime.onResultReceived += ime_onResultReceived;\n            ChangeDisplayMode(0);\n            //ThreadPool.QueueUserWorkItem((o) => this.chat.Connect());\n        }\n\n\n        /*\n        void ime_onResultReceived(object sender, IMEResultEventArgs e)\n        {\n            if (isOnCommand)\n            {\n                switch (e.result)\n                {\n                    case '\\b':\n                        if (txtInput.Length > 0)\n                        {\n                            txtInput.Remove(txtInput.Length - 1, 1);\n                        }\n                        break;\n                    case '\\x03': //复制\n                        break;\n                    case '\\x16': //粘贴\n                        {\n                            string text = System.Windows.Forms.Clipboard.GetText();\n                            if (text != null)\n                            {\n                                int idx = text.IndexOfAny(\"\\r\\n\".ToCharArray());\n                                if (idx > -1)\n                                {\n                                    text = text.Substring(0, idx);\n                                }\n                                txtInput.Append(text);\n                            }\n                        }\n                        break;\n                    case '\\r': //回车\n                        \n                        if (this.chat.IsConnected)\n                        {\n                            string content = this.txtInput.ToString();\n                            if (content.StartsWith(\"/setName \"))\n                            {\n                                ThreadPool.QueueUserWorkItem(o => this.chat.SetName(content.Substring(9)));\n                            }\n                            else\n                            {\n                                ThreadPool.QueueUserWorkItem(o => this.chat.Talk(content));\n                            }\n                            this.txtInput.Remove(0, this.txtInput.Length);\n                        }\n                        this.ime.Enabled = false;\n                        break;\n                    default:\n                        txtInput.Append(e.result);\n                        break;\n                }\n            }\n        }*/\n\n        protected override void LoadContent()\n        {\n            base.LoadContent();\n            this.renderEnv = new RenderEnv(this, this.graphics);\n            this.tooltip = new Tooltip(this.GraphicsDevice);\n            this.texLoader = new TextureLoader(this.GraphicsDevice);\n            \n            //初始化UI\n            this.uiMinimap = new UIMiniMap(this.GraphicsDevice);\n            this.uiMinimap.MapNameFont = this.renderEnv.Fonts.MapNameFont;\n        }\n\n        void windowForm_LostFocus(object sender, EventArgs e)\n        {\n            if (this.silentOnDeactive)\n            {\n                isSilent = true;\n                this.UpdateBgmVolume();\n            }\n        }\n\n        void windowForm_GotFocus(object sender, EventArgs e)\n        {\n            if (this.silentOnDeactive)\n            {\n                isSilent = false;\n                this.UpdateBgmVolume();\n            }\n        }\n\n        private void ChangeDisplayMode(int i)\n        {\n            this.renderEnv.Camera.DisplayMode = i;\n            Form windowForm = (Form)Form.FromHandle(this.Window.Handle);\n\n            switch (i)\n            {\n                default:\n                case 0:\n                case 1:\n                case 2:\n                    windowForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n                    break;\n                case 3:\n                    windowForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;\n                    break;\n            }\n\n            windowForm.SetDesktopLocation(0, 0);\n        }\n\n        protected override void OnExiting(object sender, EventArgs args)\n        {\n            foreach (RenderPatch patch in this.renderingList)\n            {\n                patch.Dispose();\n            }\n            this.renderingList.Clear();\n            Bass.BASS_StreamFree(this.bgmStream.ToInt32());\n            this.renderEnv.Dispose();\n           // this.ime.Dispose();\n            //this.chat.Disconnect();\n        }\n\n        protected override void Update(GameTime gameTime)\n        {\n            this.fpsCounter.Update(gameTime);\n            this.renderEnv.Input.Update(gameTime);\n\n            LoadState oldState = this.loadState;\n            switch (this.loadState)\n            {\n                case LoadState.NotLoad:\n                    if (this.mapImg != null)\n                    {\n                        Thread loadMapThread = new Thread(LoadMap);\n                        loadMapThread.Start();\n                        this.loadState = LoadState.Loading;\n                    }\n                    break;\n                case LoadState.Loading:\n                    // waiting...\n                    break;\n                case LoadState.LoadSuccessed:\n                    if (willReloadBgm)\n                    {\n                        this.bgmVolume = 0f;\n                        UpdateBgmVolume();\n                    }\n                    Bass.BASS_ChannelPlay(this.bgmStream.ToInt32(), false);\n                    //prepare camera\n                    {\n                        foreach (RenderPatch patch in this.renderingList)\n                        {\n                            PortalPatch portal;\n                            if (patch.ObjectType == RenderObjectType.Portal\n                                && (portal = patch as PortalPatch) != null\n                                && ((this.enterPortal == null) ?\n                                portal.PortalType == 0 : //sp\n                                portal.PortalName == this.enterPortal))\n                            {\n                                this.renderEnv.Camera.Center = portal.Position;\n                                break;\n                            }\n                        }\n                    }\n                    \n                    this.loadState = LoadState.Entering;\n\n                    //prepare minimap\n                    this.uiMinimap.MiniMap = this.miniMap;\n                    \n                    StringResult sr;\n                    if (this.stringLinker != null && this.stringLinker.StringMap.TryGetValue(this.mapID, out sr))\n                    {\n                        this.uiMinimap.MapName = sr[\"mapName\"];\n                        this.uiMinimap.StreetName = sr[\"streetName\"];\n                    }\n                    else\n                    {\n                        this.uiMinimap.MapName = null;\n                        this.uiMinimap.StreetName = null;\n                    }\n                    this.uiMinimap.UpdateSize();\n\n                    //首次更新portal状态\n                    UpdatePortalVisibility();\n                    break;\n                case LoadState.LoadFailed:\n                    // flag state\n                    break;\n                case LoadState.Entering:\n                    UpdateCamera(gameTime);\n                    UpdatePatch(gameTime);\n                    {\n                        double time = (gameTime.TotalGameTime - stateChangedTime).TotalMilliseconds;\n                        if (willReloadBgm)\n                        {\n                            this.bgmVolume = (float)(time / EnteringTime);\n                            UpdateBgmVolume();\n                        }\n                        if (time > EnteringTime)\n                        {\n                            this.loadState = LoadState.Rendering;\n                        }\n                    }\n                    break;\n                case LoadState.Rendering:\n                    UpdateInput(gameTime);\n                    UpdateCamera(gameTime);\n                    UpdatePatch(gameTime);\n                    break;\n                case LoadState.Exiting:\n                    UpdateInput(gameTime);\n                    UpdatePatch(gameTime);\n                    {\n                        double time = (gameTime.TotalGameTime - stateChangedTime).TotalMilliseconds;\n                        if (willReloadBgm)\n                        {\n                            this.bgmVolume = (float)(1 - time / ExitingTime);\n                            UpdateBgmVolume();\n                        }\n                        if (time > ExitingTime)\n                        {\n                            if (willReloadBgm)\n                            {\n                                Bass.BASS_ChannelStop(this.bgmStream.ToInt32());\n                                Bass.BASS_StreamFree(this.bgmStream.ToInt32());\n                            }\n                            DisposePatch();\n                            this.loadState = LoadState.NotLoad;\n                        }\n                    }\n                    break;\n            }\n\n            if (this.loadState != oldState)\n            {\n                stateChangedTime = gameTime.TotalGameTime;\n            }\n\n            base.Update(gameTime);\n        }\n\n        private void UpdateInput(GameTime gameTime)\n        {\n            if (!this.IsActive)\n                return;\n            InputState input = renderEnv.Input;\n\n            if (!this.isOnCommand)\n            {\n                OnGlobalHotKey(gameTime);\n            }\n            else\n            {\n                //OnInputMethod(gameTime);\n            }\n        }\n\n        /*\n        private void OnInputMethod(GameTime gameTime)\n        {\n            InputState input = renderEnv.Input;\n            if (!this.ime.Enabled)\n            {\n                this.isOnCommand = false;\n            }\n        }*/\n\n        private void OnGlobalHotKey(GameTime gameTime)\n        {\n            Camera camera = renderEnv.Camera;\n            InputState input = renderEnv.Input;\n\n            if (input.IsAltPressing && input.IsCtrlPressing && input.IsShiftPressing && input.IsKeyDown(Keys.Insert))\n            {\n                this.isOnCommand = !this.isOnCommand;\n                //this.ime.Enabled = this.isOnCommand; //开关输入法支持\n            }\n\n            if (input.IsAltPressing && input.IsKeyDown(Keys.Enter))\n            {\n                camera.DisplayMode = (camera.DisplayMode + 1) % 4;\n                this.ChangeDisplayMode(camera.DisplayMode);\n            }\n\n\n            if (input.IsKeyDown(Keys.Scroll))\n            {\n                if (!snapshotSaving)\n                {\n                    this.renderEnv.Camera.UseWorldRect = true;\n                    prepareCapture = true;\n                }\n            }\n\n            if (input.IsKeyDown(Keys.F1))\n            {\n                this.SaveAllTexture();\n            }\n\n            if (input.IsKeyDown(Keys.F5))\n            {\n                this.showProfile = !this.showProfile;\n            }\n\n            //调整音量\n            if (input.IsKeyDown(Keys.OemPlus) || input.IsKeyDown(Keys.Add))\n            {\n                if (input.IsCtrlPressing) //ctrl+加号\n                {\n                    this.silentOnDeactive = true;\n                }\n                else\n                {\n                    this.volumeGlobal = Math.Max(0, Math.Min(this.volumeGlobal + 5, 100));\n                }\n                this.UpdateBgmVolume();\n            }\n            if (input.IsKeyDown(Keys.OemMinus) || input.IsKeyDown(Keys.Subtract))\n            {\n                if (input.IsCtrlPressing) //ctrl+减号\n                {\n                    this.silentOnDeactive = false;\n                }\n                else\n                {\n                    this.volumeGlobal = Math.Max(0, Math.Min(this.volumeGlobal - 5, 100));\n                }\n                this.UpdateBgmVolume();\n            }\n            if (input.IsKeyDown(Keys.M))\n            {\n                this.uiMinimap.Visible = !this.uiMinimap.Visible;\n            }\n\n            if (input.IsCtrlPressing)\n            {\n                if (input.IsKeyDown(Keys.U)) //设置镜头限制\n                {\n                    this.cameraInMap = !this.cameraInMap;\n                }\n\n                if (input.IsKeyDown(Keys.D1))\n                {\n                    this.patchVisibility.BackVisible = !this.patchVisibility.BackVisible;\n                }\n                if (input.IsKeyDown(Keys.D2))\n                {\n                    this.patchVisibility.ReactorVisible = !this.patchVisibility.ReactorVisible;\n                }\n                if (input.IsKeyDown(Keys.D3))\n                {\n                    this.patchVisibility.ObjVisible = !this.patchVisibility.ObjVisible;\n                }\n                if (input.IsKeyDown(Keys.D4))\n                {\n                    this.patchVisibility.TileVisible = !this.patchVisibility.TileVisible;\n                }\n                if (input.IsKeyDown(Keys.D5))\n                {\n                    this.patchVisibility.NpcVisible = !this.patchVisibility.NpcVisible;\n                }\n                if (input.IsKeyDown(Keys.D6))\n                {\n                    this.patchVisibility.MobVisible = !this.patchVisibility.MobVisible;\n                }\n                if (input.IsKeyDown(Keys.D7))\n                {\n                    if (this.patchVisibility.FootHoldVisible)\n                    {\n                        this.patchVisibility.FootHoldVisible = false;\n                        this.patchVisibility.LadderRopeVisible = false;\n                    }\n                    else\n                    {\n                        this.patchVisibility.FootHoldVisible = true;\n                        this.patchVisibility.LadderRopeVisible = true;\n                    }\n                }\n                if (input.IsKeyDown(Keys.D8))\n                {\n                    if (!this.patchVisibility.PortalVisible)\n                    {\n                        this.patchVisibility.PortalVisible = true;\n                        this.patchVisibility.PortalInEditMode = false;\n                    }\n                    else if (!this.patchVisibility.PortalInEditMode)\n                    {\n                        this.patchVisibility.PortalInEditMode = true;\n                    }\n                    else\n                    {\n                        this.patchVisibility.PortalVisible = false;\n                    }\n\n                    UpdatePortalVisibility();\n                }\n                if (input.IsKeyDown(Keys.D9))\n                {\n                    this.patchVisibility.FrontVisible = !this.patchVisibility.FrontVisible;\n                }\n            }\n        }\n\n        private void UpdatePortalVisibility()\n        {\n            PortalPatch portal;\n            foreach (RenderPatch patch in this.renderingList)\n            {\n                portal = patch as PortalPatch;\n                if (portal != null)\n                {\n                    portal.EditMode = this.patchVisibility.PortalInEditMode;\n                }\n            }\n        }\n\n        private void UpdateCamera(GameTime gameTime)\n        {\n            if (!this.IsActive)\n            {\n                return;\n            }\n            InputState input = renderEnv.Input;\n\n            int boost = 1;\n            Vector2 offs = new Vector2();\n            if (input.IsKeyPressing(Keys.Left))\n                offs.X -= 5;\n            if (input.IsKeyPressing(Keys.Right))\n                offs.X += 5;\n            if (input.IsKeyPressing(Keys.Up))\n                offs.Y -= 5;\n            if (input.IsKeyPressing(Keys.Down))\n                offs.Y += 5;\n\n            if (input.IsMouseButtonPressing(MouseButton.RightButton))\n            {\n                offs.X = 5f * input.MousePosition.X / this.renderEnv.Camera.Width - 2.5f;\n                if (offs.X > 1.5f || offs.X < -1.5f)\n                {\n                    offs.X = offs.X - 1.5f * Math.Sign(offs.X);\n                }\n                else\n                {\n                    offs.X = 0;\n                }\n\n                offs.Y = 5f * input.MousePosition.Y / this.renderEnv.Camera.Height - 2.5f;\n                if (offs.Y > 1.5f || offs.Y < -1.5f)\n                {\n                    offs.Y = offs.Y - 1.5f * Math.Sign(offs.Y);\n                }\n                else\n                {\n                    offs.Y = 0;\n                }\n\n                offs *= 10;\n            }\n\n            if (input.IsCtrlPressing)\n                boost = 2;\n            this.renderEnv.Camera.Center += offs * boost;\n            if (this.cameraInMap)\n            {\n                this.renderEnv.Camera.AdjustToWorldRect();\n            }\n        }\n\n        private void UpdateBgmVolume()\n        {\n            float percent = this.bgmVolume;\n            if (this.isSilent)\n            {\n                percent = 0;\n            }\n            else\n            {\n                percent *= 0.01f * volumeGlobal;\n            }\n            percent = MathHelper.Clamp(percent, 0, 1);\n            Bass.BASS_ChannelSetAttribute(this.bgmStream.ToInt32(), BASSAttribute.BASS_ATTRIB_VOL, percent);\n        }\n\n        private void UpdatePatch(GameTime gameTime)\n        {\n            bool movingOnce = false;\n            tooltip.TooltipTarget = null;\n            foreach (RenderPatch patch in this.renderingList)\n            {\n                patch.Update(gameTime, renderEnv);\n\n                bool mouseover = patch.RenderArgs.DisplayRectangle.Contains(renderEnv.Camera.CameraToWorld(renderEnv.Input.MousePosition));\n                if (mouseover)\n                {\n                    tooltip.TooltipTarget = patch;\n                }\n\n                if (this.IsActive && !movingOnce && patch.ObjectType == RenderObjectType.Portal)\n                {\n                    PortalPatch portal = patch as PortalPatch;\n\n                    if (mouseover && this.renderEnv.Input.IsMouseButtonDown(MouseButton.LeftButton))\n                    {\n                        if (portal.ToMap == this.mapID)\n                        {\n                            PortalPatch portal2;\n                            foreach (RenderPatch patch2 in this.renderingList)\n                            {\n                                if (patch.ObjectType == RenderObjectType.Portal\n                                    && (portal2 = patch2 as PortalPatch) != null\n                                   && portal2.PortalName == portal.ToName)\n                                {\n                                    this.renderEnv.Camera.Center = portal2.Position;\n                                    movingOnce = true;\n                                    break;\n                                }\n                            }\n                        }\n                        else\n                        {\n                            if (PreLoadNextMap(portal.ToMap))\n                            {\n                                this.enterPortal = portal.ToName;\n                                this.loadState = LoadState.Exiting;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        private bool PreLoadNextMap(int mapID)\n        {\n            Wz_Image newMapImg = FindMapByID(mapID);\n            if (newMapImg == null)\n                return false;\n            this.mapImg = newMapImg;\n            string newBgm = newMapImg.Node.FindNodeByPath(\"info\\\\bgm\").GetValueEx<string>(null);\n            this.willReloadBgm = (newBgm != this.bgmName);\n            return true;\n        }\n\n        private void DisposePatch()\n        {\n            //foreach (RenderPatch patch in this.renderingList)\n            //{\n            //    patch.Dispose();\n            //}\n            this.renderingList.Clear();\n        }\n\n        protected override void Draw(GameTime gameTime)\n        {\n            this.fpsCounter.Draw(gameTime);\n            Color bgColor = Color.Black;\n\n            if (prepareCapture)\n            {\n                //检查显卡支持纹理大小\n                var maxTextureWidth = 4096;\n                var maxTextureHeight = 4096;\n\n                Rectangle oldRect = this.renderEnv.Camera.WorldRect;\n                int width = Math.Min(oldRect.Width, maxTextureWidth);\n                int height = Math.Min(oldRect.Height, maxTextureHeight);\n\n                var target2d = new RenderTarget2D(this.GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.Depth24);\n\n                //计算一组截图区\n                int horizonBlock = (int)Math.Ceiling(1.0 * oldRect.Width / width);\n                int verticalBlock = (int)Math.Ceiling(1.0 * oldRect.Height / height);\n                Color[,][] picBlocks = new Color[horizonBlock, verticalBlock][];\n                for (int j = 0; j < verticalBlock; j++)\n                {\n                    for (int i = 0; i < horizonBlock; i++)\n                    {\n                        //计算镜头区域\n                        this.renderEnv.Camera.WorldRect = new Rectangle(\n                            oldRect.X + i * width,\n                            oldRect.Y + j * height,\n                            width,\n                            height);\n                        //绘制\n                        GraphicsDevice.SetRenderTarget(target2d);\n                        GraphicsDevice.Clear(bgColor);\n                        this.renderEnv.Sprite.Begin(SpriteSortMode.Deferred, StateEx.NonPremultipled_Hidef());\n                        //this.SetRenderState();\n                        foreach (RenderPatch patch in this.renderingList)\n                        {\n                            if (this.patchVisibility.IsVisible(patch.ObjectType)\n                                && patch.RenderArgs.Visible)\n                            {\n                                patch.Draw(gameTime, renderEnv);\n                            }\n                        }\n                        this.renderEnv.Sprite.End();\n                        GraphicsDevice.SetRenderTarget(null);\n                        //保存\n                        Texture2D t2d = target2d;\n                        Color[] data = new Color[target2d.Width * target2d.Height];\n                        t2d.GetData<Color>(data);\n                        picBlocks[i, j] = data;\n                    }\n                }\n\n                target2d.Dispose();\n                SaveTexture(picBlocks, oldRect.Width, oldRect.Height, target2d.Width, target2d.Height);\n\n                //这帧就过去吧 阿门...\n                GraphicsDevice.Clear(bgColor);\n                this.renderEnv.Camera.WorldRect = oldRect;\n                this.renderEnv.Camera.UseWorldRect = false;\n                prepareCapture = false;\n            }\n            else\n            {\n                PresentationParameters pp = GraphicsDevice.PresentationParameters;\n                if (this.target2D == null\n                    || this.target2D.Width != pp.BackBufferWidth\n                    || this.target2D.Height != pp.BackBufferHeight)\n                {\n                    if (this.target2D != null)\n                    {\n                        this.target2D.Dispose();\n                    }\n                    this.target2D = new RenderTarget2D(this.GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, false , SurfaceFormat.Color, DepthFormat.Depth24);\n                }\n                GraphicsDevice.SetRenderTarget(this.target2D);\n                GraphicsDevice.Clear(bgColor);\n\n                switch (this.loadState)\n                {\n                    case LoadState.Entering:\n                    case LoadState.Rendering:\n                    case LoadState.Exiting:\n                        this.renderEnv.Sprite.Begin(SpriteSortMode.Deferred, StateEx.NonPremultipled_Hidef());\n                        //this.SetRenderState();\n                        foreach (RenderPatch patch in this.renderingList)\n                        {\n                            if (this.patchVisibility.IsVisible(patch.ObjectType)\n                                && patch.RenderArgs.Visible && !patch.RenderArgs.Culled)\n                            {\n                                patch.Draw(gameTime, renderEnv);\n                                tooltip.DrawNameTooltip(gameTime, renderEnv, patch, stringLinker);\n                            }\n                        }\n                        //绘制tooltip\n                        tooltip.DrawTooltip(gameTime, renderEnv, stringLinker);\n                        this.renderEnv.Sprite.End();\n\n                        //渲染UI\n                        if (this.uiMinimap.Visible)\n                        {\n                            this.uiMinimap.Position = new Vector2(0, this.showProfile ? 16 : 0);\n                            this.uiMinimap.Draw(this.renderEnv, gameTime);\n                        }\n                        break;\n                }\n\n                GraphicsDevice.SetRenderTarget(null);\n                GraphicsDevice.Clear(bgColor);\n                float alpha;\n                switch (this.loadState)\n                {\n                    case LoadState.Entering:\n                        {\n                            double time = (gameTime.TotalGameTime - stateChangedTime).TotalMilliseconds;\n                            alpha = MathHelper.Clamp((float)(time / EnteringTime), 0, 1);\n                        }\n                        break;\n                    case LoadState.Exiting:\n                        {\n                            double time = (gameTime.TotalGameTime - stateChangedTime).TotalMilliseconds;\n                            alpha = MathHelper.Clamp((float)(1 - time / ExitingTime), 0, 1);\n                        }\n                        break;\n                    case LoadState.Rendering: alpha = 1f; break;\n                    default: alpha = 0f; break;\n                }\n\n                this.renderEnv.Sprite.Begin(SpriteSortMode.Deferred, StateEx.NonPremultipled_Hidef());\n                //this.SetRenderState();\n                this.renderEnv.Sprite.Draw(this.target2D,\n                    new Rectangle(0, 0, this.target2D.Width, this.target2D.Height),\n                    new Color(Color.White, alpha));\n                this.renderEnv.Sprite.End();\n\n                this.renderEnv.Sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);\n                if (showProfile)\n                {\n                    profile.Remove(0, profile.Length);\n                    profile.Append(\"[F5-Hide] \");\n                    //显示地图名字\n                    if (!this.uiMinimap.Visible)\n                    {\n                        profile.Append(\"[\").Append(this.mapID).Append(\" \");\n                        StringResult sr;\n                        if (this.stringLinker != null && this.stringLinker.StringMap.TryGetValue(this.mapID, out sr))\n                            profile.Append(sr.Name);\n                        else\n                            profile.Append(\"(null)\");\n                        profile.Append(\"] \");\n                    }\n                    //显示bgm名字\n                    profile.Append(\"[\");\n                    profile.Append(bgmName);\n                    profile.AppendFormat(\" {0}%{1}\", volumeGlobal, this.silentOnDeactive ? \"A\" : null);\n                    profile.Append(\"] \");\n                    //显示当前渲染状态机\n                    profile.AppendFormat(\"[{0:p2} {1}] \", alpha, this.loadState);\n                    profile.AppendFormat(\"[fps u:{0:f2} d:{1:f2}] \", fpsCounter.UpdatePerSec, fpsCounter.DrawPerSec);\n\n                    //可见性：\n                    profile.Append(\" ctrl+\");\n\n                    int[] array = new[] { 1, 2, 3, 4, 5, 6, 7, 9, 10 };\n                    for (int i = 0; i < array.Length; i++)\n                    {\n                        RenderObjectType type = (RenderObjectType)array[i];\n                        profile.Append(this.patchVisibility.IsVisible(type) ? \"-\" : (i + 1).ToString());\n                    }\n                    this.renderEnv.Sprite.FillRectangle(new Rectangle(0, 0, renderEnv.Camera.Width, 16), new Color(Color.Black, 0.4f));\n                    this.renderEnv.Sprite.DrawStringEx(this.renderEnv.Fonts.DefaultFont, profile, Vector2.Zero, Color.Cyan);\n                }\n                this.renderEnv.Sprite.End();\n\n                //绘制文本框\n                this.renderEnv.Sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);\n                if (this.isOnCommand)\n                {\n                    Rectangle rect = new Rectangle(0, this.renderEnv.Camera.Height - 16, renderEnv.Camera.Width, 16);\n                    this.renderEnv.Sprite.FillRectangle(rect, new Color(Color.Black, 0.8f));\n                    this.renderEnv.Sprite.DrawRectangle(rect, Color.Gray);\n                    this.renderEnv.Sprite.DrawStringEx(this.renderEnv.Fonts.DefaultFont, txtInput, new Vector2(rect.Left + 2, rect.Top + 2), Color.White);\n                }\n                this.renderEnv.Sprite.End();\n\n                //绘制聊天栏\n\n                if (this.txtChat.Length > 0)\n                {\n                    Rectangle rect = new Rectangle(0, this.renderEnv.Camera.Height - 16 - 150, renderEnv.Camera.Width / 2, 150);\n                    XnaFont font = this.renderEnv.Fonts.DefaultFont;\n                    Vector2 size = font.MeasureString(this.txtChat);\n                    Vector2 origin = Vector2.Zero;\n                    if (size.Y > rect.Height)\n                    {\n                        origin = new Vector2(0, rect.Height - size.Y);\n                    }\n                    this.GraphicsDevice.ScissorRectangle = rect;\n                    this.GraphicsDevice.RasterizerState = StateEx.Scissor();\n                    this.renderEnv.Sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);\n                    this.renderEnv.Sprite.FillRectangle(rect, new Color(Color.Black, this.isOnCommand ? 0.8f : 0.5f));\n                    this.renderEnv.Sprite.DrawRectangle(rect, Color.Gray);\n                    this.renderEnv.Sprite.DrawStringEx(font, txtChat, new Vector2(rect.X + origin.X, rect.Y + origin.Y), Color.White);\n                    this.renderEnv.Sprite.End();\n\n                    this.GraphicsDevice.ScissorRectangle = Rectangle.Empty;\n                    this.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;\n                }\n\n            }\n            base.Draw(gameTime);\n        }\n\n        private void SaveTexture(Color[,][] picBlocks, int mapWidth, int mapHeight, int blockWidth, int blockHeight)\n        {\n            this.snapshotSaving = true;\n\n            new Thread(() =>\n            {\n\n                //透明处理\n                foreach (Color[] data in picBlocks)\n                {\n                    for (int i = 0, j = data.Length; i < j; i++)\n                    {\n                        if (data[i].A < 255)\n                        {\n                            //data[i].R = (byte)((data[i].R * data[i].A + bgColor.R * (255 - data[i].A)) / 255);\n                            //data[i].G = (byte)((data[i].G * data[i].A + bgColor.G * (255 - data[i].A)) / 255);\n                            //data[i].B = (byte)((data[i].B * data[i].A + bgColor.B * (255 - data[i].A)) / 255);\n                            data[i].A = 255;\n                        }\n                    }\n                }\n\n                //组装\n                byte[] mapData = new byte[mapWidth * mapHeight * 4];\n                for (int j = 0; j < picBlocks.GetLength(1); j++)\n                {\n                    for (int i = 0; i < picBlocks.GetLength(0); i++)\n                    {\n                        Color[] data = picBlocks[i, j];\n                        IntPtr pData = Marshal.UnsafeAddrOfPinnedArrayElement(data, 0);\n\n                        Rectangle blockRect = new Rectangle();\n                        blockRect.X = i * blockWidth;\n                        blockRect.Y = j * blockHeight;\n                        blockRect.Width = Math.Min(mapWidth - blockRect.X, blockWidth);\n                        blockRect.Height = Math.Min(mapHeight - blockRect.Y, blockHeight);\n\n                        int length = blockRect.Width * 4;\n                        if (blockRect.X == 0 && blockRect.Width == mapWidth) //整块复制\n                        {\n                            int startIndex = mapWidth * 4 * blockRect.Y;\n                            Marshal.Copy(pData, mapData, startIndex, blockRect.Width * blockRect.Height * 4);\n                        }\n                        else //逐行扫描\n                        {\n                            for (int y = blockRect.Top, y0 = blockRect.Bottom; y < y0; y++)\n                            {\n                                int startIndex = (y * mapWidth + blockRect.X) * 4;\n                                Marshal.Copy(pData, mapData, startIndex, length);\n                                pData = new IntPtr(pData.ToInt32() + blockWidth * 4);\n                            }\n                        }\n                    }\n                }\n\n                //反色\n                MonogameUtils.BgraToColor(mapData);\n\n                try\n                {\n                    System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(\n                        mapWidth,\n                        mapHeight,\n                        mapWidth * 4,\n                        System.Drawing.Imaging.PixelFormat.Format32bppArgb,\n                        System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(mapData, 0));\n\n                    bitmap.Save(DateTime.Now.ToString(\"yyyyMMddHHmmssfff\") + \"_\" + mapID.ToString(\"D9\") + \".png\",\n                        System.Drawing.Imaging.ImageFormat.Png);\n\n                    bitmap.Dispose();\n                }\n                catch\n                {\n                }\n                finally\n                {\n                    this.snapshotSaving = false;\n                }\n            }).Start();\n        }\n\n        private void SaveAllTexture()\n        {\n            DirectoryInfo dir = Directory.CreateDirectory(DateTime.Now.ToString(\"yyyyMMddHHmmssfff\"));\n            foreach (RenderPatch patch in this.renderingList)\n            {\n                string path = Path.Combine(dir.FullName, patch.Name + \".png\");\n                if (patch.Frames != null)\n                {\n                    var texture = patch.Frames[0].Texture;\n                    using (var file = File.OpenWrite(path))\n                    {\n                        texture.SaveAsPng(file);\n                    }\n                }\n            }\n        }\n\n        private void LoadMap()\n        {\n            System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();\n            this.texLoader.BeginCounting();\n            try\n            {\n                this.LoadInfo();\n                this.LoadBack();\n                this.LoadObjTile();\n                this.LoadFoothold();\n                this.LoadLife();\n                this.LoadPortal();\n                this.LoadReactor();\n                this.LoadTooltip();\n                this.LoadLadderRope();\n                this.LoadMinimap();\n                this.CalcMapSize();\n                this.renderingList.Sort(new Comparison<RenderPatch>(RenderPatchComarison));\n                this.loadState = LoadState.LoadSuccessed;\n            }\n            catch (Exception ex)\n            {\n                this.loadState = LoadState.LoadFailed;\n                System.Windows.Forms.MessageBox.Show(ex.ToString(), \"MapRender\");\n            }\n            this.texLoader.EndCounting();\n            this.texLoader.ClearUnusedTexture();\n            sw.Stop();\n            double ms = sw.Elapsed.TotalMilliseconds;\n\n            StringResult sr;\n            if (this.stringLinker != null && this.stringLinker.StringMap.TryGetValue(this.mapID, out sr))\n            {\n                this.Window.Title = this.mapID + \" \" + sr.Name;\n            }\n            else\n            {\n                this.Window.Title = this.mapID + \" (null)\";\n            }\n        }\n\n        private int RenderPatchComarison(RenderPatch a, RenderPatch b)\n        {\n            for (int i = 0; i < a.ZIndex.Length; i++)\n            {\n                int dz = a.ZIndex[i].CompareTo(b.ZIndex[i]);\n                if (dz != 0)\n                    return dz;\n            }\n            return ((int)a.ObjectType).CompareTo((int)b.ObjectType);\n        }\n\n        private void LoadInfo()\n        {\n            this.renderEnv.Camera.WorldRect = new Rectangle();\n            this.mapID = -1;\n\n            if (mapImg == null || !mapImg.TryExtract())\n                return;\n\n            int mapID;\n            if (mapImg.Name.Length >= 9\n                && Int32.TryParse(mapImg.Name.Substring(0, 9), out mapID))\n            {\n                this.mapID = mapID;\n            }\n\n            Wz_Node info = mapImg.Node.FindNodeByPath(\"info\");\n            if (info != null)\n            {\n                Wz_Node left = info.FindNodeByPath(\"VRLeft\"),\n                    top = info.FindNodeByPath(\"VRTop\"),\n                    right = info.FindNodeByPath(\"VRRight\"),\n                    bottom = info.FindNodeByPath(\"VRBottom\"),\n                    bgm = info.FindNodeByPath(\"bgm\"),\n                    link = info.FindNodeByPath(\"link\"),\n                    mapMark = info.FindNodeByPath(\"mapMark\");\n\n                // load mapSize\n                if (!(left == null || top == null || right == null || bottom == null))\n                {\n                    int l = left.GetValue<int>();\n                    int t = top.GetValue<int>();\n                    int r = right.GetValue<int>();\n                    int b = bottom.GetValue<int>();\n                    this.renderEnv.Camera.WorldRect = new Rectangle(l, t, r - l, b - t);\n                }\n\n                // load sound\n                if (bgm != null)\n                {\n                    string bgmPath = bgm.GetValueEx<string>(null);\n                    if (bgmPath != this.bgmName)\n                    {\n                        this.bgmName = bgmPath;\n                        LoadBgm();\n                    }\n                }\n\n                // load minimap\n                this.mapMark = mapMark.GetValueEx<string>(null);\n\n\n                // load link\n                int _link = link.GetValueEx<int>(-1);\n                if (_link > -1)\n                {\n                    Wz_Image linkMapImg = FindMapByID(_link);\n                    if (linkMapImg != null)\n                    {\n                        this.mapImg = linkMapImg;\n                    }\n                }\n            }\n\n            /* 暂时不用小地图方式来判定了\n            Wz_Node minimap = mapImg.Node.FindChildByPath(\"miniMap\");\n            if (minimap != null && this.renderEnv.Camera.WorldRect.IsEmpty)\n            {\n                Wz_Node width = minimap.FindChildByPath(\"width\"),\n                  height = minimap.FindChildByPath(\"height\"),\n                  centerX = minimap.FindChildByPath(\"centerX\"),\n                  centerY = minimap.FindChildByPath(\"centerY\");\n                if (width != null && height != null && centerX != null && centerY != null)\n                {\n                    int w = width.GetValue<int>();\n                    int h = height.GetValue<int>();\n                    int ox = centerX.GetValue<int>();\n                    int oy = centerY.GetValue<int>();\n                    this.renderEnv.Camera.WorldRect = new Rectangle(-ox + 50, -oy + 50, w - 100, h - 100);\n                    return;\n                }\n            }\n            */\n        }\n\n        private Wz_Image FindMapByID(int mapID)\n        {\n            string fullPath = string.Format(@\"Map\\Map\\Map{0}\\{1:D9}.img\", (mapID / 100000000), mapID);\n            Wz_Node mapImgNode = PluginManager.FindWz(fullPath);\n            Wz_Image mapImg;\n            if (mapImgNode != null\n                && (mapImg = mapImgNode.GetValueEx<Wz_Image>(null)) != null\n                && mapImg.TryExtract())\n            {\n                return mapImg;\n            }\n            return null;\n        }\n\n        private void LoadBgm()\n        {\n            Wz_Node soundWz = PluginManager.FindWz(Wz_Type.Sound);\n\n            this.bgmFile = null;\n            this.bgmStream = IntPtr.Zero;\n\n            if (soundWz == null || this.bgmName == null)\n                return;\n\n            string[] path = this.bgmName.Split('/');\n            if (path.Length <= 1)\n                return;\n            path[0] += \".img\";\n\n            Wz_Node soundNode = soundWz.FindNodeByPath(true, true, path);\n            Wz_Sound _sound = soundNode.GetValueEx<Wz_Sound>(null);\n            if (_sound != null)\n            {\n                byte[] newBgmFile = _sound.ExtractSound();\n                if (newBgmFile != null)\n                {\n                    int newBgmStream = Bass.BASS_StreamCreateFile(\n                        Marshal.UnsafeAddrOfPinnedArrayElement(newBgmFile, 0),\n                        0,\n                        newBgmFile.LongLength,\n                        BASSFlag.BASS_DEFAULT\n                        );\n\n                    if (newBgmStream != 0)\n                    {\n                        this.bgmFile = newBgmFile;\n                        this.bgmStream = new IntPtr(newBgmStream);\n                        Bass.BASS_ChannelFlags(newBgmStream, BASSFlag.BASS_SAMPLE_LOOP, BASSFlag.BASS_SAMPLE_LOOP);\n                    }\n                }\n            }\n        }\n\n        private void LoadMinimap()\n        {\n            if (this.uiMinimap != null && !this.uiMinimap.ResourceLoaded)\n            {\n                this.uiMinimap.LoadResource(this.GraphicsDevice);\n            }\n\n            MiniMap miniMap = new MiniMap();\n\n            Wz_Node miniMapNode = mapImg.Node.FindNodeByPath(\"miniMap\");\n\n            //读取小地图标记\n            if (mapMark != null)\n            {\n                Wz_Node markNode = PluginManager.FindWz(\"Map\\\\MapHelper.img\\\\mark\\\\\" + mapMark);\n                Wz_Png markImage = markNode.GetValueEx<Wz_Png>(null);\n                if (markImage != null)\n                {\n                    miniMap.MapMark = TextureLoader.PngToTexture(this.GraphicsDevice, markImage);\n                }\n            }\n\n            //读取小地图\n            if (miniMapNode != null)\n            {\n                Wz_Node canvas = miniMapNode.FindNodeByPath(\"canvas\"),\n                    width = miniMapNode.FindNodeByPath(\"width\"),\n                    height = miniMapNode.FindNodeByPath(\"height\"),\n                    centerX = miniMapNode.FindNodeByPath(\"centerX\"),\n                    centerY = miniMapNode.FindNodeByPath(\"centerY\"),\n                    mag = miniMapNode.FindNodeByPath(\"mag\");\n\n                Wz_Png _canvas = canvas.GetValueEx<Wz_Png>(null);\n                if (_canvas != null)\n                {\n                    miniMap.Canvas = TextureLoader.PngToTexture(this.GraphicsDevice, _canvas);\n                }\n                miniMap.Width = width.GetValueEx<int>(0);\n                miniMap.Height = height.GetValueEx<int>(0);\n                miniMap.CenterX = centerX.GetValueEx<int>(0);\n                miniMap.CenterY = centerY.GetValueEx<int>(0);\n                miniMap.Mag = mag.GetValueEx<int>(0);\n            }\n            else\n            {\n                this.miniMap = null;\n                return;\n            }\n\n            //读取传送门\n            this.uiMinimap.Portals.Clear();\n            this.uiMinimap.Transports.Clear();\n            foreach (var patch in this.renderingList)\n            {\n                if (patch.ObjectType == RenderObjectType.Portal)\n                {\n                    PortalPatch portal = patch as PortalPatch;\n                    switch (portal.PortalType)\n                    {\n                        case 2: //一般传送门\n                        case 7: //指令传送门\n                            this.uiMinimap.Portals.Add(portal.Position);\n                            break;\n\n                        case 10: //地图内传送\n                            this.uiMinimap.Transports.Add(portal.Position);\n                            break;\n                    }\n                    \n                }\n            }\n            this.miniMap = miniMap;\n        }\n\n        private void LoadObjTile()\n        {\n            Dictionary<string, RenderFrame> loadedObjRes = new Dictionary<string, RenderFrame>();\n            Dictionary<string, RenderFrame> loadedTileRes = new Dictionary<string, RenderFrame>();\n            Dictionary<string, RenderFrame[]> loadedFrames = new Dictionary<string, RenderFrame[]>();\n\n            for (int layer = 0; ; layer++)\n            {\n                Wz_Node objTileNode = mapImg.Node.FindNodeByPath(layer.ToString());\n                if (objTileNode == null)\n                {\n                    break;\n                }\n\n                Wz_Node objLstNode = objTileNode.FindNodeByPath(\"obj\");\n                if (objLstNode != null && objLstNode.Nodes.Count > 0)\n                {\n                    string[] path = new string[5];\n                    int loadIndex = 0;\n                    foreach (Wz_Node node in objLstNode.Nodes)\n                    {\n                        Wz_Node oS = node.FindNodeByPath(\"oS\"),\n                            l0 = node.FindNodeByPath(\"l0\"),\n                            l1 = node.FindNodeByPath(\"l1\"),\n                            l2 = node.FindNodeByPath(\"l2\"),\n                            x = node.FindNodeByPath(\"x\"),\n                            y = node.FindNodeByPath(\"y\"),\n                            z = node.FindNodeByPath(\"z\"),\n                            f = node.FindNodeByPath(\"f\"),\n                            zM = node.FindNodeByPath(\"zM\"),\n                            tags = node.FindNodeByPath(\"tags\");\n                        \n                        if (oS != null && l0 != null && l1 != null && l2 != null)\n                        {\n                            path[0] = \"Obj\";\n                            path[1] = oS.GetValue<string>() + \".img\";\n                            path[2] = l0.GetValue<string>();\n                            path[3] = l1.GetValue<string>();\n                            path[4] = l2.GetValue<string>();\n                            string key = string.Join(\"\\\\\", path);\n\n                            RenderFrame[] frames;\n                            Wz_Node objResNode = PluginManager.FindWz(\"Map\\\\\" + key);\n                            if (objResNode == null)\n                                continue;\n\n                            if (!loadedFrames.TryGetValue(key, out frames))\n                            {\n                                frames = LoadFrames(objResNode, loadedObjRes);\n                                loadedFrames[key] = frames;\n                            }\n\n                            RenderPatch patch = new ObjTilePatch();\n                            patch.ObjectType = RenderObjectType.Obj;\n                            patch.Position = new Vector2(x.GetValueEx<int>(0), y.GetValueEx<int>(0));\n                            patch.Flip = f.GetValueEx<int>(0) != 0;\n                            patch.ZIndex[0] = (int)RenderObjectType.Obj;\n                            patch.ZIndex[1] = layer;\n                            patch.ZIndex[2] = (int)patch.ObjectType;\n                            patch.ZIndex[3] = z.GetValueEx<int>(0);\n                            patch.ZIndex[4] = loadIndex;\n                            patch.ZIndex[5] = zM.GetValueEx<int>(0); //not use for sort\n                            patch.Frames = new RenderAnimate(frames);\n                            patch.Frames.Repeat = objResNode.FindNodeByPath(\"repeat\").GetValueEx<int>(0);\n\n                            patch.Name = string.Format(\"obj_{0}_{1}\", layer, node.Text);\n                            //patch.RenderArgs.Visible = string.IsNullOrEmpty(tags.GetValueEx<string>(null));\n                            this.renderingList.Add(patch);\n                        }\n                        loadIndex++;\n                    }\n                }\n\n                Wz_Node tS = objTileNode.FindNodeByPath(\"info\\\\tS\");\n                string _tS = tS.GetValueEx<string>(null);\n\n                Wz_Node tileLstNode = objTileNode.FindNodeByPath(\"tile\");\n                if (tileLstNode != null)\n                {\n                    string[] path = new string[4];\n                    int loadIndex = 0;\n                    foreach (Wz_Node node in tileLstNode.Nodes)\n                    {\n                        Wz_Node x = node.FindNodeByPath(\"x\"),\n                            y = node.FindNodeByPath(\"y\"),\n                            u = node.FindNodeByPath(\"u\"),\n                            no = node.FindNodeByPath(\"no\"),\n                            zM = node.FindNodeByPath(\"zM\");\n                        if (u != null && no != null)\n                        {\n                            path[0] = \"Tile\";\n                            path[1] = _tS + \".img\";\n                            path[2] = u.GetValue<string>();\n                            path[3] = no.GetValue<string>();\n\n                            string key = string.Join(\"\\\\\", path);\n\n                            RenderFrame[] frames;\n                            if (!loadedFrames.TryGetValue(key, out frames))\n                            {\n                                Wz_Node objResNode = PluginManager.FindWz(\"Map\\\\\" + key);\n                                if (objResNode == null)\n                                    continue;\n                                frames = LoadFrames(objResNode, loadedObjRes);\n                                loadedFrames[key] = frames;\n                            }\n\n                            RenderPatch patch = new ObjTilePatch();\n                            patch.ObjectType = RenderObjectType.Tile;\n                            patch.Position = new Vector2(x.GetValueEx<int>(0), y.GetValueEx<int>(0));\n                            patch.ZIndex[0] = (int)RenderObjectType.Obj;\n                            patch.ZIndex[1] = layer;\n                            patch.ZIndex[2] = (int)patch.ObjectType;\n                            patch.ZIndex[3] = frames[0].Z;\n                            patch.ZIndex[4] = loadIndex;\n                            patch.ZIndex[5] = zM.GetValueEx<int>(0); //not use for order\n                            patch.Frames = new RenderAnimate(frames);\n\n                            patch.Name = string.Format(\"tile_{0}_{1}\", layer, node.Text);\n                            this.renderingList.Add(patch);\n                        }\n                        loadIndex++;\n                    }\n                }\n            }\n        }\n\n        private void LoadBack()\n        {\n            Dictionary<string, RenderFrame> loadedBackRes = new Dictionary<string, RenderFrame>();\n            Dictionary<string, RenderFrame[]> loadedFrames = new Dictionary<string, RenderFrame[]>();\n\n            Wz_Node backLstNode = mapImg.Node.FindNodeByPath(\"back\");\n            if (backLstNode != null)\n            {\n                string[] path = new string[4];\n                int loadIndex = 0;\n                foreach (Wz_Node node in backLstNode.Nodes)\n                {\n                    Wz_Node x = node.FindNodeByPath(\"x\"),\n                        y = node.FindNodeByPath(\"y\"),\n                        bs = node.FindNodeByPath(\"bS\"),\n                        ani = node.FindNodeByPath(\"ani\"),\n                        no = node.FindNodeByPath(\"no\"),\n                        f = node.FindNodeByPath(\"f\"),\n                        front = node.FindNodeByPath(\"front\"),\n                        type = node.FindNodeByPath(\"type\"),\n                        cx = node.FindNodeByPath(\"cx\"),\n                        cy = node.FindNodeByPath(\"cy\"),\n                        rx = node.FindNodeByPath(\"rx\"),\n                        ry = node.FindNodeByPath(\"ry\"),\n                        a = node.FindNodeByPath(\"a\"),\n                        screenMode = node.FindNodeByPath(\"screenMode\");\n\n                    if (bs != null && no != null)\n                    {\n                        int _ani = ani.GetValueEx<int>(0);\n                        \n                        int _type = type.GetValueEx<int>(0);\n\n                        path[0] = \"Back\";\n                        path[1] = bs.GetValue<string>() + \".img\";\n                        switch (_ani)\n                        {\n                            case 0: path[2] = \"back\"; break;\n                            case 1: path[2] = \"ani\"; break;\n                            case 2: path[2] = \"spine\"; break;\n                        }\n                        path[3] = no.GetValue<string>();\n\n                        string key = string.Join(\"\\\\\", path);\n\n                        RenderFrame[] frames;\n                        if (!loadedFrames.TryGetValue(key, out frames))\n                        {\n                            Wz_Node objResNode = PluginManager.FindWz(\"Map\\\\\" + key);\n                            if (objResNode == null)\n                                continue;\n                            frames = LoadFrames(objResNode, loadedBackRes);\n                            loadedFrames[key] = frames;\n                        }\n\n                        BackPatch patch = new BackPatch();\n                        patch.ObjectType = front.GetValueEx<int>(0) != 0 ? RenderObjectType.Front : RenderObjectType.Back;\n                        patch.Position = new Vector2(x.GetValueEx<int>(0), y.GetValueEx<int>(0));\n                        patch.Cx = cx.GetValueEx<int>(0);\n                        patch.Cy = cy.GetValueEx<int>(0);\n                        patch.Rx = rx.GetValueEx<int>(0);\n                        patch.Ry = ry.GetValueEx<int>(0);\n                        patch.Frames = new RenderAnimate(frames);\n                        patch.Flip = f.GetValueEx<int>(0) != 0;\n                        patch.TileMode = GetBackTileMode(_type);\n                        patch.Alpha = a.GetValueEx<int>(255);\n                        patch.ScreenMode = screenMode.GetValueEx<int>(0);\n\n                        patch.ZIndex[0] = (int)patch.ObjectType;\n                        Int32.TryParse(node.Text, out patch.ZIndex[1]);\n\n                        patch.Name = string.Format(\"back_{0}\", node.Text);\n                        this.renderingList.Add(patch);\n                    }\n                    loadIndex++;\n                }\n            }\n        }\n\n        private void LoadFoothold()\n        {\n            Wz_Node fhListNode = mapImg.Node.FindNodeByPath(\"foothold\");\n            if (fhListNode != null)\n            {\n                int _layer, _z, _fh;\n                foreach (Wz_Node layerNode in fhListNode.Nodes)\n                {\n                    Int32.TryParse(layerNode.Text, out _layer);\n                    foreach (Wz_Node zNode in layerNode.Nodes)\n                    {\n                        Int32.TryParse(zNode.Text, out _z);\n                        foreach (Wz_Node fhNode in zNode.Nodes)\n                        {\n                            Int32.TryParse(fhNode.Text, out _fh);\n\n                            Wz_Node x1 = fhNode.FindNodeByPath(\"x1\"),\n                                x2 = fhNode.FindNodeByPath(\"x2\"),\n                                y1 = fhNode.FindNodeByPath(\"y1\"),\n                                y2 = fhNode.FindNodeByPath(\"y2\"),\n                                prev = fhNode.FindNodeByPath(\"prev\"),\n                                next = fhNode.FindNodeByPath(\"next\"),\n                                piece = fhNode.FindNodeByPath(\"piece\");\n\n                            FootholdPatch patch = new FootholdPatch();\n                            patch.ObjectType = RenderObjectType.Foothold;\n\n                            patch.X1 = x1.GetValueEx<int>(0);\n                            patch.X2 = x2.GetValueEx<int>(0);\n                            patch.Y1 = y1.GetValueEx<int>(0);\n                            patch.Y2 = y2.GetValueEx<int>(0);\n                            patch.Prev = prev.GetValueEx<int>(0);\n                            patch.Next = next.GetValueEx<int>(0);\n                            patch.Piece = piece.GetValueEx<int>(0);\n\n                            patch.ZIndex[0] = (int)patch.ObjectType;\n                            patch.ZIndex[1] = _layer;\n                            patch.ZIndex[2] = _z;\n                            patch.ZIndex[3] = _fh;\n\n                            patch.Name = string.Format(\"foothold_{0}\", fhNode.Text);\n                            this.renderingList.Add(patch);\n                        }\n                    }\n                }\n            }\n        }\n\n        private void LoadLadderRope()\n        {\n            Wz_Node ropeListNode = mapImg.Node.FindNodeByPath(\"ladderRope\");\n            if (ropeListNode != null)\n            {\n                int index;\n                foreach (Wz_Node ropeNode in ropeListNode.Nodes)\n                {\n                    Int32.TryParse(ropeNode.Text, out index);\n\n                    Wz_Node x = ropeNode.FindNodeByPath(\"x\"),\n                        y1 = ropeNode.FindNodeByPath(\"y1\"),\n                        y2 = ropeNode.FindNodeByPath(\"y2\"),\n                        l = ropeNode.FindNodeByPath(\"l\"),\n                        uf = ropeNode.FindNodeByPath(\"uf\"),\n                        page = ropeNode.FindNodeByPath(\"page\");\n\n                    LadderRopePatch patch = new LadderRopePatch();\n                    patch.ObjectType = RenderObjectType.LadderRope;\n                    patch.X = x.GetValueEx<int>(0);\n                    patch.Y1 = y1.GetValueEx<int>(0);\n                    patch.Y2 = y2.GetValueEx<int>(0);\n                    patch.L = l.GetValueEx<int>(0);\n                    patch.Uf = uf.GetValueEx<int>(0);\n                    patch.Page = page.GetValueEx<int>(0);\n\n                    patch.ZIndex[0] = (int)patch.ObjectType;\n                    patch.ZIndex[1] = 0;\n                    patch.ZIndex[2] = 0;\n                    patch.ZIndex[3] = index;\n\n                    patch.Name = string.Format(\"ladderRope_{0}\", ropeNode.Text);\n                    this.renderingList.Add(patch);\n                }\n            }\n        }\n\n        private void LoadLife()\n        {\n            Dictionary<int, Dictionary<string, RenderFrame[]>> loadedMob = new Dictionary<int, Dictionary<string, RenderFrame[]>>();\n            Dictionary<int, Dictionary<string, RenderFrame[]>> loadedNpc = new Dictionary<int, Dictionary<string, RenderFrame[]>>();\n\n            Wz_Node lifeLstNode = mapImg.Node.FindNodeByPath(\"life\");\n            if (lifeLstNode != null)\n            {\n                string[] path = new string[1];\n                int loadIndex = 0;\n                foreach (Wz_Node node in lifeLstNode.Nodes)\n                {\n                    Wz_Node type = node.FindNodeByPath(\"type\"),\n                        id = node.FindNodeByPath(\"id\"),\n                        x = node.FindNodeByPath(\"x\"),\n                        cy = node.FindNodeByPath(\"cy\"),\n                        f = node.FindNodeByPath(\"f\"),\n                        fh = node.FindNodeByPath(\"fh\"),\n                        hide = node.FindNodeByPath(\"hide\");\n\n                    int _id = id.GetValueEx<int>(-1);\n                    if (type != null && _id > -1)\n                    {\n                        LifePatch patch = new LifePatch();\n                        patch.Position = new Vector2(x.GetValueEx<int>(0), cy.GetValueEx<int>(0));\n                        patch.Flip = f.GetValueEx<int>(0) != 0;\n                        patch.RenderArgs.Visible = hide.GetValueEx<int>(0) == 0;\n                        patch.LifeID = _id;\n                        patch.Foothold = fh.GetValueEx<int>(0);\n\n                        path[0] = string.Format(\"{0:D7}.img\", _id);\n                        Dictionary<int, Dictionary<string, RenderFrame[]>> loadedLife;\n                        string lifeWz;\n\n                        switch (type.GetValueEx<string>(null))\n                        {\n                            case \"m\":\n                                loadedLife = loadedMob;\n                                lifeWz = \"Mob\\\\\";\n                                patch.ObjectType = RenderObjectType.Mob;\n                                break;\n\n                            case \"n\":\n                                loadedLife = loadedNpc;\n                                lifeWz = \"Npc\\\\\";\n                                patch.ObjectType = RenderObjectType.Npc;\n                                break;\n\n                            default:\n                                continue;\n                        }\n\n                        if (lifeWz == null)\n                            continue;\n\n                        // load info\n                        Wz_Node lifeImgNode = PluginManager.FindWz(lifeWz+ path[0]);\n                        if (lifeImgNode == null)\n                            continue;\n\n                        LoadLifeInfo(lifeImgNode.FindNodeByPath(\"info\"), patch.LifeInfo);\n\n                        // load actions\n                        Dictionary<string, RenderFrame[]> actions;\n                        if (!loadedLife.TryGetValue(_id, out actions))\n                        {\n                            Wz_Node link = lifeImgNode.FindNodeByPath(\"info\\\\link\");\n                            int _link = link.GetValueEx<int>(-1);\n\n                            if (_link >= 0)\n                            {\n                                if (!loadedLife.TryGetValue(_link, out actions))\n                                {\n                                    Wz_Node linkImgNode = PluginManager.FindWz(lifeWz + string.Format(\"{0:D7}.img\", _link));\n                                    if (linkImgNode == null)\n                                        continue;\n                                    actions = LoadLifeActions(linkImgNode);\n                                    loadedLife[_link] = actions;\n                                }\n                            }\n                            else\n                            {\n                                actions = LoadLifeActions(lifeImgNode);\n                                loadedLife[_id] = actions;\n                            }\n                        }\n\n                        foreach (var kv in actions)\n                        {\n                            patch.Actions.Add(kv.Key, new RenderAnimate(kv.Value));\n                        }\n\n                        patch.SwitchToDefaultAction();\n                        patch.Name = string.Format(\"life_{0}\", node.Text);\n\n                        //查找已读取的foothold计算zindex\n                        bool find = false;\n                        foreach (RenderPatch fhPatch in renderingList)\n                        {\n                            if (fhPatch.ObjectType == RenderObjectType.Foothold\n                                && fhPatch.ZIndex[3] == patch.Foothold)\n                            {\n                                patch.ZIndex[0] = (int)RenderObjectType.Obj;\n                                patch.ZIndex[1] = fhPatch.ZIndex[1];\n                                patch.ZIndex[2] = (int)patch.ObjectType;\n                                find = true;\n                                break;\n                            }\n                        }\n                        if (!find)\n                        {\n                            patch.ZIndex[0] = (int)patch.ObjectType;\n                        }\n                        patch.ZIndex[4] = loadIndex;\n                        this.renderingList.Add(patch);\n                    }\n\n                    loadIndex++;\n                }\n            }\n        }\n\n        private void LoadLifeInfo(Wz_Node infoNode, LifeInfo lifeInfo)\n        {\n            if (infoNode == null || lifeInfo == null)\n                return;\n            foreach (Wz_Node node in infoNode.Nodes)\n            {\n                switch (node.Text)\n                {\n                    case \"level\": lifeInfo.level = node.GetValueEx<int>(0); break;\n                    case \"maxHP\": lifeInfo.maxHP = node.GetValueEx<int>(0); break;\n                    case \"maxMP\": lifeInfo.maxMP = node.GetValueEx<int>(0); break;\n                    case \"speed\": lifeInfo.speed = node.GetValueEx<int>(0); break;\n                    case \"PADamage\": lifeInfo.PADamage = node.GetValueEx<int>(0); break;\n                    case \"PDDamage\": lifeInfo.PDDamage = node.GetValueEx<int>(0); break;\n                    case \"PDRate\": lifeInfo.PDRate = node.GetValueEx<int>(0); break;\n                    case \"MADamage\": lifeInfo.MADamage = node.GetValueEx<int>(0); break;\n                    case \"MDDamage\": lifeInfo.MDDamage = node.GetValueEx<int>(0); break;\n                    case \"MDRate\": lifeInfo.MDRate = node.GetValueEx<int>(0); break;\n                    case \"acc\": lifeInfo.acc = node.GetValueEx<int>(0); break;\n                    case \"eva\": lifeInfo.eva = node.GetValueEx<int>(0); break;\n                    case \"pushed\": lifeInfo.pushed = node.GetValueEx<int>(0); break;\n                    case \"exp\": lifeInfo.exp = node.GetValueEx<int>(0); break;\n                    case \"undead\": lifeInfo.undead = node.GetValueEx<int>(0) != 0; break;\n                    case \"boss\": lifeInfo.boss = node.GetValueEx<int>(0) != 0; break;\n                    case \"elemAttr\":\n                        string elem = node.GetValueEx<string>(string.Empty);\n                        for (int i = 0; i < elem.Length; i += 2)\n                        {\n                            LifeInfo.ElemResistance resist = (LifeInfo.ElemResistance)(elem[i + 1] - 48);\n                            switch (elem[i])\n                            {\n                                case 'I': lifeInfo.elemAttr.I = resist; break;\n                                case 'L': lifeInfo.elemAttr.L = resist; break;\n                                case 'F': lifeInfo.elemAttr.F = resist; break;\n                                case 'S': lifeInfo.elemAttr.S = resist; break;\n                                case 'H': lifeInfo.elemAttr.H = resist; break;\n                                case 'D': lifeInfo.elemAttr.D = resist; break;\n                                case 'P': lifeInfo.elemAttr.P = resist; break;\n                            }\n                        }\n                        break;\n                }\n            }\n        }\n\n        private void LoadPortal()\n        {\n            Dictionary<string, RenderFrame> loadedRes = new Dictionary<string, RenderFrame>();\n            Dictionary<string, RenderFrame[]> loadedFrames = new Dictionary<string, RenderFrame[]>();\n\n            Wz_Node portalImageNode = PluginManager.FindWz(\"Map\\\\MapHelper.img\\\\portal\");\n\n            if (portalImageNode == null)\n                return;\n\n            Wz_Node editorNode = portalImageNode.FindNodeByPath(\"editor\");\n\n            if (editorNode == null)\n                return;\n\n            string[] ptList = new [] { \"sp\", \"pi\", \"pv\", \"pc\", \"pg\", \"tp\", \"ps\", \"pgi\", \"psi\", \"pcs\", \"ph\", \"psh\", \"pcj\", \"pci\", \"pcig\", \"pshg\" };\n\n            Wz_Node portalNode = mapImg.Node.FindNodeByPath(\"portal\");\n            if (portalNode != null)\n            {\n                string[] path = new string[4];\n\n                foreach (Wz_Node node in portalNode.Nodes)\n                {\n                    Wz_Node pn = node.FindNodeByPath(\"pn\"),\n                       pt = node.FindNodeByPath(\"pt\"),\n                       x = node.FindNodeByPath(\"x\"),\n                       y = node.FindNodeByPath(\"y\"),\n                       tm = node.FindNodeByPath(\"tm\"),\n                       tn = node.FindNodeByPath(\"tn\"),\n                       script = node.FindNodeByPath(\"script\"),\n                       image = node.FindNodeByPath(\"image\");\n\n                    if (pt != null)\n                    {\n                        PortalPatch patch = new PortalPatch();\n                        patch.ObjectType = RenderObjectType.Portal;\n                        patch.PortalName = pn.GetValueEx<string>(null);\n                        patch.ToMap = tm.GetValueEx<int>(0);\n                        patch.ToName = tn.GetValueEx<string>(null);\n                        patch.Script = script.GetValueEx<string>(null);\n                        patch.Position = new Vector2(x.GetValueEx<int>(0), y.GetValueEx<int>(0));\n                        int _pt = pt.GetValueEx<int>(-1);\n                        patch.PortalType = _pt;\n                        if (_pt < 0 || _pt >= ptList.Length)\n                            continue;\n\n                        //读取aniEditor\n                        path[0] = \"editor\";\n                        path[1] = ptList[_pt];\n                        string key = string.Join(\"\\\\\", path, 0, 2);\n\n                        RenderFrame[] frames;\n                        Wz_Node resNode;\n                        if (!loadedFrames.TryGetValue(key, out frames))\n                        {\n                            resNode = portalImageNode.FindNodeByPath(false, path[0], path[1]);\n                            if (resNode == null)\n                                continue;\n                            frames = LoadFrames(resNode, loadedRes);\n                            loadedFrames[key] = frames;\n                        }\n                        patch.AniEditor = new RenderAnimate(frames);\n\n                        //读取aniPortal\n                        switch (_pt)\n                        {\n                            case 7: _pt = 2; break;\n                        }\n\n                        path[0] = \"game\";\n                        path[1] = ptList[_pt];\n\n                        resNode = portalImageNode.FindNodeByPath(false, path[0], path[1]);\n                        if (resNode != null)\n                        {\n                            if (resNode.FindNodeByPath(\"default\") != null) //寻找image对应的节点  否则本身作为根节点\n                            {\n                                int _image = image.GetValueEx<int>(0);\n                                if (_image == 0)\n                                    path[2] = \"default\";\n                                else\n                                    path[2] = _image.ToString();\n\n                                if ((resNode = resNode.FindNodeByPath(path[2])) == null)\n                                    continue;\n\n                            }\n                            string[] animeNames = new string[] { \"portalStart\", \"portalContinue\", \"portalExit\" };\n                            bool existsAnimes = false;\n                            foreach (string animeName in animeNames)\n                            {\n                                if (resNode.FindNodeByPath(animeName) != null)\n                                {\n                                    existsAnimes = true;\n                                    break;\n                                }\n                            }\n\n                            if (existsAnimes) //三段式读取动画\n                            {\n                                RenderFrame[][] animeFrames = new RenderFrame[animeNames.Length][];\n                                for (int i = 0; i < animeNames.Length; i++)\n                                {\n                                    path[3] = animeNames[i];\n                                    key = string.Join(\"\\\\\", path);\n\n                                    if (!loadedFrames.TryGetValue(key, out frames))\n                                    {\n                                        Wz_Node aniNode = resNode.FindNodeByPath(animeNames[i]);\n                                        if (aniNode == null)\n                                            continue;\n                                        frames = LoadFrames(aniNode, loadedRes);\n                                        loadedFrames[key] = frames;\n                                    }\n\n                                    animeFrames[i] = frames;\n                                }\n                                patch.AniStart = new RenderAnimate(animeFrames[0]);\n                                patch.AniContinue = new RenderAnimate(animeFrames[1]);\n                                patch.AniExit = new RenderAnimate(animeFrames[2]);\n                            }\n                            else //只读取动画作为continue\n                            {\n                                key = string.Join(\"\\\\\", path, 0, 2);\n                                if (!loadedFrames.TryGetValue(key, out frames))\n                                {\n                                    frames = LoadFrames(resNode, loadedRes);\n                                    loadedFrames[key] = frames;\n                                }\n                                patch.AniContinue = new RenderAnimate(frames);\n                            }\n                        }\n\n\n                        patch.ZIndex[0] = (int)patch.ObjectType;\n                        Int32.TryParse(node.Text, out patch.ZIndex[3]);\n\n                        patch.Name = string.Format(\"portal_{0}\", node.Text);\n                        this.renderingList.Add(patch);\n                    }\n                }\n            }\n        }\n\n        private void LoadReactor()\n        {\n            Wz_Node reactorWz = PluginManager.FindWz(Wz_Type.Reactor);\n            Dictionary<int, List<RenderFrame[]>> loadedFrames = new Dictionary<int, List<RenderFrame[]>>();\n\n            if (reactorWz == null)\n                return;\n\n            Wz_Node reactorNode = mapImg.Node.FindNodeByPath(\"reactor\");\n            if (reactorNode != null)\n            {\n                string[] path = new string[1];\n\n                foreach (Wz_Node node in reactorNode.Nodes)\n                {\n                    Wz_Node id = node.FindNodeByPath(\"id\"),\n                       x = node.FindNodeByPath(\"x\"),\n                       y = node.FindNodeByPath(\"y\"),\n                       f = node.FindNodeByPath(\"f\"),\n                       reactorTime = node.FindNodeByPath(\"reactorTime\"),\n                       name = node.FindNodeByPath(\"name\");\n\n                    int _id = id.GetValueEx<int>(-1);\n                    if (_id > -1)\n                    {\n                        ReactorPatch patch = new ReactorPatch();\n                        patch.ReactorID = _id;\n                        patch.Position = new Vector2(x.GetValueEx<int>(0), y.GetValueEx<int>(0));\n                        patch.ReactorName = name.GetValueEx<string>(null);\n                        patch.Flip = (f.GetValueEx<int>(0) != 0);\n                        patch.ObjectType = RenderObjectType.Reactor;\n\n                        path[0] = string.Format(\"{0:D7}.img\", _id);\n\n                        Wz_Node reactorImgNode = reactorWz.FindNodeByPath(path[0], true);\n                        if (reactorImgNode == null)\n                            continue;\n\n                        List<RenderFrame[]> stages;\n                        if (!loadedFrames.TryGetValue(_id, out stages))\n                        {\n                            Wz_Node link = reactorImgNode.FindNodeByPath(\"info\\\\link\");\n                            int _link = link.GetValueEx<int>(-1);\n\n                            if (_link >= 0)\n                            {\n                                if (!loadedFrames.TryGetValue(_link, out stages))\n                                {\n                                    Wz_Node linkImgNode = reactorWz.FindNodeByPath(string.Format(\"{0:D7}.img\", _link), true);\n                                    if (linkImgNode == null)\n                                        continue;\n                                    stages = LoadReactorStages(linkImgNode);\n                                    loadedFrames[_link] = stages;\n                                }\n                            }\n                            else\n                            {\n                                stages = LoadReactorStages(reactorImgNode);\n                                loadedFrames[_link] = stages;\n                            }\n                        }\n\n                        foreach (var stage in stages)\n                        {\n                            patch.Stages.Add(new RenderAnimate(stage));\n                        }\n\n                        int _time = reactorTime.GetValueEx<int>(0);\n                        if (_time < 0)\n                            _time = 0;\n                        if (_time < patch.Stages.Count)\n                            patch.Frames = patch.Stages[_time];\n\n                        patch.ZIndex[0] = (int)patch.ObjectType;\n                        Int32.TryParse(node.Text, out patch.ZIndex[3]);\n                        patch.Name = string.Format(\"reactor_{0}\", node.Text);\n\n                        this.renderingList.Add(patch);\n                    }\n                }\n            }\n        }\n\n        private void LoadTooltip()\n        {\n            Wz_Node tooltipNode = mapImg.Node.FindNodeByPath(\"ToolTip\");\n        }\n\n        private void CalcMapSize()\n        {\n            if (this.renderEnv.Camera.WorldRect.IsEmpty)\n            {\n                Rectangle worldRect = Rectangle.Empty;\n                foreach (RenderPatch patch in this.renderingList)\n                {\n                    Rectangle patchRect = Rectangle.Empty;\n                    switch (patch.ObjectType)\n                    {\n                        case RenderObjectType.Foothold:\n                            FootholdPatch fh = patch as FootholdPatch;\n                            patchRect = new Rectangle(fh.X1, fh.Y1, fh.X2 - fh.X1, fh.Y2 - fh.Y1);\n                            break;\n                        case RenderObjectType.LadderRope:\n                            LadderRopePatch rope = patch as LadderRopePatch;\n                            patchRect = new Rectangle(rope.X, rope.Y1, 1, rope.Y2 - rope.Y1);\n                            break;\n\n                        case RenderObjectType.Obj:\n                        case RenderObjectType.Tile:\n                            ObjTilePatch objTile = patch as ObjTilePatch;\n                            foreach (RenderFrame f in objTile.Frames)\n                            {\n                                if (f.Texture != null && !f.Texture.IsDisposed)\n                                {\n                                    Rectangle rect = this.renderEnv.Camera.MeasureDrawingRect(f.Texture.Width, f.Texture.Height, objTile.Position, f.Origin, objTile.Flip);\n\n                                    if (patchRect.IsEmpty)\n                                    {\n                                        patchRect = rect;\n                                    }\n                                    else\n                                    {\n                                        Rectangle.Union(ref patchRect, ref rect, out patchRect);\n                                    }\n                                }\n                            }\n                            break;\n                    }\n\n                    if (!patchRect.IsEmpty)\n                    {\n                        if (worldRect.IsEmpty)\n                        {\n                            worldRect = patchRect;\n                        }\n                        else\n                        {\n                            Rectangle.Union(ref worldRect, ref patchRect, out worldRect);\n                        }\n                    }\n                }\n                worldRect.Y -= 400;\n                worldRect.Height += 400;\n                this.renderEnv.Camera.WorldRect = worldRect;\n            }\n        }\n\n        private Dictionary<string, RenderFrame[]> LoadLifeActions(Wz_Node lifeNode)\n        {\n            Dictionary<string, RenderFrame[]> actions = new Dictionary<string, RenderFrame[]>();\n            Dictionary<string, RenderFrame> loadedRes = new Dictionary<string, RenderFrame>();\n\n            foreach (Wz_Node actionNode in lifeNode.Nodes)\n            {\n                if (actionNode.Text != \"info\")\n                {\n                    RenderFrame[] frames = LoadFrames(actionNode, loadedRes);\n                    if (frames.Length > 0)\n                    {\n                        actions[actionNode.Text] = frames;\n                    }\n                }\n            }\n            return actions;\n        }\n\n        private List<RenderFrame[]> LoadReactorStages(Wz_Node reactorNode)\n        {\n            List<RenderFrame[]> stages = new List<RenderFrame[]>();\n            Dictionary<string, RenderFrame> loadedRes = new Dictionary<string, RenderFrame>();\n\n            for (int i = 0; ; i++)\n            {\n                Wz_Node stageNode = reactorNode.FindNodeByPath(i.ToString());\n                if (stageNode == null)\n                    break;\n                stages.Add(LoadFrames(stageNode, loadedRes));\n            }\n            return stages;\n        }\n\n        private RenderFrame[] LoadFrames(Wz_Node resNode, Dictionary<string, RenderFrame> loadedRes)\n        {\n            if (resNode.Value is Wz_Png)\n            {\n                RenderFrame frame;\n                if (!loadedRes.TryGetValue(resNode.FullPath, out frame))\n                {\n                    frame = this.texLoader.CreateFrame(resNode);\n                    loadedRes[resNode.FullPath] = frame;\n                }\n                return new RenderFrame[1] { frame };\n            }\n\n            List<RenderFrame> frames = new List<RenderFrame>();\n            Wz_Uol uol;\n\n            for (int i = 0; ; i++)\n            {\n                Wz_Node frameNode = resNode.FindNodeByPath(i.ToString());\n                if (frameNode == null)\n                    break;\n                while ((uol = frameNode.Value as Wz_Uol) != null)\n                {\n                    frameNode = uol.HandleUol(frameNode);\n                }\n                RenderFrame frame;\n                if (!loadedRes.TryGetValue(frameNode.FullPath, out frame))\n                {\n                    frame = this.texLoader.CreateFrame(frameNode);\n                    loadedRes[frameNode.FullPath] = frame;\n                }\n                frames.Add(frame);\n            }\n\n            return frames.ToArray();\n        }\n\n        private TileMode GetBackTileMode(int type)\n        {\n            switch (type)\n            {\n                case 0: return TileMode.None;\n                case 1: return TileMode.Horizontal;\n                case 2: return TileMode.Vertical;\n                case 3: return TileMode.BothTile;\n                case 4: return TileMode.Horizontal | TileMode.ScrollHorizontial;\n                case 5: return TileMode.Vertical | TileMode.ScrollVertical;\n                case 6: return TileMode.BothTile | TileMode.ScrollHorizontial;\n                case 7: return TileMode.BothTile | TileMode.ScrollVertical;\n                default: return TileMode.None;\n            }\n        }\n\n        private enum LoadState\n        {\n            NotLoad,\n            Loading,\n            LoadSuccessed,\n            Entering,\n            Rendering,\n            Exiting,\n            LoadFailed\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/FrmMapRender2.SceneManager.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.Common;\nusing WzComparerR2.MapRender.Patches2;\nusing WzComparerR2.MapRender.UI;\nusing Microsoft.Xna.Framework;\nusing IE = System.Collections.IEnumerator;\n\nnamespace WzComparerR2.MapRender\n{\n    public partial class FrmMapRender2\n    {\n        enum SceneManagerState\n        {\n            Starting = 0,\n            Loading,\n            Entering,\n            Running,\n            Exiting,\n        }\n\n        #region private fields\n        MapViewData viewData;\n        LinkedList<MapViewData> viewHistory;\n        SceneManagerState sceneManagerState;\n        Wz_Image mapImgLoading;\n        #endregion\n\n        public void LoadMap(Wz_Image mapImg)\n        {\n            if (this.sceneManagerState == SceneManagerState.Starting || this.sceneManagerState == SceneManagerState.Running)\n            {\n                this.mapImgLoading = mapImg;\n            }\n        }\n\n        /*\n         * State machine transition table\n         *\n         * FromState |   condition  | ToState\n         * ----------|--------------|-----------\n         * Starting  | mapImg != null | Loading\n         * Starting  | mapImg == null | Running (empty scene)\n         * Loading   | mapData != null | Entering\n         * Loading   | mapData == null | Running (empty scene)\n         * Entering  | global_light >= 1.0 | Running\n         * Running   | viewData.toMap != null | Exiting\n         * Exiting  | viewData.toMap != null | Loading\n         * \n         */\n\n        private IE OnStart()\n        {\n            // initialize\n            this.viewHistory = new LinkedList<MapViewData>();\n            this.sceneManagerState = SceneManagerState.Starting;\n            // add view state\n            this.viewData = new MapViewData()\n            {\n                MapID = -1,\n                ToMapID = null,\n            };\n\n            if (this.mapImgLoading != null)\n            {\n                this.viewData.ToPortal = \"sp\";\n                yield return cm.Yield(OnMapLoading());\n            }\n            else\n            {\n                this.opacity = 1;\n                yield return cm.Yield(OnSceneRunning());\n            }\n        }\n\n        private IE OnMapLoading()\n        {\n            this.sceneManagerState = SceneManagerState.Loading;\n\n            var loadMapTask = this.LoadMap();\n            yield return new WaitTaskCompletedCoroutine(loadMapTask);\n            if (loadMapTask.Exception != null)\n            {\n                this.ui.ChatBox.AppendTextSystem($\"Failed to load map：{loadMapTask.Exception}\");\n                this.mapImgLoading = null;\n                this.opacity = 1;\n                yield return cm.Yield(OnSceneRunning());\n                yield break;\n            }\n\n            // backfill toMapID \n            if (this.viewData.ToMapID == null && this.mapData?.ID != null)\n            {\n                this.viewData.ToMapID = this.mapData.ID;\n            }\n\n            //记录历史\n            if (this.viewData.MapID > -1 && this.viewData.MapID != this.viewData.ToMapID && this.viewData.ToMapID != null)\n            {\n                if (this.viewData.IsMoveBack\n                    && this.viewData.ToMapID == this.viewHistory.Last?.Value?.MapID)\n                {\n                    var last = this.viewHistory.Last.Value;\n                    this.viewHistory.RemoveLast();\n                    var toViewData = new MapViewData()\n                    {\n                        MapID = last.MapID,\n                        Portal = last.Portal ?? \"sp\"\n                    };\n                    this.viewData = toViewData;\n                }\n                else\n                {\n                    viewHistory.AddLast(this.viewData);\n                    var toViewData = new MapViewData()\n                    {\n                        MapID = this.viewData.ToMapID.Value,\n                        Portal = this.viewData.ToPortal ?? \"sp\"\n                    };\n                    this.viewData = toViewData;\n                }\n            }\n            else\n            {\n                this.viewData.MapID = this.viewData.ToMapID ?? -1;\n                this.viewData.ToMapID = null;\n                this.viewData.Portal = this.viewData.ToPortal;\n                this.viewData.ToPortal = null;\n            }\n\n            this.viewData.IsMoveBack = false;\n            yield return cm.Yield(OnSceneEnter());\n        }\n\n        private async Task<bool> LoadMap()\n        {\n            if (this.mapImgLoading == null)\n            {\n                return false;\n            }\n\n            //开始加载\n            this.resLoader.ClearAnimationCache();\n            this.resLoader.BeginCounting();\n\n            //加载地图数据\n            var mapData = new MapData(this.Services.GetService<IRandom>());\n            mapData.Load(this.mapImgLoading.Node, resLoader);\n\n            //处理bgm\n            Music newBgm = LoadBgm(mapData);\n            Task bgmTask = null;\n            bool willSwitchBgm = this.mapData?.Bgm != mapData.Bgm;\n            if (willSwitchBgm && this.bgm != null) //准备切换\n            {\n                bgmTask = FadeOut(this.bgm, 1000);\n            }\n\n            //加载资源\n            mapData.PreloadResource(resLoader);\n\n            //准备UI和初始化\n            this.AfterLoadMap(mapData);\n\n            if (bgmTask != null)\n            {\n                await bgmTask;\n            }\n\n            //回收资源\n            this.resLoader.EndCounting();\n            this.resLoader.Recycle();\n\n            //准备场景和bgm\n            this.mapImg = this.mapImgLoading;\n            this.mapImgLoading = null;\n            this.mapData = mapData;\n            this.bgm = newBgm;\n            if (willSwitchBgm && this.bgm != null)\n            {\n                bgmTask = FadeIn(this.bgm, 1000);\n            }\n            return true;\n        }\n\n        private async Task FadeOut(Music music, int ms)\n        {\n            float vol = music.Volume;\n            for (int i = 0; i < ms; i += 30)\n            {\n                music.Volume = vol * (ms - i) / ms;\n                await Task.Delay(30);\n            }\n            music.Volume = 0f;\n            music.Stop();\n        }\n\n        private async Task FadeIn(Music music, int ms)\n        {\n            music.Play();\n            float vol = music.Volume;\n            for (int i = 0; i < ms; i += 30)\n            {\n                music.Volume = vol + (1 - vol) * i / ms;\n                await Task.Delay(30);\n            }\n            music.Volume = 1f;\n        }\n\n        private Music LoadBgm(MapData mapData, string multiBgmText = null)\n        {\n            if (!string.IsNullOrEmpty(mapData.Bgm))\n            {\n                var path = new List<string>() { \"Sound\" };\n                path.AddRange(mapData.Bgm.Split('/'));\n                path[1] += \".img\";\n                var bgmNode = PluginManager.FindWz(string.Join(\"\\\\\", path));\n                if (bgmNode != null)\n                {\n                    if (bgmNode.Value == null)\n                    {\n                        bgmNode = multiBgmText == null ? bgmNode.Nodes.FirstOrDefault(n => n.Value is Wz_Sound || n.Value is Wz_Uol) : bgmNode.Nodes[multiBgmText];\n                        if (bgmNode == null)\n                        {\n                            return null;\n                        }\n                    }\n\n                    while (bgmNode.Value is Wz_Uol uol)\n                    {\n                        bgmNode = uol.HandleUol(bgmNode);\n                    }\n                    var bgm = resLoader.Load<Music>(bgmNode);\n                    bgm.IsLoop = true;\n                    return bgm;\n                }\n            }\n            return null;\n        }\n\n        private void AfterLoadMap(MapData mapData)\n        {\n            //同步可视化状态\n            foreach (var portal in mapData.Scene.Portals)\n            {\n                portal.View.IsEditorMode = this.patchVisibility.PortalInEditMode;\n            }\n\n            //同步UI\n            this.renderEnv.Camera.WorldRect = mapData.VRect;\n\n            this.ui.MirrorFrame.Visibility = mapData.ID / 10000000 == 32 ? EmptyKeys.UserInterface.Visibility.Visible : EmptyKeys.UserInterface.Visibility.Collapsed;\n\n            this.ui.Minimap.Mirror = mapData.ID / 10000000 == 32;\n\n            StringResult sr;\n            if (mapData.ID != null && this.StringLinker != null\n                && StringLinker.StringMap.TryGetValue(mapData.ID.Value, out sr))\n            {\n                this.ui.Minimap.StreetName = sr[\"streetName\"];\n                this.ui.Minimap.MapName = sr[\"mapName\"];\n            }\n            else\n            {\n                this.ui.Minimap.StreetName = null;\n                this.ui.Minimap.MapName = null;\n            }\n\n            if (mapData.MiniMap.MapMark != null)\n            {\n                this.ui.Minimap.MapMark = engine.Renderer.CreateTexture(mapData.MiniMap.MapMark);\n            }\n            else\n            {\n                this.ui.Minimap.MapMark = null;\n            }\n\n            if (mapData.MiniMap.Canvas != null)\n            {\n                this.ui.Minimap.MinimapCanvas = engine.Renderer.CreateTexture(mapData.MiniMap.Canvas);\n            }\n            else\n            {\n                this.ui.Minimap.MinimapCanvas = null;\n            }\n\n            this.ui.Minimap.Icons.Clear();\n            foreach (var portal in mapData.Scene.Portals)\n            {\n                switch (portal.Type)\n                {\n                    case 2:\n                    case 7:\n                        object tooltip = portal.Tooltip;\n                        if (tooltip == null && portal.ToMap != null && portal.ToMap != 999999999\n                            && StringLinker.StringMap.TryGetValue(portal.ToMap.Value, out sr))\n                        {\n                            tooltip = sr[\"mapName\"];\n                        }\n                        this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                        {\n                            IconType = UIMinimap2.IconType.Portal,\n                            Tooltip = tooltip,\n                            WorldPosition = new EmptyKeys.UserInterface.PointF(portal.X, portal.Y)\n                        });\n                        break;\n\n                    case 8:\n                        if (portal.ShownAtMinimap)\n                        {\n                            this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                            {\n                                IconType = UIMinimap2.IconType.HiddenPortal,\n                                Tooltip = portal.Tooltip,\n                                WorldPosition = new EmptyKeys.UserInterface.PointF(portal.X, portal.Y)\n                            });\n                        }\n                        break;\n\n                    case 10:\n                        this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                        {\n                            IconType = portal.ToMap == mapData.ID ? UIMinimap2.IconType.ArrowUp : UIMinimap2.IconType.HiddenPortal,\n                            Tooltip = portal.Tooltip,\n                            WorldPosition = new EmptyKeys.UserInterface.PointF(portal.X, portal.Y)\n                        });\n                        break;\n\n                    case 11:\n                        if (portal.ShownAtMinimap)\n                        {\n                            this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                            {\n                                IconType = UIMinimap2.IconType.HiddenPortal,\n                                Tooltip = portal.Tooltip,\n                                WorldPosition = new EmptyKeys.UserInterface.PointF(portal.X, portal.Y)\n                            });\n                        }\n                        break;\n                }\n            }\n            foreach (var illuminantCluster in mapData.Scene.IlluminantClusters)\n            {\n                this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                {\n                    IconType = UIMinimap2.IconType.HiddenPortal,\n                    WorldPosition = new EmptyKeys.UserInterface.PointF(illuminantCluster.Start.X, illuminantCluster.Start.Y)\n                });\n            }\n\n            foreach (var npc in mapData.Scene.Npcs)\n            {\n                object tooltip = null;\n                var npcNode = PluginManager.FindWz(string.Format(\"Npc/{0:D7}.img/info\", npc.ID));\n                if ((npcNode?.Nodes[\"hide\"].GetValueEx(0) ?? 0) != 0 || (npcNode?.Nodes[\"hideName\"].GetValueEx(0) ?? 0) != 0)\n                {\n                    continue;\n                }\n                if (StringLinker.StringNpc.TryGetValue(npc.ID, out sr))\n                {\n                    if (sr.Desc != null)\n                    {\n                        tooltip = new KeyValuePair<string, string>(sr.Name, sr.Desc);\n                    }\n                    else\n                    {\n                        tooltip = sr.Name;\n                    }\n                }\n                if (npc.ID == 9010022)\n                {\n                    this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                    {\n                        IconType = UIMinimap2.IconType.Transport,\n                        Tooltip = tooltip,\n                        WorldPosition = new EmptyKeys.UserInterface.PointF(npc.X, npc.Y)\n                    });\n                }\n                else if ((npcNode?.Nodes[\"shop\"].GetValueEx(0) ?? 0) != 0)\n                {\n                    this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                    {\n                        IconType = UIMinimap2.IconType.Shop,\n                        Tooltip = tooltip,\n                        WorldPosition = new EmptyKeys.UserInterface.PointF(npc.X, npc.Y)\n                    });\n                }\n                else if (npc.ID / 10000 == 900 || npc.ID / 10000 == 901)\n                {\n                    this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                    {\n                        IconType = UIMinimap2.IconType.EventNpc,\n                        Tooltip = tooltip,\n                        WorldPosition = new EmptyKeys.UserInterface.PointF(npc.X, npc.Y)\n                    });\n                }\n                else if ((npcNode?.Nodes[\"trunkPut\"].GetValueEx(0) ?? 0) != 0)\n                {\n                    this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                    {\n                        IconType = UIMinimap2.IconType.Trunk,\n                        Tooltip = tooltip,\n                        WorldPosition = new EmptyKeys.UserInterface.PointF(npc.X, npc.Y)\n                    });\n                }\n                else\n                {\n                    this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                    {\n                        IconType = UIMinimap2.IconType.Npc,\n                        Tooltip = tooltip,\n                        WorldPosition = new EmptyKeys.UserInterface.PointF(npc.X, npc.Y)\n                    });\n                }\n            }\n            foreach (var mob in mapData.Scene.Mobs)\n            {\n                var mobNode = PluginManager.FindWz(string.Format(\"Mob/{0:D7}.img/info\", mob.ID));\n                if ((mobNode?.Nodes[\"minimap\"].GetValueEx(0) ?? 0) != 0)\n                {\n                    this.ui.Minimap.Icons.Add(new UIMinimap2.MapIcon()\n                    {\n                        IconType = UIMinimap2.IconType.Another,\n                        WorldPosition = new EmptyKeys.UserInterface.PointF(mob.X, mob.Y)\n                    });\n                }\n            }\n\n            if (mapData.MiniMap.Width > 0 && mapData.MiniMap.Height > 0)\n            {\n                this.ui.Minimap.MapRegion = new Rectangle(-mapData.MiniMap.CenterX, -mapData.MiniMap.CenterY, mapData.MiniMap.Width, mapData.MiniMap.Height).ToRect();\n            }\n            else\n            {\n                this.ui.Minimap.MapRegion = mapData.VRect.ToRect();\n            }\n\n            this.ui.WorldMap.CurrentMapID = mapData?.ID;\n        }\n\n        private IE OnSceneEnter()\n        {\n            this.sceneManagerState = SceneManagerState.Entering;\n\n            //初始化指向传送门\n            if (!string.IsNullOrEmpty(viewData.Portal))\n            {\n                var portal = this.mapData.Scene.FindPortal(viewData.Portal);\n                if (portal != null)\n                {\n                    this.renderEnv.Camera.Center = new Vector2(portal.X, portal.Y);\n                }\n                else\n                {\n                    this.renderEnv.Camera.Center = Vector2.Zero;\n                }\n                this.renderEnv.Camera.AdjustToWorldRect();\n            }\n            viewData.Portal = null;\n\n            //场景渐入\n            this.opacity = 0;\n            double time = 500;\n            for (double i = 0; i < time; i += cm.GameTime.ElapsedGameTime.TotalMilliseconds)\n            {\n                this.opacity = (float)(i / time);\n                SceneUpdate();\n                yield return null;\n            }\n            this.opacity = 1;\n            yield return cm.Yield(OnSceneRunning());\n        }\n\n        private IE OnSceneExit()\n        {\n            this.sceneManagerState = SceneManagerState.Exiting;\n\n            //场景渐出\n            this.opacity = 1;\n            double time = 500;\n            for (double i = 0; i < time; i += cm.GameTime.ElapsedGameTime.TotalMilliseconds)\n            {\n                this.opacity = 1f - (float)(i / time);\n                yield return null;\n            }\n            this.opacity = 0;\n            yield return null;\n            yield return cm.Yield(OnMapLoading());\n        }\n\n        private IE OnSceneRunning()\n        {\n            this.sceneManagerState = SceneManagerState.Running;\n            while (true)\n            {\n                SceneUpdate();\n                if (this.mapImgLoading != null)\n                {\n                    break;\n                }\n                yield return null;\n            }\n            yield return cm.Yield(OnSceneExit());\n        }\n\n        private IE OnCameraMoving(Point toPos, int ms)\n        {\n            Vector2 cameraFrom = this.renderEnv.Camera.Center;\n            Vector2 cameraTo = toPos.ToVector2();\n            for (double i = 0; i < ms; i += cm.GameTime.ElapsedGameTime.TotalMilliseconds)\n            {\n                var percent = (i / ms);\n                this.renderEnv.Camera.Center = Vector2.Lerp(cameraFrom, cameraTo, (float)Math.Sqrt(percent));\n                this.renderEnv.Camera.AdjustToWorldRect();\n                yield return null;\n            }\n            this.renderEnv.Camera.Center = cameraTo;\n            this.renderEnv.Camera.AdjustToWorldRect();\n        }\n\n        private void SceneUpdate()\n        {\n            var gameTime = cm.GameTime;\n            var mapData = this.mapData;\n\n            if (this.IsActive)\n            {\n                this.renderEnv.Input.Update(gameTime);\n                this.ui.UpdateInput(gameTime.ElapsedGameTime.TotalMilliseconds);\n            }\n\n            //需要手动更新数据部分\n            this.renderEnv.Camera.AdjustToWorldRect();\n            {\n                var rect = this.renderEnv.Camera.ClipRect;\n                this.ui.Minimap.CameraViewPort = new EmptyKeys.UserInterface.Rect(rect.X, rect.Y, rect.Width, rect.Height);\n            }\n            //更新topbar\n            UpdateTopBar();\n            //更新ui\n            this.ui.UpdateLayout(gameTime.ElapsedGameTime.TotalMilliseconds);\n            //更新场景\n            if (mapData != null)\n            {\n                UpdateAllItems(mapData.Scene, gameTime.ElapsedGameTime);\n            }\n            //更新tooltip\n            UpdateTooltip();\n        }\n\n        private void MoveToPortal(int? toMap, string pName, string fromPName = null, bool isBack = false)\n        {\n            if (toMap != null && toMap != this.mapData?.ID) //跳转地图\n            {\n                //寻找地图数据\n                Wz_Node node;\n                if (MapData.FindMapByID(toMap.Value, out node))\n                {\n                    Wz_Image img = node.GetNodeWzImage();\n                    if (img != null)\n                    {\n                        this.mapImgLoading = img;\n                        viewData.ToMapID = toMap;\n                        viewData.ToPortal = pName;\n                        viewData.Portal = fromPName;\n                        viewData.IsMoveBack = isBack;\n                    }\n                }\n                else\n                {\n                    this.ui.ChatBox.AppendTextSystem($\"没有找到ID:{toMap.Value}的地图。\");\n                }\n            }\n            else //当前地图\n            {\n                viewData.ToMapID = null;\n                viewData.ToPortal = null;\n\n                var portal = this.mapData.Scene.FindPortal(pName);\n                if (portal != null)\n                {\n                    this.cm.StartCoroutine(OnCameraMoving(new Point(portal.X, portal.Y), 500));\n                }\n            }\n        }\n\n        private void MoveToLastMap()\n        {\n            if (viewHistory.Count > 0)\n            {\n                var last = viewHistory.Last.Value;\n                if (last.MapID > -1)\n                {\n                    MoveToPortal(last.MapID, last.Portal, null, true);\n                }\n            }\n        }\n\n        class MapViewData\n        {\n            public int MapID { get; set; }\n            public string Portal { get; set; }\n            public int? ToMapID { get; set; }\n            public string ToPortal { get; set; }\n            public bool IsMoveBack { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/FrmMapRender2.SceneRendering.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\nusing WzComparerR2.Common;\nusing WzComparerR2.MapRender.UI;\nusing WzComparerR2.MapRender.Patches2;\nusing WzComparerR2.Animation;\n\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Controls;\nusing WzComparerR2.MapRender.Effects;\n\nnamespace WzComparerR2.MapRender\n{\n    public partial class FrmMapRender2\n    {\n        private void UpdateAllItems(SceneNode node, TimeSpan elapsed)\n        {\n            var container = node as ContainerNode;\n            if (container != null)  //暂时不考虑缩进z层递归合并  container下没有子节点\n            {\n                foreach (var item in container.Slots)\n                {\n                    if (item is BackItem)\n                    {\n                        var back = (BackItem)item;\n                        (back.View.Animator as WzComparerR2.Controls.AnimationItem)?.Update(elapsed);\n                        back.View.Time += (int)elapsed.TotalMilliseconds;\n                    }\n                    else if (item is ObjItem)\n                    {\n                        var _item = (ObjItem)item;\n                        ApplyMapEvents(_item.Events, _item.View.Animator, _item.SpineAni);\n                        (_item.View.Animator as WzComparerR2.Controls.AnimationItem)?.Update(elapsed);\n                        _item.View.Time += (int)elapsed.TotalMilliseconds;\n                    }\n                    else if (item is TileItem)\n                    {\n                        var tile = (TileItem)item;\n                        (tile.View.Animator as WzComparerR2.Controls.AnimationItem)?.Update(elapsed);\n                        tile.View.Time += (int)elapsed.TotalMilliseconds;\n                    }\n                    else if (item is LifeItem)\n                    {\n                        var life = (LifeItem)item;\n                        var smAni = (life.View.Animator as StateMachineAnimator);\n                        if (smAni != null)\n                        {\n                            if (smAni.GetCurrent() == null) //当前无动作\n                            {\n                                smAni.SetAnimation(smAni.Data.States[0]); //动作0\n                            }\n                            smAni.Update(elapsed);\n                        }\n\n                        life.View.Time += (int)elapsed.TotalMilliseconds;\n                    }\n                    else if (item is PortalItem)\n                    {\n                        var portal = (PortalItem)item;\n\n                        //更新状态\n                        var cursorPos = renderEnv.Camera.CameraToWorld(renderEnv.Input.MousePosition);\n                        var sensorRect = new Rectangle(portal.X - 250, portal.Y - 150, 500, 300);\n                        portal.View.IsFocusing = sensorRect.Contains(cursorPos);\n\n                        //更新动画\n                        var ani = portal.View.IsEditorMode ? portal.View.EditorAnimator : portal.View.Animator;\n                        if (ani is StateMachineAnimator)\n                        {\n                            if (portal.View.Controller != null)\n                            {\n                                portal.View.Controller.Update(elapsed);\n                            }\n                            else\n                            {\n                                ((StateMachineAnimator)ani).Update(elapsed);\n                            }\n                        }\n                        else if (ani is FrameAnimator)\n                        {\n                            var frameAni = (FrameAnimator)ani;\n                            frameAni.Update(elapsed);\n                        }\n                    }\n                    else if (item is IlluminantClusterItem)\n                    {\n                        var illuminantCluster = (IlluminantClusterItem)item;\n                        (illuminantCluster.StartView.Animator as WzComparerR2.Controls.AnimationItem)?.Update(elapsed);\n                        illuminantCluster.StartView.Time += (int)elapsed.TotalMilliseconds;\n                        (illuminantCluster.EndView.Animator as WzComparerR2.Controls.AnimationItem)?.Update(elapsed);\n                        illuminantCluster.EndView.Time += (int)elapsed.TotalMilliseconds;\n                    }\n                    else if (item is ReactorItem)\n                    {\n                        var reactor = (ReactorItem)item;\n                        var ani = reactor.View.Animator;\n                        if (ani is StateMachineAnimator)\n                        {\n                            if (reactor.View.Controller != null)\n                            {\n                                reactor.View.Controller.Update(elapsed);\n                            }\n                            else\n                            {\n                                ((StateMachineAnimator)ani).Update(elapsed);\n                            }\n                        }\n                    }\n                    else if (item is ParticleItem)\n                    {\n                        var particle = (ParticleItem)item;\n                        var pSystem = particle.View?.ParticleSystem;\n                        if (pSystem != null)\n                        {\n                            pSystem.Update(elapsed);\n                        }\n                    }\n                }\n            }\n            else\n            {\n                for (int i = 0, i1 = node.Nodes.Count; i < i1; i++)\n                {\n                    UpdateAllItems(node.Nodes[i], elapsed);\n                }\n            }\n        }\n\n        private void ApplyMapEvents(IEnumerable<ItemEvent> itemEvents, object animator, string defaultAniName)\n        {\n            if (itemEvents.Count() == 0 || animator is not ISpineAnimator)\n            {\n                return;\n            }\n            var cursorPos = renderEnv.Camera.CameraToWorld(renderEnv.Input.MousePosition);\n            var eventList = itemEvents.Select(ie =>\n                {\n                    return new\n                    {\n                        SlotName = ie.SlotName,\n                        Animation = ie.Animation,\n                        MapEvent = this.mapData.MapEvents.Where(me => ie.ActionKeys.Contains(me.Index)),\n                        Rect = (animator as ISpineAnimator).GetSlotBounds(ie.SlotName),\n                    };\n                }).Where(data => data.MapEvent != null || !string.IsNullOrEmpty(data.Animation));\n\n            foreach (var data in eventList)\n            {\n                var sensorRect = data.Rect;\n                if (sensorRect.Contains(cursorPos))\n                {\n                    if (!string.IsNullOrEmpty(data.Animation))\n                    {\n                        var tmpMapEvent = new MapEvent(null, \"SetAnimationOnceAndReturn\", defaultAniName, data.Animation, null);\n                        InvokeMapEvent(animator as ISpineAnimator, new List<MapEvent>() { tmpMapEvent });\n                    }\n                    else InvokeMapEvent(animator as ISpineAnimator, data.MapEvent.ToList());\n                }\n            }\n        }\n\n        private void InvokeMapEvent(ISpineAnimator sender, List<MapEvent> mapEvents)\n        {\n            if (sender != null)\n            {\n                foreach (var mapEvent in mapEvents)\n                {\n                    if (mapEvent == null) continue;\n\n                    List<ISpineAnimator> targets = mapEvent.Tags == null ? new List<ISpineAnimator>() { sender as ISpineAnimator } :\n                        GetSceneContainers(this.mapData?.Scene)\n                        .SelectMany(container => container.Slots)\n                        .Where(sceneItem => sceneItem.Tags != null && sceneItem.Tags.Contains(mapEvent.Tags))\n                        .Select(sceneItem => (ISpineAnimator)((sceneItem as ObjItem)?.View?.Animator) ?? null)\n                        .Where(spine => spine != null)\n                        .Distinct()\n                        .ToList();\n                    switch (mapEvent.Type)\n                    {\n                        case MapEventType.SetAnimationOnceAndReturn:\n                            foreach (var spine in targets)\n                            {\n                                if (spine.NextAnimationName.Count > 0)\n                                {\n                                    return;\n                                }\n                                else\n                                {\n                                    spine.SelectedAnimationName = mapEvent.ChangedAnimation;\n                                    spine.NextAnimationName.Enqueue(mapEvent.DefaultAnimation);\n                                }\n                            }\n                            break;\n                    }\n                }\n            }\n        }\n\n        private void UpdateTooltip()\n        {\n            var mouse = renderEnv.Input.MousePosition;\n\n            var mouseElem = EmptyKeys.UserInterface.Input.InputManager.Current.MouseDevice.MouseOverElement;\n            object target = null;\n            if (mouseElem == this.ui.ContentControl)\n            {\n                var mouseTarget = this.allItems.Reverse<ItemRect>().FirstOrDefault(item =>\n                {\n                    return item.rect.Contains(mouse) && (item.item is LifeItem || item.item is PortalItem || item.item is IlluminantClusterItem || item.item is ReactorItem);\n                });\n                target = mouseTarget.item;\n            }\n            else if (mouseElem is ITooltipTarget)\n            {\n                var pos = EmptyKeys.UserInterface.Input.InputManager.Current.MouseDevice.GetPosition(mouseElem);\n                target = ((ITooltipTarget)mouseElem).GetTooltipTarget(pos);\n            }\n            tooltip.TooltipTarget = target;\n        }\n\n        private void UpdateTopBar()\n        {\n            StringBuilder sb = new StringBuilder();\n            var topbar = this.ui.TopBar;\n\n            //显示地图名字\n            int? mapID = this.mapData?.ID;\n            sb.Append(\"[\").Append(mapID != null ? mapID.ToString() : mapImg?.Node.FullPathToFile);\n            if (!topbar.IsShortMode)\n            {\n                sb.Append(\" \");\n                StringResult sr;\n                if (this.StringLinker != null && mapID != null\n                    && this.StringLinker.StringMap.TryGetValue(mapID.Value, out sr))\n                {\n                    sb.Append(sr.Name);\n                }\n                else\n                {\n                    sb.Append(\"(null)\");\n                }\n            }\n            sb.Append(\"]\");\n\n            //显示bgm名字\n            sb.Append(\" [\").Append(this.mapData?.Bgm ?? \"(noBgm)\").Append(\"]\");\n\n            //显示fps\n            sb.AppendFormat(\" [fps u:{0:f2} d:{1:f2}]\", fpsCounter.UpdatePerSec, fpsCounter.DrawPerSec);\n\n            //可见性\n            sb.Append(\" ctrl+\");\n            int[] array = new[] { 1, 2, 3, 4, 5, 6, 7, 9, 10 };\n            for (int i = 0; i < array.Length; i++)\n            {\n                var objType = (Patches.RenderObjectType)array[i];\n                sb.Append(this.patchVisibility.IsVisible(objType) ? \"-\" : (i + 1).ToString());\n            }\n\n            sb.Append(\" Mouse:\");\n            var mouse = this.renderEnv.Input.MousePosition;\n            var mousePos = this.renderEnv.Camera.CameraToWorld(mouse);\n            sb.AppendFormat(\"{0},{1}\", mousePos.X, mousePos.Y);\n            this.ui.TopBar.Text = sb.ToString();\n        }\n\n        private void OnSceneItemClick(SceneItem item)\n        {\n            if (item is PortalItem)\n            {\n                var portal = (PortalItem)item;\n                if (portal.ToMap != 999999999)\n                {\n                    MoveToPortal(portal.ToMap, portal.ToName, portal.PName);\n                }\n                else if (portal.GraphTargetMap.Count == 1)\n                {\n                    MoveToPortal(portal.GraphTargetMap[0], \"sp\", portal.PName);\n                }\n                else if (portal.GraphTargetMap.Count > 1)\n                {\n                    this.ui.Teleport.Sl = this.StringLinker;\n                    this.ui.Teleport.CmbMaps.ItemsSource = portal.GraphTargetMap.ToList();\n                    this.ui.Teleport.CmbMaps.SelectedIndex = 0;\n                    this.ui.Teleport.Toggle();\n                }\n            }\n            else if (item is IlluminantClusterItem)\n            {\n                var illuminantCluster = (IlluminantClusterItem)item;\n                this.cm.StartCoroutine(OnCameraMoving(new Point(illuminantCluster.End.X, illuminantCluster.End.Y), 500));\n            }\n        }\n\n        private void DrawScene(GameTime gameTime)\n        {\n            if (this.mapData == null)\n            {\n                return;\n            }\n            RenderTarget2D lightMapTexture = null;\n\n            // draw lightmap if available\n            if (this.mapData?.Light != null)\n            {\n                var viewport = this.GraphicsDevice.Viewport;\n                lightMapTexture = new RenderTarget2D(this.GraphicsDevice, viewport.Width, viewport.Height, false, SurfaceFormat.Color, DepthFormat.None);\n                var oldRenderTarget = this.GraphicsDevice.GetRenderTargets();\n                this.GraphicsDevice.SetRenderTarget(lightMapTexture);\n                this.PrepareLightMap();\n                this.GraphicsDevice.SetRenderTargets(oldRenderTarget);\n            }\n\n            allItems.Clear();\n            var camera = this.renderEnv.Camera;\n            var origin = camera.Origin;\n            this.batcher.Begin(origin, (float)(gameTime.TotalGameTime.TotalSeconds % 1000));\n            Rectangle[] rects = null;\n            //绘制场景\n            foreach (var kv in GetDrawableItems(this.mapData.Scene))\n            {\n                this.batcher.Draw(kv.Value);\n\n                //绘制标签\n                DrawName(kv.Key);\n\n                //缓存绘图区域\n                {\n                    int rectCount;\n                    this.batcher.Measure(kv.Value, ref rects, out rectCount);\n                    if (kv.Value.RenderObject is Frame)\n                    {\n                        var frame = (Frame)kv.Value.RenderObject;\n                    }\n                    if (rects != null && rectCount > 0)\n                    {\n                        for (int i = 0; i < rectCount; i++)\n                        {\n                            rects[i].X -= (int)origin.X;\n                            rects[i].Y -= (int)origin.Y;\n                            allItems.Add(new ItemRect() { item = kv.Key, rect = rects[i] });\n                        }\n                    }\n                }\n\n                this.batcher.MeshPush(kv.Value);\n            }\n\n            //在场景之上绘制额外标记\n            DrawFootholds(gameTime);\n            this.batcher.End();\n\n            // apply light map\n            if (lightMapTexture != null)\n            {\n                this.ApplyLightMap(lightMapTexture);\n                lightMapTexture.Dispose();\n            }\n        }\n\n        private void DrawTooltipItems(GameTime gameTime)\n        {\n            var pos = renderEnv.Camera.CameraToWorld(renderEnv.Input.MousePosition);\n            var origin = renderEnv.Camera.Origin.ToPoint();\n            foreach (var item in mapData.Tooltips)\n            {\n                if (item.CharRect.Contains(pos) || item.Rect.Contains(pos))\n                {\n                    var center = new Vector2(item.Rect.Center.X - origin.X, item.Rect.Center.Y - origin.Y);\n                    tooltip.Draw(gameTime, renderEnv, item, center);\n                }\n            }\n        }\n\n        private void DrawFootholds(GameTime gameTime)\n        {\n            var color = MathHelper2.HSVtoColor((float)gameTime.TotalGameTime.TotalSeconds * 100 % 360, 1f, 1f);\n            if (patchVisibility.FootHoldVisible)\n            {\n                var lines = new List<Point>();\n                foreach (LayerNode layer in this.mapData.Scene.Layers.Nodes)\n                {\n                    var fhList = layer.Foothold.Nodes.OfType<ContainerNode<FootholdItem>>()\n                        .Select(container => container.Item);\n                    foreach (var fh in fhList)\n                    {\n                        lines.Add(new Point(fh.X1, fh.Y1));\n                        lines.Add(new Point(fh.X2, fh.Y2));\n                    }\n                }\n\n                if (lines.Count > 0)\n                {\n                    var meshItem = this.batcher.MeshPop();\n                    meshItem.RenderObject = new LineListMesh(lines.ToArray(), color, 2);\n                    this.batcher.Draw(meshItem);\n                    this.batcher.MeshPush(meshItem);\n                }\n            }\n\n            if (patchVisibility.LadderRopeVisible)\n            {\n                var lines = new List<Point>();\n                var ladderList = this.mapData.Scene.Fly.LadderRope.Slots.OfType<LadderRopeItem>();\n                foreach (var item in ladderList)\n                {\n                    lines.Add(new Point(item.X, item.Y1));\n                    lines.Add(new Point(item.X, item.Y2));\n                }\n\n                if (lines.Count > 0)\n                {\n                    var meshItem = this.batcher.MeshPop();\n                    meshItem.RenderObject = new LineListMesh(lines.ToArray(), color, 3);\n                    this.batcher.Draw(meshItem);\n                    this.batcher.MeshPush(meshItem);\n                }\n            }\n\n            if (patchVisibility.SkyWhaleVisible)\n            {\n                var lines = new List<Point>();\n                var skyWhaleList = this.mapData.Scene.Fly.SkyWhale.Slots.OfType<SkyWhaleItem>();\n                foreach (var item in skyWhaleList)\n                {\n                    foreach (var dx in new[] { 0, -item.Width / 2, item.Width / 2 })\n                    {\n                        Point start = new Point(item.Start.X + dx, item.Start.Y);\n                        Point end = new Point(item.End.X + dx, item.End.Y);\n                        //画箭头\n                        lines.Add(start);\n                        lines.Add(end);\n                        lines.Add(end);\n                        lines.Add(new Point(end.X - 5, end.Y + 8));\n                        lines.Add(end);\n                        lines.Add(new Point(end.X + 5, end.Y + 8));\n                    }\n                }\n\n                if (lines.Count > 0)\n                {\n                    var meshItem = this.batcher.MeshPop();\n                    meshItem.RenderObject = new LineListMesh(lines.ToArray(), color, 1);\n                    this.batcher.Draw(meshItem);\n                    this.batcher.MeshPush(meshItem);\n                }\n            }\n\n            if (patchVisibility.IlluminantClusterPathVisible)\n            {\n                var lines = new List<Point>();\n                var illuminentClusterList = this.mapData.Scene.Fly.IlluminantCluster.Slots.OfType<IlluminantClusterItem>();\n                foreach (var item in illuminentClusterList)\n                {\n                    foreach (var dx in new[] { 0, -item.Size / 2, item.Size / 2 })\n                    {\n                        Point start = new Point(item.Start.X + dx, item.Start.Y);\n                        Point end = new Point(item.End.X + dx, item.End.Y);\n                        //画箭头\n                        lines.Add(start);\n                        lines.Add(end);\n                        lines.Add(end);\n                        lines.Add(new Point(end.X - 5, end.Y + 8));\n                        lines.Add(end);\n                        lines.Add(new Point(end.X + 5, end.Y + 8));\n                    }\n                }\n\n                if (lines.Count > 0)\n                {\n                    var meshItem = this.batcher.MeshPop();\n                    meshItem.RenderObject = new LineListMesh(lines.ToArray(), color, 1);\n                    this.batcher.Draw(meshItem);\n                    this.batcher.MeshPush(meshItem);\n                }\n            }\n        }\n\n        private void DrawName(SceneItem item)\n        {\n            StringResult sr = null;\n            MeshItem mesh = null;\n\n            if (item is LifeItem)\n            {\n                var life = (LifeItem)item;\n                switch (life.Type)\n                {\n                    case LifeItem.LifeType.Mob:\n                        if (this.patchVisibility.MobNameVisible)\n                        {\n                            string lv = \"Lv.\" + (life.LifeInfo?.level ?? 0);\n                            string name;\n                            if (this.StringLinker?.StringMob.TryGetValue(life.ID, out sr) ?? false)\n                                name = sr.Name;\n                            else\n                                name = life.ID.ToString();\n\n                            //绘制怪物名称\n                            mesh = batcher.MeshPop();\n                            mesh.Position = new Vector2(life.X, life.Cy + 4);\n                            mesh.RenderObject = new TextMesh()\n                            {\n                                Align = Alignment.Center,\n                                ForeColor = Color.White,\n                                BackColor = new Color(Color.Black, 0.7f),\n                                Font = renderEnv.Fonts.MobNameFont,\n                                Padding = new Margins(2, 2, 2, 2),\n                                Text = name\n                            };\n                            batcher.Draw(mesh);\n\n                            //绘制怪物等级\n                            var nameRect = batcher.Measure(mesh)[0];\n                            mesh.Position = new Vector2(nameRect.X - 2, nameRect.Y + 3);\n                            mesh.RenderObject = new TextMesh()\n                            {\n                                Align = Alignment.Far,\n                                ForeColor = Color.White,\n                                BackColor = new Color(Color.Black, 0.7f),\n                                Font = renderEnv.Fonts.MobLevelFont,\n                                Padding = new Margins(2, 1, 1, 1),\n                                Text = lv\n                            };\n                            batcher.Draw(mesh);\n                            batcher.MeshPush(mesh);\n                        }\n                        break;\n\n                    case LifeItem.LifeType.Npc:\n                        if (this.patchVisibility.NpcNameVisible)\n                        {\n                            if (life.HideName) break;\n\n                            string name, desc;\n                            if (this.StringLinker?.StringNpc.TryGetValue(life.ID, out sr) ?? false)\n                            {\n                                name = sr.Name;\n                                desc = sr.Desc;\n                            }\n                            else\n                            {\n                                name = life.ID.ToString();\n                                desc = null;\n                            }\n\n                            if (name != null)\n                            {\n                                mesh = batcher.MeshPop();\n                                mesh.Position = new Vector2(life.X, life.Cy + 4);\n                                mesh.RenderObject = new TextMesh()\n                                {\n                                    Align = Alignment.Center,\n                                    ForeColor = Color.Yellow,\n                                    BackColor = new Color(Color.Black, 0.7f),\n                                    Font = renderEnv.Fonts.NpcNameFont,\n                                    Padding = new Margins(2, 2, 2, 2),\n                                    Text = name\n                                };\n                                batcher.Draw(mesh);\n                                batcher.MeshPush(mesh);\n                            }\n                            if (desc != null)\n                            {\n                                mesh = batcher.MeshPop();\n                                mesh.Position = new Vector2(life.X, life.Cy + 20);\n                                // temporarily ignore font name and size here.\n                                mesh.RenderObject = new TextMesh()\n                                {\n                                    Align = Alignment.Center,\n                                    ForeColor = life.CustomFont?.FontColor ?? Color.Yellow,\n                                    BackColor = new Color(Color.Black, 0.7f),\n                                    Font = renderEnv.Fonts.NpcNameFont,\n                                    Padding = new Margins(2, 2, 2, 2),\n                                    Text = desc\n                                };\n                                batcher.Draw(mesh);\n                                batcher.MeshPush(mesh);\n                            }\n                        }\n                        break;\n                }\n            }\n        }\n\n        private void PrepareLightMap()\n        {\n            var mapLight = this.mapData.Light;\n            var origin = this.renderEnv.Camera.Origin.ToPoint();\n            this.GraphicsDevice.Clear(mapLight.BackColor);\n\n            this.lightRenderer.Begin(Matrix.CreateTranslation(new Vector3(-origin.X, -origin.Y, 0)));\n            // render spot light\n            foreach (var light2D in mapLight.Lights)\n            {\n                this.lightRenderer.DrawSpotLight(light2D);\n            }\n            // render texture light\n            foreach(var container in GetSceneContainers(this.mapData.Scene))\n            {\n                foreach (var item in container.Slots)\n                {\n                    if (item is ObjItem obj && obj.Light && obj.View.Animator is FrameAnimator frameAni)\n                    {\n                        var frame = frameAni.CurrentFrame;\n                        this.lightRenderer.DrawTextureLight(frame.Texture, new Vector2(obj.X, obj.Y), frame.AtlasRect, frame.Origin.ToVector2(), obj.View.Flip, new Color(Color.White, frame.A0));\n                    }\n                }\n            }\n            this.lightRenderer.End();\n        }\n\n        private void ApplyLightMap(Texture2D lightMapTexture)\n        {\n            this.renderEnv.Sprite.Begin(blendState: this.renderEnv.BlendStateMultiplyRGB);\n            this.renderEnv.Sprite.Draw(lightMapTexture, Vector2.Zero, Color.White);\n            this.renderEnv.Sprite.End();\n        }\n\n        private IEnumerable<ContainerNode> GetSceneContainers(SceneNode node)\n        {\n            /*\n            var container = node as ContainerNode;\n            if (container != null)  //暂时不考虑缩进z层递归合并  container下没有子节点\n            {\n                yield return container;\n            }\n            else \n            {\n                foreach (var mesh in node.Nodes.SelectMany(child => GetSceneContainers(child)))\n                {\n                    yield return mesh;\n                }\n            }*/\n            Stack<SceneNode> sceneStack = new Stack<SceneNode>();\n            Stack<int> indices = new Stack<int>();\n\n            SceneNode currNode = node;\n            int i = 0;\n\n            while (currNode != null)\n            {\n                var container = currNode as ContainerNode;\n                if (container != null)\n                {\n                    yield return container;\n                    goto _pop;\n                }\n                else\n                {\n                    if (i < currNode.Nodes.Count)\n                    {\n                        var child = currNode.Nodes[i];\n                        //push\n                        sceneStack.Push(currNode);\n                        indices.Push(i + 1);\n                        currNode = child;\n                        i = 0;\n                        continue;\n                    }\n                    else\n                    {\n                        goto _pop;\n                    }\n                }\n\n                _pop:\n                if (sceneStack.Count > 0)\n                {\n                    currNode = sceneStack.Pop();\n                    i = indices.Pop();\n                }\n                else\n                {\n                    break;\n                }\n                continue;\n            }\n        }\n\n        private IEnumerable<KeyValuePair<SceneItem, MeshItem>> GetDrawableItems(MapScene scene)\n        {\n            var containers = GetSceneContainers(scene);\n            var kvList = this.drawableItemsCache;\n\n            foreach (var container in containers)\n            {\n                kvList.Clear();\n                foreach (var item in container.Slots)\n                {\n                    var mesh = GetMesh(item);\n                    if (mesh != null)\n                    {\n                        kvList.Add(new KeyValuePair<SceneItem, MeshItem>(item, mesh));\n                    }\n                    if (item is IlluminantClusterItem)\n                    {\n                        if (patchVisibility.IlluminantClusterVisible)\n                        {\n                            foreach (var meshIlluminantCluster in GetMeshesIlluminantCluster((IlluminantClusterItem)item))\n                            {\n                                kvList.Add(new KeyValuePair<SceneItem, MeshItem>(item, meshIlluminantCluster));\n                            }\n                        }\n                    }\n                }\n                kvList.Sort((kv1, kv2) => kv1.Value.CompareTo(kv2.Value));\n                foreach (var kv in kvList)\n                {\n                    yield return kv;\n                }\n            }\n\n            kvList.Clear();\n        }\n\n        private MeshItem GetMesh(SceneItem item)\n        {\n            if (item.Tags != null && item.Tags.Any(tag => !patchVisibility.IsTagVisible(tag)))\n            {\n                return null;\n            }\n\n            switch (item)\n            {\n                case BackItem back:\n                    if (back.Quest.Exists(quest => !patchVisibility.IsQuestVisible(quest.ID, quest.State)))\n                    {\n                        return null;\n                    }\n                    if (back.IsFront ? patchVisibility.FrontVisible : patchVisibility.BackVisible)\n                    {\n                        return GetMeshBack(back);\n                    }\n                    break;\n\n                case ObjItem obj:\n                    if (patchVisibility.ObjVisible && !obj.Light)\n                    {\n                        if (obj.Quest.Exists(quest => !patchVisibility.IsQuestVisible(quest.ID, quest.State)))\n                        {\n                            return null;\n                        }\n                        if (obj.Questex.Exists(questex => !patchVisibility.IsQuestVisible(questex.ID, questex.Key, questex.State)))\n                        {\n                            return null;\n                        }\n                        return GetMeshObj(obj);\n                    }\n                    break;\n\n                case TileItem tile:\n                    if (patchVisibility.TileVisible)\n                    {\n                        return GetMeshTile(tile);\n                    }\n                    break;\n\n                case LifeItem life:\n                    if ((life.Type == LifeItem.LifeType.Mob && patchVisibility.MobVisible)\n                        || (life.Type == LifeItem.LifeType.Npc && patchVisibility.NpcVisible))\n                    {\n                        return GetMeshLife(life);\n                    }\n                    break;\n\n                case PortalItem portal:\n                    if (patchVisibility.PortalVisible)\n                    {\n                        return GetMeshPortal(portal);\n                    }\n                    break;\n\n                case ReactorItem reactor:\n                    if (patchVisibility.ReactorVisible)\n                    {\n                        return GetMeshReactor(reactor);\n                    }\n                    break;\n\n                case ParticleItem particle:\n                    if (particle.Quest.Exists(quest => !patchVisibility.IsQuestVisible(quest.ID, quest.State)))\n                    {\n                        return null;\n                    }\n                    if (patchVisibility.EffectVisible)\n                    {\n                        return GetMeshParticle((ParticleItem)item);\n                    }\n                    break;\n            }\n            return null;\n        }\n\n        private MeshItem GetMeshBack(BackItem back)\n        {\n            //计算计算culling\n            if (back.ScreenMode != 0 && back.ScreenMode != renderEnv.Camera.DisplayMode + 1)\n            {\n                return null;\n            }\n\n            //计算坐标\n            int cx = back.Cx;\n            int cy = back.Cy;\n            if ((back.TileMode & TileMode.BothTile) != 0 && (cx == 0 || cy == 0))\n            {\n                Point renderSize = Point.Zero;\n                switch (back.View.Animator)\n                {\n                    case FrameAnimator frameAni:\n                        renderSize = frameAni.Data.GetBound().Size;\n                        break;\n                    case AnimationItem aniItem:\n                        // For spine animation, we don't know how to calculate the correct cx and cy\n                        renderSize = aniItem.Measure().Size;\n                        break;\n                    case MsCustomSprite msCustomSprite:\n                        renderSize = msCustomSprite.Size.ToPoint();\n                        break;\n                }\n\n                if (cx == 0) cx = renderSize.X;\n                if (cy == 0) cy = renderSize.Y;\n            }\n\n            Vector2 tileOff = new Vector2(cx, cy);\n            Vector2 position = new Vector2(back.X, back.Y);\n\n            //计算水平卷动\n            if ((back.TileMode & TileMode.ScrollHorizontal) != 0)\n            {\n                position.X += ((float)back.Rx * 5 * back.View.Time / 1000) % cx;// +this.Camera.Center.X * (100 - Math.Abs(this.rx)) / 100;\n            }\n            else //镜头移动比率偏移\n            {\n                position.X += renderEnv.Camera.Center.X * (100 + back.Rx) / 100;\n            }\n\n            //计算垂直卷动\n            if ((back.TileMode & TileMode.ScrollVertical) != 0)\n            {\n                position.Y += ((float)back.Ry * 5 * back.View.Time / 1000) % cy;// +this.Camera.Center.Y * (100 - Math.Abs(this.ry)) / 100;\n            }\n            else //镜头移动比率偏移\n            {\n                position.Y += (renderEnv.Camera.Center.Y) * (100 + back.Ry) / 100;\n            }\n\n            //y轴镜头调整\n            //if (back.TileMode == TileMode.None && renderEnv.Camera.WorldRect.Height > 600)\n            //    position.Y += (renderEnv.Camera.Height - 600) / 2;\n\n            //取整\n            position.X = (float)Math.Floor(position.X);\n            position.Y = (float)Math.Floor(position.Y);\n\n            //计算tile\n            Rectangle? tileRect = null;\n            if (back.TileMode != TileMode.None)\n            {\n                var cameraRect = renderEnv.Camera.ClipRect;\n\n                int l, t, r, b;\n                if ((back.TileMode & TileMode.Horizontal) != 0 && cx > 0)\n                {\n                    l = (int)Math.Floor((cameraRect.Left - position.X) / cx) - 1;\n                    r = (int)Math.Ceiling((cameraRect.Right - position.X) / cx) + 1;\n                }\n                else\n                {\n                    l = 0;\n                    r = 1;\n                }\n\n                if ((back.TileMode & TileMode.Vertical) != 0 && cy > 0)\n                {\n                    t = (int)Math.Floor((cameraRect.Top - position.Y) / cy) - 1;\n                    b = (int)Math.Ceiling((cameraRect.Bottom - position.Y) / cy) + 1;\n                }\n                else\n                {\n                    t = 0;\n                    b = 1;\n                }\n\n                tileRect = new Rectangle(l, t, r - l, b - t);\n            }\n\n            //生成mesh\n            var renderObj = GetRenderObject(back.View.Animator, back.Flip, back.Alpha);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var mesh = batcher.MeshPop();\n            mesh.RenderObject = renderObj;\n            mesh.Position = position;\n            mesh.Z0 = 0;\n            mesh.Z1 = back.Index;\n            mesh.FlipX = back.Flip;\n            mesh.TileRegion = tileRect;\n            mesh.TileOffset = tileOff;\n            return mesh;\n        }\n\n        private MeshItem GetMeshObj(ObjItem obj)\n        {\n            var renderObj = GetRenderObject(obj.View.Animator, flip: obj.View.Flip);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var mesh = batcher.MeshPop();\n            mesh.RenderObject = renderObj;\n            mesh.Position = new Vector2(obj.X, obj.Y);\n            mesh.Z0 = obj.Z;\n            mesh.Z1 = obj.Index;\n\n            if (obj.MoveW != 0 || obj.MoveH != 0)\n            {\n                mesh.Position += GetMovingObjPos(obj);\n            }\n            mesh.FlipX = obj.View.Flip;\n\n            return mesh;\n        }\n\n        private MeshItem GetMeshTile(TileItem tile)\n        {\n            var renderObj = GetRenderObject(tile.View.Animator);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var mesh = batcher.MeshPop();\n            mesh.RenderObject = renderObj;\n            mesh.Position = new Vector2(tile.X, tile.Y);\n            mesh.Z0 = ((renderObj as Frame)?.Z ?? 0);\n            mesh.Z1 = tile.Index;\n            return mesh;\n        }\n\n        private MeshItem GetMeshLife(LifeItem life)\n        {\n            var renderObj = GetRenderObject(life.View.Animator);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var mesh = batcher.MeshPop();\n            mesh.RenderObject = renderObj;\n            mesh.Position = new Vector2(life.X, life.Cy);\n            mesh.FlipX = life.Flip;\n            mesh.Z0 = ((renderObj as Frame)?.Z ?? 0);\n            mesh.Z1 = life.Index;\n            return mesh;\n        }\n\n        private MeshItem GetMeshPortal(PortalItem portal)\n        {\n            var renderObj = GetRenderObject(portal.View.IsEditorMode ? portal.View.EditorAnimator : portal.View.Animator);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var mesh = batcher.MeshPop();\n            mesh.RenderObject = renderObj;\n            mesh.Position = new Vector2(portal.X, portal.Y);\n            mesh.Z0 = ((renderObj as Frame)?.Z ?? 0);\n            mesh.Z1 = portal.Index;\n            return mesh;\n        }\n\n        private MeshItem[] GetMeshesIlluminantCluster(IlluminantClusterItem illuminantCluster)\n        {\n            var renderObj = GetRenderObject(illuminantCluster.StartView.Animator);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var startMesh = batcher.MeshPop();\n            startMesh.RenderObject = renderObj;\n            startMesh.Position = new Vector2(illuminantCluster.Start.X + illuminantCluster.Size / 2, illuminantCluster.Start.Y - illuminantCluster.Size / 2);\n            startMesh.Z0 = ((renderObj as Frame)?.Z ?? 0);\n            startMesh.Z1 = illuminantCluster.Index;\n            renderObj = GetRenderObject(illuminantCluster.EndView.Animator);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var endMesh = batcher.MeshPop();\n            endMesh.RenderObject = renderObj;\n            endMesh.Position = new Vector2(illuminantCluster.End.X + illuminantCluster.Size / 2, illuminantCluster.End.Y - illuminantCluster.Size / 2);\n            endMesh.Z0 = ((renderObj as Frame)?.Z ?? 0);\n            endMesh.Z1 = illuminantCluster.Index;\n            return new MeshItem[] { startMesh, endMesh };\n        }\n\n        private MeshItem GetMeshReactor(ReactorItem reactor)\n        {\n            var renderObj = GetRenderObject(reactor.View.Animator);\n            if (renderObj == null)\n            {\n                return null;\n            }\n            var mesh = batcher.MeshPop();\n            mesh.RenderObject = renderObj;\n            mesh.Position = new Vector2(reactor.X, reactor.Y);\n            mesh.FlipX = reactor.Flip;\n            mesh.Z0 = ((renderObj as Frame)?.Z ?? 0);\n            mesh.Z1 = reactor.Index;\n            return mesh;\n        }\n\n        private MeshItem GetMeshParticle(ParticleItem particle)\n        {\n            var pSystem = particle.View?.ParticleSystem;\n            if (pSystem == null)\n            {\n                return null;\n            }\n\n            Vector2 position;\n            position.X = renderEnv.Camera.Center.X * (100 + particle.Rx) / 100;\n            position.Y = renderEnv.Camera.Center.Y * (100 + particle.Ry) / 100;\n\n            var mesh = batcher.MeshPop();\n            mesh.RenderObject = pSystem;\n            mesh.Position = position;\n            mesh.Z0 = particle.Z;\n            return mesh;\n        }\n\n        private object GetRenderObject(object animator, bool flip = false, int alpha = 255)\n        {\n            if (animator is FrameAnimator frameAni)\n            {\n                var frame = frameAni.CurrentFrame;\n                if (frame != null)\n                {\n                    if (alpha < 255) //理论上应该返回一个新的实例\n                    {\n                        frame.A0 = frame.A0 * alpha / 255;\n                    }\n                    return frame;\n                }\n            }\n            else if (animator is SpineAnimatorV2 spineAniV2)\n            {\n                var skeleton = spineAniV2.Skeleton;\n                if (skeleton != null)\n                {\n                    if (alpha < 255)\n                    {\n                        skeleton.A = alpha / 255.0f;\n                    }\n                    return skeleton;\n                }\n            }\n            else if (animator is SpineAnimatorV4 spineAniV4)\n            {\n                var skeleton = spineAniV4.Skeleton;\n                if (skeleton != null)\n                {\n                    if (alpha < 255)\n                    {\n                        skeleton.A = alpha / 255.0f;\n                    }\n                    return skeleton;\n                }\n            }\n            else if (animator is StateMachineAnimator smAni)\n            {\n                return smAni.Data.GetMesh();\n            }\n            else if (animator is MsCustomSprite msCustomSprite)\n            {\n                this.UpdateShaderConstant(msCustomSprite.Material);\n                return msCustomSprite;\n            }\n\n            return null;\n        }\n\n        private Vector2 GetMovingObjPos(ObjItem obj)\n        {\n            double movingX = 0;\n            double movingY = 0;\n            double time = obj.View.Time;\n            switch (obj.MoveType)\n            {\n                case 1:\n                case 2: // line\n                    time *= Math.PI * 2 / obj.MoveP;\n                    movingX = obj.MoveW * Math.Cos(time);\n                    movingY = obj.MoveH * Math.Cos(time);\n                    break;\n                case 3: // circle\n                    time *= Math.PI * 2 / obj.MoveP;\n                    movingX = obj.MoveW * Math.Cos(time);\n                    movingY = obj.MoveH * Math.Sin(time);\n                    break;\n\n                case 6:\n                case 7:\n                case 8:\n                    int sign = -1;\n                    double freq = (double)(obj.MoveP + obj.MoveDelay) * 2;\n                    time = time % freq;\n                    if (time >= freq / 2)\n                    {\n                        time -= freq / 2;\n                        if (obj.MoveType == 8)\n                            obj.View.Flip = !obj.Flip;\n\n                        if (obj.MoveType != 6)\n                            sign = +1;\n                    }\n                    else\n                    {\n                        if (obj.MoveType == 8)\n                            obj.View.Flip = obj.Flip;\n                    }\n\n                    movingX = (Math.Min(1, Math.Max(-1, (time - obj.MoveDelay) * -2 / obj.MoveP + 1)) * sign + 1) / 2 * obj.MoveW;\n                    movingY = (Math.Min(1, Math.Max(-1, (time - obj.MoveDelay) * -2 / obj.MoveP + 1)) * sign + 1) / 2 * obj.MoveH;\n\n                    break;\n\n                default:\n                    break;\n            }\n            return new Vector2((float)movingX, (float)movingY);\n        }\n\n        private void UpdateShaderConstant(ShaderMaterial shaderMaterial)\n        {\n            // we don't know the exact value that being used in the original client\n            switch (shaderMaterial)\n            {\n                case LightPixelShaderMaterial light:\n                    light.PlayerPos = renderEnv.Camera.CameraToWorld(renderEnv.Input.MousePosition).ToVector2();\n                    light.LightInnerRadius = 50f;\n                    light.LightOuterRadius = 200f;\n                    light.PlayerLightColor = Color.White.ToVector4();\n                    light.TopColor = Color.White.ToVector4();\n                    light.BottomColor = new Color(0.2f, 0.2f, 0.2f, 1f).ToVector4();\n                    light.MinY = 4500;\n                    light.MaxY = 19500;\n                    break;\n\n                case WaterFrontPixelShaderMaterial waterFront:\n                    waterFront.PlayerPos = renderEnv.Camera.CameraToWorld(renderEnv.Input.MousePosition).ToVector2();\n                    waterFront.Factor1 = 1f;\n                    waterFront.MinY = 4500;\n                    waterFront.MaxY = 19500;\n                    waterFront.DistNoiseCenterPos = waterFront.PlayerPos;\n                    waterFront.Factor2 = 1f;\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/FrmMapRender2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Runtime.InteropServices;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.MapRender.Config;\nusing WzComparerR2.MapRender.Patches2;\nusing WzComparerR2.MapRender.UI;\nusing WzComparerR2.Rendering;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing Form = System.Windows.Forms.Form;\nusing JLChnToZ.IMEHelper;\n\n#region USING_EK\nusing EmptyKeys.UserInterface;\nusing KeyBinding = EmptyKeys.UserInterface.Input.KeyBinding;\nusing RelayCommand = EmptyKeys.UserInterface.Input.RelayCommand;\nusing KeyCode = EmptyKeys.UserInterface.Input.KeyCode;\nusing ModifierKeys = EmptyKeys.UserInterface.Input.ModifierKeys;\nusing ServiceManager = EmptyKeys.UserInterface.Mvvm.ServiceManager;\nusing WzComparerR2.MapRender.Effects;\n#endregion\n\nnamespace WzComparerR2.MapRender\n{\n    public partial class FrmMapRender2 : Game\n    {\n        public FrmMapRender2()\n        {\n            graphics = new GraphicsDeviceManager(this);\n            graphics.DeviceCreated += Graphics_DeviceCreated;\n            graphics.DeviceResetting += Graphics_DeviceResetting;\n            graphics.GraphicsProfile = GraphicsProfile.HiDef;\n\n            this.MaxElapsedTime = TimeSpan.MaxValue;\n            this.IsFixedTimeStep = false;\n            this.TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 60);\n            this.InactiveSleepTime = TimeSpan.FromSeconds(1.0 / 30);\n            this.IsMouseVisible = true;\n            this.Exiting += (o, e) => this.OnExiting();\n\n            this.Content = new WcR2ContentManager(this.Services);\n            this.patchVisibility = new PatchVisibility();\n            this.patchVisibility.FootHoldVisible = false;\n            this.patchVisibility.LadderRopeVisible = false;\n            this.patchVisibility.SkyWhaleVisible = false;\n            this.patchVisibility.IlluminantClusterPathVisible = false;\n\n            var form = Form.FromHandle(this.Window.Handle) as Form;\n            form.Load += Form_Load;\n            form.GotFocus += Form_GotFocus;\n            form.LostFocus += Form_LostFocus;\n            form.FormClosing += Form_FormClosing;\n            form.FormClosed += Form_FormClosed;\n\n            this.imeHelper = new IMEHandler(this, true);\n            this.Exiting += (o, e) => this.imeHelper.Dispose();\n            this.Disposed += (o, e) => this.imeHelper.Dispose();\n            GameExt.FixKeyboard(this);\n        }\n\n        private void Form_Load(object sender, EventArgs e)\n        {\n            var form = (Form)sender;\n            form.StartPosition = System.Windows.Forms.FormStartPosition.Manual;\n            form.SetDesktopLocation(0, 0);\n        }\n\n        private void Form_GotFocus(object sender, EventArgs e)\n        {\n            if (MapRenderConfig.Default.MuteOnLeaveFocus)\n            {\n                if (this.bgm != null)\n                {\n                    this.bgm.Volume = 1;\n                }\n            }\n        }\n\n        private void Form_LostFocus(object sender, EventArgs e)\n        {\n            if (MapRenderConfig.Default.MuteOnLeaveFocus)\n            {\n                if (this.bgm != null)\n                {\n                    this.bgm.Volume = 0;\n                }\n            }\n        }\n        private void Form_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e)\n        {\n            GameExt.ReleaseKeyboard(this);\n        }\n\n        private void Form_FormClosed(object sender, System.Windows.Forms.FormClosedEventArgs e)\n        {\n            GameExt.EnsureGameExit(this);\n        }\n\n        private void Graphics_DeviceCreated(object sender, EventArgs e)\n        {\n            this.engine = new WcR2Engine(this.GraphicsDevice,\n                graphics.PreferredBackBufferWidth,\n                graphics.PreferredBackBufferHeight);\n\n            WcR2Engine.FixEKBugs();\n\n            (this.engine.AssetManager as WcR2AssetManager).DefaultContentManager = this.Content as WcR2ContentManager;\n        }\n\n        private void Graphics_DeviceResetting(object sender, EventArgs e)\n        {\n            // fix DXGI error 0x887A0001 on gamewindow resizing\n            WzComparerR2.Rendering.D2DFactory.Instance.ReleaseContext(graphics.GraphicsDevice);\n        }\n\n        public StringLinker StringLinker { get; set; }\n\n        GraphicsDeviceManager graphics;\n        Wz_Image mapImg;\n        RenderEnv renderEnv;\n        MapData mapData;\n        ResourceLoader resLoader;\n        MeshBatcher batcher;\n        LightRenderer lightRenderer;\n        PatchVisibility patchVisibility;\n\n        bool prepareCapture;\n        Task captureTask;\n        Resolution resolution;\n        float opacity;\n\n        List<ItemRect> allItems = new List<ItemRect>();\n        List<KeyValuePair<SceneItem, MeshItem>> drawableItemsCache = new List<KeyValuePair<SceneItem, MeshItem>>();\n        MapRenderUIRoot ui;\n        Tooltip2 tooltip;\n        WcR2Engine engine;\n        Music bgm;\n\n        CoroutineManager cm;\n        FpsCounter fpsCounter;\n        readonly List<IDisposable> attachedEvent = new List<IDisposable>();\n        IMEHandler imeHelper;\n\n        bool isUnloaded;\n        bool isExiting;\n\n        protected override void Initialize()\n        {\n\n            //init services\n            this.Services.AddService<Random>(new Random());\n            this.Services.AddService<IRandom>(new ParticleRandom(this.Services.GetService<Random>()));\n            this.Services.AddService<IMEHandler>(this.imeHelper);\n\n            ServiceManager.Instance.AddService<IMEHandler>(this.imeHelper);\n\n            //init components\n            this.renderEnv = new RenderEnv(this, this.graphics);\n            this.batcher = new MeshBatcher(this.GraphicsDevice) { CullingEnabled = true };\n            this.lightRenderer = new LightRenderer(this.GraphicsDevice);\n            this.resLoader = new ResourceLoader(this.Services);\n            this.resLoader.PatchVisibility = this.patchVisibility;\n            this.ui = new MapRenderUIRoot();\n            this.BindingUIInput();\n            this.tooltip = new Tooltip2(this.Content);\n            this.tooltip.StringLinker = this.StringLinker;\n            this.cm = new CoroutineManager(this);\n            this.cm.StartCoroutine(OnStart()); //entry\n            this.Components.Add(cm);\n            this.fpsCounter = new FpsCounter(this) { UseStopwatch = true };\n            this.Components.Add(fpsCounter);\n\n            this.ApplySetting();\n            SwitchResolution(Resolution.Window_800_600);\n            base.Initialize();\n\n            //init UI teleport\n            this.ui.Teleport.Sl = this.StringLinker;\n        }\n\n        protected override void OnActivated(object sender, EventArgs args)\n        {\n            base.OnActivated(sender, args);\n        }\n\n        protected override void OnDeactivated(object sender, EventArgs args)\n        {\n            base.OnDeactivated(sender, args);\n        }\n\n        private void BindingUIInput()\n        {\n            //键盘事件\n            //切换分辨率\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => SwitchResolution()), KeyCode.Enter, ModifierKeys.Alt));\n\n            //开关小地图\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.ui.Minimap.Toggle()), KeyCode.M, ModifierKeys.None) { IsRepeatEnabled = true });\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.ui.WorldMap.Toggle()), KeyCode.W, ModifierKeys.None) { IsRepeatEnabled = true });\n\n            //选项界面\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ =>\n            {\n                var uiWnd = this.ui.Windows.OfType<UIOptions>().FirstOrDefault();\n                if (uiWnd == null)\n                {\n                    uiWnd = new UIOptions();\n                    uiWnd.DataContext = new UIOptionsDataModel();\n                    uiWnd.OK += UIOption_OK;\n                    uiWnd.Cancel += UIOption_Cancel;\n                    uiWnd.Visible += UiWnd_Visible;\n                    uiWnd.Visibility = EmptyKeys.UserInterface.Visibility.Visible;\n                    this.ui.Windows.Add(uiWnd);\n                    uiWnd.Parent = this.ui;\n                }\n                else\n                {\n                    uiWnd.Toggle();\n                }\n            }), KeyCode.Escape, ModifierKeys.None));\n\n            //截图\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => { if (CanCapture()) prepareCapture = true; }), KeyCode.Scroll, ModifierKeys.None));\n\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => { renderEnv.Camera.AdjustRectEnabled = !renderEnv.Camera.AdjustRectEnabled; }), KeyCode.U, ModifierKeys.Control));\n\n            //层隐藏\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.BackVisible = !this.patchVisibility.BackVisible), KeyCode.D1, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.ReactorVisible = !this.patchVisibility.ReactorVisible), KeyCode.D2, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.ObjVisible = !this.patchVisibility.ObjVisible), KeyCode.D3, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.TileVisible = !this.patchVisibility.TileVisible), KeyCode.D4, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.NpcVisible = !this.patchVisibility.NpcVisible), KeyCode.D5, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.MobVisible = !this.patchVisibility.MobVisible), KeyCode.D6, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ =>\n            {\n                var visible = this.patchVisibility.FootHoldVisible;\n                this.patchVisibility.FootHoldVisible = !visible;\n                this.patchVisibility.LadderRopeVisible = !visible;\n                this.patchVisibility.SkyWhaleVisible = !visible;\n                this.patchVisibility.IlluminantClusterPathVisible = !visible;\n            }), KeyCode.D7, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ =>\n            {\n                var portals = this.mapData.Scene.Portals;\n                if (!this.patchVisibility.PortalVisible)\n                {\n                    this.patchVisibility.PortalVisible = true;\n                    this.patchVisibility.PortalInEditMode = false;\n                    foreach (var item in portals)\n                    {\n                        item.View.IsEditorMode = false;\n                    }\n                    this.patchVisibility.IlluminantClusterVisible = true;\n                }\n                else if (!this.patchVisibility.PortalInEditMode)\n                {\n                    this.patchVisibility.PortalInEditMode = true;\n                    foreach (var item in portals)\n                    {\n                        item.View.IsEditorMode = true;\n                    }\n                    this.patchVisibility.IlluminantClusterVisible = false;\n                }\n                else\n                {\n                    this.patchVisibility.PortalVisible = false;\n                }\n            }), KeyCode.D8, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.FrontVisible = !this.patchVisibility.FrontVisible), KeyCode.D9, ModifierKeys.Control));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.patchVisibility.EffectVisible = !this.patchVisibility.EffectVisible), KeyCode.D0, ModifierKeys.Control));\n            //移动操作\n            #region 移动操作\n            {\n                //键盘移动\n                int boostMoveFlag = 0;\n                var direction1 = Vector2.Zero;\n\n                Action<Vector2> calcKeyboardMoveDir = dir =>\n                {\n                    if (dir.X != 0)\n                    {\n                        var preMove = dir.X * 10 * (boostMoveFlag != 0 ? 3 : 1);\n\n                        if (Math.Sign(preMove) * Math.Sign(direction1.X) == -1\n                            && Math.Abs(direction1.X) <= Math.Abs(preMove))\n                        {\n                            direction1.X = 0;\n                        }\n                        else\n                        {\n                            direction1.X += preMove;\n                        }\n                    }\n                    if (dir.Y != 0)\n                    {\n                        var preMove = dir.Y * 10 * (boostMoveFlag != 0 ? 3 : 1);\n\n                        if (Math.Sign(preMove) * Math.Sign(direction1.Y) == -1\n                            && Math.Abs(direction1.Y) <= Math.Abs(preMove))\n                        {\n                            direction1.Y = 0;\n                        }\n                        else\n                        {\n                            direction1.Y += preMove;\n                        }\n                    }\n                };\n\n                //键盘动量减速\n                Action keyboardMoveSlowDown = () =>\n                {\n                    if (direction1.X > 2 || direction1.X < -2) direction1.X *= 0.98f;\n                    else direction1.X = 0;\n                    if (direction1.Y > 2 || direction1.Y < 2) direction1.Y *= 0.98f;\n                    else direction1.Y = 0;\n                };\n\n                EmptyKeys.UserInterface.Input.KeyEventHandler keyEv;\n                var keyApplied = new Dictionary<KeyCode, bool>();\n\n                keyEv = (o, e) =>\n                {\n                    if (EmptyKeys.UserInterface.Input.InputManager.Current.FocusedElement is EmptyKeys.UserInterface.Controls.Primitives.TextBoxBase)\n                    {\n                        return;\n                    }\n\n                    switch (e.Key)\n                    {\n                        case KeyCode.Up:\n                            calcKeyboardMoveDir(new Vector2(0, -1));\n                            break;\n                        case KeyCode.Down:\n                            calcKeyboardMoveDir(new Vector2(0, 1));\n                            break;\n                        case KeyCode.Left:\n                            calcKeyboardMoveDir(new Vector2(-1, 0));\n                            break;\n                        case KeyCode.Right:\n                            calcKeyboardMoveDir(new Vector2(1, 0));\n                            break;\n\n                        case KeyCode.LeftControl:\n                            boostMoveFlag |= 0x01;\n                            break;\n                        case KeyCode.RightControl:\n                            boostMoveFlag |= 0x02;\n                            break;\n\n                        default:\n                            return;\n                    }\n                    keyApplied[e.Key] = true;\n                    e.Handled = true;\n                };\n                this.ui.PreviewKeyDown += keyEv;\n                this.attachedEvent.Add(EventDisposable(keyEv, _ev => this.ui.PreviewKeyDown -= _ev));\n\n                keyEv = (o, e) =>\n                {\n                    switch (e.Key)\n                    {\n                        case KeyCode.Up:\n                        case KeyCode.Down:\n                        case KeyCode.Left:\n                        case KeyCode.Right:\n                            if (keyApplied.TryGetValue(e.Key, out bool pressed) && pressed)\n                            {\n                                keyApplied[e.Key] = false;\n                                e.Handled = true;\n                            }\n                            break;\n\n                        case KeyCode.LeftControl:\n                            boostMoveFlag &= ~0x01;\n                            break;\n                        case KeyCode.RightControl:\n                            boostMoveFlag &= ~0x02;\n                            break;\n                    }\n                };\n                this.ui.PreviewKeyUp += keyEv;\n                this.attachedEvent.Add(EventDisposable(keyEv, _ev => this.ui.PreviewKeyUp -= _ev));\n\n                //鼠标移动\n                bool isMouseDown = false;\n                var direction2 = Vector2.Zero;\n\n                Action<EmptyKeys.UserInterface.Input.MouseEventArgs> calcMouseMoveDir = e =>\n                {\n                    var mousePos = e.GetPosition();\n                    Vector2 vec = new Vector2(2 * mousePos.X / this.ui.Width - 1, 2 * mousePos.Y / this.ui.Height - 1);\n                    var distance = vec.Length();\n                    if (distance >= 0.4f)\n                    {\n                        vec *= (distance - 0.4f) / distance;\n                    }\n                    else\n                    {\n                        vec = Vector2.Zero;\n                    }\n                    direction2 = vec * 20;\n                };\n\n                EmptyKeys.UserInterface.Input.MouseEventHandler mouseEv;\n                EmptyKeys.UserInterface.Input.MouseButtonEventHandler mouseBtnEv;\n\n                mouseBtnEv = (o, e) =>\n                {\n                    if (e.ChangedButton == EmptyKeys.UserInterface.Input.MouseButton.Right)\n                    {\n                        isMouseDown = true;\n                        calcMouseMoveDir(e);\n                    }\n                };\n                this.ui.MouseDown += mouseBtnEv;\n                this.attachedEvent.Add(EventDisposable(mouseBtnEv, _ev => this.ui.MouseDown -= _ev));\n\n                mouseEv = (o, e) =>\n                {\n                    if (isMouseDown)\n                    {\n                        calcMouseMoveDir(e);\n                    }\n                };\n                this.ui.MouseMove += mouseEv;\n                this.attachedEvent.Add(EventDisposable(mouseEv, _ev => this.ui.MouseMove -= _ev));\n\n                mouseBtnEv = (o, e) =>\n                {\n                    if (e.ChangedButton == EmptyKeys.UserInterface.Input.MouseButton.Right)\n                    {\n                        isMouseDown = false;\n                        direction2 = Vector2.Zero;\n                    }\n                };\n                this.ui.MouseUp += mouseBtnEv;\n                this.attachedEvent.Add(EventDisposable(mouseBtnEv, _ev => this.ui.MouseUp -= _ev));\n\n                //更新事件\n                EventHandler ev = (o, e) =>\n                {\n                    this.renderEnv.Camera.Center += direction1 + direction2 * ((boostMoveFlag != 0) ? 3 : 1);\n                    keyboardMoveSlowDown();\n                };\n                this.ui.InputUpdated += ev;\n                this.attachedEvent.Add(EventDisposable(ev, _ev => this.ui.InputUpdated -= _ev));\n            }\n            #endregion\n\n            //点击事件\n            var disposable = UIHelper.RegisterClickEvent<SceneItem>(this.ui.ContentControl,\n                (sender, point) =>\n                {\n                    int x = (int)point.X;\n                    int y = (int)point.Y;\n                    var mouseTarget = this.allItems.Reverse<ItemRect>().FirstOrDefault(item =>\n                    {\n                        return item.rect.Contains(x, y) && (item.item is PortalItem || item.item is IlluminantClusterItem || item.item is ReactorItem);\n                    });\n                    return mouseTarget.item;\n                },\n                this.OnSceneItemClick);\n            this.attachedEvent.Add(disposable);\n\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => {\n                if (this.ui.Visibility == Visibility.Visible)\n                {\n                    this.ui.ChatBox.TextBoxChat.Focus();\n                }\n            }), KeyCode.Enter, ModifierKeys.None));\n            this.ui.InputBindings.Add(new KeyBinding(new RelayCommand(_ => this.ui.ChatBox.Toggle()), KeyCode.Oem3, ModifierKeys.None));\n            this.ui.WorldMap.MapSpotClick += WorldMap_MapSpotClick;\n            this.ui.Teleport.SelectedMapGo += Teleport_SelectedMapGo;\n            this.ui.ChatBox.TextBoxChat.TextSubmit += ChatBox_TextSubmit;\n        }\n\n        private void UIOption_OK(object sender, EventArgs e)\n        {\n            var wnd = sender as UIOptions;\n            var data = wnd.DataContext as UIOptionsDataModel;\n            SaveOptionData(data);\n            wnd.Hide();\n\n            ApplySetting();\n        }\n\n        private void UIOption_Cancel(object sender, EventArgs e)\n        {\n            var wnd = sender as UIOptions;\n            wnd.Hide();\n        }\n\n        private void UiWnd_Visible(object sender, RoutedEventArgs e)\n        {\n            var wnd = sender as UIOptions;\n            var data = wnd.DataContext as UIOptionsDataModel;\n            LoadOptionData(data);\n        }\n\n        private void WorldMap_MapSpotClick(object sender, UIWorldMap.MapSpotEventArgs e)\n        {\n            int mapID = e.MapID;\n\n            var callback = new EmptyKeys.UserInterface.Input.RelayCommand<MessageBoxResult>(r =>\n            {\n                if (r == MessageBoxResult.OK)\n                {\n                    this.MoveToPortal(mapID, \"sp\");\n                }\n            });\n\n            StringResult sr = null;\n            this.StringLinker?.StringMap.TryGetValue(mapID, out sr);\n            var message = string.Format(\"是否传送到地图\\r\\n{0} ({1})？\", sr?.Name ?? \"null\", mapID);\n            MessageBox.Show(message, \"提示\", MessageBoxButton.OKCancel, callback, false);\n        }\n\n        private void Teleport_SelectedMapGo(object sender, UITeleport.SelectedMapGoEventArgs e)\n        {\n            int mapID = e.MapID;\n            this.MoveToPortal(mapID, \"sp\");\n        }\n\n        private void ChatBox_TextSubmit(object sender, TextEventArgs e)\n        {\n            if (!string.IsNullOrWhiteSpace(e.Text))\n            {\n                if (e.Text.StartsWith(\"/\"))\n                {\n                    ChatCommand(e.Text);\n                }\n                else\n                {\n                    this.ui.ChatBox.AppendTextNormal(\"MapRender: \" + e.Text);\n                }\n            }\n        }\n\n        private async void ChatCommand(string command)\n        {\n            string[] arguments = command.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);\n            if (arguments.Length <= 0)\n            {\n                return;\n            }\n\n            switch (arguments[0].ToLower())\n            {\n                case \"/help\":\n                case \"/?\":\n                    this.ui.ChatBox.AppendTextHelp(@\"/help 显示帮助。\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/map (mapID) 跳转地图。\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/back 回到上一地图。\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/home 回城。\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/history [maxCount] 查看历史地图。\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/minimap 设置迷你地图状态。\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/scene 设置地图场景显示状态。\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/quest 任务设置\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/questex 设置任务Key的值\");\n                    this.ui.ChatBox.AppendTextHelp(@\"/multibgm 多重BGM设置\");\n                    break;\n\n                case \"/map\":\n                    int toMapID;\n                    if (arguments.Length > 1 && Int32.TryParse(arguments[1], out toMapID) && toMapID > -1)\n                    {\n                        this.MoveToPortal(toMapID, \"sp\");\n                    }\n                    else\n                    {\n                        this.ui.ChatBox.AppendTextSystem($\"缺少地图ID。\");\n                    }\n                    break;\n\n                case \"/back\":\n                    if (this.viewHistory.Count > 0)\n                    {\n                        this.MoveToLastMap();\n                    }\n                    else\n                    {\n                        this.ui.ChatBox.AppendTextSystem($\"前面没有地图。\");\n                    }\n                    break;\n\n                case \"/home\":\n                    var retMapID = this.mapData?.ReturnMap;\n                    if (retMapID == null || retMapID == 999999999)\n                    {\n                        this.ui.ChatBox.AppendTextSystem($\"回不到那里去。\");\n                    }\n                    else\n                    {\n                        this.MoveToPortal(retMapID, \"sp\");\n                    }\n                    break;\n\n                case \"/history\":\n                    int historyCount;\n                    if (!(arguments.Length > 1\n                        && Int32.TryParse(arguments[1], out historyCount)\n                        && historyCount > 0))\n                    {\n                        historyCount = 5;\n                    }\n                    this.ui.ChatBox.AppendTextHelp($\"历史地图：({this.viewHistory.Count})\");\n                    var node = this.viewHistory.Last;\n                    while (node != null && historyCount > 0)\n                    {\n                        StringResult sr = null;\n                        if (node.Value != null && this.StringLinker != null)\n                        {\n                            this.StringLinker.StringMap.TryGetValue(node.Value.MapID, out sr);\n                        }\n                        this.ui.ChatBox.AppendTextHelp($\"  {sr?.Name ?? \"(null)\"}({node.Value.MapID})\");\n\n                        node = node.Previous;\n                        historyCount--;\n                    }\n                    break;\n\n                case \"/minimap\":\n                    var canvasList = this.mapData?.MiniMap?.ExtraCanvas;\n                    switch (arguments.ElementAtOrDefault(1))\n                    {\n                        case \"list\":\n                            this.ui.ChatBox.AppendTextHelp($\"minimap: {string.Join(\", \", canvasList?.Keys)}\");\n                            break;\n\n                        case \"set\":\n                            string canvasName = arguments.ElementAtOrDefault(2);\n                            if (canvasList != null && canvasList.TryGetValue(canvasName, out Texture2D canvas))\n                            {\n                                this.ui.Minimap.MinimapCanvas = engine.Renderer.CreateTexture(canvas);\n                                this.ui.ChatBox.AppendTextHelp($\"设置迷你地图：{canvasName}\");\n                            }\n                            else\n                            {\n                                this.ui.ChatBox.AppendTextSystem($\"找不到迷你地图：{canvasName}\");\n                            }\n                            break;\n\n                        default:\n                            this.ui.ChatBox.AppendTextHelp(@\"/minimap list 显示所有迷你地图名称。\");\n                            this.ui.ChatBox.AppendTextHelp(@\"/minimap set (canvasName) 设置迷你地图。\");\n                            break;\n                    }\n                    break;\n\n                case \"/scene\":\n                    switch (arguments.ElementAtOrDefault(1))\n                    {\n                        case \"tag\":\n                            switch (arguments.ElementAtOrDefault(2))\n                            {\n                                case \"list\":\n                                    var mapTags = GetSceneContainers(this.mapData?.Scene)\n                                        .SelectMany(container => container.Slots)\n                                        .Where(sceneItem => sceneItem.Tags != null && sceneItem.Tags.Length > 0)\n                                        .SelectMany(sceneItem => sceneItem.Tags)\n                                        .Distinct()\n                                        .OrderBy(tag => tag)\n                                        .ToList();\n                                    this.ui.ChatBox.AppendTextHelp($\"当前地图tags: {string.Join(\", \", mapTags)}\");\n                                    break;\n                                case \"info\":\n                                    var visibleTags = this.patchVisibility.TagsVisible.Where(kv => kv.Value).Select(kv => kv.Key).ToList();\n                                    var hiddenTags = this.patchVisibility.TagsVisible.Where(kv => !kv.Value).Select(kv => kv.Key).ToList();\n                                    this.ui.ChatBox.AppendTextHelp($\"默认tag显示状态: {this.patchVisibility.DefaultTagVisible}\");\n                                    this.ui.ChatBox.AppendTextHelp($\"显示tags: {string.Join(\", \", visibleTags)}\");\n                                    this.ui.ChatBox.AppendTextHelp($\"隐藏tags: {string.Join(\", \", hiddenTags)}\");\n                                    break;\n                                case \"show\":\n                                    string[] tags = arguments.Skip(3).ToArray();\n                                    if (tags.Length > 0)\n                                    {\n                                        foreach (var tag in tags)\n                                        {\n                                            this.patchVisibility.SetTagVisible(tag, true);\n                                        }\n                                        this.ui.ChatBox.AppendTextHelp($\"显示tag: {string.Join(\", \", tags)}\");\n                                    }\n                                    else\n                                    {\n                                        this.ui.ChatBox.AppendTextSystem(\"没有输入tagName。\");\n                                    }\n                                    break;\n                                case \"hide\":\n                                    tags = arguments.Skip(3).ToArray();\n                                    if (tags.Length > 0)\n                                    {\n                                        foreach (var tag in tags)\n                                        {\n                                            this.patchVisibility.SetTagVisible(tag, false);\n                                        }\n                                        this.ui.ChatBox.AppendTextHelp($\"隐藏tag: {string.Join(\", \", tags)}\");\n                                    }\n                                    else\n                                    {\n                                        this.ui.ChatBox.AppendTextSystem(\"没有输入tagName。\");\n                                    }\n                                    break;\n                                case \"reset\":\n                                    tags = arguments.Skip(3).ToArray();\n                                    if (tags.Length > 0)\n                                    {\n                                        this.patchVisibility.ResetTagVisible(tags);\n                                        this.ui.ChatBox.AppendTextHelp($\"重置tag: {string.Join(\", \", tags)}\");\n                                    }\n                                    else\n                                    {\n                                        this.ui.ChatBox.AppendTextSystem(\"没有输入tagName。\");\n                                    }\n                                    break;\n                                case \"reset-all\":\n                                    this.patchVisibility.ResetTagVisible();\n                                    this.ui.ChatBox.AppendTextHelp($\"重置已设置tag。\");\n                                    break;\n                                case \"set-default\":\n                                    if (bool.TryParse(arguments.ElementAtOrDefault(3), out bool isVisible))\n                                    {\n                                        this.patchVisibility.DefaultTagVisible = isVisible;\n                                        this.ui.ChatBox.AppendTextHelp($\"设置tag默认显示状态：{isVisible}\");\n                                    }\n                                    else\n                                    {\n                                        this.ui.ChatBox.AppendTextSystem(\"参数错误。\");\n                                    }\n                                    break;\n                                default:\n                                    this.ui.ChatBox.AppendTextHelp(@\"/scene tag list 获取场景中所有物体的tag。\");\n                                    this.ui.ChatBox.AppendTextHelp(@\"/scene tag info 获取当前自定义显示状态。\");\n                                    this.ui.ChatBox.AppendTextHelp(@\"/scene tag show (tagName)... 显示tagName的物体。\");\n                                    this.ui.ChatBox.AppendTextHelp(@\"/scene tag hide (tagName)... 隐藏tagName的物体。\");\n                                    this.ui.ChatBox.AppendTextHelp(@\"/scene tag reset (tagName)... 重置指定tagName的显示状态。\");\n                                    this.ui.ChatBox.AppendTextHelp(@\"/scene tag reset-all 重置所有物体为显示状态。\");\n                                    this.ui.ChatBox.AppendTextHelp(@\"/scene tag set-default (true/false) 设置所有tag的默认显示状态。\");\n                                    break;\n                            }\n                            break;\n\n                        default:\n                            this.ui.ChatBox.AppendTextHelp(@\"/scene tag 设置tag相关的显示状态。\");\n                            break;\n                    }\n                    break;\n\n                case \"/multibgm\":\n                    switch (arguments.ElementAtOrDefault(1))\n                    {\n                        case \"list\":\n                            if (!string.IsNullOrEmpty(this.mapData.Bgm))\n                            {\n                                var path = new List<string>() { \"Sound\" };\n                                path.AddRange(this.mapData.Bgm.Split('/'));\n                                path[1] += \".img\";\n                                var bgmNode = PluginBase.PluginManager.FindWz(string.Join(\"\\\\\", path));\n                                var subNodes = bgmNode?.Nodes ?? new Wz_Node.WzNodeCollection(null);\n                                this.ui.ChatBox.AppendTextHelp($\"多重BGM: {subNodes.Count}\");\n                                foreach (Wz_Node subNode in subNodes)\n                                {\n                                    this.ui.ChatBox.AppendTextHelp($\"  {subNode.Text}\");\n                                }\n                            }\n                            else\n                            {\n                                this.ui.ChatBox.AppendTextHelp($\"多重BGM: 0\");\n                            }\n                            break;\n\n                        case \"set\":\n                            string bgmName = string.Join(\" \", arguments.Skip(2));\n                            Music multiBgm = LoadBgm(this.mapData, bgmName);\n                            if (multiBgm != null)\n                            {\n                                this.ui.ChatBox.AppendTextSystem($\"多重BGM已更改为{bgmName}。\");\n\n                                Task bgmTask = null;\n                                bool willSwitchBgm = this.bgm != multiBgm;\n                                if (willSwitchBgm && this.bgm != null) //准备切换\n                                {\n                                    bgmTask = FadeOut(this.bgm, 1000);\n                                }\n\n                                if (bgmTask != null)\n                                {\n                                    await bgmTask;\n                                }\n\n                                this.bgm = multiBgm;\n                                if (willSwitchBgm && this.bgm != null)\n                                {\n                                    bgmTask = FadeIn(this.bgm, 1000);\n                                }\n                            }\n                            else\n                            {\n                                this.ui.ChatBox.AppendTextHelp($\"请输入正确的多重BGM名称\");\n                            }\n                            break;\n\n                        default:\n                            this.ui.ChatBox.AppendTextHelp(@\"/multibgm list 显示多重BGM列表\");\n                            this.ui.ChatBox.AppendTextHelp(@\"/multibgm set (multiBgm) 播放指定的多重BGM\");\n                            break;\n                    }\n                    break;\n\n                case \"/quest\":\n                    switch (arguments.ElementAtOrDefault(1))\n                    {\n                        case \"list\":\n                            List<QuestInfo> questList = this?.mapData.Scene.Back.Slots.SelectMany(item => ((BackItem)item).Quest)\n                                .Concat(this?.mapData.Scene.Layers.Nodes.SelectMany(l => ((LayerNode)l).Obj.Slots.SelectMany(item => ((ObjItem)item).Quest)))\n                                .Concat(this?.mapData.Scene.Npcs.SelectMany(item => item.Quest))\n                                .Concat(this?.mapData.Scene.Front.Slots.SelectMany(item => ((BackItem)item).Quest))\n                                .Concat(this?.mapData.Scene.Effect.Slots.Where(item => item is ParticleItem).SelectMany(item => ((ParticleItem)item).Quest))\n                                .Concat(this?.mapData.Scene.Effect.Slots.Where(item => item is ParticleItem).SelectMany(item => ((ParticleItem)item).SubItems).SelectMany(item => item.Quest))\n                                .Distinct().ToList();\n                            this.ui.ChatBox.AppendTextHelp($\"相关任务数量：({questList.Count()})\");\n                            foreach (QuestInfo item in questList)\n                            {\n                                Wz_Node questInfoNode = PluginBase.PluginManager.FindWz($@\"Quest\\QuestData\\{item.ID}.img\\QuestInfo\")\n                                    ?? PluginBase.PluginManager.FindWz($@\"Quest\\QuestInfo.img\\{item.ID}\");\n                                string questName = questInfoNode?.Nodes[\"name\"].GetValueEx<string>(null) ?? \"null\";\n                                this.ui.ChatBox.AppendTextHelp($\"  {questName}({item.ID}) / {item.State}\");\n                            }\n                            break;\n\n                        case \"set\":\n                            if (Int32.TryParse(arguments.ElementAtOrDefault(2), out int questID) && questID > -1 && Int32.TryParse(arguments.ElementAtOrDefault(3), out int questState) && questState >= -1 && questState <= 2)\n                            {\n                                this.patchVisibility.SetQuestVisible(questID, questState);\n                                this.mapData.PreloadResource(resLoader);\n                                Wz_Node questInfoNode = PluginBase.PluginManager.FindWz($@\"Quest\\QuestData\\{questID}.img\\QuestInfo\")\n                                    ?? PluginBase.PluginManager.FindWz($@\"Quest\\QuestInfo.img\\{questID}\");\n                                string questName = questInfoNode?.Nodes[\"name\"].GetValueEx<string>(null) ?? \"null\";\n                                this.ui.ChatBox.AppendTextSystem($\"{questName}({questID}) 的状态已更改为 {questState}。\");\n                            }\n                            else\n                            {\n                                this.ui.ChatBox.AppendTextSystem($\"请输入正确的任务状态。\");\n                            }\n                            break;\n\n                        default:\n                            this.ui.ChatBox.AppendTextHelp(@\"/quest list 查看相关任务列表\");\n                            this.ui.ChatBox.AppendTextHelp(@\"/quest set (questID) (questState) 设置任务的状态\");\n                            break;\n                    }\n                    break;\n\n                case \"/questex\":\n                    switch (arguments.ElementAtOrDefault(1))\n                    {\n                        case \"list\":\n                            List<QuestExInfo> questList = this?.mapData.Scene.Layers.Nodes.SelectMany(l => ((LayerNode)l).Obj.Slots.SelectMany(item => ((ObjItem)item).Questex))\n                                .Distinct().ToList();\n                            this.ui.ChatBox.AppendTextHelp($\"相关任务Key的数量：({questList.Count()})\");\n                            foreach (QuestExInfo item in questList)\n                            {\n                                Wz_Node questInfoNode = PluginBase.PluginManager.FindWz($@\"Quest\\QuestData\\{item.ID}.img\\QuestInfo\")\n                                    ?? PluginBase.PluginManager.FindWz($@\"Quest\\QuestInfo.img\\{item.ID}\");\n                                string questName = questInfoNode?.Nodes[\"name\"].GetValueEx<string>(null) ?? \"null\";\n                                this.ui.ChatBox.AppendTextHelp($\"  {questName}({item.ID}) / Key:{item.Key}, Value:{item.State}\");\n                            }\n                            break;\n\n                        case \"set\":\n                            string qkey = arguments.ElementAtOrDefault(3);\n                            if (Int32.TryParse(arguments.ElementAtOrDefault(2), out int questID) && questID > -1 && Int32.TryParse(arguments.ElementAtOrDefault(4), out int questState) && questState >= -1 && qkey != null)\n                            {\n                                this.patchVisibility.SetQuestVisible(questID, qkey, questState);\n                                this.mapData.PreloadResource(resLoader);\n                                Wz_Node questInfoNode = PluginBase.PluginManager.FindWz($@\"Quest\\QuestData\\{questID}.img\\QuestInfo\")\n                                    ?? PluginBase.PluginManager.FindWz($@\"Quest\\QuestInfo.img\\{questID}\");\n                                string questName = questInfoNode?.Nodes[\"name\"].GetValueEx<string>(null) ?? \"null\";\n                                this.ui.ChatBox.AppendTextSystem($\"{questName}({questID}, Key={qkey}) 的状态已更改为 {questState}。\");\n                            }\n                            else\n                            {\n                                this.ui.ChatBox.AppendTextSystem($\"请输入正确的任务状态。\");\n                            }\n                            break;\n\n                        default:\n                            this.ui.ChatBox.AppendTextHelp(@\"/questex list 查看相关任务Key列表\");\n                            this.ui.ChatBox.AppendTextHelp(@\"/questex set (questID) (key) (questState) 设置任务Key的状态\");\n                            break;\n                    }\n                    break;\n\n                default:\n                    this.ui.ChatBox.AppendTextSystem($\"不支持{arguments[0]}命令。\");\n                    break;\n            }\n        }\n\n        protected override void LoadContent()\n        {\n            base.LoadContent();\n            this.ui.LoadContent(this.Content);\n            this.renderEnv.Fonts.LoadContent(this.Content);\n            this.isUnloaded = false;\n        }\n\n        protected override void Update(GameTime gameTime)\n        {\n            base.Update(gameTime);\n        }\n\n        protected override void Draw(GameTime gameTime)\n        {\n            opacity = MathHelper.Clamp(opacity, 0f, 1f);\n\n            if (opacity <= 0)\n            {\n                this.GraphicsDevice.Clear(Color.Black);\n            }\n            else\n            {\n                if (prepareCapture)\n                {\n                    Capture(gameTime);\n                }\n\n                this.GraphicsDevice.Clear(Color.Black);\n                if (this.mapData != null)\n                {\n                    DrawScene(gameTime);\n                    DrawTooltipItems(gameTime);\n                }\n                this.ui.Draw(gameTime.ElapsedGameTime.TotalMilliseconds);\n                this.tooltip.Draw(gameTime, renderEnv);\n                if (opacity < 1f)\n                {\n                    this.renderEnv.Sprite.Begin(blendState: BlendState.NonPremultiplied);\n                    var rect = new Rectangle(0, 0, this.renderEnv.Camera.Width, this.renderEnv.Camera.Height);\n                    this.renderEnv.Sprite.FillRectangle(rect, new Color(Color.Black, 1 - opacity));\n                    this.renderEnv.Sprite.End();\n                }\n            }\n\n            base.Draw(gameTime);\n        }\n\n        #region 截图相关\n\n        private bool CanCapture()\n        {\n            return (captureTask == null || captureTask.IsCompleted) && !prepareCapture;\n        }\n\n        private void Capture(GameTime gameTime)\n        {\n            if (this.mapData == null)\n            {\n                prepareCapture = false;\n                return;\n            }\n\n            var oldTarget = GraphicsDevice.GetRenderTargets();\n\n            //检查显卡支持纹理大小\n            var maxTextureWidth = 4096;\n            var maxTextureHeight = 4096;\n\n            Rectangle oldRect = this.renderEnv.Camera.WorldRect;\n            int width = Math.Min(oldRect.Width, maxTextureWidth);\n            int height = Math.Min(oldRect.Height, maxTextureHeight);\n            this.renderEnv.Camera.UseWorldRect = true;\n\n            var target2d = new RenderTarget2D(this.GraphicsDevice, width, height, false, SurfaceFormat.Bgra32, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);\n            PngEffect pngEffect = null;\n\n            Color bgColor = Color.Black;\n            var config = MapRenderConfig.Default;\n            if (ColorWConverter.TryParse(config?.ScreenshotBackgroundColor?.Value, out var colorW))\n            {\n                bgColor = new Color(colorW.PackedValue);\n            }\n            Color bgColorPMA = bgColor.A switch\n            {\n                255 => bgColor,\n                0 => Color.Transparent,\n                _ => Color.FromNonPremultiplied(bgColor.ToVector4()),\n            };\n\n            //计算一组截图区\n            int horizonBlock = (int)Math.Ceiling(1.0 * oldRect.Width / width);\n            int verticalBlock = (int)Math.Ceiling(1.0 * oldRect.Height / height);\n            byte[,][] picBlocks = new byte[horizonBlock, verticalBlock][];\n            for (int j = 0; j < verticalBlock; j++)\n            {\n                for (int i = 0; i < horizonBlock; i++)\n                {\n                    //计算镜头区域\n                    this.renderEnv.Camera.WorldRect = new Rectangle(\n                        oldRect.X + i * width,\n                        oldRect.Y + j * height,\n                        width,\n                        height);\n\n                    //绘制\n                    GraphicsDevice.SetRenderTarget(target2d);\n                    GraphicsDevice.Clear(bgColorPMA);\n                    DrawScene(gameTime);\n                    //保存\n                    if (bgColor.A == 255)\n                    {\n                        Texture2D t2d = target2d;\n                        byte[] data = new byte[target2d.Width * target2d.Height * 4];\n                        t2d.GetData(data);\n                        picBlocks[i, j] = data;\n                    }\n                    else\n                    {\n                        if (pngEffect == null)\n                        {\n                            pngEffect = new PngEffect(this.GraphicsDevice);\n                            pngEffect.AlphaMixEnabled = false;\n                        }\n                        using var texture = new RenderTarget2D(this.GraphicsDevice, width, height, false, SurfaceFormat.Bgra32, DepthFormat.None);\n                        using var sb = new SpriteBatch(this.GraphicsDevice);\n                        this.GraphicsDevice.SetRenderTarget(texture);\n                        this.GraphicsDevice.Clear(Color.Transparent);\n                        sb.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, null, null, pngEffect, null);\n                        sb.Draw(target2d, Vector2.Zero, Color.White);\n                        sb.End();\n                        byte[] data = new byte[texture.Width * texture.Height * 4];\n                        texture.GetData(data);\n                        picBlocks[i, j] = data;\n                    }\n                }\n            }\n            pngEffect?.Dispose();\n            target2d.Dispose();\n\n            this.renderEnv.Camera.WorldRect = oldRect;\n            this.renderEnv.Camera.UseWorldRect = false;\n\n            GraphicsDevice.SetRenderTargets(oldTarget);\n            prepareCapture = false;\n\n            captureTask = Task.Factory.StartNew(() =>\n                SaveTexture(picBlocks, oldRect.Width, oldRect.Height, width, height, bgColor.A == 255)\n            );\n        }\n\n        private void SaveTexture(byte[,][] picBlocks, int mapWidth, int mapHeight, int blockWidth, int blockHeight, bool resetAlphaChannel)\n        {\n            //组装\n            byte[] mapData = new byte[mapWidth * mapHeight * 4];\n            for (int j = 0; j < picBlocks.GetLength(1); j++)\n            {\n                for (int i = 0; i < picBlocks.GetLength(0); i++)\n                {\n                    byte[] data = picBlocks[i, j];\n\n                    Rectangle blockRect = new Rectangle();\n                    blockRect.X = i * blockWidth;\n                    blockRect.Y = j * blockHeight;\n                    blockRect.Width = Math.Min(mapWidth - blockRect.X, blockWidth);\n                    blockRect.Height = Math.Min(mapHeight - blockRect.Y, blockHeight);\n\n                    int length = blockRect.Width * 4;\n                    if (blockRect.X == 0 && blockRect.Width == mapWidth) //整块复制\n                    {\n                        int startIndex = mapWidth * 4 * blockRect.Y;\n                        Buffer.BlockCopy(data, 0, mapData, startIndex, blockRect.Width * blockRect.Height * 4);\n                    }\n                    else //逐行扫描\n                    {\n                        int offset = 0;\n                        for (int y = blockRect.Top, y0 = blockRect.Bottom; y < y0; y++)\n                        {\n                            int startIndex = (y * mapWidth + blockRect.X) * 4;\n                            Buffer.BlockCopy(data, offset, mapData, startIndex, length);\n                            offset += blockWidth * 4;\n                        }\n                    }\n                }\n            }\n\n            if (resetAlphaChannel)\n            {\n                for (int i = 0; i < mapData.Length; i += 4)\n                {\n                    mapData[i + 3] = 255;\n                }\n            }\n\n            try\n            {\n                using System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(\n                    mapWidth,\n                    mapHeight,\n                    mapWidth * 4,\n                    System.Drawing.Imaging.PixelFormat.Format32bppArgb,\n                    Marshal.UnsafeAddrOfPinnedArrayElement(mapData, 0));\n\n                string outputFileName = DateTime.Now.ToString(\"yyyyMMddHHmmssfff\") + \"_\" + (this.mapData?.ID ?? 0).ToString(\"D9\") + \".png\";\n                bitmap.Save(outputFileName, System.Drawing.Imaging.ImageFormat.Png);\n\n                this.cm.StartCoroutine(cm.Post((v) =>\n                {\n                    v.ui.ChatBox.AppendTextHelp($\"截图保存到文件{v.outputFileName}\");\n                }, new\n                {\n                    this.ui,\n                    outputFileName\n                }));\n            }\n            catch\n            {\n            }\n        }\n        #endregion\n\n        #region 配置文件相关\n        private void ApplySetting()\n        {\n            var config = MapRenderConfig.Default;\n            Music.GlobalVolume = config.Volume;\n            this.renderEnv.Camera.AdjustRectEnabled = config.ClipMapRegion;\n            if (config.TopBarVisible)\n            {\n                this.ui.TopBar.Show();\n            }\n            else\n            {\n                this.ui.TopBar.Hide();\n            }\n            this.ui.Minimap.CameraRegionVisible = config.Minimap_CameraRegionVisible;\n            this.ui.WorldMap.UseImageNameAsInfoName = config.WorldMap_UseImageNameAsInfoName;\n            this.batcher.D2DEnabled = config.UseD2dRenderer;\n            (this.Content as WcR2ContentManager).UseD2DFont = config.UseD2dRenderer;\n        }\n\n        private void LoadOptionData(UIOptionsDataModel model)\n        {\n            var config = MapRenderConfig.Default;\n            model.SelectedFont = config.DefaultFontIndex;\n            model.Volume = Music.GlobalVolume;\n            model.MuteOnLeaveFocus = config.MuteOnLeaveFocus;\n            model.ClipMapRegion = renderEnv.Camera.AdjustRectEnabled;\n            model.UseD2dRenderer = config.UseD2dRenderer;\n            model.NpcNameVisible = this.patchVisibility.NpcNameVisible;\n            model.MobNameVisible = this.patchVisibility.MobNameVisible;\n            model.TopBarVisible = this.ui.TopBar.Visibility == EmptyKeys.UserInterface.Visibility.Visible;\n            model.ScreenshotBackgroundColor = config.ScreenshotBackgroundColor;\n            model.Minimap_CameraRegionVisible = this.ui.Minimap.CameraRegionVisible;\n            model.WorldMap_UseImageNameAsInfoName = this.ui.WorldMap.UseImageNameAsInfoName;\n        }\n\n        private void SaveOptionData(UIOptionsDataModel model)\n        {\n            WzComparerR2.Config.ConfigManager.Reload();\n            var config = MapRenderConfig.Default;\n            config.DefaultFontIndex = model.SelectedFont;\n            config.Volume = model.Volume;\n            config.MuteOnLeaveFocus = model.MuteOnLeaveFocus;\n            config.ClipMapRegion = model.ClipMapRegion;\n            config.UseD2dRenderer = model.UseD2dRenderer;\n            this.patchVisibility.NpcNameVisible = model.NpcNameVisible;\n            this.patchVisibility.MobNameVisible = model.MobNameVisible;\n            config.TopBarVisible = model.TopBarVisible;\n            config.ScreenshotBackgroundColor = model.ScreenshotBackgroundColor;\n            config.Minimap_CameraRegionVisible = model.Minimap_CameraRegionVisible;\n            config.WorldMap_UseImageNameAsInfoName = model.WorldMap_UseImageNameAsInfoName;\n            WzComparerR2.Config.ConfigManager.Save();\n        }\n        #endregion\n\n        protected override void UnloadContent()\n        {\n            base.UnloadContent();\n\n            if (!this.isUnloaded)\n            {\n                this.resLoader?.Unload();\n                this.ui?.UnloadContents();\n                this.Content.Unload();\n                this.imeHelper?.Dispose();\n                this.bgm = null;\n                this.mapImg = null;\n                this.mapData = null;\n                this.isUnloaded = true;\n            }\n        }\n\n        private void OnExiting()\n        {\n            if (this.isExiting)\n            {\n                return;\n            }\n\n            this.batcher?.Dispose();\n            this.batcher = null;\n            this.renderEnv?.Dispose();\n            this.renderEnv = null;\n            this.lightRenderer?.Dispose();\n            this.lightRenderer = null;\n            this.engine = null;\n\n            foreach (var disposable in this.attachedEvent)\n            {\n                disposable.Dispose();\n            }\n            this.attachedEvent.Clear();\n            this.ui?.InputBindings.Clear();\n\n            GameExt.RemoveKeyboardEvent(this);\n            GameExt.RemoveMouseStateCache();\n            WcR2Engine.Unload();\n            ServiceManager.Instance.RemoveService<IMEHandler>();\n            this.isExiting = true;\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                this.UnloadContent();\n                this.OnExiting();\n            }\n            base.Dispose(disposing);\n        }\n\n        private void SwitchResolution()\n        {\n            var r = (Resolution)(((int)this.resolution + 1) % 4);\n            SwitchResolution(r);\n        }\n\n        private void SwitchResolution(Resolution r)\n        {\n            Form gameWindow = (Form)Form.FromHandle(this.Window.Handle);\n            switch (r)\n            {\n                case Resolution.Window_800_600:\n                case Resolution.Window_1024_768:\n                case Resolution.Window_1366_768:\n                    gameWindow.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n                    break;\n                case Resolution.WindowFullScreen:\n                    gameWindow.SetDesktopLocation(0, 0);\n                    gameWindow.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;\n                    break;\n                default:\n                    r = Resolution.Window_800_600;\n                    goto case Resolution.Window_800_600;\n            }\n\n            this.resolution = r;\n            this.renderEnv.Camera.DisplayMode = (int)r;\n            this.ui.Width = this.renderEnv.Camera.Width;\n            this.ui.Height = this.renderEnv.Camera.Height;\n            engine.Renderer.ResetNativeSize();\n        }\n\n        private IDisposable EventDisposable<TDelegate>(TDelegate arg, Action<TDelegate> action)\n        {\n            return new Disposable<TDelegate>(action, arg);\n        }\n\n        enum Resolution\n        {\n            Window_800_600 = 0,\n            Window_1024_768 = 1,\n            Window_1366_768 = 2,\n            WindowFullScreen = 3,\n        }\n\n        struct ItemRect\n        {\n            public SceneItem item;\n            public Rectangle rect;\n        }\n\n        class Disposable<TDelegate> : IDisposable\n        {\n            public Disposable(Action<TDelegate> action, TDelegate arg)\n            {\n                this.Action = action;\n                this.Arg = arg;\n            }\n\n            public readonly Action<TDelegate> Action;\n            public readonly TDelegate Arg;\n\n            public void Dispose()\n            {\n                this.Action?.Invoke(this.Arg);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/GameExt.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nusing SharpDX;\nusing SharpDX.RawInput;\nusing SharpDX.Multimedia;\nusing Microsoft.Xna.Framework;\nusing System.Windows.Forms;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.MapRender\n{\n    static class GameExt\n    {\n        public static void FixKeyboard(Game game)\n        {\n            IntPtr hWnd = game.Window.Handle;\n            Device.RegisterDevice(UsagePage.Generic, UsageId.GenericKeyboard, DeviceFlags.None, hWnd, RegisterDeviceOptions.Default);\n            \n            if(!filterCache.ContainsKey(hWnd))\n            {\n                var filter = new RawInputMessageFilter();\n                filterCache[hWnd] = filter;\n                MessageFilterHook.AddMessageFilter(hWnd, filter);\n            }\n        }\n\n\n        public static void ReleaseKeyboard(Game game)\n        {\n            if (game == null || game.Window == null)\n            {\n                return;\n            }\n\n            RawInputMessageFilter filter;\n            IntPtr hWnd = game.Window.Handle;\n\n            if (filterCache.TryGetValue(hWnd, out filter))\n            {\n                MessageFilterHook.RemoveMessageFilter(hWnd, filter);\n                filterCache.Remove(hWnd);\n            }\n        }\n\n        public static void RemoveKeyboardEvent(Game game)\n        {\n            if (game == null || game.Window == null)\n            {\n                return;\n            }\n            var fieldInfo = typeof(Device).GetField(\"KeyboardInput\", BindingFlags.Static | BindingFlags.NonPublic);\n            var value = (EventHandler<KeyboardInputEventArgs>)fieldInfo.GetValue(null);\n            var methodInfo = game.Window.GetType().GetMethod(\"OnRawKeyEvent\", BindingFlags.Instance | BindingFlags.NonPublic);\n            if (value != null)\n            {\n                var fn = (EventHandler<KeyboardInputEventArgs>)Delegate.CreateDelegate(typeof(EventHandler<KeyboardInputEventArgs>), game.Window, methodInfo);\n                value -= fn;\n                fieldInfo.SetValue(null, value);\n            }\n        }\n\n        public static void RemoveMouseStateCache()\n        {\n            var fieldInfo = typeof(Microsoft.Xna.Framework.Input.Mouse).GetField(\"PrimaryWindow\", BindingFlags.Static | BindingFlags.NonPublic);\n            if (fieldInfo != null)\n            {\n                fieldInfo.SetValue(null, null);\n            }\n\n            fieldInfo = typeof(Microsoft.Xna.Framework.Input.Mouse).GetField(\"Window\", BindingFlags.Static | BindingFlags.NonPublic);\n            if (fieldInfo != null)\n            {\n                fieldInfo.SetValue(null, null);\n            }\n\n            fieldInfo = typeof(Microsoft.Xna.Framework.Input.Touch.TouchPanel).GetField(\"PrimaryWindow\", BindingFlags.Static | BindingFlags.NonPublic);\n            if (fieldInfo != null)\n            {\n                fieldInfo.SetValue(null, null);\n            }\n        }\n\n        public static void EnsureGameExit(Game game)\n        {\n            var tid = GetCurrentThreadId();\n            bool success = PostThreadMessage(tid, WM_QUIT, IntPtr.Zero, IntPtr.Zero);\n        }\n\n        public static T GetService<T>(this IServiceProvider services) where T : class\n        {\n            return services.GetService(typeof(T)) as T;\n        }\n\n        private static Dictionary<IntPtr, RawInputMessageFilter> filterCache = new Dictionary<IntPtr, RawInputMessageFilter>();\n\n        class RawInputMessageFilter : IMessageFilter\n        {\n            public virtual bool PreFilterMessage(ref Message m)\n            {\n                if (m.Msg == 0xff)\n                    Device.HandleMessage(m.LParam, m.HWnd);\n                return false;\n            }\n        }\n\n        [DllImport(\"kernel32\")]\n        static extern int GetCurrentThreadId();\n\n        [DllImport(\"user32\")]\n        static extern bool PostThreadMessage(int tid, int msg, IntPtr wparam, IntPtr lparam);\n\n        const int WM_QUIT = 0x12;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/IRandom.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public interface IRandom\n    {\n        float NextVar(float baseValue, float varRange, bool nonNegative = false);\n        int NextVar(int baseValue, int varRange, bool nonNegative = false);\n        Vector2 NextVar(Vector2 baseValue, Vector2 varRange);\n        Color NextVar(Color baseValue, Color varRange);\n        int Next(int maxValue);\n        bool NextPercent(float percent);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/ITooltip.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public interface ITooltip\n    {\n        Rectangle TooltipSenseRegion { get; }\n        bool TooltipDisplayed { get; set; }\n        string TooltipTitle { get; }\n        string TooltipContent { get; }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/IWcR2Font.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender\n{\n    public interface IWcR2Font\n    {\n        float Size { get; }\n        float LineHeight { get; set; }\n        object BaseFont { get; }\n        Vector2 MeasureString(string text);\n        Vector2 MeasureString(StringBuilder text);\n    }\n\n    public class D2DFontAdapter : IWcR2Font\n    {\n        public D2DFontAdapter(D2DFont baseFont)\n        {\n            if (baseFont == null)\n            {\n                throw new ArgumentNullException(\"baseFont\");\n            }\n            this._baseFont = baseFont;\n        }\n\n        public float Size { get { return this._baseFont.Size; } }\n        public float LineHeight\n        {\n            get { return this._baseFont.Height; }\n            set { this._baseFont.Height = value; }\n        }\n        public object BaseFont { get { return this._baseFont; } }\n\n        private readonly D2DFont _baseFont;\n\n        public Vector2 MeasureString(string text)\n        {\n            return this._baseFont.MeasureString(text);\n        }\n\n        public Vector2 MeasureString(StringBuilder text)\n        {\n            return this._baseFont.MeasureString(text.ToString());\n        }\n    }\n\n    public class XnaFontAdapter : IWcR2Font, IDisposable\n    {\n        public XnaFontAdapter(XnaFont baseFont)\n        {\n            if (baseFont == null)\n            {\n                throw new ArgumentNullException(\"baseFont\");\n            }\n            this._baseFont = baseFont;\n        }\n\n        public float Size { get { return this._baseFont.Height; } }\n        public float LineHeight\n        {\n            get { return this._baseFont.Height; }\n            set { this._baseFont.Height = (int)value; }\n        }\n        public object BaseFont { get { return this._baseFont; } }\n\n        private readonly XnaFont _baseFont;\n\n        public Vector2 MeasureString(string text)\n        {\n            return this._baseFont.MeasureString(text);\n        }\n\n        public Vector2 MeasureString(StringBuilder text)\n        {\n            return this._baseFont.MeasureString(text);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                this._baseFont.Dispose();\n            }\n        }\n\n        public void Dispose()\n        {\n            Dispose(true);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/InputState.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Input;\nusing System.Reflection;\n\nnamespace WzComparerR2.MapRender\n{\n    public class InputState\n    {\n        public InputState()\n        {\n\n        }\n\n        public InputState(Game game)\n        {\n            _gameWindow = game.Window;\n        }\n\n        private GameWindow _gameWindow;\n\n        private KeyboardState CurrentKeyboardState;\n        private KeyboardState LastKeyboardState;\n\n        private MouseState CurrentMouseState;\n        private MouseState LastMouseState;\n\n        /// <summary>\n        /// 更新当前输入设备的信息，这个方法应该在游戏执行Update时调用。\n        /// </summary>\n        public void Update(GameTime gameTime)\n        {\n            LastKeyboardState = CurrentKeyboardState;\n            CurrentKeyboardState = Keyboard.GetState();\n\n            LastMouseState = CurrentMouseState;\n            CurrentMouseState = _gameWindow != null ? Mouse.GetState(_gameWindow) : Mouse.GetState();\n        }\n\n        /// <summary>\n        /// 检查最后一次更新前，指定的key是否被按下。\n        /// </summary>\n        /// <param name=\"key\">要检查的key。</param>\n        /// <returns></returns>\n        public bool IsKeyDown(Keys key)\n        {\n            return CurrentKeyboardState.IsKeyDown(key) && LastKeyboardState.IsKeyUp(key);\n        }\n\n        /// <summary>\n        /// 检查最后一次更新前，指定的key是否弹起。\n        /// </summary>\n        /// <param name=\"key\">要检查的key。</param>\n        /// <returns></returns>\n        public bool IsKeyUp(Keys key)\n        {\n            return CurrentKeyboardState.IsKeyUp(key) && LastKeyboardState.IsKeyDown(key);\n        }\n\n        /// <summary>\n        /// 检查当前指定的key是否正在被按下的状态。\n        /// </summary>\n        /// <param name=\"key\">要检查的key。</param>\n        /// <returns></returns>\n        public bool IsKeyPressing(Keys key)\n        {\n            return CurrentKeyboardState.IsKeyDown(key);\n        }\n\n        public bool IsCtrlPressing\n        {\n            get\n            {\n                return this.IsKeyPressing(Keys.LeftControl) || this.IsKeyPressing(Keys.RightControl);\n            }\n        }\n\n        public bool IsAltPressing\n        {\n            get\n            {\n                return this.IsKeyPressing(Keys.LeftAlt) || this.IsKeyPressing(Keys.RightAlt);\n            }\n        }\n\n        public bool IsShiftPressing\n        {\n            get\n            {\n                return this.IsKeyPressing(Keys.LeftShift) || this.IsKeyPressing(Keys.RightShift);\n            }\n        }\n\n        /// <summary>\n        /// 获取或设置当前的鼠标指针位置。\n        /// </summary>\n        public Point MousePosition\n        {\n            get { return new Point(CurrentMouseState.X, CurrentMouseState.Y); }\n            set { Mouse.SetPosition(value.X, value.Y); CurrentMouseState = Mouse.GetState(); }\n        }\n\n        public Point MousePositionLast\n        {\n            get { return new Point(LastMouseState.X, LastMouseState.Y); }\n        }\n\n        /// <summary>\n        /// 检查最后一次更新前，指定的鼠标按键是否被按下。\n        /// </summary>\n        /// <param name=\"button\">要检查的鼠标按键的组合。</param>\n        /// <returns></returns>\n        public bool IsMouseButtonDown(MouseButton button)\n        {\n            MouseButton[] baseButtons = (MouseButton[])Enum.GetValues(typeof(MouseButton));\n            bool isBtnDown = false;\n            foreach (MouseButton baseBtn in baseButtons)\n            {\n                if ((int)(button & baseBtn) != 0)\n                    isBtnDown |= IsSingleMouseButtonDown(baseBtn);\n                if (isBtnDown)\n                    break;\n            }\n            return isBtnDown;\n        }\n\n        private bool IsSingleMouseButtonDown(MouseButton button)\n        {\n            switch (button)\n            {\n                case MouseButton.LeftButton:\n                    return CurrentMouseState.LeftButton == ButtonState.Pressed &&\n                        LastMouseState.LeftButton == ButtonState.Released;\n                case MouseButton.MiddleButton:\n                    return CurrentMouseState.MiddleButton == ButtonState.Pressed &&\n                        LastMouseState.MiddleButton == ButtonState.Released;\n                case MouseButton.RightButton:\n                    return CurrentMouseState.RightButton == ButtonState.Pressed &&\n                        LastMouseState.RightButton == ButtonState.Released;\n                case MouseButton.XButton1:\n                    return CurrentMouseState.XButton1 == ButtonState.Pressed &&\n                        LastMouseState.XButton1 == ButtonState.Released;\n                case MouseButton.XButton2:\n                    return CurrentMouseState.XButton2 == ButtonState.Pressed &&\n                        LastMouseState.XButton2 == ButtonState.Released;\n                default:\n                    return false;\n            }\n        }\n\n        /// <summary>\n        /// 检查最后一次更新前，指定的鼠标按键是否弹起.\n        /// </summary>\n        /// <param name=\"button\">要检查的鼠标按键的组合。</param>\n        /// <returns></returns>\n        public bool IsMouseButtonUp(MouseButton button)\n        {\n            MouseButton[] baseButtons = (MouseButton[])Enum.GetValues(typeof(MouseButton));\n            bool isBtnUp = false;\n            foreach (MouseButton baseBtn in baseButtons)\n            {\n                if ((int)(button & baseBtn) != 0)\n                    isBtnUp |= IsSingleMouseButtonUp(baseBtn);\n                if (isBtnUp)\n                    break;\n            }\n            return isBtnUp;\n        }\n\n        private bool IsSingleMouseButtonUp(MouseButton button)\n        {\n            switch (button)\n            {\n                case MouseButton.LeftButton:\n                    return CurrentMouseState.LeftButton == ButtonState.Released &&\n                        LastMouseState.LeftButton == ButtonState.Pressed;\n                case MouseButton.MiddleButton:\n                    return CurrentMouseState.MiddleButton == ButtonState.Released &&\n                        LastMouseState.MiddleButton == ButtonState.Pressed;\n                case MouseButton.RightButton:\n                    return CurrentMouseState.RightButton == ButtonState.Released &&\n                        LastMouseState.RightButton == ButtonState.Pressed;\n                case MouseButton.XButton1:\n                    return CurrentMouseState.XButton1 == ButtonState.Released &&\n                        LastMouseState.XButton1 == ButtonState.Pressed;\n                case MouseButton.XButton2:\n                    return CurrentMouseState.XButton2 == ButtonState.Released &&\n                        LastMouseState.XButton2 == ButtonState.Pressed;\n                default:\n                    return false;\n            }\n        }\n\n        public bool IsMouseButtonPressing(MouseButton button)\n        {\n            MouseButton[] baseButtons = (MouseButton[])Enum.GetValues(typeof(MouseButton));\n            bool isBtnPressing = false;\n            foreach (MouseButton baseBtn in baseButtons)\n            {\n                if ((int)(button & baseBtn) != 0)\n                    isBtnPressing |= IsSingleMouseButtonPressing(baseBtn);\n                if (isBtnPressing)\n                    break;\n            }\n            return isBtnPressing;\n        }\n\n        private bool IsSingleMouseButtonPressing(MouseButton button)\n        {\n            switch (button)\n            {\n                case MouseButton.LeftButton:\n                    return CurrentMouseState.LeftButton == ButtonState.Pressed;\n                case MouseButton.MiddleButton:\n                    return CurrentMouseState.MiddleButton == ButtonState.Pressed;\n                case MouseButton.RightButton:\n                    return CurrentMouseState.RightButton == ButtonState.Pressed;\n                case MouseButton.XButton1:\n                    return CurrentMouseState.XButton1 == ButtonState.Pressed;\n                case MouseButton.XButton2:\n                    return CurrentMouseState.XButton2 == ButtonState.Pressed;\n                default:\n                    return false;\n            }\n        }\n\n        /// <summary>\n        /// 获取最后一次更新前，鼠标滚轮滚动变化值。\n        /// </summary>\n        /// <returns></returns>\n        public int GetMouseWheelScrolledValue()\n        {\n            return CurrentMouseState.ScrollWheelValue - LastMouseState.ScrollWheelValue;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/LifeInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender\n{\n    public class LifeInfo\n    {\n        public LifeInfo()\n        {\n            this.speed = -100;\n        }\n\n        public int level;\n        public long maxHP;\n        public long maxMP;\n        public int speed;\n        public int PADamage;\n        public int PDDamage;\n        public int PDRate;\n        public int MADamage;\n        public int MDDamage;\n        public int MDRate;\n        public int acc;\n        public int eva;\n        public int pushed;\n        public int exp;\n        public ElemAttr elemAttr;\n        public bool undead;\n        public bool boss;\n\n        public struct ElemAttr\n        {\n            public ElemResistance I;\n            public ElemResistance L;\n            public ElemResistance F;\n            public ElemResistance S;\n            public ElemResistance H;\n            public ElemResistance D;\n            public ElemResistance P;\n        }\n\n        public enum ElemResistance : byte\n        {\n            Normal = 0,\n            Immune = 1,\n            Resist = 2,\n            Weak = 3,\n        }\n\n        public static LifeInfo CreateFromNode(Wz_Node mobNode)\n        {\n            if (mobNode == null)\n            {\n                return null;\n            }\n\n            var lifeInfo = new LifeInfo();\n            var infoNode = mobNode.Nodes[\"info\"];\n\n            if (infoNode != null)\n            {\n                foreach (Wz_Node node in infoNode.Nodes)\n                {\n                    switch (node.Text)\n                    {\n                        case \"level\": lifeInfo.level = node.GetValueEx<int>(0); break;\n                        case \"maxHP\": lifeInfo.maxHP = node.GetValueEx<long>(0); break;\n                        case \"maxMP\": lifeInfo.maxMP = node.GetValueEx<long>(0); break;\n                        case \"speed\": lifeInfo.speed = node.GetValueEx<int>(0); break;\n                        case \"PADamage\": lifeInfo.PADamage = node.GetValueEx<int>(0); break;\n                        case \"PDDamage\": lifeInfo.PDDamage = node.GetValueEx<int>(0); break;\n                        case \"PDRate\": lifeInfo.PDRate = node.GetValueEx<int>(0); break;\n                        case \"MADamage\": lifeInfo.MADamage = node.GetValueEx<int>(0); break;\n                        case \"MDDamage\": lifeInfo.MDDamage = node.GetValueEx<int>(0); break;\n                        case \"MDRate\": lifeInfo.MDRate = node.GetValueEx<int>(0); break;\n                        case \"acc\": lifeInfo.acc = node.GetValueEx<int>(0); break;\n                        case \"eva\": lifeInfo.eva = node.GetValueEx<int>(0); break;\n                        case \"pushed\": lifeInfo.pushed = node.GetValueEx<int>(0); break;\n                        case \"exp\": lifeInfo.exp = node.GetValueEx<int>(0); break;\n                        case \"undead\": lifeInfo.undead = node.GetValueEx<int>(0) != 0; break;\n                        case \"boss\": lifeInfo.boss = node.GetValueEx<int>(0) != 0; break;\n                        case \"elemAttr\":\n                            string elem = node.GetValueEx<string>(string.Empty);\n                            for (int i = 0; i < elem.Length; i += 2)\n                            {\n                                LifeInfo.ElemResistance resist = (LifeInfo.ElemResistance)(elem[i + 1] - 48);\n                                switch (elem[i])\n                                {\n                                    case 'I': lifeInfo.elemAttr.I = resist; break;\n                                    case 'L': lifeInfo.elemAttr.L = resist; break;\n                                    case 'F': lifeInfo.elemAttr.F = resist; break;\n                                    case 'S': lifeInfo.elemAttr.S = resist; break;\n                                    case 'H': lifeInfo.elemAttr.H = resist; break;\n                                    case 'D': lifeInfo.elemAttr.D = resist; break;\n                                    case 'P': lifeInfo.elemAttr.P = resist; break;\n                                }\n                            }\n                            break;\n                    }\n                }\n            }\n            return lifeInfo;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/LightRenderer.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender\n{\n    public class LightRenderer : IDisposable\n    {\n        public LightRenderer(GraphicsDevice graphicsDevice) : this(graphicsDevice, 2048)\n        {\n\n        }\n\n        public LightRenderer(GraphicsDevice graphicsDevice, int capacity)\n        {\n            this.graphicsDevice = graphicsDevice;\n            this.effect = new BasicEffect(graphicsDevice)\n            {\n                FogEnabled = false,\n                LightingEnabled = false,\n                VertexColorEnabled = true,\n            };\n            this.blendState = new BlendState()\n            {\n                AlphaSourceBlend = Blend.Zero,\n                AlphaDestinationBlend = Blend.One,\n                AlphaBlendFunction = BlendFunction.Add,\n                ColorSourceBlend = Blend.One,\n                ColorDestinationBlend = Blend.One,\n                ColorBlendFunction = BlendFunction.Add,\n            };\n\n            if (capacity > 0)\n            {\n                this.vertices = new VertexPosition2ColorTexture[capacity];\n                this.indices = new int[capacity * 3];\n            }\n        }\n\n        private readonly GraphicsDevice graphicsDevice;\n        private readonly BasicEffect effect;\n        private readonly BlendState blendState;\n        private bool isInBeginEndPair = false;\n        private VertexPosition2ColorTexture[] vertices;\n        private int numVertices;\n        private int[] indices;\n        private int primitiveCount;\n        private Texture2D texture;\n        private Matrix world;\n        private bool isDisposed;\n\n        public void Begin(Matrix world)\n        {\n            if (this.isInBeginEndPair)\n            {\n               throw new InvalidOperationException(\"Begin cannot be called again until End has been successfully called.\");\n            }\n            \n            this.world = world;\n            this.isInBeginEndPair = true;\n        }\n\n        public void DrawSpotLight(Light2D light)\n        {\n            if (light == null)\n            {\n                throw new ArgumentNullException(nameof(light));\n            }\n            this.ValidCheck();\n\n            const int arcPoints = 36;\n            float innerAngle = light.InnerAngle;\n            float outerAngle = light.OuterAngle;\n            bool isCircle = (innerAngle == outerAngle);\n\n            if (innerAngle <= outerAngle)\n            {\n                innerAngle += 360f;\n            }\n            var radStart = MathHelper.ToRadians(outerAngle);\n            var radEnd = MathHelper.ToRadians(innerAngle);\n            float rotationIncrement = (radEnd - radStart) / arcPoints;\n\n            // angle: up=0, right=90, down=180, left=-90\n            // x=sin(d), y=-cos(d)\n\n            var center = new Vector2(light.X, light.Y);\n            var color = light.Color;\n            var radiusSteps = light.InnerRadius == 0 ? new float[] { light.OuterRadius } : new float[] { light.InnerRadius, light.OuterRadius };\n            var colorSteps = light.InnerRadius == 0 ? new[] { Color.Transparent } : new[] { light.Color, Color.Transparent };\n            int numPointsPerArc = arcPoints + (isCircle ? 0 : 1);\n\n            int numVertices = 1 + numPointsPerArc * radiusSteps.Length;\n            int primitiveCount = arcPoints * (radiusSteps.Length * 2 - 1);\n            this.AcquireBuffer(numVertices, primitiveCount, null,\n                out Span<VertexPosition2ColorTexture> vertexBuffer,\n                out Span<int> indexBuffer,\n                out int vertexIndexStart);\n            int indexCur = 0;\n\n            vertexBuffer[0] = new VertexPosition2ColorTexture(center, color);\n\n            for (int i = 0; i < arcPoints; i++)\n            {\n                var angle = radStart + rotationIncrement * i;\n                Vector2 direction = new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle));\n                vertexBuffer[i + 1] = new VertexPosition2ColorTexture(center + direction * radiusSteps[0], colorSteps[0]);\n                indexBuffer[indexCur++] = 0;\n                indexBuffer[indexCur++] = i + 1;\n                indexBuffer[indexCur++] = i + 2;\n\n                for (int s = 1; s < radiusSteps.Length; s++)\n                {\n                    vertexBuffer[i + 1 + numPointsPerArc] = new VertexPosition2ColorTexture(center + direction * radiusSteps[s], colorSteps[s]);\n                    int baseVertexIndex = i + numPointsPerArc * (s - 1) + 1;\n                    indexBuffer[indexCur++] = baseVertexIndex;\n                    indexBuffer[indexCur++] = baseVertexIndex + numPointsPerArc;\n                    indexBuffer[indexCur++] = baseVertexIndex + 1;\n                    indexBuffer[indexCur++] = baseVertexIndex + numPointsPerArc;\n                    indexBuffer[indexCur++] = baseVertexIndex + 1 + numPointsPerArc;\n                    indexBuffer[indexCur++] = baseVertexIndex + 1;\n                }\n            }\n\n            if (isCircle)\n            {\n                int lastLoopStartIndex = 3 * (arcPoints - 1) * (radiusSteps.Length * 2 - 1);\n                indexBuffer[lastLoopStartIndex + 2] = 1;\n                for (int s = 1; s < radiusSteps.Length; s++)\n                {\n                    int baseIndex = lastLoopStartIndex + 3 + (s - 1) * 6;\n                    indexBuffer[baseIndex + 2] = 1 + numPointsPerArc * (s - 1);\n                    indexBuffer[baseIndex + 4] = 1 + numPointsPerArc * s;\n                    indexBuffer[baseIndex + 5] = indexBuffer[baseIndex + 2];\n                }\n            }\n            else\n            {\n                Vector2 direction = new Vector2((float)Math.Sin(radEnd), -(float)Math.Cos(radEnd));\n                vertexBuffer[numPointsPerArc] = new VertexPosition2ColorTexture(center + direction * radiusSteps[0], colorSteps[0]);\n\n                for (int s = 1; s < radiusSteps.Length; s++)\n                {\n                    vertexBuffer[numPointsPerArc * (s + 1)] = new VertexPosition2ColorTexture(center + direction * radiusSteps[s], colorSteps[s]);\n                }\n            }\n\n            // indexBuffer add vertex offset\n            for (int i = 0; i < indexBuffer.Length; i++)\n            {\n                indexBuffer[i] += vertexIndexStart;\n            }\n        }\n\n        public void DrawTextureLight(Texture2D texture, Vector2 position, Rectangle? srcRect = default, Vector2 origin = default, bool flipX = false, Color? color = default)\n        {\n            if (texture == null)\n            {\n                throw new ArgumentNullException(nameof(texture));\n            }\n            this.ValidCheck();\n\n            this.AcquireBuffer(4, 2, texture,\n               out Span<VertexPosition2ColorTexture> vertexBuffer,\n               out Span<int> indexBuffer,\n               out int vertexIndexStart);\n\n            var sourceRect = srcRect ?? new Rectangle(0, 0, texture.Width, texture.Height);\n            float srcLeft, srcTop, srcRight, srcBottom;\n            float dstLeft, dstTop, dstRight, dstBottom;\n            if (flipX)\n            {\n                srcLeft = (float)sourceRect.Right / texture.Width;\n                srcRight = (float)sourceRect.Left / texture.Width;\n                dstRight = position.X + origin.X;\n                dstLeft = dstRight - sourceRect.Width;\n            }\n            else\n            {\n                srcLeft = (float)sourceRect.Left / texture.Width;\n                srcRight = (float)sourceRect.Right / texture.Width;\n                dstLeft = position.X - origin.X;\n                dstRight = dstLeft + sourceRect.Width;\n            }\n            srcTop = (float)sourceRect.Top / texture.Height;\n            srcBottom = (float)sourceRect.Bottom / texture.Height;\n            dstTop = position.Y - origin.Y;\n            dstBottom = dstTop + sourceRect.Height;\n\n            Color vertexColor = color ?? Color.White;\n            vertexBuffer[0] = new VertexPosition2ColorTexture(new Vector2(dstLeft, dstTop), vertexColor, new Vector2(srcLeft, srcTop));\n            vertexBuffer[1] = new VertexPosition2ColorTexture(new Vector2(dstRight, dstTop), vertexColor, new Vector2(srcRight, srcTop));\n            vertexBuffer[2] = new VertexPosition2ColorTexture(new Vector2(dstLeft, dstBottom), vertexColor, new Vector2(srcLeft, srcBottom));\n            vertexBuffer[3] = new VertexPosition2ColorTexture(new Vector2(dstRight, dstBottom), vertexColor, new Vector2(srcRight, srcBottom));\n            indexBuffer[0] = vertexIndexStart + 0;\n            indexBuffer[1] = vertexIndexStart + 1;\n            indexBuffer[2] = vertexIndexStart + 2;\n            indexBuffer[3] = vertexIndexStart + 2;\n            indexBuffer[4] = vertexIndexStart + 1;\n            indexBuffer[5] = vertexIndexStart + 3;\n        }\n\n        public void End()\n        {\n            if (!this.isInBeginEndPair)\n            {\n                throw new InvalidOperationException(\"Begin must be called before calling End.\");\n            }\n\n            this.ValidCheck();\n            this.Flush();\n            this.isInBeginEndPair = false;\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (!isDisposed)\n            {\n                if (disposing)\n                {\n                    this.effect?.Dispose();\n                    this.blendState?.Dispose();\n                }\n\n                this.vertices = null;\n                this.indices = null;\n                isDisposed = true;\n            }\n        }\n\n        private void ValidCheck()\n        {\n            if (this.isDisposed)\n            {\n                throw new ObjectDisposedException(nameof(LightRenderer));\n            }\n            if (!this.isInBeginEndPair)\n            {\n                throw new InvalidOperationException(\"Begin must be called successfully before you can call Draw.\");\n            }\n        }\n\n        private void AcquireBuffer(int vertexCount, int primitiveCount, Texture2D texture, out Span<VertexPosition2ColorTexture> vertexBuffer, out Span<int> indexBuffer, out int vertexIndexStart)\n        {\n            int requireIndexBufferSize = primitiveCount * 3;\n            if (this.vertices == null || this.indices == null)\n            {\n                this.vertices = new VertexPosition2ColorTexture[vertexCount];\n                this.indices = new int[requireIndexBufferSize];\n                vertexBuffer = this.vertices;\n                indexBuffer = this.indices;\n                vertexIndexStart = 0;\n                this.numVertices = vertexCount;\n                this.primitiveCount = primitiveCount;\n                return;\n            }\n\n            if (this.vertices.Length - this.numVertices < vertexCount \n                || this.indices.Length - this.primitiveCount * 3 < requireIndexBufferSize\n                || this.texture != texture)\n            {\n                this.Flush();\n\n                // if still not enough, resize buffer\n                if (this.vertices.Length < vertexCount)\n                {\n                    Array.Resize(ref this.vertices, vertexCount);\n                }\n                if (this.indices.Length < requireIndexBufferSize)\n                {\n                    Array.Resize(ref this.indices, requireIndexBufferSize);\n                }\n            }\n\n            vertexBuffer = this.vertices.AsSpan().Slice(this.numVertices, vertexCount);\n            indexBuffer = this.indices.AsSpan().Slice(this.primitiveCount * 3, requireIndexBufferSize);\n            vertexIndexStart = this.numVertices;\n            this.numVertices += vertexCount;\n            this.primitiveCount += primitiveCount;\n            this.texture = texture;\n        }\n\n        private void Flush()\n        {\n            if (this.vertices == null || this.indices == null || this.numVertices == 0 || this.primitiveCount == 0)\n            {\n                return;\n            }\n\n            Rectangle viewPort = this.graphicsDevice.Viewport.Bounds;\n            this.effect.World = this.world;\n            this.effect.Projection = Matrix.CreateOrthographicOffCenter(viewPort, 1, 0);\n            if (this.texture != null)\n            {\n                this.effect.Texture = this.texture;\n                this.effect.TextureEnabled = true;\n            }\n            else\n            {\n                this.effect.Texture = null;\n                this.effect.TextureEnabled = false;\n            }\n            this.graphicsDevice.BlendState = this.blendState;\n            this.graphicsDevice.RasterizerState = RasterizerState.CullNone;\n\n            foreach (var pass in this.effect.CurrentTechnique.Passes)\n            {\n                pass.Apply();\n                this.graphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, this.vertices, 0, this.numVertices, this.indices, 0, this.primitiveCount);\n            }\n\n            // reset buffer\n            Array.Clear(this.vertices, 0, this.numVertices);\n            Array.Clear(this.indices, 0, this.primitiveCount * 3);\n            this.numVertices = 0;\n            this.primitiveCount = 0;\n            this.effect.Texture = null;\n            this.texture = null;\n        }\n\n        [StructLayout(LayoutKind.Sequential)]\n        internal struct VertexPosition2ColorTexture : IVertexType\n        {\n            public static readonly int Size = 16;\n\n            public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(\n                new VertexElement(0, VertexElementFormat.Vector2, VertexElementUsage.Position, 0),\n                new VertexElement(8, VertexElementFormat.Color, VertexElementUsage.Color, 0),\n                new VertexElement(12, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0));\n\n            public Vector2 Position;\n            public Color Color;\n            public Vector2 TextureCoordinate;\n\n            public VertexPosition2ColorTexture(Vector2 position, Color color) : this(position, color, Vector2.Zero)\n            {\n            }\n\n            public VertexPosition2ColorTexture(Vector2 position, Color color, Vector2 texcoord)\n            {\n                Position = position;\n                Color = color;\n                TextureCoordinate = texcoord;\n            }\n\n            public override string ToString() => $\"{{position: {this.Position}, color: {this.Color}, texcoord: {this.TextureCoordinate}}}\";\n\n            VertexDeclaration IVertexType.VertexDeclaration => VertexDeclaration;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/LineListMesh.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    class LineListMesh\n    {\n        public LineListMesh(Point from, Point to, Color color)\n            : this(from, to, color, 1)\n        {\n        }\n\n        public LineListMesh(Point from, Point to, Color color, int thickness)\n            : this(new Point[] { from, to }, color, thickness)\n        {\n        }\n\n        public LineListMesh(Point[] lines, Color color)\n            : this(lines, color, 1)\n        {\n        }\n\n        public LineListMesh(Point[] lines, Color color, int thickness)\n        {\n            this.Lines = lines;\n            this.Color = color;\n            this.Thickness = thickness;\n        }\n\n        public Point[] Lines { get; set; }\n        public int Thickness { get; set; } = 1;\n        public Color Color { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MapData.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text.RegularExpressions;\n\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.MapRender.Effects;\nusing WzComparerR2.MapRender.Patches2;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.Animation;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MapData\n    {\n        public MapData(IRandom random)\n        {\n            this.Scene = new MapScene();\n            this.MiniMap = new MiniMap();\n            this.Tooltips = new List<TooltipItem>();\n            this.MapEvents = new List<MapEvent>();\n\n            this.random = random;\n        }\n\n        #region 基本信息\n        public int? ID { get; set; }\n        public string Name { get; set; }\n        public int? Link { get; set; }\n        public Rectangle VRect { get; set; }\n        public string MapMark { get; set; }\n        public string Bgm { get; set; }\n\n        public bool IsTown { get; set; }\n        public bool CanFly { get; set; }\n        public bool CanSwim { get; set; }\n        public int? ReturnMap { get; set; }\n        public bool HideMinimap { get; set; }\n        public int FieldLimit { get; set; }\n\n        public MiniMap MiniMap { get; private set; }\n        public MapLight Light { get; private set; }\n        #endregion\n\n        public MapScene Scene { get; private set; }\n        public IList<TooltipItem> Tooltips { get; private set; }\n        public List<MapEvent> MapEvents { get; private set; }\n\n        private readonly IRandom random;\n\n        public void Load(Wz_Node mapImgNode, ResourceLoader resLoader)\n        {\n            var infoNode = mapImgNode.Nodes[\"info\"];\n            if (infoNode == null)\n            {\n                throw new Exception(\"Cannot find map info node.\");\n            }\n\n            //试图读取ID\n            LoadIDOrName(mapImgNode);\n            //加载基本信息\n            LoadInfo(infoNode);\n            //读取link\n            if (this.Link != null && !FindMapByID(this.Link.Value, out mapImgNode))\n            {\n                throw new Exception(\"Cannot find or extract map link node.\");\n            }\n\n            //加载小地图\n            Wz_Node node;\n            if (!string.IsNullOrEmpty(this.MapMark))\n            {\n                node = PluginManager.FindWz(\"Map\\\\MapHelper.img\\\\mark\\\\\" + this.MapMark);\n                if (node != null)\n                {\n                    node = node.GetLinkedSourceNode(PluginManager.FindWz);\n                    this.MiniMap.MapMark = resLoader.Load<Texture2D>(node);\n                }\n            }\n            if ((node = mapImgNode.Nodes[\"miniMap\"]) != null)\n            {\n                LoadMinimap(node, resLoader);\n            }\n\n            //加载地图元件\n            if ((node = mapImgNode.Nodes[\"back\"]) != null)\n            {\n                LoadBack(node);\n            }\n            for (int i = 0; i <= 7; i++)\n            {\n                if ((node = mapImgNode.Nodes[i.ToString()]) != null)\n                {\n                    LoadLayer(node, i);\n                }\n            }\n            if ((node = mapImgNode.Nodes[\"foothold\"]) != null)\n            {\n                for (int i = 0; i <= 7; i++)\n                {\n                    var fhLevel = node.Nodes[i.ToString()];\n                    if (fhLevel != null)\n                    {\n                        LoadFoothold(fhLevel, i);\n                    }\n                }\n            }\n            if ((node = mapImgNode.Nodes[\"life\"]) != null)\n            {\n                LoadLife(node);\n            }\n            if ((node = mapImgNode.Nodes[\"reactor\"]) != null)\n            {\n                LoadReactor(node);\n            }\n            if ((node = mapImgNode.Nodes[\"portal\"]) != null)\n            {\n                LoadPortal(node);\n            }\n            if ((node = mapImgNode.Nodes[\"ladderRope\"]) != null)\n            {\n                LoadLadderRope(node);\n            }\n            if ((node = mapImgNode.Nodes[\"skyWhale\"]) != null)\n            {\n                LoadSkyWhale(node);\n            }\n            if ((node = mapImgNode.Nodes[\"illuminantCluster\"]) != null)\n            {\n                LoadIlluminantCluster(node);\n            }\n            if ((node = mapImgNode.Nodes[\"ToolTip\"]) != null)\n            {\n                LoadTooltip(node);\n            }\n            if ((node = mapImgNode.Nodes[\"particle\"]) != null)\n            {\n                LoadParticle(node);\n            }\n            if ((node = mapImgNode.Nodes[\"light\"]) != null)\n            {\n                LoadLight(node);\n            }\n            if ((node = mapImgNode.Nodes[\"effect\"]) != null)\n            {\n                LoadMapEvents(node);\n            }\n\n            //计算地图大小\n            CalcMapSize();\n        }\n\n        private void LoadIDOrName(Wz_Node mapImgNode)\n        {\n            var m = Regex.Match(mapImgNode.Text, @\"(\\d{9})\\.img\");\n            if (m.Success)\n            {\n                this.ID = int.Parse(m.Result(\"$1\"));\n            }\n            this.Name = mapImgNode.Text;\n        }\n\n        private void LoadInfo(Wz_Node infoNode)\n        {\n            int l = infoNode.Nodes[\"VRLeft\"].GetValueEx(0),\n                t = infoNode.Nodes[\"VRTop\"].GetValueEx(0),\n                r = infoNode.Nodes[\"VRRight\"].GetValueEx(0),\n                b = infoNode.Nodes[\"VRBottom\"].GetValueEx(0);\n            this.VRect = new Rectangle(l, t, r - l, b - t);\n            this.Bgm = infoNode.Nodes[\"bgm\"].GetValueEx<string>(null);\n            this.Link = infoNode.Nodes[\"link\"].GetValueEx<int>();\n            this.MapMark = infoNode.Nodes[\"mapMark\"].GetValueEx<string>(null);\n\n            this.IsTown = infoNode.Nodes[\"town\"].GetValueEx(false);\n            this.CanFly = infoNode.Nodes[\"fly\"].GetValueEx(false);\n            this.CanSwim = infoNode.Nodes[\"swim\"].GetValueEx(false);\n            this.ReturnMap = infoNode.Nodes[\"returnMap\"].GetValueEx<int>();\n            this.HideMinimap = infoNode.Nodes[\"hideMinimap\"].GetValueEx(false);\n            this.FieldLimit = infoNode.Nodes[\"fieldLimit\"].GetValueEx(0);\n        }\n\n        private void LoadMinimap(Wz_Node miniMapNode, ResourceLoader resLoader)\n        {\n            Wz_Node canvas = miniMapNode.FindNodeByPath(\"canvas\"),\n                   width = miniMapNode.FindNodeByPath(\"width\"),\n                   height = miniMapNode.FindNodeByPath(\"height\"),\n                   centerX = miniMapNode.FindNodeByPath(\"centerX\"),\n                   centerY = miniMapNode.FindNodeByPath(\"centerY\"),\n                   mag = miniMapNode.FindNodeByPath(\"mag\");\n            this.MiniMap.ExtraCanvas.Clear();\n\n            canvas = canvas.GetLinkedSourceNode(PluginManager.FindWz);\n            if (canvas != null)\n            {\n                this.MiniMap.Canvas = resLoader.Load<Texture2D>(canvas);\n                this.MiniMap.ExtraCanvas.Add(\"canvas\", this.MiniMap.Canvas);\n            }\n            else\n            {\n                this.MiniMap.Canvas = null;\n            }\n\n            // example mapID: 993200000, KMST1140\n            for (int i = 1; ; i++)\n            {\n                string canvasName = $\"canvas{i}\";\n                var extraCanvas = miniMapNode.FindNodeByPath(canvasName);\n                if (extraCanvas == null)\n                {\n                    break;\n                }\n                extraCanvas = extraCanvas.GetLinkedSourceNode(PluginManager.FindWz);\n                this.MiniMap.ExtraCanvas.Add(canvasName, resLoader.Load<Texture2D>(extraCanvas));\n            }\n\n            this.MiniMap.Width = width.GetValueEx(0);\n            this.MiniMap.Height = height.GetValueEx(0);\n            this.MiniMap.CenterX = centerX.GetValueEx(0);\n            this.MiniMap.CenterY = centerY.GetValueEx(0);\n            this.MiniMap.Mag = mag.GetValueEx(0);\n        }\n\n        private void LoadBack(Wz_Node backNode)\n        {\n            foreach (var node in backNode.Nodes)\n            {\n                var item = BackItem.LoadFromNode(node);\n                item.Name = $\"back_{node.Text}\";\n                item.Index = int.Parse(node.Text);\n\n                (item.IsFront ? this.Scene.Front : this.Scene.Back).Slots.Add(item);\n            }\n        }\n\n        private void LoadLayer(Wz_Node layerNode, int level)\n        {\n            var layerSceneNode = (LayerNode)this.Scene.Layers.Nodes[level];\n\n            //读取obj\n            var objNode = layerNode.Nodes[\"obj\"];\n            if (objNode != null)\n            {\n                foreach (var node in objNode.Nodes)\n                {\n                    var item = ObjItem.LoadFromNode(node);\n                    item.Name = $\"obj_{level}_{node.Text}\";\n                    item.Index = int.Parse(node.Text);\n\n                    layerSceneNode.Obj.Slots.Add(item);\n                }\n            }\n\n            //读取tile\n            string tS = layerNode.Nodes[\"info\"]?.Nodes[\"tS\"].GetValueEx<string>(null);\n            var tileNode = layerNode.Nodes[\"tile\"];\n            if (tS != null && tileNode != null)\n            {\n                foreach (var node in tileNode.Nodes)\n                {\n                    var item = TileItem.LoadFromNode(node);\n                    item.TS = tS;\n                    item.Name = $\"tile_{level}_{node.Text}\";\n                    item.Index = int.Parse(node.Text);\n\n                    layerSceneNode.Tile.Slots.Add(item);\n                }\n            }\n        }\n\n        private void LoadFoothold(Wz_Node fhLayerNode, int level)\n        {\n            var layerSceneNode = (LayerNode)this.Scene.Layers.Nodes[level];\n\n            foreach (var group in fhLayerNode.Nodes)\n            {\n                foreach (var node in group.Nodes)\n                {\n                    var item = FootholdItem.LoadFromNode(node);\n                    item.ID = int.Parse(node.Text);\n                    item.Name = $\"fh_{level}_{group.Text}_{node.Text}\";\n\n                    var fhSceneNode = new ContainerNode<FootholdItem>() { Item = item };\n                    layerSceneNode.Foothold.Nodes.Add(fhSceneNode);\n                }\n            }\n        }\n\n        private void LoadLife(Wz_Node lifeNode)\n        {\n            bool isCategory = lifeNode.Nodes[\"isCategory\"].GetValueEx<int>(0) != 0;\n            var lifeNodeList = !isCategory ? lifeNode.Nodes : lifeNode.Nodes.SelectMany(n => n.Nodes);\n\n            int i = 0;\n            foreach (var node in lifeNodeList)\n            {\n                var item = LifeItem.LoadFromNode(node);\n                if (isCategory)\n                {\n                    item.Name = $\"life_{item.Type}_{node.ParentNode.Text}_{node.Text}\";\n                    item.Index = i++;\n                }\n                else\n                {\n                    item.Name = $\"life_{item.Type}_{node.Text}\";\n                    item.Index = int.Parse(node.Text);\n                }\n\n                //直接绑定foothold\n                ContainerNode<FootholdItem> fhNode;\n                if (item.Fh != 0 && (fhNode = FindFootholdByID(item.Fh)) != null)\n                {\n                    fhNode.Slots.Add(item);\n                }\n                else\n                {\n                    Scene.Fly.Sky.Slots.Add(item);\n                }\n            }\n        }\n\n        private void LoadPortal(Wz_Node portalNode)\n        {\n            string mapID = this.ID.ToString().PadLeft(9, '0');\n            var portalTooltipNode = PluginManager.FindWz(\"String/ToolTipHelp.img/PortalTooltip/\" + this.ID);\n            var graphMapNode = PluginManager.FindWz(string.Format(\"Map/Map/Graph.img/{0:D2}/{1}/portal\", this.ID / 10000000, mapID));\n            if (graphMapNode == null)\n            {\n                foreach (var graphImgSubNode in PluginManager.FindWz(\"Map/Map/Graph.img\")?.Nodes ?? Enumerable.Empty<Wz_Node>())\n                {\n                    if (graphImgSubNode.Nodes[mapID] != null)\n                    {\n                        graphMapNode = graphImgSubNode.Nodes[mapID].Nodes[\"portal\"];\n                        break;\n                    }\n                }\n            }\n\n            foreach (var node in portalNode.Nodes)\n            {\n                var item = PortalItem.LoadFromNode(node);\n                item.Name = $\"portal_{node.Text}\";\n                item.Index = int.Parse(node.Text);\n\n                //加载tooltip\n                if (portalTooltipNode != null && !string.IsNullOrEmpty(item.PName))\n                {\n                    var tooltipNode = portalTooltipNode.Nodes[item.PName];\n                    if (tooltipNode != null)\n                    {\n                        var tooltip = new PortalItem.ItemTooltip();\n\n                        if (tooltipNode.Nodes.Count > 0)\n                        {\n                            tooltip.Title = tooltipNode.Nodes[\"Title\"].GetValueEx<string>(null);\n                        }\n                        else\n                        {\n                            tooltip.Title = tooltipNode.GetValue<String>();\n                        }\n\n                        item.Tooltip = tooltip;\n                    }\n                }\n                //Graph.img에 따른 이동경로 출력\n                item.GraphTargetMap = new List<int>();\n                if (graphMapNode != null)\n                {\n                    foreach (var graphPortalNode in graphMapNode.Nodes)\n                    {\n                        if (item.Index == graphPortalNode.Nodes[\"portalNum\"].GetValueEx<int>())\n                        {\n                            var targetMapID = graphPortalNode.Nodes[\"targetMap\"].GetValueEx<int>();\n                            if (targetMapID != null)\n                            {\n                                item.GraphTargetMap.Add(targetMapID.Value);\n                            }\n                        }\n                    }\n                }\n                Scene.Fly.Portal.Slots.Add(item);\n            }\n        }\n\n        private void LoadReactor(Wz_Node reactorNode)\n        {\n            //计算reactor所在层\n            var layer = Scene.Layers.Nodes.OfType<LayerNode>()\n                .FirstOrDefault(l => l.Foothold.Nodes.Count > 0)\n                ?? (Scene.Layers.Nodes[0] as LayerNode);\n\n            foreach (var node in reactorNode.Nodes)\n            {\n                var item = ReactorItem.LoadFromNode(node);\n                item.Name = $\"reactor_{node.Text}\";\n                item.Index = int.Parse(node.Text);\n\n                layer.Reactor.Slots.Add(item);\n            }\n        }\n\n        private void LoadLadderRope(Wz_Node ladderRopeNode)\n        {\n            foreach (var node in ladderRopeNode.Nodes)\n            {\n                var item = LadderRopeItem.LoadFromNode(node);\n                item.Name = $\"ladderRope_{node.Text}\";\n                item.Index = int.Parse(node.Text);\n\n                Scene.Fly.LadderRope.Slots.Add(item);\n            }\n        }\n\n        private void LoadSkyWhale(Wz_Node skyWhaleNode)\n        {\n            foreach (var node in skyWhaleNode.Nodes)\n            {\n                var item = SkyWhaleItem.LoadFromNode(node);\n                item.Name = node.Text;\n                Scene.Fly.SkyWhale.Slots.Add(item);\n            }\n        }\n\n        private void LoadIlluminantCluster(Wz_Node illuminantClusterNode)\n        {\n            foreach (var node in illuminantClusterNode.Nodes)\n            {\n                if (node.Nodes.Count > 0)\n                {\n                    var item = IlluminantClusterItem.LoadFromNode(node);\n                    item.Name = node.Text;\n                    Scene.Fly.IlluminantCluster.Slots.Add(item);\n                }\n            }\n        }\n\n        private void LoadTooltip(Wz_Node tooltipNode)\n        {\n            Func<Wz_Node, Rectangle> getRect = (node) =>\n            {\n                int x1 = node.Nodes[\"x1\"].GetValueEx<int>(0);\n                int x2 = node.Nodes[\"x2\"].GetValueEx<int>(0);\n                int y1 = node.Nodes[\"y1\"].GetValueEx<int>(0);\n                int y2 = node.Nodes[\"y2\"].GetValueEx<int>(0);\n                return new Rectangle(x1, y1, x2 - x1, y2 - y1);\n            };\n\n            var tooltipDescNode = PluginManager.FindWz(\"String/ToolTipHelp.img/Mapobject/\" + this.ID);\n\n            for (int i = 0; ; i++)\n            {\n                var rectNode = tooltipNode.Nodes[i.ToString()];\n                var charNode = tooltipNode.Nodes[i + \"char\"];\n                if (rectNode != null)\n                {\n                    var item = new TooltipItem();\n                    item.Name = i.ToString();\n                    item.Index = i;\n                    item.Rect = getRect(rectNode);\n                    if (charNode != null)\n                    {\n                        item.CharRect = getRect(charNode);\n                    }\n\n                    var descNode = tooltipDescNode?.Nodes[i.ToString()];\n                    if (descNode != null)\n                    {\n                        item.Title = descNode.Nodes[\"Title\"].GetValueEx<string>(null);\n                        item.Desc = descNode.Nodes[\"Desc\"].GetValueEx<string>(null);\n                        item.ItemEU = descNode.Nodes[\"ItemEU\"].GetValueEx<string>(null);\n                    }\n\n                    this.Tooltips.Add(item);\n                }\n                else\n                {\n                    break;\n                }\n            }\n        }\n\n        private void LoadParticle(Wz_Node node)\n        {\n            foreach (var particleNode in node.Nodes)\n            {\n                var item = ParticleItem.LoadFromNode(particleNode);\n                item.Name = node.Text;\n                Scene.Effect.Slots.Add(item);\n            }\n        }\n\n        private void LoadLight(Wz_Node node)\n        {\n            var mapLight = new MapLight()\n            {\n                Mode = node.Nodes[\"mode\"].GetValueEx(0),\n                AmbientColor = node.Nodes[\"ambient_color\"].GetXnaColor(),\n                DirectionalLightColor = node.Nodes[\"directional_light_color\"].GetXnaColor(),\n                LuminanceLimit = node.Nodes[\"luminance_limit\"].GetValueEx(0f),\n                BackColor = node.Nodes[\"back_color\"].GetXnaColor(),\n            };\n\n            for (int i=0; ; i++)\n            {\n                var lightNode = node.Nodes[i.ToString()];\n                if (lightNode == null)\n                {\n                    break;\n                }\n\n                var light = new Light2D()\n                {\n                    Type = lightNode.Nodes[\"type\"].GetValueEx(0),\n                    X = lightNode.Nodes[\"x\"].GetValueEx(0),\n                    Y = lightNode.Nodes[\"y\"].GetValueEx(0),\n                    Color = lightNode.Nodes[\"color\"].GetXnaColor(),\n                    InnerRadius = lightNode.Nodes[\"inner_radius\"].GetValueEx(0),\n                    OuterRadius = lightNode.Nodes[\"outer_radius\"].GetValueEx(0),\n                    InnerAngle = lightNode.Nodes[\"inner_angle\"].GetValueEx(0),\n                    OuterAngle = lightNode.Nodes[\"outer_angle\"].GetValueEx(0),\n                    DirectionAngle = lightNode.Nodes[\"direction_angle\"].GetValueEx(0),\n                };\n                mapLight.Lights.Add(light);\n            }\n            this.Light = mapLight;\n        }\n\n        private void LoadMapEvents(Wz_Node effectNode)\n        {\n            foreach (var node in effectNode.Nodes)\n            {\n                var index = node.Text;\n                var type = node.FindNodeByPath(\"type\").GetValueEx<string>(null);\n                var defaultAnimation = node.FindNodeByPath(\"defaultAnimation\").GetValueEx<string>(null);\n                var changedAnimation = node.FindNodeByPath(\"changedAnimation\").GetValueEx<string>(null);\n                var tags = node.FindNodeByPath(\"tags\").GetValueEx<string>(null);\n                var item = new MapEvent(index, type, defaultAnimation, changedAnimation, tags);\n\n                this.MapEvents.Add(item);\n            }\n        }\n\n        private void CalcMapSize()\n        {\n            if (!this.VRect.IsEmpty)\n            {\n                return;\n            }\n\n            var rect = Rectangle.Empty;\n\n            int xMAX = int.MinValue;\n            foreach (LayerNode layer in this.Scene.Layers.Nodes)\n            {\n                foreach (ContainerNode<FootholdItem> item in layer.Foothold.Nodes)\n                {\n                    var fh = item.Item;\n                    var fhRect = new Rectangle(\n                        Math.Min(fh.X1, fh.X2),\n                        Math.Min(fh.Y1, fh.Y2),\n                        Math.Abs(fh.X2 - fh.X1),\n                        Math.Abs(fh.Y2 - fh.Y1));\n                    xMAX = Math.Max(fhRect.Right, xMAX);\n                    var oldrec = rect;\n                    if (rect.IsEmpty)\n                    {\n                        rect = fhRect;\n                    }\n                    else\n                    {\n                        Rectangle newRect;\n                        Rectangle.Union(ref rect, ref fhRect, out newRect);\n                        rect = newRect;\n                    }\n                }\n            }\n\n            rect.Y -= 250;\n            rect.Height += 450;\n\n            foreach (LadderRopeItem item in this.Scene.Fly.LadderRope.Slots)\n            {\n                var lrRect = new Rectangle(item.X, Math.Min(item.Y1, item.Y2), 1, Math.Abs(item.Y2 - item.Y1));\n                if (rect.IsEmpty)\n                {\n                    rect = lrRect;\n                }\n                else\n                {\n                    Rectangle newRect;\n                    Rectangle.Union(ref rect, ref lrRect, out newRect);\n                    rect = newRect;\n                }\n            }\n\n            this.VRect = rect;\n        }\n\n        private ContainerNode<FootholdItem> FindFootholdByID(int fhID)\n        {\n            return this.Scene.Layers.Nodes.OfType<LayerNode>()\n                .SelectMany(layerNode => layerNode.Foothold.Nodes).OfType<ContainerNode<FootholdItem>>()\n                .FirstOrDefault(fhNode => fhNode.Item.ID == fhID);\n        }\n\n        /// <summary>\n        /// 对场景中所有的物件预加载动画资源。\n        /// </summary>\n        /// <param name=\"resLoader\"></param>\n        public void PreloadResource(ResourceLoader resLoader)\n        {\n            Action<SceneNode> loadFunc = null;\n            loadFunc = (node) =>\n            {\n                var container = node as ContainerNode;\n                if (container != null)\n                {\n                    foreach (var item in container.Slots)\n                    {\n                        if (item is BackItem)\n                        {\n                            PreloadResource(resLoader, (BackItem)item);\n                        }\n                        else if (item is ObjItem)\n                        {\n                            PreloadResource(resLoader, (ObjItem)item);\n                        }\n                        else if (item is TileItem)\n                        {\n                            PreloadResource(resLoader, (TileItem)item);\n                        }\n                        else if (item is LifeItem)\n                        {\n                            PreloadResource(resLoader, (LifeItem)item);\n                        }\n                        else if (item is PortalItem)\n                        {\n                            PreloadResource(resLoader, (PortalItem)item);\n                        }\n                        else if (item is IlluminantClusterItem)\n                        {\n                            PreloadResource(resLoader, (IlluminantClusterItem)item);\n                        }\n                        else if (item is ReactorItem)\n                        {\n                            PreloadResource(resLoader, (ReactorItem)item);\n                        }\n                        else if (item is ParticleItem)\n                        {\n                            PreloadResource(resLoader, (ParticleItem)item);\n                        }\n                    }\n                }\n\n                foreach (var child in node.Nodes)\n                {\n                    loadFunc(child);\n                }\n            };\n\n            loadFunc(this.Scene);\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, BackItem back)\n        {\n            string aniDir;\n            switch (back.Ani)\n            {\n                case 0: aniDir = \"back\"; break;\n                case 1: aniDir = \"ani\"; break;\n                case 2: aniDir = $\"spine{back.SpineNo}\"; break;\n                default: throw new Exception($\"Unknown back ani value: {back.Ani}.\");\n            }\n            string path = $@\"Map\\Back\\{back.BS}.img\\{aniDir}\\{back.No}\";\n            var aniItem = resLoader.LoadAnimationData(path);\n\n            back.View = new BackItem.ItemView()\n            {\n                Animator = CreateAnimator(aniItem, back.SpineAni)\n            };\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, ObjItem obj)\n        {\n            string path = $@\"Map\\Obj\\{obj.OS}.img\\{obj.L0}\\{obj.L1}\\{obj.L2}\";\n            var aniItem = resLoader.LoadAnimationData(path);\n            obj.View = new ObjItem.ItemView()\n            {\n                Animator = CreateAnimator(aniItem, obj.SpineAni),\n                Flip = obj.Flip\n            };\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, TileItem tile)\n        {\n            string path = $@\"Map\\Tile\\{tile.TS}.img\\{tile.U}\\{tile.No}\";\n            var aniItem = resLoader.LoadAnimationData(path);\n            tile.View = new TileItem.ItemView()\n            {\n                Animator = CreateAnimator(aniItem)\n            };\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, LifeItem life)\n        {\n            string path;\n            switch (life.Type)\n            {\n                case LifeItem.LifeType.Mob:\n                    path = $@\"Mob\\{life.ID:D7}.img\";\n                    var mobNode = PluginManager.FindWz(path);\n\n                    //加载mob数据\n                    if (mobNode != null)\n                    {\n                        life.LifeInfo = LifeInfo.CreateFromNode(mobNode);\n                    }\n\n                    //获取link\n                    int? mobLink = mobNode?.FindNodeByPath(@\"info\\link\").GetValueEx<int>();\n                    if (mobLink != null)\n                    {\n                        path = $@\"Mob\\{mobLink.Value:D7}.img\";\n                        mobNode = PluginManager.FindWz(path);\n                    }\n\n                    //加载动画\n                    if (mobNode != null)\n                    {\n                        var aniItem = this.CreateSMAnimator(mobNode, resLoader);\n                        if (aniItem != null)\n                        {\n                            AddMobAI(aniItem);\n                            life.View = new LifeItem.ItemView()\n                            {\n                                Animator = aniItem\n                            };\n                        }\n                    }\n                    break;\n\n                case LifeItem.LifeType.Npc:\n                    path = $@\"Npc\\{life.ID:D7}.img\";\n                    var npcNode = PluginManager.FindWz(path);\n\n                    //TODO: 加载npc数据\n\n                    life.HideName = (npcNode?.FindNodeByPath(@\"info\\hideName\")?.GetValueEx<int>(0) ?? 0) != 0;\n                    var customFontNode = npcNode?.FindNodeByPath(@\"info\\customFont:func\");\n                    if (customFontNode != null)\n                    {\n                        life.CustomFont = LifeItem.LoadCustomFontFunc(customFontNode);\n                    }\n\n                    int? npcLink = npcNode?.FindNodeByPath(@\"info\\link\").GetValueEx<int>();\n                    if (npcLink != null)\n                    {\n                        path = $@\"Npc\\{npcLink.Value:D7}.img\";\n                        npcNode = PluginManager.FindWz(path);\n                    }\n\n                    //加载动画\n                    if (npcNode != null)\n                    {\n                        var aniItem = this.CreateSMAnimator(npcNode, resLoader);\n                        if (aniItem != null)\n                        {\n                            AddNpcAI(aniItem);\n                            life.View = new LifeItem.ItemView()\n                            {\n                                Animator = aniItem\n                            };\n                        }\n                    }\n                    break;\n            }\n\n            if (life.View == null) //空动画\n            {\n                life.View = new LifeItem.ItemView();\n            }\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, PortalItem portal)\n        {\n            string path;\n\n            var view = new PortalItem.ItemView();\n            //加载editor\n            {\n                var typeName = PortalItem.PortalTypes[portal.Type];\n                path = $@\"Map\\MapHelper.img\\portal\\editor\\{typeName}\";\n                var aniData = resLoader.LoadAnimationData(path);\n                if (aniData != null)\n                {\n                    view.EditorAnimator = CreateAnimator(aniData);\n                }\n\n            }\n            //加载动画\n            {\n                string typeName, imgName;\n                switch (portal.Type)\n                {\n                    case 7:\n                        typeName = PortalItem.PortalTypes[2]; break;\n                    default:\n                        typeName = PortalItem.PortalTypes[portal.Type]; break;\n                }\n\n                switch (portal.Image)\n                {\n                    case 0:\n                        imgName = \"default\"; break;\n                    default:\n                        imgName = portal.Image.ToString(); break;\n                }\n                path = $@\"Map\\MapHelper.img\\portal\\game\\{typeName}\\{imgName}\";\n\n                var aniNode = PluginManager.FindWz(path);\n                if (aniNode != null)\n                {\n                    bool useParts = new[] { \"portalStart\", \"portalContinue\", \"portalExit\" }\n                        .Any(aniName => aniNode.Nodes[aniName] != null);\n\n                    if (useParts) //加载动作动画\n                    {\n                        var animator = CreateSMAnimator(aniNode, resLoader);\n                        view.Animator = animator;\n                        view.Controller = new PortalItem.Controller(view);\n                    }\n                    else //加载普通动画\n                    {\n                        var aniData = resLoader.LoadAnimationData(aniNode);\n                        if (aniData != null)\n                        {\n                            view.Animator = CreateAnimator(aniData);\n                        }\n                    }\n                }\n            }\n\n            portal.View = view;\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, IlluminantClusterItem illuminantCluster)\n        {\n            string path;\n\n            var view = new IlluminantClusterItem.ItemView();\n            path = $@\"Map\\Obj\\sellas.img\\fieldGimmick\\cluster\\{illuminantCluster.StartPoint * 2}\";\n\n            var aniNode = PluginManager.FindWz(path);\n            if (aniNode != null)\n            {\n                var aniData = resLoader.LoadAnimationData(aniNode);\n                if (aniData != null)\n                {\n                    view.Animator = CreateAnimator(aniData);\n                }\n            }\n\n            illuminantCluster.StartView = view;\n\n            view = new IlluminantClusterItem.ItemView();\n            path = $@\"Map\\Obj\\sellas.img\\fieldGimmick\\cluster\\{illuminantCluster.EndPoint * 2 + 1}\";\n\n            aniNode = PluginManager.FindWz(path);\n            if (aniNode != null)\n            {\n                var aniData = resLoader.LoadAnimationData(aniNode);\n                if (aniData != null)\n                {\n                    view.Animator = CreateAnimator(aniData);\n                }\n            }\n\n            illuminantCluster.EndView = view;\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, ReactorItem reactor)\n        {\n            string path = $@\"Reactor\\{reactor.ID:D7}.img\";\n            var reactorNode = PluginManager.FindWz(path);\n\n            int? reactorLink = reactorNode?.FindNodeByPath(@\"info\\link\").GetValueEx<int>();\n            if (reactorLink != null)\n            {\n                path = $@\"Reactor\\{reactorLink.Value:D7}.img\";\n                reactorNode = PluginManager.FindWz(path);\n            }\n\n            //加载动画\n            var aniData = new Dictionary<string, RepeatableFrameAnimationData>();\n\n            Wz_Node frameNode;\n            for (int i = 0; (frameNode = reactorNode.Nodes[i.ToString()]) != null; i++)\n            {\n                //加载循环动画\n                var ani = resLoader.LoadAnimationData(frameNode) as RepeatableFrameAnimationData;\n                if (ani != null)\n                {\n                    var ani2 = new RepeatableFrameAnimationData(ani.Frames);\n                    ani2.Repeat = ani.Repeat ?? true; //默认循环\n                    aniData.Add(i.ToString(), ani2);\n                }\n\n                //加载跳转动画\n                var hitNode = frameNode.Nodes[\"hit\"];\n                Wz_Uol uol;\n                if ((uol = hitNode?.GetValue<Wz_Uol>()) != null)\n                {\n                    hitNode = uol.HandleUol(hitNode);\n                }\n                if (hitNode != null)\n                {\n                    var aniHit = resLoader.LoadAnimationData(hitNode) as RepeatableFrameAnimationData;\n                    aniData.Add($@\"{i}/hit\", aniHit);\n                }\n            }\n\n            var view = new ReactorItem.ItemView();\n            view.Animator = new StateMachineAnimator(aniData);\n            view.Controller = new ReactorItem.Controller(view);\n\n            reactor.View = view;\n        }\n\n        private void PreloadResource(ResourceLoader resLoader, ParticleItem particle)\n        {\n            string path = $@\"Effect\\particle.img\\{particle.ParticleName}\";\n            var particleNode = PluginManager.FindWz(path);\n\n            if (particleNode == null)\n            {\n                return;\n            }\n\n            var desc = resLoader.LoadParticleDesc(particleNode);\n            switch (desc)\n            {\n                case ParticleDesc desc0:\n                    {\n                        var pSystem = new ParticleSystem(this.random);\n                        pSystem.LoadDescription(desc0);\n\n                        for (int i = 0; i < particle.SubItems.Length; i++)\n                        {\n                            var subItem = particle.SubItems[i];\n                            var pGroup = pSystem.CreateGroup(i.ToString());\n                            pGroup.Position = new Vector2(subItem.X, subItem.Y);\n                            pGroup.Active();\n                            pSystem.Groups.Add(pGroup);\n                        }\n\n                        particle.View = new ParticleItem.ItemView()\n                        {\n                            ParticleSystem = pSystem\n                        };\n                    }\n                    break;\n            }\n        }\n\n        private StateMachineAnimator CreateSMAnimator(Wz_Node node, ResourceLoader resLoader)\n        {\n            var aniData = new Dictionary<string, RepeatableFrameAnimationData>();\n            foreach (var actionNode in node.Nodes)\n            {\n                var actName = actionNode.Text;\n                if (actName != \"info\" && !actName.StartsWith(\"condition\"))\n                {\n                    var ani = resLoader.LoadAnimationData(actionNode) as RepeatableFrameAnimationData;\n                    if (ani != null)\n                    {\n                        aniData.Add(actName, ani);\n                    }\n                }\n            }\n            if (aniData.Count > 0)\n            {\n                return new StateMachineAnimator(aniData);\n            }\n            else\n            {\n                return null;\n            }\n        }\n\n        private object CreateAnimator(object animationData, string aniName = null)\n        {\n            switch (animationData) {\n                case RepeatableFrameAnimationData repFrameAni:\n                    return new RepeatableFrameAnimator(repFrameAni);\n\n                case FrameAnimationData frameAni:\n                    return new FrameAnimator(frameAni);\n\n                case ISpineAnimationData spineAniData:\n                    var spineAni = spineAniData.CreateAnimator();\n                    if (aniName != null)\n                    {\n                        spineAni.SelectedAnimationName = aniName;\n                    }\n                    return spineAni;\n\n                case MsCustomSpriteData msSpriteData:\n                    var defaultTexture = msSpriteData.Textures[0].Texture;\n                    return new MsCustomSprite()\n                    {\n                        Size = new Vector2(defaultTexture.Width, defaultTexture.Height),\n                        Material = ShaderMaterialFactory.Create(msSpriteData),\n                    };\n\n                default:\n                    return null;\n            }\n        }\n\n        private void AddMobAI(StateMachineAnimator ani)\n        {\n            var actions = new[] { \"stand\", \"say\", \"mouse\", \"move\", \"hand\", \"laugh\", \"eye\" };\n            ani.AnimationEnd += (o, e) =>\n            {\n                switch(e.CurrentState)\n                {\n                    case \"regen\":\n                        if (ani.Data.States.Contains(\"stand\")) e.NextState = \"stand\";\n                        else if (ani.Data.States.Contains(\"fly\")) e.NextState = \"fly\";\n                        break;\n\n                    case \"stand\":\n                        if (ani.Data.States.Contains(\"jump\") && this.random.NextPercent(0.05f))\n                        {\n                            e.NextState = \"jump\";\n                        }\n                        else if (ani.Data.States.Contains(\"move\") && this.random.NextPercent(0.3f))\n                        {\n                            e.NextState = \"move\";\n                        }\n                        else\n                        {\n                            e.NextState = e.CurrentState;\n                        }\n                        break;\n\n                    default: \n                        goto case \"regen\";\n                }\n            };\n        }\n\n        private void AddNpcAI(StateMachineAnimator ani)\n        {\n            var actions = new[] { \"stand\", \"say\", \"mouse\", \"move\", \"hand\", \"laugh\", \"eye\" };\n            var availActions = ani.Data.States.Where(act => actions.Contains(act)).ToArray();\n            if (availActions.Length > 0)\n            {\n                ani.AnimationEnd += (o, e) =>\n                {\n                    e.NextState = availActions[this.random.Next(availActions.Length)];\n                };\n            }\n        }\n\n        public static bool FindMapByID(int mapID, out Wz_Node mapImgNode)\n        {\n            string fullPath = string.Format(@\"Map\\Map\\Map{0}\\{1:D9}.img\", (mapID / 100000000), mapID);\n            mapImgNode = PluginManager.FindWz(fullPath);\n            Wz_Image mapImg;\n            if (mapImgNode != null\n                && (mapImg = mapImgNode.GetValueEx<Wz_Image>(null)) != null\n                && mapImg.TryExtract())\n            {\n                mapImgNode = mapImg.Node;\n                return true;\n            }\n            else\n            {\n                mapImgNode = null;\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MapEvent.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MapEvent\n    {\n        public MapEvent(string index, string type, string defaultAnimation, string changedAnimation, string tags)\n        {\n            if (!Enum.TryParse<MapEventType>(type, out var eventType))\n            {\n                eventType = MapEventType.Unknown;\n            }\n            Index = index;\n            Type = eventType;\n            DefaultAnimation = defaultAnimation;\n            ChangedAnimation = changedAnimation;\n            Tags = tags;\n        }\n\n        public string Index { get; set; }\n        public MapEventType Type { get; set; }\n        public string DefaultAnimation { get; set; }\n        public string ChangedAnimation { get; set; }\n        public string Tags { get; set; }\n    }\n\n    public enum MapEventType\n    {\n        Unknown = 0,\n        SetAnimationOnceAndReturn = 1,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MapLight.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MapLight\n    {\n        public int Mode { get; set; }\n        public Color AmbientColor { get; set; }\n        public Color DirectionalLightColor { get; set; }\n        public float LuminanceLimit { get; set; }\n        public Color BackColor { get; set; }\n        public List<Light2D> Lights { get; private set; } = new List<Light2D>();\n    }\n\n    public class Light2D\n    {\n        public int Type { get; set; }\n        public int X { get; set; }\n        public int Y { get; set; }\n        public Color Color { get; set; }\n        public int InnerRadius { get; set; }\n        public int OuterRadius { get; set; }\n        public int InnerAngle { get; set; }\n        public int OuterAngle { get; set; }\n        public int DirectionAngle { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MapRenderFonts.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Text;\nusing System.Drawing;\nusing Microsoft.Xna.Framework.Content;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.MapRender.Config;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MapRenderFonts : IDisposable\n    {\n        public static readonly IReadOnlyList<string> DefaultFonts = new ReadOnlyCollection<string>(new[]\n        {\n            \"SimSun\", \"Dotum\"\n        });\n\n        public static string GetFontResourceKey(string familyName, float size, FontStyle style)\n        {\n            string assetName = string.Join(\",\", familyName, size, style);\n            return assetName;\n        }\n\n        public MapRenderFonts()\n        {\n            this.fonts = new Dictionary<string, IWcR2Font>();\n        }\n\n        private Dictionary<string, IWcR2Font> fonts;\n\n        public void LoadContent(ContentManager content)\n        {\n            var config = MapRenderConfig.Default;\n\n            var fontIndex = config.DefaultFontIndex;\n            if (fontIndex < 0 || fontIndex >= DefaultFonts.Count)\n            {\n                fontIndex = 0;\n            }\n\n            string familyName = DefaultFonts[fontIndex];\n\n            fonts[\"default\"] = content.Load<IWcR2Font>(GetFontResourceKey(familyName, 12f, FontStyle.Regular));\n            fonts[\"npcName\"] = fonts[\"default\"];\n            fonts[\"mobName\"] = fonts[\"default\"];\n            fonts[\"mobLevel\"] = content.Load<IWcR2Font>(GetFontResourceKey(\"Tahoma\", 9f, FontStyle.Regular));\n            fonts[\"tooltipTitle\"] = content.Load<IWcR2Font>(GetFontResourceKey(familyName, 14f, FontStyle.Bold));\n            fonts[\"tooltipContent\"] = fonts[\"default\"];\n        }\n\n        protected IWcR2Font this[string key]\n        {\n            get\n            {\n                IWcR2Font font;\n                this.fonts.TryGetValue(key, out font);\n                return font;\n            }\n        }\n\n        public IWcR2Font DefaultFont\n        {\n            get { return this[\"default\"]; }\n        }\n\n        public IWcR2Font NpcNameFont\n        {\n            get { return this[\"npcName\"]; }\n        }\n\n        public IWcR2Font MobNameFont\n        {\n            get { return this[\"mobName\"]; }\n        }\n\n        public IWcR2Font MobLevelFont\n        {\n            get { return this[\"mobLevel\"]; }\n        }\n\n        public IWcR2Font MapNameFont\n        {\n            get { return this[\"npcName\"]; }\n        }\n\n        public IWcR2Font TooltipTitleFont\n        {\n            get { return this[\"tooltipTitle\"]; }\n        }\n\n        public IWcR2Font TooltipContentFont\n        {\n            get { return this[\"tooltipContent\"]; }\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            this.fonts.Clear();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MapScene.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\nusing WzComparerR2.MapRender.Patches2;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MapScene : SceneNode\n    {\n        public MapScene()\n        {\n            this.Nodes.AddRange(new SceneNode[] {\n                Back = new ContainerNode(),\n                Layers = new SceneNode(),\n                Fly = new FlyLayerNode(),\n                Front = new ContainerNode(),\n                Effect = new ContainerNode()\n            });\n\n            for (int i = 0; i <= 7; i++)\n            {\n                this.Layers.Nodes.Add(new LayerNode());\n            }\n        }\n\n        public ContainerNode Back { get; private set; }\n        public SceneNode Layers { get; private set; }\n        public FlyLayerNode Fly { get; private set; }\n        public ContainerNode Front { get; private set; }\n        public ContainerNode Effect { get; private set; }\n\n        public IEnumerable<PortalItem> Portals => this.Fly.Portal.Slots.OfType<PortalItem>();\n        public IEnumerable<IlluminantClusterItem> IlluminantClusters => this.Fly.IlluminantCluster.Slots.OfType<IlluminantClusterItem>();\n        public IEnumerable<LifeItem> Npcs => this.Layers.Nodes.OfType<LayerNode>()\n            .SelectMany(layerNode => layerNode.Foothold.Nodes).OfType<ContainerNode<FootholdItem>>()\n            .SelectMany(fhNode => fhNode.Slots).OfType<LifeItem>()\n            .Where(lifeNode => lifeNode.Type == LifeItem.LifeType.Npc && !lifeNode.Hide)\n            .Concat(this.Fly.Sky.Slots.OfType<LifeItem>()\n            .Where(lifeNode => lifeNode.Type == LifeItem.LifeType.Npc && !lifeNode.Hide));\n        public IEnumerable<LifeItem> Mobs => this.Layers.Nodes.OfType<LayerNode>()\n                    .SelectMany(layerNode => layerNode.Foothold.Nodes).OfType<ContainerNode<FootholdItem>>()\n                    .SelectMany(fhNode => fhNode.Slots).OfType<LifeItem>()\n                    .Where(lifeNode => lifeNode.Type == LifeItem.LifeType.Mob && !lifeNode.Hide)\n                    .Concat(this.Fly.Sky.Slots.OfType<LifeItem>()\n                    .Where(lifeNode => lifeNode.Type == LifeItem.LifeType.Mob && !lifeNode.Hide));\n\n        public PortalItem FindPortal(string pName)\n        {\n            return this.Portals.FirstOrDefault(_portal => _portal.PName == pName);\n        }\n    }\n\n    public class LayerNode : SceneNode\n    {\n        public LayerNode()\n        {\n            this.Nodes.AddRange(new SceneNode[]\n            {\n                Obj = new ContainerNode(),\n                Reactor = new ContainerNode(),\n                Tile = new ContainerNode(),\n                Foothold = new SceneNode()\n            });\n        }\n\n        public ContainerNode Obj { get; private set; }\n        public ContainerNode Reactor { get; private set; }\n        public ContainerNode Tile { get; private set; }\n        public SceneNode Foothold { get; private set; }\n    }\n\n    public class FlyLayerNode : SceneNode\n    {\n        public FlyLayerNode()\n        {\n            this.Nodes.AddRange(new SceneNode[]\n            {\n                Portal = new ContainerNode(),\n                LadderRope = new ContainerNode(),\n                Sky = new ContainerNode(),\n                SkyWhale = new ContainerNode(),\n                IlluminantCluster = new ContainerNode(),\n            });\n        }\n\n\n        public ContainerNode Portal { get; private set; }\n        public ContainerNode LadderRope { get; private set; }\n        /// <summary>\n        /// 表示不属于任何Foothold的虚拟节点。\n        /// </summary>\n        public ContainerNode Sky { get; private set; }\n        public ContainerNode SkyWhale { get; private set; }\n        public ContainerNode IlluminantCluster { get; private set; }\n    }\n\n    public class ContainerNode : SceneNode\n    {\n        public ContainerNode()\n        {\n            this.Slots = new List<SceneItem>();\n        }\n\n        public List<SceneItem> Slots { get; private set; }\n    }\n\n    public class ContainerNode<T> : ContainerNode\n    {\n        public T Item { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MathHelper2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public static class MathHelper2\n    {\n        public static Vector2 Round(Vector2 vector2)\n        {\n            var rounding = MidpointRounding.AwayFromZero;\n            return new Vector2((float)Math.Round(vector2.X, rounding), (float)Math.Round(vector2.Y, rounding));\n        }\n\n        public static Rectangle Transform(Rectangle rectangle, Matrix matrix)\n        {\n            Vector2 lt = new Vector2(rectangle.Left, rectangle.Top);\n            Vector2 rb = new Vector2(rectangle.Right, rectangle.Bottom);\n            Vector2.Transform(ref lt, ref matrix, out lt);\n            Vector2.Transform(ref rb, ref matrix, out rb);\n            return new Rectangle((int)lt.X, (int)lt.Y, (int)(rb.X - lt.X), (int)(rb.Y - lt.Y));\n        }\n\n        public static float Max(params float[] values)\n        {\n            if (values != null && values.Length > 0)\n            {\n                float maxValue = values[0];\n                for (int i = 1; i < values.Length; i++)\n                {\n                    maxValue = Math.Max(maxValue, values[i]);\n                }\n                return maxValue;\n            }\n            else\n            {\n                return 0;\n            }\n        }\n\n        public static float Min(params float[] values)\n        {\n            if (values != null && values.Length > 0)\n            {\n                float minValue = values[0];\n                for (int i = 1; i < values.Length; i++)\n                {\n                    minValue = Math.Min(minValue, values[i]);\n                }\n                return minValue;\n            }\n            else\n            {\n                return 0;\n            }\n        }\n\n        public static Color Lerp(Color[] colors, float amount)\n        {\n            if (colors == null || colors.Length <= 0)\n            {\n                return Color.Transparent;\n            }\n            if (colors.Length == 1)\n            {\n                return colors[0];\n            }\n            amount = amount % (colors.Length - 1);\n            int index = (int)amount;\n            amount = amount - index;\n            return new Color(\n                (byte)MathHelper.Lerp(colors[index].R, colors[index + 1].R, amount),\n                (byte)MathHelper.Lerp(colors[index].G, colors[index + 1].G, amount),\n                (byte)MathHelper.Lerp(colors[index].B, colors[index + 1].B, amount),\n                (byte)MathHelper.Lerp(colors[index].A, colors[index + 1].A, amount)\n                );\n        }\n\n        public static Color HSVtoColor(float hue, float saturation, float value)\n        {\n            Vector3 color = Vector3.Zero;\n            if (saturation == 0)\n            {\n                color = new Vector3(value);\n            }\n            else\n            {\n                hue /= 60;\n                var i = (int)hue;\n                var f = hue - i;\n                var a = value * (1 - saturation);\n                var b = value * (1 - saturation * f);\n                var c = value * (1 - saturation * (1 - f));\n                switch (i)\n                {\n                    case 0: color = new Vector3(value, c, a); break;\n                    case 1: color = new Vector3(b, value, a); break;\n                    case 2: color = new Vector3(a, value, c); break;\n                    case 3: color = new Vector3(a, b, value); break;\n                    case 4: color = new Vector3(c, a, value); break;\n                    case 5: color = new Vector3(value, a, b); break;\n                }\n            }\n            return new Color(color);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MeshBatcher.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing WzComparerR2.Animation;\nusing WzComparerR2.Rendering;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender\n{\n    sealed class MeshBatcher : IDisposable\n    {\n        public MeshBatcher(GraphicsDevice graphicsDevice)\n        {\n            this.GraphicsDevice = graphicsDevice;\n            this.alphaBlendState = StateEx.NonPremultipled_Hidef();\n            this.maskState = StateEx.SrcAlphaMask();\n            this.meshPool = new Stack<MeshItem>();\n        }\n\n        public GraphicsDevice GraphicsDevice { get; private set; }\n        public bool D2DEnabled { get; set; }\n        public bool CullingEnabled { get; set; }\n\n        //内部batcher\n        SpriteBatchEx sprite;\n        Spine.SkeletonRenderer spineRender;\n        D2DRenderer d2dRender;\n        MsSpriteRenderer msSpriteRenderer;\n        ItemType lastItem;\n        Stack<MeshItem> meshPool;\n\n        //innerState\n        private readonly BlendState alphaBlendState;\n        private readonly BlendState maskState;\n\n        //start参数\n        private Matrix matrix;\n        private Vector2 camaraOriginWorldPosition;\n        private float gameTime;\n        private bool isInBeginEndPair;\n\n        //culling参数\n        private bool matrixNoRot;\n        private Rectangle viewport;\n\n\n        public void Begin(Vector2 camaraOriginWorldPosition, float gameTime)\n        {\n            this.matrix = Matrix.CreateTranslation(-camaraOriginWorldPosition.X, -camaraOriginWorldPosition.Y, 0);\n            this.camaraOriginWorldPosition = camaraOriginWorldPosition;\n            this.gameTime = gameTime;\n            this.lastItem = ItemType.Unknown;\n            this.isInBeginEndPair = true;\n            this.PrepareCullingParameters();\n        }\n\n        private void PrepareCullingParameters()\n        {\n            if (this.matrix.M12 == 0 && this.matrix.M21 == 0)\n            {\n                matrixNoRot = true;\n            }\n            else\n            {\n                matrixNoRot = false;\n            }\n\n\n            //重新计算viewport\n            this.viewport = this.GraphicsDevice.Viewport.Bounds;\n            if (matrixNoRot)\n            {\n                var invmt = Matrix.Invert(this.matrix);\n                var lt = Vector2.Transform(this.viewport.Location.ToVector2(), invmt);\n                var rb = Vector2.Transform(new Vector2(this.viewport.Right, this.viewport.Bottom), invmt);\n                int l = (int)Math.Floor(lt.X);\n                int t = (int)Math.Floor(lt.Y);\n                int r = (int)Math.Ceiling(rb.X);\n                int b = (int)Math.Ceiling(rb.Y);\n                this.viewport = new Rectangle(l, t, r - l, b - t);\n            }\n        }\n\n        public void Draw(MeshItem mesh)\n        {\n            if (mesh.RenderObject is Frame frame)\n            {\n                this.DrawItem(mesh, frame);\n            }\n            else if (mesh.RenderObject is Spine.V2.Skeleton skeletonV2)\n            {\n                this.DrawItem(mesh, skeletonV2);\n            }\n            else if (mesh.RenderObject is Spine.Skeleton skeletonV4)\n            {\n                this.DrawItem(mesh, skeletonV4);\n            }\n            else if (mesh.RenderObject is TextMesh text)\n            {\n                this.DrawItem(mesh, text);\n            }\n            else if (mesh.RenderObject is LineListMesh lineList)\n            {\n                this.DrawItem(lineList);\n            }\n            else if (mesh.RenderObject is ParticleSystem particle)\n            {\n                this.DrawItem(mesh, particle);\n            }\n            else if (mesh.RenderObject is MsCustomSprite msCustomSprite)\n            {\n                this.DrawItem(mesh, msCustomSprite);\n            }\n        }\n\n        private void DrawItem(MeshItem mesh, Frame frame)\n        {\n            if (frame == null || frame.Texture == null)\n            {\n                return;\n            }\n\n            var origin = mesh.FlipX ? new Vector2(frame.Rectangle.Width - frame.Origin.X, frame.Origin.Y) : frame.Origin.ToVector2();\n            var eff = mesh.FlipX ? SpriteEffects.FlipHorizontally : SpriteEffects.None;\n\n            var rect = frame.Rectangle;\n            if (mesh.FlipX)\n            {\n                rect.X = -rect.Right;\n            }\n\n            //兼容平铺\n            if (mesh.TileRegion != null)\n            {\n                var region = mesh.TileRegion.Value;\n                for (int y = region.Top; y < region.Bottom; y++)\n                {\n                    for (int x = region.Left; x < region.Right; x++)\n                    {\n                        Vector2 pos = mesh.Position + mesh.TileOffset * new Vector2(x, y);\n                        if (this.CullingEnabled)\n                        {\n                            var tileRect = rect;\n                            tileRect.Offset(pos);\n                            if (!this.IntersectsVP(tileRect))\n                                continue;\n                        }\n\n                        Prepare(frame.Blend ? ItemType.Sprite_BlendAdditive : ItemType.Sprite);\n                        sprite.Draw(frame.Texture, pos,\n                            frame.AtlasRect,\n                            new Color(Color.White, frame.A0),\n                            0,\n                            origin,\n                            1,\n                            eff,\n                            0\n                            );\n                    }\n                }\n            }\n            else\n            {\n                rect.Offset(mesh.Position);\n                if (!this.CullingEnabled || this.IntersectsVP(rect))\n                {\n                    Prepare(frame.Blend ? ItemType.Sprite_BlendAdditive : ItemType.Sprite);\n                    sprite.Draw(frame.Texture, mesh.Position,\n                        frame.AtlasRect,\n                        new Color(Color.White, frame.A0),\n                        0,\n                        origin,\n                        1,\n                        eff,\n                        0\n                        );\n                }\n            }\n        }\n\n        private void DrawItem(MeshItem mesh, Spine.V2.Skeleton skeleton)\n        {\n            skeleton.FlipX = mesh.FlipX;\n\n            //兼容平铺\n            if (mesh.TileRegion != null)\n            {\n                var region = mesh.TileRegion.Value;\n                for (int y = region.Top; y < region.Bottom; y++)\n                {\n                    for (int x = region.Left; x < region.Right; x++)\n                    {\n                        Vector2 pos = mesh.Position + mesh.TileOffset * new Vector2(x, y);\n                        skeleton.X = pos.X;\n                        skeleton.Y = pos.Y;\n                        skeleton.UpdateWorldTransform();\n                        Prepare(ItemType.Skeleton);\n                        this.spineRender.Draw(skeleton);\n                    }\n                }\n            }\n            else\n            {\n                skeleton.X = mesh.Position.X;\n                skeleton.Y = mesh.Position.Y;\n                skeleton.UpdateWorldTransform();\n                Prepare(ItemType.Skeleton);\n                this.spineRender.Draw(skeleton);\n            }\n        }\n\n        private void DrawItem(MeshItem mesh, Spine.Skeleton skeleton)\n        {\n            skeleton.ScaleX = mesh.FlipX ? -1 : 1;\n\n            //兼容平铺\n            if (mesh.TileRegion != null)\n            {\n                var region = mesh.TileRegion.Value;\n                for (int y = region.Top; y < region.Bottom; y++)\n                {\n                    for (int x = region.Left; x < region.Right; x++)\n                    {\n                        Vector2 pos = mesh.Position + mesh.TileOffset * new Vector2(x, y);\n                        skeleton.X = pos.X;\n                        skeleton.Y = pos.Y;\n                        skeleton.UpdateWorldTransform();\n                        Prepare(ItemType.Skeleton);\n                        this.spineRender.Draw(skeleton);\n                    }\n                }\n            }\n            else\n            {\n                skeleton.X = mesh.Position.X;\n                skeleton.Y = mesh.Position.Y;\n                skeleton.UpdateWorldTransform();\n                Prepare(ItemType.Skeleton);\n                this.spineRender.Draw(skeleton);\n            }\n        }\n\n        private void DrawItem(MeshItem mesh, TextMesh text)\n        {\n            if (text.Text != null && text.Font != null)\n            {\n                var size = text.Font.MeasureString(text.Text);\n                var pos = mesh.Position;\n\n                switch (text.Align)\n                {\n                    case Alignment.Near: break;\n                    case Alignment.Center: pos.X -= (int)(size.X / 2); break;\n                    case Alignment.Far: pos.X -= size.X; break;\n                }\n\n                var padding = text.Padding;\n                var rect = new Rectangle((int)(pos.X - padding.Left),\n                    (int)(pos.Y - padding.Top),\n                    (int)(size.X + padding.Left + padding.Right),\n                    (int)(size.Y + padding.Top + padding.Bottom)\n                    );\n\n                if (this.CullingEnabled)\n                {\n                    if (!this.IntersectsVP(rect))\n                    {\n                        return;\n                    }\n                }\n\n                object baseFont = text.Font.BaseFont ?? text.Font;\n\n                if (baseFont is XnaFont)\n                {\n                    Prepare(ItemType.Sprite);\n                }\n                else if (baseFont is D2DFont)\n                {\n                    Prepare(ItemType.D2DObject);\n                }\n                else\n                {\n                    return;\n                }\n\n                if (text.BackColor.A > 0) //绘制背景\n                {\n                    switch (this.lastItem)\n                    {\n                        case ItemType.Sprite: sprite.FillRoundedRectangle(rect, text.BackColor); break;\n                        case ItemType.D2DObject: d2dRender.FillRoundedRectangle(rect, 3, text.BackColor); break;\n                    }\n                }\n\n                if (text.ForeColor.A > 0) //绘制文字\n                {\n                    switch (this.lastItem)\n                    {\n                        case ItemType.Sprite: sprite.DrawStringEx((XnaFont)baseFont, text.Text, pos, text.ForeColor); break;\n                        case ItemType.D2DObject: d2dRender.DrawString((D2DFont)baseFont, text.Text, pos, text.ForeColor); break;\n                    }\n                }\n            }\n        }\n\n        private void DrawItem(LineListMesh lineList)\n        {\n            if (lineList != null && lineList.Lines != null)\n            {\n                var vertices = lineList.Lines;\n                int vertexCount = vertices.Length / 2 * 2;\n                if (this.D2DEnabled)\n                {\n                    Prepare(ItemType.D2DObject);\n                    for (int i = 0; i < vertexCount; i += 2)\n                    {\n                        this.d2dRender.DrawLine(vertices[i].ToVector2(), vertices[i + 1].ToVector2(),\n                            lineList.Thickness, lineList.Color);\n                    }\n                }\n                else\n                {\n                    Prepare(ItemType.Sprite);\n                    for (int i = 0; i < vertexCount; i += 2)\n                    {\n                        sprite.DrawLine(vertices[i], vertices[i + 1], lineList.Thickness, lineList.Color);\n                    }\n                }\n            }\n        }\n\n        private void DrawItem(MeshItem mesh, ParticleSystem particleSystem)\n        {\n            if (particleSystem.Texture?.Texture != null)\n            {\n                ItemType itemType = ItemType.Unknown;\n                if (particleSystem.BlendFuncSrc == ParticleBlendFunc.SRC_ALPHA\n                    || (int)particleSystem.BlendFuncSrc == 12) //TODO: what's this? KMS v.293, esfera_temple_big\n                {\n                    switch (particleSystem.BlendFuncDst)\n                    {\n                        case ParticleBlendFunc.ONE: //5,2\n                        case ParticleBlendFunc.DEST_ALPHA: //5,7\n                            itemType = ItemType.Sprite_BlendAdditive;\n                            break;\n                        case ParticleBlendFunc.SRC_COLOR: //12,3\n                        case ParticleBlendFunc.INV_SRC_ALPHA: //5,6\n                            itemType = ItemType.Sprite_BlendNonPremultiplied;\n                            break;\n                    }\n                }\n                if (particleSystem.BlendFuncSrc == ParticleBlendFunc.ZERO && particleSystem.BlendFuncDst == ParticleBlendFunc.INV_SRC_ALPHA) //1,6\n                {\n                    itemType = ItemType.Sprite_BlendMask;\n                }\n\n                if (itemType == ItemType.Unknown)\n                {\n                    throw new Exception($\"Unknown particle blendfunc: {particleSystem.BlendFuncSrc}, {particleSystem.BlendFuncDst}\");\n                }\n                Prepare(itemType);\n                particleSystem.Draw(this.sprite, mesh.Position);\n            }\n        }\n\n        private void DrawItem(MeshItem mesh, MsCustomSprite msCustomSprite)\n        {\n            Prepare(ItemType.MsSprite);\n            this.msSpriteRenderer.Draw(mesh.Position, msCustomSprite.Size, msCustomSprite.Material);\n        }\n\n        public Rectangle[] Measure(MeshItem mesh)\n        {\n            Rectangle[] region = null;\n            int count;\n            Measure(mesh, ref region, out count);\n            return region;\n        }\n\n        public void Measure(MeshItem mesh, ref Rectangle[] region, out int count)\n        {\n            Rectangle rect = Rectangle.Empty;\n\n            if (mesh.RenderObject is TextMesh)\n            {\n                var textItem = (TextMesh)mesh.RenderObject;\n                var size = textItem.Font.MeasureString(textItem.Text);\n                var pos = mesh.Position;\n\n                switch (textItem.Align)\n                {\n                    case Alignment.Near: break;\n                    case Alignment.Center: pos.X -= size.X / 2; break;\n                    case Alignment.Far: pos.X -= size.X; break;\n                }\n\n                var padding = textItem.Padding;\n                var rectBg = new Rectangle((int)(pos.X - padding.Left),\n                    (int)(pos.Y - padding.Top),\n                    (int)(size.X + padding.Left + padding.Right),\n                    (int)(size.Y + padding.Top + padding.Bottom)\n                    );\n                var rectText = new Rectangle((int)pos.X, (int)pos.Y, (int)size.X, (int)size.Y);\n\n                count = 1;\n                EnsureArraySize(ref region, count);\n                Rectangle.Union(ref rectBg, ref rectText, out region[0]);\n                return;\n            }\n\n            if (mesh.RenderObject is Frame)\n            {\n                var frame = (Frame)mesh.RenderObject;\n                rect = frame.Rectangle;\n                if (mesh.FlipX)\n                {\n                    rect.X = -rect.Right;\n                }\n            }\n            else\n            {\n                count = 0;\n                return;\n            }\n\n            rect.X += (int)mesh.Position.X;\n            rect.Y += (int)mesh.Position.Y;\n\n            if (mesh.TileRegion != null)\n            {\n                var tileRegion = mesh.TileRegion.Value;\n                count = tileRegion.Width * tileRegion.Height;\n                EnsureArraySize(ref region, count);\n                Point offset = mesh.TileOffset.ToPoint();\n                int i = 0;\n\n                for (int y = tileRegion.Top; y < tileRegion.Bottom; y++)\n                {\n                    for (int x = tileRegion.Left; x < tileRegion.Right; x++)\n                    {\n                        region[i++] = new Rectangle(rect.X + x * offset.X,\n                            rect.Y + y * offset.Y,\n                            rect.Width,\n                            rect.Height);\n                    }\n                }\n            }\n            else\n            {\n                count = 1;\n                EnsureArraySize(ref region, count);\n                region[0] = rect;\n            }\n        }\n\n        private void EnsureArraySize<T>(ref T[] array, int length)\n        {\n            if (array == null)\n            {\n                array = new T[length];\n            }\n            else if (array.Length < length)\n            {\n                Array.Resize(ref array, length);\n            }\n        }\n\n        public void End()\n        {\n            InnerFlush();\n            this.lastItem = ItemType.Unknown;\n            this.isInBeginEndPair = false;\n        }\n\n        private void Prepare(ItemType itemType)\n        {\n            if (lastItem == itemType)\n            {\n                return;\n            }\n\n            switch (itemType)\n            {\n                case ItemType.Sprite:\n                case ItemType.Skeleton:\n                case ItemType.D2DObject:\n                case ItemType.Sprite_BlendAdditive:\n                case ItemType.Sprite_BlendNonPremultiplied:\n                case ItemType.Sprite_BlendMask:\n                case ItemType.MsSprite:\n                    InnerFlush();\n                    lastItem = itemType;\n                    InnerBegin();\n                    break;\n            }\n        }\n\n        private void InnerBegin()\n        {\n            switch (lastItem)\n            {\n                case ItemType.Sprite:\n                    if (this.sprite == null)\n                    {\n                        this.sprite = new SpriteBatchEx(this.GraphicsDevice);\n                    }\n                    this.sprite.Begin(SpriteSortMode.Deferred, this.alphaBlendState, transformMatrix: this.matrix);\n                    break;\n\n                case ItemType.Skeleton:\n                    if (this.spineRender == null)\n                    {\n                        this.spineRender = new Spine.SkeletonRenderer(this.GraphicsDevice);\n                    }\n                    if (this.spineRender.Effect is BasicEffect basicEff)\n                    {\n                        basicEff.World = this.matrix;\n                        basicEff.Projection = Matrix.CreateOrthographicOffCenter(0, this.GraphicsDevice.Viewport.Width, this.GraphicsDevice.Viewport.Height, 0, 1, 0);\n                    }\n                    this.spineRender.Begin();\n                    break;\n\n                case ItemType.D2DObject:\n                    if (this.d2dRender == null)\n                    {\n                        this.d2dRender = new D2DRenderer(this.GraphicsDevice);\n                    }\n                    this.d2dRender.Begin(this.matrix);\n                    break;\n\n                case ItemType.Sprite_BlendAdditive:\n                    if (this.sprite == null)\n                    {\n                        this.sprite = new SpriteBatchEx(this.GraphicsDevice);\n                    }\n                    this.sprite.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: this.matrix);\n                    break;\n\n                case ItemType.Sprite_BlendNonPremultiplied:\n                    if (this.sprite == null)\n                    {\n                        this.sprite = new SpriteBatchEx(this.GraphicsDevice);\n                    }\n                    this.sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: this.matrix);\n                    break;\n                case ItemType.Sprite_BlendMask:\n                    if (this.sprite == null)\n                    {\n                        this.sprite = new SpriteBatchEx(this.GraphicsDevice);\n                    }\n                    this.sprite.Begin(SpriteSortMode.Deferred, this.maskState, transformMatrix: this.matrix);\n                    break;\n\n                case ItemType.MsSprite:\n                    if (this.msSpriteRenderer == null)\n                    {\n                        this.msSpriteRenderer = new MsSpriteRenderer(this.GraphicsDevice);\n                    }\n                    this.msSpriteRenderer.Begin(this.camaraOriginWorldPosition, this.gameTime);\n                    break;\n            }\n        }\n\n        private void InnerFlush()\n        {\n            switch (lastItem)\n            {\n                case ItemType.Sprite:\n                case ItemType.Sprite_BlendAdditive:\n                case ItemType.Sprite_BlendNonPremultiplied:\n                case ItemType.Sprite_BlendMask:\n                    this.sprite.End();\n                    break;\n\n                case ItemType.Skeleton:\n                    this.spineRender.End();\n                    break;\n\n                case ItemType.D2DObject:\n                    this.d2dRender.End();\n                    break;\n\n                case ItemType.MsSprite:\n                    this.msSpriteRenderer.End();\n                    break;\n            }\n        }\n\n        private bool IntersectsVP(Rectangle rect)\n        {\n            if (this.matrixNoRot)\n            {\n                bool isIntersects;\n                this.viewport.Intersects(ref rect, out isIntersects);\n                return isIntersects;\n            }\n            else\n            {\n                var mt = this.matrix;\n                Vector2 lt = new Vector2(rect.Left, rect.Top),\n                    rt = new Vector2(rect.Right, rect.Top),\n                    lb = new Vector2(rect.Left, rect.Bottom),\n                    rb = new Vector2(rect.Right, rect.Bottom);\n                Vector2.Transform(ref lt, ref mt, out lt);\n                Vector2.Transform(ref rt, ref mt, out rt);\n                Vector2.Transform(ref lb, ref mt, out lb);\n                Vector2.Transform(ref rb, ref mt, out rb);\n\n                int left = (int)Math.Floor(MathHelper2.Min(lt.X, rt.X, lb.X, rb.X));\n                int top = (int)Math.Floor(MathHelper2.Min(lt.Y, rt.Y, lb.Y, rb.Y));\n                int right = (int)Math.Ceiling(MathHelper2.Max(lt.X, rt.X, lb.X, rb.X));\n                int bottom = (int)Math.Ceiling(MathHelper2.Max(lt.Y, rt.Y, lb.Y, rb.Y));\n                var bound = new Rectangle(left, top, right - left, bottom - top);\n                bool isIntersects;\n                this.viewport.Intersects(ref bound, out isIntersects);\n                return isIntersects;\n            }\n        }\n\n        public MeshItem MeshPop()\n        {\n            if (this.meshPool.Count > 0)\n            {\n                var mesh = this.meshPool.Pop();\n                mesh.RenderObject = null;\n                mesh.Position = Vector2.Zero;\n                mesh.Z0 = 0;\n                mesh.Z1 = 0;\n                mesh.FlipX = false;\n                mesh.TileRegion = null;\n                mesh.TileOffset = Vector2.Zero;\n                return mesh;\n            }\n            else\n            {\n                return new MeshItem();\n            }\n        }\n\n        public void MeshPush(MeshItem mesh)\n        {\n            mesh.RenderObject = null;\n            this.meshPool.Push(mesh);\n        }\n\n        public void Dispose()\n        {\n            this.sprite?.Dispose();\n            this.spineRender?.Dispose();\n            this.alphaBlendState.Dispose();\n            this.maskState.Dispose();\n            this.meshPool.Clear();\n        }\n\n        private enum ItemType\n        {\n            Unknown = 0,\n            Sprite = 1,\n            Skeleton = 2,\n            D2DObject = 3,\n            Sprite_AlphaBlend = Sprite,\n            Sprite_BlendAdditive = 4,\n            Sprite_BlendNonPremultiplied = 5,\n            Sprite_BlendMask = 6,\n            MsSprite = 7,\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MeshItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    class MeshItem : IComparable<MeshItem>\n    {\n        public object RenderObject { get; set; }\n        public Vector2 Position { get; set; }\n        public int Z0 { get; set; }\n        public int Z1 { get; set; }\n\n        //附加信息\n        public bool FlipX { get; set; }\n        public Rectangle? TileRegion { get; set; }\n        public Vector2 TileOffset { get; set; }\n\n        public int CompareTo(MeshItem other)\n        {\n            int comp;\n            if ((comp = this.Z0.CompareTo(other.Z0)) != 0\n                || (comp = this.Z1.CompareTo(other.Z1)) != 0)\n            {\n                return comp;\n            }\n            return 0;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MiniMap.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MiniMap\n    {\n        public MiniMap()\n        {\n            this.ExtraCanvas = new Dictionary<string, Texture2D>();\n        }\n\n        public Texture2D Canvas { get; set; }\n        public Dictionary<string, Texture2D> ExtraCanvas { get; private set; }\n        public int Width { get; set; }\n        public int Height { get; set; }\n        public int CenterX { get; set; }\n        public int CenterY { get; set; }\n        public int Mag { get; set; }\n\n        public Texture2D MapMark { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MouseButton.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.MapRender\n{\n    [Flags]\n    public enum MouseButton\n    {\n        LeftButton = 1,\n        MiddleButton = 2,\n        RightButton = 4,\n        XButton1 = 8,\n        XButton2 = 16\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MsCustomSprite.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.MapRender.Effects;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MsCustomSpriteData\n    {\n        public MsCustomTexture[] Textures { get; set; }\n        public MsShader Shader { get; set; }\n    }\n\n    public class MsShader\n    {\n        public string ID { get; set; }\n        public Dictionary<string, MsShaderConstant> Constants { get; private set; } = new();\n    }\n\n    public struct MsCustomTexture\n    {\n        public Texture2D Texture { get; set; }\n        public Texture2D[] Textures { get; set; }\n        public int AddressU { get; set; }\n        public int AddressV { get; set; }\n    }\n\n    public struct MsShaderConstant\n    {\n        public MsShaderConstant(float value)\n        {\n            this.ColumnCount = 1;\n            this.X = value;\n            this.Y = 0;\n            this.Z = 0;\n        }\n\n        public MsShaderConstant(float x, float y)\n        {\n            this.ColumnCount = 2;\n            this.X = x;\n            this.Y = y;\n            this.Z = 0;\n        }\n\n        public MsShaderConstant(float x, float y, float z)\n        {\n            this.ColumnCount = 3;\n            this.X = x;\n            this.Y = y;\n            this.Z = z;\n        }\n\n        public int ColumnCount { get; set; }\n        public float X { get; set; }\n        public float Y { get; set; }\n        public float Z { get; set; }\n\n        public float ToScalar() => this.X;\n        public Vector2 ToVector2() => new Vector2(this.X, this.Y);\n        public Vector3 ToVector3() => new Vector3(this.X, this.Y, this.Z);\n    }\n\n    public class MsCustomSprite\n    {\n        public Vector2 Size { get; set; }\n        public ShaderMaterial Material { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/MsSpriteRenderer.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.MapRender.Effects;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender\n{\n    public class MsSpriteRenderer : IDisposable\n    {\n        public MsSpriteRenderer(GraphicsDevice graphicsDevice) : this(graphicsDevice, 128)\n        {\n        }\n\n        public MsSpriteRenderer(GraphicsDevice graphicsDevice, int capacity)\n        {\n            this.GraphicsDevice = graphicsDevice;\n            this.loadedPixelShaders = new Dictionary<string, Effect>();\n            this.vertexShader = EffectResources.CreateNativeShader(graphicsDevice, \"vs_position_color_texture\");\n            this.capacity = capacity;\n            this.vertices = new VertexPosition4ColorTexture[capacity * 4];\n            this.indices = new short[capacity * 6];\n        }\n\n        public GraphicsDevice GraphicsDevice { get; private set; }\n        private Effect vertexShader;\n        private readonly Dictionary<string, Effect> loadedPixelShaders;\n        private Texture2D cachedBackBufferTexture;\n\n        private ShaderMaterial lastShaderMaterial;\n        private VertexPosition4ColorTexture[] vertices;\n        private short[] indices;\n        private int capacity;\n        private int primitiveCount;\n        private bool isInBeginEndPair;\n        private bool isDisposed;\n\n        private Matrix vp;\n        private Matrix vp_inv;\n        private Vector4 resolution_time;\n        private Matrix world;\n\n        public void Begin(Vector2 cameraOrigin, float gameTime)\n        {\n            if (this.isInBeginEndPair)\n            {\n                throw new InvalidOperationException(\"Begin cannot be called again until End has been successfully called.\");\n            }\n\n            var viewPort = this.GraphicsDevice.Viewport;\n            Matrix.CreateOrthographicOffCenter(cameraOrigin.X, cameraOrigin.X + viewPort.Width, cameraOrigin.Y + viewPort.Height, cameraOrigin.Y, 0, -1, out this.vp);\n            Matrix.Invert(ref this.vp, out this.vp_inv);\n            this.resolution_time = new Vector4(viewPort.Width, viewPort.Height, gameTime, 0);\n            this.world = Matrix.Identity;\n            this.TrySetCommonParameters(this.vertexShader);\n\n            this.isInBeginEndPair = true;\n        }\n\n        public void Draw(Vector2 worldPosition, Vector2 size, ShaderMaterial shaderMaterial)\n        {\n            if (this.isDisposed)\n            {\n                throw new ObjectDisposedException(nameof(LightRenderer));\n            }\n            if (!this.isInBeginEndPair)\n            {\n                throw new InvalidOperationException(\"Begin must be called successfully before you can call Draw.\");\n            }\n\n            if (this.lastShaderMaterial != null && !this.lastShaderMaterial.Equals(shaderMaterial))\n            {\n                this.Flush();\n            }\n            if (this.capacity - this.primitiveCount < 2)\n            {\n                this.Flush();\n            }\n\n            this.lastShaderMaterial = shaderMaterial;\n            int vertexOffset = this.primitiveCount * 2;\n            int indexOffset = this.primitiveCount * 3;\n            Vector2 lt = worldPosition;\n            Vector2 rb = worldPosition + size;\n            this.vertices[vertexOffset + 0] = new VertexPosition4ColorTexture(new Vector4(lt.X, lt.Y, 0, 1), Color.White, new Vector2(0, 0));\n            this.vertices[vertexOffset + 1] = new VertexPosition4ColorTexture(new Vector4(rb.X, lt.Y, 0, 1), Color.White, new Vector2(1, 0));\n            this.vertices[vertexOffset + 2] = new VertexPosition4ColorTexture(new Vector4(lt.X, rb.Y, 0, 1), Color.White, new Vector2(0, 1));\n            this.vertices[vertexOffset + 3] = new VertexPosition4ColorTexture(new Vector4(rb.X, rb.Y, 0, 1), Color.White, new Vector2(1, 1));\n            this.indices[indexOffset + 0] = (short)(vertexOffset + 0);\n            this.indices[indexOffset + 1] = (short)(vertexOffset + 1);\n            this.indices[indexOffset + 2] = (short)(vertexOffset + 2);\n            this.indices[indexOffset + 3] = (short)(vertexOffset + 1);\n            this.indices[indexOffset + 4] = (short)(vertexOffset + 3);\n            this.indices[indexOffset + 5] = (short)(vertexOffset + 2);\n            this.primitiveCount += 2;\n        }\n\n        public void End()\n        {\n            if (!this.isInBeginEndPair)\n            {\n                throw new InvalidOperationException(\"Begin must be called before calling End.\");\n            }\n\n            this.Flush();\n            this.lastShaderMaterial = null;\n            this.isInBeginEndPair = false;\n        }\n\n        private void Flush()\n        {\n            var shaderMaterial = this.lastShaderMaterial;\n            var primitiveCount = this.primitiveCount;\n            if (shaderMaterial == null || primitiveCount == 0)\n            {\n                return;\n            }\n\n            if (!this.loadedPixelShaders.TryGetValue(shaderMaterial.ShaderID, out Effect pixelShader))\n            {\n                pixelShader = EffectResources.CreateNativeShader(this.GraphicsDevice, shaderMaterial.ShaderID);\n                this.loadedPixelShaders.Add(shaderMaterial.ShaderID, pixelShader);\n            }\n            this.vertexShader.CurrentTechnique.Passes[0].Apply();\n\n            // pixel shader pre-process\n            if (shaderMaterial is IMaplestoryEffectMatrices m)\n            {\n                m.ViewProjection = this.vp;\n                m.ViewProjectionInverse = this.vp_inv;\n                m.ResolutionTime = this.resolution_time;\n            }\n            IBackgroundCaptureEffect bgTexEffect = shaderMaterial as IBackgroundCaptureEffect;\n            if (bgTexEffect != null)\n            {\n                var pp = this.GraphicsDevice.PresentationParameters;\n                Texture2D bgTex;\n                if (this.cachedBackBufferTexture == null \n                    || this.cachedBackBufferTexture.Width != pp.BackBufferWidth \n                    || this.cachedBackBufferTexture.Height != pp.BackBufferHeight \n                    || this.cachedBackBufferTexture.Format != pp.BackBufferFormat)\n                {\n                    bgTex = new Texture2D(this.GraphicsDevice,\n                        this.GraphicsDevice.PresentationParameters.BackBufferWidth,\n                        this.GraphicsDevice.PresentationParameters.BackBufferHeight,\n                        false,\n                        this.GraphicsDevice.PresentationParameters.BackBufferFormat);\n                    this.cachedBackBufferTexture?.Dispose();\n                    this.cachedBackBufferTexture = bgTex;\n                }\n                else\n                {\n                    bgTex = this.cachedBackBufferTexture;\n                }\n                this.GraphicsDevice.CopyBackBuffer(bgTex);\n                bgTexEffect.BackgroundTexture = bgTex;\n            }\n            shaderMaterial.ApplyParameters(pixelShader);\n            pixelShader.CurrentTechnique.Passes[0].Apply();\n            shaderMaterial.ApplySamplerStates(this.GraphicsDevice);\n\n            // draw primitives\n            this.GraphicsDevice.RasterizerState = RasterizerState.CullNone;\n            this.GraphicsDevice.BlendState = BlendState.NonPremultiplied;\n            this.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, this.vertices, 0, primitiveCount * 2, this.indices, 0, primitiveCount);\n            this.primitiveCount = 0;\n\n            // clean-up shader parameters\n            if (bgTexEffect != null)\n            {\n                bgTexEffect.BackgroundTexture = null;\n            }\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        protected void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (this.vertexShader != null)\n                {\n                    this.vertexShader.Dispose();\n                    this.vertexShader = null;\n                }\n                if (this.loadedPixelShaders != null)\n                {\n                    foreach (var effect in this.loadedPixelShaders)\n                    {\n                        effect.Value.Dispose();\n                    }\n                    this.loadedPixelShaders.Clear();\n                }\n                if (this.cachedBackBufferTexture != null)\n                {\n                    this.cachedBackBufferTexture.Dispose();\n                    this.cachedBackBufferTexture = null;\n                }\n            }\n\n            this.lastShaderMaterial = null;\n            this.vertices = null;\n            this.indices = null;\n            this.isDisposed = true;\n        }\n\n        private void TrySetCommonParameters(Effect effect)\n        {\n            effect.Parameters[\"vp\"]?.SetValue(this.vp);\n            effect.Parameters[\"vp_inv\"]?.SetValue(this.vp_inv);\n            effect.Parameters[\"resolution_time\"]?.SetValue(this.resolution_time);\n            effect.Parameters[\"world\"]?.SetValue(this.world);\n        }\n\n        public struct VertexPosition4ColorTexture : IVertexType\n        {\n            public static readonly int Size = 28;\n\n            public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(Size,\n                new VertexElement(0, VertexElementFormat.Vector4, VertexElementUsage.Position, 0),\n                new VertexElement(16, VertexElementFormat.Color, VertexElementUsage.Color, 0),\n                new VertexElement(20, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)\n            );\n\n            public VertexPosition4ColorTexture(Vector4 position, Color color, Vector2 textureCoordinate)\n            {\n                this.Position = position;\n                this.Color = color;\n                this.TextureCoordinate = textureCoordinate;\n            }\n\n            public Vector4 Position { get; set; }\n            public Color Color { get; set; }\n            public Vector2 TextureCoordinate { get; set; }\n\n            public override string ToString() => $\"{{position: {this.Position}, color: {this.Color}, texcoord: {this.TextureCoordinate}}}\";\n            VertexDeclaration IVertexType.VertexDeclaration => VertexPosition4ColorTexture.VertexDeclaration;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Music.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing WzComparerR2.WzLib;\nusing System.Runtime.InteropServices;\nusing ManagedBass;\n\nnamespace WzComparerR2.MapRender\n{\n    class Music : IDisposable\n    {\n        public Music(Wz_Sound sound)\n        {\n            this.soundData = sound.ExtractSound();\n            this.pData = GCHandle.Alloc(this.soundData, GCHandleType.Pinned);\n            this.hStream = Bass.CreateStream(pData.AddrOfPinnedObject(), 0, this.soundData.Length, BassFlags.Default);\n            Music.GlobalVolumeChanged += this.OnGlobalVolumeChanged;\n        }\n\n        private byte[] soundData;\n        private GCHandle pData;\n        private int hStream;\n        private float? vol;\n\n        public bool IsLoop\n        {\n            get { return (Bass.ChannelFlags(hStream, 0, 0) & BassFlags.Loop) != 0; }\n            set { Bass.ChannelFlags(hStream, value ? BassFlags.Loop : BassFlags.Default, BassFlags.Loop); }\n        }\n\n        public PlayState State\n        {\n            get\n            {\n                var active = Bass.ChannelIsActive(hStream);\n                switch (active)\n                {\n                    case PlaybackState.Stopped: return PlayState.Stopped;\n                    case PlaybackState.Playing: return PlayState.Playing;\n                    case PlaybackState.Paused: return PlayState.Paused;\n                    default: return PlayState.Unknown;\n                }\n            }\n        }\n\n        public float Volume\n        {\n            get\n            {\n                if (vol == null)\n                {\n                    vol = Bass.ChannelGetAttribute(hStream, ChannelAttribute.Volume, out float value) ? value : 0;\n                }\n                return vol.Value;\n            }\n            set\n            {\n                vol = value;\n                Bass.ChannelSetAttribute(hStream, ChannelAttribute.Volume, vol.Value * globalVol);\n            }\n        }\n\n        public void Play()\n        {\n            Bass.ChannelPlay(hStream, false);\n        }\n\n        public void Pause()\n        {\n            Bass.ChannelPause(hStream);\n        }\n\n        public void Stop()\n        {\n            Bass.ChannelStop(hStream);\n        }\n\n        public void Dispose()\n        {\n            Music.GlobalVolumeChanged -= this.OnGlobalVolumeChanged;\n            Bass.StreamFree(hStream);\n            this.pData.Free();\n\n        }\n\n        public enum PlayState\n        {\n            Stopped = 0,\n            Playing = 1,\n            Paused = 2,\n\n            Unknown = -1,\n        }\n\n        private void OnGlobalVolumeChanged(object sender, EventArgs e)\n        {\n            this.Volume = Volume;\n        }\n\n        #region Global Volume\n        private static float globalVol = 1f;\n        private static event EventHandler GlobalVolumeChanged;\n        public static float GlobalVolume\n        {\n            get { return globalVol; }\n            set\n            {\n                globalVol = value;\n                GlobalVolumeChanged?.Invoke(null, EventArgs.Empty);\n            }\n        }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Particle.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public class Particle\n    {\n        public Vector2 Pos;\n        public float Angle;\n        public Color StartColor;\n        public Color EndColor;\n        public float StartSize;\n        public float EndSize;\n        public float StartSpin;\n        public float EndSpin;\n        public float Life;\n\n        //gravity\n        public Vector2 Dir;\n        public float RadialAcc;\n        public float TangentialAcc;\n\n        //radius\n        public float StartRadius;\n        public float EndRadius;\n        public float RotatePerSecond;\n\n        //lifecycle\n        public float Time;\n        public float NormalizedTime;\n\n        public void Reset()\n        {\n            this.Pos = Vector2.Zero;\n            this.StartColor = new Color();\n            this.EndColor = new Color();\n            this.StartSize = 0;\n            this.EndSize = 0;\n            this.StartSpin = 0;\n            this.EndSpin = 0;\n            this.Life = 0;\n\n            this.Dir = Vector2.Zero;\n            this.RadialAcc = 0;\n            this.TangentialAcc = 0;\n\n            this.StartRadius = 0;\n            this.EndRadius = 0;\n            this.RotatePerSecond = 0;\n\n            this.Time = 0;\n            this.NormalizedTime = 0;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/ParticleDesc.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Animation;\n\nnamespace WzComparerR2.MapRender\n{\n    public class ParticleDesc\n    {\n        public string Name { get; set; }\n        public int TotalParticle { get; set; }\n        public float Angle { get; set; }\n        public float AngleVar { get; set; }\n        public float Duration { get; set; }\n        public ParticleBlendFunc BlendFuncSrc { get; set; }\n        public ParticleBlendFunc BlendFuncDst { get; set; }\n        public Color StartColor { get; set; }\n        public Color StartColorVar { get; set; }\n        public Color EndColor { get; set; }\n        public Color EndColorVar { get; set; }\n        public int MiddlePoint0 { get; set; }\n        public int MiddlePointAlpha0 { get; set; }\n        public int MiddlePoint1 { get; set; }\n        public int MiddlePointAlpha1 { get; set; }\n        public float StartSize { get; set; }\n        public float StartSizeVar { get; set; }\n        public float EndSize { get; set; }\n        public float EndSizeVar { get; set; }\n        public float PosX { get; set; }\n        public float PosY { get; set; }\n        public float PosVarX { get; set; }\n        public float PosVarY { get; set; }\n        public float StartSpin { get; set; }\n        public float StartSpinVar { get; set; }\n        public float EndSpin { get; set; }\n        public float EndSpinVar { get; set; }\n        public ParticleGravityDesc Gravity { get; set; }\n        public ParticleRadiusDesc Radius { get; set; }\n        public float Life { get; set; }\n        public float LifeVar { get; set; }\n        public bool OpacityModifyRGB { get; set; }\n        public int PositionType { get; set; }\n        public Frame Texture { get; set; }\n    }\n\n    public class ParticleGravityDesc\n    {\n        public float X { get; set; }\n        public float Y { get; set; }\n        public float Speed { get; set; }\n        public float SpeedVar { get; set; }\n        public float RadialAccel { get; set; }\n        public float RadialAccelVar { get; set; }\n        public float TangentialAccel { get; set; }\n        public float TangentialAccelVar { get; set; }\n        public bool RotationIsDir { get; set; }\n    }\n\n    public class ParticleRadiusDesc\n    {\n        public float StartRadius { get; set; }\n        public float StartRadiusVar { get; set; }\n        public float EndRadius { get; set; }\n        public float EndRadiusVar { get; set; }\n        public float RotatePerSecond { get; set; }\n        public float RotatePerSecondVar { get; set; }\n    }\n\n    //same as d3d11blend\n    public enum ParticleBlendFunc\n    {\n        ZERO = 1,\n        ONE = 2,\n        SRC_COLOR = 3,\n        INV_SRC_COLOR = 4,\n        SRC_ALPHA = 5,\n        INV_SRC_ALPHA = 6,\n        DEST_ALPHA = 7,\n        INV_DEST_ALPHA = 8,\n        DEST_COLOR = 9,\n        INV_DEST_COLOR = 10,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/ParticleDesc1.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.MapRender\n{\n    public class ParticleDesc1\n    {\n        public string Name { get; set; }\n        public int MaxParticleCount { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/ParticleDesc3.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.MapRender\n{\n    public class ParticleDesc3\n    {\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/ParticleEmitter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public class ParticleEmitter\n    {\n        public ParticleEmitter(ParticleSystem particleSystem)\n        {\n            this.Owner = particleSystem;\n        }\n\n        public ParticleSystem Owner { get; private set; }\n\n        //初始速度方向\n        public float Angle { get; set; }\n        public float AngleVar { get; set; }\n        //颜色\n        public Color StartColor { get; set; }\n        public Color StartColorVar { get; set; }\n        public Color EndColor { get; set; }\n        public Color EndColorVar { get; set; }\n        //大小\n        public float StartSize { get; set; }\n        public float StartSizeVar { get; set; }\n        public float EndSize { get; set; }\n        public float EndSizeVar { get; set; }\n        //位置\n        public Vector2 Pos { get; set; }\n        public Vector2 PosVar { get; set; }\n        //旋转\n        public float StartSpin { get; set; }\n        public float StartSpinVar { get; set; }\n        public float EndSpin { get; set; }\n        public float EndSpinVar { get; set; }\n\n        //gravity相关\n        public float Speed { get; set; }\n        public float SpeedVar { get; set; }\n        public float RadialAccel { get; set; }\n        public float RadialAccelVar { get; set; }\n        public float TangentialAccel { get; set; }\n        public float TangentialAccelVar { get; set; }\n        public bool RotationIsDir { get; set; }\n\n        //radius相关\n        public float StartRadius { get; set; }\n        public float StartRadiusVar { get; set; }\n        public float EndRadius { get; set; }\n        public float EndRadiusVar { get; set; }\n        public float RotatePerSecond { get; set; }\n        public float RotatePerSecondVar { get; set; }\n\n        //life\n        public float Life { get; set; }\n        public float LifeVar { get; set; }\n\n        //发射频率\n        public float EmissionRate { get; set; } = 200;\n\n        public Particle Emit()\n        {\n            var particle = this.CreateParticle();\n            this.InitParticle(particle);\n            return particle;\n        }\n\n        private Particle CreateParticle()\n        {\n            Particle particle;\n            if (this.Owner.ParticlePool.Count > 0)\n            {\n                particle = this.Owner.ParticlePool.Pop();\n                particle.Reset();\n            }\n            else\n            {\n                particle = new Particle();\n            }\n            return particle;\n        }\n\n        private void InitParticle(Particle particle)\n        {\n            var r = this.Owner.Rand;\n            //计算位置 大小 颜色 自旋\n            particle.Pos = r.NextVar(this.Pos, this.PosVar);\n            particle.StartSize = r.NextVar(this.StartSize, this.StartSizeVar, true);\n            particle.EndSize = r.NextVar(this.EndSize, this.EndSizeVar, true);\n            particle.StartColor = r.NextVar(this.StartColor, this.StartColorVar);\n            particle.EndColor = r.NextVar(this.EndColor, this.EndColorVar);\n            particle.StartSpin = r.NextVar(this.StartSpin, this.StartSpinVar);\n            particle.EndSpin = r.NextVar(this.EndSpin, this.EndSpinVar);\n\n            //计算速度方向\n            var speed = r.NextVar(this.Speed, this.SpeedVar);\n            var rotAngle = r.NextVar(this.Angle, this.AngleVar);\n            var rot = MathHelper.ToRadians(rotAngle);\n            var dir = new Vector2((float)Math.Cos(rot), -(float)Math.Sin(rot));\n            particle.Dir = dir * speed;\n            if (this.RotationIsDir)\n            {\n                var deltaSpin = particle.EndSpin - particle.StartSpin;\n                particle.StartSpin = rotAngle;\n                particle.EndSize = rotAngle + deltaSpin;\n            }\n\n            //计算圆周运动\n            particle.StartRadius = r.NextVar(this.StartRadius, this.StartRadiusVar, true);\n            particle.EndRadius = r.NextVar(this.EndRadius, this.EndRadiusVar, true);\n            particle.RotatePerSecond = r.NextVar(this.RotatePerSecond, this.RotatePerSecondVar, true);\n            particle.Angle = rotAngle;\n\n            //初始化加速度\n            particle.RadialAcc = r.NextVar(this.RadialAccel, this.RadialAccelVar);\n            particle.TangentialAcc = r.NextVar(this.TangentialAccel, this.TangentialAccelVar);\n\n            //生命周期\n            particle.Life = r.NextVar(this.Life, this.LifeVar, true);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/ParticleRandom.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    class ParticleRandom : IRandom\n    {\n        public ParticleRandom() : this(new Random())\n        {\n\n        }\n\n        public ParticleRandom(Random random)\n        {\n            this.Random = random;\n        }\n\n        public Random Random { get; private set; }\n\n        private double NextMinOneToOne()\n        {\n            return this.Random.NextDouble() * 2 - 1;\n        }\n\n        public float NextVar(float baseValue, float varRange, bool nonNegative = false)\n        {\n            if (varRange == 0)\n            {\n                return baseValue;\n            }\n\n            var val = baseValue + (float)this.NextMinOneToOne() * varRange;\n            if (nonNegative && val < 0)\n            {\n                val = 0;\n            }\n            return val;\n        }\n\n        public int NextVar(int baseValue, int varRange, bool nonNegative = false)\n        {\n            if (varRange == 0)\n            {\n                return baseValue;\n            }\n            var val = this.Random.Next(baseValue - varRange, baseValue + varRange + 1);\n            if (nonNegative && val < 0)\n            {\n                val = 0;\n            }\n            return val;\n        }\n\n        public Vector2 NextVar(Vector2 baseValue, Vector2 varRange)\n        {\n            var x = this.NextVar(baseValue.X, varRange.X, false);\n            var y = this.NextVar(baseValue.Y, varRange.Y, false);\n            return new Vector2(x, y);\n        }\n\n        public Color NextVar(Color baseValue, Color varRange)\n        {\n            var baseColorVec = baseValue.ToVector4();\n            var varRangeVec = varRange.ToVector4();\n            var r = this.NextVar(baseColorVec.X, varRangeVec.X, false);\n            var g = this.NextVar(baseColorVec.Y, varRangeVec.Y, false);\n            var b = this.NextVar(baseColorVec.Z, varRangeVec.Z, false);\n            var a = this.NextVar(baseColorVec.W, varRangeVec.W, false);\n            return new Color(r, g, b, a);\n        }\n\n        public int Next(int maxValue)\n        {\n            return this.Random.Next(maxValue);\n        }\n\n        public bool NextPercent(float percent)\n        {\n            return this.Random.NextDouble() < percent;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/ParticleSystem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Animation;\n\nnamespace WzComparerR2.MapRender\n{\n    public class ParticleSystem\n    {\n        public ParticleSystem(IRandom random)\n        {\n            this.Rand = random;\n            this.ParticlePool = new Stack<Particle>();\n            this.Groups = new List<ParticleGroup>();\n        }\n\n        public IRandom Rand { get; private set; }\n        public Stack<Particle> ParticlePool { get; private set; }\n        public ParticleEmitter Emitter { get; private set; }\n        public List<ParticleGroup> Groups { get; private set; }\n\n        public int ParticleCount { get; set; }\n        public float Duration { get; set; }\n        public Vector2 Gravity { get; set; }\n        public Frame Texture { get; set; }\n        public bool OpacityModifyRGB { get; set; }\n        public ParticleBlendFunc BlendFuncSrc { get; set; }\n        public ParticleBlendFunc BlendFuncDst { get; set; }\n\n        private AlphaPoint[] alphaPoints;\n\n        public void LoadDescription(ParticleDesc desc)\n        {\n            this.Texture = desc.Texture;\n            this.ParticleCount = desc.TotalParticle;\n            this.Duration = desc.Duration;\n            this.OpacityModifyRGB = desc.OpacityModifyRGB;\n            if (desc.Gravity != null)\n            {\n                this.Gravity = new Vector2(desc.Gravity.X, desc.Gravity.Y);\n            }\n            var alphaPointList = new List<AlphaPoint>();\n            if (desc.MiddlePoint0 > 0)\n            {\n                alphaPointList.Add(new AlphaPoint(desc.MiddlePoint0 / 100f, desc.MiddlePointAlpha0));\n            }\n            if (desc.MiddlePoint1 > 0)\n            {\n                alphaPointList.Add(new AlphaPoint(desc.MiddlePoint1 / 100f, desc.MiddlePointAlpha1));\n            }\n            this.alphaPoints = alphaPointList.ToArray();\n            this.BlendFuncSrc = desc.BlendFuncSrc;\n            this.BlendFuncDst = desc.BlendFuncDst;\n            this.CreateEmitter(desc);\n        }\n\n        private void CreateEmitter(ParticleDesc desc)\n        {\n            var emitter = new ParticleEmitter(this);\n            emitter.Angle = desc.Angle;\n            emitter.AngleVar = desc.AngleVar;\n            emitter.StartColor = desc.StartColor;\n            emitter.StartColorVar = desc.StartColorVar;\n            emitter.EndColor = desc.EndColor;\n            emitter.EndColorVar = desc.EndColorVar;\n            emitter.StartSize = desc.StartSize;\n            emitter.StartSizeVar = desc.StartSizeVar;\n            emitter.EndSize = desc.EndSize;\n            emitter.EndSizeVar = desc.EndSizeVar;\n            emitter.Pos = new Vector2(desc.PosX, desc.PosY);\n            emitter.PosVar = new Vector2(desc.PosVarX, desc.PosVarY);\n            emitter.StartSpin = desc.StartSpin;\n            emitter.StartSpinVar = desc.StartSpinVar;\n            emitter.EndSpin = desc.EndSpin;\n            emitter.EndSpinVar = desc.EndSpinVar;\n            if (desc.Gravity != null)\n            {\n                emitter.Speed = desc.Gravity.Speed;\n                emitter.SpeedVar = desc.Gravity.SpeedVar;\n                emitter.RadialAccel = desc.Gravity.RadialAccel;\n                emitter.RadialAccelVar = desc.Gravity.RadialAccelVar;\n                emitter.TangentialAccel = desc.Gravity.TangentialAccel;\n                emitter.TangentialAccelVar = desc.Gravity.TangentialAccelVar;\n                emitter.RotationIsDir = desc.Gravity.RotationIsDir;\n            }\n            if (desc.Radius != null)\n            {\n\n            }\n            emitter.Life = desc.Life;\n            emitter.LifeVar = desc.LifeVar;\n\n            //计算发射速率\n            if (desc.Duration > 0)\n            {\n                emitter.EmissionRate = desc.TotalParticle / Math.Min(desc.Duration, 1);\n            }\n            else\n            {\n                float lifeMin = Math.Max(0, desc.Life - desc.LifeVar);\n                float lifeMax = Math.Max(0, desc.Life + desc.LifeVar);\n                var life = (lifeMin + lifeMax) / 2;\n                if (life <= 0)\n                {\n                    life = 1;\n                }\n                emitter.EmissionRate = desc.TotalParticle / life;\n            }\n\n            this.Emitter = emitter;\n        }\n\n        public ParticleGroup CreateGroup(string name = null)\n        {\n            var pGroup = new ParticleGroup(this);\n            pGroup.Name = name;\n            pGroup.Particles.Capacity = this.ParticleCount;\n            return pGroup;\n        }\n\n        public void Update(TimeSpan elapsed)\n        {\n            foreach (var pGroup in this.Groups)\n            {\n                this.UpdateGroup(pGroup, elapsed);\n            }\n        }\n\n        public void UpdateGroup(ParticleGroup pGroup, TimeSpan elapsed)\n        {\n            var time = (float)elapsed.TotalSeconds;\n\n            if (pGroup.IsActive)\n            {\n                if (pGroup.Particles.Count < this.ParticleCount) //生成粒子\n                {\n                    float emitCount = this.Emitter.EmissionRate * time;\n                    var count = (int)emitCount + (this.Rand.NextPercent(emitCount % 1) ? 1 : 0);\n                    count = Math.Min(count, this.ParticleCount - pGroup.Particles.Count);\n                    for (int i = 0; i < count; i++)\n                    {\n                        var p = this.Emitter.Emit();\n                        if (p.Life <= 0) //fix bug\n                        {\n                            continue;\n                        }\n                        pGroup.Particles.Add(p);\n                    }\n                }\n\n                //计算时间\n                if (this.Duration > 0)\n                {\n                    pGroup.Time += time;\n                    if (pGroup.Time > this.Duration)\n                    {\n                        pGroup.IsActive = false;\n                    }\n                }\n            }\n\n            //更新所有粒子\n            if (pGroup.Particles.Count > 0)\n            {\n                pGroup.Particles.ForEach(p =>\n                {\n                    p.Time += time;\n                    p.NormalizedTime = p.Life <= 0 ? 0 : (p.Time / p.Life);\n                    if (p.NormalizedTime >= 1)\n                    {\n                        return;\n                    }\n\n                    //更新粒子\n                    var accDir = p.Pos;\n                    if (accDir != Vector2.Zero) //计算方向\n                    {\n                        accDir.Normalize();\n                    }\n                    var radial = accDir * p.RadialAcc; //法线加速度矢量\n                    //var tangent = new Vector2(-accDir.Y, accDir.X) * p.TangentialAcc; //切线加速度矢量\n                    var tangent = Vector2.Zero; //tangent not works in the game?\n                    var acc = this.Gravity + radial + tangent; //总加速度矢量\n                    p.Dir += acc * time; //计算加速度\n                    p.Pos += p.Dir * time; //计算位置\n\n                    //计算旋转\n                    p.Angle += p.RotatePerSecond * time;\n                    var rad = MathHelper.Lerp(p.StartRadius, p.EndRadius, p.NormalizedTime);\n                    if (rad > 0)\n                    {\n                        var radian = MathHelper.ToRadians(p.Angle);\n                        accDir = new Vector2((float)Math.Cos(radian), (float)Math.Sin(radian));\n                        p.Pos += accDir * rad * time;\n                    }\n                });\n\n                //回收粒子\n                pGroup.Particles.RemoveAll(p =>\n                {\n                    if (p.NormalizedTime >= 1)\n                    {\n                        this.CollectParticle(p);\n                        return true;\n                    }\n                    else\n                    {\n                        return false;\n                    }\n                });\n            }\n        }\n\n        private void CollectParticle(Particle particle)\n        {\n            this.ParticlePool.Push(particle);\n        }\n\n        public void Draw(SpriteBatch sprite, Vector2 position)\n        {\n            foreach (var pGroup in this.Groups)\n            {\n                this.DrawGroup(sprite, pGroup, position);\n            }\n        }\n\n        public void DrawGroup(SpriteBatch sprite, ParticleGroup pGroup, Vector2 position)\n        {\n            if (this.Texture?.Texture == null)\n            {\n                return;\n            }\n\n            var textureSize = this.Texture.AtlasRect?.Size.ToVector2() ?? new Vector2(this.Texture.Texture.Width, this.Texture.Texture.Height);\n            var rad = textureSize.Length() / (float)Math.Sqrt(2);\n            var origin = this.Texture.Origin.ToVector2();\n            position += pGroup.Position;\n\n            foreach (var p in pGroup.Particles)\n            {\n                var color = Lerp(p.StartColor, p.EndColor, p.NormalizedTime);\n                var size = MathHelper.Lerp(p.StartSize, p.EndSize, p.NormalizedTime);\n                var scale = size / rad;\n                var rot = MathHelper.ToRadians(MathHelper.Lerp(p.StartSpin, p.EndSpin, p.NormalizedTime));\n                //重新计算alpha\n                if (this.alphaPoints.Length > 0)\n                {\n                    color.A = (byte)CalcAlphaFromPoints(p);\n                }\n\n                if (this.OpacityModifyRGB)\n                {\n                    if (color.R == 0 && color.G == 0 && color.B == 0)\n                    {\n                        color.R = color.G = color.B = color.A;\n                    }\n                    else\n                    {\n                        //float alpha = color.A / 255.0f; ;\n                        //color.R = (byte)(color.R * alpha);\n                        //color.G = (byte)(color.G * alpha);\n                        //color.B = (byte)(color.B * alpha);\n                    }\n                }\n\n                var pos = p.Pos + position;\n                sprite.Draw(this.Texture.Texture, pos, this.Texture.AtlasRect, color, rot, origin, scale, SpriteEffects.None, 0);\n            }\n        }\n\n        private int CalcAlphaFromPoints(Particle p)\n        {\n            int? alpha = null;\n            var time = p.NormalizedTime;\n            if (time < this.alphaPoints[0].Time)\n            {\n                alpha = Lerp(p.StartColor.A, this.alphaPoints[0].Alpha,\n                    0, this.alphaPoints[0].Time,\n                    time);\n            }\n            for (int i = 1; alpha == null && i < this.alphaPoints.Length; i++)\n            {\n                if (time < this.alphaPoints[i].Time)\n                {\n                    alpha = Lerp(this.alphaPoints[i - 1].Alpha, this.alphaPoints[i].Alpha,\n                        this.alphaPoints[i - 1].Time, this.alphaPoints[i].Time,\n                        time);\n                    break;\n                }\n            }\n            if (alpha == null)\n            {\n                alpha = Lerp(this.alphaPoints[this.alphaPoints.Length - 1].Alpha, p.EndColor.A,\n                  this.alphaPoints[this.alphaPoints.Length - 1].Time, 1,\n                  time);\n            }\n            return alpha.Value;\n        }\n\n        private Color Lerp(Color color1, Color color2, float amount)\n        {\n            return new Color(\n                (byte)MathHelper.Lerp(color1.R, color2.R, amount),\n                (byte)MathHelper.Lerp(color1.G, color2.G, amount),\n                (byte)MathHelper.Lerp(color1.B, color2.B, amount),\n                (byte)MathHelper.Lerp(color1.A, color2.A, amount)\n                );\n        }\n\n        private int Lerp(int int1, int int2, float from, float to, float value)\n        {\n            if (from == to)\n            {\n                return int2;\n            }\n\n            var amount = (value - from) / (to - from);\n            return (int)MathHelper.Lerp(int1, int2, amount);\n        }\n\n        private struct AlphaPoint\n        {\n            public AlphaPoint(float time, int alpha)\n            {\n                this.Time = time;\n                this.Alpha = alpha;\n            }\n\n            public float Time;\n            public int Alpha;\n        }\n    }\n\n    public class ParticleGroup\n    {\n        public ParticleGroup(ParticleSystem owner)\n        {\n            this.Owner = owner;\n            this.Particles = new List<Particle>();\n        }\n\n        public ParticleSystem Owner { get; private set; }\n        public string Name { get; set; }\n        public List<Particle> Particles { get; private set; }\n        public Vector2 Position { get; set; }\n        public float Time { get; set; }\n        public bool IsActive { get; set; }\n\n        public void Active()\n        {\n            this.IsActive = true;\n            this.Time = 0;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/PatchVisibility.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.MapRender.Patches;\n\nnamespace WzComparerR2.MapRender\n{\n    public class PatchVisibility\n    {\n        public PatchVisibility()\n        {\n            this.dictVisible = new Dictionary<RenderObjectType, bool>();\n            this.tagsVisible = new SortedDictionary<string, bool>();\n            this.questVisible = new Dictionary<int, int>();\n            this.questexVisible = new Dictionary<Tuple<int, string>, int>();\n            foreach (RenderObjectType type in Enum.GetValues(typeof(RenderObjectType)))\n            {\n                this.dictVisible[type] = true;\n            }\n            this.PortalInEditMode = false;\n            this.DefaultTagVisible = true;\n            this.IlluminantClusterVisible = true;\n        }\n\n        public bool BackVisible\n        {\n            get { return IsVisible(RenderObjectType.Back); }\n            set { this.SetVisible(RenderObjectType.Back, value); }\n        }\n\n        public bool ReactorVisible\n        {\n            get { return IsVisible(RenderObjectType.Reactor); }\n            set { this.SetVisible(RenderObjectType.Reactor, value); }\n        }\n\n        public bool ObjVisible\n        {\n            get { return IsVisible(RenderObjectType.Obj); }\n            set { this.SetVisible(RenderObjectType.Obj, value); }\n        }\n\n        public bool TileVisible\n        {\n            get { return IsVisible(RenderObjectType.Tile); }\n            set { this.SetVisible(RenderObjectType.Tile, value); }\n        }\n\n        public bool NpcVisible\n        {\n            get { return IsVisible(RenderObjectType.Npc); }\n            set { this.SetVisible(RenderObjectType.Npc, value); }\n        }\n\n        public bool MobVisible\n        {\n            get { return IsVisible(RenderObjectType.Mob); }\n            set { this.SetVisible(RenderObjectType.Mob, value); }\n        }\n\n        public bool FootHoldVisible\n        {\n            get { return IsVisible(RenderObjectType.Foothold); }\n            set { this.SetVisible(RenderObjectType.Foothold, value); }\n        }\n\n        public bool LadderRopeVisible\n        {\n            get { return IsVisible(RenderObjectType.LadderRope); }\n            set { this.SetVisible(RenderObjectType.LadderRope, value); }\n        }\n\n        public bool SkyWhaleVisible { get; set; }\n\n        public bool IlluminantClusterPathVisible { get; set; }\n\n        public bool PortalVisible\n        {\n            get { return IsVisible(RenderObjectType.Portal); }\n            set { this.SetVisible(RenderObjectType.Portal, value); }\n        }\n\n        public bool PortalInEditMode { get; set; }\n\n        public bool IlluminantClusterVisible { get; set; }\n\n        public bool FrontVisible\n        {\n            get { return IsVisible(RenderObjectType.Front); }\n            set { this.SetVisible(RenderObjectType.Front, value); }\n        }\n\n        public bool NpcNameVisible\n        {\n            get { return IsVisible(RenderObjectType.NpcName); }\n            set { this.SetVisible(RenderObjectType.NpcName, value); }\n        }\n\n        public bool MobNameVisible\n        {\n            get { return IsVisible(RenderObjectType.MobName); }\n            set { this.SetVisible(RenderObjectType.MobName, value); }\n        }\n\n        public bool EffectVisible\n        {\n            get { return IsVisible(RenderObjectType.Effect); }\n            set { this.SetVisible(RenderObjectType.Effect, value); }\n        }\n\n        public IReadOnlyDictionary<string, bool> TagsVisible\n        {\n            get { return this.tagsVisible; }\n        }\n\n        public bool DefaultTagVisible { get; set; }\n\n        private Dictionary<RenderObjectType, bool> dictVisible;\n        private SortedDictionary<string, bool> tagsVisible;\n        private Dictionary<int, int> questVisible;\n        private Dictionary<Tuple<int, string>, int> questexVisible;\n\n        public bool IsVisible(RenderObjectType type)\n        {\n            bool visible;\n            dictVisible.TryGetValue(type, out visible);\n            return visible;\n        }\n\n        public bool IsTagVisible(string tag)\n        {\n            return this.tagsVisible.TryGetValue(tag, out var isVisible) ? isVisible : this.DefaultTagVisible;\n        }\n\n        public void SetTagVisible(string tag, bool isVisible)\n        {\n            this.tagsVisible[tag] = isVisible;\n        }\n\n        public void ResetTagVisible()\n        {\n            this.tagsVisible.Clear();\n        }\n\n        public void ResetTagVisible(string[] tags)\n        {\n            foreach (var tag in tags)\n            {\n                this.tagsVisible.Remove(tag);\n            }\n        }\n\n        private void SetVisible(RenderObjectType type, bool visible)\n        {\n            this.dictVisible[type] = visible;\n        }\n\n        public bool IsQuestVisible(int questID, int questState)\n        {\n            int visible;\n            if (!this.questVisible.TryGetValue(questID, out visible))\n            {\n                return true;\n            }\n            return visible == -1 || visible == questState;\n        }\n\n        public bool IsQuestVisibleExact(int questID, int questState)\n        {\n            int visible;\n            if (!this.questVisible.TryGetValue(questID, out visible))\n            {\n                return false;\n            }\n            return visible == questState;\n        }\n\n        public void SetQuestVisible(int questID, int questState)\n        {\n            this.questVisible[questID] = questState;\n        }\n\n        public bool IsQuestVisible(int questID, string qkey, int questState)\n        {\n            int visible;\n            if (!this.questexVisible.TryGetValue(new Tuple<int, string>(questID, qkey), out visible))\n            {\n                return true;\n            }\n            return visible == -1 || visible == questState;\n        }\n\n        public bool IsQuestVisibleExact(int questID, string qkey, int questState)\n        {\n            int visible;\n            if (!this.questexVisible.TryGetValue(new Tuple<int, string>(questID, qkey), out visible))\n            {\n                return false;\n            }\n            return visible == questState;\n        }\n\n        public void SetQuestVisible(int questID, string qkey, int questState)\n        {\n            this.questexVisible[new Tuple<int, string>(questID, qkey)] = questState;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/BackPatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class BackPatch : RenderPatch\n    {\n        public BackPatch()\n        {\n        }\n\n        int cx;\n        int cy;\n        int rx;\n        int ry;\n        int screenMode;\n        TileMode tileMode;\n\n        // RenderArgs\n        Vector2 origin2;\n        Rectangle tileRect;\n\n        public int Cx\n        {\n            get { return cx; }\n            set { cx = value; }\n        }\n\n        public int Cy\n        {\n            get { return cy; }\n            set { cy = value; }\n        }\n\n        public int Rx\n        {\n            get { return rx; }\n            set { rx = value; }\n        }\n\n        public int Ry\n        {\n            get { return ry; }\n            set { ry = value; }\n        }\n\n        public int ScreenMode\n        {\n            get { return screenMode; }\n            set { screenMode = value; }\n        }\n\n        public TileMode TileMode\n        {\n            get { return tileMode; }\n            set { tileMode = value; }\n        }\n\n        public override void Update(GameTime gameTime, RenderEnv env)\n        {\n            base.Update(gameTime, env);\n\n\n            if (this.RenderArgs.CurrentIndex < 0)\n                return;\n            RenderFrame frame = this.Frames[this.RenderArgs.CurrentIndex];\n\n            //计算背景的二次偏移\n            int cx = (this.cx == 0 ? frame.Texture.Width : this.cx),\n                cy = (this.cy == 0 ? frame.Texture.Height : this.cy);\n            Vector2 origin2 = new Vector2();\n\n            double ms = gameTime.TotalGameTime.TotalSeconds;\n\n            if ((this.TileMode & TileMode.ScrollHorizontial) != 0)\n            {\n                origin2.X = (float)((this.rx * 5 * ms) % cx);// +this.Camera.Center.X * (100 - Math.Abs(this.rx)) / 100;\n            }\n            else\n            {\n                origin2.X = env.Camera.Center.X * (100 + this.rx) / 100;\n            }\n\n            if ((this.TileMode & TileMode.ScrollVertical) != 0)\n            {\n                origin2.Y = (float)((this.ry * 5 * ms) % cy);// +this.Camera.Center.Y * (100 - Math.Abs(this.ry)) / 100;\n            }\n            else\n            {\n                //origin2.Y = env.Camera.Center.Y * (100 + this.ry) / 100;\n                origin2.Y = (env.Camera.Center.Y) * (100 + this.ry) / 100; //屏幕大小适配补丁\n            }\n\n            origin2.Y += (env.Camera.Height - 600);\n            this.origin2 = origin2;\n            \n\n            //计算平铺绘制参数\n            if (this.TileMode != TileMode.None)\n            {\n                this.RenderArgs.Culled = false;\n                Rectangle originRect = env.Camera.MeasureDrawingRect(frame.Texture.Width, frame.Texture.Height, this.Position + origin2, frame.Origin, this.Flip);\n                int l, t, r, b;\n                if ((this.TileMode & TileMode.Horizontal) != 0)\n                {\n                    l = (int)Math.Floor((double)(env.Camera.ClipRect.Left - originRect.X) / cx);\n                    r = (int)Math.Ceiling((double)(env.Camera.ClipRect.Right - originRect.X) / cx);\n                }\n                else\n                {\n                    l = 0;\n                    r = 1;\n                }\n                if ((this.TileMode & TileMode.Vertical) != 0)\n                {\n                    t = (int)Math.Floor((double)(env.Camera.ClipRect.Top - originRect.Y) / cy);\n                    b = (int)Math.Ceiling((double)(env.Camera.ClipRect.Bottom - originRect.Y) / cy);\n                }\n                else\n                {\n                    t = 0;\n                    b = 1;\n                }\n                this.tileRect = new Rectangle(l, t, r - l, b - t);\n            }\n            else\n            {\n                this.RenderArgs.Culled = !env.Camera.CheckSpriteVisible(frame, this.Position + this.origin2, this.Flip);\n                this.tileRect = new Rectangle(0, 0, 1, 1);\n            }\n\n            if (this.screenMode != 0)\n            {\n                this.RenderArgs.Culled |= (this.screenMode != env.Camera.DisplayMode + 1);\n            }\n\n            this.RenderArgs.DisplayRectangle = Rectangle.Empty;\n        }\n\n        public override void Draw(GameTime gameTime, RenderEnv env)\n        {\n            if (this.RenderArgs.CurrentIndex < 0)\n            {\n                return;\n            }\n            RenderFrame frame = this.Frames[this.RenderArgs.CurrentIndex];\n            float curPer = this.RenderArgs.CurrentPercent;\n            float alpha = (frame.A0 * (1 - curPer) + frame.A1 * curPer) / 255.0f * this.Alpha / 255.0f;\n\n            Vector2 pos = this.Position + this.origin2 - env.Camera.Origin;\n            pos = new Vector2((float)Math.Floor(pos.X), (float)Math.Floor(pos.Y));\n\n            int cx = (this.cx == 0 ? frame.Texture.Width : this.cx),\n                cy = (this.cy == 0 ? frame.Texture.Height : this.cy);\n            for (int j = this.tileRect.Top; j < this.tileRect.Bottom; j++)\n            {\n                for (int i = this.tileRect.Left; i < this.tileRect.Right; i++)\n                {\n                    Vector2 pos2 = pos + new Vector2(i * cx, j * cy);\n\n                    env.Sprite.Draw(frame.Texture,\n                       pos2,\n                       null,\n                       new Color(Color.White, alpha),\n                       0f,\n                       this.Flip? new Vector2(frame.Texture.Width - frame.Origin.X, frame.Origin.Y) : frame.Origin,\n                       1f,\n                       this.Flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None,\n                       0f);\n                }\n            }\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/FootholdPatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class FootholdPatch : RenderPatch\n    {\n        public FootholdPatch()\n        {\n        }\n\n        private int x1;\n        private int y1;\n        private int x2;\n        private int y2;\n        private int prev;\n        private int next;\n        private int piece;\n\n        public int X1\n        {\n            get { return x1; }\n            set { x1 = value; }\n        }\n\n        public int Y1\n        {\n            get { return y1; }\n            set { y1 = value; }\n        }\n\n        public int X2\n        {\n            get { return x2; }\n            set { x2 = value; }\n        }\n\n        public int Y2\n        {\n            get { return y2; }\n            set { y2 = value; }\n        }\n\n        public int Prev\n        {\n            get { return prev; }\n            set { prev = value; }\n        }\n\n        public int Next\n        {\n            get { return next; }\n            set { next = value; }\n        }\n\n        public int Piece\n        {\n            get { return piece; }\n            set { piece = value; }\n        }\n\n        public override void Update(GameTime gameTime, RenderEnv env)\n        {\n        }\n\n        public override void Draw(GameTime gameTime, RenderEnv env)\n        {\n            Vector2 origin = env.Camera.Origin;\n            Point p1 = new Point(x1 - (int)origin.X, y1 - (int)origin.Y);\n            Point p2 = new Point(x2 - (int)origin.X, y2 - (int)origin.Y);\n            Color color = MathHelper2.Lerp(colorTable, (float)gameTime.TotalGameTime.TotalMilliseconds % 10000 / 2000);\n            if (x1 != x2 && y1 != y2)\n            {\n            }\n            env.Sprite.DrawLine(p1, p2, 2, color);\n        }\n\n        private static Color[] colorTable = new Color[] { \n            Color.Red,\n            Color.Yellow,\n            Color.Blue,\n            Color.Purple,\n            Color.Red};\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/LadderRopePatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class LadderRopePatch : RenderPatch\n    {\n        public LadderRopePatch()\n        {\n\n        }\n\n        public int X { get; set; }\n        public int Y1 { get; set; }\n        public int Y2 { get; set; }\n        public int L { get; set; }\n        public int Uf { get; set; }\n        public int Page { get; set; }\n\n        public override void Update(GameTime gameTime, RenderEnv env)\n        {\n\n        }\n\n        public override void Draw(GameTime gameTime, RenderEnv env)\n        {\n            Vector2 origin = env.Camera.Origin;\n            Point p1 = new Point(this.X - (int)origin.X, this.Y1 - (int)origin.Y);\n            Point p2 = new Point(this.X - (int)origin.X, this.Y2 - (int)origin.Y);\n            Color color = MathHelper2.Lerp(colorTable, (float)gameTime.TotalGameTime.TotalMilliseconds % 10000 / 2000);\n\n            env.Sprite.DrawLine(p1, p2, 5, color);\n        }\n\n        private static Color[] colorTable = new Color[] { \n            Color.Red,\n            Color.Yellow,\n            Color.Blue,\n            Color.Purple,\n            Color.Red};\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/LifePatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class LifePatch : RenderPatch\n    {\n        public LifePatch()\n        {\n            actions = new Dictionary<string, RenderAnimate>();\n            lifeInfo = new LifeInfo();\n        }\n\n        private Dictionary<string, RenderAnimate> actions;\n        private int lifeID;\n        private int foothold;\n        private LifeInfo lifeInfo;\n\n        public int LifeID\n        {\n            get { return lifeID; }\n            set { lifeID = value; }\n        }\n\n        public int Foothold\n        {\n            get { return foothold; }\n            set { foothold = value; }\n        }\n\n        public LifeInfo LifeInfo\n        {\n            get { return lifeInfo; }\n        }\n\n        public Dictionary<string, RenderAnimate> Actions\n        {\n            get { return actions; }\n        }\n\n        public void SwitchToDefaultAction()\n        {\n            RenderAnimate action;\n            if (!this.actions.TryGetValue(\"stand\", out action)\n                && !this.actions.TryGetValue(\"fly\", out action))\n            {\n                foreach (var kv in this.actions)\n                {\n                    action = kv.Value;\n                    break;\n                }\n            }\n            if (this.Frames != action)\n            {\n                this.Frames = action;\n            }\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                foreach (var kv in this.actions)\n                {\n                    foreach (RenderFrame frame in kv.Value)\n                    {\n                        frame.Texture.Dispose();\n                    }\n                }\n            }\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/ObjTilePatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class ObjTilePatch : RenderPatch\n    {\n        public override void Update(GameTime gameTime, RenderEnv env)\n        {\n            base.Update(gameTime, env);\n            this.RenderArgs.DisplayRectangle = Rectangle.Empty;\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/PortalPatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class PortalPatch : RenderPatch\n    {\n        public PortalPatch()\n        {\n            this.EditMode = false;\n        }\n\n        RenderAnimate aniStart;\n        RenderAnimate aniContinue;\n        RenderAnimate aniExit;\n        RenderAnimate aniEditor;\n        string pn;\n        int pt;\n        int tm;\n        string tn;\n        string script;\n\n        public RenderAnimate AniStart\n        {\n            get { return aniStart; }\n            set { aniStart = value; }\n        }\n\n        public RenderAnimate AniContinue\n        {\n            get { return aniContinue; }\n            set { aniContinue = value; }\n        }\n\n        public RenderAnimate AniExit\n        {\n            get { return aniExit; }\n            set { aniExit = value; }\n        }\n\n        public RenderAnimate AniEditor\n        {\n            get { return aniEditor; }\n            set { aniEditor = value; }\n        }\n\n        public string PortalName\n        {\n            get { return pn; }\n            set { pn = value; }\n        }\n\n        public int PortalType\n        {\n            get { return pt; }\n            set { pt = value; }\n        }\n\n        public int ToMap\n        {\n            get { return tm; }\n            set { tm = value; }\n        }\n\n        public string ToName\n        {\n            get { return tn; }\n            set { tn = value; }\n        }\n\n        public string Script\n        {\n            get { return script; }\n            set { script = value; }\n        }\n\n        public bool EditMode { get; set; }\n\n        public override void Update(GameTime gameTime, RenderEnv env)\n        {\n            if (this.EditMode)\n            {\n                this.Frames = this.aniEditor;\n            }\n            else\n            {\n                this.Frames = this.aniContinue;\n            }\n\n            base.Update(gameTime, env);\n\n            //添加鼠标感应范围\n\n            if (this.EditMode && this.RenderArgs.DisplayRectangle.IsEmpty)\n            {\n                Point p = new Point((int)this.Position.X, (int)this.Position.Y);\n                this.RenderArgs.DisplayRectangle = new Rectangle(p.X - 25, p.Y - 50, 50, 50);\n            }\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                RenderAnimate[] animeLst = { this.aniContinue, this.aniEditor, this.aniExit, this.aniStart };\n                foreach (RenderAnimate ani in animeLst)\n                {\n                    if (ani != null)\n                    {\n                        foreach (RenderFrame frame in ani)\n                        {\n                            frame.Texture.Dispose();\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/ReactorPatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class ReactorPatch : RenderPatch\n    {\n        public ReactorPatch()\n        {\n            this.stages = new List<RenderAnimate>();\n        }\n\n        int reactorID;\n        string reactorName;\n        int reactorTime;\n        List<RenderAnimate> stages;\n\n        public int ReactorID\n        {\n            get { return reactorID; }\n            set { reactorID = value; }\n        }\n        \n        public string ReactorName\n        {\n            get { return reactorName; }\n            set { reactorName = value; }\n        }\n        \n        public int ReactorTime\n        {\n            get { return reactorTime; }\n            set { reactorTime = value; }\n        }\n\n        public List<RenderAnimate> Stages\n        {\n            get { return stages; }\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                foreach (var stage in this.stages)\n                {\n                    foreach (RenderFrame frame in stage)\n                    {\n                        frame.Texture.Dispose();\n                    }\n                }\n            }\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/RenderObjectType.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public enum RenderObjectType\n    {\n        Back = 1,\n        Reactor,\n        Obj,\n        Tile,\n        Npc,\n        Mob,\n        Foothold,\n        LadderRope,\n        Portal,\n        Front,\n        NpcName,\n        MobName,\n        Effect,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/RenderPatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class RenderPatch : IDisposable\n    {\n        public RenderPatch()\n        {\n            zIndex = new int[8];\n            flip = false;\n            renderArgs = new RenderArgs();\n            alpha = 255;\n        }\n\n        string name;\n        Vector2 position;\n        bool flip;\n        int[] zIndex;\n        RenderAnimate frames;\n        RenderObjectType objectType;\n        TimeSpan playStateChangedTime;\n        RenderArgs renderArgs;\n        int alpha;\n\n        public string Name\n        {\n            get { return name; }\n            set { name = value; }\n        }\n\n        public Vector2 Position\n        {\n            get { return position; }\n            set { position = value; }\n        }\n\n        public bool Flip\n        {\n            get { return flip; }\n            set { flip = value; }\n        }\n\n        public int[] ZIndex\n        {\n            get { return zIndex; }\n        }\n\n        public RenderAnimate Frames\n        {\n            get { return frames; }\n            set { frames = value; }\n        }\n\n        public virtual RenderObjectType ObjectType\n        {\n            get { return objectType; }\n            set { objectType = value; }\n        }\n\n        public TimeSpan PlayStateChangedTime\n        {\n            get { return playStateChangedTime; }\n            set { playStateChangedTime = value; }\n        }\n\n        public RenderArgs RenderArgs\n        {\n            get { return renderArgs; }\n        }\n\n        public int Alpha\n        {\n            get { return alpha; }\n            set { alpha = value; }\n        }\n\n        public int UpdateCurrentFrameIndex(TimeSpan totalTime)\n        {\n            float framePlayed;\n            return UpdateCurrentFrameIndex(totalTime, out framePlayed);\n        }\n\n        public virtual int UpdateCurrentFrameIndex(TimeSpan totalTime, out float framePlayed)\n        {\n            framePlayed = 0f;\n            if (this.frames == null || this.frames.Length == 0)\n            {\n                return -1;\n            }\n            int[] timeLine = this.frames.TimeLine;\n            if (timeLine[timeLine.Length - 1] <= 0)\n            {\n                return 0;\n            }\n\n            double ms = (totalTime.TotalMilliseconds - playStateChangedTime.TotalMilliseconds);\n            int startTimeLine = 1;\n            if (this.frames.Repeat <= 0 || this.frames.Repeat >= this.frames.Length) //使用全帧绘制\n            {\n                ms %= timeLine[timeLine.Length - 1];\n            }\n            else\n            {\n                if (ms < timeLine[timeLine.Length - 1]) //首次绘制\n                {\n                    ms %= timeLine[timeLine.Length - 1];\n                }\n                else //循环段\n                {\n                    startTimeLine = frames.Repeat + 1;\n                    ms -= timeLine[timeLine.Length - 1];\n                    int newDelay = timeLine[timeLine.Length - 1] - timeLine[frames.Repeat];\n                    if (newDelay <= 0)\n                        return frames.Repeat;\n                    ms = ms % newDelay + timeLine[frames.Repeat];\n                }\n            }\n\n            for (int i = startTimeLine; i < timeLine.Length; i++)\n            {\n                if (ms < timeLine[i])\n                {\n                    framePlayed = (float)((ms - timeLine[i - 1]) / (timeLine[i] - timeLine[i - 1]));\n                    return i - 1;\n                }\n            }\n            return frames.Repeat;\n        }\n\n        public virtual void Update(GameTime gameTime, RenderEnv env)\n        {\n            float played;\n            int index = this.UpdateCurrentFrameIndex(gameTime.TotalGameTime, out played);\n            if (index > -1)\n            {\n                //获取当前帧绘制参数\n                RenderFrame frame = this.Frames[index];\n                this.RenderArgs.CurrentIndex = index;\n                this.RenderArgs.CurrentPercent = played;\n                Rectangle rect;\n                this.RenderArgs.Culled = !env.Camera.CheckSpriteVisible(frame, this.position, this.flip, out rect);\n                this.renderArgs.DisplayRectangle = rect;\n            }\n            else\n            {\n                this.RenderArgs.CurrentIndex = -1;\n                this.RenderArgs.CurrentPercent = 0f;\n                this.RenderArgs.Culled = true;\n            }\n        }\n\n        public virtual void Draw(GameTime gameTime, RenderEnv env)\n        {\n            if (this.RenderArgs.CurrentIndex < 0)\n            {\n                return;\n            }\n\n            RenderFrame frame = this.Frames[this.RenderArgs.CurrentIndex];\n            Vector2 cameraOrigin = env.Camera.Origin;\n\n            float curPer = this.RenderArgs.CurrentPercent;\n            float alpha = (frame.A0 * (1 - curPer) + frame.A1 * curPer) / 255.0f * this.alpha / 255.0f;\n\n            env.Sprite.Draw(frame.Texture,\n               this.Position - cameraOrigin,\n               null,\n               new Color(Color.White, alpha),\n               0f,\n               this.flip ? new Vector2(frame.Texture.Width - frame.Origin.X, frame.Origin.Y) : frame.Origin,\n               1f,\n               this.flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None,\n               0f);\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (this.Frames != null && this.Frames.Length > 0)\n                {\n                    foreach (RenderFrame frame in this.Frames)\n                    {\n                        if (!frame.Texture.IsDisposed)\n                        {\n                            frame.Texture.Dispose();\n                        }\n                    }\n                }\n            }\n        }\n\n        public override string ToString()\n        {\n            return base.ToString() + \" \" + this.name;\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches/TooltipPatch.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender.Patches\n{\n    public class TooltipPatch : RenderPatch\n    {\n        public TooltipPatch()\n        {\n        }\n\n        public int X1 { get; set; }\n        public int X2 { get; set; }\n        public int Y1 { get; set; }\n        public int Y2 { get; set; }\n\n        public int CharX1 { get; set; }\n        public int CharX2 { get; set; }\n        public int CharY1 { get; set; }\n        public int CharY2 { get; set; }\n\n        public string Title { get; set; }\n        public string Desc { get; set; }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/BackItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class BackItem : SceneItem\n    {\n        public string BS { get; set; }\n        public int Ani { get; set; }\n        public string No { get; set; }\n        public string SpineAni { get; set; }\n        public int? SpineNo { get; set; }\n        public int X { get; set; }\n        public int Y { get; set; }\n        public int Cx { get; set; }\n        public int Cy { get; set; }\n        public int Rx { get; set; }\n        public int Ry { get; set; }\n        public int Alpha { get; set; }\n        public TileMode TileMode { get; set; }\n        public int ScreenMode { get; set; }\n        public bool Flip { get; set; }\n        public bool IsFront { get; set; }\n        public List<QuestInfo> Quest { get; private set; } = new List<QuestInfo>();\n\n        public ItemView View { get; set; }\n\n        public static BackItem LoadFromNode(Wz_Node node)\n        {\n            var item = new BackItem()\n            {\n                BS = node.Nodes[\"bS\"].GetValueEx<string>(null),\n                Ani = node.Nodes[\"ani\"].GetValueEx<int>(0),\n                No = node.Nodes[\"no\"].GetValueEx<string>(null),\n                SpineAni = node.Nodes[\"spineAni\"].GetValueEx<string>(null),\n                SpineNo = node.Nodes[\"spineNo\"].GetValueEx<int?>(null),\n\n                X = node.Nodes[\"x\"].GetValueEx(0),\n                Y = node.Nodes[\"y\"].GetValueEx(0),\n                Cx = node.Nodes[\"cx\"].GetValueEx(0),\n                Cy = node.Nodes[\"cy\"].GetValueEx(0),\n                Rx = node.Nodes[\"rx\"].GetValueEx(0),\n                Ry = node.Nodes[\"ry\"].GetValueEx(0),\n                Alpha = node.Nodes[\"a\"].GetValueEx(255),\n\n                TileMode = GetBackTileMode(node.Nodes[\"type\"].GetValueEx(0)),\n                ScreenMode = node.Nodes[\"screenMode\"].GetValueEx(0),\n                Flip = node.Nodes[\"f\"].GetValueEx(false),\n                IsFront = node.Nodes[\"front\"].GetValueEx(false),\n            };\n            string backTags = node.Nodes[\"backTags\"].GetValueEx<string>(null);\n            if (!string.IsNullOrWhiteSpace(backTags))\n            {\n                item.Tags = backTags.Split(',').Select(tag => tag.Trim()).ToArray();\n\n                if (int.TryParse(backTags, out int questID))\n                {\n                    item.Quest.Add(new QuestInfo(questID, 1));\n                }\n            }\n            return item;\n        }\n\n        private static TileMode GetBackTileMode(int type)\n        {\n            switch (type)\n            {\n                case 0: return TileMode.None;\n                case 1: return TileMode.Horizontal;\n                case 2: return TileMode.Vertical;\n                case 3: return TileMode.BothTile;\n                case 4: return TileMode.Horizontal | TileMode.ScrollHorizontal;\n                case 5: return TileMode.Vertical | TileMode.ScrollVertical;\n                case 6: return TileMode.BothTile | TileMode.ScrollHorizontal;\n                case 7: return TileMode.BothTile | TileMode.ScrollVertical;\n                default: return TileMode.None;\n            }\n        }\n\n        public class ItemView\n        {\n            /// <summary>\n            /// 时间关联，单位为毫秒。\n            /// </summary>\n            public int Time { get; set; }\n\n            /// <summary>\n            /// 动画资源。\n            /// </summary>\n            public object Animator { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/FootholdItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class FootholdItem : SceneItem\n    {\n        public int ID { get; set; }\n        public int X1 { get; set; }\n        public int Y1 { get; set; }\n        public int X2 { get; set; }\n        public int Y2 { get; set; }\n        public int Prev { get; set; }\n        public int Next { get; set; }\n        public int Piece { get; set; }\n\n        public static FootholdItem LoadFromNode(Wz_Node node)\n        {\n            var item = new FootholdItem()\n            {\n                X1 = node.Nodes[\"x1\"].GetValueEx(0),\n                Y1 = node.Nodes[\"y1\"].GetValueEx(0),\n                X2 = node.Nodes[\"x2\"].GetValueEx(0),\n                Y2 = node.Nodes[\"y2\"].GetValueEx(0),\n                Prev = node.Nodes[\"prev\"].GetValueEx(0),\n                Next = node.Nodes[\"next\"].GetValueEx(0),\n                Piece = node.Nodes[\"piece\"].GetValueEx(0),\n            };\n            return item;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/IlluminantClusterItem.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Animation;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class IlluminantClusterItem : SceneItem\n    {\n        public Point Start { get; set; }\n        public Point End { get; set; }\n        public int Size { get; set; }\n        public double Speed { get; set; }\n        public int Particle { get; set; }\n        public int StartPoint { get; set; }\n        public int EndPoint { get; set; }\n\n        public ItemView StartView { get; set; }\n        public ItemView EndView { get; set; }\n\n        public static IlluminantClusterItem LoadFromNode(Wz_Node node)\n        {\n            var item = new IlluminantClusterItem()\n            {\n                Start = node.Nodes[\"start\"].GetValueEx<Wz_Vector>(null)?.ToPoint() ?? Point.Zero,\n                End = node.Nodes[\"end\"].GetValueEx<Wz_Vector>(null)?.ToPoint() ?? Point.Zero,\n                Size = node.Nodes[\"size\"].GetValueEx<int>(0),\n                Speed = node.Nodes[\"speed\"].GetValueEx<double>(0),\n                Particle = node.Nodes[\"particle\"].GetValueEx<int>(0),\n                StartPoint = node.Nodes[\"startPoint\"].GetValueEx<int>(0),\n                EndPoint = node.Nodes[\"endPoint\"].GetValueEx<int>(0),\n            };\n            return item;\n        }\n\n        public class ItemView\n        {\n            public int Time { get; set; }\n\n            public object Animator { get; set; }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/ItemEvent.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class ItemEvent\n    {\n        public ItemEvent(string index, string collision, string animation, string slotName, string target, List<string> actionKeys)\n        {\n            Index = index;\n            Collision = collision;\n            Animation = animation;\n            SlotName = slotName;\n            Target = target;\n            ActionKeys = actionKeys;\n        }\n\n        public string Index { get; set; }\n        public string Collision { get; set; }\n        public string Animation { get; set; }\n        public string SlotName { get; set; }\n        public string Target { get; set; }\n        public List<string> ActionKeys { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/LadderRopeItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class LadderRopeItem : SceneItem\n    {\n        public int X { get; set; }\n        public int Y1 { get; set; }\n        public int Y2 { get; set; }\n        public int L { get; set; }\n        public int Uf { get; set; }\n        public int Page { get; set; }\n\n        public static LadderRopeItem LoadFromNode(Wz_Node node)\n        {\n            var item = new LadderRopeItem()\n            {\n                X = node.Nodes[\"x\"].GetValueEx(0),\n                Y1 = node.Nodes[\"y1\"].GetValueEx(0),\n                Y2 = node.Nodes[\"y2\"].GetValueEx(0),\n                L = node.Nodes[\"l\"].GetValueEx(0),\n                Uf = node.Nodes[\"uf\"].GetValueEx(0),\n                Page = node.Nodes[\"page\"].GetValueEx(0),\n            };\n            return item;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/LifeItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class LifeItem : SceneItem\n    {\n        public int ID { get; set; }\n        public LifeType Type { get; set; }\n        public int X { get; set; }\n        public int Y { get; set; }\n        public int MobTime { get; set; }\n        public bool Flip { get; set; }\n        public bool Hide { get; set; }\n        public int Fh { get; set; }\n        public int Cy { get; set; }\n        public int Rx0 { get; set; }\n        public int Rx1 { get; set; }\n        public List<QuestInfo> Quest { get; private set; } = new List<QuestInfo>();\n        public List<Tuple<long, long>> Date { get; set; }\n\n        public ItemView View { get; set; }\n\n        public LifeInfo LifeInfo { get; set; }\n        public bool HideName { get; set; }\n        public CustomFontFunc CustomFont { get; set; }\n\n        public static LifeItem LoadFromNode(Wz_Node node)\n        {\n            var item = new LifeItem()\n            {\n                ID = node.Nodes[\"id\"].GetValueEx(0),\n                Type = ParseLifeType(node.Nodes[\"type\"].GetValueEx<string>(null)),\n                X = node.Nodes[\"x\"].GetValueEx(0),\n                Y = node.Nodes[\"y\"].GetValueEx(0),\n                MobTime = node.Nodes[\"mobTime\"].GetValueEx(0),\n                Flip = node.Nodes[\"f\"].GetValueEx(false),\n                Hide = node.Nodes[\"hide\"].GetValueEx(false),\n                Fh = node.Nodes[\"fh\"].GetValueEx(0),\n                Cy = node.Nodes[\"cy\"].GetValueEx(0),\n                Rx0 = node.Nodes[\"rx0\"].GetValueEx(0),\n                Rx1 = node.Nodes[\"rx1\"].GetValueEx(0)\n            };\n\n            item.Date = new List<Tuple<long, long>>();\n            if (item.Type == LifeType.Npc)\n            {\n                string path = $@\"Npc\\{item.ID:D7}.img\";\n                var npcNode = PluginBase.PluginManager.FindWz(path);\n\n                int? npcLink = npcNode?.FindNodeByPath(@\"info\\link\").GetValueEx<int>();\n                if (npcLink != null)\n                {\n                    path = $@\"Npc\\{npcLink.Value:D7}.img\";\n                    npcNode = PluginBase.PluginManager.FindWz(path);\n                }\n\n                if (npcNode != null)\n                {\n                    // TODO: this is totally wrong, we should load this part in StateMachineAnimator\n                    foreach (Wz_Node conditionNode in npcNode.Nodes.Where(n => n.Text.StartsWith(\"condition\")))\n                    {\n                        foreach (Wz_Node questNode in conditionNode.Nodes)\n                        {\n                            if (int.TryParse(questNode.Text, out int questID) && questNode.Value != null)\n                            {\n                                item.Quest.Add(new QuestInfo(questID, Convert.ToInt32(questNode.Value)));\n                            }\n                        }\n                        if (conditionNode.Nodes[\"dateStart\"] != null || conditionNode.Nodes[\"dateEnd\"] != null)\n                        {\n                            item.Date.Add(Tuple.Create(conditionNode.Nodes[\"dateStart\"].GetValueEx<long>(0), conditionNode.Nodes[\"dateEnd\"].GetValueEx<long>(0)));\n                        }\n                    }\n                }\n            }\n            return item;\n        }\n\n        public static CustomFontFunc LoadCustomFontFunc(Wz_Node node)\n        {\n            var customFontFunc = new CustomFontFunc()\n            {\n                Font = node.Nodes[\"font\"].GetValueEx<string>(null),\n                FontSize = node.Nodes[\"fontSize\"]?.GetValue<int>(),\n            };\n            string fontColor = node.Nodes[\"fontColor\"].GetValueEx<string>(null);\n            if (fontColor != null && int.TryParse(fontColor, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int argbColor))\n            {\n                customFontFunc.FontColor = MonogameUtils.ToXnaColor(argbColor);\n            }\n            return customFontFunc;\n        }\n\n        private static LifeType ParseLifeType(string text)\n        {\n            switch (text)\n            {\n                case \"m\": return LifeType.Mob;\n                case \"n\": return LifeType.Npc;\n                default: return LifeType.Unknown;\n            }\n        }\n\n        public class ItemView\n        {\n            /// <summary>\n            /// 时间关联，单位为毫秒。\n            /// </summary>\n            public int Time { get; set; }\n\n            /// <summary>\n            /// 动画资源。\n            /// </summary>\n            public object Animator { get; set; }\n        }\n\n        public enum LifeType\n        {\n            Unknown = 0,\n            Mob = 1,\n            Npc = 2\n        }\n\n        public class CustomFontFunc\n        {\n            public string Font { get; set; }\n            public Color? FontColor { get; set; }\n            public int? FontSize { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/ObjItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class ObjItem : SceneItem\n    {\n        public string OS { get; set; }\n        public string L0 { get; set; }\n        public string L1 { get; set; }\n        public string L2 { get; set; }\n        public int X { get; set; }\n        public int Y { get; set; }\n        public int Z { get; set; }\n        public int MoveType { get; set; }\n        public int MoveW { get; set; }\n        public int MoveH { get; set; }\n        public int MoveP { get; set; }\n        public int MoveDelay { get; set; }\n        public bool Flip { get; set; }\n        public bool Light { get; set; }\n        public string SpineAni { get; set; }\n        public List<QuestInfo> Quest { get; private set; } = new List<QuestInfo>();\n        public List<QuestExInfo> Questex { get; private set; } = new List<QuestExInfo>();\n        public List<ItemEvent> Events { get; private set; } = new List<ItemEvent>();\n\n        public ItemView View { get; set; }\n\n        public static ObjItem LoadFromNode(Wz_Node node)\n        {\n            var item = new ObjItem()\n            {\n                OS = node.Nodes[\"oS\"].GetValueEx<string>(null),\n                L0 = node.Nodes[\"l0\"].GetValueEx<string>(null),\n                L1 = node.Nodes[\"l1\"].GetValueEx<string>(null),\n                L2 = node.Nodes[\"l2\"].GetValueEx<string>(null),\n\n                X = node.Nodes[\"x\"].GetValueEx(0),\n                Y = node.Nodes[\"y\"].GetValueEx(0),\n                Z = node.Nodes[\"z\"].GetValueEx(0),\n\n                MoveType = 0,\n                MoveW = 0,\n                MoveH = 0,\n                MoveP = 5000,\n                MoveDelay = 0,\n\n                Flip = node.Nodes[\"f\"].GetValueEx(false),\n                Light = node.Nodes[\"light\"].GetValueEx<int>(0) != 0,\n                SpineAni = node.Nodes[\"spineAni\"].GetValueEx<string>(null),\n            };\n\n            string objTags = node.Nodes[\"tags\"].GetValueEx<string>(null);\n            if (!string.IsNullOrWhiteSpace(objTags))\n            {\n                item.Tags = objTags.Split(',').Select(tag => tag.Trim()).ToArray();\n            }\n\n            if (item.Tags != null)\n            {\n                int questID;\n                foreach (string tag in item.Tags)\n                {\n                    if (int.TryParse(tag, out questID) || (tag.StartsWith(\"q\") && int.TryParse(tag.Substring(1), out questID)))\n                    {\n                        item.Quest.Add(new QuestInfo(questID, 1));\n                    }\n                }\n            }\n\n            if (node.Nodes[\"quest\"] != null)\n            {\n                foreach (Wz_Node questNode in node.Nodes[\"quest\"].Nodes)\n                {\n                    if (int.TryParse(questNode.Text, out int questID))\n                    {\n                        item.Quest.Add(new QuestInfo(questID, Convert.ToInt32(questNode.Value)));\n                    }  \n                }\n            }\n\n            if (node.Nodes[\"questex\"] != null)\n            {\n                foreach (Wz_Node questNode in node.Nodes[\"questex\"].Nodes)\n                {\n                    if (int.TryParse(questNode.Text, out int questID))\n                    {\n                        Wz_Node keyNode = questNode.Nodes[\"key\"];\n                        Wz_Node valueNode = questNode.Nodes[\"value\"];\n                        if (keyNode != null && valueNode != null)\n                        {\n                            item.Questex.Add(new QuestExInfo(questID, keyNode.GetValueEx<string>(null), valueNode.GetValueEx<int>(-1)));\n                        }\n                    }\n                }\n            }\n\n            if (node.Nodes[\"event\"] != null)\n            {\n                foreach (Wz_Node eventNode in node.Nodes[\"event\"].Nodes)\n                {\n                    var index = eventNode.Text;\n                    var condNode = eventNode.Nodes[\"condition\"];\n\n                    var collision = condNode?.FindNodeByPath(\"collision\").GetValueEx<string>(null);\n                    var slotName = condNode?.FindNodeByPath(\"slotName\").GetValueEx<string>(null);\n                    var animation = eventNode?.FindNodeByPath(\"animation\").GetValueEx<string>(null);\n                    var target = condNode?.FindNodeByPath(\"target\").GetValueEx<string>(null);\n                    var actionKeys = (eventNode.FindNodeByPath(\"action\\\\playEffect\")?.Nodes ?? new Wz_Node.WzNodeCollection(null)).Select(node => node.GetValueEx<string>(null)).ToList();\n\n                    item.Events.Add(new ItemEvent(index, collision, animation, slotName, target, actionKeys));\n                }\n            }\n            \n            string path = $@\"Map\\Obj\\{item.OS}.img\\{item.L0}\\{item.L1}\\{item.L2}\\0\";\n            var obj_node = PluginManager.FindWz(path);\n            if (obj_node != null)\n            {\n                item.MoveType = obj_node.Nodes[\"moveType\"].GetValueEx(0);\n                item.MoveW = obj_node.Nodes[\"moveW\"].GetValueEx(0);\n                item.MoveH = obj_node.Nodes[\"moveH\"].GetValueEx(0);\n                item.MoveP = obj_node.Nodes[\"moveP\"].GetValueEx(5000);\n                item.MoveDelay = obj_node.Nodes[\"moveDelay\"].GetValueEx(0);\n            }\n\n            return item;\n        }\n\n        public class ItemView\n        {\n            /// <summary>\n            /// 时间关联，单位为毫秒。\n            /// </summary>\n            public int Time { get; set; }\n\n            /// <summary>\n            /// 动画资源。\n            /// </summary>\n            public object Animator { get; set; }\n\n            /// <summary>\n            /// Flip state of item.\n            /// </summary>\n            public bool Flip { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/ParticleItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    class ParticleItem : SceneItem\n    {\n        public string ParticleName { get; set; }\n        public int Rx { get; set; }\n        public int Ry { get; set; }\n        public int Z { get; set; }\n        public SubParticleItem[] SubItems { get; set; }\n        public List<QuestInfo> Quest { get; private set; } = new List<QuestInfo>();\n        public ItemView View { get; set; }\n\n        public static ParticleItem LoadFromNode(Wz_Node node)\n        {\n            var item = new ParticleItem()\n            {\n                ParticleName = node.Text,\n                Rx = node.Nodes[\"rx\"].GetValueEx(-100),\n                Ry = node.Nodes[\"ry\"].GetValueEx(-100),\n                Z = node.Nodes[\"z\"].GetValueEx(0)\n            };\n\n            if (node.Nodes[\"quest\"] != null)\n            {\n                foreach (Wz_Node questNode in node.Nodes[\"quest\"].Nodes)\n                {\n                    if (int.TryParse(questNode.Text, out int questID))\n                    {\n                        item.Quest.Add(new QuestInfo(questID, Convert.ToInt32(questNode.Value)));\n                    }\n                }\n            }\n\n            var subItems = new List<SubParticleItem>();\n            for (int i = 0; ; i++)\n            {\n                var subNode = node.Nodes[i.ToString()];\n                if (subNode == null)\n                {\n                    break;\n                }\n                var subitem = new SubParticleItem()\n                {\n                    X = subNode.Nodes[\"x\"].GetValueEx(0),\n                    Y = subNode.Nodes[\"y\"].GetValueEx(0),\n                };\n\n                if (subNode.Nodes[\"quest\"] != null)\n                {\n                    foreach (Wz_Node questNode in subNode.Nodes[\"quest\"].Nodes)\n                    {\n                        if (int.TryParse(questNode.Text, out int questID))\n                        {\n                            subitem.Quest.Add(new QuestInfo(questID, Convert.ToInt32(questNode.Value)));\n                        }\n                    }\n                }\n                subItems.Add(subitem);\n            }\n\n            if (subItems.Count <= 0)\n            {\n                subItems.Add(new SubParticleItem()\n                {\n                    X = node.Nodes[\"x\"].GetValueEx(0),\n                    Y = node.Nodes[\"y\"].GetValueEx(0),\n                });\n            }\n            item.SubItems = subItems.ToArray();\n            return item;\n        }\n\n        public class SubParticleItem\n        {\n            public int X { get; set; }\n            public int Y { get; set; }\n            public List<QuestInfo> Quest { get; private set; } = new List<QuestInfo>();\n        }\n\n        public class ItemView\n        {\n            public ParticleSystem ParticleSystem { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/PortalItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Animation;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class PortalItem : SceneItem\n    {\n        public int Type { get; set; }\n        public string PName { get; set; }\n        public int X { get; set; }\n        public int Y { get; set; }\n        public int? ToMap { get; set; }\n        public string ToName { get; set; }\n        //Graph.img에 따른 이동경로 출력\n        public List<int> GraphTargetMap { get; set; }\n        public string Script { get; set; }\n        public int Image { get; set; }\n        public bool EnchantPortal { get; set; }\n        public bool ShownAtMinimap { get; set; }\n\n        public ItemView View { get; set; }\n        public ItemTooltip Tooltip { get; set; }\n\n        public static PortalItem LoadFromNode(Wz_Node node)\n        {\n            var item = new PortalItem()\n            {\n                PName = node.Nodes[\"pn\"].GetValueEx<string>(null),\n                Type = node.Nodes[\"pt\"].GetValueEx(0),\n                X = node.Nodes[\"x\"].GetValueEx(0),\n                Y = node.Nodes[\"y\"].GetValueEx(0),\n                ToMap = node.Nodes[\"tm\"].GetValueEx<int>(),\n                ToName = node.Nodes[\"tn\"].GetValueEx<string>(null),\n                Script = node.Nodes[\"script\"].GetValueEx<string>(null),\n                Image = node.Nodes[\"image\"].GetValueEx<int>(0),\n                EnchantPortal = node.Nodes[\"enchantPortal\"].GetValueEx<int>(0) != 0,\n                ShownAtMinimap = node.Nodes[\"shownAtMinimap\"].GetValueEx<int>(0) != 0\n            };\n            return item;\n        }\n\n        public static readonly IReadOnlyList<string> PortalTypes = new[] { \"sp\", \"pi\", \"pv\", \"pc\", \"pg\", \"tp\", \"ps\", \"pgi\", \"psi\", \"pcs\", \"ph\", \"psh\", \"pcj\", \"pci\", \"pci2\", \"pcig\", \"pshg\", \"pcc\", \"pcir\" };\n\n        public class ItemView\n        {\n            public bool IsEditorMode { get; set; }\n            public bool IsFocusing { get; set; }\n\n            public object Animator { get; set; }\n            public object EditorAnimator { get; set; }\n\n            public Controller Controller { get; set; }\n        }\n\n        public class ItemTooltip\n        {\n            public string Title { get; set; }\n        }\n\n        public class Controller : IDisposable\n        {\n            public Controller(ItemView view)\n            {\n                this.View = view;\n                AttachEvent();\n            }\n\n            public ItemView View { get; private set; }\n\n            private StateMachineAnimator animator;\n\n            public void Update(TimeSpan elapsed)\n            {\n                var ani = this.animator.GetCurrent();\n                if (ani == null) //隐藏状态\n                {\n                    if (OnStateUpdate(null, out ani) && ani != null)\n                    {\n                        this.animator.SetAnimation(ani);\n                    }\n                }\n                else\n                {\n                    animator.Update(elapsed);\n                }\n            }\n\n            private void AttachEvent()\n            {\n                this.animator = this.View.Animator as StateMachineAnimator;\n                if (animator != null)\n                {\n                    animator.AnimationEnd += Animator_AnimationEnd;\n                }\n            }\n\n            private void Animator_AnimationEnd(object sender, StateMachineAnimator.AnimationEndEventArgs e)\n            {\n                string nextState;\n                if (OnStateUpdate(e.CurrentState, out nextState))\n                {\n                    e.NextState = nextState;\n                }\n            }\n\n            private bool OnStateUpdate(string curState, out string nextState)\n            {\n                switch (curState)\n                {\n                    case null: //初始状态\n                        if (this.View.IsFocusing)\n                        {\n                            nextState = \"portalStart\";\n                        }\n                        else\n                        {\n                            nextState = null;\n                        }\n                        return true;\n\n                    case \"portalStart\": //开始动画\n                        if (this.View.IsFocusing)\n                        {\n                            nextState = \"portalContinue\";\n                        }\n                        else\n                        {\n                            nextState = \"portalExit\";\n                        }\n                        return true;\n\n                    case \"portalContinue\": //循环动画\n                        if (this.View.IsFocusing)\n                        {\n                            nextState = \"portalContinue\";\n                        }\n                        else\n                        {\n                            nextState = \"portalExit\";\n                        }\n                        return true;\n\n                    case \"portalExit\": //结束动画\n                        if (this.View.IsFocusing)\n                        {\n                            nextState = \"portalStart\";\n                        }\n                        else\n                        {\n                            nextState = null;\n                        }\n                        return true;\n                }\n\n                nextState = null;\n                return false;\n            }\n\n            public void Dispose()\n            {\n                if (animator != null)\n                {\n                    animator.AnimationEnd -= Animator_AnimationEnd;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/QuestInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public struct QuestInfo\n    {\n        public QuestInfo(int id, int state)\n        {\n            this.ID = id;\n            this.State = state;\n        }\n\n        public int ID { get; set; }\n        public int State { get; set; }\n    }\n\n    public struct QuestExInfo\n    {\n        public QuestExInfo(int id, string key, int state)\n        {\n            this.ID = id;\n            this.Key = key;\n            this.State = state;\n        }\n\n        public int ID { get; set; }\n        public string Key { get; set; }\n        public int State { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/ReactorItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Animation;\nusing System.Text.RegularExpressions;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class ReactorItem : SceneItem\n    {\n        public int ID { get; set; }\n        public int X { get; set; }\n        public int Y { get; set; }\n        public bool Flip { get; set; }\n        public string ReactorName { get; set; }\n        public int ReactorTime { get; set; }\n\n        public ItemView View { get; set; }\n\n        public static ReactorItem LoadFromNode(Wz_Node node)\n        {\n            var item = new ReactorItem()\n            {\n                ID = node.Nodes[\"id\"].GetValueEx(0),\n                X = node.Nodes[\"x\"].GetValueEx(0),\n                Y = node.Nodes[\"y\"].GetValueEx(0),\n                Flip = node.Nodes[\"f\"].GetValueEx(false),\n                ReactorName = node.Nodes[\"name\"].GetValueEx<string>(null),\n                ReactorTime = node.Nodes[\"reactorTime\"].GetValueEx<int>(0),\n            };\n            return item;\n        }\n\n        public class ItemView\n        {\n            public int Stage { get; set; }\n            public int? NextStage { get; set; }\n            public object Animator { get; set; }\n            public Controller Controller { get; set; }\n        }\n\n        public class Controller : IDisposable\n        {\n            public Controller(ItemView view)\n            {\n                this.View = view;\n                AttachEvent();\n            }\n\n            public ItemView View { get; private set; }\n\n            private StateMachineAnimator animator;\n\n            public void Update(TimeSpan elapsed)\n            {\n                var ani = this.animator.GetCurrent();\n                if (ani == null) //隐藏状态\n                {\n                    if (OnStateUpdate(null, out ani) && ani != null)\n                    {\n                        this.animator.SetAnimation(ani);\n                    }\n                }\n                else\n                {\n                    animator.Update(elapsed);\n                }\n            }\n\n            private void AttachEvent()\n            {\n                this.animator = this.View.Animator as StateMachineAnimator;\n                if (animator != null)\n                {\n                    animator.AnimationEnd += Animator_AnimationEnd;\n                }\n            }\n\n            private void Animator_AnimationEnd(object sender, StateMachineAnimator.AnimationEndEventArgs e)\n            {\n                string nextState;\n                if (OnStateUpdate(e.CurrentState, out nextState))\n                {\n                    e.NextState = nextState;\n                }\n            }\n\n            private bool OnStateUpdate(string curState, out string nextState)\n            {\n                switch (curState)\n                {\n                    case null: //初始状态\n                        if (this.View.Stage > -1)\n                        {\n                            nextState = this.View.Stage.ToString();\n                        }\n                        else\n                        {\n                            nextState = null;\n                        }\n                        return true;\n\n                    default:\n                        Match m;\n                        if ((m = Regex.Match(curState, @\"^(\\d+)$\")).Success)\n                        {\n                            int curStage = int.Parse(m.Result(\"$1\"));\n                            if (this.View.NextStage != null)\n                            {\n                                string hitAniName = $@\"{curStage}/hit\";\n                                if (this.animator.Data.States.Contains(hitAniName)) //跳转到hit动作\n                                {\n                                    nextState = hitAniName;\n                                    return true;\n                                }\n                                else\n                                {\n                                    goto _lbl1;\n                                }\n                            }\n                        }\n                        else if ((m = Regex.Match(curState, @\"^(\\d+)/hit$\")).Success)\n                        {\n                            int curStage = int.Parse(m.Result(\"$1\"));\n                            if (this.View.NextStage != null)\n                            {\n                                goto _lbl1;\n                            }\n                        }\n                        break;\n                }\n\n                nextState = null;\n                return false;\n\n                _lbl1:\n                {\n                    string aniName = this.View.NextStage.Value.ToString();\n                    if (this.animator.Data.States.Contains(aniName)) //动作存在 直接跳转\n                    {\n                        nextState = aniName;\n                        AfterStageChanged(this.View.NextStage.Value);\n                    }\n                    else //动作不存在 忽略\n                    {\n                        nextState = curState;\n                        this.View.NextStage = null;\n                    }\n                    return true;\n                }\n            }\n\n            private void AfterStageChanged(int newStage)\n            {\n                this.View.Stage = newStage;\n                this.View.NextStage = null;\n            }\n\n            public void Dispose()\n            {\n                if (animator != null)\n                {\n                    animator.AnimationEnd -= Animator_AnimationEnd;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/SceneItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class SceneItem\n    {\n        public string Name { get; set; }\n        public int Index { get; set; }\n        public string[] Tags { get; set; }\n\n        public override string ToString()\n        {\n            return $\"{Name} {GetType().Name}\";\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/SkyWhaleItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    class SkyWhaleItem : SceneItem\n    {\n        public Point Start { get; set; }\n        public Point End { get; set; }\n        public int Width { get; set; }\n        public double Speed { get; set; }\n        public double FixSpeedShoe { get; set; }\n        public double VRate { get; set; }\n        public int ApplyTerm { get; set; }\n\n        public static SkyWhaleItem LoadFromNode(Wz_Node node)\n        {\n            var item = new SkyWhaleItem()\n            {\n                Start = node.Nodes[\"start\"].GetValueEx<Wz_Vector>(null)?.ToPoint()?? Point.Zero,\n                End = node.Nodes[\"end\"].GetValueEx<Wz_Vector>(null)?.ToPoint() ?? Point.Zero,\n                Width = node.Nodes[\"width\"].GetValueEx<int>(0),\n                Speed = node.Nodes[\"speed\"].GetValueEx<double>(0),\n                FixSpeedShoe = node.Nodes[\"fixSpeedShoe\"].GetValueEx<double>(0),\n                VRate = node.Nodes[\"vrate\"].GetValueEx<double>(0),\n                ApplyTerm = node.Nodes[\"applyTerm\"].GetValueEx<int>(0),\n            };\n            return item;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/TileItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class TileItem : SceneItem\n    {\n        public string TS { get; set; }\n        public string U { get; set; }\n        public string No { get; set; }\n        public int X { get; set; }\n        public int Y { get; set; }\n\n        public ItemView View { get; set; }\n\n        public static TileItem LoadFromNode(Wz_Node node)\n        {\n            var item = new TileItem()\n            {\n                U = node.Nodes[\"u\"].GetValueEx<string>(null),\n                No = node.Nodes[\"no\"].GetValueEx<string>(null),\n\n                X = node.Nodes[\"x\"].GetValueEx(0),\n                Y = node.Nodes[\"y\"].GetValueEx(0),\n            };\n            return item;\n        }\n\n        public class ItemView\n        {\n            /// <summary>\n            /// 时间关联，单位为毫秒。\n            /// </summary>\n            public int Time { get; set; }\n\n            /// <summary>\n            /// 动画资源。\n            /// </summary>\n            public object Animator { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Patches2/TooltipItem.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender.Patches2\n{\n    public class TooltipItem : SceneItem\n    {\n        public Rectangle Rect { get; set; }\n        public Rectangle CharRect { get; set; }\n        public string Title { get; set; }\n        public string Desc { get; set; }\n        public string ItemEU { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的常规信息通过以下\n// 特性集控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2.MapRender\")]\n[assembly: AssemblyDescription(\"Map Simulator Plugin for WzComparerR2.\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Kagamia Studio\")]\n[assembly: AssemblyProduct(\"WzComparerR2.MapRender\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2015-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 使此程序集中的类型\n// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型，\n// 则将该类型上的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"c12049a5-0b6b-4e3d-8cc0-aefa5701dac5\")]\n\n// 程序集的版本信息由下面四个值组成:\n//\n//      主版本\n//      次版本 \n//      生成号\n//      修订号\n//\n// 可以指定所有这些值，也可以使用“生成号”和“修订号”的默认值，\n// 方法是按如下所示使用“*”:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2.MapRender/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     這段程式碼是由工具產生的。\n//     執行階段版本:4.0.30319.42000\n//\n//     對這個檔案所做的變更可能會造成錯誤的行為，而且如果重新產生程式碼，\n//     變更將會遺失。\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace WzComparerR2.MapRender.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   用於查詢當地語系化字串等的強類型資源類別。\n    /// </summary>\n    // 這個類別是自動產生的，是利用 StronglyTypedResourceBuilder\n    // 類別透過 ResGen 或 Visual Studio 這類工具。\n    // 若要加入或移除成員，請編輯您的 .ResX 檔，然後重新執行 ResGen\n    // (利用 /str 選項)，或重建您的 VS 專案。\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"17.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   傳回這個類別使用的快取的 ResourceManager 執行個體。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"WzComparerR2.MapRender.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   覆寫目前執行緒的 CurrentUICulture 屬性，對象是所有\n        ///   使用這個強類型資源類別的資源查閱。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtCancel4_disabled_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtCancel4_disabled_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtCancel4_mouseOver_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtCancel4_mouseOver_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtCancel4_normal_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtCancel4_normal_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtCancel4_pressed_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtCancel4_pressed_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtClose3_disabled_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtClose3_disabled_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtClose3_mouseOver_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtClose3_mouseOver_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtClose3_normal_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtClose3_normal_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtClose3_pressed_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtClose3_pressed_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtNo3_disabled_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtNo3_disabled_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtNo3_mouseOver_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtNo3_mouseOver_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtNo3_normal_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtNo3_normal_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtNo3_pressed_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtNo3_pressed_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtOK4_disabled_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtOK4_disabled_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtOK4_mouseOver_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtOK4_mouseOver_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtOK4_normal_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtOK4_normal_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_BtOK4_pressed_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_BtOK4_pressed_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_ComboBox_normal_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_ComboBox_normal_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_ComboBox_normal_1 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_ComboBox_normal_1\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_ComboBox_normal_2 {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_ComboBox_normal_2\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_Notice6_box {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_Notice6_box\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_Notice6_c {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_Notice6_c\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_Notice6_c_box {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_Notice6_c_box\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_Notice6_s {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_Notice6_s\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_Notice6_s_box {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_Notice6_s_box\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap Basic_img_Notice6_t {\n            get {\n                object obj = ResourceManager.GetObject(\"Basic_img_Notice6_t\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap StatusBar3_img_chat_ingame_input_layer_backgrnd {\n            get {\n                object obj = ResourceManager.GetObject(\"StatusBar3_img_chat_ingame_input_layer_backgrnd\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap StatusBar3_img_chat_ingame_input_layer_chatEnter {\n            get {\n                object obj = ResourceManager.GetObject(\"StatusBar3_img_chat_ingame_input_layer_chatEnter\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap StatusBar3_img_chat_ingame_view_max_bottom {\n            get {\n                object obj = ResourceManager.GetObject(\"StatusBar3_img_chat_ingame_view_max_bottom\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap StatusBar3_img_chat_ingame_view_max_center {\n            get {\n                object obj = ResourceManager.GetObject(\"StatusBar3_img_chat_ingame_view_max_center\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap StatusBar3_img_chat_ingame_view_max_top {\n            get {\n                object obj = ResourceManager.GetObject(\"StatusBar3_img_chat_ingame_view_max_top\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow_img_ToolTip_WorldMap_ArcaneForce {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow_img_ToolTip_WorldMap_ArcaneForce\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow_img_ToolTip_WorldMap_AuthenticForce {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow_img_ToolTip_WorldMap_AuthenticForce\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow_img_ToolTip_WorldMap_enchantMob {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow_img_ToolTip_WorldMap_enchantMob\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow_img_ToolTip_WorldMap_Line {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow_img_ToolTip_WorldMap_Line\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow_img_ToolTip_WorldMap_Mob {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow_img_ToolTip_WorldMap_Mob\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow_img_ToolTip_WorldMap_Npc {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow_img_ToolTip_WorldMap_Npc\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n        \n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow_img_ToolTip_WorldMap_StarForce {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow_img_ToolTip_WorldMap_StarForce\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n\n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow2_img_Teleport_customBG\n        {\n            get\n            {\n                object obj = ResourceManager.GetObject(\"UIWindow2_img_Teleport_customBG\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n\n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow2_img_WorldMap_Border_0 {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow2_img_WorldMap_Border_0\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n\n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow3_img_mirrorFrame_1024 {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow3_img_mirrorFrame_1024\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n\n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow3_img_mirrorFrame_1366 {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow3_img_mirrorFrame_1366\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n\n        /// <summary>\n        ///   查詢類型 System.Drawing.Bitmap 的當地語系化資源。\n        /// </summary>\n        internal static System.Drawing.Bitmap UIWindow3_img_mirrorFrame_800 {\n            get {\n                object obj = ResourceManager.GetObject(\"UIWindow3_img_mirrorFrame_800\", resourceCulture);\n                return ((System.Drawing.Bitmap)(obj));\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.MapRender/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <assembly alias=\"System.Windows.Forms\" name=\"System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\" />\n  <data name=\"Basic_img_ComboBox_normal_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\resources\\basic.img.combobox.normal.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_ComboBox_normal_1\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\resources\\basic.img.combobox.normal.1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_ComboBox_normal_2\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\resources\\basic.img.combobox.normal.2.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow2_img_WorldMap_Border_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow2.img.WorldMap.Border.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow3_img_mirrorFrame_800\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow3.img.mirrorFrame.800.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow3_img_mirrorFrame_1024\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow3.img.mirrorFrame.1024.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow3_img_mirrorFrame_1366\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow3.img.mirrorFrame.1366.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow_img_ToolTip_WorldMap_ArcaneForce\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow.img.ToolTip.WorldMap.ArcaneForce.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow_img_ToolTip_WorldMap_AuthenticForce\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow.img.ToolTip.WorldMap.AuthenticForce.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow_img_ToolTip_WorldMap_enchantMob\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow.img.ToolTip.WorldMap.enchantMob.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow_img_ToolTip_WorldMap_Line\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow.img.ToolTip.WorldMap.Line.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow_img_ToolTip_WorldMap_Mob\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow.img.ToolTip.WorldMap.Mob.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow_img_ToolTip_WorldMap_Npc\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow.img.ToolTip.WorldMap.Npc.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow_img_ToolTip_WorldMap_StarForce\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow.img.ToolTip.WorldMap.StarForce.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_Notice6_c\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.Notice6.c.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_Notice6_s\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.Notice6.s.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_Notice6_t\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.Notice6.t.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtCancel4_disabled_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtCancel4.disabled.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtCancel4_mouseOver_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtCancel4.mouseOver.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtCancel4_normal_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtCancel4.normal.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtCancel4_pressed_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtCancel4.pressed.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtClose3_disabled_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtClose3.disabled.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtClose3_mouseOver_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtClose3.mouseOver.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtClose3_normal_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtClose3.normal.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtClose3_pressed_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtClose3.pressed.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtNo3_disabled_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtNo3.disabled.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtNo3_mouseOver_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtNo3.mouseOver.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtNo3_normal_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtNo3.normal.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtNo3_pressed_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtNo3.pressed.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtOK4_disabled_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtOK4.disabled.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtOK4_mouseOver_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtOK4.mouseOver.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtOK4_normal_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtOK4.normal.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_BtOK4_pressed_0\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.BtOK4.pressed.0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"StatusBar3_img_chat_ingame_input_layer_backgrnd\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\StatusBar3.img.chat.ingame.input.layer_backgrnd.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"StatusBar3_img_chat_ingame_input_layer_chatEnter\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\StatusBar3.img.chat.ingame.input.layer_chatEnter.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"StatusBar3_img_chat_ingame_view_max_bottom\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\StatusBar3.img.chat.ingame.view.max.bottom.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"StatusBar3_img_chat_ingame_view_max_center\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\StatusBar3.img.chat.ingame.view.max.center.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"StatusBar3_img_chat_ingame_view_max_top\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\StatusBar3.img.chat.ingame.view.max.top.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_Notice6_box\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.Notice6.box.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_Notice6_c_box\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.Notice6.c_box.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"Basic_img_Notice6_s_box\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\Basic.img.Notice6.s_box.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n  <data name=\"UIWindow2_img_Teleport_customBG\" type=\"System.Resources.ResXFileRef, System.Windows.Forms\">\n    <value>..\\Resources\\UIWindow2_img_Teleport_customBG.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>\n  </data>\n</root>"
  },
  {
    "path": "WzComparerR2.MapRender/RenderAnimate.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.MapRender\n{\n    public class RenderAnimate : IEnumerable<RenderFrame>\n    {\n        public RenderAnimate(RenderFrame[] frames)\n        {\n            this.frames = frames;\n            UpdateTimeLine();\n        }\n\n        RenderFrame[] frames;\n        int repeat;\n        int[] timeLine;\n\n        public RenderFrame this[int index]\n        {\n            get { return frames[index]; }\n        }\n\n        public int Length\n        {\n            get { return frames == null ? -1 : frames.Length; }\n        }\n\n        public int Repeat\n        {\n            get { return repeat; }\n            set { repeat = value; }\n        }\n\n        public int[] TimeLine\n        {\n            get { return timeLine; }\n        }\n\n        private void UpdateTimeLine()\n        {\n            if (this.frames == null || this.frames.Length == 0)\n            {\n                timeLine = new int[] { 0 };\n                return;\n            }\n            if (this.timeLine == null || (this.timeLine.Length != this.frames.Length + 1))\n            {\n                this.timeLine = new int[this.frames.Length + 1];\n            }\n            for (int i = 0; i < this.frames.Length; i++)\n            {\n                this.timeLine[i + 1] = this.timeLine[i] + this.frames[i].Delay;\n            }\n        }\n\n        public IEnumerator<RenderFrame> GetEnumerator()\n        {\n            return new System.Collections.ObjectModel.ReadOnlyCollection<RenderFrame>(this.frames).GetEnumerator();\n        }\n\n        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\n        {\n            return this.GetEnumerator();\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/RenderArgs.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    public class RenderArgs\n    {\n        public RenderArgs()\n        {\n            currentIndex = -1;\n            currentPercent = 0f;\n            culled = false;\n            visible = true;\n        }\n\n        private int currentIndex;\n        private float currentPercent;\n        private bool culled;\n        private bool visible;\n        private Rectangle displayRectangle;\n\n        public int CurrentIndex\n        {\n            get { return currentIndex; }\n            set { currentIndex = value; }\n        }\n\n        public float CurrentPercent\n        {\n            get { return currentPercent; }\n            set { currentPercent = value; }\n        }\n\n        public bool Culled\n        {\n            get { return culled; }\n            set { culled = value; }\n        }\n\n        public bool Visible\n        {\n            get { return visible; }\n            set { visible = value; }\n        }\n\n        public Rectangle DisplayRectangle\n        {\n            get { return displayRectangle; }\n            set { displayRectangle = value; }\n        }\n\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/RenderEnv.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender\n{\n    public class RenderEnv : IDisposable\n    {\n        public RenderEnv(Game game, GraphicsDeviceManager graphics)\n        {\n            this.GraphicsDevice = graphics.GraphicsDevice;\n            this.Camera = new Camera(graphics);\n            this.Sprite = new SpriteBatchEx(this.GraphicsDevice);\n            this.D2DRenderer = new D2DRenderer(this.GraphicsDevice);\n            this.Input = new InputState(game);\n            this.Fonts = new MapRenderFonts();\n\n            this.BlendStateMultiplyRGB = StateEx.MultiplyRGB();\n        }\n\n        public Camera Camera { get; private set; }\n        public SpriteBatchEx Sprite { get; private set; }\n        public D2DRenderer D2DRenderer { get; private set; }\n        public InputState Input { get; private set; }\n        public GraphicsDevice GraphicsDevice { get; private set; }\n        public MapRenderFonts Fonts { get; private set; }\n        \n        public BlendState BlendStateMultiplyRGB { get; private set; }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                this.Sprite.Dispose();\n                this.Fonts.Dispose();\n                this.BlendStateMultiplyRGB.Dispose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/RenderFrame.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender\n{\n    public class RenderFrame\n    {\n        public RenderFrame()\n        {\n            \n        }\n\n        Texture2D texture;\n        Vector2 origin;\n        int z;\n        int delay;\n        int a0;\n        int a1;\n\n        public Texture2D Texture\n        {\n            get { return texture; }\n            set { texture = value; }\n        }\n\n        public Vector2 Origin\n        {\n            get { return origin; }\n            set { origin = value; }\n        }\n\n        public int Z\n        {\n            get { return z; }\n            set { z = value; }\n        }\n        \n        public int Delay\n        {\n            get { return delay; }\n            set { delay = value; }\n        }\n       \n        public int A0\n        {\n            get { return a0; }\n            set { a0 = value; }\n        }\n\n        public int A1\n        {\n            get { return a1; }\n            set { a1 = value; }\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/ResourceLoader.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Animation;\nusing WzComparerR2.Common;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.MapRender\n{\n    public class ResourceLoader : IDisposable\n    {\n        public ResourceLoader(IServiceProvider serviceProvider)\n        {\n            this.Services = serviceProvider;\n            this.loadedItems = new Dictionary<string, ResourceHolder>();\n            this.loadedAnimationData = new Dictionary<string, object>();\n        }\n\n        public PatchVisibility PatchVisibility { get; set; }\n        public IServiceProvider Services { get; protected set; }\n\n        protected Dictionary<string, ResourceHolder> loadedItems;\n        protected Dictionary<string, object> loadedAnimationData;\n        private bool isCounting;\n\n        private GraphicsDevice GraphicsDevice\n        {\n            get\n            {\n                return this.Services.GetService<IGraphicsDeviceService>().GraphicsDevice;\n            }\n        }\n\n        public virtual T Load<T>(string assetName)\n        {\n            return Load<T>(null, assetName);\n        }\n\n        public virtual T Load<T>(Wz_Node node)\n        {\n            return Load<T>(node, null);\n        }\n\n        private T Load<T>(Wz_Node node = null, string assetName = null)\n        {\n            if (node == null && assetName == null)\n                throw new ArgumentNullException();\n\n            assetName = assetName ?? node.FullPathToFile;\n\n            ResourceHolder holder;\n            if (!loadedItems.TryGetValue(assetName, out holder))\n            {\n                var res = InnerLoad(node ?? PluginManager.FindWz(assetName), typeof(T));\n                if (res == null)\n                {\n                    return default(T);\n                }\n\n                holder = new ResourceHolder();\n                holder.Resource = res;\n                loadedItems[assetName] = holder;\n            }\n\n            //结算计数器\n            if (isCounting)\n            {\n                holder.Count++;\n            }\n\n            //特殊处理\n            return (T)holder.Resource;\n        }\n\n        public object LoadAnimationData(Wz_Node node)\n        {\n            object aniData;\n            string assetName = node.FullPathToFile;\n            if (!loadedAnimationData.TryGetValue(assetName, out aniData))\n            {\n                aniData = InnerLoadAnimationData(node);\n                if (aniData == null)\n                {\n                    return null;\n                }\n                loadedAnimationData[assetName] = aniData;\n            }\n            return aniData;\n        }\n\n        public object LoadAnimationData(string assetName)\n        {\n            var node = PluginManager.FindWz(assetName);\n            return node != null ? LoadAnimationData(node) : null;\n        }\n\n        public object LoadParticleDesc(Wz_Node node)\n        {\n            int dataType = node.Nodes[\"data_type\"].GetValueEx<int>(0);\n            switch (dataType)\n            {\n                case 0: return LoadParticleDesc0(node);\n                case 1: return LoadParticleDesc1(node);\n                case 3: return LoadParticleDesc3(node);\n                default: throw new Exception($\"Unknown particle data type {dataType}.\");\n            }\n        }\n\n        public ParticleDesc LoadParticleDesc0(Wz_Node node)\n        {\n            var desc = new ParticleDesc();\n            desc.Name = node.Text;\n            foreach (var pNode in node.Nodes)\n            {\n                switch (pNode.Text)\n                {\n                    case \"totalParticle\": desc.TotalParticle = pNode.GetValue<int>(); break;\n                    case \"angle\": desc.Angle = pNode.GetValue<float>(); break;\n                    case \"angleVar\": desc.AngleVar = pNode.GetValue<float>(); break;\n                    case \"duration\": desc.Duration = pNode.GetValue<float>(); break;\n                    case \"blendFuncSrc\": desc.BlendFuncSrc = (ParticleBlendFunc)pNode.GetValue<int>(); break;\n                    case \"blendFuncDst\": desc.BlendFuncDst = (ParticleBlendFunc)pNode.GetValue<int>(); break;\n                    case \"startColor\": desc.StartColor = pNode.GetXnaColor(); break;\n                    case \"startColorVar\": desc.StartColorVar = pNode.GetXnaColor(); break;\n                    case \"endColor\": desc.EndColor = pNode.GetXnaColor(); break;\n                    case \"endColorVar\": desc.EndColorVar = pNode.GetXnaColor(); break;\n                    case \"MiddlePoint0\": desc.MiddlePoint0 = pNode.GetValue<int>(); break;\n                    case \"MiddlePointAlpha0\": desc.MiddlePointAlpha0 = pNode.GetValue<int>(); break;\n                    case \"MiddlePoint1\": desc.MiddlePoint1 = pNode.GetValue<int>(); break;\n                    case \"MiddlePointAlpha1\": desc.MiddlePointAlpha1 = pNode.GetValue<int>(); break;\n                    case \"startSize\": desc.StartSize = pNode.GetValue<float>(); break;\n                    case \"startSizeVar\": desc.StartSizeVar = pNode.GetValue<float>(); break;\n                    case \"endSize\": desc.EndSize = pNode.GetValue<float>(); break;\n                    case \"endSizeVar\": desc.EndSizeVar = pNode.GetValue<float>(); break;\n                    case \"posX\": desc.PosX = pNode.GetValue<float>(); break;\n                    case \"posY\": desc.PosY = pNode.GetValue<float>(); break;\n                    case \"posVarX\": desc.PosVarX = pNode.GetValue<float>(); break;\n                    case \"posVarY\": desc.PosVarY = pNode.GetValue<float>(); break;\n                    case \"startSpin\": desc.StartSpin = pNode.GetValue<float>(); break;\n                    case \"startSpinVar\": desc.StartSpinVar = pNode.GetValue<float>(); break;\n                    case \"endSpin\": desc.EndSpin = pNode.GetValue<float>(); break;\n                    case \"endSpinVar\": desc.EndSpinVar = pNode.GetValue<float>(); break;\n                    case \"GRAVITY\":\n                        {\n                            var gravityDesc = new ParticleGravityDesc();\n                            foreach (var pNode2 in pNode.Nodes)\n                            {\n                                switch (pNode2.Text)\n                                {\n                                    case \"x\": gravityDesc.X = pNode2.GetValue<float>(); break;\n                                    case \"y\": gravityDesc.Y = pNode2.GetValue<float>(); break;\n                                    case \"speed\": gravityDesc.Speed = pNode2.GetValue<float>(); break;\n                                    case \"speedVar\": gravityDesc.SpeedVar = pNode2.GetValue<float>(); break;\n                                    case \"radialAccel\": gravityDesc.RadialAccel = pNode2.GetValue<float>(); break;\n                                    case \"radialAccelVar\": gravityDesc.RadialAccelVar = pNode2.GetValue<float>(); break;\n                                    case \"tangentialAccel\": gravityDesc.TangentialAccel = pNode2.GetValue<float>(); break;\n                                    case \"tangentialAccelVar\": gravityDesc.TangentialAccelVar = pNode2.GetValue<float>(); break;\n                                    case \"rotationIsDir\": gravityDesc.RotationIsDir = pNode2.GetValue<int>() != 0; break;\n                                }\n                            }\n                            desc.Gravity = gravityDesc;\n                        }\n                        break;\n                    case \"RADIUS\":\n                        {\n                            var radiusDesc = new ParticleRadiusDesc();\n                            foreach (var pNode2 in pNode.Nodes)\n                            {\n                                switch (pNode2.Text)\n                                {\n                                    case \"startRadius\": radiusDesc.StartRadius = pNode2.GetValue<float>(); break;\n                                    case \"startRadiusVar\": radiusDesc.StartRadiusVar = pNode2.GetValue<float>(); break;\n                                    case \"endRadius\": radiusDesc.EndRadius = pNode2.GetValue<float>(); break;\n                                    case \"endRadiusVar\": radiusDesc.EndRadiusVar = pNode2.GetValue<float>(); break;\n                                    case \"rotatePerSecond\": radiusDesc.RotatePerSecond = pNode2.GetValue<float>(); break;\n                                    case \"rotatePerSecondVar\": radiusDesc.RotatePerSecondVar = pNode2.GetValue<float>(); break;\n                                }\n                            }\n                            desc.Radius = radiusDesc;\n                        }\n                        break;\n                    case \"life\": desc.Life = pNode.GetValue<float>(); break;\n                    case \"lifeVar\": desc.LifeVar = pNode.GetValue<float>(); break;\n                    case \"opacityModifyRGB\": desc.OpacityModifyRGB = pNode.GetValue<int>() != 0; break;\n                    case \"positionType\": desc.PositionType = pNode.GetValue<int>(); break;\n                    case \"texture\":\n                        desc.Texture = this.LoadFrame(pNode);\n                        break;\n                }\n            }\n            return desc;\n        }\n\n        public ParticleDesc1 LoadParticleDesc1(Wz_Node node)\n        {\n            return new ParticleDesc1();\n        }\n\n        public ParticleDesc3 LoadParticleDesc3(Wz_Node node)\n        {\n            return new ParticleDesc3();\n        }\n\n        public void BeginCounting()\n        {\n            foreach (var kv in this.loadedItems)\n            {\n                kv.Value.Count = 0;\n            }\n            isCounting = true;\n        }\n\n        public void EndCounting()\n        {\n            isCounting = false;\n        }\n\n        public void ClearAnimationCache()\n        {\n            this.loadedAnimationData.Clear();\n        }\n\n        public void Recycle()\n        {\n            var preRemoved = this.loadedItems\n                .Where(kv => kv.Value.Count <= 0)\n                .ToList();\n\n            foreach (var kv in preRemoved)\n            {\n                this.loadedItems.Remove(kv.Key);\n                UnloadResource(kv.Value.Resource);\n            }\n        }\n\n        public void Unload()\n        {\n            foreach (var kv in this.loadedItems)\n            {\n                UnloadResource(kv.Value.Resource);\n            }\n\n            this.loadedItems.Clear();\n            this.loadedAnimationData.Clear();\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        private void UnloadResource(object resource)\n        {\n            if (resource is Texture2D)\n            {\n                ((Texture2D)resource).Dispose();\n            }\n            else if (resource is TextureAtlas) //回头再考虑回收策略\n            {\n                ((TextureAtlas)resource).Texture.Dispose();\n            }\n            else if (resource is Music)\n            {\n                ((Music)resource).Dispose();\n            }\n            else\n            {\n                (resource as IDisposable)?.Dispose();\n            }\n        }\n\n        private object InnerLoad(Wz_Node node, Type assetType)\n        {\n            if (assetType == typeof(TextureAtlas)) //回头再说\n            {\n                var png = node.GetValue<Wz_Png>();\n                if (png != null)\n                {\n                    return new TextureAtlas(png.ToTexture(this.GraphicsDevice));\n                }\n            }\n            else if (assetType == typeof(Texture2D))\n            {\n                var png = node.GetValue<Wz_Png>();\n                if (png != null)\n                {\n                    return png.ToTexture(this.GraphicsDevice);\n                }\n            }\n            else if (assetType == typeof(Music))\n            {\n                var sound = node.GetValue<Wz_Sound>();\n                if (sound != null)\n                {\n                    return new Music(sound);\n                }\n            }\n            else if (assetType == typeof(MsShader))\n            {\n                return this.LoadMsShader(node);\n            }\n            return null;\n        }\n\n        private object InnerLoadAnimationData(Wz_Node node)\n        {\n            if (node != null && (node = node.ResolveUol()) != null)\n            {\n                SpineDetectionResult detectionResult;\n\n                if (node.Value is Wz_Png) //单帧动画\n                {\n                    var aniData = new FrameAnimationData();\n                    var frame = LoadFrame(node);\n                    aniData.Frames.Add(frame);\n                    return aniData;\n                }\n                else if ((detectionResult = SpineLoader.Detect(node)).Success)\n                {\n                    return this.LoadSpineAnimationData(detectionResult);\n                }\n                else if (node.Value == null && node.Nodes.Count > 0) //分析目录\n                {\n                    if (node.Nodes[\"type\"]?.Value is string type)\n                    {\n                        switch (type)\n                        {\n                            case \"sprite\":\n                                var msSprite = this.LoadMsSpriteData(node);\n                                return msSprite;\n                        }\n                    }\n                    else if (node.Nodes[\"spine\"]?.Value is string spine)\n                    {\n                        var textureLoader = new SpineTextureLoader(this, node);\n                        textureLoader.EnableTextureMissingFallback = true;\n                        var atlasNode = node.Nodes[spine + \".atlas\"];\n\n                        detectionResult = SpineLoader.Detect(atlasNode);\n                        if (detectionResult.Success)\n                        {\n                            return this.LoadSpineAnimationData(detectionResult);\n                        }\n                    }\n                    else //读取序列帧动画\n                    {\n                        var frames = new List<Frame>();\n                        Wz_Node frameNode;\n                        for (int i = 0; (frameNode = node.Nodes[i.ToString()]) != null; i++)\n                        {\n                            var frame = LoadFrame(frameNode);\n                            frames.Add(frame);\n                        }\n                        var repeat = node.Nodes[\"repeat\"].GetValueEx<bool>();\n                        return new RepeatableFrameAnimationData(frames) { Repeat = repeat };\n                    }\n                }\n            }\n            return null;\n        }\n\n        private Frame LoadFrame(Wz_Node node)\n        {\n            //处理uol\n            while (node?.Value is Wz_Uol uol)\n            {\n                node = uol.HandleUol(node);\n            }\n            if (node == null)\n            {\n                return new Frame();\n            }\n            //寻找link\n            var linkNode = node.GetLinkedSourceNode(PluginManager.FindWz);\n            //加载资源\n            var atlas = Load<TextureAtlas>(linkNode);\n            //读取其他信息\n            var frame = new Frame()\n            {\n                Texture = atlas.Texture,\n                AtlasRect = atlas.SrcRect,\n                Z = node.Nodes[\"z\"].GetValueEx(0),\n                Delay = node.Nodes[\"delay\"].GetValueEx(120),\n                Blend = node.Nodes[\"blend\"].GetValueEx(0) != 0,\n                Origin = (node.Nodes[\"origin\"]?.Value as Wz_Vector)?.ToPoint() ?? Point.Zero\n            };\n            frame.A0 = node.Nodes[\"a0\"].GetValueEx(255);\n            frame.A1 = node.Nodes[\"a1\"].GetValueEx(frame.A0);\n            return frame;\n        }\n\n        private ISpineAnimationData LoadSpineAnimationData(SpineDetectionResult detectionResult)\n        {\n            if (detectionResult.Success)\n            {\n                var textureLoader = new SpineTextureLoader(this, detectionResult.SourceNode.ParentNode);\n                textureLoader.EnableTextureMissingFallback = true;\n                if (detectionResult.Version == SpineVersion.V2)\n                {\n                    return SpineAnimationDataV2.Create(detectionResult, textureLoader);\n                }\n                else if (detectionResult.Version == SpineVersion.V4)\n                {\n                    return SpineAnimationDataV4.Create(detectionResult, textureLoader);\n                }\n            }\n            \n            return null;\n        }\n\n        private Texture2D CreateEmptyTexture(string path, int width, int height)\n        {\n            if (!loadedItems.TryGetValue(path, out ResourceHolder holder))\n            {\n                holder = new ResourceHolder();\n                holder.Resource = new Texture2D(this.GraphicsDevice, width, height, false, SurfaceFormat.Alpha8);\n                loadedItems[path] = holder;\n            }\n\n            //结算计数器\n            if (isCounting)\n            {\n                holder.Count++;\n            }\n\n            //特殊处理\n            return (Texture2D)holder.Resource;\n        }\n\n        private MsCustomSpriteData LoadMsSpriteData(Wz_Node node)\n        {\n            var textures = new List<MsCustomTexture>();\n            // load textures\n            for (int i = 0; ; i++)\n            {\n                Wz_Node textureNode = node.Nodes[i.ToString()];\n                if (textureNode == null)\n                {\n                    break;\n                }\n                var textureArray = new List<Texture2D>();\n                for (int j = 0; ; j++)\n                {\n                    Wz_Node textureIndexNode = textureNode.Nodes[j.ToString()];\n                    if (textureIndexNode == null)\n                    {\n                        break;\n                    }\n                    var linkNode = textureIndexNode.GetLinkedSourceNode(PluginManager.FindWz);\n                    textureArray.Add(this.Load<Texture2D>(linkNode));\n                }\n                var addressU = textureNode.Nodes[\"address_u\"].GetValueEx<int>(0);\n                var addressV = textureNode.Nodes[\"address_v\"].GetValueEx<int>(0);\n                textures.Add(new MsCustomTexture\n                {\n                    Texture = textureArray.FirstOrDefault(),\n                    Textures = textureArray.ToArray(),\n                    AddressU = addressU,\n                    AddressV = addressV,\n                });\n            }\n            // load shader\n            string shaderName = node.Nodes[\"shader\"].GetValueEx<string>(null);\n            MsShader shader = null;\n            if (shaderName != null)\n            {\n                Wz_Node shaderNode = node.GetNodeWzImage()?.Node.FindNodeByPath(false, shaderName.Split('/'));\n                if (shaderNode != null)\n                {\n                    shader = this.Load<MsShader>(shaderNode);\n                }\n            }\n\n            var msSprite = new MsCustomSpriteData()\n            {\n                Textures = textures.ToArray(),\n                Shader = shader,\n            };\n            return msSprite;\n        }\n\n        private MsShader LoadMsShader(Wz_Node node)\n        {\n            var shader = new MsShader();\n            shader.ID = node.Nodes[\"id\"]?.GetValueEx<string>(null);\n            if (node.Nodes[\"constant\"] is Wz_Node constantRoot)\n            {\n                foreach (var constantNode in constantRoot.Nodes)\n                {\n                    if (constantNode.Nodes.Count > 0) // read as vector\n                    {\n                        if (constantNode.Nodes[\"x\"]?.Value is float x)\n                        {\n                            if (constantNode.Nodes[\"y\"]?.Value is float y)\n                            {\n                                if (constantNode.Nodes[\"z\"]?.Value is float z)\n                                {\n                                    shader.Constants.Add(constantNode.Text, new MsShaderConstant(x, y, z));\n                                }\n                                else\n                                {\n                                    shader.Constants.Add(constantNode.Text, new MsShaderConstant(x, y));\n                                }\n                            }\n                            else\n                            {\n                                shader.Constants.Add(constantNode.Text, new MsShaderConstant(x));\n                            }\n                        }\n                    }\n                    else if (constantNode.Value is float x) // read as scalar\n                    {\n                        shader.Constants.Add(constantNode.Text, new MsShaderConstant(x));\n                    }\n                }\n            }\n            return shader;\n        }\n\n        protected virtual void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                Unload();\n            }\n        }\n\n        ~ResourceLoader()\n        {\n            this.Dispose(false);\n        }\n\n        protected class ResourceHolder\n        {\n            public object Resource { get; set; }\n            public int Count { get; set; }\n        }\n\n        private class SpineTextureLoader : Spine.TextureLoader, Spine.V2.TextureLoader\n        {\n            public SpineTextureLoader(ResourceLoader resLoader, Wz_Node topNode)\n            {\n                this.BaseLoader = resLoader;\n                this.TopNode = topNode;\n            }\n\n            public ResourceLoader BaseLoader { get; set; }\n            public Wz_Node TopNode { get; set; }\n            public bool EnableTextureMissingFallback { get; set; }\n\n            public void Load(Spine.AtlasPage page, string path)\n            {\n                if (this.TryLoadTexture(path, out var texture))\n                {\n                    page.rendererObject = texture;\n                    page.width = texture.Width;\n                    page.height = texture.Height;\n                }\n                else if (this.EnableTextureMissingFallback && page.width > 0 && page.height > 0)\n                {\n                    page.rendererObject = this.CreateEmptyTexture(path, page.width, page.height);\n                }\n            }\n\n            public void Load(Spine.V2.AtlasPage page, string path)\n            {\n                if (this.TryLoadTexture(path, out var texture))\n                {\n                    page.rendererObject = texture;\n                    page.width = texture.Width;\n                    page.height = texture.Height;\n                }\n                else if (this.EnableTextureMissingFallback && page.width > 0 && page.height > 0)\n                {\n                    page.rendererObject = this.CreateEmptyTexture(path, page.width, page.height);\n                }\n            }\n\n            public void Unload(object texture)\n            {\n                //什么都不做\n            }\n\n            private bool TryLoadTexture(string path, out Texture2D texture)\n            {\n                texture = null;\n                var frameNode = this.TopNode.FindNodeByPath(path);\n                frameNode = frameNode?.ResolveUol();\n\n                if (frameNode?.Value is Wz_Png)\n                {\n                    var linkNode = frameNode.GetLinkedSourceNode(PluginManager.FindWz);\n                    // workaround for KMST 1172, skeleton atlas and other obj may link to the same png node.\n                    // TODO: add options to always load into a standalone texture, disable dynamic atlas on this resource, \n                    texture = BaseLoader.Load<TextureAtlas>(linkNode).Texture;\n                    return true;\n                }\n                return false;\n            }\n\n            private Texture2D CreateEmptyTexture(string path, int width, int height) => this.BaseLoader.CreateEmptyTexture(path, width, height);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/SceneNode.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Collections.ObjectModel;\n\nnamespace WzComparerR2.MapRender\n{\n    public class SceneNode\n    {\n        public SceneNode()\n        {\n            this.Nodes = new SceneNodeCollection(this);\n        }\n\n        public SceneNode Parent { get; private set; }\n        public SceneNodeCollection Nodes { get; private set; }\n\n\n        public IEnumerable<SceneNode> Descendants()\n        {\n            foreach (var node in this.Nodes)\n            {\n                yield return node;\n                foreach (var subNode in node.Descendants())\n                {\n                    yield return subNode;\n                }\n            }\n        }\n\n        public class SceneNodeCollection : Collection<SceneNode>\n        {\n            public SceneNodeCollection(SceneNode owner)\n            {\n                this._owner = owner;\n            }\n\n            private SceneNode _owner;\n\n            public void AddRange(IEnumerable<SceneNode> nodes)\n            {\n                foreach (var node in nodes)\n                {\n                    this.Add(node);\n                }\n            }\n\n            protected override void InsertItem(int index, SceneNode item)\n            {\n                if (item.Parent != null)\n                    throw new ArgumentException(\"Item already has parent.\");\n                item.Parent = _owner;\n                base.InsertItem(index, item);\n            }\n\n            protected override void SetItem(int index, SceneNode item)\n            {\n                if (item.Parent != null)\n                    throw new ArgumentException(\"Item already has parent.\");\n                this[index].Parent = null;\n                item.Parent = _owner;\n                base.SetItem(index, item);\n            }\n\n            protected override void RemoveItem(int index)\n            {\n                this[index].Parent = null;\n                base.RemoveItem(index);\n            }\n\n            protected override void ClearItems()\n            {\n                foreach (var item in this)\n                    item.Parent = null;\n                base.ClearItems();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/TextMesh.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing WzComparerR2.Rendering;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender\n{\n    class TextMesh\n    {\n        public string Text { get; set; }\n        public IWcR2Font Font { get; set; }\n        public Color BackColor { get; set; }\n        public Color ForeColor { get; set; }\n        public Margins Padding { get; set; }\n        public Alignment Align { get; set; }\n    }\n\n    struct Margins\n    {\n        public Margins(int left, int right, int top, int bottom)\n        {\n            this.Left = left;\n            this.Right = right;\n            this.Top = top;\n            this.Bottom = bottom;\n        }\n\n        public int Left { get; set; }\n        public int Right { get; set; }\n        public int Top { get; set; }\n        public int Bottom { get; set; }\n    }\n\n    enum Alignment\n    {\n        Near = 0,\n        Center = 1,\n        Far = 2\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/TextureAtlas.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender\n{\n    public struct TextureAtlas\n    {\n        public TextureAtlas(Texture2D texture)\n        {\n            this.Texture = texture;\n            this.SrcRect = null;\n        }\n\n        public TextureAtlas(Texture2D texture, Rectangle rect)\n        {\n            this.Texture = texture;\n            this.SrcRect = rect;\n        }\n\n        public Texture2D Texture { get; set; }\n        public Rectangle? SrcRect { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/TextureLoader.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Specialized;\nusing System.Text;\nusing System.IO;\nusing System.IO.Compression;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.PluginBase;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\n\nnamespace WzComparerR2.MapRender\n{\n    public class TextureLoader\n    {\n        public TextureLoader(GraphicsDevice graphicsDevice)\n        {\n            this.GraphicsDevice = graphicsDevice;\n            this.loadedTexture = new Dictionary<string, TextureItem>(StringComparer.CurrentCultureIgnoreCase);\n        }\n\n        public GraphicsDevice GraphicsDevice { get; private set; }\n        public bool IsCounting { get; private set; }\n\n        private Dictionary<string, TextureItem> loadedTexture;\n\n#if MapRenderV1\n        public RenderFrame CreateFrame(Wz_Node frameNode)\n        {\n            string key = frameNode.FullPathToFile.Replace('\\\\', '/');\n            Wz_Png png = null;\n\n            string source = frameNode.FindNodeByPath(\"source\").GetValueEx<string>(null);\n            if (!string.IsNullOrEmpty(source))\n            {\n                Wz_Node sourceNode = PluginManager.FindWz(source);\n                if (sourceNode != null)\n                {\n                    png = sourceNode.Value as Wz_Png;\n                    key = sourceNode.FullPathToFile.Replace('\\\\', '/');\n                }\n            }\n            else\n            {\n                png = frameNode.Value as Wz_Png;\n            }\n\n            if (png == null)\n                return null;\n\n            RenderFrame frame = new RenderFrame();\n            Wz_Vector vec = frameNode.FindNodeByPath(\"origin\").GetValueEx<Wz_Vector>(null);\n            frame.Texture = GetTexture(png, key);\n            frame.Origin = (vec == null ? new Vector2() : new Vector2(vec.X, vec.Y));\n            frame.Delay = frameNode.FindNodeByPath(\"delay\").GetValueEx<int>(100);\n            frame.Z = frameNode.FindNodeByPath(\"z\").GetValueEx<int>(0);\n            frame.A0 = frameNode.FindNodeByPath(\"a0\").GetValueEx<int>(255);\n            frame.A1 = frameNode.FindNodeByPath(\"a1\").GetValueEx<int>(frame.A0);\n            return frame;\n        }\n#endif\n\n        private Texture2D GetTexture(Wz_Png png, string key)\n        {\n            TextureItem texItem;\n            if (!this.loadedTexture.TryGetValue(key, out texItem))\n            {\n                texItem = new TextureItem() { Texture = PngToTexture(this.GraphicsDevice, png) };\n                this.loadedTexture[key] = texItem;\n            }\n            else\n            {\n            }\n\n            if (IsCounting)\n            {\n                texItem.counter++;\n            }\n            return texItem.Texture;\n        }\n\n        public void BeginCounting()\n        {\n            if (!this.IsCounting)\n            {\n                this.IsCounting = true;\n                foreach (var kv in this.loadedTexture)\n                {\n                    kv.Value.counter = 0;\n                }\n            }\n        }\n\n        public void EndCounting()\n        {\n            if (this.IsCounting)\n            {\n                this.IsCounting = false;\n            }\n        }\n\n        public void ClearUnusedTexture()\n        {\n            List<string> removedKeys = new List<string>();\n            foreach (var kv in this.loadedTexture)\n            {\n                if (kv.Value.Texture != null && kv.Value.counter <= 0 && !kv.Value.Texture.IsDisposed)\n                {\n                    kv.Value.Texture.Dispose();\n                    removedKeys.Add(kv.Key);\n                }\n            }\n            foreach (string key in removedKeys)\n            {\n                this.loadedTexture.Remove(key);\n            }\n        }\n\n        public static Texture2D PngToTexture(GraphicsDevice device, Wz_Png png)\n        {\n            return WzComparerR2.Rendering.WzLibExtension.ToTexture(png, device);\n        }\n\n        private class TextureItem\n        {\n            public TextureItem()\n            {\n            }\n\n            public Texture2D Texture { get; set; }\n            public int counter { get; set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/TileMode.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.MapRender\n{\n    [Flags]\n    public enum TileMode\n    {\n        None = 0,\n        Horizontal = 1,\n        Vertical = 2,\n        BothTile = Horizontal | Vertical,\n        ScrollHorizontal = 4,\n        ScrollVertical = 8\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/ColorWConverter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface.Media;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public class ColorWConverter\n    {\n        public static bool TryParse(string s, out ColorW colorW)\n        {\n            if (s != null)\n            {\n                if (s.Length == 6) //RGB\n                {\n                    if (uint.TryParse(s, System.Globalization.NumberStyles.HexNumber,null, out uint rgb))\n                    {\n                        colorW = new ColorW((byte)(rgb >> 16), (byte)(rgb >> 8), (byte)(rgb), 0xff);\n                        return true;\n                    }\n                }\n                else if (s.Length == 8) //ARGB\n                {\n                    if (uint.TryParse(s, System.Globalization.NumberStyles.HexNumber, null, out uint argb))\n                    {\n                        colorW = new ColorW((byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb), (byte)(argb>>24));\n                        return true;\n                    }\n                }\n            }\n\n            colorW = ColorW.TransparentBlack;\n            return false;\n        }\n\n        public static string ToString(ColorW colorW)\n        {\n            return colorW.PackedValue.ToString(\"x8\");\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/HitMap.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class HitMap\n    {\n        public HitMap(int width, int height)\n        {\n            this.Width = width;\n            this.Height = height;\n            this.bitArray = new byte[this.Stride * this.Height];\n        }\n\n        public HitMap(bool defaultHit)\n        {\n            this.DefaultHit = defaultHit;\n        }\n\n        public int Width { get; private set; }\n        public int Height { get; private set; }\n        public bool DefaultHit { get; set; }\n\n        public int Stride\n        {\n            get\n            {\n                return (this.Width + 7) / 8;\n            }\n        }\n\n        private byte[] bitArray;\n\n        public bool this[int x, int y]\n        {\n            get\n            {\n                if (x < 0 || x >= this.Width || y < 0 || y >= this.Height || this.bitArray == null)\n                {\n                    return this.DefaultHit;\n                }\n                return bitArray[this.Stride * y + x / 8] >> (x % 8) != 0;\n            }\n            set\n            {\n                if (x < 0 || x >= this.Width || y < 0 || y >= this.Height || this.bitArray == null)\n                {\n                    return;\n                }\n                int i = this.Stride * y + x / 8;\n                if (value)\n                {\n                    bitArray[i] |= (byte)(1 << (x % 8));\n                }\n                else\n                {\n                    bitArray[i] &= (byte)~(1 << (x % 8));\n                }\n            }\n        }\n\n        public void SetRow(int y, bool[] isHit)\n        {\n            int index = 0;\n            for (int i = 0; i < this.Stride; i++)\n            {\n                int val = 0;\n                for (int j = 0; j < 8; j++)\n                {\n                    if (index < isHit.Length)\n                    {\n                        if (isHit[index])\n                        {\n                            val |= 1 << j;\n                        }\n                        index++;\n                    }\n                }\n                this.bitArray[this.Stride * y + i] = (byte)val;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/ITooltipTarget.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public interface ITooltipTarget\n    {\n        object GetTooltipTarget(PointF mouseLocation);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/LCRBrush.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Renderers;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class LCRBrush : Brush\n    {\n        public LCRBrush()\n        {\n        }\n\n        public INinePatchResource<TextureBase> Resource\n        {\n            get { return (INinePatchResource<TextureBase>)GetValue(ResourceProperty); }\n            set { SetValue(ResourceProperty, value); }\n        }\n\n        public override void Draw(TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, Size renderSize, float opacity)\n        {\n            var blocks = UIGraphics.LayoutLCR(this.Resource, new Point((int)renderSize.Width, (int)renderSize.Height));\n\n            foreach (var block in blocks)\n            {\n                if (block.Texture != null && block.Rectangle.Width > 0 && block.Rectangle.Height > 0)\n                {\n                    PointF pos = new PointF(block.Rectangle.X + position.X, block.Rectangle.Y + position.Y);\n                    Size size = new Size(block.Rectangle.Width, block.Rectangle.Height);\n                    ColorW color = new ColorW(1f, 1f, 1f, this.Opacity);\n                    renderer.Draw(block.Texture, pos, size, color, false);\n                }\n            }\n        }\n\n        public override void DrawGeometry(GeometryBuffer buffer, TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, float opacity)\n        {\n            throw new NotImplementedException();\n        }\n\n        public static readonly DependencyProperty ResourceProperty = DependencyProperty.Register(\"Resource\", typeof(INinePatchResource<TextureBase>), typeof(LCRBrush), new FrameworkPropertyMetadata(null));\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/MapRenderButtonStyle.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Themes;\nusing EmptyKeys.UserInterface.Media.Imaging;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    static class MapRenderButtonStyle\n    {\n        public static Style CreateMapRenderButtonStyle()\n        {\n            var style = ImageButtonStyle.CreateImageButtonStyle();\n\n            //btnOK\n            var trigger = new Trigger()\n            {\n                Property = UIElement.NameProperty,\n                Value = \"OK\"\n            };\n            trigger.Setters.AddRange(GetMapRenderButtonSetters(\"OK\"));\n            style.Triggers.Add(trigger);\n            //btnYes\n            trigger = new Trigger()\n            {\n                Property = UIElement.NameProperty,\n                Value = \"Yes\"\n            };\n            trigger.Setters.AddRange(GetMapRenderButtonSetters(\"Yes\"));\n            style.Triggers.Add(trigger);\n            //btnNo\n            trigger = new Trigger()\n            {\n                Property = UIElement.NameProperty,\n                Value = \"No\"\n            };\n            trigger.Setters.AddRange(GetMapRenderButtonSetters(\"No\"));\n            style.Triggers.Add(trigger);\n            //btnCancel\n            trigger = new Trigger()\n            {\n                Property = UIElement.NameProperty,\n                Value = \"Cancel\"\n            };\n            trigger.Setters.AddRange(GetMapRenderButtonSetters(\"Cancel\"));\n            style.Triggers.Add(trigger);\n            //btnClose\n            trigger = new Trigger()\n            {\n                Property = UIElement.NameProperty,\n                Value = \"Close\"\n            };\n            trigger.Setters.AddRange(GetMapRenderButtonSetters(\"Close\"));\n            style.Triggers.Add(trigger);\n\n            return style;\n        }\n\n        public static Setter[] GetMapRenderButtonSetters(string name)\n        {\n            switch (name)\n            {\n                case \"OK\":\n                case \"Yes\":\n                    return CreateButtonSetters(42, 16,\n                        nameof(MRes.Basic_img_BtOK4_normal_0),\n                        nameof(MRes.Basic_img_BtOK4_mouseOver_0),\n                        nameof(MRes.Basic_img_BtOK4_pressed_0),\n                        nameof(MRes.Basic_img_BtOK4_disabled_0));\n\n                case \"No\":\n                    return CreateButtonSetters(42, 16,\n                        nameof(MRes.Basic_img_BtNo3_normal_0),\n                        nameof(MRes.Basic_img_BtNo3_mouseOver_0),\n                        nameof(MRes.Basic_img_BtNo3_pressed_0),\n                        nameof(MRes.Basic_img_BtNo3_disabled_0));\n\n                case \"Cancel\":\n                    return CreateButtonSetters(42, 16,\n                        nameof(MRes.Basic_img_BtCancel4_normal_0),\n                        nameof(MRes.Basic_img_BtCancel4_mouseOver_0),\n                        nameof(MRes.Basic_img_BtCancel4_pressed_0),\n                        nameof(MRes.Basic_img_BtCancel4_disabled_0));\n\n                case \"Close\":\n                    return CreateButtonSetters(13, 13,\n                         nameof(MRes.Basic_img_BtClose3_normal_0),\n                         nameof(MRes.Basic_img_BtClose3_mouseOver_0),\n                         nameof(MRes.Basic_img_BtClose3_pressed_0),\n                         nameof(MRes.Basic_img_BtClose3_disabled_0));\n\n                default:\n                    return null;\n            }\n        }\n\n        private static Setter[] CreateButtonSetters(float width, float height,\n            string normalAsset, string mouseOverAsset, string pressedAsset, string disabledAsset)\n        {\n            return new Setter[]\n            {\n                new Setter(UIElement.WidthProperty, width),\n                new Setter(UIElement.HeightProperty, height),\n                new Setter(ImageButton.ImageNormalProperty, new BitmapImage(){ TextureAsset=normalAsset }),\n                new Setter(ImageButton.ImageHoverProperty, new BitmapImage(){ TextureAsset=mouseOverAsset }),\n                new Setter(ImageButton.ImagePressedProperty, new BitmapImage(){ TextureAsset=pressedAsset }),\n                new Setter(ImageButton.ImageDisabledProperty, new BitmapImage(){ TextureAsset=disabledAsset }),\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/MapRenderResourceKey.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public enum MapRenderResourceKey\n    {\n        Invalid = 0,\n        TooltipBrush = 1,\n        FontList,\n        DefaultFontFamily,\n        DefaultFontSize,\n        MessageBoxBackgroundBrush,\n        MapRenderButtonStyle,\n        TextBoxExStyle,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/MapRenderUIRoot.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Reflection;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Input;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Media.Effects;\nusing EmptyKeys.UserInterface.Themes;\nusing EmptyKeys.UserInterface.Data;\nusing Res = CharaSimResource.Resource;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class MapRenderUIRoot : UIRoot\n    {\n        public MapRenderUIRoot() : base()\n        {\n            InitGlobalResource();\n            InitializeComponents();\n\n            //获取root容器\n            var propInfo = typeof(Control).GetProperty(\"VisualTree\", BindingFlags.NonPublic | BindingFlags.Instance);\n            var border = propInfo?.GetValue(this) as Border;\n            this.ContentControl = border?.Child as ContentPresenter;\n        }\n\n        public event EventHandler InputUpdated;\n        public ContentPresenter ContentControl { get; private set; }\n        public UIMirrorFrame MirrorFrame { get; private set; }\n        public UIMinimap2 Minimap { get; private set; }\n        public UIWorldMap WorldMap { get; private set; }\n        public UITopBar TopBar { get; private set; }\n        public UIChatBox ChatBox { get; private set; }\n        public UITeleport Teleport { get; private set; }\n\n        private void InitializeComponents()\n        {\n            Style style = RootStyle.CreateRootStyle();\n            style.TargetType = this.GetType();\n            this.Style = style;\n            this.Background = null;\n\n            var mirrorFrame = new UIMirrorFrame();\n            mirrorFrame.Parent = this;\n            mirrorFrame.IsOnTop = false;\n            mirrorFrame.Visibility = Visibility.Collapsed;\n            mirrorFrame.SetBinding(UIMirrorFrame.WidthProperty, new Binding(UIRoot.WidthProperty) { Source = this });\n            mirrorFrame.SetBinding(UIMirrorFrame.HeightProperty, new Binding(UIRoot.HeightProperty) { Source = this });\n            this.MirrorFrame = mirrorFrame;\n            this.Windows.Add(mirrorFrame);\n\n            var minimap = new UIMinimap2();\n            minimap.Parent = this;\n            this.Minimap = minimap;\n            this.Windows.Add(minimap);\n\n            var worldmap = new UIWorldMap();\n            worldmap.Parent = this;\n            worldmap.Hide();\n            worldmap.Visible += Worldmap_Visible;\n            this.WorldMap = worldmap;\n            this.Windows.Add(worldmap);\n\n            var topBar = new UITopBar();\n            topBar.Parent = this;\n            topBar.IsOnTop = false;\n            topBar.SetBinding(UITopBar.WidthProperty, new Binding(UIRoot.WidthProperty) { Source = this });\n            topBar.SetBinding(UITopBar.PaddingLeftProperty, new Binding(Window.WidthProperty) { Source = minimap });\n            topBar.SetBinding(UITopBar.IsShortModeProperty, new Binding(Window.VisibilityProperty) { Source = minimap, Converter = UIHelper.CreateConverter((Visibility o) => o == Visibility.Visible) });\n            this.TopBar = topBar;\n            this.Windows.Add(topBar);\n\n            var chatBox = new UIChatBox();\n            chatBox.Parent = this;\n            chatBox.SetBinding(UIChatBox.TopProperty, new Binding(HeightProperty) { Source = this, Converter = UIHelper.CreateConverter((float height) => height - chatBox.Height) });\n            this.ChatBox = chatBox;\n            this.Windows.Add(chatBox);\n\n            var teleport = new UITeleport();\n            teleport.Parent = this;\n            teleport.Hide();\n            teleport.Visible += Teleport_Visible;\n            this.Teleport = teleport;\n            this.Windows.Add(teleport);\n\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtOK4_normal_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtOK4_mouseOver_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtOK4_pressed_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtOK4_disabled_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtNo3_normal_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtNo3_mouseOver_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtNo3_pressed_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtNo3_disabled_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtCancel4_normal_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtCancel4_mouseOver_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtCancel4_pressed_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtCancel4_disabled_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtClose3_normal_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtClose3_mouseOver_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtClose3_pressed_0));\n            ImageManager.Instance.AddImage(nameof(MRes.Basic_img_BtClose3_disabled_0));\n            this.Resources[CommonResourceKeys.MessageBoxWindowStyleKey] = MessageBoxStyle.CreateMessageBoxStyle();\n        }\n\n        private void Worldmap_Visible(object sender, RoutedEventArgs e)\n        {\n            UIWorldMap wnd = sender as UIWorldMap;\n            wnd.Left = (int)Math.Max(0, (this.Width - wnd.Width) / 2);\n            wnd.Top = (int)Math.Max(0, (this.Height - wnd.Height) / 2);\n\n            if (!wnd.IsDataLoaded)\n            {\n                wnd.LoadWzResource();\n            }\n            else\n            {\n                wnd.JumpToCurrentMap();\n            }\n        }\n\n        private void Teleport_Visible(object sender, RoutedEventArgs e)\n        {\n            UITeleport wnd = sender as UITeleport;\n            wnd.Left = (int)Math.Max(0, (this.Width - wnd.Width) / 2);\n            wnd.Top = (int)Math.Max(0, (this.Height - wnd.Height) / 2);\n        }\n\n        public void LoadContent(object contentManager)\n        {\n            //UI资源\n            FontManager.DefaultFontFamily = (FontFamily)this.FindResource(MapRenderResourceKey.DefaultFontFamily);\n            FontManager.DefaultFontSize = (float)this.FindResource(MapRenderResourceKey.DefaultFontSize);\n            FontManager.Instance.AddFont(FontManager.DefaultFontFamily.Source, FontManager.DefaultFontSize, FontStyle.Regular);\n            FontManager.Instance.LoadFonts(contentManager);\n            ImageManager.Instance.LoadImages(contentManager);\n            SoundManager.Instance.LoadSounds(contentManager);\n            EffectManager.Instance.LoadEffects(contentManager);\n            FontManager.DefaultFont = FontManager.Instance.GetFont(FontManager.DefaultFontFamily.Source, FontManager.DefaultFontSize, FontStyle.Regular);\n\n            //其他资源\n            this.LoadResource();\n            this.Minimap.MapAreaControl.LoadWzResource();\n        }\n\n        private void InitGlobalResource()\n        {\n            //初始化字体\n            var fontList = MapRenderFonts.DefaultFonts;\n            var config = MapRender.Config.MapRenderConfig.Default;\n            var resDict = ResourceDictionary.DefaultDictionary;\n\n            var fontIndex = config.DefaultFontIndex;\n            if (fontIndex < 0 || fontIndex >= fontList.Count)\n            {\n                fontIndex = 0;\n            }\n            \n            resDict[MapRenderResourceKey.FontList] = fontList;\n            resDict[MapRenderResourceKey.DefaultFontFamily] = new FontFamily(fontList[fontIndex]);\n            resDict[MapRenderResourceKey.DefaultFontSize] = 12f;\n\n            //初始化style\n            resDict[MapRenderResourceKey.MapRenderButtonStyle] = MapRenderButtonStyle.CreateMapRenderButtonStyle();\n            resDict[MapRenderResourceKey.TextBoxExStyle] = TextBoxEx.CreateStyle();\n        }\n\n        private void LoadResource()\n        {\n            var assetManager = Engine.Instance.AssetManager;\n\n            var tooltipBrush = new NinePatchBrush()\n            {\n                Resource = new EKNineFormResource()\n                {\n                    NW = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_nw)),\n                    N = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_n)),\n                    NE = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_ne)),\n                    W = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_w)),\n                    C = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_c)),\n                    E = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_e)),\n                    SW = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_sw)),\n                    S = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_s)),\n                    SE = assetManager.LoadTexture(null, nameof(Res.UIToolTip_img_Item_Frame2_se)),\n                }\n            };\n\n            var msgBoxBackgroundBrush = new MessageBoxBackgroundBrush()\n            {\n                Resource = new MessageBoxBackgroundResource()\n                {\n                    T = assetManager.LoadTexture(null, nameof(MRes.Basic_img_Notice6_t)),\n                    C = assetManager.LoadTexture(null, nameof(MRes.Basic_img_Notice6_c)),\n                    C_Box = assetManager.LoadTexture(null, nameof(MRes.Basic_img_Notice6_c_box)),\n                    Box = assetManager.LoadTexture(null, nameof(MRes.Basic_img_Notice6_box)),\n                    S_Box = assetManager.LoadTexture(null, nameof(MRes.Basic_img_Notice6_s_box)),\n                    S = assetManager.LoadTexture(null, nameof(MRes.Basic_img_Notice6_s)),\n                }\n            };\n\n            this.Resources[MapRenderResourceKey.TooltipBrush] = tooltipBrush;\n            this.Resources[MapRenderResourceKey.MessageBoxBackgroundBrush] = msgBoxBackgroundBrush;\n        }\n\n        public void UnloadContents()\n        {\n            FontManager.Instance.ClearCache();\n            ImageManager.Instance.ClearCache();\n            SoundManager.Instance.ClearCache();\n            EffectManager.Instance.ClearCache();\n            FontManager.DefaultFont = null;\n        }\n\n        protected override void OnKeyDown(KeyEventArgs e)\n        {\n            var listener = e.Source as KeyInputListener;\n\n            if (listener != null)\n            {\n                foreach (var binding in this.InputBindings)\n                {\n                    if (binding.IsRepeatEnabled && binding.Gesture.Matches(e))\n                    {\n                        listener.EnableRepeat();\n                        break;\n                    }\n                }\n            }\n            base.OnKeyDown(e);\n        }\n\n        public new void UpdateInput(double elapsedGameTime)\n        {\n            try\n            {\n                base.UpdateInput(elapsedGameTime);\n            }\n            catch(Exception ex)\n            {\n            }\n            this.OnInputUpdated(EventArgs.Empty);\n        }\n\n        protected virtual void OnInputUpdated(EventArgs e)\n        {\n            this.InputUpdated?.Invoke(this, e);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/MessageBoxBackgroundBrush.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Renderers;\nusing Microsoft.Xna.Framework;\nusing TextureBlock = WzComparerR2.MapRender.UI.UIGraphics.RenderBlock<EmptyKeys.UserInterface.Media.TextureBase>;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class MessageBoxBackgroundBrush : Brush\n    {\n        public static readonly DependencyProperty ResourceProperty = DependencyProperty.Register(\"Resource\", typeof(MessageBoxBackgroundResource), typeof(MessageBoxBackgroundBrush), new FrameworkPropertyMetadata(null));\n\n        public MessageBoxBackgroundBrush()\n        {\n\n        }\n\n        public MessageBoxBackgroundResource Resource\n        {\n            get { return (MessageBoxBackgroundResource)this.GetValue(ResourceProperty); }\n            set { this.SetValue(ResourceProperty, value); }\n        }\n\n        public override void Draw(TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, Size renderSize, float opacity)\n        {\n            if (this.Resource == null)\n            {\n                return;\n            }\n\n            IList<TextureBlock> blocks;\n            var boxOffset = this.GetBoxOffset();\n\n            if (boxOffset >= 0)\n            {\n                blocks = this.Layout(this.Resource, new Point((int)renderSize.Width, (int)renderSize.Height), (int)boxOffset);\n            }\n            else //fallback\n            {\n                blocks = UIGraphics.LayoutTCB(this.Resource, new Point((int)renderSize.Width, (int)renderSize.Height));\n            }\n\n            foreach (var block in blocks)\n            {\n                if (block.Texture != null && block.Rectangle.Width > 0 && block.Rectangle.Height > 0)\n                {\n                    PointF pos = new PointF(block.Rectangle.X + position.X, block.Rectangle.Y + position.Y);\n                    Size size = new Size(block.Rectangle.Width, block.Rectangle.Height);\n                    ColorW color = new ColorW(1f, 1f, 1f, this.Opacity);\n                    renderer.Draw(block.Texture, pos, size, color, false);\n                }\n            }\n        }\n\n        private float GetBoxOffset()\n        {\n            Grid grid = this.Parent as Grid;\n            if (grid != null)\n            {\n                ContentPresenter pnlContent = VisualTreeHelper.Instance.FindElementByName(grid, \"PART_WindowContent\") as ContentPresenter;\n                if (pnlContent != null)\n                {\n                    var grid2 = pnlContent.Content as Grid;\n                    if (grid2 != null)\n                    {\n                        StackPanel pnlButtons = grid2.Children.OfType<StackPanel>().FirstOrDefault();\n                        if (pnlButtons != null)\n                        {\n                            return pnlButtons.VisualPosition.Y - grid.VisualPosition.Y;\n                        }\n                    }\n                }\n            }\n            return float.NaN;\n        }\n\n        private IList<TextureBlock> Layout(MessageBoxBackgroundResource res, Point size, int boxOffset)\n        {\n            var blocks = new List<TextureBlock>(6);\n            Point t = res.GetSize(res.T);\n            Point c = res.GetSize(res.C);\n            Point cBox = res.GetSize(res.C_Box);\n            Point box = res.GetSize(res.Box);\n            Point sBox = res.GetSize(res.S_Box);\n\n            //计算框线\n            int[] y = new int[6] {\n                0,\n                t.Y,\n                boxOffset - cBox.Y,\n                boxOffset,\n                size.Y - sBox.Y,\n                size.Y\n            };\n\n            //绘制上\n            blocks.Add(new TextureBlock(res.T, new Rectangle(0, y[0], size.X, y[1] - y[0])));\n            //绘制中\n            blocks.Add(new TextureBlock(res.C, new Rectangle(0, y[1], size.X, y[2] - y[1])));\n            //绘制box\n            blocks.Add(new TextureBlock(res.C_Box, new Rectangle(0, y[2], size.X, y[3] - y[2])));\n            blocks.Add(new TextureBlock(res.Box, new Rectangle(0, y[3], size.X, y[4] - y[3])));\n            blocks.Add(new TextureBlock(res.S_Box, new Rectangle(0, y[4], size.X, y[5] - y[4])));\n\n            return blocks;\n        }\n\n        public override void DrawGeometry(GeometryBuffer buffer, TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, float opacity)\n        {\n            throw new NotImplementedException();\n        }\n    }\n\n    class MessageBoxBackgroundResource : INinePatchResource<TextureBase>\n    {\n        public TextureBase T { get; set; }\n        public TextureBase C { get; set; }\n        public TextureBase C_Box { get; set; }\n        public TextureBase Box { get; set; }\n        public TextureBase S_Box { get; set; }\n        public TextureBase S { get; set; }\n\n        TextureBase INinePatchResource<TextureBase>.NW { get { return null; } }\n        TextureBase INinePatchResource<TextureBase>.N { get { return this.T; } }\n        TextureBase INinePatchResource<TextureBase>.NE { get { return null; } }\n        TextureBase INinePatchResource<TextureBase>.W { get { return null; } }\n        TextureBase INinePatchResource<TextureBase>.C { get { return this.C; } }\n        TextureBase INinePatchResource<TextureBase>.E { get { return null; } }\n        TextureBase INinePatchResource<TextureBase>.SW { get { return null; } }\n        TextureBase INinePatchResource<TextureBase>.S { get { return this.S; } }\n        TextureBase INinePatchResource<TextureBase>.SE { get { return null; } }\n\n        public Point GetSize(TextureBase texture)\n        {\n            return new Point(texture.Width, texture.Height);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/MessageBoxStyle.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Themes;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    static class MessageBoxStyle\n    {\n        public static Style CreateMessageBoxStyle()\n        {\n            Style style = new Style(typeof(Window));\n            style.Setters.Add(new Setter(Control.TemplateProperty, CreateMessageBoxControlTemplate()));\n            return style;\n        }\n\n        public static ControlTemplate CreateMessageBoxControlTemplate()\n        {\n            ControlTemplate template = new ControlTemplate(typeof(Window), CreateMessageBoxControlFunc);\n            return template;\n        }\n\n        private static UIElement CreateMessageBoxControlFunc(UIElement parent)\n        {\n            Grid screenBg = new Grid();\n            screenBg.HorizontalAlignment = HorizontalAlignment.Center;\n            screenBg.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });\n            screenBg.SetBinding(Control.BackgroundProperty, new Binding(Control.BackgroundProperty) { Source = parent });\n            screenBg.Parent = parent;\n            screenBg.Foreground = EmptyKeys.UserInterface.Media.Brushes.White;\n\n            Grid grid = new Grid();\n            grid.Width = 260;\n            grid.SetResourceReference(Control.BackgroundProperty, MapRenderResourceKey.MessageBoxBackgroundBrush);\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n            grid.RowDefinitions.Add(new RowDefinition());\n            grid.RowDefinitions.Add(new RowDefinition());\n            screenBg.Children.Add(grid);\n            Grid.SetColumn(grid, 0);\n\n            Border border = new Border();\n            grid.Children.Add(border);\n            Grid.SetRow(border, 0);\n\n            ContentPresenter pnlTitle = CommonHelpers.CreateContentPresenter(parent, \"Title\");\n            pnlTitle.Height = 26;\n            pnlTitle.Margin = new Thickness(0, 4, 0, 0);\n            pnlTitle.HorizontalAlignment = HorizontalAlignment.Center;\n            pnlTitle.VerticalAlignment = VerticalAlignment.Center;\n            pnlTitle.IsHitTestVisible = false;\n            pnlTitle.Name = \"PART_WindowTitle\";\n            border.Child = pnlTitle;\n\n            ContentPresenter pnlContent = CommonHelpers.CreateContentPresenter(parent, \"Content\");\n            pnlContent.Margin = new Thickness(20, 8, 20, 0);\n            pnlContent.Name = \"PART_WindowContent\";\n            grid.Children.Add(pnlContent);\n            Grid.SetRow(pnlContent, 1);\n\n            Window msgBox = parent as Window;\n            if (msgBox != null)\n            {\n                msgBox.Visible += MsgBox_Visible;\n                msgBox.SizeChanged += MsgBox_SizeChanged;\n                var style = MapRenderButtonStyle.CreateMapRenderButtonStyle();\n                style.TargetType = typeof(Button);\n                msgBox.Resources[typeof(Button)] = style;\n            }\n            return screenBg;\n        }\n\n        private static void MsgBox_Visible(object sender, RoutedEventArgs e)\n        {\n            var wnd = sender as Window;\n            if (wnd != null)\n            {\n                var grid = wnd.Content as Grid;\n                if (grid != null)\n                {\n                    var textBlock = grid.Children.OfType<TextBlock>().FirstOrDefault();\n                    if (textBlock != null)\n                    {\n                        textBlock.Margin = new Thickness(0, 0, 0, 8);\n                        if (textBlock.Text != null && textBlock.Font != null)\n                        {\n                            textBlock.TextWrapping = TextWrapping.NoWrap;\n                            var size = textBlock.Font.MeasureString(textBlock.Text, new Size(220, 0));\n                            if (size.Height > textBlock.ActualHeight)\n                            {\n                                textBlock.Height = size.Height;\n                            }\n                        }\n                    }\n                    var stackPanel = grid.Children.OfType<StackPanel>().FirstOrDefault();\n                    if (stackPanel != null)\n                    {\n                        stackPanel.HorizontalAlignment = HorizontalAlignment.Right;\n                        foreach (var btn in stackPanel.Children.OfType<Button>())\n                        {\n                            btn.Margin = new Thickness(2, 0, 0, 6);\n                        }\n                    }\n                }\n                SetWindowAlignCenter(wnd);\n            }\n        }\n\n        private static void TextBlock_SizeChanged(object sender, RoutedEventArgs e)\n        {\n            var tb = sender as TextBlock;\n            if (tb != null && tb.Text != null && tb.ActualWidth > 0)\n            {\n                var size = tb.Font.MeasureString(tb.Text, new Size(tb.ActualWidth, tb.ActualHeight));\n                if (size.Height > tb.ActualHeight)\n                {\n                    tb.Height = size.Height;\n                }\n            }\n        }\n\n        private static void MsgBox_SizeChanged(object sender, RoutedEventArgs e)\n        {\n            SetWindowAlignCenter(sender as Window);\n        }\n\n        private static void SetWindowAlignCenter(Window wnd)\n        {\n            if (wnd != null && wnd.DesiredSize.Height > 0)\n            {\n                wnd.Top = (int)(Engine.Instance.Renderer.GetViewport().Height - wnd.DesiredSize.Height) / 2;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/NineFormResource.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing EKTexture = EmptyKeys.UserInterface.Media.TextureBase;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public class NineFormResource : INinePatchResource<Texture2D>\n    {\n        /// <summary>\n        /// 左上\n        /// </summary>\n        public Texture2D NW { get; set; }\n        /// <summary>\n        /// 上。\n        /// </summary>\n        public Texture2D N { get; set; }\n        /// <summary>\n        /// 右上。\n        /// </summary>\n        public Texture2D NE { get; set; }\n\n        /// <summary>\n        /// 左。\n        /// </summary>\n        public Texture2D W { get; set; }\n        /// <summary>\n        /// 中。\n        /// </summary>\n        public Texture2D C { get; set; }\n        /// <summary>\n        /// 右。\n        /// </summary>\n        public Texture2D E { get; set; }\n\n        /// <summary>\n        /// 左下。\n        /// </summary>\n        public Texture2D SW { get; set; }\n        /// <summary>\n        /// 下。\n        /// </summary>\n        public Texture2D S { get; set; }\n        /// <summary>\n        /// 右下。\n        /// </summary>\n        public Texture2D SE { get; set; }\n\n        public bool IsFitSize\n        {\n            get\n            {\n                return this.NW.Height == this.NE.Height\n                    && this.NE.Width == this.SE.Width\n                    && this.SE.Height == this.SW.Height\n                    && this.SW.Width == this.NW.Width;\n            }\n        }\n\n        public Point GetSize(Texture2D texture)\n        {\n            return texture == null ? Point.Zero : new Point(texture.Width, texture.Height);\n        }\n    }\n\n    public class EKNineFormResource : INinePatchResource<EKTexture>\n    {\n        /// <summary>\n        /// 左上\n        /// </summary>\n        public EKTexture NW { get; set; }\n        /// <summary>\n        /// 上。\n        /// </summary>\n        public EKTexture N { get; set; }\n        /// <summary>\n        /// 右上。\n        /// </summary>\n        public EKTexture NE { get; set; }\n\n        /// <summary>\n        /// 左。\n        /// </summary>\n        public EKTexture W { get; set; }\n        /// <summary>\n        /// 中。\n        /// </summary>\n        public EKTexture C { get; set; }\n        /// <summary>\n        /// 右。\n        /// </summary>\n        public EKTexture E { get; set; }\n\n        /// <summary>\n        /// 左下。\n        /// </summary>\n        public EKTexture SW { get; set; }\n        /// <summary>\n        /// 下。\n        /// </summary>\n        public EKTexture S { get; set; }\n        /// <summary>\n        /// 右下。\n        /// </summary>\n        public EKTexture SE { get; set; }\n\n        public bool IsFitSize\n        {\n            get\n            {\n                return this.NW.Height == this.NE.Height\n                    && this.NE.Width == this.SE.Width\n                    && this.SE.Height == this.SW.Height\n                    && this.SW.Width == this.NW.Width;\n            }\n        }\n\n        public Point GetSize(EKTexture texture)\n        {\n            return texture == null ? Point.Zero : new Point(texture.Width, texture.Height);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/NinePatchBrush.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Renderers;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class NinePatchBrush : Brush\n    {\n        public NinePatchBrush()\n        {\n        }\n\n        public INinePatchResource<TextureBase> Resource\n        {\n            get { return (INinePatchResource<TextureBase>)GetValue(ResourceProperty); }\n            set { SetValue(ResourceProperty, value); }\n        }\n\n        public override void Draw(TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, Size renderSize, float opacity)\n        {\n            var blocks = UIGraphics.LayoutNinePatch(this.Resource, new Point((int)renderSize.Width, (int)renderSize.Height));\n\n            foreach (var block in blocks)\n            {\n                if (block.Texture != null && block.Rectangle.Width > 0 && block.Rectangle.Height > 0)\n                {\n                    PointF pos = new PointF(block.Rectangle.X + position.X, block.Rectangle.Y + position.Y);\n                    Size size = new Size(block.Rectangle.Width, block.Rectangle.Height);\n                    ColorW color = new ColorW(1f, 1f, 1f, this.Opacity);\n                    renderer.Draw(block.Texture, pos, size, color, false);\n                }\n            }\n        }\n\n        public override void DrawGeometry(GeometryBuffer buffer, TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, float opacity)\n        {\n            throw new NotImplementedException();\n        }\n\n        public static readonly DependencyProperty ResourceProperty = DependencyProperty.Register(\"Resource\", typeof(INinePatchResource<TextureBase>), typeof(NinePatchBrush), new FrameworkPropertyMetadata(null));\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/TCBBrush.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Renderers;\nusing Microsoft.Xna.Framework;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class TCBBrush : Brush\n    {\n        public TCBBrush()\n        {\n        }\n\n        public INinePatchResource<TextureBase> Resource\n        {\n            get { return (INinePatchResource<TextureBase>)GetValue(ResourceProperty); }\n            set { SetValue(ResourceProperty, value); }\n        }\n\n        public override void Draw(TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, Size renderSize, float opacity)\n        {\n            var blocks = UIGraphics.LayoutTCB(this.Resource, new Point((int)renderSize.Width, (int)renderSize.Height));\n\n            foreach (var block in blocks)\n            {\n                if (block.Texture != null && block.Rectangle.Width > 0 && block.Rectangle.Height > 0)\n                {\n                    PointF pos = new PointF(block.Rectangle.X + position.X, block.Rectangle.Y + position.Y);\n                    Size size = new Size(block.Rectangle.Width, block.Rectangle.Height);\n                    ColorW color = new ColorW(1f, 1f, 1f, this.Opacity);\n                    renderer.Draw(block.Texture, pos, size, color, false);\n                }\n            }\n        }\n\n        public override void DrawGeometry(GeometryBuffer buffer, TextureBase texture, Renderer renderer, double elapsedGameTime, PointF position, float opacity)\n        {\n            throw new NotImplementedException();\n        }\n\n        public static readonly DependencyProperty ResourceProperty = DependencyProperty.Register(\"Resource\", typeof(INinePatchResource<TextureBase>), typeof(TCBBrush), new FrameworkPropertyMetadata(null));\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/TextBoxEx.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Controls.Primitives;\nusing EmptyKeys.UserInterface.Input;\nusing EmptyKeys.UserInterface.Themes;\nusing EmptyKeys.UserInterface.Mvvm;\nusing JLChnToZ.IMEHelper;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class TextBoxEx : TextBox\n    {\n        public static readonly DependencyProperty IMEEnabledProperty = DependencyProperty.Register(\"IMEEnabled\", typeof(bool), typeof(TextBoxEx), new FrameworkPropertyMetadata(false));\n        public TextBoxEx()\n        {\n            this.SetResourceReference(StyleProperty, MapRenderResourceKey.TextBoxExStyle);\n            this.textEditor = new TextEditorProxy(this);\n            this.undoManager = new UndoManagerProxy(this);\n\n            this._ScrollViewerGet = (Func<ScrollViewer>)typeof(TextBoxBase)\n                    .GetProperty(\"ScrollViewer\", BindingFlags.Instance | BindingFlags.NonPublic)\n                    .GetGetMethod(true)\n                    .CreateDelegate(typeof(Func<ScrollViewer>), this);\n        }\n\n        public event EventHandler<TextEventArgs> TextSubmit;\n\n        private TextEditorProxy textEditor;\n        private UndoManagerProxy undoManager;\n        private Func<ScrollViewer> _ScrollViewerGet;\n        \n        //IMEhandler\n        private IMEHandler imeHandler;\n        private DateTime lastCompositionTime;\n        private bool prevHasComposition;\n        private readonly TimeSpan imeEndThreshold = TimeSpan.FromSeconds(0.033);\n\n        public bool IMEEnabled\n        {\n            get { return (bool)this.GetValue(IMEEnabledProperty); }\n            set { this.SetValue(IMEEnabledProperty, value); }\n        }\n\n        private ScrollViewer ScrollViewer\n        {\n            get { return this._ScrollViewerGet(); }\n        }\n\n        protected override void OnKeyDown(KeyEventArgs e)\n        {\n            var imeHandler = this.GetIMEService();\n            bool imeEnabled = imeHandler != null && imeHandler.Enabled;\n            bool hasComposition = imeEnabled && !string.IsNullOrEmpty(imeHandler.Composition);\n            bool isCompositionEnd = (DateTime.Now - lastCompositionTime) >= imeEndThreshold;\n            /*  \n             *  keys  | noIME  | IME_noComp | IME_comp\n             *  --------------------------------------\n             *  chars | pass   | handle     | handle\n             *  cmds  | pass   | pass       | handle\n             *  enter | submit | submit     | handle\n             */\n\n            switch (e.Key)\n            {\n                case KeyCode.Enter:\n                    e.Handled = true;\n                    if (!imeEnabled || !hasComposition)\n                    {\n                        if (isCompositionEnd)\n                        {\n                            this.Submit();\n                        }\n                    }\n                    break;\n\n                case KeyCode.Y:\n                case KeyCode.Z:\n                case KeyCode.X:\n                case KeyCode.C:\n                case KeyCode.V:\n                case KeyCode.A:\n                    if (Keyboard.IsControlPressed)\n                    {\n                        if (hasComposition)\n                        {\n                            e.Handled = true;\n                        }\n                    }\n                    else\n                    {\n                        goto default;\n                    }\n                    break;\n\n                case KeyCode.Left:\n                case KeyCode.Up:\n                case KeyCode.Right:\n                case KeyCode.Down:\n                case KeyCode.Home:\n                case KeyCode.End:\n                case KeyCode.Delete:\n                    if (hasComposition)\n                    {\n                        e.Handled = true;\n                    }\n                    break;\n\n                case KeyCode.Back:\n                    if (imeEnabled)\n                    {\n                        if (hasComposition || !isCompositionEnd)\n                        {\n                            e.Handled = true;\n                        }\n                    }\n                    break;\n\n                case KeyCode.Escape:\n                    if (InputManager.Current.FocusedElement == this)\n                    {\n                        InputManager.Current.ClearFocus();\n                        e.Handled = true;\n                    }\n                    break;\n\n                default:\n                    if (imeEnabled)\n                    {\n                        e.Handled = true;\n                    }\n                    break;\n            }\n\n            if (e.Key == KeyCode.A && Keyboard.IsControlPressed)\n            {\n                e.Handled = true;\n                this.SelectAll();\n            }\n\n            base.OnKeyDown(e);\n        }\n\n        protected override void OnGotFocus(object sender, RoutedEventArgs e)\n        {\n            base.OnGotFocus(sender, e);\n\n            if (this.IMEEnabled)\n            {\n                if (this.imeHandler == null)\n                {\n                    this.imeHandler = this.GetIMEService();\n                }\n                if (this.imeHandler != null)\n                {\n                    this.imeHandler.Enabled = true;\n                    this.imeHandler.onResultReceived += ImeHandler_onResultReceived;\n                    this.imeHandler.onCompositionReceived += ImeHandler_onCompositionReceived;\n                }\n            }\n        }\n\n        protected override void OnLostFocus(object sender, RoutedEventArgs e)\n        {\n            base.OnLostFocus(sender, e);\n\n            if (this.imeHandler != null)\n            {\n                this.imeHandler.onResultReceived -= ImeHandler_onResultReceived;\n                this.imeHandler.onCompositionReceived -= ImeHandler_onCompositionReceived;\n                this.imeHandler.Enabled = false;\n                this.imeHandler = null;\n            }\n        }\n\n        private void Submit()\n        {\n            string text = this.Text;\n            this.textEditor.SetText(\"\");\n            this.undoManager.Clear();\n            this.textEditor.RaiseTextContainerChanged();\n            InputManager.Current.ClearFocus();\n\n            if (!string.IsNullOrEmpty(text))\n            {\n                this.OnTextSubmit(new TextEventArgs(text));\n            }\n        }\n\n        private void ImeHandler_onResultReceived(object sender, IMEResultEventArgs e)\n        {\n            if (e.result >= 0x20)\n            {\n                this.InsertText(e.result.ToString());\n            }\n        }\n\n        private void ImeHandler_onCompositionReceived(object sender, EventArgs e)\n        {\n            var imeWindow = sender as IMENativeWindow;\n            if (imeWindow != null)\n            {\n                bool hasComposition = !string.IsNullOrEmpty(imeWindow.CompositionString);\n                if (hasComposition!=this.prevHasComposition)\n                {\n                    lastCompositionTime = DateTime.Now;\n                }\n                this.prevHasComposition = hasComposition;\n\n                var caretPos = this.textEditor.GetCaretVisualOffset(1, 1);\n                var scrollViewer = this.ScrollViewer;\n                if (scrollViewer != null)\n                {\n                    caretPos.X += scrollViewer.VisualPosition.X;\n                    caretPos.Y += scrollViewer.VisualPosition.Y;\n\n                    caretPos.Y += scrollViewer.Padding.Top - this.VerticalOffset;\n                    switch (this.TextAlignment)\n                    {\n                        case TextAlignment.Left:\n                            caretPos.X += scrollViewer.Padding.Left - this.HorizontalOffset;\n                            break;\n                        case TextAlignment.Right:\n                            caretPos.X += -scrollViewer.Padding.Left - scrollViewer.Padding.Right - this.HorizontalOffset;\n                            break;\n                    }\n                }\n                else\n                {\n                    caretPos.X += this.VisualPosition.X;\n                    caretPos.Y += this.VisualPosition.Y;\n                }\n                IMM.COMPOSITIONFORM form = new IMM.COMPOSITIONFORM();\n                form.dwStyle = IMM.CFSPoint;\n                form.ptCurrentPos.x = (int)(caretPos.X);\n                form.ptCurrentPos.y = (int)(caretPos.Y);\n                bool success = IMM.SetCompositionWindow(imeWindow.IMEContext, ref form);\n            }\n        }\n\n        private void InsertText(string text)\n        {\n            if (string.IsNullOrEmpty(text))\n            {\n                return;\n            }\n\n            if (this.textEditor.SelectionLength > 0)\n            {\n                this.textEditor.StoreSelection(this.undoManager);\n            }\n\n            var sb = this.textEditor.StringBuilder;\n            if (this.MaxLength != 0 && sb.Length + text.Length > this.MaxLength)\n            {\n                text = text.Substring(0, this.MaxLength - sb.Length);\n\n                if (string.IsNullOrEmpty(text))\n                {\n                    return;\n                }\n            }\n\n            var caretIndex = this.textEditor.CaretIndex;\n            if (caretIndex >= 0 && caretIndex <= sb.Length)\n            {\n                sb.Insert(caretIndex, text);\n                this.textEditor.RecordInsertText(this.undoManager, caretIndex, text);\n            }\n            else\n            {\n                sb.Append(text);\n                this.textEditor.RecordInsertText(this.undoManager, -1, text);\n            }\n            this.textEditor.CaretIndex = caretIndex + text.Length;\n            this.textEditor.RaiseTextContainerChanged();\n        }\n\n        private IMEHandler GetIMEService()\n        {\n            var imeHandler = ServiceManager.Instance.GetService<IMEHandler>();\n            return imeHandler;\n        }\n\n        protected virtual void OnTextSubmit(TextEventArgs e)\n        {\n            this.TextSubmit?.Invoke(this, e);\n        }\n\n        public static Style CreateStyle()\n        {\n            var style = TextBoxStyle.CreateTextBoxStyle();\n            style.TargetType = typeof(TextBoxEx);\n            return style;\n        }\n\n        private class TextEditorProxy\n        {\n            public TextEditorProxy(TextBox textBox)\n            {\n                object target = typeof(TextBoxBase)\n                    ?.GetProperty(\"TextEditor\", BindingFlags.Instance | BindingFlags.NonPublic)\n                    ?.GetGetMethod(true)\n                    ?.Invoke(textBox, null);\n\n                if (target == null)\n                {\n                    throw new Exception(\"Get TextEditor failed.\");\n                }\n\n                this.InitMethodCache(target);\n                this.Target = target;\n            }\n\n            public object Target { get; private set; }\n\n            private Func<int> _CaretIndexGet;\n            private Action<int> _CaretIndexSet;\n            private Func<int> _SelectionLengthGet;\n            private Action<int> _SelectionLengthSet;\n            private Func<int> _SelectionStartIndexGet;\n            private Action<int> _SelectionStartIndexSet;\n            private Action _RaiseTextContainerChanged;\n            private MethodInfo _StoreSelectionMethod;\n            private Action<string> _SetTextMethod;\n            private Func<float, float, PointF> _GetCaretVisualOffsetMethod;\n            private FieldInfo _StringBuilderField;\n            private Type _insertTextHistoryType;\n            private ConstructorInfo _insertTextHistoryCtor;\n\n\n            public int CaretIndex\n            {\n                get { return this._CaretIndexGet(); }\n                set { this._CaretIndexSet(value); }\n            }\n\n            public int SelectionLength\n            {\n                get { return this._SelectionLengthGet(); }\n                set { this._SelectionLengthSet(value); }\n            }\n\n            public int SelectionStartIndex\n            {\n                get { return this._SelectionStartIndexGet(); }\n                set { this._SelectionStartIndexSet(value); }\n            }\n\n            public StringBuilder StringBuilder\n            {\n                get { return (StringBuilder)this._StringBuilderField.GetValue(this.Target); }\n            }\n\n            public void RaiseTextContainerChanged()\n            {\n                this._RaiseTextContainerChanged();\n            }\n\n            public void StoreSelection(UndoManagerProxy undoManager)\n            {\n                this._StoreSelectionMethod.Invoke(this.Target, new object[] { undoManager.Target });\n            }\n\n            public void SetText(string text)\n            {\n                this._SetTextMethod(text);\n            }\n\n            public PointF GetCaretVisualOffset(float dpiX, float dpiY)\n            {\n                return this._GetCaretVisualOffsetMethod(dpiX, dpiY);\n            }\n\n            public void RecordInsertText(UndoManagerProxy undoManager, int position, string text)\n            {\n                object memento = this._insertTextHistoryCtor.Invoke(new object[] { position, text });\n                undoManager.Store(memento);\n            }\n\n\n            private void InitMethodCache(object target)\n            {\n                var type = target.GetType();\n                var flag = BindingFlags.Instance | BindingFlags.NonPublic;\n\n                var caretIndexProp = type.GetProperty(\"CaretIndex\", flag);\n                this._CaretIndexGet = (Func<int>)caretIndexProp.GetGetMethod(true).CreateDelegate(typeof(Func<int>), target);\n                this._CaretIndexSet = (Action<int>)caretIndexProp.GetSetMethod(true).CreateDelegate(typeof(Action<int>), target);\n\n                var selectionLengthProp = type.GetProperty(\"SelectionLength\", flag);\n                this._SelectionLengthGet = (Func<int>)selectionLengthProp.GetGetMethod(true).CreateDelegate(typeof(Func<int>), target);\n                this._SelectionLengthSet = (Action<int>)selectionLengthProp.GetSetMethod(true).CreateDelegate(typeof(Action<int>), target);\n\n                var selectionIndexProp = type.GetProperty(\"SelectionStartIndex\", flag);\n                this._SelectionStartIndexGet = (Func<int>)selectionIndexProp.GetGetMethod(true).CreateDelegate(typeof(Func<int>), target);\n                this._SelectionStartIndexSet = (Action<int>)selectionIndexProp.GetSetMethod(true).CreateDelegate(typeof(Action<int>), target);\n\n                this._RaiseTextContainerChanged = (Action)type.GetMethod(\"RaiseTextContainerChanged\", flag).CreateDelegate(typeof(Action), target);\n                this._StoreSelectionMethod = type.GetMethod(\"/*Ԟ\", flag);\n                this._SetTextMethod = (Action<string>)type.GetMethod(\"SetText\", flag).CreateDelegate(typeof(Action<string>), target);\n                this._GetCaretVisualOffsetMethod = (Func<float, float, PointF>)type.GetMethod(\"GetCaretVisualOffset\", flag).CreateDelegate(typeof(Func<float, float, PointF>), target);\n                this._StringBuilderField = type.GetField(\"/*Ԗ\", flag);\n\n                this._insertTextHistoryType = type.Assembly.GetType(@\"EmptyKeys.UserInterface.Documents./\\*Յ\");\n                this._insertTextHistoryCtor = this._insertTextHistoryType.GetConstructor(new[] { typeof(int), typeof(string) });\n            }\n        }\n\n        private class UndoManagerProxy\n        {\n            public UndoManagerProxy(TextBox textBox)\n            {\n                object target = typeof(TextBoxBase)\n                    ?.GetProperty(\"UndoManager\", BindingFlags.Instance | BindingFlags.NonPublic)\n                    ?.GetGetMethod(true)\n                    ?.Invoke(textBox, null);\n\n                if (target == null)\n                {\n                    throw new Exception(\"Get UndoManager failed.\");\n                }\n\n                this.InitMethodCache(target);\n                this.Target = target;\n            }\n\n            public object Target { get; private set; }\n\n            private MethodInfo _StoreMethod;\n            private Action _ClearMethod;\n\n            public void Store(object memento)\n            {\n                this._StoreMethod.Invoke(this.Target, new object[] { memento });\n            }\n\n            public void Clear()\n            {\n                this._ClearMethod();\n            }\n\n            private void InitMethodCache(object target)\n            {\n                var type = target.GetType();\n                this._StoreMethod = type.GetMethod(\"Store\");\n                this._ClearMethod = (Action)type.GetMethod(\"Clear\").CreateDelegate(typeof(Action), target);\n            }\n        }\n    }\n\n    class TextEventArgs : EventArgs\n    {\n        public TextEventArgs(string text)\n        {\n            this.Text = text;\n        }\n\n        public string Text { get; private set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/Tooltip.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing Resource = CharaSimResource.Resource;\nusing WzComparerR2.MapRender.Patches;\nusing WzComparerR2.Common;\nusing WzComparerR2.Rendering;\nusing static WzComparerR2.MapRender.UI.TooltipHelper;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public class Tooltip\n    {\n        public Tooltip(GraphicsDevice graphicsDevice)\n        {\n            this.frame = new Dictionary<string, Texture2D>();\n            this.frame[\"n\"] = Resource.UIToolTip_img_Item_Frame2_n.ToTexture(graphicsDevice);\n            this.frame[\"ne\"] = Resource.UIToolTip_img_Item_Frame2_ne.ToTexture(graphicsDevice);\n            this.frame[\"e\"] = Resource.UIToolTip_img_Item_Frame2_e.ToTexture(graphicsDevice);\n            this.frame[\"se\"] = Resource.UIToolTip_img_Item_Frame2_se.ToTexture(graphicsDevice);\n            this.frame[\"s\"] = Resource.UIToolTip_img_Item_Frame2_s.ToTexture(graphicsDevice);\n            this.frame[\"sw\"] = Resource.UIToolTip_img_Item_Frame2_sw.ToTexture(graphicsDevice);\n            this.frame[\"w\"] = Resource.UIToolTip_img_Item_Frame2_w.ToTexture(graphicsDevice);\n            this.frame[\"nw\"] = Resource.UIToolTip_img_Item_Frame2_nw.ToTexture(graphicsDevice);\n            this.frame[\"c\"] = Resource.UIToolTip_img_Item_Frame2_c.ToTexture(graphicsDevice);\n            this.frame[\"cover\"] = Resource.UIToolTip_img_Item_Frame2_cover.ToTexture(graphicsDevice);\n        }\n\n        private Dictionary<string, Texture2D> frame;\n        private RenderPatch tooltipTarget;\n\n        public RenderPatch TooltipTarget\n        {\n            get { return tooltipTarget; }\n            set { tooltipTarget = value; }\n        }\n\n        public void DrawTooltip(GameTime gameTime, RenderEnv env, StringLinker stringLinker)\n        {\n            if (tooltipTarget == null)\n                return;\n\n            StringResult sr;\n            List<TextBlock> blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n            switch (tooltipTarget.ObjectType)\n            {\n                case RenderObjectType.Mob:\n                    {\n                        LifePatch p = tooltipTarget as LifePatch;\n                        stringLinker.StringMob.TryGetValue(p.LifeID, out sr);\n                        Vector2 current = Vector2.Zero;\n\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipTitleFont, sr == null ? \"(null)\" : sr.Name, ref current, Color.White));\n                        current += new Vector2(4, 4);\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipContentFont, \"id:\" + p.LifeID.ToString(\"d7\"), ref current, Color.White));\n                        size.X = Math.Max(size.X, current.X);\n                        current = new Vector2(0, current.Y + 16);\n\n                        LifeInfo info = p.LifeInfo;\n                        Vector2 size2;\n                        var blocks2 = TooltipHelper.Prepare(info, env.Fonts, out size2);\n                        for (int i = 0; i < blocks2.Length; i++)\n                        {\n                            blocks2[i].Position.Y += current.Y;\n                            blocks.Add(blocks2[i]);\n                        }\n                        size.X = Math.Max(size.X, size2.X);\n                        size.Y = current.Y + size2.Y;\n                    }\n                    break;\n                case RenderObjectType.Npc:\n                    {\n                        LifePatch p = tooltipTarget as LifePatch;\n                        stringLinker.StringNpc.TryGetValue(p.LifeID, out sr);\n                        Vector2 current = Vector2.Zero;\n\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipTitleFont, sr == null ? \"(null)\" : sr.Name, ref current, Color.White));\n                        current += new Vector2(4, 4);\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipContentFont, \"id:\" + p.LifeID.ToString(\"d7\"), ref current, Color.White));\n                        size.X = Math.Max(size.X, current.X);\n                        current = new Vector2(0, current.Y + 16);\n\n                        foreach (var kv in p.Actions)\n                        {\n                            if (kv.Value == p.Frames)\n                            {\n                                blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, \"action: \" + kv.Key, ref current, Color.White, ref size.X));\n                            }\n                        }\n                        size.Y = current.Y;\n                    }\n                    break;\n\n                case RenderObjectType.Portal:\n                    {\n                        PortalPatch p = tooltipTarget as PortalPatch;\n                        Vector2 current = Vector2.Zero;\n                        blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, \"pName: \" + p.PortalName, ref current, Color.White, ref size.X));\n                        string pTypeName = GetPortalTypeString(p.PortalType);\n                        blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, \"pType: \" + p.PortalType + (pTypeName == null ? null : (\" (\" + pTypeName + \")\")), ref current, Color.White, ref size.X));\n                        stringLinker.StringMap.TryGetValue(p.ToMap, out sr);\n                        blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, \"toMap: \" + (sr == null ? \"(null)\" : sr.Name) + \"(\" + p.ToMap + \")\", ref current, Color.White, ref size.X));\n                        blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, \"toName: \" + p.ToName, ref current, Color.White, ref size.X));\n                        if (!string.IsNullOrEmpty(p.Script))\n                            blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, \"script: \" + p.Script, ref current, Color.White, ref size.X));\n                        size.Y = current.Y;\n                    }\n                    break;\n            }\n\n            if (blocks.Count > 0)\n            {\n                size += new Vector2(26, 26);\n                Vector2 origin = new Vector2(env.Input.MousePosition.X, env.Input.MousePosition.Y);\n                origin.X = MathHelper.Clamp(origin.X, 0, Math.Max(0, env.Camera.Width - size.X));\n                origin.Y = MathHelper.Clamp(origin.Y, 0, Math.Max(0, env.Camera.Height - size.Y));\n                this.DrawFrame(env, origin, size);\n\n                origin += new Vector2(13, 13);\n                foreach (TextBlock block in blocks)\n                {\n                    env.Sprite.DrawStringEx(block.Font, block.Text, block.Position, block.ForeColor, -origin);\n                }\n            }\n        }\n\n        private void DrawFrame(RenderEnv env, Vector2 position, Vector2 size)\n        {\n            SpriteBatchEx sprite = env.Sprite;\n            sprite.Draw(this.frame[\"nw\"], position, Color.White);\n            sprite.Draw(this.frame[\"ne\"], position + new Vector2(size.X - 13, 0), Color.White);\n            sprite.Draw(this.frame[\"sw\"], position + new Vector2(0, size.Y - 13), Color.White);\n            sprite.Draw(this.frame[\"se\"], position + new Vector2(size.X - 13, size.Y - 13), Color.White);\n            if (size.X > 26)\n            {\n                sprite.Draw(this.frame[\"n\"], new Rectangle((int)position.X + 13, (int)position.Y, (int)size.X - 26, 13), Color.White);\n                sprite.Draw(this.frame[\"s\"], new Rectangle((int)position.X + 13, (int)(position.Y + size.Y) - 13, (int)size.X - 26, 13), Color.White);\n            }\n            if (size.Y > 26)\n            {\n                sprite.Draw(this.frame[\"e\"], new Rectangle((int)(position.X + size.X) - 13, (int)position.Y + 13, 13, (int)size.Y - 26), Color.White);\n                sprite.Draw(this.frame[\"w\"], new Rectangle((int)position.X, (int)position.Y + 13, 13, (int)size.Y - 26), Color.White);\n            }\n            if (size.X > 26 && size.Y > 26)\n            {\n                sprite.Draw(this.frame[\"c\"], new Rectangle((int)position.X + 13, (int)position.Y + 13, (int)size.X - 26, (int)size.Y - 26), Color.White);\n            }\n            sprite.Draw(this.frame[\"cover\"], position, Color.White);\n        }\n\n        public void DrawNameTooltip(GameTime gameTime, RenderEnv env, RenderPatch patch, StringLinker stringLinker)\n        {\n            StringResult sr;\n            switch (patch.ObjectType)\n            {\n                case RenderObjectType.Mob:\n                    {\n                        LifePatch p = patch as LifePatch;\n                        string name = \"lv.\" + p.LifeInfo.level + \" \";\n                        if (stringLinker != null && stringLinker.StringMob.TryGetValue(p.LifeID, out sr))\n                            name += sr.Name;\n                        else\n                            name += p.LifeID.ToString();\n                        DrawNameTooltip(env, name, env.Fonts.MobNameFont, p.Position, Color.White);\n                    }\n                    break;\n                case RenderObjectType.Npc:\n                    {\n                        LifePatch p = patch as LifePatch;\n                        string name;\n                        if (stringLinker != null && stringLinker.StringNpc.TryGetValue(p.LifeID, out sr))\n                            name = sr.Name;\n                        else\n                            name = p.LifeID.ToString();\n                        DrawNameTooltip(env, name, env.Fonts.NpcNameFont, p.Position, Color.Yellow);\n                    }\n                    break;\n            }\n        }\n\n        private void DrawNameTooltip(RenderEnv env, string name, XnaFont font, Vector2 mapPosition, Color color)\n        {\n            SpriteBatchEx sprite = env.Sprite;\n            Vector2 size = font.MeasureString(name);\n            Rectangle rect = new Rectangle((int)(mapPosition.X - size.X / 2 - 2), (int)(mapPosition.Y + 2), (int)(size.X + 4), (int)(size.Y + 3));\n            sprite.FillRectangle(rect, new Color(Color.Black, 0.7f), env.Camera.Origin);\n            sprite.DrawStringEx(\n                font,\n                name,\n                new Vector2(rect.X + 2, rect.Y + 2),\n                color,\n                env.Camera.Origin);\n        }\n\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/UI/Tooltip2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing Microsoft.Xna.Framework.Content;\n\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.Animation;\nusing WzComparerR2.MapRender.Patches2;\nusing WzComparerR2.PluginBase;\n\nusing Res = CharaSimResource.Resource;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\nusing static WzComparerR2.MapRender.UI.TooltipHelper;\nusing TextureBlock = WzComparerR2.MapRender.UI.UIGraphics.RenderBlock<Microsoft.Xna.Framework.Graphics.Texture2D>;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class Tooltip2\n    {\n        public Tooltip2(ContentManager content)\n        {\n            this.Content = content;\n\n            this.LoadContent(content);\n        }\n\n        public ContentManager Content { get; private set; }\n        public NineFormResource Resource { get; private set; }\n        public StringLinker StringLinker { get; set; }\n        public object TooltipTarget { get; set; }\n\n        public void Draw(GameTime gameTime, RenderEnv env)\n        {\n            if (this.TooltipTarget == null)\n            {\n                return;\n            }\n\n            var content = Draw(gameTime, env, this.TooltipTarget);\n            if (content.blocks != null)\n            {\n                var pos = env.Input.MousePosition;\n                DrawContent(env, content, new Vector2(pos.X + 16, pos.Y + 16), true);\n            }\n        }\n\n        public void Draw(GameTime gameTime, RenderEnv env, object item, Vector2 centerPosition)\n        {\n            if (item == null)\n            {\n                return;\n            }\n\n            var content = Draw(gameTime, env, item);\n            if (content.blocks != null)\n            {\n                var pos = new Vector2(centerPosition.X - content.size.X / 2, centerPosition.Y - content.size.Y / 2);\n                DrawContent(env, content, pos, false);\n            }\n        }\n\n        private void LoadContent(ContentManager content)\n        {\n            var res = new NineFormResource();\n            res.N = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_n));\n            res.NE = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_ne));\n            res.E = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_e));\n            res.SE = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_se));\n            res.S = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_s));\n            res.SW = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_sw));\n            res.W = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_w));\n            res.NW = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_nw));\n            res.C = content.Load<Texture2D>(nameof(Res.UIToolTip_img_Item_Frame2_c));\n            this.Resource = res;\n        }\n\n        private TooltipContent Draw(GameTime gameTime, RenderEnv env, object target)\n        {\n            if (target is LifeItem)\n            {\n                return DrawItem(gameTime, env, (LifeItem)target);\n            }\n            else if (target is PortalItem)\n            {\n                return DrawItem(gameTime, env, (PortalItem)target);\n            }\n            else if (target is IlluminantClusterItem)\n            {\n                return DrawItem(gameTime, env, (IlluminantClusterItem)target);\n            }\n            else if (target is ReactorItem)\n            {\n                return DrawItem(gameTime, env, (ReactorItem)target);\n            }\n            else if (target is TooltipItem)\n            {\n                return DrawItem(gameTime, env, (TooltipItem)target);\n            }\n            else if (target is PortalItem.ItemTooltip)\n            {\n                return DrawString(gameTime, env, ((PortalItem.ItemTooltip)target).Title);\n            }\n            else if (target is UIWorldMap.MapSpotTooltip)\n            {\n                return DrawItem(gameTime, env, (UIWorldMap.MapSpotTooltip)target);\n            }\n            else if (target is UIWorldMap.MapLinkTooltip)\n            {\n                return DrawItem(gameTime, env, (UIWorldMap.MapLinkTooltip)target);\n            }\n            else if (target is string)\n            {\n                return DrawString(gameTime, env, (string)target);\n            }\n            return new TooltipContent();\n        }\n\n        private TooltipContent DrawItem(GameTime gameTime, RenderEnv env, LifeItem item)\n        {\n            var blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n\n            blocks = new List<TextBlock>();\n            StringResult sr = null;\n            Vector2 current = Vector2.Zero;\n\n            switch (item.Type)\n            {\n                case LifeItem.LifeType.Mob:\n                    {\n                        this.StringLinker?.StringMob.TryGetValue(item.ID, out sr);\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipTitleFont, sr == null ? \"(null)\" : sr.Name, ref current, Color.LightYellow));\n                        current += new Vector2(4, 4);\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipContentFont, \"id:\" + item.ID.ToString(\"d7\"), ref current, Color.White));\n                        size.X = Math.Max(size.X, current.X);\n                        current = new Vector2(0, current.Y + 16);\n\n                        Vector2 size2;\n                        var blocks2 = TooltipHelper.Prepare(item.LifeInfo, env.Fonts, out size2);\n                        for (int i = 0; i < blocks2.Length; i++)\n                        {\n                            blocks2[i].Position.Y += current.Y;\n                            blocks.Add(blocks2[i]);\n                        }\n                        size.X = Math.Max(size.X, size2.X);\n                        size.Y = current.Y + size2.Y;\n                    }\n                    break;\n\n                case LifeItem.LifeType.Npc:\n                    {\n                        this.StringLinker?.StringNpc.TryGetValue(item.ID, out sr);\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipTitleFont, sr == null ? \"(null)\" : sr.Name, ref current, Color.LightYellow));\n                        current += new Vector2(4, 4);\n                        blocks.Add(PrepareTextBlock(env.Fonts.TooltipContentFont, \"id:\" + item.ID.ToString(\"d7\"), ref current, Color.White));\n                        size.X = Math.Max(size.X, current.X);\n                        current = new Vector2(0, current.Y + 16);\n\n                        var aniName = (item.View?.Animator as StateMachineAnimator)?.GetCurrent();\n                        if (aniName != null)\n                        {\n                            blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, \"action: \" + aniName, ref current, Color.White, ref size.X));\n                        }\n\n                        size.Y = current.Y;\n                    }\n                    break;\n            }\n\n            return new TooltipContent() { blocks = blocks, size = size };\n        }\n\n        private TooltipContent DrawItem(GameTime gameTime, RenderEnv env, PortalItem item)\n        {\n            var blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n            StringResult sr = null;\n            Vector2 current = Vector2.Zero;\n\n            var sb = new StringBuilder();\n            sb.Append(\"pName: \").AppendLine(item.PName);\n\n            string pTypeName = GetPortalTypeString(item.Type);\n            sb.Append(\"pType: \").Append(item.Type);\n            if (pTypeName != null)\n            {\n                sb.Append(\"(\").Append(pTypeName).Append(\")\");\n            }\n            sb.AppendLine();\n\n            sb.Append(\"toMap: \").Append(item.ToMap);\n            if (item.ToMap != null)\n            {\n                this.StringLinker?.StringMap.TryGetValue(item.ToMap.Value, out sr);\n                string toMapName = sr?.Name;\n                sb.Append(\"(\").Append(sr?.Name ?? \"null\").Append(\")\");\n            }\n            sb.AppendLine();\n\n            sb.Append(\"toName: \").AppendLine(item.ToName);\n\n            if (!string.IsNullOrEmpty(item.Script))\n            {\n                sb.Append(\"script: \").AppendLine(item.Script);\n\n                //Graph.img에 따른 이동경로 출력\n                if (item.GraphTargetMap.Count > 0)\n                {\n                    sb.Append(\"scriptMap: \");\n                    foreach (var targetMapID in item.GraphTargetMap)\n                    {\n                        sb.Append(targetMapID);\n                        this.StringLinker?.StringMap.TryGetValue(targetMapID, out sr);\n                        string toMapName = sr?.Name;\n                        sb.Append(\"(\").Append(sr?.Name ?? \"null\").AppendLine(\")\");\n                    }\n                }\n            }\n\n            sb.Length -= 2;\n\n            blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, sb.ToString(), ref current, Color.White, ref size.X));\n            size.Y = current.Y;\n            return new TooltipContent() { blocks = blocks, size = size };\n        }\n\n        private TooltipContent DrawItem(GameTime gameTime, RenderEnv env, IlluminantClusterItem item)\n        {\n            var blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n            StringResult sr = null;\n            Vector2 current = Vector2.Zero;\n\n            var sb = new StringBuilder();\n            sb.Append(\"Name: \").AppendLine(item.Name);\n\n            sb.AppendLine(\"Type: 发光体群落\");\n\n            sb.Length -= 2;\n\n            blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, sb.ToString(), ref current, Color.White, ref size.X));\n            size.Y = current.Y;\n            return new TooltipContent() { blocks = blocks, size = size };\n        }\n\n\n        private TooltipContent DrawItem(GameTime gameTime, RenderEnv env, ReactorItem item)\n        {\n            var blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n            Vector2 current = Vector2.Zero;\n\n            var sb = new StringBuilder();\n            sb.Append(\"ID: \").Append(item.ID).AppendLine();\n            sb.Append(\"rName: \").AppendLine(item.ReactorName);\n            sb.Append(\"rTime: \").Append(item.ReactorTime).AppendLine();\n\n            sb.Append(\"state: \").Append(item.View.Stage);\n            var ani = item.View.Animator as StateMachineAnimator;\n            if (ani != null)\n            {\n                sb.Append(\" (\").Append(ani.Data.SelectedState).Append(\")\");\n            }\n            sb.AppendLine();\n\n            sb.Length -= 2;\n            blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, sb.ToString(), ref current, Color.White, ref size.X));\n            size.Y = current.Y;\n            return new TooltipContent() { blocks = blocks, size = size };\n        }\n\n        private TooltipContent DrawItem(GameTime gameTime, RenderEnv env, TooltipItem item)\n        {\n            var blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n            Vector2 current = Vector2.Zero;\n\n            if (!string.IsNullOrEmpty(item.Title))\n            {\n                bool hasDesc = !string.IsNullOrEmpty(item.Desc) || !string.IsNullOrEmpty(item.ItemEU);\n                var titleFont = hasDesc ? env.Fonts.TooltipTitleFont : env.Fonts.TooltipContentFont;\n                blocks.Add(PrepareTextLine(titleFont, item.Title, ref current, Color.White, ref size.X));\n            }\n            if (!string.IsNullOrEmpty(item.Desc))\n            {\n                blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, item.Desc, ref current, Color.White, ref size.X));\n            }\n            if (!string.IsNullOrEmpty(item.ItemEU))\n            {\n                blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, item.ItemEU, ref current, Color.White, ref size.X));\n            }\n\n            size.Y = current.Y;\n            return new TooltipContent() { blocks = blocks, size = size };\n        }\n\n        private TooltipContent DrawItem(GameTime gameTime, RenderEnv env, UIWorldMap.MapSpotTooltip item)\n        {\n            var blocks = new List<TextBlock>();\n            var textures = new List<TextureBlock>();\n            Vector2 size = Vector2.Zero;\n            Vector2 current = Vector2.Zero;\n            StringResult sr = null;\n\n            var spot = item.Spot;\n            if (spot != null)\n            {\n                //计算属性要求 获取怪物列表和npc列表\n                int spotBarrier = 0, spotBarrierArc = 0, spotBarrierAut = 0;\n                var mobNames = new List<string>();\n                var npcNames = new List<string>();\n\n                if (!spot.NoTooltip)\n                {\n                    HashSet<int> mobs = new HashSet<int>();\n                    HashSet<int> npcs = new HashSet<int>();\n                    //TODO: caching mobs level.\n                    foreach (var mapNo in spot.MapNo)\n                    {\n                        var mapNode = PluginManager.FindWz(string.Format(\"Map/Map/Map{0}/{1:D9}.img/info\", mapNo / 100000000, mapNo));\n                        if (mapNode != null)\n                        {\n                            int barrier = mapNode?.Nodes[\"barrier\"].GetValueEx(0) ?? 0;\n                            int barrierArc = mapNode?.Nodes[\"barrierArc\"].GetValueEx(0) ?? 0;\n                            int barrierAut = mapNode?.Nodes[\"barrierAut\"].GetValueEx(0) ?? 0;\n                            spotBarrier = Math.Max(spotBarrier, barrier);\n                            spotBarrierArc = Math.Max(spotBarrierArc, barrierArc);\n                            spotBarrierAut = Math.Max(spotBarrierAut, barrierAut);\n                        }\n\n                        var mapInfo = PluginManager.FindWz(string.Format(\"Etc/MapObjectInfo.img/{0}\", mapNo));\n                        if (mapInfo != null)\n                        {\n                            var mobNode = mapInfo.Nodes[\"mob\"];\n                            if (mobNode != null)\n                            {\n                                foreach (var valNode in mobNode.Nodes)\n                                {\n                                    mobs.Add(valNode.GetValue<int>());\n                                }\n                            }\n                            var npcNode = mapInfo.Nodes[\"npc\"];\n                            if (npcNode != null)\n                            {\n                                foreach (var valNode in npcNode.Nodes)\n                                {\n                                    npcs.Add(valNode.GetValue<int>());\n                                }\n                            }\n                        }\n                    }\n\n                    if (mobs.Count > 0)\n                    {\n                        foreach (var mobID in mobs)\n                        {\n                            this.StringLinker?.StringMob.TryGetValue(mobID, out sr);\n                            var mobLevel = PluginManager.FindWz(string.Format(\"Mob/{0:D7}.img/info/level\", mobID)).GetValueEx<int>(0);\n                            string mobText = sr != null ? string.Format(\"{0}(Lv.{1})\", sr.Name, mobLevel) : mobID.ToString();\n                            mobNames.Add(mobText);\n                        }\n                    }\n                    if (npcs.Count > 0)\n                    {\n                        foreach (var npcID in npcs)\n                        {\n                            this.StringLinker?.StringNpc.TryGetValue(npcID, out sr);\n                            string npcText = sr?.Name ?? npcID.ToString();\n                            npcNames.Add(npcText);\n                        }\n                    }\n                }\n\n                //预计算宽度\n                int partWidth = 0;\n                int? drawNpcColumnWidth = null;\n                var font = env.Fonts.TooltipContentFont;\n                if (mobNames.Count > 0 || npcNames.Count > 0)\n                {\n                    float mobWidth = mobNames.Count <= 0 ? 0 : mobNames.Max(text => font.MeasureString(text).X);\n                    float npcWidth = npcNames.Count <= 0 ? 0 : npcNames.Max(text => font.MeasureString(text).X);\n                    if (npcNames.Count > 0 && mobNames.Count + npcNames.Count > 18)\n                    {\n                        partWidth = (int)Math.Max(mobWidth, npcWidth * 2 + 10);\n                        drawNpcColumnWidth = (int)npcWidth;\n                    }\n                    else\n                    {\n                        partWidth = (int)Math.Max(mobWidth, npcWidth);\n                    }\n                    partWidth += 15;\n                }\n\n                //开始绘制\n                //属性要求\n                List<object> part1 = null;\n                float part1Width = 0;\n                if (spotBarrier > 0 || spotBarrierArc > 0 || spotBarrierAut > 0)\n                {\n                    part1 = new List<object>();\n                    Action<int, Texture2D, Color> addBarrier = (barrier, icon, foreColor) =>\n                    {\n                        if (icon != null)\n                        {\n                            var rect = new Rectangle((int)current.X, (int)current.Y + 1, icon.Width, icon.Height);\n                            part1.Add(new TextureBlock(icon, rect));\n                            current.X += rect.Width + 1;\n                        }\n\n                        var textBlock = PrepareTextBlock(env.Fonts.DefaultFont, barrier.ToString(), ref current, foreColor);\n                        part1.Add(textBlock);\n                    };\n\n                    if (spotBarrier > 0)\n                    {\n                        var icon = Content.Load<Texture2D>(nameof(MRes.UIWindow_img_ToolTip_WorldMap_StarForce));\n                        addBarrier(spotBarrier, icon, new Color(255, 204, 0));\n                    }\n                    else if (spotBarrierArc > 0)\n                    {\n                        var icon = Content.Load<Texture2D>(nameof(MRes.UIWindow_img_ToolTip_WorldMap_ArcaneForce));\n                        addBarrier(spotBarrierArc, icon, new Color(221, 170, 255));\n                    }\n                    else if (spotBarrierAut > 0)\n                    {\n                        var icon = Content.Load<Texture2D>(nameof(MRes.UIWindow_img_ToolTip_WorldMap_AuthenticForce));\n                        addBarrier(spotBarrierAut, icon, new Color(221, 170, 255));\n                    }\n\n                    part1Width = current.X;\n                    size.X = Math.Max(size.X, current.X);\n                    current.X = 0;\n                    current.Y += 15;\n                }\n\n                //地图名称\n                List<TextBlock> part2 = new List<TextBlock>();\n                List<TextBlock> part2_1 = null;\n                float part2Width = 0;\n                {\n                    int mapID = spot.MapNo[0];\n                    this.StringLinker?.StringMap.TryGetValue(mapID, out sr);\n                    string title = spot.Title ?? (sr != null ? string.Format(\"{0} : {1}\", sr[\"streetName\"], sr[\"mapName\"]) : mapID.ToString());\n                    string desc = spot.Desc ?? sr?[\"mapDesc\"];\n                    var titleFont = string.IsNullOrEmpty(desc) ? env.Fonts.TooltipContentFont : env.Fonts.TooltipTitleFont;\n                    part2.Add(PrepareTextLine(titleFont, title, ref current, Color.White, ref part2Width));\n                    size.X = Math.Max(size.X, part2Width);\n\n                    if (!string.IsNullOrEmpty(desc))\n                    {\n                        current.Y += 2;\n                        part2_1 = new List<TextBlock>();\n                        int width = (int)MathHelper2.Max(280, part2Width, size.X, partWidth);\n                        part2_1.AddRange(PrepareFormatText(env.Fonts.TooltipContentFont, desc, ref current, width, ref size.X));\n                    }\n\n                    current.Y += 4;\n                }\n\n                //准备分割线\n                List<TextureBlock> lines = new List<TextureBlock>();\n                var line = Content.Load<Texture2D>(nameof(MRes.UIWindow_img_ToolTip_WorldMap_Line));\n\n                //绘制怪物\n                List<object> part3 = null;\n                if (mobNames.Count > 0)\n                {\n                    part3 = new List<object>();\n\n                    //绘制分割线\n                    lines.Add(new TextureBlock(line, new Rectangle(current.ToPoint(), Point.Zero)));\n                    current.Y += 8;\n\n                    //怪物列表\n                    Texture2D icon;\n                    Color color;\n                    if (spotBarrier > 0 || spotBarrierArc > 0 || spotBarrierAut > 0)\n                    {\n                        icon = Content.Load<Texture2D>(nameof(MRes.UIWindow_img_ToolTip_WorldMap_enchantMob));\n                        color = new Color(255, 0, 102);\n                    }\n                    else\n                    {\n                        icon = Content.Load<Texture2D>(nameof(MRes.UIWindow_img_ToolTip_WorldMap_Mob));\n                        color = new Color(119, 255, 0);\n                    }\n                    part3.Add(new TextureBlock(icon, new Rectangle(0, (int)current.Y + 1, 0, 0)));\n                    foreach (var mobName in mobNames)\n                    {\n                        part3.Add(new TextBlock()\n                        {\n                            Font = font,\n                            Text = mobName,\n                            Position = new Vector2(15, current.Y),\n                            ForeColor = color\n                        });\n                        current.Y += 18;\n                    }\n                }\n\n                //绘制npc\n                List<object> part4 = null;\n                if (npcNames.Count > 0)\n                {\n                    part4 = new List<object>();\n                    //绘制分割线\n                    lines.Add(new TextureBlock(line, new Rectangle(current.ToPoint(), Point.Zero)));\n                    current.Y += 8;\n\n                    //npc列表\n                    Texture2D icon = Content.Load<Texture2D>(nameof(MRes.UIWindow_img_ToolTip_WorldMap_Npc));\n                    Color color = new Color(119, 204, 255);\n                    part4.Add(new TextureBlock(icon, new Rectangle(0, (int)current.Y + 1, 0, 0)));\n                    for (int i = 0; i < npcNames.Count; i++)\n                    {\n                        var pos = new Vector2(15, current.Y);\n                        if (i % 2 == 1 && drawNpcColumnWidth != null)\n                        {\n                            pos.X += 10 + drawNpcColumnWidth.Value;\n                        }\n\n                        part4.Add(new TextBlock()\n                        {\n                            Font = font,\n                            Text = npcNames[i],\n                            Position = pos,\n                            ForeColor = color\n                        });\n\n                        if (i == npcNames.Count - 1 || drawNpcColumnWidth == null || i % 2 == 1)\n                        {\n                            current.X = 0;\n                            current.Y += 18;\n                        }\n                    }\n                }\n\n                size.X = Math.Max(size.X, partWidth);\n                current.Y -= 4;\n\n                //合并parts\n                //对part1 part2居中\n                if (part1 != null)\n                {\n                    int offset = (int)((size.X - part1Width) / 2);\n                    foreach (object obj in part1)\n                    {\n                        if (obj is TextBlock)\n                        {\n                            var tb = (TextBlock)obj;\n                            tb.Position.X += offset;\n                            blocks.Add(tb);\n                        }\n                        else if (obj is TextureBlock)\n                        {\n                            var tex = (TextureBlock)obj;\n                            tex.Rectangle.X += offset;\n                            textures.Add(tex);\n                        }\n                    }\n                }\n                if (part2 != null)\n                {\n                    int offset = (int)((size.X - part2Width) / 2);\n                    for (int i = 0; i < part2.Count; i++)\n                    {\n                        var tb = part2[i];\n                        tb.Position.X += offset;\n                        blocks.Add(tb);\n                    }\n                }\n                if (part2_1 != null)\n                {\n                    foreach (var tb in part2_1)\n                    {\n                        blocks.Add(tb);\n                    }\n                }\n                if (lines != null)\n                {\n                    for (int i = 0; i < lines.Count; i++)\n                    {\n                        var tex = lines[i];\n                        tex.Rectangle.Width = (int)size.X;\n                        tex.Rectangle.Height = 1;\n                        textures.Add(tex);\n                    }\n                }\n                foreach (var _part in new[] { part3, part4 })\n                {\n                    if (_part != null)\n                    {\n                        foreach (object obj in _part)\n                        {\n                            if (obj is TextBlock)\n                            {\n                                var tb = (TextBlock)obj;\n                                blocks.Add(tb);\n                            }\n                            else if (obj is TextureBlock)\n                            {\n                                var tex = (TextureBlock)obj;\n                                textures.Add(tex);\n                            }\n                        }\n                    }\n                }\n            }\n            size.Y = current.Y;\n            return new TooltipContent() { blocks = blocks, textures = textures, size = size };\n        }\n\n        private TooltipContent DrawItem(GameTime gameTime, RenderEnv env, UIWorldMap.MapLinkTooltip item)\n        {\n            var blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n            Vector2 current = Vector2.Zero;\n\n            string tooltip = item.Link.Tooltip, desc = item.Link.Desc;\n            float titleWidth = 0;\n\n            if (!string.IsNullOrEmpty(tooltip))\n            {\n                blocks.Add(PrepareTextLine(env.Fonts.TooltipTitleFont, tooltip, ref current, Color.White, ref size.X));\n                titleWidth = size.X;\n                current.Y += 6;\n            }\n            if (!string.IsNullOrEmpty(desc))\n            {\n                blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, desc, ref current, Color.White, ref size.X));\n            }\n\n            size.X = Math.Max(310, size.X);\n            size.Y = current.Y;\n\n            // align center\n            if (titleWidth > 0 && titleWidth < size.X)\n            {\n                var titleBlock = blocks[0];\n                titleBlock.Position.X += (int)(size.X - titleWidth) / 2;\n                blocks[0] = titleBlock;\n            }\n            return new TooltipContent() { blocks = blocks, size = size };\n        }\n\n        private TooltipContent DrawString(GameTime gameTime, RenderEnv env, string text)\n        {\n            var blocks = new List<TextBlock>();\n            Vector2 size = Vector2.Zero;\n            Vector2 current = Vector2.Zero;\n            blocks.Add(PrepareTextLine(env.Fonts.TooltipContentFont, text, ref current, Color.White, ref size.X));\n            size.Y = current.Y;\n            return new TooltipContent() { blocks = blocks, size = size };\n        }\n\n        private void DrawContent(RenderEnv env, TooltipContent content, Vector2 position, bool adjustToWindow)\n        {\n            Vector2 padding = new Vector2(10, 8);\n            Vector2 preferSize = new Vector2(\n                Math.Max(content.size.X + padding.X * 2, 26),\n                Math.Max(content.size.Y + padding.Y * 2, 26));\n\n            if (adjustToWindow)\n            {\n                position.X = Math.Max(0, Math.Min(position.X, env.Camera.Width - preferSize.X));\n                position.Y = Math.Max(0, Math.Min(position.Y, env.Camera.Height - preferSize.Y));\n            }\n\n            env.Sprite.Begin(blendState: BlendState.NonPremultiplied);\n            var background = UIGraphics.LayoutNinePatch(this.Resource, new Point((int)preferSize.X, (int)preferSize.Y));\n            foreach (var block in background)\n            {\n                if (block.Rectangle.Width > 0 && block.Rectangle.Height > 0 && block.Texture != null)\n                {\n                    var rect = new Rectangle((int)position.X + block.Rectangle.X,\n                        (int)position.Y + block.Rectangle.Y,\n                        block.Rectangle.Width,\n                        block.Rectangle.Height);\n                    env.Sprite.Draw(block.Texture, rect, Color.White);\n                }\n            }\n\n            if (content.textures != null)\n            {\n                foreach (var block in content.textures)\n                {\n                    if (block.Texture != null)\n                    {\n                        var rect = block.Rectangle;\n                        rect.X += (int)(position.X + padding.X);\n                        rect.Y += (int)(position.Y + padding.Y);\n                        if (rect.Width == 0) rect.Width = block.Texture.Width;\n                        if (rect.Height == 0) rect.Height = block.Texture.Height;\n                        env.Sprite.Draw(block.Texture, rect, Color.White);\n                    }\n                }\n            }\n            env.Sprite.Flush();\n\n            foreach (var block in content.blocks)\n            {\n                var pos = new Vector2(position.X + padding.X + block.Position.X,\n                    position.Y + padding.Y + block.Position.Y);\n\n                var baseFont = block.Font.BaseFont;\n\n                if (baseFont is XnaFont)\n                {\n                    env.Sprite.DrawStringEx((XnaFont)baseFont, block.Text, pos, block.ForeColor);\n                    env.Sprite.Flush();\n                }\n                else if (baseFont is D2DFont)\n                {\n                    env.D2DRenderer.Begin();\n                    env.D2DRenderer.DrawString((D2DFont)baseFont, block.Text, pos, block.ForeColor);\n                    env.D2DRenderer.End();\n                }\n            }\n            env.Sprite.End();\n        }\n\n        private struct TooltipContent\n        {\n            public List<TextBlock> blocks;\n            public List<TextureBlock> textures;\n            public Vector2 size;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/TooltipHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing WzComparerR2.Common;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public static class TooltipHelper\n    {\n        public static TextBlock PrepareTextBlock(IWcR2Font font, string text, ref Vector2 pos, Color color)\n        {\n            Vector2 size = font.MeasureString(text);\n\n            TextBlock block = new TextBlock();\n            block.Font = font;\n            block.Text = text;\n            block.Position = pos;\n            block.ForeColor = color;\n\n            pos.X += size.X;\n            return block;\n        }\n\n        public static TextBlock PrepareTextLine(IWcR2Font font, string text, ref Vector2 pos, Color color, ref float maxWidth)\n        {\n            Vector2 size = font.MeasureString(text);\n\n            TextBlock block = new TextBlock();\n            block.Font = font;\n            block.Text = text;\n            block.Position = pos;\n            block.ForeColor = color;\n\n            maxWidth = Math.Max(pos.X + size.X, maxWidth);\n            pos.X = 0;\n            pos.Y += font.LineHeight;\n\n            if (size.Y >= font.LineHeight)\n            {\n                pos.Y += size.Y - font.Size;\n            }\n\n            return block;\n        }\n\n        public static TextBlock[] PrepareFormatText(IWcR2Font font, string formatText, ref Vector2 pos, int width, ref float maxWidth)\n        {\n            var layouter = new TextLayouter();\n            int y = (int)pos.Y;\n            var blocks = layouter.LayoutFormatText(font, formatText, width, ref y);\n            for (int i = 0; i < blocks.Length; i++)\n            {\n                blocks[i].Position.X += pos.X;\n                var blockWidth = blocks[i].Font.MeasureString(blocks[i].Text).X;\n                maxWidth = Math.Max(maxWidth, blocks[i].Position.X + blockWidth);\n            }\n            pos.X = 0;\n            pos.Y = y;\n            return blocks;\n        }\n\n        public static TextBlock[] Prepare(LifeInfo info, MapRenderFonts fonts, out Vector2 size)\n        {\n            var blocks = new List<TextBlock>();\n            var current = Vector2.Zero;\n            size = Vector2.Zero;\n\n            blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"Level: \" + info.level + (info.boss ? \" (Boss)\" : null), ref current, Color.White, ref size.X));\n            blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"HP/MP: \" + info.maxHP + \" / \" + info.maxMP, ref current, Color.White, ref size.X));\n            blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"PAD/MAD: \" + info.PADamage + \" / \" + info.MADamage, ref current, Color.White, ref size.X));\n            blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"PDr/MDr: \" + info.PDRate + \"% / \" + info.MDRate + \"%\", ref current, Color.White, ref size.X));\n            blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"Acc/Eva: \" + info.acc + \" / \" + info.eva, ref current, Color.White, ref size.X));\n            blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"KB: \" + info.pushed, ref current, Color.White, ref size.X));\n            blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"Exp: \" + info.exp, ref current, Color.White, ref size.X));\n            if (info.undead) blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"undead: 1\", ref current, Color.White, ref size.X));\n            StringBuilder sb;\n            if ((sb = GetLifeElemAttrString(ref info.elemAttr)).Length > 0)\n                blocks.Add(PrepareTextLine(fonts.TooltipContentFont, \"elem: \" + sb.ToString(), ref current, Color.White, ref size.X));\n            size.Y = current.Y;\n\n            return blocks.ToArray();\n        }\n\n        public static StringBuilder GetLifeElemAttrString(ref LifeInfo.ElemAttr elemAttr)\n        {\n            StringBuilder sb = new StringBuilder(14);\n            sb.Append(GetElemResistanceString(\"冰\", elemAttr.I));\n            sb.Append(GetElemResistanceString(\"雷\", elemAttr.L));\n            sb.Append(GetElemResistanceString(\"火\", elemAttr.F));\n            sb.Append(GetElemResistanceString(\"毒\", elemAttr.S));\n            sb.Append(GetElemResistanceString(\"圣\", elemAttr.H));\n            sb.Append(GetElemResistanceString(\"暗\", elemAttr.D));\n            sb.Append(GetElemResistanceString(\"物\", elemAttr.P));\n            return sb;\n        }\n\n        public static string GetElemResistanceString(string elemName, LifeInfo.ElemResistance resist)\n        {\n            string e = null;\n            switch (resist)\n            {\n                case LifeInfo.ElemResistance.Immune: e = \"× \"; break;\n                case LifeInfo.ElemResistance.Resist: e = \"△ \"; break;\n                case LifeInfo.ElemResistance.Normal: e = null; break;\n                case LifeInfo.ElemResistance.Weak: e = \"◎ \"; break;\n            }\n            return e != null ? (elemName + e) : null;\n        }\n\n        public static string GetPortalTypeString(int pType)\n        {\n            switch (pType)\n            {\n                case 0: return \"地图出生点\";\n                case 1: return \"一般传送门(隐藏)\";\n                case 2: return \"一般传送门\";\n                case 3: return \"一般传送门(接触)\";\n                case 6: return \"时空门入口点\";\n                case 7: return \"脚本传送门\";\n                case 8: return \"脚本传送门(隐藏)\";\n                case 9: return \"脚本传送门(接触)\";\n                case 10: return \"地图内传送门\";\n                case 12: return \"弹力装置\";\n                default: return null;\n            }\n        }\n\n        public struct TextBlock\n        {\n            public Vector2 Position;\n            public Color ForeColor;\n            public IWcR2Font Font;\n            public string Text;\n        }\n\n        public class TextLayouter : WzComparerR2.Text.TextRenderer<IWcR2Font>\n        {\n            public TextLayouter() : base()\n            {\n\n            }\n\n            List<TextBlock> blocks;\n\n            public TextBlock[] LayoutFormatText(IWcR2Font font, string s, int width, ref int y)\n            {\n                this.blocks = new List<TextBlock>();\n                base.DrawFormatString(s, font, width, ref y, (int)Math.Ceiling(font.LineHeight));\n                return this.blocks.ToArray();\n            }\n\n            protected override void Flush(StringBuilder sb, int startIndex, int length, int x, int y, string colorID)\n            {\n                this.blocks.Add(new TextBlock()\n                {\n                    Position = new Vector2(x, y),\n                    ForeColor = this.GetColor(colorID),\n                    Font = this.font,\n                    Text = sb.ToString(startIndex, length),\n                });\n            }\n\n            protected override void MeasureRuns(List<WzComparerR2.Text.Run> runs)\n            {\n                int x = 0;\n                foreach (var run in runs)\n                {\n                    if (run.IsBreakLine)\n                    {\n                        run.X = x;\n                        run.Length = 0;\n                    }\n                    else\n                    {\n                        var size = base.font.MeasureString(this.sb.ToString(run.StartIndex, run.Length));\n                        run.X = x;\n                        run.Width = (int)size.X;\n                        x += run.Width;\n                    }\n                }\n            }\n\n            protected override System.Drawing.Rectangle[] MeasureChars(int startIndex, int length)\n            {\n                var regions = new System.Drawing.Rectangle[length];\n                int x = 0;\n                for (int i = 0; i < length; i++)\n                {\n                    var text = this.sb[startIndex + i].ToString();\n                    var size = this.font.MeasureString(text);\n                    regions[i] = new System.Drawing.Rectangle(x, 0, (int)size.X, (int)size.Y);\n                    x += (int)size.X;\n                }\n                return regions;\n            }\n\n            public virtual Color GetColor(string colorID)\n            {\n                switch (colorID)\n                {\n                    case \"c\":\n                        return new Color(255, 153, 0);\n                    default:\n                        return Color.White;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIChatBox.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Reflection;\nusing Microsoft.Xna.Framework;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Input;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Controls.Primitives;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Themes;\nusing EmptyKeys.UserInterface.Media.Imaging;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class UIChatBox : WindowEx\n    {\n        public UIChatBox()\n        {\n\n        }\n\n        public TextBoxEx TextBoxChat { get; private set; }\n        private ScrollViewer scrollView;\n        private StackPanel pnlMessage;\n        private bool isResizing;\n        private PointF startPoint;\n\n        private static readonly string Part_Resize = \"UIChatBox_ResizeBorder\";\n\n        protected override void InitializeComponents()\n        {\n            var grid = new Grid();\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1f, GridUnitType.Star) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(28) });\n            grid.SetBinding(Canvas.WidthProperty, new Binding(UIChatBox.WidthProperty) { Source = this });\n            grid.SetBinding(Canvas.HeightProperty, new Binding(UIChatBox.HeightProperty) { Source = this });\n            this.Content = grid;\n\n            var border1 = new Border();\n            border1.Name = Part_Resize;\n            border1.IsHitTestVisible = true;\n            border1.Background = new TCBBrush() { Resource = GetBackgroundResource() };\n            Grid.SetRow(border1, 0);\n            grid.Children.Add(border1);\n\n            var stackPanel = new StackPanel();\n            stackPanel.Orientation = Orientation.Vertical;\n            stackPanel.Margin = new Thickness(0);\n            this.pnlMessage = stackPanel;\n\n            var scrollViewer = new ScrollViewer();\n            scrollViewer.Style = CreateScrollViewerStyle();\n            scrollViewer.Margin = new Thickness(8, 10, 8, 4);\n            scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;\n            scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;\n            scrollViewer.Content = stackPanel;\n            border1.Child = scrollViewer;\n            this.scrollView = scrollViewer;\n\n            var border2 = new Border();\n            border2.Background = new ImageBrush() { ImageSource = new BitmapImage() { TextureAsset = nameof(MRes.StatusBar3_img_chat_ingame_input_layer_backgrnd) } };\n            Grid.SetRow(border2, 1);\n            grid.Children.Add(border2);\n\n            var textBox = new TextBoxEx();\n            textBox.Background = new ImageBrush() { ImageSource = new BitmapImage() { TextureAsset = nameof(MRes.StatusBar3_img_chat_ingame_input_layer_chatEnter) } };\n            textBox.SelectionBrush = Brushes.Blue;\n            textBox.CaretBrush = Brushes.White;\n            textBox.IMEEnabled = true;\n            textBox.IsTabStop = false;\n            textBox.BorderThickness = new Thickness(0);\n            textBox.Width = 471;\n            textBox.Height = 20;\n            border2.Child = textBox;\n            this.TextBoxChat = textBox;\n\n            this.Width = 574;\n            this.Height = 80;\n            this.MinHeight = 80;\n            ImageManager.Instance.AddImage(nameof(MRes.StatusBar3_img_chat_ingame_input_layer_backgrnd));\n            ImageManager.Instance.AddImage(nameof(MRes.StatusBar3_img_chat_ingame_input_layer_chatEnter));\n            base.InitializeComponents();\n        }\n\n        public void AppendTextNormal(string msgText)\n        {\n            this.AppendMessage(msgText, Color.White, Color.Transparent);\n        }\n\n        public void AppendTextSystem(string msgText)\n        {\n            this.AppendMessage(msgText, new Color(255, 170, 170), Color.Transparent);\n        }\n\n        public void AppendTextInfo(string msgText)\n        {\n            this.AppendMessage(msgText, new Color(187, 187, 187), Color.Transparent);\n        }\n\n        public void AppendTextHelp(string msgText)\n        {\n            this.AppendMessage(msgText, new Color(255, 255, 0), Color.Transparent);\n        }\n\n        public void AppendMessage(string msgText, Color foreColor, Color backColor)\n        {\n            var border = new Border();\n            border.Background = new SolidColorBrush(new ColorW(backColor.PackedValue));\n\n            var textBlock = new TextBlock();\n            textBlock.Text = msgText;\n            textBlock.Foreground = new SolidColorBrush(new ColorW(foreColor.PackedValue));\n            textBlock.Margin = new Thickness(0, 1, 0, 1);\n            border.Child = textBlock;\n\n            this.pnlMessage.Children.Add(border);\n\n            if (textBlock.Font != null)\n            {\n                UIElement scrollBar = VisualTreeHelper.Instance.FindElementByName(this.scrollView, \"PART_VerticalScrollBar\");\n                var desiredSize = new Size(this.scrollView.ActualWidth - scrollBar.ActualWidth, 0);\n                var textSize = textBlock.Font.MeasureString(msgText, desiredSize);\n                if (textSize.Width > 0)\n                {\n                    textBlock.Width = Math.Max(desiredSize.Width, textSize.Width);\n                }\n                if (textSize.Height > 0)\n                {\n                    textBlock.Height = textSize.Height;\n                }\n            }\n\n            while (this.pnlMessage.Children.Count > 500)\n            {\n                this.pnlMessage.Children.RemoveAt(0);\n            }\n\n            if (this.scrollView.ExtentHeight - (this.scrollView.VerticalOffset + this.scrollView.ActualHeight) < 20)\n            {\n                this.scrollView.ScrollToBottom();\n            }\n        }\n\n        protected override void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)\n        {\n            if (e.ChangedButton == EmptyKeys.UserInterface.Input.MouseButton.Left)\n            {\n                UIElement elem = e.Source as UIElement;\n                if (elem != null && elem.Name == Part_Resize && !isResizing)\n                {\n                    var point = e.GetPosition(this);\n                    if (point.Y < 8) //top\n                    {\n                        isResizing = true;\n                        this.startPoint = e.GetPosition();\n                        elem.CaptureMouse();\n                        this.Focus();\n                        e.Handled = true;\n                        return;\n                    }\n                }\n            }\n            base.OnPreviewMouseDown(sender, e);\n        }\n\n        protected override void OnPreviewMouseMove(object sender, MouseEventArgs e)\n        {\n            if (this.isResizing)\n            {\n                var elem = e.Source as UIElement;\n                if (elem != null)\n                {\n                    var point = e.GetPosition();\n                    var dx = point.X - this.startPoint.X;\n                    var dy = point.Y - this.startPoint.Y;\n                    var vp = Engine.Instance.Renderer.GetViewport();\n                    //resize-top\n                    if (dy != 0)\n                    {\n                        var height = this.Height - dy;\n                        if (height < this.MinHeight)\n                        {\n                            height = this.MinHeight;\n                        }\n                        if (height > this.MaxHeight)\n                        {\n                            height = this.MaxHeight;\n                        }\n                        if (height > vp.Height)\n                        {\n                            height = vp.Height;\n                        }\n                        dy = this.Height - height;\n                        if (dy != 0)\n                        {\n                            this.Top += dy;\n                            this.Height = height;\n                            this.startPoint.Y += dy;\n                            VisualTreeHelper.Instance.InvalidateMeasure(this);\n                        }\n                    }\n                }\n                e.Handled = true;\n            }\n            base.OnPreviewMouseMove(sender, e);\n        }\n\n        protected override void OnPreviewMouseUp(object sender, MouseButtonEventArgs e)\n        {\n            if (isResizing)\n            {\n                isResizing = false;\n                e.Handled = true;\n                var elem = (e.Source as UIElement);\n                if (elem != null)\n                {\n                    elem.ReleaseMouseCapture();\n                }\n            }\n            base.OnPreviewMouseUp(sender, e);\n        }\n\n        private INinePatchResource<TextureBase> GetBackgroundResource()\n        {\n            var assetManager = Engine.Instance.AssetManager;\n\n            return new EKNineFormResource()\n            {\n                N = assetManager.LoadTexture(null, nameof(MRes.StatusBar3_img_chat_ingame_view_max_top)),\n                C = assetManager.LoadTexture(null, nameof(MRes.StatusBar3_img_chat_ingame_view_max_center)),\n                S = assetManager.LoadTexture(null, nameof(MRes.StatusBar3_img_chat_ingame_view_max_bottom)),\n            };\n        }\n\n        private Style CreateScrollViewerStyle()\n        {\n            var style = ScrollViewerStyle.CreateScrollViewerStyle();\n            var templateSetter = style.Setters.FirstOrDefault(s => s.Property == Control.TemplateProperty);\n            if (templateSetter != null)\n            {\n                var oldTemplate = templateSetter.Value as ControlTemplate;\n                var funcType = typeof(Func<UIElement, UIElement>);\n                var funcField = oldTemplate.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)\n                    .FirstOrDefault(field => field.FieldType == funcType);\n                var oldMethod = funcField?.GetValue(oldTemplate) as Func<UIElement, UIElement>;\n                if (oldMethod != null)\n                {\n                    var newMethod = new Func<UIElement, UIElement>(parent =>\n                    {\n                        UIElement elem = oldMethod(parent);\n                        ScrollBar scrollBar = VisualTreeHelper.Instance.FindElementByName(elem, \"PART_VerticalScrollBar\") as ScrollBar;\n                        if (scrollBar != null)\n                        {\n                            scrollBar.Width = 12;\n                            scrollBar.MaxWidth = 12;\n                            scrollBar.MinWidth = 12;\n                        }\n                        scrollBar = VisualTreeHelper.Instance.FindElementByName(elem, \"PART_HorizontalScrollBar\") as ScrollBar;\n                        if (scrollBar != null)\n                        {\n                            scrollBar.Height = 50;\n                        }\n                        return elem;\n                    });\n                    funcField.SetValue(oldTemplate, newMethod);\n                }\n            }\n            return style;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIDanmaku.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Collections.ObjectModel;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Media.Imaging;\nusing Color = Microsoft.Xna.Framework.Color;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class UIDanmaku : WindowEx\n    {\n        public UIDanmaku()\n        {\n            this.Bullets = new BulletCollection(this);\n        }\n\n        public BulletCollection Bullets { get; private set; }\n\n        private Canvas danmakuContainer;\n        private List<List<TextBlock>> bulletFlow = new List<List<TextBlock>>();\n\n        protected override void InitializeComponents()\n        {\n            this.IsHitTestVisible = false;\n\n            Canvas canvas = new Canvas();\n            canvas.SetBinding(Canvas.WidthProperty, new Binding(Window.WidthProperty) { Source = this });\n            canvas.SetBinding(Canvas.HeightProperty, new Binding(Window.HeightProperty) { Source = this });\n            canvas.LayoutUpdated += Canvas_LayoutUpdated;\n            canvas.FontFamily = new FontFamily(\"微软雅黑\");\n            canvas.FontSize = 24;\n            canvas.FontStyle = FontStyle.Regular;\n\n            this.Content = canvas;\n            this.danmakuContainer = canvas;\n\n            FontManager.Instance.AddFont(canvas.FontFamily.Source, canvas.FontSize, canvas.FontStyle);\n            base.InitializeComponents();\n        }\n\n        protected override void OnDraw(Renderer spriterenderer, double elapsedGameTime, float opacity)\n        {\n            base.OnDraw(spriterenderer, elapsedGameTime, opacity);\n            this.Update(TimeSpan.FromMilliseconds(elapsedGameTime));\n        }\n\n        private void Canvas_LayoutUpdated(object sender, EventArgs e)\n        {\n            \n        }\n\n        private void Update(TimeSpan elapsed)\n        {\n            List<TextBlock> preRemoved = null;\n\n            foreach (TextBlock textBlock in this.danmakuContainer.Children)\n            {\n                if (textBlock.ActualWidth > 0)\n                {\n                    float left = Canvas.GetLeft(textBlock);\n                    if (left + textBlock.ActualWidth <= 0)\n                    {\n                        (preRemoved ?? (preRemoved = new List<TextBlock>())).Add(textBlock);\n                    }\n                    else\n                    {\n                        float speed = (this.Width + textBlock.ActualWidth) / 5;\n                        left -= (float)(speed * elapsed.TotalSeconds);\n                        Canvas.SetLeft(textBlock, left);\n                    }\n                }\n            }\n\n            if (preRemoved != null)\n            {\n                foreach (var textBlock in preRemoved)\n                {\n                    var bullet = textBlock.DataContext as Bullet;\n                    this.danmakuContainer.Children.Remove(textBlock);\n                    this.Bullets.Remove(bullet);\n                }\n            }\n        }\n\n        private void AddBullet(Bullet bullet)\n        {\n            var textBlock = new TextBlock()\n            {\n                Foreground = new SolidColorBrush(new ColorW(bullet.ForeColor.PackedValue)),\n                Text = bullet.Text\n            };\n           \n            this.danmakuContainer.Children.Add(textBlock);\n            //寻找位置\n            int line = 0;\n            for (; line < this.bulletFlow.Count; line++)\n            {\n                var row = this.bulletFlow[line];\n                if (row.Count <= 0)\n                {\n                    break;\n                }\n                var tb = row[row.Count - 1];\n                //这里应该计算成剩余时间 暂时偷懒。\n                if (tb.ActualWidth > 0 && Canvas.GetLeft(tb) + tb.ActualWidth + 50 < this.Width)\n                {\n                    break;\n                }\n            }\n            while (line >= this.bulletFlow.Count)\n            {\n                this.bulletFlow.Add(new List<TextBlock>());\n            }\n            this.bulletFlow[line].Add(textBlock);\n\n            Canvas.SetLeft(textBlock, this.Width);\n            var font = FontManager.Instance.GetFont(this.danmakuContainer.FontFamily.Source,\n                this.danmakuContainer.FontSize,\n                this.danmakuContainer.FontStyle);\n            Canvas.SetTop(textBlock, line * font.LineSpacing);\n        }\n\n        private void RemoveBullet(Bullet bullet)\n        {\n            var textBlock = this.danmakuContainer.Children.OfType<TextBlock>()\n                .FirstOrDefault(text => text.Tag == bullet);\n            if (textBlock != null)\n            {\n                this.danmakuContainer.Children.Remove(textBlock);\n            }\n        }\n\n        public class Bullet\n        {\n            public Bullet() : this(null)\n            {\n            }\n\n            public Bullet(string text) : this(text, Color.White)\n            {\n            }\n\n            public Bullet(string text, Color foreColor)\n            {\n                this.Text = text;\n                this.ForeColor = foreColor;\n            }\n\n            public string Text { get; set; }\n            public Color ForeColor { get; set; }\n            internal int Line { get; set; }\n        }\n\n        public class BulletCollection : Collection<Bullet>\n        {\n            public BulletCollection(UIDanmaku owner)\n            {\n                this.owner = owner;\n            }\n\n            private readonly UIDanmaku owner;\n\n            protected override void InsertItem(int index, Bullet item)\n            {\n                base.InsertItem(index, item);\n                this.owner.AddBullet(item);\n            }\n\n            protected override void SetItem(int index, Bullet item)\n            {\n                throw new NotSupportedException();\n            }\n\n            protected override void RemoveItem(int index)\n            {\n                Bullet item = this[index];\n                base.RemoveItem(index);\n                this.owner.RemoveBullet(item);\n            }\n\n            protected override void ClearItems()\n            {\n                base.ClearItems();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIGraphics.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public class UIGraphics\n    {\n        public static IList<RenderBlock<T>> LayoutNinePatch<T>(INinePatchResource<T> res, Point size)\n        {\n            var blocks = new List<RenderBlock<T>>(13);\n\n            Point nw = res.GetSize(res.NW);\n            Point n = res.GetSize(res.N);\n            Point ne = res.GetSize(res.NE);\n            Point w = res.GetSize(res.W);\n            Point e = res.GetSize(res.E);\n            Point sw = res.GetSize(res.SW);\n            Point s = res.GetSize(res.S);\n            Point se = res.GetSize(res.SE);\n            //计算框线\n            int[] x = new int[4] { 0, nw.X, size.X - ne.X, size.X };\n            int[] y = new int[4] { 0, nw.Y, size.Y - sw.Y, size.Y };\n\n            //绘制左上\n            blocks.Add(new RenderBlock<T>(res.NW, new Rectangle(x[0], y[0], x[1] - x[0], y[1] - y[0])));\n\n            //绘制上\n            if (nw.Y == n.Y)\n            {\n                blocks.Add(new RenderBlock<T>(res.N, new Rectangle(x[1], y[0], x[2] - x[1], y[1] - y[0])));\n            }\n            else if (nw.Y > n.Y)\n            {\n                blocks.Add(new RenderBlock<T>(res.N, new Rectangle(x[1], y[0], x[2] - x[1], n.Y)));\n                blocks.Add(new RenderBlock<T>(res.C, new Rectangle(x[1], n.Y, x[2] - x[1], y[1] - n.Y)));\n            }\n\n            //绘制右上\n            blocks.Add(new RenderBlock<T>(res.NE, new Rectangle(x[2], y[0], x[3] - x[2], y[1] - y[0])));\n\n            //绘制左\n            if (nw.X == w.X)\n            {\n                blocks.Add(new RenderBlock<T>(res.W, new Rectangle(x[0], y[1], x[1] - x[0], y[2] - y[1])));\n            }\n            else if (nw.X > w.X)\n            {\n                blocks.Add(new RenderBlock<T>(res.W, new Rectangle(x[0], y[1], w.X, y[2] - y[1])));\n                blocks.Add(new RenderBlock<T>(res.C, new Rectangle(w.X, y[1], x[1] - w.X, y[2] - y[1])));\n            }\n\n            //绘制中\n            blocks.Add(new RenderBlock<T>(res.C, new Rectangle(x[1], y[1], x[2] - x[1], y[2] - y[1])));\n\n            //绘制右\n            if (ne.X == e.X)\n            {\n                blocks.Add(new RenderBlock<T>(res.E, new Rectangle(x[2], y[1], x[3] - x[2], y[2] - y[1])));\n            }\n            else if (ne.X > e.X)\n            {\n                blocks.Add(new RenderBlock<T>(res.E, new Rectangle(x[3] - e.X, y[1], e.X, y[2] - y[1])));\n                blocks.Add(new RenderBlock<T>(res.C, new Rectangle(x[2], y[1], x[3] - x[2] - e.X, y[2] - y[1])));\n            }\n\n            //绘制左下\n            blocks.Add(new RenderBlock<T>(res.SW, new Rectangle(x[0], y[2], x[1] - x[0], y[3] - y[2])));\n\n            //绘制下\n            if (sw.Y == s.Y)\n            {\n                blocks.Add(new RenderBlock<T>(res.S, new Rectangle(x[1], y[2], x[2] - x[1], y[3] - y[2])));\n            }\n            else if (sw.Y > s.Y)\n            {\n                blocks.Add(new RenderBlock<T>(res.S, new Rectangle(x[1], y[3] - s.Y, x[2] - x[1], s.Y)));\n                blocks.Add(new RenderBlock<T>(res.C, new Rectangle(x[1], y[2], x[2] - x[1], y[3] - y[2] - s.Y)));\n            }\n\n            //绘制右下\n            blocks.Add(new RenderBlock<T>(res.SE, new Rectangle(x[2], y[2], x[3] - x[2], y[3] - y[2])));\n\n            return blocks;\n        }\n\n        public static IList<RenderBlock<T>> LayoutLCR<T>(INinePatchResource<T> res, Point size)\n        {\n            var blocks = new List<RenderBlock<T>>(3);\n            Point w = res.GetSize(res.W);\n            Point c = res.GetSize(res.C);\n            Point e = res.GetSize(res.E);\n\n            //计算框线\n            int[] x = new int[4] { 0, w.X, size.X - e.X, size.X };\n\n            //绘制左\n            blocks.Add(new RenderBlock<T>(res.W, new Rectangle(x[0], 0, x[1] - x[0], size.Y)));\n            //绘制中\n            blocks.Add(new RenderBlock<T>(res.C, new Rectangle(x[1], 0, x[2] - x[1], size.Y)));\n            //绘制右\n            blocks.Add(new RenderBlock<T>(res.E, new Rectangle(x[2], 0, x[3] - x[2], size.Y)));\n\n            return blocks;\n        }\n\n        public static IList<RenderBlock<T>> LayoutTCB<T>(INinePatchResource<T> res, Point size)\n        {\n            var blocks = new List<RenderBlock<T>>(3);\n            Point n = res.GetSize(res.N);\n            Point c = res.GetSize(res.C);\n            Point s = res.GetSize(res.S);\n\n            //计算框线\n            int[] y = new int[4] { 0, n.Y, size.Y - s.Y, size.Y };\n\n            //绘制上\n            blocks.Add(new RenderBlock<T>(res.N, new Rectangle(0, y[0], size.X, y[1] - y[0])));\n            //绘制中\n            blocks.Add(new RenderBlock<T>(res.C, new Rectangle(0, y[1], size.X, y[2] - y[1])));\n            //绘制下\n            blocks.Add(new RenderBlock<T>(res.S, new Rectangle(0, y[2], size.X, y[3] - y[2])));\n\n            return blocks;\n        }\n\n        public static void DrawNineForm(RenderEnv env, NineFormResource res, Vector2 position, Vector2 size)\n        {\n            SpriteBatchEx sprite = env.Sprite;\n            var blocks = LayoutNinePatch(res, size.ToPoint());\n\n            foreach (var block in blocks)\n            {\n                if (block.Texture != null && block.Rectangle.Width > 0 && block.Rectangle.Height > 0)\n                {\n                    Rectangle rect = new Rectangle(block.Rectangle.X + (int)position.X,\n                        block.Rectangle.Y + (int)position.Y,\n                        block.Rectangle.Width,\n                        block.Rectangle.Height);\n                    sprite.Draw(block.Texture, rect, Color.White);\n                }\n            }\n        }\n\n        public struct RenderBlock<T>\n        {\n            public RenderBlock(T texture, Rectangle rectangle)\n            {\n                this.Texture = texture;\n                this.Rectangle = rectangle;\n            }\n            public T Texture;\n            public Rectangle Rectangle;\n        }\n    }\n\n    public interface INinePatchResource<T>\n    {\n        T NW { get; }\n        T N { get; }\n        T NE { get; }\n        T W { get; }\n        T C { get; }\n        T E { get; }\n        T SW { get; }\n        T S { get; }\n        T SE { get; }\n\n        Point GetSize(T texture);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIHelper.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.Rendering;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Input;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing Microsoft.Xna.Framework.Graphics;\nusing System.Globalization;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    static class UIHelper\n    {\n        public static IDisposable RegisterClickEvent<T>(UIElement control, Func<UIElement, PointF, T> getItemFunc, Action<T> onClick)\n        {\n            var holder = new ClickEventHolder<T>(control)\n            {\n                GetItemFunc = getItemFunc,\n                ClickFunc = onClick,\n            };\n            holder.Register();\n            return holder;\n        }\n\n        public static TextureBase LoadTexture(Wz_Node node)\n        {\n            node = node?.GetLinkedSourceNode(PluginManager.FindWz);\n            var png = node.GetValueEx<Wz_Png>(null);\n            if (png != null)\n            {\n                return Engine.Instance.AssetManager.LoadTexture(null, node.FullPathToFile);\n            }\n            else\n            {\n                return null;\n            }\n        }\n\n        public static HitMap CreateHitMap(Texture2D texture)\n        {\n            HitMap hitMap = null;\n            byte[] colorData;\n            bool[] rowHit;\n            switch (texture.Format)\n            {\n                case SurfaceFormat.Color:\n                case SurfaceFormat.Bgra32:\n                    hitMap = new HitMap(texture.Width, texture.Height);\n                    colorData = new byte[texture.Width * texture.Height * 4];\n                    rowHit = new bool[texture.Width];\n                    texture.GetData(colorData);\n                    for (int y = 0; y < texture.Height; y++)\n                    {\n                        int rowStart = y * texture.Width * 4;\n                        for (int i = 0; i < rowHit.Length; i++)\n                        {\n                            rowHit[i] = colorData[rowStart + i * 4 + 3] != 0;\n                        }\n                        hitMap.SetRow(y, rowHit);\n                    }\n                    break;\n\n                case SurfaceFormat.Bgra4444:\n                    hitMap = new HitMap(texture.Width, texture.Height);\n                    colorData = new byte[texture.Width * texture.Height * 2];\n                    rowHit = new bool[texture.Width];\n                    texture.GetData(colorData);\n                    for (int y = 0; y < texture.Height; y++)\n                    {\n                        int rowStart = y * texture.Width * 2;\n                        for (int i = 0; i < rowHit.Length; i++)\n                        {\n                            rowHit[i] = colorData[rowStart + i * 2 + 1] >> 4 != 0;\n                        }\n                        hitMap.SetRow(y, rowHit);\n                    }\n                    break;\n\n                default:\n                    hitMap = new HitMap(true);\n                    break;\n            }\n            return hitMap;\n        }\n\n        public static IValueConverter CreateConverter(Func<object, object> convertFunc)\n        {\n            return new CustomConverter(convertFunc);\n        }\n\n        public static IValueConverter CreateConverter<TIn, TOut>(Func<TIn, TOut> convertFunc)\n        {\n            var wrapFunc = new Func<object, object>(o =>\n            {\n                return (convertFunc != null && o is TIn) ? (object)convertFunc((TIn)o) : null;\n            });\n            return new CustomConverter(wrapFunc);\n        }\n\n        class ClickEventHolder<T> : IDisposable\n        {\n            public ClickEventHolder(UIElement control)\n            {\n                this.Control = control;\n            }\n\n            public UIElement Control { get; private set; }\n            public Func<UIElement, PointF, T> GetItemFunc { get; set; }\n            public Action<T> ClickFunc { get; set; }\n\n            private T item;\n\n            public void Register()\n            {\n                this.Control.MouseDown += this.OnMouseDown;\n                this.Control.MouseUp += this.OnMouseUp;\n            }\n\n            public void Deregister()\n            {\n                this.Control.MouseDown -= this.OnMouseDown;\n                this.Control.MouseUp -= this.OnMouseUp;\n            }\n\n            private void OnMouseDown(object sender, MouseButtonEventArgs e)\n            {\n                if (GetItemFunc != null && e.ChangedButton == EmptyKeys.UserInterface.Input.MouseButton.Left)\n                {\n                    this.item = GetItemFunc.Invoke(this.Control, e.GetPosition(this.Control));\n                }\n            }\n\n            private void OnMouseUp(object sender, MouseButtonEventArgs e)\n            {\n                if (GetItemFunc != null && e.ChangedButton == EmptyKeys.UserInterface.Input.MouseButton.Left)\n                {\n                    T item = GetItemFunc.Invoke(this.Control, e.GetPosition(this.Control));\n                    if (item != null && object.Equals(item, this.item))\n                    {\n                        this.ClickFunc?.Invoke(item);\n                    }\n                    this.item = default(T);\n                }\n            }\n\n            void IDisposable.Dispose()\n            {\n                this.Deregister();\n            }\n        }\n\n        class CustomConverter : IValueConverter\n        {\n            public CustomConverter(Func<object, object> convertFunc)\n            {\n                this.convertFunc = convertFunc;\n            }\n\n            private Func<object, object> convertFunc;\n\n            public object Convert(object value, Type target, object parameter, CultureInfo culture)\n            {\n                return convertFunc?.Invoke(value);\n            }\n\n            public object ConvertBack(object value, Type target, object parameter, CultureInfo culture)\n            {\n                return convertFunc?.Invoke(value);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIMiniMap.cs",
    "content": "﻿#if MapRenderV1\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing Resource = CharaSimResource.Resource;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    public class UIMiniMap\n    {\n        public UIMiniMap(GraphicsDevice graphicsDevice)\n        {\n            this.frame = new Dictionary<string, Texture2D>();\n            this.frame[\"n\"] =  Resource.UIWindow2_img_MiniMap_MaxMap_n.ToTexture(graphicsDevice);\n            this.frame[\"ne\"] = Resource.UIWindow2_img_MiniMap_MaxMap_ne.ToTexture(graphicsDevice);\n            this.frame[\"e\"] =  Resource.UIWindow2_img_MiniMap_MaxMap_e.ToTexture(graphicsDevice);\n            this.frame[\"se\"] = Resource.UIWindow2_img_MiniMap_MaxMap_se.ToTexture(graphicsDevice);\n            this.frame[\"s\"] =  Resource.UIWindow2_img_MiniMap_MaxMap_s.ToTexture(graphicsDevice);\n            this.frame[\"sw\"] = Resource.UIWindow2_img_MiniMap_MaxMap_sw.ToTexture(graphicsDevice);\n            this.frame[\"w\"] =  Resource.UIWindow2_img_MiniMap_MaxMap_w.ToTexture(graphicsDevice);\n            this.frame[\"nw\"] = Resource.UIWindow2_img_MiniMap_MaxMap_nw.ToTexture(graphicsDevice);\n            this.frame[\"c\"] =  Resource.UIWindow2_img_MiniMap_MaxMap_c.ToTexture(graphicsDevice);\n            this.frame[\"nw2\"] = Resource.UIWindow2_img_MiniMap_MaxMap_nw2.ToTexture(graphicsDevice);\n\n            this.resource = new NineFormResource();\n\n            this.MapMarkVisible = true;\n            this.Size = new Vector2(300, 300);\n            this.mapMarkOrigin = new Vector2(7, 17);\n            this.miniMapOrigin = new Vector2(this.frame[\"w\"].Width, this.frame[\"n\"].Height);\n            this.minSize = new Vector2(this.frame[\"nw\"].Width + this.frame[\"ne\"].Width, this.frame[\"nw\"].Height + this.frame[\"sw\"].Height);\n            this.streetNameOrigin = new Vector2(48, 20);\n            this.mapNameOrigin = new Vector2(48, 34);\n\n            this.Portals = new List<Vector2>();\n            this.Transports = new List<Vector2>();\n        }\n\n        private Dictionary<string, Texture2D> frame;\n        private NineFormResource resource;\n        private readonly Vector2 mapMarkOrigin;\n        private readonly Vector2 miniMapOrigin;\n        private readonly Vector2 minSize;\n        private readonly Vector2 streetNameOrigin;\n        private readonly Vector2 mapNameOrigin;\n\n        private bool mapMarkVisible;\n\n        public bool MapMarkVisible\n        {\n            get { return this.mapMarkVisible; }\n            set\n            {\n                this.mapMarkVisible = value;\n                this.UpdateResource();\n            }\n        }\n\n        public Vector2 Size { get; set; }\n        public Vector2 Position { get; set; }\n        public bool Visible { get; set; }\n        public MiniMap MiniMap { get; set; }\n        public String MapName { get; set; }\n        public String StreetName { get; set; }\n\n        public List<Vector2> Portals { get; private set; }\n        public List<Vector2> Transports { get; private set; }\n        public bool ResourceLoaded { get; private set; }\n\n        public XnaFont MapNameFont { get; set; }\n\n        private Texture2D texPortal;\n        private Texture2D texTransport;\n\n        public Rectangle MinimapRectangle\n        {\n            get\n            {\n                int x = (int)this.miniMapOrigin.X,\n                    y = (int)this.miniMapOrigin.Y;\n                int w = (int)this.Size.X - this.resource.W.Width - this.resource.E.Width,\n                    h = (int)this.Size.Y - this.resource.N.Height - this.resource.S.Height;\n                return new Rectangle(x, y, w, h);\n            }\n        }\n        private void UpdateResource()\n        {\n            this.resource.N = this.frame[\"n\"];\n            this.resource.NE = this.frame[\"ne\"];\n            this.resource.E = this.frame[\"e\"];\n            this.resource.SE = this.frame[\"se\"];\n            this.resource.S = this.frame[\"s\"];\n            this.resource.SW = this.frame[\"sw\"];\n            this.resource.W = this.frame[\"w\"];\n            //this.resource.C = this.frame[\"c\"];\n            this.resource.C = null;\n            if (this.mapMarkVisible)\n            {\n                this.resource.NW = this.frame[\"nw\"];\n            }\n            else\n            {\n                this.resource.NW = this.frame[\"nw2\"];\n            }\n        }\n\n        public void Draw(RenderEnv env, GameTime gameTime)\n        {\n            //计算UI偏移\n            Matrix trans = Matrix.CreateTranslation(this.Position.X, this.Position.Y, 0);\n\n            //绘制外框\n            env.GraphicsDevice.ScissorRectangle = new Rectangle((int)this.Position.X, (int)this.Position.Y, (int)this.Size.X, (int)this.Size.Y);\n            env.GraphicsDevice.RasterizerState = StateEx.Scissor();\n\n            env.Sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: trans);\n            env.Sprite.FillRectangle(this.MinimapRectangle, new Color(Color.Black, 0.7f));\n            UIGraphics.DrawNineForm(env, this.resource, Vector2.Zero, this.Size);\n            env.Sprite.End();\n\n            env.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;\n\n            //绘制标题\n            if (this.MapNameFont != null)\n            {\n                env.Sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: trans);\n                if (this.StreetName != null)\n                {\n                    env.Sprite.DrawStringEx(this.MapNameFont, this.StreetName, this.streetNameOrigin, Color.White);\n                }\n                if (this.MapName != null)\n                {\n                    env.Sprite.DrawStringEx(this.MapNameFont, this.MapName, this.mapNameOrigin, Color.White);\n                }\n                env.Sprite.End();\n            }\n\n            //绘制小地图\n            if (this.MiniMap != null)\n            {\n                //绘制小地图标记\n                env.Sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: trans);\n                if (MapMarkVisible && this.MiniMap.MapMark != null)\n                {\n                    env.Sprite.Draw(this.MiniMap.MapMark, mapMarkOrigin, Color.White);\n                }\n                env.Sprite.End();\n\n                if (this.MiniMap.Canvas != null)\n                {\n                    //计算世界地图到小地图的偏移\n                    Texture2D canvas = this.MiniMap.Canvas;\n                    Rectangle fromRect;\n                    if (this.MiniMap.Width > 0 && this.MiniMap.Height > 0)\n                    {\n                        fromRect = new Rectangle(-this.MiniMap.CenterX, -this.MiniMap.CenterY, this.MiniMap.Width, this.MiniMap.Height);\n                    }\n                    else\n                    {\n                        fromRect = env.Camera.WorldRect;\n                    }\n                    Rectangle toRect = new Rectangle(0, 0, canvas.Width, canvas.Height);\n                    Matrix worldToMinimap = Matrix.CreateTranslation(-fromRect.X, -fromRect.Y, 0)\n                            * Matrix.CreateScale(1f / fromRect.Width * toRect.Width, 1f / fromRect.Height * toRect.Height, 0)\n                            * Matrix.CreateTranslation(toRect.X, toRect.Y, 0);\n\n                    //计算小地图区域的二次偏移\n                    Rectangle rect = this.MinimapRectangle;\n                    Vector2 offset = new Vector2((rect.Width - canvas.Width) / 2, (rect.Height - canvas.Height) / 2);\n                    worldToMinimap *= Matrix.CreateTranslation(offset.X, offset.Y, 0);\n\n                    //设置剪裁区域\n                    env.GraphicsDevice.ScissorRectangle = new Rectangle(\n                      (int)this.Position.X + rect.X, (int)this.Position.Y + rect.Y, rect.Width, rect.Height);\n                    env.GraphicsDevice.RasterizerState = StateEx.Scissor();\n\n                    //绘制小地图本体\n                    env.Sprite.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: trans);\n                    env.Sprite.Draw(this.MiniMap.Canvas, miniMapOrigin + offset, Color.White);\n\n                    if (this.ResourceLoaded)\n                    {\n                        Vector2 iconOrigin = new Vector2(0, 5);\n                        //绘制一般传送门\n                        if (this.Portals.Count > 0 && this.texPortal != null)\n                        {\n                            Vector2 origin = new Vector2(this.texPortal.Width / 2, this.texPortal.Height / 2);\n                            foreach (var portal in this.Portals)\n                            {\n                                Vector2 position = Vector2.Transform(portal, worldToMinimap);\n                                position = miniMapOrigin + position - origin - iconOrigin;\n                                position = MathHelper2.Round(position);\n                                env.Sprite.Draw(this.texPortal, position, Color.White);\n                            }\n                        }\n\n                        //绘制地图内传送门\n                        if (this.Transports.Count > 0 && this.texTransport != null)\n                        {\n                            Vector2 origin = new Vector2(this.texTransport.Width / 2, this.texTransport.Height / 2);\n                            foreach (var portal in this.Transports)\n                            {\n                                Vector2 position = Vector2.Transform(portal, worldToMinimap);\n                                position = miniMapOrigin + position - origin - iconOrigin;\n                                position = MathHelper2.Round(position);\n                                env.Sprite.Draw(this.texTransport, position, Color.White);\n                            }\n                        }\n                    }\n\n                    //绘制摄像机区域框\n                    Rectangle cameraRect = MathHelper2.Transform(env.Camera.ClipRect, worldToMinimap);\n                    cameraRect.X += (int)miniMapOrigin.X;\n                    cameraRect.Y += (int)miniMapOrigin.Y;\n                    env.Sprite.DrawRectangle(cameraRect, Color.Yellow);\n                    env.Sprite.End();\n                    env.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;\n                }\n            }\n\n        }\n\n        public void UpdateSize()\n        {\n            Vector2 minimapSize;\n            if (this.MiniMap == null || this.MiniMap.Canvas == null)\n            {\n                minimapSize = Vector2.Zero;\n            }\n            else\n            {\n                Texture2D tex = this.MiniMap.Canvas;\n                minimapSize = new Vector2(tex.Width, tex.Height);\n            }\n\n            //计算小地图size\n            int top = this.resource.N.Height,\n                bottom = this.resource.S.Height,\n                left = this.resource.W.Width,\n                right = this.resource.E.Width;\n\n            minimapSize = new Vector2(left + right + minimapSize.X,\n               top + bottom + minimapSize.Y);\n\n            //计算地图名称size\n            float mapNameRight = this.mapNameOrigin.X;\n            if (this.MapNameFont != null)\n            {\n                if (this.MapName != null)\n                {\n                    mapNameRight = Math.Max(mapNameRight,\n                        this.mapNameOrigin.X + this.MapNameFont.MeasureString(this.MapName).X);\n                }\n                if (this.StreetName != null)\n                {\n                    mapNameRight = Math.Max(mapNameRight,\n                        this.mapNameOrigin.X + this.MapNameFont.MeasureString(this.StreetName).X);\n                }\n            }\n            mapNameRight += this.resource.E.Width;\n\n            this.Size = new Vector2(MathHelper2.Max(this.minSize.X, minimapSize.X, mapNameRight),\n                MathHelper2.Max(this.minSize.Y, minimapSize.Y));\n        }\n\n        public void LoadResource(GraphicsDevice graphicsDevice)\n        {\n            Wz_Node minimapNode = PluginBase.PluginManager.FindWz(\"Map\\\\MapHelper.img\\\\minimap\");\n            if (minimapNode != null)\n            {\n                Wz_Node portalNode = minimapNode.FindNodeByPath(\"portal\");\n                Wz_Node transportNode = minimapNode.FindNodeByPath(\"transport\");\n\n                Wz_Png png;\n                if ((png = portalNode.GetValueEx<Wz_Png>(null)) != null)\n                {\n                    this.texPortal = TextureLoader.PngToTexture(graphicsDevice, png);\n                }\n\n                if ((png = transportNode.GetValueEx<Wz_Png>(null)) != null)\n                {\n                    this.texTransport = TextureLoader.PngToTexture(graphicsDevice, png);\n                }\n            }\n            this.ResourceLoaded = true;\n        }\n    }\n}\n#endif"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIMinimap2.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Media.Imaging;\n\nusing Microsoft.Xna.Framework;\nusing WzComparerR2.WzLib;\nusing Res = CharaSimResource.Resource;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\nusing MathHelper = Microsoft.Xna.Framework.MathHelper;\nusing System.Globalization;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class UIMinimap2 : WindowEx\n    {\n        public static readonly DependencyProperty StreetNameProperty = DependencyProperty.Register(\"StreetName\", typeof(string), typeof(UIMinimap2), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty MapNameProperty = DependencyProperty.Register(\"MapName\", typeof(string), typeof(UIMinimap2), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty MapMarkProperty = DependencyProperty.Register(\"MapMark\", typeof(TextureBase), typeof(UIMinimap2), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty MinimapCanvasProperty = DependencyProperty.Register(\"MinimapCanvas\", typeof(TextureBase), typeof(UIMinimap2), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty MapRegionProperty = DependencyProperty.Register(\"MapRegion\", typeof(Rect), typeof(UIMinimap2), new FrameworkPropertyMetadata(new Rect()));\n        public static readonly DependencyProperty CameraViewPortProperty = DependencyProperty.Register(\"CameraViewPort\", typeof(Rect), typeof(UIMinimap2), new FrameworkPropertyMetadata(new Rect()));\n        public static readonly DependencyProperty IconsProperty = DependencyProperty.Register(\"Icons\", typeof(List<MapIcon>), typeof(UIMinimap2), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty CameraRegionVisibleProperty = DependencyProperty.Register(\"CameraRegionVisible\", typeof(bool), typeof(UIMinimap2), new FrameworkPropertyMetadata(false));\n\n        public UIMinimap2()\n        {\n            this.Focusable = false;\n            this.Icons = new List<MapIcon>();\n        }\n\n        public string StreetName\n        {\n            get { return (string)GetValue(StreetNameProperty); }\n            set { SetValue(StreetNameProperty, value); }\n        }\n\n        public string MapName\n        {\n            get { return (string)GetValue(MapNameProperty); }\n            set { SetValue(MapNameProperty, value); }\n        }\n\n        public TextureBase MapMark\n        {\n            get { return (TextureBase)GetValue(MapMarkProperty); }\n            set { SetValue(MapMarkProperty, value); }\n        }\n\n        public TextureBase MinimapCanvas\n        {\n            get { return (TextureBase)GetValue(MinimapCanvasProperty); }\n            set { SetValue(MinimapCanvasProperty, value); }\n        }\n\n        public Rect MapRegion\n        {\n            get { return (Rect)GetValue(MapRegionProperty); }\n            set { SetValue(MapRegionProperty, value); }\n        }\n\n        public Rect CameraViewPort\n        {\n            get { return (Rect)GetValue(CameraViewPortProperty); }\n            set { SetValue(CameraViewPortProperty, value); }\n        }\n\n        public List<MapIcon> Icons\n        {\n            get { return (List<MapIcon>)GetValue(IconsProperty); }\n            private set { SetValue(IconsProperty, value); }\n        }\n\n        public bool CameraRegionVisible\n        {\n            get { return (bool)GetValue(CameraRegionVisibleProperty); }\n            set { SetValue(CameraRegionVisibleProperty, value); }\n        }\n\n        public MapArea MapAreaControl { get; private set; }\n        public bool Mirror\n        {\n            get { return mirror; }\n            set\n            {\n                if (value != mirror)\n                {\n                    this.resource.LoadResource(Engine.Instance.AssetManager, value);\n                }\n                mirror = value;\n            }\n        }\n\n        private UIMinimapResource resource;\n\n        private TextBlock lblStreetName;\n        private TextBlock lblMapName;\n\n        private bool mirror;\n\n        protected override void InitializeComponents()\n        {\n            UIMinimapResource resource = new UIMinimapResource();\n            this.MinWidth = resource.NW.Width + resource.NE.Width;\n            this.MinHeight = resource.NW.Height + resource.SW.Height;\n            this.Width = this.MinWidth;\n            this.Height = this.MinHeight;\n            this.MaxWidth = 1000;\n            this.MaxHeight = 1000;\n\n            Canvas canvas = new Canvas();\n            canvas.Background = new NinePatchBrush() { Resource = resource };\n            canvas.SetBinding(Canvas.WidthProperty, new Binding(UIMinimap2.WidthProperty) { Source = this });\n            canvas.SetBinding(Canvas.HeightProperty, new Binding(UIMinimap2.HeightProperty) { Source = this });\n            canvas.Parent = this;\n            this.Content = canvas;\n\n            Border title = new Border();\n            title.Height = 17;\n            title.SetBinding(Canvas.WidthProperty, new Binding(Canvas.WidthProperty) { Source = canvas });\n            canvas.Children.Add(title);\n            this.SetDragTarget(title);\n\n            Border mapAreaBorder = new Border();\n            mapAreaBorder.Background = new SolidColorBrush(new ColorW(0f, 0f, 0f, 0.6f));\n            Canvas.SetLeft(mapAreaBorder, resource.W.Width);\n            Canvas.SetTop(mapAreaBorder, resource.N.Height);\n            canvas.Children.Add(mapAreaBorder);\n\n            MapArea mapArea = new MapArea();\n            mapArea.Name = \"mapArea\";\n            mapArea.SetBinding(MapArea.WidthProperty, new Binding(Border.WidthProperty) { Source = mapAreaBorder, Mode = BindingMode.TwoWay });\n            mapArea.SetBinding(MapArea.HeightProperty, new Binding(Border.HeightProperty) { Source = mapAreaBorder, Mode = BindingMode.TwoWay });\n            mapAreaBorder.Child = mapArea;\n            this.SetBinding(UIMinimap2.MinimapCanvasProperty, new Binding(\"MinimapTexture\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.SetBinding(UIMinimap2.MapRegionProperty, new Binding(\"MapRegion\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.SetBinding(UIMinimap2.CameraViewPortProperty, new Binding(\"CameraViewPort\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.SetBinding(UIMinimap2.IconsProperty, new Binding(\"Icons\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.SetBinding(UIMinimap2.CameraRegionVisibleProperty, new Binding(\"CameraRegionVisible\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n\n            Image imgMapMark = new Image();\n            imgMapMark.SetBinding(Image.SourceProperty, new Binding(\"MapMark\") { Source = this, Converter = new TextureImageConverter() });\n            Canvas.SetLeft(imgMapMark, 7);\n            Canvas.SetTop(imgMapMark, 17);\n            canvas.Children.Add(imgMapMark);\n\n            TextBlock lblStreetName = new TextBlock();\n            lblStreetName.Name = \"lblStreetName\";\n            lblStreetName.FontStyle = FontStyle.Bold;\n            lblStreetName.Foreground = Brushes.White;\n            lblStreetName.Padding = new Thickness(0, 0, 6, 0);\n            lblStreetName.SetBinding(TextBlock.TextProperty, new Binding(\"StreetName\") { Source = this });\n            lblStreetName.SetResourceReference(TextBlock.FontFamilyProperty, MapRenderResourceKey.DefaultFontFamily);\n            lblStreetName.SetResourceReference(TextBlock.FontSizeProperty, MapRenderResourceKey.DefaultFontSize);\n            Canvas.SetLeft(lblStreetName, 48);\n            Canvas.SetTop(lblStreetName, 20);\n            canvas.Children.Add(lblStreetName);\n\n            TextBlock lblMapName = new TextBlock();\n            lblMapName.Name = \"lblMapName\";\n            lblMapName.FontStyle = FontStyle.Bold;\n            lblMapName.Foreground = Brushes.White;\n            lblMapName.Padding = new Thickness(0, 0, 6, 0);\n            lblMapName.SetBinding(TextBlock.TextProperty, new Binding(\"MapName\") { Source = this });\n            lblMapName.SetResourceReference(TextBlock.FontFamilyProperty, MapRenderResourceKey.DefaultFontFamily);\n            lblMapName.SetResourceReference(TextBlock.FontSizeProperty, MapRenderResourceKey.DefaultFontSize);\n            Canvas.SetLeft(lblMapName, 48);\n            Canvas.SetTop(lblMapName, 34);\n            canvas.Children.Add(lblMapName);\n\n            ComboBox cb = new ComboBox();\n            cb.Background = new LCRBrush() { Resource = new ComboBoxBackgroundResource() };\n            cb.Width = 150;\n            cb.Height = 17;\n            cb.Focusable = false;\n            cb.Visibility = Visibility.Hidden;\n            canvas.Children.Add(cb);\n\n            this.resource = resource;\n            this.MapAreaControl = mapArea;\n            this.lblStreetName = lblStreetName;\n            this.lblMapName = lblMapName;\n\n            FontManager.Instance.AddFont(lblStreetName.FontFamily.Source, lblStreetName.FontSize, lblStreetName.FontStyle);\n            FontManager.Instance.AddFont(lblMapName.FontFamily.Source, lblMapName.FontSize, lblMapName.FontStyle);\n            base.InitializeComponents();\n        }\n\n        protected override void OnPropertyChanged(DependencyProperty property)\n        {\n            base.OnPropertyChanged(property);\n\n            if (property == MinimapCanvasProperty\n                || property == MapNameProperty\n                || property == StreetNameProperty)\n            {\n                CalcControlSize();\n            }\n        }\n\n        private void CalcControlSize()\n        {\n            var canvas = this.MinimapCanvas;\n            Size minimapSize = new Size(canvas?.Width ?? 0, canvas?.Height ?? 0);\n\n            //计算边框\n            int top = this.resource.N.Height,\n                bottom = this.resource.S.Height,\n                left = this.resource.W.Width,\n                right = this.resource.E.Width;\n\n            //计算实际大小\n            Size desireSize = new Size(minimapSize.Width + left + right, minimapSize.Height + top + bottom);\n\n            //计算标题\n            if (this.lblStreetName.Text != null)\n            {\n                this.lblStreetName.Measure(new Size(double.MaxValue, double.MaxValue));\n                var lblRight = Canvas.GetLeft(this.lblStreetName) + this.lblStreetName.DesiredSize.Width;\n                desireSize.Width = Math.Max(desireSize.Width, lblRight);\n            }\n            if (this.lblMapName.Text != null)\n            {\n                this.lblMapName.Measure(new Size(double.MaxValue, double.MaxValue));\n                var lblRight = Canvas.GetLeft(this.lblMapName) + this.lblMapName.DesiredSize.Width;\n                desireSize.Width = Math.Max(desireSize.Width, lblRight);\n            }\n\n            this.Width = MathHelper.Clamp(desireSize.Width, this.MinWidth, this.MaxWidth);\n            this.Height = MathHelper.Clamp(desireSize.Height, this.MinHeight, this.MaxHeight);\n            this.MapAreaControl.Width = Math.Max(0, this.Width - left - right);\n            this.MapAreaControl.Height = Math.Max(0, this.Height - top - bottom);\n        }\n\n        public class MapArea : Control, ITooltipTarget\n        {\n            public MapArea()\n            {\n                iconRectCache = new List<IconRect>();\n            }\n\n            public TextureBase MinimapTexture { get; set; }\n            public Rect MapRegion { get; set; }\n            public Rect CameraViewPort { get; set; }\n            public List<MapIcon> Icons { get; set; }\n            public bool CameraRegionVisible { get; set; }\n\n            private List<IconRect> iconRectCache;\n            private PointF canvasPosCache;\n\n            protected override void OnDraw(Renderer spriterenderer, double elapsedGameTime, float opacity)\n            {\n                base.OnDraw(spriterenderer, elapsedGameTime, opacity);\n\n                if (this.MinimapTexture == null || this.MapRegion.Width <= 0 || this.MapRegion.Height <= 0)\n                {\n                    return;\n                }\n\n                var pos = this.RenderPosition;\n                var size = this.RenderSize;\n                spriterenderer.End();\n                spriterenderer.BeginClipped(new Rect(pos.X, pos.Y, size.Width, size.Height));\n\n                Matrix transform;\n                Vector2 cameraPos;\n                Vector2 cameraSize;\n                //绘制canvas\n                {\n                    var canvas = this.MinimapTexture;\n                    var mapRegion = this.MapRegion;\n\n                    //计算世界坐标系到小地图坐标系的转换\n                    transform = Matrix.CreateTranslation(-mapRegion.X, -mapRegion.Y, 0)\n                            * Matrix.CreateScale((float)canvas.Width / mapRegion.Width, (float)canvas.Height / mapRegion.Height, 0);\n                    var vp = this.CameraViewPort;\n                    cameraPos = Vector2.Transform(new Vector2(vp.X, vp.Y), transform);\n                    cameraSize = new Vector2(vp.Width * ((float)canvas.Width / mapRegion.Width), vp.Height * ((float)canvas.Height / mapRegion.Height));\n\n                    //计算小地图显示位置\n                    PointF canvasPos = new PointF(-this.canvasPosCache.X, -this.canvasPosCache.Y);\n\n                    if (this.Width >= canvas.Width)\n                    {\n                        canvasPos.X = (this.Width - canvas.Width) / 2;\n                    }\n                    else\n                    {\n                        if (cameraPos.X < canvasPos.X)\n                        {\n                            canvasPos.X = cameraPos.X;\n                        }\n                        else if (cameraPos.X + cameraSize.X > canvasPos.X + this.Width)\n                        {\n                            canvasPos.X = cameraPos.X + cameraSize.X - this.Width;\n                        }\n                        canvasPos.X = -MathHelper.Clamp(canvasPos.X, 0, canvas.Width - this.Width);\n                    }\n                    if (this.Height >= canvas.Height)\n                    {\n                        canvasPos.Y = (this.Height - canvas.Height) / 2;\n                    }\n                    else\n                    {\n                        if (cameraPos.Y < canvasPos.Y)\n                        {\n                            canvasPos.Y = cameraPos.Y;\n                        }\n                        else if (cameraPos.Y + cameraSize.Y > canvasPos.Y + this.Height)\n                        {\n                            canvasPos.Y = cameraPos.Y + cameraSize.Y - this.Height;\n                        }\n                        canvasPos.Y = -MathHelper.Clamp(canvasPos.Y, 0, canvas.Height - this.Height);\n                    }\n\n                    this.canvasPosCache = canvasPos;\n                    //计算全局坐标\n                    canvasPos = new PointF(pos.X + canvasPos.X, pos.Y + canvasPos.Y);\n\n\n                    transform *= Matrix.CreateTranslation(canvasPos.X, canvasPos.Y, 0);\n\n                    //绘制小地图\n                    spriterenderer.Draw(canvas, canvasPos, new Size(canvas.Width, canvas.Height), new ColorW(1f, 1f, 1f, opacity), false);\n                }\n\n                /* 绘制框线*/\n                if (CameraRegionVisible)\n                {\n                    using (var gb = spriterenderer.CreateGeometryBuffer())\n                    {\n                        var pts = new List<PointF>();\n                        pts.Add(new PointF(cameraPos.X, cameraPos.Y));\n                        pts.Add(new PointF(cameraPos.X + cameraSize.X, cameraPos.Y));\n                        pts.Add(new PointF(cameraPos.X + cameraSize.X, cameraPos.Y + cameraSize.Y));\n                        pts.Add(new PointF(cameraPos.X, cameraPos.Y + cameraSize.Y));\n                        pts.Add(pts[0]);\n                        gb.FillColor(pts, GeometryPrimitiveType.LineStrip);\n\n                        spriterenderer.DrawGeometryColor(gb,\n                            new PointF(pos.X + canvasPosCache.X, pos.Y + canvasPosCache.Y),\n                            new ColorW(255, 255, 0), opacity, 0f);\n                    }\n                }\n\n                //绘制小图标\n                Func<TextureBase, PointF, Rect> drawIconFunc = (texture, worldPos) =>\n                {\n                    var iconPos = Vector2.Transform(new Vector2(worldPos.X, worldPos.Y), transform);\n                    var posF = new PointF(\n                        Math.Round(iconPos.X - texture.Width / 2 - 0),\n                        Math.Round(iconPos.Y - texture.Height / 2 - 5));\n                    var iconSize = new Size(texture.Width, texture.Height);\n                    spriterenderer.Draw(texture, posF, iconSize, new ColorW(1f, 1f, 1f, opacity), false);\n                    return new Rect(posF.X - pos.X, posF.Y - pos.Y, iconSize.Width, iconSize.Height);\n                };\n\n                iconRectCache.Clear();\n                foreach (var icon in this.Icons)\n                {\n                    switch (icon.IconType)\n                    {\n                        case IconType.Portal:\n                            {\n                                var texture = this.FindResource(\"portal\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.EnchantPortal:\n                            {\n                                var texture = this.FindResource(\"enchantportal\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.HiddenPortal:\n                            {\n                                var texture = this.FindResource(\"hiddenportal\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.Transport:\n                            {\n                                var texture = this.FindResource(\"transport\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.Npc:\n                            {\n                                var texture = this.FindResource(\"npc\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.EventNpc:\n                            {\n                                var texture = this.FindResource(\"eventnpc\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.Shop:\n                            {\n                                var texture = this.FindResource(\"shop\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.Trunk:\n                            {\n                                var texture = this.FindResource(\"trunk\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.ArrowUp:\n                            {\n                                var texture = this.FindResource(\"arrowup\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n\n                        case IconType.Another:\n                            {\n                                var texture = this.FindResource(\"another\") as TextureBase;\n                                if (texture != null)\n                                {\n                                    var rect = drawIconFunc(texture, icon.WorldPosition);\n                                    iconRectCache.Add(new IconRect() { Rect = rect, Tooltip = icon.Tooltip });\n                                }\n                            }\n                            break;\n                    }\n                }\n\n                spriterenderer.EndClipped();\n                spriterenderer.Begin();\n            }\n\n            public object GetTooltipTarget(PointF mouseLocation)\n            {\n                foreach (var iconRect in this.iconRectCache.Reverse<IconRect>())\n                {\n                    if (iconRect.Rect.Contains(mouseLocation))\n                    {\n                        return iconRect.Tooltip;\n                    }\n                }\n                return null;\n            }\n\n            public void LoadWzResource()\n            {\n                var mapHelperNode = PluginBase.PluginManager.FindWz(\"Map/MapHelper.img/minimap\");\n                Action<string> addResource = (key) =>\n                {\n                    Wz_Node resNode = mapHelperNode.Nodes[key];\n                    var texture = UIHelper.LoadTexture(resNode);\n                    if (texture != null)\n                    {\n                        this.Resources[key] = texture;\n                    }\n                };\n\n                if (mapHelperNode != null)\n                {\n                    addResource(\"transport\");\n                    addResource(\"portal\");\n                    addResource(\"enchantportal\");\n                    addResource(\"hiddenportal\");\n                    addResource(\"npc\");\n                    addResource(\"eventnpc\");\n                    addResource(\"shop\");\n                    addResource(\"trunk\");\n                    addResource(\"arrowup\");\n                    addResource(\"another\");\n                }\n            }\n\n            private struct IconRect\n            {\n                public Rect Rect;\n                public object Tooltip;\n            }\n        }\n\n        public class MapIcon\n        {\n            public IconType IconType { get; set; }\n            public PointF WorldPosition { get; set; }\n            public object Tooltip { get; set; }\n        }\n\n        public enum IconType\n        {\n            Unknown = 0,\n            Portal,\n            EnchantPortal,\n            HiddenPortal,\n            Transport,\n            Npc,\n            EventNpc,\n            Shop,\n            Trunk,\n            ArrowUp,\n            Another,\n        }\n\n        private sealed class UIMinimapResource : INinePatchResource<TextureBase>\n        {\n            public UIMinimapResource()\n            {\n                this.LoadResource(Engine.Instance.AssetManager);\n            }\n\n            public bool HasIcon { get; set; }\n\n            public TextureBase NW1 { get; set; }\n            public TextureBase NW2 { get; set; }\n\n            /// <summary>\n            /// 左上\n            /// </summary>\n            public TextureBase NW\n            {\n                get { return this.HasIcon ? NW1 : NW2; }\n            }\n            /// <summary>\n            /// 上。\n            /// </summary>\n            public TextureBase N { get; set; }\n            /// <summary>\n            /// 右上。5\n            /// </summary>\n            public TextureBase NE { get; set; }\n\n            /// <summary>\n            /// 左。\n            /// </summary>\n            public TextureBase W { get; set; }\n            /// <summary>\n            /// 中。\n            /// </summary>\n            public TextureBase C { get; set; }\n            /// <summary>\n            /// 右。\n            /// </summary>\n            public TextureBase E { get; set; }\n\n            /// <summary>\n            /// 左下。\n            /// </summary>\n            public TextureBase SW { get; set; }\n            /// <summary>\n            /// 下。\n            /// </summary>\n            public TextureBase S { get; set; }\n            /// <summary>\n            /// 右下。\n            /// </summary>\n            public TextureBase SE { get; set; }\n\n            public Microsoft.Xna.Framework.Point GetSize(TextureBase texture)\n            {\n                return new Microsoft.Xna.Framework.Point(texture.Width, texture.Height);\n            }\n\n            public void LoadResource(AssetManager assetManager, bool mirror = false)\n            {\n                if (!mirror)\n                {\n                    this.NW1 = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_nw));\n                    this.NW2 = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_nw2));\n                    this.N = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_n));\n                    this.NE = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_ne));\n                    this.W = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_w));\n                    //this.C = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_c);\n                    this.E = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_e));\n                    this.SW = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_sw));\n                    this.S = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_s));\n                    this.SE = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMap_se));\n                }\n                else\n                {\n                    this.NW1 = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_nw));\n                    this.NW2 = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_nw2));\n                    this.N = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_n));\n                    this.NE = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_ne));\n                    this.W = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_w));\n                    //this.C = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_c);\n                    this.E = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_e));\n                    this.SW = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_sw));\n                    this.S = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_s));\n                    this.SE = assetManager.LoadTexture(null, nameof(Res.UIWindow2_img_MiniMap_MaxMapMirror_se));\n                }\n            }\n        }\n\n        private sealed class ComboBoxBackgroundResource : INinePatchResource<TextureBase>\n        {\n            public ComboBoxBackgroundResource()\n            {\n                this.LoadResource(Engine.Instance.AssetManager);\n            }\n\n            public TextureBase Left { get; set; }\n            public TextureBase Center { get; set; }\n            public TextureBase Right { get; set; }\n\n            public Microsoft.Xna.Framework.Point GetSize(TextureBase texture)\n            {\n                return new Microsoft.Xna.Framework.Point(texture.Width, texture.Height);\n            }\n\n            private void LoadResource(AssetManager assetManager)\n            {\n                this.Left = assetManager.LoadTexture(null, nameof(MRes.Basic_img_ComboBox_normal_0));\n                this.Center = assetManager.LoadTexture(null, nameof(MRes.Basic_img_ComboBox_normal_1));\n                this.Right = assetManager.LoadTexture(null, nameof(MRes.Basic_img_ComboBox_normal_2));\n            }\n\n            #region Interface implementation\n            TextureBase INinePatchResource<TextureBase>.C\n            {\n                get { return this.Center; }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.E\n            {\n                get { return this.Right; }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.W\n            {\n                get { return this.Left; }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.N\n            {\n                get { throw new NotImplementedException(); }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.NE\n            {\n                get { throw new NotImplementedException(); }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.NW\n            {\n                get { throw new NotImplementedException(); }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.S\n            {\n                get { throw new NotImplementedException(); }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.SE\n            {\n                get { throw new NotImplementedException(); }\n            }\n\n            TextureBase INinePatchResource<TextureBase>.SW\n            {\n                get { throw new NotImplementedException(); }\n            }\n            #endregion\n        }\n\n        private class TextureImageConverter : IValueConverter\n        {\n            public object Convert(object value, Type target, object parameter, CultureInfo culture)\n            {\n                var texture = value as TextureBase;\n                if (texture != null)\n                {\n                    return new BitmapImage() { Texture = texture };\n                }\n                return null;\n            }\n\n            public object ConvertBack(object value, Type target, object parameter, CultureInfo culture)\n            {\n                var image = value as BitmapImage;\n                if (image != null)\n                {\n                    return image.Texture;\n                }\n                return null;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIMirrorFrame.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Media.Imaging;\n\nnamespace WzComparerR2.MapRender.UI\n{\n\n    class UIMirrorFrame : WindowEx\n    {\n        private BitmapImage mirrorFrame800;\n        private BitmapImage mirrorFrame1024;\n        private BitmapImage mirrorFrame1366;\n\n        protected override void InitializeComponents()\n        {\n            mirrorFrame800 = new BitmapImage() { Texture = Engine.Instance.AssetManager.LoadTexture(null, nameof(Properties.Resources.UIWindow3_img_mirrorFrame_800)) };\n            mirrorFrame1024 = new BitmapImage() { Texture = Engine.Instance.AssetManager.LoadTexture(null, nameof(Properties.Resources.UIWindow3_img_mirrorFrame_1024)) };\n            mirrorFrame1366 = new BitmapImage() { Texture = Engine.Instance.AssetManager.LoadTexture(null, nameof(Properties.Resources.UIWindow3_img_mirrorFrame_1366)) };\n\n            Image image = new Image();\n            image.SetBinding(Image.WidthProperty, new Binding(UIMirrorFrame.WidthProperty) { Source = this });\n            image.SetBinding(Image.HeightProperty, new Binding(UIMirrorFrame.HeightProperty) { Source = this });\n            image.SetBinding(Image.SourceProperty, new Binding(UIMirrorFrame.WidthProperty) { Source = this, Converter = UIHelper.CreateConverter((float w) => w == 800 ? mirrorFrame800 : (w == 1024 ? mirrorFrame1024 : (w == 1366 ? mirrorFrame1366 : null))) });\n            this.Content = image;\n\n            this.IsHitTestVisible = false;\n            base.InitializeComponents();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIOptions.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Reflection;\n\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Media.Imaging;\nusing EmptyKeys.UserInterface.Mvvm;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class UIOptions : WindowEx\n    {\n        public UIOptions()\n        {\n\n        }\n\n        public event EventHandler OK;\n        public event EventHandler Cancel;\n\n        protected override void InitializeComponents()\n        {\n            Grid grid = new Grid();\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(30) });\n            grid.RowDefinitions.Add(new RowDefinition());\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(16) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(30) });\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n            grid.SetBinding(BackgroundProperty, new Binding(Control.BackgroundProperty) { Source = this });\n            this.Content = grid;\n\n            TextBlock title = new TextBlock();\n            title.Text = \"Options\";\n            title.IsHitTestVisible = false;\n            title.Foreground = Brushes.Gold;\n            title.HorizontalAlignment = HorizontalAlignment.Center;\n            title.VerticalAlignment = VerticalAlignment.Center;\n\n            Border header = new Border();\n            header.Child = title;\n            grid.Children.Add(header);\n            Grid.SetRow(header, 0);\n            Grid.SetColumn(header, 0);\n            this.SetDragTarget(header);\n\n            TabItem tab1 = new TabItem();\n            tab1.Header = \"General\";\n            tab1.Content = GetTabContent1();\n\n            TabItem tab2 = new TabItem();\n            tab2.Header = \"TopBar\";\n            tab2.Content = GetTabContent2();\n\n            TabItem tab3 = new TabItem();\n            tab3.Header = \"Minimap\";\n            tab3.Content = GetTabContent3();\n\n            TabItem tab4 = new TabItem();\n            tab4.Header = \"WorldMap\";\n            tab4.Content = GetTabContent4();\n\n            TabItem tab5 = new TabItem();\n            tab5.Header = \"Help\";\n            tab5.Content = GetTabContent5();\n\n            TabControl tabControl = new TabControl();\n            tabControl.Resources.Add(typeof(TabItem), GetTabItemStyle());\n            tabControl.Margin = new Thickness(5, 0, 5, 0);\n            tabControl.TabStripPlacement = Dock.Left;\n            tabControl.ItemsSource = new[] { tab1, tab2, tab3, tab4, tab5 };\n            grid.Children.Add(tabControl);\n            Grid.SetRow(tabControl, 1);\n            Grid.SetColumn(tabControl, 0);\n\n            TextBlock lblHint = new TextBlock();\n            lblHint.Foreground = Brushes.Yellow;\n            lblHint.VerticalAlignment = VerticalAlignment.Center;\n            lblHint.Text = \"* 某些选项需要重启MapRender才能生效。\";\n            lblHint.Margin = new Thickness(20, 0, 0, 0);\n            grid.Children.Add(lblHint);\n            Grid.SetRow(lblHint, 2);\n            Grid.SetColumn(lblHint, 0);\n\n            Button btnOK = new Button();\n            btnOK.Width = 50;\n            btnOK.Height = 20;\n            btnOK.Margin = new Thickness(5);\n            btnOK.Content = \"OK\";\n            btnOK.Click += BtnOK_Click;\n\n            Button btnCancel = new Button();\n            btnCancel.Width = 50;\n            btnCancel.Height = 20;\n            btnCancel.Margin = new Thickness(5);\n            btnCancel.Content = \"Cancel\";\n            btnCancel.Click += BtnCancel_Click;\n\n            StackPanel footerPanel = new StackPanel();\n            footerPanel.HorizontalAlignment = HorizontalAlignment.Center;\n            footerPanel.VerticalAlignment = VerticalAlignment.Center;\n            footerPanel.Orientation = Orientation.Horizontal;\n            footerPanel.Children.Add(btnOK);\n            footerPanel.Children.Add(btnCancel);\n\n            Border footer = new Border();\n            footer.Child = footerPanel;\n            grid.Children.Add(footer);\n            Grid.SetRow(footer, 3);\n            Grid.SetColumn(footer, 0);\n\n            this.Width = 300;\n            this.Height = 240;\n            this.SetResourceReference(BackgroundProperty, MapRenderResourceKey.TooltipBrush);\n            base.InitializeComponents();\n        }\n\n        private void BtnOK_Click(object sender, RoutedEventArgs e)\n        {\n            this.OK?.Invoke(this, EventArgs.Empty);\n        }\n\n        private void BtnCancel_Click(object sender, RoutedEventArgs e)\n        {\n            this.Cancel?.Invoke(this, EventArgs.Empty);\n        }\n\n        private UIElement GetTabContent1()\n        {\n            Grid grid = new Grid();\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n\n            TextBlock lbl1 = new TextBlock();\n            lbl1.VerticalAlignment = VerticalAlignment.Center;\n            lbl1.Text = \"背景音乐\";\n            lbl1.Foreground = Brushes.Yellow;\n            Grid.SetRow(lbl1, 0);\n            Grid.SetColumn(lbl1, 0);\n            grid.Children.Add(lbl1);\n\n            CheckBox chk1 = new CheckBox();\n            chk1.Content = \"不在最前时自动静音\";\n            chk1.Margin = new Thickness(18, 0, 0, 0);\n            chk1.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.MuteOnLeaveFocus)));\n            Grid.SetRow(chk1, 1);\n            Grid.SetColumn(chk1, 0);\n            Grid.SetColumnSpan(chk1, 2);\n            grid.Children.Add(chk1);\n\n            StackPanel pnl1 = new StackPanel();\n            pnl1.Orientation = Orientation.Horizontal;\n            Grid.SetRow(pnl1, 2);\n            Grid.SetColumn(pnl1, 0);\n            Grid.SetColumnSpan(pnl1, 2);\n            grid.Children.Add(pnl1);\n\n            TextBlock lbl2 = new TextBlock();\n            lbl2.TextAlignment = TextAlignment.Center;\n            lbl2.HorizontalAlignment = HorizontalAlignment.Center;\n            lbl2.VerticalAlignment = VerticalAlignment.Center;\n            lbl2.Padding = new Thickness(24, 0, 0, 0);\n            lbl2.Text = \"音量\";\n            pnl1.Children.Add(lbl2);\n\n            Slider slider1 = new Slider();\n            slider1.Minimum = 0f;\n            slider1.Maximum = 1f;\n            slider1.Width = 100;\n            slider1.SetBinding(Slider.ValueProperty, new Binding(nameof(UIOptionsDataModel.Volume)));\n            pnl1.Children.Add(slider1);\n\n            TextBlock lblVol = new TextBlock();\n            lblVol.TextAlignment = TextAlignment.Center;\n            lblVol.HorizontalAlignment = HorizontalAlignment.Left;\n            lblVol.VerticalAlignment = VerticalAlignment.Center;\n            lblVol.Padding = new Thickness(12, 0, 0, 0);\n            lblVol.SetBinding(TextBlock.TextProperty, new Binding(Slider.ValueProperty)\n            {\n                Source = slider1,\n                Converter = UIHelper.CreateConverter(o => string.Format(\"{0:0}\", ((float)o * 100)))\n            });\n            pnl1.Children.Add(lblVol);\n\n            TextBlock lbl3 = new TextBlock();\n            lbl3.VerticalAlignment = VerticalAlignment.Center;\n            lbl3.Text = \"默认字体\";\n            lbl3.Foreground = Brushes.Yellow;\n            Grid.SetRow(lbl3, 3);\n            Grid.SetColumn(lbl3, 0);\n            grid.Children.Add(lbl3);\n\n            ComboBox cmb1 = new ComboBox();\n            cmb1.ItemsSource = (IEnumerable<string>)this.FindResource(MapRenderResourceKey.FontList); //source reference has bugs.\n            cmb1.SetBinding(ComboBox.SelectedIndexProperty, new Binding(nameof(UIOptionsDataModel.SelectedFont)));\n            Grid.SetRow(cmb1, 3);\n            Grid.SetColumn(cmb1, 1);\n            grid.Children.Add(cmb1);\n\n            TextBlock lbl4 = new TextBlock();\n            lbl4.VerticalAlignment = VerticalAlignment.Center;\n            lbl4.Text = \"地图视窗\";\n            lbl4.Foreground = Brushes.Yellow;\n            Grid.SetRow(lbl4, 4);\n            Grid.SetColumn(lbl4, 0);\n            grid.Children.Add(lbl4);\n\n            CheckBox chk2 = new CheckBox();\n            chk2.Content = \"限制地图范围\";\n            chk2.Margin = new Thickness(18, 0, 0, 0);\n            chk2.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.ClipMapRegion)));\n            Grid.SetRow(chk2, 5);\n            Grid.SetColumn(chk2, 0);\n            Grid.SetColumnSpan(chk2, 2);\n            grid.Children.Add(chk2);\n\n            TextBlock lbl5 = new TextBlock();\n            lbl5.VerticalAlignment = VerticalAlignment.Center;\n            lbl5.Text = \"渲染\";\n            lbl5.Foreground = Brushes.Yellow;\n            Grid.SetRow(lbl5, 6);\n            Grid.SetColumn(lbl5, 0);\n            grid.Children.Add(lbl5);\n\n            CheckBox chk3 = new CheckBox();\n            chk3.Content = \"使用D2D绘制\";\n            chk3.Margin = new Thickness(18, 0, 0, 0);\n            chk3.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.UseD2dRenderer)));\n            Grid.SetRow(chk3, 7);\n            Grid.SetColumn(chk3, 0);\n            Grid.SetColumnSpan(chk3, 2);\n            grid.Children.Add(chk3);\n\n            CheckBox chk4 = new CheckBox();\n            chk4.Content = \"显示Npc名称\";\n            chk4.Margin = new Thickness(18, 0, 0, 0);\n            chk4.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.NpcNameVisible)));\n            Grid.SetRow(chk4, 8);\n            Grid.SetColumn(chk4, 0);\n            Grid.SetColumnSpan(chk4, 2);\n            grid.Children.Add(chk4);\n\n            CheckBox chk5 = new CheckBox();\n            chk5.Content = \"显示Mob名称\";\n            chk5.Margin = new Thickness(18, 0, 0, 0);\n            chk5.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.MobNameVisible)));\n            Grid.SetRow(chk5, 9);\n            Grid.SetColumn(chk5, 0);\n            Grid.SetColumnSpan(chk5, 2);\n            grid.Children.Add(chk5);\n\n            TextBlock lbl6 = new TextBlock();\n            lbl6.VerticalAlignment = VerticalAlignment.Center;\n            lbl6.Text = \"截图\";\n            lbl6.Foreground = Brushes.Yellow;\n            Grid.SetRow(lbl6, 10);\n            Grid.SetColumn(lbl6, 0);\n            grid.Children.Add(lbl6);\n\n            StackPanel pnl2 = new StackPanel();\n            pnl2.Orientation = Orientation.Horizontal;\n            Grid.SetRow(pnl2, 11);\n            Grid.SetColumn(pnl2, 0);\n            Grid.SetColumnSpan(pnl2, 2);\n            grid.Children.Add(pnl2);\n\n            TextBlock lbl7 = new TextBlock();\n            lbl7.TextAlignment = TextAlignment.Center;\n            lbl7.HorizontalAlignment = HorizontalAlignment.Center;\n            lbl7.VerticalAlignment = VerticalAlignment.Center;\n            lbl7.Padding = new Thickness(24, 0, 0, 0);\n            lbl7.Text = \"背景色(argb)\";\n            pnl2.Children.Add(lbl7);\n\n            TextBox tb1 = new TextBox();\n            tb1.Width = 60;\n            tb1.HorizontalAlignment = HorizontalAlignment.Center;\n            tb1.VerticalAlignment = VerticalAlignment.Center;\n            tb1.MaxLength = 8;\n            tb1.SetBinding(TextBox.TextProperty, new Binding(nameof(UIOptionsDataModel.ScreenshotBackgroundColor)));\n            pnl2.Children.Add(tb1);\n\n            Canvas img1 = new Canvas();\n            img1.Width = 20;\n            img1.Height = 20;\n            img1.Margin = new Thickness(2);\n            img1.SetBinding(Canvas.BackgroundProperty, new Binding(nameof(UIOptionsDataModel.ScreenshotBackgroundColor)) {\n                Converter = UIHelper.CreateConverter((string s) => ColorWConverter.TryParse(s, out var color) ? new SolidColorBrush(color) : null)\n            });\n            pnl2.Children.Add(img1);\n\n            ScrollViewer viewer = new ScrollViewer();\n            viewer.Content = grid;\n            return viewer;\n        }\n\n        private UIElement GetTabContent2()\n        {\n            Grid grid = new Grid();\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n\n            CheckBox chk1 = new CheckBox();\n            chk1.Content = \"开启TopBar\";\n            chk1.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.TopBarVisible)));\n            Grid.SetRow(chk1, 0);\n            Grid.SetColumn(chk1, 0);\n            Grid.SetColumnSpan(chk1, 2);\n            grid.Children.Add(chk1);\n\n            return grid;\n        }\n\n        private UIElement GetTabContent3()\n        {\n            Grid grid = new Grid();\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n\n            CheckBox chk1 = new CheckBox();\n            chk1.Content = \"显示可视区域\";\n            chk1.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.Minimap_CameraRegionVisible)));\n            Grid.SetRow(chk1, 0);\n            Grid.SetColumn(chk1, 0);\n            Grid.SetColumnSpan(chk1, 2);\n            grid.Children.Add(chk1);\n\n            return grid;\n        }\n\n        private UIElement GetTabContent4()\n        {\n            Grid grid = new Grid();\n            grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(24, GridUnitType.Pixel) });\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n            grid.ColumnDefinitions.Add(new ColumnDefinition());\n\n            CheckBox chk1 = new CheckBox();\n            chk1.Content = \"以Image名称作为Name\";\n            chk1.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(UIOptionsDataModel.WorldMap_UseImageNameAsInfoName)));\n            Grid.SetRow(chk1, 0);\n            Grid.SetColumn(chk1, 0);\n            Grid.SetColumnSpan(chk1, 2);\n            grid.Children.Add(chk1);\n\n            return grid;\n        }\n\n        private UIElement GetTabContent5()\n        {\n            StackPanel panel = new StackPanel();\n            panel.Orientation = Orientation.Vertical;\n            \n            var tips = new[]\n            {\n                 \"快捷键指示：\",\n                 \"\",\n                 \"M 小地图\",\n                 \"W 大地图\",\n                 \"Esc 设置\",\n                 \"Ctrl+1~9 开关图层\",\n                 \"Ctrl+U 解除地图范围锁定\",\n                 \"~ 开关控制台\",\n                 \"Alt+Enter 切换分辨率\",\n                 \"ScrollLock 截图\",\n            };\n\n            foreach (var tip in tips)\n            {\n                TextBlock lbl = new TextBlock();\n                lbl.TextWrapping = TextWrapping.Wrap;\n                lbl.Text = tip;\n                lbl.Margin = new Thickness(0, 1, 0, 1);\n                panel.Children.Add(lbl);\n            }\n\n            ScrollViewer viewer = new ScrollViewer();\n            viewer.Content = panel;\n            return viewer;\n        }\n\n        private Style GetTabItemStyle()\n        {\n            var style = EmptyKeys.UserInterface.Themes.TabControlStyle.CreateTabItemStyle();\n            var templateSetter = style.Setters.FirstOrDefault(s => s.Property == Control.TemplateProperty);\n            if (templateSetter != null)\n            {\n                var oldTemplate = templateSetter.Value as ControlTemplate;\n                var funcType = typeof(Func<UIElement, UIElement>);\n                var funcField = oldTemplate.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)\n                    .FirstOrDefault(field => field.FieldType == funcType);\n                var oldMethod = funcField?.GetValue(oldTemplate) as Func<UIElement, UIElement>;\n                if (oldMethod != null)\n                {\n                    var newMethod = new Func<UIElement, UIElement>(parent =>\n                    {\n                        UIElement elem = oldMethod(parent);\n                        ContentPresenter presenter = (elem as Border)?.Child as ContentPresenter;\n                        if (presenter != null)\n                        {\n                            presenter.Margin = new Thickness(6, 1, 6, 1);\n                        }\n                        return elem;\n                    });\n                    funcField.SetValue(oldTemplate, newMethod);\n                }\n            }\n\n            return style;\n        }\n    }\n\n    class UIOptionsDataModel : BindableBase\n    {\n        private bool _muteOnLeaveFocus;\n        private float _volume;\n        private int _selectedFont;\n        private bool _clipMapRegion;\n        private bool _useD2dRenderer;\n        private bool _npcNameVisible;\n        private bool _mobNameVisible;\n        private bool _topBarVisible;\n        private bool _minimap_cameraRegionVisible;\n        private bool _worldmap_useImageNameAsInfoName;\n        private string _screenshotBackgroundColor;\n\n        public bool MuteOnLeaveFocus\n        {\n            get { return this._muteOnLeaveFocus; }\n            set { base.SetProperty(ref this._muteOnLeaveFocus, value); }\n        }\n\n        public float Volume\n        {\n            get { return this._volume; }\n            set { base.SetProperty(ref this._volume, value); }\n        }\n\n        public int SelectedFont\n        {\n            get { return this._selectedFont; }\n            set { base.SetProperty(ref this._selectedFont, value); }\n        }\n\n        public bool ClipMapRegion\n        {\n            get { return this._clipMapRegion; }\n            set { base.SetProperty(ref this._clipMapRegion, value); }\n        }\n\n        public bool UseD2dRenderer\n        {\n            get { return this._useD2dRenderer; }\n            set { base.SetProperty(ref this._useD2dRenderer, value); }\n        }\n\n        public bool NpcNameVisible\n        {\n            get { return this._npcNameVisible; }\n            set { base.SetProperty(ref this._npcNameVisible, value); }\n        }\n\n        public bool MobNameVisible\n        {\n            get { return this._mobNameVisible; }\n            set { base.SetProperty(ref this._mobNameVisible, value); }\n        }\n\n        public string ScreenshotBackgroundColor\n        {\n            get { return this._screenshotBackgroundColor; }\n            set { base.SetProperty(ref this._screenshotBackgroundColor, value); }\n        }\n\n        public bool TopBarVisible\n        {\n            get { return this._topBarVisible; }\n            set { base.SetProperty(ref this._topBarVisible, value); }\n        }\n\n        public bool Minimap_CameraRegionVisible\n        {\n            get { return this._minimap_cameraRegionVisible; }\n            set { base.SetProperty(ref this._minimap_cameraRegionVisible, value); }\n        }\n\n        public bool WorldMap_UseImageNameAsInfoName\n        {\n            get { return this._worldmap_useImageNameAsInfoName; }\n            set { base.SetProperty(ref this._worldmap_useImageNameAsInfoName, value); }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UITeleport.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing TextRenderer = System.Windows.Forms.TextRenderer;\n\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Media.Imaging;\nusing EmptyKeys.UserInterface.Input;\n\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\n\nusing Res = CharaSimResource.Resource;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\nusing MathHelper = Microsoft.Xna.Framework.MathHelper;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class UITeleport : WindowEx // base code from UIWorldmap.cs\n    {\n        public static readonly DependencyProperty SelectedMapIDProperty = DependencyProperty.Register(\"SelectedMapID\", typeof(int?), typeof(UITeleport), new FrameworkPropertyMetadata(null));\n\n        public UITeleport() : base()\n        {\n        }\n\n        public ComboBox CmbMaps { get; private set; }\n        public TextBlock TextStreet { get; private set; }\n        public TextBlock TextMap { get; private set; }\n        //public TextBlock TextMapID { get; private set; }\n        public TextBlockTooltip Tooltip { get; private set; }\n        public StringLinker Sl { get; set; }\n\n        public event EventHandler<SelectedMapGoEventArgs> SelectedMapGo;\n\n        public int? SelectedMapID\n        {\n            get { return (int?)this.GetValue(SelectedMapIDProperty); }\n            set { this.SetValue(SelectedMapIDProperty, value); }\n        }\n\n        protected override void InitializeComponents()\n        {\n            var canvas = new Canvas();\n            var canvasBackTexture = Engine.Instance.AssetManager.LoadTexture(null, nameof(MRes.UIWindow2_img_Teleport_customBG));\n            canvas.Background = new ImageBrush() { ImageSource = new BitmapImage() { Texture = canvasBackTexture }, Stretch = Stretch.None };\n            canvas.SetBinding(Canvas.WidthProperty, new Binding(UITeleport.WidthProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.SetBinding(Canvas.HeightProperty, new Binding(UITeleport.HeightProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.Parent = this;\n            this.Content = canvas;\n\n            Border title = new Border();\n            title.Height = 24;\n            title.SetBinding(Border.WidthProperty, new Binding(Border.WidthProperty) { Source = canvas });\n            canvas.Children.Add(title);\n            this.SetDragTarget(title);\n\n            TextBlock textStreet = new TextBlock();\n            textStreet.MaxWidth = 134 - 4 * 1;\n            textStreet.MaxHeight = 12;\n            textStreet.Background = Brushes.Transparent;\n            textStreet.Foreground = Brushes.Black;\n            textStreet.Margin = new Thickness(4, 3, 0, -3);\n            Canvas.SetLeft(textStreet, 13);\n            Canvas.SetTop(textStreet, 78 + 18 * 0); // 1행 배치\n            canvas.Children.Add(textStreet);\n            this.TextStreet = textStreet;\n\n            TextBlock TextMap = new TextBlock();\n            TextMap.MaxWidth = 134 - 4 * 1;\n            TextMap.MaxHeight = 12;\n            TextMap.Background = Brushes.Transparent;\n            TextMap.Foreground = Brushes.Black;\n            TextMap.Margin = new Thickness(4, 3, 0, -3);\n            Canvas.SetLeft(TextMap, 13);\n            Canvas.SetTop(TextMap, 78 + 18 * 1); // 2행 배치\n            canvas.Children.Add(TextMap);\n            this.TextMap = TextMap;\n\n            ComboBox cmbMaps = new ComboBox();\n            cmbMaps.Width = 134;\n            cmbMaps.Height = 17 + 1 * 2;\n            Canvas.SetLeft(cmbMaps, 13);\n            Canvas.SetTop(cmbMaps, 78 + 18 * 2 - 1); // 3행 배치\n            cmbMaps.SetBinding(ComboBox.SelectedItemProperty, new Binding(UITeleport.SelectedMapIDProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.Children.Add(cmbMaps);\n            this.CmbMaps = cmbMaps;\n\n            /* 맵ID; 미사용\n            TextBlock TextMapID = new TextBlock();\n            TextMapID.Width = 134;\n            TextMapID.MaxHeight = 17;\n            TextMap.Background = Brushes.Transparent;\n            TextMapID.Foreground = Brushes.Black;\n            TextMap.Margin = new Thickness(4, 3, 0, -3);\n            Canvas.SetLeft(TextMapID, 13);\n            Canvas.SetTop(TextMapID, 78 + 18 * 3); // 4행 배치\n            canvas.Children.Add(TextMapID);\n            this.TextMapID = TextMapID;*/\n\n            TextBlockTooltip tooltip = new TextBlockTooltip();\n            tooltip.Width = 134;\n            tooltip.Height = 17 + 18 * 1; // 툴팁영역 2칸 (street + map)\n            Canvas.SetLeft(tooltip, 13);\n            Canvas.SetTop(tooltip, 78 + 18 * 0);\n            canvas.Children.Add(tooltip);\n            this.Tooltip = tooltip;\n\n            Button btnBack = new ImageButton();\n            btnBack.Name = \"Cancel\";\n            btnBack.Click += BtnBack_Click;\n            btnBack.SetResourceReference(UIElement.StyleProperty, MapRenderResourceKey.MapRenderButtonStyle);\n            btnBack.Focusable = false;\n            Canvas.SetLeft(btnBack, 106);\n            Canvas.SetTop(btnBack, 149);\n            canvas.Children.Add(btnBack);\n\n            ImageButton btnGo = new ImageButton();\n            btnGo.Name = \"OK\";\n            btnGo.Click += BtnGo_Click;\n            btnGo.SetResourceReference(UIElement.StyleProperty, MapRenderResourceKey.MapRenderButtonStyle);\n            btnGo.Focusable = false;\n            Canvas.SetLeft(btnGo, 58);\n            Canvas.SetTop(btnGo, 149);\n            canvas.Children.Add(btnGo);\n\n            ImageButton btnClose = new ImageButton();\n            btnClose.Name = \"Close\";\n            btnClose.Click += BtnClose_Click;\n            btnClose.SetResourceReference(UIElement.StyleProperty, MapRenderResourceKey.MapRenderButtonStyle);\n            btnClose.Focusable = false;\n            Canvas.SetRight(btnClose, 7);\n            Canvas.SetTop(btnClose, 5);\n            canvas.Children.Add(btnClose);\n\n            this.Width = canvasBackTexture.Width;\n            this.Height = canvasBackTexture.Height;\n            base.InitializeComponents();\n        }\n\n        protected override void OnPropertyChanged(DependencyProperty property)\n        {\n            base.OnPropertyChanged(property);\n\n            if (property == SelectedMapIDProperty) // 콤보박스 선택 시\n            {\n                if (SelectedMapID != null)\n                {\n                    //TextMapID.Text = SelectedMapID.ToString();\n                    StringResult sr = null;\n                    Sl?.StringMap.TryGetValue(SelectedMapID ?? 999999999, out sr);\n                    var streetSTR = sr?[\"streetName\"] ?? \"(null)\";\n                    var mapSTR = sr?[\"mapName\"] ?? \"(null)\";\n                    TextStreet.Text = GetStringResult(streetSTR);\n                    TextMap.Text = GetStringResult(mapSTR);\n                    Tooltip.ResetTooltip(streetSTR, mapSTR);\n                }\n            }\n        }\n\n        private string GetStringResult(string str) // 주어진 칸 넘는 문자열 ..처리\n        {\n            if (str == null) return \"(null)\";\n            var res = str;\n            var num = 0;\n            var textSize = TextStreet.Font.MeasureString(res, new Size(0, 0));\n            while (textSize.Width > 134 - 4 * 1)\n            {\n                res = str.Substring(0, str.Length - num) + \"..\";\n                num += 1;\n                textSize = TextStreet.Font.MeasureString(res, new Size(0, 0));\n            }\n            return res;\n        }\n        \n        public class TextBlockTooltip : Control, ITooltipTarget // 마우스오버시 툴팁 출력 영역\n        {\n            public TextBlockTooltip()\n            {\n                itemRectList = new List<ItemRect>();\n                itemRectList.Add(new ItemRect() { Rect = new Rect(-1, -1, -1, -1), Tooltip = \"\" });\n            }\n\n            private List<ItemRect> itemRectList;\n\n            public void ResetTooltip(string text1, string text2)\n            {\n                itemRectList.Clear();\n                itemRectList.Add(new ItemRect() { Rect = new Rect(0, 0, 134, 17), Tooltip = text1 });\n                itemRectList.Add(new ItemRect() { Rect = new Rect(0, 0 + 18 * 1, 134, 17), Tooltip = text2 });\n            }\n\n            public object GetTooltipTarget(PointF mouseLocation)\n            {\n                foreach (var itemRect in itemRectList)\n                {\n                    if (itemRect.Rect.Contains(mouseLocation))\n                    {\n                        return itemRect.Tooltip;\n                    }\n                }\n                return null;\n            }\n            private struct ItemRect\n            {\n                public Rect Rect;\n                public object Tooltip;\n            }\n        }\n\n        private void BtnBack_Click(object sender, RoutedEventArgs e)\n        {\n            this.Hide();\n        }\n\n        private void BtnGo_Click(object sender, RoutedEventArgs e)\n        {\n            this.Hide();\n            this.SelectedMapGo?.Invoke(this, new SelectedMapGoEventArgs(SelectedMapID ?? 999999999));\n        }\n\n        private void BtnClose_Click(object sender, RoutedEventArgs e)\n        {\n            this.Hide();\n        }\n\n        public class SelectedMapGoEventArgs : EventArgs\n        {\n            public SelectedMapGoEventArgs(int mapID)\n            {\n                this.MapID = mapID;\n            }\n\n            public int MapID { get; private set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UITopBar.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Media.Imaging;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class UITopBar : WindowEx\n    {\n        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(\"Text\", typeof(string), typeof(UITopBar), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty IsShortModeProperty = DependencyProperty.Register(\"IsShortMode\", typeof(bool), typeof(UITopBar), new FrameworkPropertyMetadata(false));\n        public static readonly DependencyProperty PaddingLeftProperty = DependencyProperty.Register(\"PaddingLeft\", typeof(float), typeof(UITopBar), new FrameworkPropertyMetadata(0));\n\n        public string Text\n        {\n            get { return (string)GetValue(TextProperty); }\n            set { SetValue(TextProperty, value); }\n        }\n\n        public bool IsShortMode\n        {\n            get { return (bool)GetValue(IsShortModeProperty); }\n            set { SetValue(TextProperty, IsShortModeProperty); }\n        }\n\n        public float PaddingLeft\n        {\n            get { return (float)GetValue(PaddingLeftProperty); }\n            set { SetValue(TextProperty, PaddingLeftProperty); }\n        }\n\n        protected override void InitializeComponents()\n        {\n            Border border = new Border();\n            border.Background = new SolidColorBrush(new ColorW(0, 0, 0, 128));\n            this.Content = border;\n\n            StackPanel panel = new StackPanel();\n            panel.Orientation = Orientation.Horizontal;\n            border.Child = panel;\n\n            Border blank = new Border();\n            blank.SetBinding(Border.VisibilityProperty, new Binding(IsShortModeProperty) { Source = this, Converter = UIHelper.CreateConverter((bool o) => o ? Visibility.Visible : Visibility.Collapsed) });\n            blank.SetBinding(Border.WidthProperty, new Binding(PaddingLeftProperty) { Source = this });\n            blank.SetBinding(Border.HeightProperty, new Binding(HeightProperty) { Source = this });\n            panel.Children.Add(blank);\n\n            TextBlock textBlock = new TextBlock();\n            textBlock.Foreground = Brushes.Cyan;\n            textBlock.Margin = new Thickness(6, 0, 0, 0);\n            textBlock.HorizontalAlignment = HorizontalAlignment.Left;\n            textBlock.VerticalAlignment = VerticalAlignment.Center;\n\n            textBlock.SetBinding(TextBlock.TextProperty, new Binding(TextProperty) { Source = this });\n            panel.Children.Add(textBlock);\n\n            this.Height = 16;\n            this.IsHitTestVisible = false;\n            base.InitializeComponents();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/UIWorldMap.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Text;\nusing System.Text.RegularExpressions;\n\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Media.Imaging;\nusing EmptyKeys.UserInterface.Input;\n\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\n\nusing Res = CharaSimResource.Resource;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\nusing MathHelper = Microsoft.Xna.Framework.MathHelper;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class UIWorldMap : WindowEx\n    {\n        public static readonly DependencyProperty CurrentWorldMapProperty = DependencyProperty.Register(\"CurrentWorldMap\", typeof(WorldMapInfo), typeof(UIWorldMap), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty CurrentMapIDProperty = DependencyProperty.Register(\"CurrentMapID\", typeof(int?), typeof(UIWorldMap), new FrameworkPropertyMetadata(null));\n        public static readonly DependencyProperty UseImageNameAsInfoNameProperty = DependencyProperty.Register(\"UseImageNameAsInfoName\", typeof(bool), typeof(UIWorldMap), new FrameworkPropertyMetadata(false));\n        public static readonly DependencyProperty SelectedQuestLimitIndexProperty = DependencyProperty.Register(\"SelectedQuestLimitIndex\", typeof(int), typeof(UIWorldMap), new FrameworkPropertyMetadata(-1));\n        public static readonly DependencyProperty SelectedFogIndexProperty = DependencyProperty.Register(\"SelectedFogIndex\", typeof(int), typeof(UIWorldMap), new FrameworkPropertyMetadata(-1));\n\n        public UIWorldMap() : base()\n        {\n            this.worldMaps = new ObservableCollection<WorldMapInfo>();\n\n            this.CmbMaps.ItemsSource = this.worldMaps;\n\n            //添加默认item样式\n            var template = this.CmbMaps.FindResource(typeof(string)) as DataTemplate;\n            if (template != null)\n            {\n                this.CmbMaps.Resources.Add(typeof(WorldMapInfo), template);\n                this.CmbQuestList.Resources.Add(typeof(QuestLimitInfo), template);\n                this.CmbFogList.Resources.Add(typeof(FogInfo), template);\n            }\n\n            //添加默认点击事件\n            UIHelper.RegisterClickEvent(this.MapArea, (_, point) => this.MapArea.HitTest(point), this.OnMapAreaClick);\n        }\n\n        public bool IsDataLoaded { get; private set; }\n        public ComboBox CmbMaps { get; private set; }\n        public ComboBox CmbQuestList { get; private set; }\n        public ComboBox CmbFogList { get; private set; }\n        public WorldMapArea MapArea { get; private set; }\n\n        public event EventHandler<MapSpotEventArgs> MapSpotClick;\n\n        private ObservableCollection<WorldMapInfo> worldMaps;\n\n        public WorldMapInfo CurrentWorldMap\n        {\n            get { return (WorldMapInfo)this.GetValue(CurrentWorldMapProperty); }\n            set { this.SetValue(CurrentWorldMapProperty, value); }\n        }\n\n        public int? CurrentMapID\n        {\n            get { return (int?)this.GetValue(CurrentMapIDProperty); }\n            set { this.SetValue(CurrentMapIDProperty, value); }\n        }\n\n        public bool UseImageNameAsInfoName\n        {\n            get { return (bool)this.GetValue(UseImageNameAsInfoNameProperty); }\n            set { this.SetValue(UseImageNameAsInfoNameProperty, value); }\n        }\n\n        protected override void InitializeComponents()\n        {\n            var canvas = new Canvas();\n            var canvasBackTexture = Engine.Instance.AssetManager.LoadTexture(null, nameof(MRes.UIWindow2_img_WorldMap_Border_0));\n            canvas.Background = new ImageBrush() { ImageSource = new BitmapImage() { Texture = canvasBackTexture }, Stretch = Stretch.None };\n            canvas.SetBinding(Canvas.WidthProperty, new Binding(UIWorldMap.WidthProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.SetBinding(Canvas.HeightProperty, new Binding(UIWorldMap.HeightProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.Parent = this;\n            this.Content = canvas;\n\n            Border title = new Border();\n            title.Height = 17;\n            title.SetBinding(Canvas.WidthProperty, new Binding(Canvas.WidthProperty) { Source = canvas });\n            canvas.Children.Add(title);\n            this.SetDragTarget(title);\n\n            ComboBox cmbMaps = new ComboBox();\n            cmbMaps.Width = 150;\n            cmbMaps.Height = 20;\n            Canvas.SetLeft(cmbMaps, 10);\n            Canvas.SetTop(cmbMaps, 23);\n            cmbMaps.SetBinding(ComboBox.SelectedItemProperty, new Binding(UIWorldMap.CurrentWorldMapProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.Children.Add(cmbMaps);\n            this.CmbMaps = cmbMaps;\n\n            ComboBox cmbQuestList = new ComboBox();\n            cmbQuestList.Width = 100;\n            cmbQuestList.Height = 20;\n            Canvas.SetLeft(cmbQuestList, 250);\n            Canvas.SetTop(cmbQuestList, 23);\n            cmbQuestList.SetBinding(ComboBox.SelectedIndexProperty, new Binding(UIWorldMap.SelectedQuestLimitIndexProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.Children.Add(cmbQuestList);\n            this.CmbQuestList = cmbQuestList;\n\n            ComboBox cmbFogList = new ComboBox();\n            cmbFogList.Width = 100;\n            cmbFogList.Height = 20;\n            Canvas.SetLeft(cmbFogList, 250);\n            Canvas.SetTop(cmbFogList, 23);\n            cmbFogList.SetBinding(ComboBox.SelectedIndexProperty, new Binding(UIWorldMap.SelectedFogIndexProperty) { Source = this, Mode = BindingMode.TwoWay });\n            canvas.Children.Add(cmbFogList);\n            this.CmbFogList = cmbFogList;\n\n            WorldMapArea mapArea = new WorldMapArea();\n            mapArea.Width = 640;\n            mapArea.Height = 480;\n            mapArea.InputBindings.Add(new InputBinding(new RelayCommand(MapArea_RightClick), new MouseGesture(MouseAction.RightClick)));\n            Canvas.SetLeft(mapArea, 7);\n            Canvas.SetTop(mapArea, 44);\n            canvas.Children.Add(mapArea);\n            this.SetBinding(CurrentWorldMapProperty, new Binding(Control.DataContextProperty) { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.SetBinding(CurrentMapIDProperty, new Binding(\"CurrentMapID\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.SetBinding(SelectedQuestLimitIndexProperty, new Binding(\"SelectedQuestIndex\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.SetBinding(SelectedFogIndexProperty, new Binding(\"SelectedFogIndex\") { Source = mapArea, Mode = BindingMode.OneWayToSource });\n            this.MapArea = mapArea;\n\n            Button btnBack = new Button();\n            btnBack.Width = 50;\n            btnBack.Height = 20;\n            btnBack.Content = \"返回\";\n            btnBack.Click += BtnBack_Click;\n            Canvas.SetLeft(btnBack, 180);\n            Canvas.SetTop(btnBack, 23);\n            canvas.Children.Add(btnBack);\n\n            ImageButton btnClose = new ImageButton();\n            btnClose.Name = \"Close\";\n            btnClose.Click += BtnClose_Click;\n            btnClose.SetResourceReference(UIElement.StyleProperty, MapRenderResourceKey.MapRenderButtonStyle);\n            Canvas.SetRight(btnClose, 7);\n            Canvas.SetTop(btnClose, 5);\n            canvas.Children.Add(btnClose);\n\n            this.Width = canvasBackTexture.Width;\n            this.Height = canvasBackTexture.Height;\n            base.InitializeComponents();\n        }\n\n        protected override void OnPropertyChanged(DependencyProperty property)\n        {\n            base.OnPropertyChanged(property);\n\n            if (property == CurrentWorldMapProperty)\n            {\n                if (this.CmbQuestList != null)\n                {\n                    var quests = this.CurrentWorldMap?.QuestLimit;\n                    if (quests == null || quests.Count <= 0)\n                    {\n                        if (this.CmbQuestList.ItemsSource != null)\n                        {\n                            this.CmbQuestList.ItemsSource = null;\n                        }\n                        this.CmbQuestList.Visibility = Visibility.Hidden;\n                    }\n                    else\n                    {\n                        var dataSource = new List<object>();\n                        dataSource.AddRange(quests);\n                        dataSource.Add(\"(none)\");\n                        this.CmbQuestList.ItemsSource = dataSource;\n                        this.CmbQuestList.SelectedIndex = 0;\n                        this.CmbQuestList.Visibility = Visibility.Visible;\n                    }\n                }\n                if (this.CmbFogList != null)\n                {\n                    var fogs = this.CurrentWorldMap?.Fog;\n                    if (fogs == null || fogs.Count <= 0)\n                    {\n                        if (this.CmbFogList.ItemsSource != null)\n                        {\n                            this.CmbFogList.ItemsSource = null;\n                        }\n                        this.CmbFogList.Visibility = Visibility.Hidden;\n                    }\n                    else\n                    {\n                        var dataSource = new List<object>();\n                        dataSource.AddRange(fogs);\n                        dataSource.Add(\"(none)\");\n                        this.CmbFogList.ItemsSource = dataSource;\n                        this.CmbFogList.SelectedIndex = 0;\n                        this.CmbFogList.Visibility = Visibility.Visible;\n                    }\n                }\n            }\n        }\n\n        public void LoadWzResource()\n        {\n            if (this.IsDataLoaded)\n            {\n                return;\n            }\n\n            //读取所有世界地图\n            var worldmapNode = PluginBase.PluginManager.FindWz(\"Map/WorldMap\");\n            if (worldmapNode == null) //加载失败\n            {\n                return;\n            }\n\n            this.worldMaps.Clear();\n            this.CurrentWorldMap = null;\n\n            foreach (var imgNode in worldmapNode.Nodes)\n            {\n                var img = imgNode.GetNodeWzImage();\n                Wz_Node node;\n                if (img != null && img.TryExtract())\n                {\n                    var worldMapInfo = new WorldMapInfo();\n\n                    //加载地图索引\n                    node = img.Node.Nodes[\"info\"];\n                    if (node != null)\n                    {\n                        if (!this.UseImageNameAsInfoName)\n                        {\n                            worldMapInfo.Name = node.Nodes[\"WorldMap\"].GetValueEx<string>(null);\n                        }\n                        worldMapInfo.ParentMap = node.Nodes[\"parentMap\"].GetValueEx<string>(null);\n                    }\n                    if (string.IsNullOrEmpty(worldMapInfo.Name))\n                    {\n                        var m = Regex.Match(img.Name, @\"^(.*)\\.img$\");\n                        worldMapInfo.Name = m.Success ? m.Result(\"$1\") : img.Name;\n                    }\n\n                    //加载地图名称\n                    {\n                        var m = Regex.Match(worldMapInfo.Name, @\"^WorldMap(.+)$\");\n                        if (m.Success)\n                        {\n                            var stringNode = PluginBase.PluginManager.FindWz(\"String/WorldMap.img/\" + m.Result(\"$1\"));\n                            worldMapInfo.DisplayName = stringNode?.Nodes[\"name\"].GetValueEx<string>(null);\n                        }\n                    }\n\n                    //加载baseImg\n                    node = img.Node.Nodes[\"BaseImg\"]?.Nodes[\"0\"];\n                    if (node == null)\n                    {\n                        continue;\n                    }\n                    worldMapInfo.BaseImg = LoadTextureItem(node);\n\n                    //加载地图列表\n                    node = img.Node.Nodes[\"MapList\"];\n                    if (node != null)\n                    {\n                        // KMST 1125: the spot indices are not always continuous.\n                        var spotNodes = node.Nodes.Select(child => new {\n                                child,\n                                index = int.TryParse(child.Text, out var nodeInex) ? nodeInex : -1,\n                            }).Where(item => item.index > -1)\n                            .OrderBy(item => item.index)\n                            .Select(item => item.child);\n\n                        foreach (var spotNode in spotNodes)\n                        {\n                            var spot = new MapSpot();\n                            var location = spotNode.Nodes[\"spot\"]?.GetValueEx<Wz_Vector>(null);\n                            if (location != null)\n                            {\n                                spot.Spot = location.ToPointF();\n                            }\n                            else //兼容pre-bb的格式\n                            {\n                                spot.IsPreBB = true;\n                                spot.Spot = new PointF(spotNode.Nodes[\"x\"].GetValueEx<int>(0),\n                                    spotNode.Nodes[\"y\"].GetValueEx<int>(0));\n                            }\n                            spot.Type = spotNode.Nodes[\"type\"].GetValueEx<int>(0);\n                            spot.Title = spotNode.Nodes[\"title\"].GetValueEx<string>(null);\n                            spot.Desc = spotNode.Nodes[\"desc\"].GetValueEx<string>(null);\n                            spot.NoTooltip = spotNode.Nodes[\"noToolTip\"].GetValueEx<int>(0) != 0;\n                            var pathNode = spotNode.Nodes[\"path\"];\n                            if (pathNode != null)\n                            {\n                                spot.Path = LoadTextureItem(pathNode);\n                            }\n                            var mapNoNode = spotNode.Nodes[\"mapNo\"];\n                            if (mapNoNode != null)\n                            {\n                                foreach (var subNode in mapNoNode.Nodes)\n                                {\n                                    spot.MapNo.Add(subNode.GetValue<int>());\n                                }\n                            }\n                            worldMapInfo.MapList.Add(spot);\n                        }\n                    }\n\n                    //加载地图链接\n                    node = img.Node.Nodes[\"MapLink\"];\n                    if (node != null)\n                    {\n                        foreach (var mapLinkNode in node.Nodes)\n                        {\n                            var link = new MapLink();\n                            link.Index = int.Parse(mapLinkNode.Text);\n                            link.Tooltip = mapLinkNode.Nodes[\"toolTip\"].GetValueEx<string>(null);\n                            link.Desc = mapLinkNode.Nodes[\"desc\"].GetValueEx<string>(null);\n                            var linkNode = mapLinkNode.Nodes[\"link\"];\n                            if (linkNode != null)\n                            {\n                                link.LinkMap = linkNode.Nodes[\"linkMap\"].GetValueEx<string>(null);\n                                var linkImgNode = linkNode.Nodes[\"linkImg\"];\n                                if (linkImgNode != null)\n                                {\n                                    link.LinkImg = LoadTextureItem(linkImgNode, true);\n                                }\n                            }\n                            worldMapInfo.MapLinks.Add(link);\n                        }\n                    }\n\n                    //KMST1070 加载阶段大地图\n                    node = img.Node.Nodes[\"QuestLimit\"];\n                    if (node != null)\n                    {\n                        var qlNode = node.Nodes[\"default\"];\n                        if (qlNode != null)\n                        {\n                            worldMapInfo.QuestLimit.Add(this.LoadQuestLimit(qlNode));\n                        }\n                        for (int i = 0; ; i++)\n                        {\n                            qlNode = node.Nodes[i.ToString()];\n                            if (qlNode == null)\n                            {\n                                break;\n                            }\n\n                            worldMapInfo.QuestLimit.Add(this.LoadQuestLimit(qlNode));\n                        }\n                    }\n\n                    node = img.Node.Nodes[\"Fog\"];\n                    if (node != null)\n                    {\n                        for (int i = 0; ; i++)\n                        {\n                            var fogNode = node.Nodes[i.ToString()];\n                            if (fogNode == null)\n                            {\n                                break;\n                            }\n\n                            worldMapInfo.Fog.Add(this.LoadFog(fogNode));\n                        }\n                    }\n\n                    this.worldMaps.Add(worldMapInfo);\n                }\n            }\n\n            //读取公共资源\n            var worldMapResNode = PluginBase.PluginManager.FindWz(\"Map/MapHelper.img/worldMap\");\n            var mapImageNode = worldMapResNode?.Nodes[\"mapImage\"];\n            if (mapImageNode != null)\n            {\n                foreach (var imgNode in mapImageNode.Nodes)\n                {\n                    var texture = this.LoadTextureItem(imgNode);\n                    var key = \"mapImage/\" + imgNode.Text;\n                    this.Resources[key] = texture;\n                }\n            }\n\n            var curPosNode = worldMapResNode?.FindNodeByPath(@\"curPos\\0\");\n            if (curPosNode != null)\n            {\n                var texture = this.LoadTextureItem(curPosNode);\n                this.Resources[\"curPos\"] = texture;\n            }\n\n            //处理当前地图信息\n            foreach (var map in this.worldMaps)\n            {\n                if (map.ParentMap != null)\n                {\n                    map.ParentMapInfo = this.worldMaps.FirstOrDefault(_map => _map.Name == map.ParentMap);\n                }\n            }\n\n            this.IsDataLoaded = true;\n            this.JumpToCurrentMap();\n        }\n\n        private QuestLimitInfo LoadQuestLimit(Wz_Node node)\n        {\n            var questLimit = new QuestLimitInfo();\n            questLimit.Name = node.Text;\n\n            var baseImgNode = node.Nodes[\"0\"];\n            if (baseImgNode != null)\n            {\n                questLimit.BaseImg = this.LoadTextureItem(baseImgNode);\n            }\n\n            var openMapNode = node.Nodes[\"openMap\"];\n            if (openMapNode != null)\n            {\n                for (int i = 0; ; i++)\n                {\n                    var subNode = openMapNode.Nodes[i.ToString()];\n                    if (subNode == null)\n                    {\n                        break;\n                    }\n\n                    var spotIndex = subNode.GetValue<int>();\n                    questLimit.OpenMaps.Add(spotIndex);\n                }\n            }\n\n            var closeMapNode = node.Nodes[\"closeMap\"];\n            if (closeMapNode != null)\n            {\n                for (int i = 0; ; i++)\n                {\n                    var subNode = closeMapNode.Nodes[i.ToString()];\n                    if (subNode == null)\n                    {\n                        break;\n                    }\n\n                    var spotIndex = subNode.GetValue<int>();\n                    questLimit.CloseMaps.Add(spotIndex);\n                }\n            }\n\n            questLimit.IsDefault = node.Nodes[\"default\"].GetValueEx(0) != 0;\n            questLimit.Quest = node.Nodes[\"quest\"].GetValueEx(0);\n            questLimit.State = node.Nodes[\"state\"].GetValueEx(0);\n            questLimit.CloseMapImageType = node.Nodes[\"closeMapImageType\"].GetValueEx<int?>(null);\n\n            return questLimit;\n        }\n\n        private FogInfo LoadFog(Wz_Node node)\n        {\n            var fog = new FogInfo();\n            fog.Name = node.Text;\n\n            var imgNode = node.Nodes[\"0\"];\n            if (imgNode != null)\n            {\n                fog.Img = this.LoadTextureItem(imgNode);\n                if (fog.Img != null)\n                {\n                    fog.Img.Origin.Y -= 20;\n                    fog.Img.Z = 255;\n                }\n            }\n\n            fog.Quest = node.Nodes[\"quest\"].GetValueEx(0);\n            fog.State = node.Nodes[\"qState\"].GetValueEx(0);\n\n            return fog;\n        }\n\n        public void JumpToCurrentMap()\n        {\n            if (this.CurrentMapID != null)\n            {\n                var mapID = this.CurrentMapID.Value;\n                var query = this.worldMaps.Where(_map => _map.MapList.Any(_spot => _spot.MapNo.Contains(mapID)))\n                    .Select(_map =>\n                    {\n                        int level = 0;\n                        for (var cur = _map; cur != null; cur = cur.ParentMapInfo)\n                        {\n                            level++;\n                        }\n                        return new { map = _map, level = level };\n                    })\n                    .OrderByDescending(item => item.level);\n                var suitMap = query.FirstOrDefault()?.map;\n                if (suitMap != null)\n                {\n                    this.CurrentWorldMap = suitMap;\n                    return;\n                }\n            }\n            this.CurrentWorldMap = this.worldMaps.FirstOrDefault();\n        }\n\n        private TextureItem LoadTextureItem(Wz_Node node, bool loadHitMap = false)\n        {\n            var item = new TextureItem();\n            var linkNode = node.GetLinkedSourceNode(PluginBase.PluginManager.FindWz);\n            item.Texture = UIHelper.LoadTexture(linkNode);\n            item.Z = node.Nodes[\"z\"].GetValueEx<int>(0);\n            var origin = node.Nodes[\"origin\"]?.GetValueEx<Wz_Vector>(null);\n            item.Origin = origin.ToPointF();\n            if (item.Texture != null && loadHitMap)\n            {\n                item.HitMap = UIHelper.CreateHitMap(item.Texture.GetNativeTexture() as Microsoft.Xna.Framework.Graphics.Texture2D);\n            }\n            return item;\n        }\n\n        private void OnMapAreaClick(object obj)\n        {\n            if (obj is MapLink)\n            {\n                var link = (MapLink)obj;\n                var linkmapInfo = this.worldMaps.FirstOrDefault(map => map.Name == link.LinkMap);\n                if (linkmapInfo != null)\n                {\n                    this.CurrentWorldMap = linkmapInfo;\n                }\n            }\n            else if (obj is MapSpot)\n            {\n                var spot = (MapSpot)obj;\n                if (spot.MapNo.Count > 0)\n                {\n                    this.MapSpotClick?.Invoke(this, new MapSpotEventArgs(spot.MapNo[0]));\n                }\n            }\n        }\n\n        private void BtnBack_Click(object sender, RoutedEventArgs e)\n        {\n            this.GoBack();\n        }\n\n        private void MapArea_RightClick(object obj)\n        {\n            this.GoBack();\n        }\n\n        private void BtnClose_Click(object sender, RoutedEventArgs e)\n        {\n            this.Hide();\n        }\n\n        private void GoBack()\n        {\n            var curMap = this.CurrentWorldMap;\n            if (curMap != null && curMap.ParentMap != null)\n            {\n                if (curMap.ParentMapInfo != null)\n                {\n                    this.CurrentWorldMap = curMap.ParentMapInfo;\n                }\n            }\n        }\n\n        public class WorldMapArea : Control, ITooltipTarget\n        {\n            public WorldMapArea()\n            {\n                this.hitAreaCache = new List<DrawItem>();\n            }\n\n            public WorldMapInfo WorldMap { get; set; }\n            public int? CurrentMapID { get; set; }\n            public int SelectedQuestIndex { get; set; }\n            public int SelectedFogIndex { get; set; }\n\n            private List<DrawItem> hitAreaCache;\n\n            public object GetTooltipTarget(PointF mouseLocation)\n            {\n                var hitItem = HitTest(mouseLocation);\n                switch(hitItem)\n                {\n                    case MapSpot spot:\n                        return new UIWorldMap.MapSpotTooltip()\n                        {\n                            Spot = spot\n                        };\n\n                    case MapLink link:\n                        return new UIWorldMap.MapLinkTooltip()\n                        {\n                            Link = link\n                        };\n                }\n                return null;\n            }\n\n            public object HitTest(PointF position)\n            {\n                for (int i = this.hitAreaCache.Count - 1; i >= 0; i--)\n                {\n                    var item = this.hitAreaCache[i];\n                    var pos = new PointF(position.X - item.Position.X, position.Y - item.Position.Y);\n                    if (item.Target != null)\n                    {\n                        if (item.TextureItem.HitMap != null ? item.TextureItem.HitMap[(int)pos.X, (int)pos.Y]\n                            : new Rect(0, 0, item.TextureItem.Texture.Width, item.TextureItem.Texture.Height).Contains(pos))\n                        {\n                            return item.Target;\n                        }\n                    }\n                }\n                return null;\n            }\n\n            protected override void OnDraw(Renderer spriterenderer, double elapsedGameTime, float opacity)\n            {\n                base.OnDraw(spriterenderer, elapsedGameTime, opacity);\n                this.hitAreaCache.Clear();\n\n                var curMap = this.DataContext as WorldMapInfo;\n                if (curMap == null)\n                {\n                    return;\n                }\n\n                TextureItem baseImg = curMap.BaseImg;\n                SpotState[] spotState = new SpotState[curMap.MapList.Count];\n\n                if (curMap.QuestLimit != null && this.SelectedQuestIndex > -1 && this.SelectedQuestIndex < curMap.QuestLimit.Count)\n                {\n                    for(int i = 0; i <= this.SelectedQuestIndex; i++)\n                    {\n                        var quest = curMap.QuestLimit[i];\n                        //重写底图\n                        if (quest.BaseImg != null)\n                        {\n                            baseImg = quest.BaseImg;\n                        }\n                        else if (quest.IsDefault)\n                        {\n                            baseImg = curMap.BaseImg;\n                        }\n                        //重写spot属性\n\n                        if (quest.IsDefault)\n                        {\n                            for (int j = 0; j < spotState.Length; j++)\n                            {\n                                spotState[j].IsOverride = true;\n                                spotState[j].IsVisible = false;\n                            }\n                        }\n                        foreach (var spotIndex in quest.CloseMaps)\n                        {\n                            spotState[spotIndex].IsOverride = true;\n                            spotState[spotIndex].IsVisible = true;\n                            spotState[spotIndex].IsOpen = false;\n                            spotState[spotIndex].ImgType = quest.CloseMapImageType ?? -1;\n                        }\n                        foreach (var spotIndex in quest.OpenMaps)\n                        {\n                            spotState[spotIndex].IsOverride = true;\n                            spotState[spotIndex].IsVisible = true;\n                            spotState[spotIndex].IsOpen = true;\n                            spotState[spotIndex].ImgType = -1;\n                        }\n                    }\n                }\n\n                var baseOrigin = new PointF((int)this.Width / 2, (int)this.Height / 2);\n                if (baseImg?.Texture != null)\n                {\n                    var baseImgRect = new Rect(baseOrigin.X - baseImg.Origin.X, baseOrigin.Y - baseImg.Origin.Y, baseImg.Texture.Width, baseImg.Texture.Height);\n                    // workaround for CMS WorldMap177\n                    int overflowThreshold = 10;\n                    if (baseImgRect.Left < -overflowThreshold || baseImgRect.Right > this.Width + overflowThreshold\n                        || baseImgRect.Top < -overflowThreshold || baseImgRect.Bottom > this.Height + overflowThreshold)\n                    {\n                        // draw baseImg aligned center by adjusting baseOrigin\n                        PointF baseImgOriginNew = new PointF(baseImg.Texture.Width / 2, baseImg.Texture.Height / 2);\n                        PointF originDiff = new PointF(baseImgOriginNew.X - baseImg.Origin.X, baseImgOriginNew.Y - baseImg.Origin.Y);\n                        baseOrigin = new PointF(baseOrigin.X - originDiff.X, baseOrigin.Y - originDiff.Y);\n                    }\n                }\n\n                var drawOrder = new List<DrawItem>();\n                var addItem = new Action<TextureItem, object>((texture, obj) =>\n                {\n                    drawOrder.Add(new DrawItem() { Target = obj, TextureItem = texture });\n                });\n\n                //获取鼠标位置\n                var mousePos = InputManager.Current.MouseDevice.GetPosition(this);\n                MapSpot curSpot = null;\n\n                //绘制底图\n                if (baseImg != null)\n                {\n                    addItem(baseImg, null);\n                }\n\n                //绘制link\n                foreach (var link in curMap.MapLinks)\n                {\n                    if (link.LinkImg != null)\n                    {\n                        var pos = new PointF(mousePos.X - (baseOrigin.X - link.LinkImg.Origin.X),\n                           mousePos.Y - (baseOrigin.Y - link.LinkImg.Origin.Y));\n                        if (link.LinkImg.HitMap?[(int)pos.X, (int)pos.Y] ?? false)\n                        {\n                            addItem(link.LinkImg, link);\n                        }\n                    }\n                }\n\n                //绘制地图点\n                for (int i = 0, i0 = curMap.MapList.Count; i < i0; i++)\n                {\n                    var spot = curMap.MapList[i];\n                    int spotType = spot.Type;\n                    if (spotState[i].IsOverride) //重写判定\n                    {\n                        if (!spotState[i].IsVisible)\n                        {\n                            continue;\n                        }\n                        if (!spotState[i].IsOpen) //close\n                        {\n                            if (spotState[i].ImgType > -1)\n                            {\n                                spotType = spotState[i].ImgType;\n                            }\n                        }\n                    }\n\n                    var texture = this.FindResource(\"mapImage/\" + spotType) as TextureItem;\n                    if (texture != null)\n                    {\n                        var item = new TextureItem()\n                        {\n                            Texture = texture.Texture,\n                            Origin = new PointF(-spot.Spot.X + texture.Origin.X, -spot.Spot.Y + texture.Origin.Y),\n                            Z = 128 + texture.Z\n                        };\n                        if (spot.IsPreBB && curMap.BaseImg != null) //pre-bb地图点调整\n                        {\n                            item.Origin.X += curMap.BaseImg.Origin.X;\n                            item.Origin.Y += curMap.BaseImg.Origin.Y;\n                        }\n                        addItem(item, spot);\n\n                        //判断鼠标位置绘制path\n                        if (spot.Path != null)\n                        {\n                            var rect = new Rect(baseOrigin.X - item.Origin.X, baseOrigin.Y - item.Origin.Y, texture.Texture.Width, texture.Texture.Height);\n                            if (rect.Contains(mousePos))\n                            {\n                                addItem(spot.Path, null);\n                            }\n                        }\n                    }\n\n                    if (this.CurrentMapID != null && spot.MapNo.Contains(this.CurrentMapID.Value))\n                    {\n                        curSpot = spot;\n                    }\n                }\n\n                if (curMap.Fog != null && this.SelectedFogIndex > -1 && this.SelectedFogIndex < curMap.Fog.Count)\n                {\n                    for (int i = 0; i <= this.SelectedFogIndex; i++)\n                    {\n                        var fog = curMap.Fog[i];\n                        //重写底图\n                        if (fog.Img != null)\n                        {\n                            addItem(fog.Img, null);\n                        }\n                    }\n                }\n\n                //绘制当前地图标记\n                if (curSpot != null)\n                {\n                    var posTexture = this.FindResource(\"curPos\") as TextureItem;\n                    if (posTexture != null)\n                    {\n                        var item = new TextureItem()\n                        {\n                            Texture = posTexture.Texture,\n                            Origin = new PointF(-curSpot.Spot.X + posTexture.Origin.X, -curSpot.Spot.Y + posTexture.Origin.Y),\n                            Z = 255,\n                        };\n                        addItem(item, null);\n                    }\n                }\n\n                //开始绘制\n                foreach (var item in drawOrder.OrderBy(_item => _item.TextureItem.Z))\n                {\n                    var tex = item.TextureItem;\n                    if (tex != null)\n                    {\n                        var pos = new PointF(baseOrigin.X - tex.Origin.X, baseOrigin.Y - tex.Origin.Y);\n                        var size = new Size(tex.Texture.Width, tex.Texture.Height);\n                        spriterenderer.Draw(tex.Texture,\n                            new PointF(this.RenderPosition.X + pos.X, this.RenderPosition.Y + pos.Y),\n                            size,\n                            new ColorW(1f, 1f, 1f, opacity),\n                            false);\n\n                        item.Position = pos;\n                        this.hitAreaCache.Add(item);\n                    }\n                }\n            }\n\n            private class DrawItem\n            {\n                public object Target;\n                public PointF Position;\n                public TextureItem TextureItem;\n            }\n\n            private struct SpotState\n            {\n                public bool IsOverride;\n                public bool IsVisible;\n                public bool IsOpen;\n                public int ImgType;\n            }\n        }\n\n        public class WorldMapInfo\n        {\n            public string Name { get; set; }\n            public string DisplayName { get; set; }\n            public string ParentMap { get; set; }\n            public TextureItem BaseImg { get; set; }\n            public List<MapSpot> MapList { get; private set; } = new List<MapSpot>();\n            public List<MapLink> MapLinks { get; private set; } = new List<MapLink>();\n            public List<QuestLimitInfo> QuestLimit { get; private set; } = new List<QuestLimitInfo>();\n            public List<FogInfo> Fog { get; private set; } = new List<FogInfo>();\n\n            //缓存数据\n            public WorldMapInfo ParentMapInfo { get; set; }\n\n            public override string ToString()\n            {\n                return this.DisplayName ?? this.Name ?? base.ToString();\n            }\n        }\n\n        public class MapSpot\n        {\n            public PointF Spot { get; set; }\n            public bool IsPreBB { get; set; }\n            public int Type { get; set; }\n            public TextureItem Path { get; set; }\n            public string Title { get; set; }\n            public string Desc { get; set; }\n            public List<int> MapNo { get; private set; } = new List<int>();\n            public bool NoTooltip { get; set; }\n\n            public override string ToString()\n            {\n                return string.Format(\"{0}, {1} maps, {2}\", this.Title ?? \"(null)\", this.MapNo.Count, this.GetType().Name);\n            }\n        }\n\n        public class MapLink\n        {\n            public int Index { get; set; }\n            public string Tooltip { get; set; }\n            public string Desc { get; set; }\n            public string LinkMap { get; set; }\n            public TextureItem LinkImg { get; set; }\n        }\n\n        public class QuestLimitInfo\n        {\n            public string Name { get; set; }\n            public TextureItem BaseImg { get; set; }\n            public List<int> OpenMaps { get; private set; } = new List<int>();\n            public List<int> CloseMaps { get; private set; } = new List<int>();\n            public int Quest { get; set; }\n            public int State { get; set; }\n            public int? CloseMapImageType { get; set; }\n            public bool IsDefault { get; set; }\n\n            public override string ToString()\n            {\n                return this.Name;\n            }\n        }\n\n        public class FogInfo\n        {\n            public string Name { get; set; }\n            public TextureItem Img { get; set; }\n            public int Quest { get; set; }\n            public int State { get; set; }\n\n            public override string ToString()\n            {\n                return this.Name;\n            }\n        }\n\n        public class TextureItem\n        {\n            public TextureBase Texture;\n            public PointF Origin;\n            public int Z;\n            public HitMap HitMap;\n        }\n\n        public class MapSpotTooltip\n        {\n            public MapSpot Spot { get; set; }\n        }\n\n        public class MapLinkTooltip\n        {\n            public MapLink Link { get; set; }\n        }\n\n        public class MapSpotEventArgs : EventArgs\n        {\n            public MapSpotEventArgs(int mapID)\n            {\n                this.MapID = mapID;\n            }\n\n            public int MapID { get; private set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/WcR2Engine.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Input;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Renderers;\nusing EmptyKeys.UserInterface.Mvvm;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing System.Reflection;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Rendering;\nusing WzComparerR2.MapRender;\nusing ContentManager = Microsoft.Xna.Framework.Content.ContentManager;\nusing MRes = WzComparerR2.MapRender.Properties.Resources;\nusing Res = CharaSimResource.Resource;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    #region Engine\n    class WcR2Engine : Engine\n    {\n        public WcR2Engine(GraphicsDevice graphicsDevice, int nativeScreenWidth, int nativeScreenHeight)\n        {\n            _renderer = new WcR2Renderer(graphicsDevice, nativeScreenWidth, nativeScreenHeight);\n            _assetManager = new WcR2AssetManager();\n            _audioDevice = new WcR2AudioDevice();\n            _inputDevice = new MonoGameInputDevice();\n\n            if (ServiceManager.Instance.GetService<IClipboardService>() == null)\n            {\n                ServiceManager.Instance.AddService<IClipboardService>(new ClipBoardService());\n            }\n        }\n\n        private WcR2AssetManager _assetManager;\n        private WcR2AudioDevice _audioDevice;\n        private MonoGameInputDevice _inputDevice;\n        private WcR2Renderer _renderer;\n\n        public override AssetManager AssetManager\n        {\n            get { return _assetManager; }\n        }\n\n        public override AudioDevice AudioDevice\n        {\n            get { return _audioDevice; }\n        }\n\n        public override InputDeviceBase InputDevice\n        {\n            get { return _inputDevice; }\n        }\n\n        public override Renderer Renderer\n        {\n            get { return _renderer; }\n        }\n\n        public static void FixEKBugs()\n        {\n            InitialInputManager();\n            FixBorderTexture();\n            FixDefaultTheme();\n        }\n\n        public static void Unload()\n        {\n            if (Engine.instance?.InputDevice?.KeyboardState != null)\n            {\n                InputManager.Current.ClearFocus();\n                Engine.instance = null;\n\n                FieldInfo inputManagerCurrentField = typeof(InputManager)\n                    .GetFields(BindingFlags.Static | BindingFlags.NonPublic)\n                    .FirstOrDefault(field => field.FieldType == typeof(InputManager));\n                inputManagerCurrentField.SetValue(null, (InputManager)null);\n            }\n          \n            VisualTreeHelper.Instance.ClearParentCache();\n            typeof(MessageBox).GetFields(BindingFlags.Static | BindingFlags.NonPublic)\n                .FirstOrDefault(field => field.FieldType == typeof(MessageBox))\n                .SetValue(null, Activator.CreateInstance(typeof(MessageBox), true));\n            typeof(DragDrop).GetFields(BindingFlags.Static | BindingFlags.NonPublic)\n                .FirstOrDefault(field => field.FieldType == typeof(DragDrop))\n                .SetValue(null, Activator.CreateInstance(typeof(DragDrop), true));\n        }\n\n        public static void InitialInputManager()\n        {\n            //重置inputManager\n            {\n                var type = typeof(InputManager);\n                var fieldInfo = type.GetFields(BindingFlags.Static | BindingFlags.NonPublic)\n                    .FirstOrDefault(_field => _field.FieldType == typeof(InputManager));\n\n                var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.NonPublic, Type.DefaultBinder, new Type[0], null);\n\n                if (fieldInfo != null && ctor != null)\n                {\n                    var instance = ctor.Invoke(new object[0]);\n                    fieldInfo.SetValue(null, instance);\n                }\n            }\n            //重置keyboardState\n            {\n                var type = typeof(Keyboard);\n                var propertyInfo = type.GetProperty(nameof(Keyboard.Modifiers), BindingFlags.Static | BindingFlags.Public);\n\n                if (propertyInfo != null)\n                {\n                    propertyInfo.GetSetMethod(true).Invoke(null, new object[] { ModifierKeys.None });\n                }\n            }\n        }\n\n        private static void FixBorderTexture()\n        {\n            var type = typeof(EmptyKeys.UserInterface.Controls.Border);\n            var propSetFunc = typeof(DependencyProperty).GetProperty(nameof(DependencyProperty.DefaultValue)).GetSetMethod(true);\n            var defaultValue = Engine.instance.Renderer.CreateTexture(1, 1, false, false);\n            bool isUsed = false;\n            {\n                var field = type.GetField(\"BackgroundTextureProperty\", BindingFlags.Static | BindingFlags.NonPublic);\n                var oldVal = field.GetValue(null) as DependencyProperty;\n                if (oldVal != null)\n                {\n                    oldVal.DefaultMetadata.DefaultValue = defaultValue;\n                    propSetFunc.Invoke(oldVal, new[] { defaultValue });\n                    isUsed = true;\n                }\n            }\n            {\n                var field = type.GetField(\"BorderTextureProperty\", BindingFlags.Static | BindingFlags.NonPublic);\n                var oldVal = field.GetValue(null) as DependencyProperty;\n                if (oldVal != null)\n                {\n                    oldVal.DefaultMetadata.DefaultValue = defaultValue;\n                    propSetFunc.Invoke(oldVal, new[] { defaultValue });\n                    isUsed = true;\n                }\n            }\n\n            if (!isUsed)\n            {\n                defaultValue.Dispose();\n            }\n            else\n            {\n                defaultValue.GenerateOneToOne();\n            }\n        }\n\n        private static void FixDefaultTheme()\n        {\n            var type = typeof(EmptyKeys.UserInterface.Themes.EmptyKeysTheme);\n            var fieldType = typeof(ResourceDictionary);\n            var field = type.GetFields(BindingFlags.Static | BindingFlags.NonPublic)\n                .FirstOrDefault(f => f.FieldType == fieldType);\n            if (field != null)\n            {\n                ResourceDictionary value = field.GetValue(null) as ResourceDictionary;\n                if (value != null)\n                {\n                    var texture = value.Values.OfType<ImageBrush>().FirstOrDefault()?.ImageSource?.Texture?.GetNativeTexture() as Texture2D;\n                    if (texture != null && texture.IsDisposed)\n                    {\n                        value = new ResourceDictionary();\n                        field.SetValue(null, value);\n                        EmptyKeys.UserInterface.Themes.EmptyKeysTheme.CreateColorsAndBrushes();\n                        EmptyKeys.UserInterface.Themes.CommonHelpers.CreateStyles(value);\n                        EmptyKeys.UserInterface.Themes.CommonHelpers.CreateLocalizationResources(value);\n                        ResourceDictionary.DefaultDictionary = null;\n                    }\n                }\n            }\n        }\n    }\n\n    class WcR2AudioDevice : AudioDevice\n    {\n        public override SoundBase CreateSound(object nativeSound)\n        {\n            return new WcR2Sound(nativeSound);\n        }\n    }\n\n    class WcR2AssetManager : MonoGameAssetManager\n    {\n        public ContentManager DefaultContentManager { get; set; }\n\n        public override FontBase LoadFont(object contentManager, string file)\n        {\n            var cm = (contentManager as WcR2ContentManager) ?? this.DefaultContentManager;\n            if (cm != null)\n            {\n                var wcR2Font = cm.Load<IWcR2Font>(file);\n                return Engine.Instance.Renderer.CreateFont(wcR2Font);\n            }\n\n            return base.LoadFont(contentManager, file);\n        }\n\n        /// <remarks>\n        /// 用于<see cref=\"ImageManager.LoadImages\"/>。\n        /// </remarks>\n        public override TextureBase LoadTexture(object contentManager, string file)\n        {\n            var cm = (contentManager as WcR2ContentManager) ?? this.DefaultContentManager;\n            if (cm != null)\n            {\n                var texture = cm.Load<Texture2D>(file);\n                if (texture != null)\n                {\n                    return Engine.Instance.Renderer.CreateTexture(texture);\n                }\n            }\n\n            return base.LoadTexture(contentManager, file);\n        }\n    }\n    #endregion\n\n    #region Resources implementation\n    class WcR2Font : FontBase\n    {\n        public WcR2Font(object nativeFont) : base(nativeFont)\n        {\n            this.NativeFont = nativeFont as IWcR2Font;\n            if (this.NativeFont == null)\n            {\n                throw new ArgumentException(\"nativeFont not implements IWcR2Font.\");\n            }\n        }\n\n        public IWcR2Font NativeFont { get; private set; }\n\n        public override char? DefaultCharacter\n        {\n            get { return null; }\n        }\n\n        public override FontEffectType EffectType\n        {\n            get { return FontEffectType.None; }\n        }\n\n        public override int LineSpacing\n        {\n            get { return (int)NativeFont.LineHeight; }\n        }\n\n        public override float Spacing\n        {\n            get { return 0; }\n            set { throw new NotImplementedException(); }\n        }\n\n        public override object GetNativeFont()\n        {\n            return this.NativeFont;\n        }\n\n        public override Size MeasureString(StringBuilder text, float dpiScaleX, float dpiScaleY)\n        {\n            var size = NativeFont.MeasureString(text);\n            return new Size(size.X, size.Y);\n        }\n\n        public override Size MeasureString(string text, float dpiScaleX, float dpiScaleY)\n        {\n            var size = NativeFont.MeasureString(text);\n            return new Size(size.X, size.Y);\n        }\n    }\n\n    class WcR2Sound : SoundBase\n    {\n        public WcR2Sound(object nativeSound) : base(nativeSound)\n        {\n            this.music = nativeSound as Music;\n        }\n\n        private Music music;\n\n        public override SoundState State\n        {\n            get\n            {\n                switch (music.State)\n                {\n                    case Music.PlayState.Stopped: return SoundState.Stopped;\n                    case Music.PlayState.Playing: return SoundState.Playing;\n                    case Music.PlayState.Paused: return SoundState.Paused;\n                    default: return SoundState.Stopped;\n                }\n            }\n        }\n\n        public override float Volume\n        {\n            get { return music.Volume; }\n            set { music.Volume = value; }\n        }\n\n        public override void Pause()\n        {\n            music.Pause();\n        }\n\n        public override void Play()\n        {\n            music.Play();\n        }\n\n        public override void Stop()\n        {\n            music.Stop();\n        }\n    }\n    #endregion\n\n    class WcR2ContentManager : ContentManager\n    {\n        public WcR2ContentManager(IServiceProvider serviceProvider)\n            : base(serviceProvider)\n        {\n        }\n\n        private GraphicsDevice GraphicsDevice\n        {\n            get\n            {\n                return this.ServiceProvider.GetService<IGraphicsDeviceService>().GraphicsDevice;\n            }\n        }\n\n        public bool UseD2DFont { get; set; }\n\n        public override T Load<T>(string assetName)\n        {\n            if (assetName == \"DirectionalBlurShader\")\n            {\n                return default(T);\n            }\n            if (typeof(T) == typeof(IWcR2Font))\n            {\n                object value;\n                if (!LoadedAssets.TryGetValue(assetName, out value))\n                {\n                    value = LoadXnaFont(assetName);\n                    if (value != null)\n                    {\n                        LoadedAssets[assetName] = value;\n                    }\n                }\n                return (T)value;\n            }\n            else if (typeof(T) == typeof(Texture2D))\n            {\n                object value;\n                if (!LoadedAssets.TryGetValue(assetName, out value))\n                {\n                    var bitmap = MRes.ResourceManager.GetObject(assetName) as System.Drawing.Bitmap;\n                    if (bitmap == null)\n                    {\n                        var obj = Res.ResourceManager.GetObject(assetName);\n                        bitmap = Res.ResourceManager.GetObject(assetName) as System.Drawing.Bitmap;\n                    }\n                    if (bitmap != null)\n                    {\n                        value = bitmap.ToTexture(this.GraphicsDevice);\n                    }\n                    else //寻找wz\n                    {\n                        var png = PluginBase.PluginManager.FindWz(assetName).GetValueEx<Wz_Png>(null);\n                        if (png != null)\n                        {\n                            value = png.ToTexture(this.GraphicsDevice);\n                        }\n                    }\n\n                    if (value != null)\n                    {\n                        LoadedAssets[assetName] = value;\n                    }\n                }\n                return (T)value;\n            }\n            return base.Load<T>(assetName);\n        }\n\n        private IWcR2Font LoadXnaFont(string assetName)\n        {\n            string[] fontDesc = assetName.Split(new[] { ',' }, 3);\n            string familyName = fontDesc[0];\n            float size;\n            System.Drawing.FontStyle fStyle;\n            if (float.TryParse(fontDesc[1], out size)\n                && Enum.TryParse(fontDesc[2], out fStyle))\n            {\n                if (this.UseD2DFont)\n                {\n                    var d2dFont = new D2DFont(familyName, size,\n                        (fStyle & System.Drawing.FontStyle.Bold) != 0,\n                        (fStyle & System.Drawing.FontStyle.Italic) != 0\n                        );\n                    return new D2DFontAdapter(d2dFont);\n                }\n                else\n                {\n                    var baseFont = new System.Drawing.Font(familyName, size, fStyle, System.Drawing.GraphicsUnit.Pixel);\n                    var xnaFont = new XnaFont(GraphicsDevice, baseFont);\n                    return new XnaFontAdapter(xnaFont);\n                }\n            }\n            else\n            {\n                return null;\n            }\n        }\n\n        public override void Unload()\n        {\n            foreach (var kv in this.LoadedAssets)\n            {\n                IDisposable disposable = kv.Value as IDisposable;\n                if (disposable != null)\n                {\n                    disposable.Dispose();\n                }\n            }\n            base.Unload();\n        }\n    }\n\n    class ClipBoardService : IClipboardService\n    {\n        public string GetText()\n        {\n            var text = string.Empty;\n            var thread = new Thread(() =>\n            {\n                text = System.Windows.Forms.Clipboard.GetText();\n            });\n            thread.SetApartmentState(ApartmentState.STA);\n            thread.Start();\n            thread.Join();\n            return text;\n        }\n\n        public void SetText(string text)\n        {\n            var thread = new Thread(() =>\n            {\n                System.Windows.Forms.Clipboard.SetText(text);\n            });\n            thread.SetApartmentState(ApartmentState.STA);\n            thread.Start();\n            thread.Join();\n        }\n    }\n\n    static class Extensions\n    {\n        public static void AddFont(this FontManager fontManager, string familyName, float size, FontStyle style)\n        {\n            System.Drawing.FontStyle fStyle = System.Drawing.FontStyle.Regular;\n            if ((style & FontStyle.Bold) != 0)\n                fStyle |= System.Drawing.FontStyle.Bold;\n            if ((style & FontStyle.Italic) != 0)\n                fStyle |= System.Drawing.FontStyle.Italic;\n            string assetName = MapRenderFonts.GetFontResourceKey(familyName, size, fStyle);\n            fontManager.AddFont(familyName, size, style, assetName);\n        }\n\n        public static Rect ToRect(this Microsoft.Xna.Framework.Rectangle rect)\n        {\n            return new Rect(rect.X, rect.Y, rect.Width, rect.Height);\n        }\n\n        public static PointF ToPointF(this WzComparerR2.WzLib.Wz_Vector vector)\n        {\n            return vector == null ? new PointF() : new PointF(vector.X, vector.Y);\n        }\n\n        public static Size MeasureString(this FontBase font, string text, Size layoutSize)\n        {\n            var wcR2Font = (font.GetNativeFont() as IWcR2Font)?.BaseFont;\n            if (wcR2Font != null)\n            {\n                if (wcR2Font is XnaFont)\n                {\n                    var xnaFont = (XnaFont)wcR2Font;\n                    var size = xnaFont.MeasureString(text, new Vector2(layoutSize.Width, layoutSize.Height));\n                    return new Size(size.X, size.Y);\n                }\n                else if (wcR2Font is D2DFont)\n                {\n                    var d2dFont = (D2DFont)wcR2Font;\n                    var size = d2dFont.MeasureString(text, new Vector2(layoutSize.Width, layoutSize.Height));\n                    return new Size(size.X, size.Y);\n                }\n            }\n            return font.MeasureString(text, 1, 1);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/WcR2Renderer.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Microsoft.Xna.Framework;\nusing Microsoft.Xna.Framework.Graphics;\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Media;\nusing EmptyKeys.UserInterface.Renderers;\nusing WzComparerR2.Rendering;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    /// <summary>\n    /// Implements Mono Game renderer.\n    /// </summary>\n    public class WcR2Renderer : Renderer, IDisposable\n    {\n        /// <summary>\n        /// The graphics device\n        /// </summary>\n        /// <value>\n        /// The graphics device.\n        /// </value>\n        public GraphicsDevice GraphicsDevice\n        {\n            get;\n            private set;\n        }\n\n        private RasterizerState clippingRasterizeState = new RasterizerState { ScissorTestEnable = true, CullMode = CullMode.None };\n        private RasterizerState previousState;\n        private SpriteBatchEx spriteBatch;\n        private D2DRenderer d2dRenderer;\n\n        private bool isClipped;\n        private Stack<Rectangle> clipRectanges;\n\n        private Stack<Effect> activeEffects;\n        private Effect currentActiveEffect;\n\n        private BasicEffect basicEffect;\n        private RasterizerState rasterizeStateGeometry = new RasterizerState { ScissorTestEnable = false, CullMode = CullMode.None, FillMode = FillMode.Solid };\n        //private bool isSpriteRenderInProgress;\n        private DrawState currState;\n\n        /// <summary>\n        /// Gets a value indicating whether is full screen.\n        /// </summary>\n        /// <value>\n        /// <c>true</c> if is full screen; otherwise, <c>false</c>.\n        /// </value>\n        public override bool IsFullScreen\n        {\n            get { return GraphicsDevice.PresentationParameters.IsFullScreen; }\n        }\n\n        /// <summary>\n        /// Gets or sets the projection.\n        /// </summary>\n        /// <value>\n        /// The projection.\n        /// </value>\n        /// <exception cref=\"System.NotImplementedException\">\n        /// </exception>\n        public Matrix Projection\n        {\n            get;\n            set;\n        }\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"WcR2Renderer\" /> class.\n        /// </summary>\n        /// <param name=\"graphicsDevice\">The graphics device.</param>\n        /// <param name=\"nativeScreenWidth\">Width of the native screen.</param>\n        /// <param name=\"nativeScreenHeight\">Height of the native screen.</param>\n        public WcR2Renderer(GraphicsDevice graphicsDevice, int nativeScreenWidth, int nativeScreenHeight)\n        {\n            spriteBatch = new SpriteBatchEx(graphicsDevice);\n            this.d2dRenderer = new D2DRenderer(graphicsDevice);\n            GraphicsDevice = graphicsDevice;\n\n            if (graphicsDevice.PresentationParameters.IsFullScreen)\n            {\n                NativeScreenWidth = nativeScreenWidth;\n                NativeScreenHeight = nativeScreenHeight;\n            }\n            else\n            {\n                NativeScreenWidth = graphicsDevice.PresentationParameters.BackBufferWidth;\n                NativeScreenHeight = graphicsDevice.PresentationParameters.BackBufferHeight;\n            }\n\n            clipRectanges = new Stack<Rectangle>();\n            activeEffects = new Stack<Effect>();\n        }\n\n        /// <summary>\n        /// Begins the rendering\n        /// </summary>\n        public override void Begin()\n        {\n            Begin(null);\n        }\n\n        private void UpdateCurrentEffect(EffectBase effect)\n        {\n            Effect effectInstance = effect != null ? effect.GetNativeEffect() as Effect : null;\n            if (effectInstance != null)\n            {\n                if (currentActiveEffect != null)\n                {\n                    activeEffects.Push(currentActiveEffect);\n                }\n\n                currentActiveEffect = effectInstance;\n            }\n\n            if (currentActiveEffect == null && activeEffects.Count > 0)\n            {\n                currentActiveEffect = activeEffects.Pop();\n            }\n        }\n\n        /// <summary>\n        /// Draws the specified texture.\n        /// </summary>\n        /// <param name=\"texture\">The texture.</param>\n        /// <param name=\"position\">The position.</param>\n        /// <param name=\"renderSize\">Size of the render.</param>\n        /// <param name=\"color\">The color.</param>\n        /// <param name=\"centerOrigin\">if set to <c>true</c> [center origin].</param>\n        public override void Draw(TextureBase texture, PointF position, Size renderSize, ColorW color, bool centerOrigin)\n        {\n            Rectangle testRectangle;\n            testRectangle.X = (int)position.X;\n            testRectangle.Y = (int)position.Y;\n            testRectangle.Width = (int)renderSize.Width;\n            testRectangle.Height = (int)renderSize.Height;\n            if (isClipped && !this.GraphicsDevice.ScissorRectangle.Intersects(testRectangle))\n            {\n                return;\n            }\n\n            this.Prepare(DrawState.Sprite);\n            Color vecColor = new Color();\n            vecColor.PackedValue = color.PackedValue;\n            Texture2D native = texture.GetNativeTexture() as Texture2D;\n            spriteBatch.Draw(native, testRectangle, vecColor);\n        }\n\n        /// <summary>\n        /// Draws the specified texture.\n        /// </summary>\n        /// <param name=\"texture\">The texture.</param>\n        /// <param name=\"position\">The position.</param>\n        /// <param name=\"renderSize\">Size of the render.</param>\n        /// <param name=\"color\">The color.</param>\n        /// <param name=\"source\">The source.</param>\n        /// <param name=\"centerOrigin\">if set to <c>true</c> [center origin].</param>\n        public override void Draw(TextureBase texture, PointF position, Size renderSize, ColorW color, Rect source, bool centerOrigin)\n        {\n            Rectangle testRectangle;\n            testRectangle.X = (int)position.X;\n            testRectangle.Y = (int)position.Y;\n            testRectangle.Width = (int)renderSize.Width;\n            testRectangle.Height = (int)renderSize.Height;\n            if (isClipped && !this.GraphicsDevice.ScissorRectangle.Intersects(testRectangle))\n            {\n                return;\n            }\n\n            this.Prepare(DrawState.Sprite);\n            Rectangle sourceRect;\n            sourceRect.X = (int)source.X;\n            sourceRect.Y = (int)source.Y;\n            sourceRect.Width = (int)source.Width;\n            sourceRect.Height = (int)source.Height;\n            Color vecColor = new Color();\n            vecColor.PackedValue = color.PackedValue;\n            Texture2D native = texture.GetNativeTexture() as Texture2D;\n            spriteBatch.Draw(native, testRectangle, sourceRect, vecColor, 0, Vector2.Zero, SpriteEffects.None, 0);\n        }\n\n        /// <summary>\n        /// Ends rendering\n        /// </summary>\n        public override void End(bool endEffect = false)\n        {\n            this.Flush();\n            isClipped = false;\n            if (endEffect)\n            {\n                currentActiveEffect = null;\n            }\n            else\n            {\n                activeEffects.Push(currentActiveEffect);\n                currentActiveEffect = null;\n            }\n        }\n\n        /// <summary>\n        /// Begins the clipped.\n        /// </summary>\n        /// <param name=\"clipRect\">The clip rect.</param>\n        public override void BeginClipped(Rect clipRect)\n        {\n            BeginClipped(clipRect, null);\n        }\n\n        /// <summary>\n        /// Begins the clipped rendering with custom effect\n        /// </summary>\n        /// <param name=\"clipRect\">The clip rect.</param>\n        /// <param name=\"effect\">The effect.</param>\n        public override void BeginClipped(Rect clipRect, EffectBase effect)\n        {\n            Rectangle clipRectangle;\n            clipRectangle.X = (int)clipRect.X;\n            clipRectangle.Y = (int)clipRect.Y;\n            clipRectangle.Width = (int)clipRect.Width;\n            clipRectangle.Height = (int)clipRect.Height;\n\n            UpdateCurrentEffect(effect);\n\n            BeginClipped(clipRectangle);\n        }\n\n        /// <summary>\n        /// Ends the clipped drawing\n        /// </summary>\n        public override void EndClipped(bool endEffect = false)\n        {\n            this.Flush();\n            isClipped = false;\n\n            if (endEffect)\n            {\n                currentActiveEffect = null;\n            }\n            else\n            {\n                activeEffects.Push(currentActiveEffect);\n                currentActiveEffect = null;\n            }\n\n            clipRectanges.Pop();\n        }\n\n        /// <summary>\n        /// Gets the viewport.\n        /// </summary>\n        /// <returns></returns>\n        public override Rect GetViewport()\n        {\n            Viewport viewport = GraphicsDevice.Viewport;\n            return new Rect(viewport.X, viewport.Y, viewport.Width, viewport.Height);\n        }\n\n        /// <summary>\n        /// Creates the texture.\n        /// </summary>\n        /// <param name=\"width\">The width.</param>\n        /// <param name=\"height\">The height.</param>\n        /// <param name=\"mipmap\">if set to <c>true</c> [mipmap].</param>\n        /// <param name=\"dynamic\">if set to <c>true</c> [dynamic].</param>\n        /// <returns></returns>\n        public override TextureBase CreateTexture(int width, int height, bool mipmap, bool dynamic)\n        {\n            if (width == 0 || height == 0)\n            {\n                return null;\n            }\n\n            Texture2D native = new Texture2D(GraphicsDevice, width, height, false, SurfaceFormat.Color);\n            MonoGameTexture texture = new MonoGameTexture(native);\n            return texture;\n        }\n\n        /// <summary>\n        /// Resets the size of the native. Sets NativeScreenWidth and NativeScreenHeight based on active back buffer\n        /// </summary>\n        public override void ResetNativeSize()\n        {\n            NativeScreenWidth = GraphicsDevice.PresentationParameters.BackBufferWidth;\n            NativeScreenHeight = GraphicsDevice.PresentationParameters.BackBufferHeight;\n        }\n\n        /// <summary>\n        /// Draws the color of the geometry.\n        /// </summary>\n        /// <param name=\"buffer\">The buffer.</param>\n        /// <param name=\"position\">The position.</param>\n        /// <param name=\"color\">The color.</param>\n        /// <param name=\"opacity\">The opacity.</param>\n        /// <param name=\"depth\">The depth.</param>\n        public override void DrawGeometryColor(GeometryBuffer buffer, PointF position, ColorW color, float opacity, float depth)\n        {\n            if (basicEffect == null)\n            {\n                basicEffect = new BasicEffect(GraphicsDevice);\n            }\n\n            basicEffect.Alpha = color.A / (float)byte.MaxValue * opacity;\n            //color = color * effect.Alpha;\n            basicEffect.DiffuseColor = new Vector3(color.R / (float)byte.MaxValue, color.G / (float)byte.MaxValue, color.B / (float)byte.MaxValue);\n            basicEffect.TextureEnabled = false;\n            basicEffect.VertexColorEnabled = true;\n\n            DrawGeometry(buffer, position, depth);\n        }\n\n        /// <summary>\n        /// Draws the geometry texture.\n        /// </summary>\n        /// <param name=\"buffer\">The buffer.</param>\n        /// <param name=\"position\">The position.</param>\n        /// <param name=\"texture\">The texture.</param>\n        /// <param name=\"opacity\">The opacity.</param>\n        /// <param name=\"depth\">The depth.</param>\n        public override void DrawGeometryTexture(GeometryBuffer buffer, PointF position, TextureBase texture, float opacity, float depth)\n        {\n            if (basicEffect == null)\n            {\n                basicEffect = new BasicEffect(GraphicsDevice);\n            }\n\n            basicEffect.Alpha = opacity;\n            basicEffect.DiffuseColor = new Vector3(1, 1, 1);\n            basicEffect.Texture = texture.GetNativeTexture() as Texture2D;\n            basicEffect.VertexColorEnabled = false;\n            basicEffect.TextureEnabled = true;\n\n            DrawGeometry(buffer, position, depth);\n        }\n\n        /// <summary>\n        /// Determines whether the specified rectangle is outside of clip bounds\n        /// </summary>\n        /// <param name=\"position\">The position.</param>\n        /// <param name=\"renderSize\">Size of the render.</param>\n        /// <returns></returns>\n        public override bool IsClipped(PointF position, Size renderSize)\n        {\n            if (isClipped)\n            {\n                Rectangle testRectangle;\n                testRectangle.X = (int)position.X;\n                testRectangle.Y = (int)position.Y;\n                testRectangle.Width = (int)renderSize.Width;\n                testRectangle.Height = (int)renderSize.Height;\n\n                if (!this.GraphicsDevice.ScissorRectangle.Intersects(testRectangle))\n                {\n                    return true;\n                }\n            }\n\n            return false;\n        }\n\n        /// <summary>\n        /// Creates the effect.\n        /// </summary>\n        /// <param name=\"nativeEffect\">The native effect.</param>\n        /// <returns></returns> \n        public override EffectBase CreateEffect(object nativeEffect)\n        {\n            return new MonoGameEffect(nativeEffect);\n        }\n\n        /// <summary>\n        /// Gets the SDF font effect.\n        /// </summary>\n        /// <returns></returns>\n        /// <exception cref=\"System.NotImplementedException\"></exception>\n        public override EffectBase GetSDFFontEffect()\n        {\n            throw new NotImplementedException();\n        }\n\n        public override FontBase CreateFont(object nativeFont)\n        {\n            return new WcR2Font(nativeFont);\n        }\n\n        public override TextureBase CreateTexture(object nativeTexture)\n        {\n            if (nativeTexture is Texture2D)\n            {\n                return new MonoGameTexture(nativeTexture);\n            }\n            else\n            {\n                throw new ArgumentException(\"nativeTexture is not Texture2D.\");\n            }\n        }\n\n        public override void DrawText(FontBase font, string text, PointF position, Size renderSize, ColorW color, PointF scale, float depth)\n        {\n            var wcR2Font = (font.GetNativeFont() as IWcR2Font)?.BaseFont;\n            if (wcR2Font != null)\n            {\n                if (wcR2Font is XnaFont)\n                {\n                    //snap pixels\n                    position.X = (float)Math.Round(position.X);\n                    position.Y = (float)Math.Round(position.Y);\n\n                    Prepare(DrawState.Sprite);\n                    spriteBatch.DrawStringEx((XnaFont)wcR2Font,\n                        text,\n                        new Vector2(position.X, position.Y),\n                        new Vector2(renderSize.Width, renderSize.Height),\n                        new Color(color.R, color.G, color.B, color.A));\n                }\n                else if (wcR2Font is D2DFont)\n                {\n                    Prepare(DrawState.D2D);\n                    d2dRenderer.DrawString((D2DFont)wcR2Font,\n                        text,\n                        new Vector2(position.X, position.Y),\n                        new Vector2(renderSize.Width, renderSize.Height),\n                        new Color(color.R, color.G, color.B, color.A));\n                }\n            }\n        }\n\n        /// <summary>\n        /// Begins the rendering with custom effect\n        /// </summary>\n        /// <param name=\"effect\">The effect.</param>\n        public override void Begin(EffectBase effect)\n        {\n            isClipped = false;\n            UpdateCurrentEffect(effect);\n            if (previousState != null)\n            {\n                GraphicsDevice.RasterizerState = previousState;\n                previousState = null;\n            }\n\n            if (clipRectanges.Count > 0)\n            {\n                Rectangle previousClip = clipRectanges.Pop();\n                BeginClipped(previousClip);\n            }\n            else\n            {\n                currState = DrawState.None;\n            }\n        }\n\n        /// <summary>\n        /// Begins the clipped.\n        /// </summary>\n        /// <param name=\"clipRect\">The clip rect.</param>\n        private void BeginClipped(Rectangle clipRect)\n        {\n            isClipped = true;\n            currState = DrawState.None;\n\n            if (clipRectanges.Count > 0)\n            {\n                Rectangle previousClip = clipRectanges.Peek();\n                if (previousClip.Intersects(clipRect))\n                {\n                    clipRect = Rectangle.Intersect(previousClip, clipRect);\n                }\n                else\n                {\n                    clipRect = previousClip;\n                }\n            }\n\n            GraphicsDevice.ScissorRectangle = clipRect;\n            previousState = spriteBatch.GraphicsDevice.RasterizerState;\n            clipRectanges.Push(clipRect);\n        }\n\n        /// <summary>\n        /// Creates the geometry buffer.\n        /// </summary>\n        /// <returns></returns>\n        public override GeometryBuffer CreateGeometryBuffer()\n        {\n            return new WcR2GeometryBuffer(this.GraphicsDevice);\n        }\n\n        private void DrawGeometry(GeometryBuffer buffer, PointF position, float depth)\n        {\n            this.Prepare(DrawState.Geometry);\n\n            WcR2GeometryBuffer wcR2Buffer = buffer as WcR2GeometryBuffer;\n            GraphicsDevice device = GraphicsDevice;\n\n            RasterizerState rasState = device.RasterizerState;\n            BlendState blendState = device.BlendState;\n            DepthStencilState stencilState = device.DepthStencilState;\n\n            device.BlendState = BlendState.NonPremultiplied;\n            device.DepthStencilState = DepthStencilState.DepthRead;\n\n            if (isClipped)\n            {\n                device.RasterizerState = clippingRasterizeState;\n            }\n            else\n            {\n                device.RasterizerState = rasterizeStateGeometry;\n            }\n\n            basicEffect.World = Matrix.CreateTranslation(position.X, position.Y, depth);\n            basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);\n            basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, (float)device.Viewport.Width, (float)device.Viewport.Height, 0, 1.0f, 1000.0f);\n\n            device.SetVertexBuffer(wcR2Buffer.VertexBuffer);\n            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)\n            {\n                pass.Apply();\n\n                switch (buffer.PrimitiveType)\n                {\n                    case GeometryPrimitiveType.TriangleList:\n                        device.DrawPrimitives(PrimitiveType.TriangleList, 0, wcR2Buffer.PrimitiveCount);\n                        break;\n                    case GeometryPrimitiveType.TriangleStrip:\n                        device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, wcR2Buffer.PrimitiveCount);\n                        break;\n                    case GeometryPrimitiveType.LineList:\n                        device.DrawPrimitives(PrimitiveType.LineList, 0, wcR2Buffer.PrimitiveCount);\n                        break;\n                    case GeometryPrimitiveType.LineStrip:\n                        device.DrawPrimitives(PrimitiveType.LineStrip, 0, wcR2Buffer.PrimitiveCount);\n                        break;\n                    default:\n                        break;\n                }\n            }\n\n            device.DepthStencilState = stencilState;\n            device.BlendState = blendState;\n            device.RasterizerState = rasState;\n        }\n\n        private void Prepare(DrawState nextState)\n        {\n            if (this.currState == nextState)\n            {\n                return;\n            }\n\n            Flush();\n\n            switch (nextState)\n            {\n                case DrawState.None: break;\n                case DrawState.Geometry: break;\n                case DrawState.Sprite:\n                    RasterizerState rasterizer = isClipped ? this.clippingRasterizeState : null;\n                    spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearClamp, DepthStencilState.None, rasterizer, effect: currentActiveEffect);\n                    break;\n\n                case DrawState.D2D:\n                    d2dRenderer.Begin();\n                    if (isClipped)\n                    {\n                        d2dRenderer.PushClip(this.GraphicsDevice.ScissorRectangle);\n                    }\n                    break;\n            }\n\n            this.currState = nextState;\n        }\n\n        private void Flush()\n        {\n            switch (this.currState)\n            {\n                case DrawState.None: break;\n                case DrawState.Geometry: break;\n                case DrawState.Sprite:\n                    this.spriteBatch.End();\n                    break;\n\n                case DrawState.D2D:\n                    if (isClipped)\n                    {\n                        this.d2dRenderer.PopClip();\n                    }\n                    this.d2dRenderer.End();\n                    break;\n            }\n        }\n\n        public void Dispose()\n        {\n            this.activeEffects.Clear();\n\n            this.clippingRasterizeState?.Dispose();\n            this.rasterizeStateGeometry?.Dispose();\n            this.spriteBatch?.Dispose();\n            this.basicEffect?.Dispose();\n        }\n\n        enum DrawState\n        {\n            None = 0,\n            Geometry = 1,\n            Sprite = 2,\n            D2D = 3\n        }\n    }\n\n    public class WcR2GeometryBuffer : GeometryBuffer\n    {\n        /// <summary>\n        /// Gets or sets the vertex buffer.\n        /// </summary>\n        /// <value>\n        /// The vertex buffer.\n        /// </value>\n        public VertexBuffer VertexBuffer { get; set; }\n\n        private GraphicsDevice graphicsDevice;\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"WcR2GeometryBuffer\"/> class.\n        /// </summary>\n        public WcR2GeometryBuffer(GraphicsDevice device)\n            : base()\n        {\n            this.graphicsDevice = device;\n        }\n\n        /// <summary>\n        /// Fills the color type buffer (VertexPositionColor)\n        /// </summary>\n        /// <param name=\"points\">The points.</param>\n        /// <param name=\"primitiveType\">Type of the primitive.</param>\n        public override void FillColor(List<PointF> points, GeometryPrimitiveType primitiveType)\n        {\n            SetPrimitiveCount(primitiveType, points.Count);\n\n            VertexPositionColor[] vertex = new VertexPositionColor[points.Count];\n            for (int i = 0; i < points.Count; i++)\n            {\n                vertex[i] = new VertexPositionColor(new Vector3(points[i].X, points[i].Y, 0), Color.White);\n            }\n\n            VertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.VertexDeclaration, vertex.Length, BufferUsage.WriteOnly);\n            VertexBuffer.SetData<VertexPositionColor>(vertex);\n        }\n\n        private void SetPrimitiveCount(GeometryPrimitiveType primitiveType, int pointCount)\n        {\n            PrimitiveType = primitiveType;\n            switch (primitiveType)\n            {\n                case GeometryPrimitiveType.TriangleList:\n                    PrimitiveCount = pointCount / 3;\n                    break;\n                case GeometryPrimitiveType.TriangleStrip:\n                    PrimitiveCount = pointCount - 2;\n                    break;\n                case GeometryPrimitiveType.LineList:\n                    PrimitiveCount = pointCount / 2;\n                    break;\n                case GeometryPrimitiveType.LineStrip:\n                    PrimitiveCount = pointCount - 1;\n                    break;\n                default:\n                    break;\n            }\n        }\n\n        /// <summary>\n        /// Fills the texture type buffer (VertexPositionTexture)\n        /// </summary>\n        /// <param name=\"points\">The points.</param>\n        /// <param name=\"destinationSize\">Size of the destination.</param>\n        /// <param name=\"sourceRect\">The source rect.</param>\n        /// <param name=\"primitiveType\">Type of the primitive.</param>\n        public override void FillTexture(List<PointF> points, Size destinationSize, Rect sourceRect, GeometryPrimitiveType primitiveType)\n        {\n            SetPrimitiveCount(primitiveType, points.Count);\n\n            VertexPositionTexture[] vertex = new VertexPositionTexture[points.Count];\n            for (int i = 0; i < points.Count; i++)\n            {\n                Vector2 uv = new Vector2(sourceRect.X + (points[i].X / destinationSize.Width) * sourceRect.Width,\n                                         sourceRect.Y + (points[i].Y / destinationSize.Height) * sourceRect.Height);\n                vertex[i] = new VertexPositionTexture(new Vector3(points[i].X, points[i].Y, 0), uv);\n            }\n\n            VertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionTexture.VertexDeclaration, vertex.Length, BufferUsage.WriteOnly);\n            VertexBuffer.SetData<VertexPositionTexture>(vertex);\n        }\n\n        /// <summary>\n        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.\n        /// </summary>\n        public override void Dispose()\n        {\n            if (VertexBuffer != null && !VertexBuffer.IsDisposed)\n            {\n                VertexBuffer.Dispose();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/UI/WindowEx.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing EmptyKeys.UserInterface;\nusing EmptyKeys.UserInterface.Controls;\nusing EmptyKeys.UserInterface.Data;\nusing EmptyKeys.UserInterface.Input;\n\nnamespace WzComparerR2.MapRender.UI\n{\n    class WindowEx : Window\n    {\n        public WindowEx()\n        {\n            InitializeComponents();\n        }\n\n        protected virtual void InitializeComponents()\n        {\n            this.Template = new ControlTemplate(CreateControls);\n        }\n\n        private UIElement CreateControls(UIElement parent)\n        {\n            ContentPresenter p = new ContentPresenter();\n            p.Parent = parent;\n            p.SetBinding(ContentPresenter.ContentProperty, new Binding() { Source = this, SourceDependencyProperty = Window.ContentProperty });\n            return p;\n        }\n\n        protected void SetDragTarget(UIElement element)\n        {\n            element.Name = \"PART_WindowTitleBorder\";\n        }\n\n        protected override void OnPropertyChanged(DependencyProperty property)\n        {\n            base.OnPropertyChanged(property);\n\n            if (property == VisibilityProperty)\n            {\n                if (this.Visibility == Visibility.Visible)\n                {\n                    this.Focus();\n                }\n            }\n        }\n\n        protected override void OnGotFocus(object sender, RoutedEventArgs e)\n        {\n            base.OnGotFocus(sender, e);\n\n            if (this.IsOnTop)\n            {\n                this.BringToFront();\n            }\n        }\n\n        protected override void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)\n        {\n            base.OnPreviewMouseDown(sender, e);\n\n            if (this.IsOnTop && !this.IsFocused)\n            {\n                this.BringToFront();\n            }\n        }\n\n        public void Toggle()\n        {\n            if (this.Visibility == Visibility.Visible)\n            {\n                this.Hide();\n            }\n            else\n            {\n                this.Show();\n            }\n        }\n\n        public void BringToFront()\n        {\n            var root = this.Parent as UIRoot;\n            if (root != null)\n            {\n                if (!this.IsZFront)\n                {\n                    root.Windows.Remove(this);\n                    root.Windows.Add(this);\n                }\n            }\n        }\n\n        public void Show()\n        {\n            this.Visibility = Visibility.Visible;\n        }\n\n        public void Hide()\n        {\n            this.Visibility = Visibility.Collapsed;\n        }\n\n        private bool IsZFront\n        {\n            get\n            {\n                var root = this.Parent as UIRoot;\n                if (root != null)\n                {\n                    for (int i = root.Windows.Count - 1; i >= 0; i--)\n                    {\n                        if (root.Windows[i].IsOnTop == this.IsOnTop)\n                        {\n                            return root.Windows[i] == this;\n                        }\n                    }\n                }\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.MapRender/WzComparerR2.MapRender.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2.MapRender</AssemblyName>\n    <RootNamespace>WzComparerR2.MapRender</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources>\n    <WcR2Plugin>true</WcR2Plugin>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    \n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n    \n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Configuration\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WzComparerR2.Common\\WzComparerR2.Common.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2.PluginBase\\WzComparerR2.PluginBase.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2.WzLib\\WzComparerR2.WzLib.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\CharaSimResource\\CharaSimResource.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <Reference Include=\"DevComponents.DotNetBar2\">\n      <HintPath>..\\References\\DevComponents.DotNetBar2.dll</HintPath>\n      <Private>false</Private>\n    </Reference>\n    <Reference Include=\"EmptyKeys.UserInterface\">\n      <HintPath>..\\References\\EmptyKeys.UserInterface.dll</HintPath>\n    </Reference>\n    <Reference Include=\"EmptyKeys.UserInterface.Core\">\n      <HintPath>..\\References\\EmptyKeys.UserInterface.Core.dll</HintPath>\n    </Reference>\n    <Reference Include=\"EmptyKeys.UserInterface.MonoGame\">\n      <HintPath>..\\References\\EmptyKeys.UserInterface.MonoGame.dll</HintPath>\n    </Reference>\n    <Reference Include=\"IMEHelper\">\n      <HintPath>..\\References\\IMEHelper.dll</HintPath>\n    </Reference>\n    <Reference Include=\"spine-monogame\">\n      <HintPath>..\\References\\spine-monogame.dll</HintPath>\n      <Private>False</Private>\n    </Reference>\n    <PackageReference Include=\"MonoGame.Framework.WindowsDX\" Version=\"$(MonogameFrameworkVersion)\" ExcludeAssets=\"runtime\" />\n    <PackageReference Include=\"ManagedBass\" Version=\"3.1.1\" ExcludeAssets=\"runtime\" />\n    <PackageReference Include=\"SharpDX\" Version=\"$(SharpDXVersion)\" ExcludeAssets=\"runtime\" />\n    <PackageReference Include=\"SharpDX.RawInput\" Version=\"$(SharpDXVersion)\" NoWarn=\"NU1701\" />\n    <PackageReference Include=\"System.Resources.Extensions\" Version=\"$(SystemResourcesExtensionsVersion)\" ExcludeAssets=\"runtime\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n  <ItemGroup>\n    <EmbeddedResource Include=\"Effects\\Resources\\Native\\*\" />\n  </ItemGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\WcR2Plugin.targets\" />\n</Project>"
  },
  {
    "path": "WzComparerR2.Network/Contracts/ByteArrayConverter.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Serialization;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    class ByteArrayConverter : JsonConverter\n    {\n        static readonly Type supportedType = typeof(byte[]);\n\n        public override bool CanConvert(Type objectType)\n        {\n            return objectType == supportedType;\n        }\n\n        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)\n        {\n            string value = reader.Value as string;\n\n            return value != null ? Convert.FromBase64String(value) : null;\n        }\n\n        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)\n        {\n            writer.WriteValue(Convert.ToBase64String((byte[])value));\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackCryptReq.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"1\")]\n    public sealed class PackCryptReq\n    {\n        public byte[] Exponent { get; set; }\n        public byte[] Modulus { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackCryptResp.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"2\")]\n    public sealed class PackCryptResp\n    {\n        public byte[] KeyEncryptedS2C { get; set; }\n        public byte[] KeyEncryptedC2S { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackCustomPackage.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"12\")]\n    public sealed class PackCustomPackage\n    {\n        public SendTarget Target { get; set; }\n        public string[] ID { get; set; }\n        public object Package { get; set; }\n    }\n\n    public enum SendTarget\n    {\n        None = 0,\n        Self = 1,\n        ExceptSelf = 2,\n        ID = 3,\n        ExceptID = 4,\n        All = 5\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackGetAllUsersReq.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"7\")]\n    public sealed class PackGetAllUsersReq\n    {\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackGetAllUsersResp.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"8\")]\n    public sealed class PackGetAllUsersResp\n    {\n        public List<UserInfo> Users { get; set; }\n    }\n\n    [JsonObject(\"8A\")]\n    public sealed class UserInfo\n    {\n        public ClientType ClientType { get; set; }\n        public string SID { get; set; }\n        public string UID { get; set; }\n        public string NickName { get; set; }\n        public DateTime LoginTimeUTC { get; set; }\n    }\n\n    public enum ClientType\n    {\n        Unknown = 0,\n        WcR2 = 1,\n        Web = 2,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackGetServerInfoReq.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"5\")]\n    public sealed class PackGetServerInfoReq\n    {\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackGetServerInfoResp.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"6\")]\n    public sealed class PackGetServerInfoResp\n    {\n        public string Version { get; set; }\n        public DateTime StartTimeUTC { get; set; }\n        public DateTime CurrentTimeUTC { get; set; }\n        public int UserCount { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackHeartBeat.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"0\")]\n    public sealed class PackHeartBeat\n    {\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackLoginReq.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"3\")]\n    public sealed class PackLoginReq\n    {\n        public string WcID { get; set; }\n        public string NickName { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackLoginResp.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"4\")]\n    public sealed class PackLoginResp\n    {\n        public string SessionID { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackOnChat.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"10\")]\n    public sealed class PackOnChat\n    {\n        public string FromID { get; set; }\n        public object Message { get; set; }\n        public ChatGroup Group { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackOnCustomPackage.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"13\")]\n    public sealed class PackOnCustomPackage\n    {\n        public string FromID { get; set; }\n        public object Package { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackOnServerMessage.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"11\")]\n    public sealed class PackOnServerMessage\n    {\n        public MessageType Type { get; set; }\n        public object Message { get; set; }\n    }\n\n    public enum MessageType\n    {\n        Normal = 0,\n        Error = 1,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackOnUserUpdate.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"14\")]\n    public sealed class PackOnUserUpdate\n    {\n        public UserUpdateReason UpdateReason { get; set; }\n        public UserInfo UserInfo { get; set; }\n    }\n\n    public enum UserUpdateReason\n    {\n        InfoChanged = 0,\n        Online = 1,\n        Offline = 2\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackSendChat.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"9\")]\n    public sealed class PackSendChat\n    {\n        public object Message { get; set; }\n        public ChatGroup Group { get; set; }\n    }\n\n    public enum ChatGroup\n    {\n        Public = 0,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/PackUserProfileUpdateReq.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing Newtonsoft.Json;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    [JsonObject(\"15\")]\n    public sealed class PackUserProfileUpdateReq\n    {\n        public string NickName { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Contracts/TypeNameBinder.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Linq;\nusing System.Reflection;\nusing Newtonsoft.Json;\nusing Newtonsoft.Json.Serialization;\n\nnamespace WzComparerR2.Network.Contracts\n{\n    class TypeNameBinder : DefaultSerializationBinder\n    {\n        readonly Dictionary<Type, string> knownTypeNames = new Dictionary<Type, string>();\n        readonly Dictionary<string, Type> knownTypes = new Dictionary<string, Type>();\n\n        /// <summary>\n        /// 对此构造器的调用不应为尾调用, 否则<see cref=\"Assembly.GetCallingAssembly\"/>会因尾调用优化出错.\n        /// </summary>\n        /// <seealso cref=\"https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getcallingassembly?redirectedfrom=MSDN&view=netcore-3.1#System_Reflection_Assembly_GetCallingAssembly\"/>\n        public TypeNameBinder()\n        {\n            var types = Assembly.GetCallingAssembly().GetTypes()\n                .Select(t => new { type = t, attr = t.GetTypeInfo().GetCustomAttribute<JsonObjectAttribute>(false) })\n                .Where(item => item.attr != null);\n\n            foreach (var item in types)\n            {\n                knownTypeNames.Add(item.type, item.attr.Id);\n                knownTypes.Add(item.attr.Id, item.type);\n            }\n        }\n\n        public override void BindToName(Type serializedType, out string assemblyName, out string typeName)\n        {\n            if (knownTypeNames.TryGetValue(serializedType, out typeName))\n            {\n                assemblyName = null;\n            }\n            else\n            {\n                base.BindToName(serializedType, out assemblyName, out typeName);\n            }\n        }\n\n        public override Type BindToType(string assemblyName, string typeName)\n        {\n            Type type;\n            if (knownTypes.TryGetValue(typeName, out type))\n            {\n                return type;\n            }\n            return base.BindToType(assemblyName, typeName);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Entry.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\nusing WzComparerR2.Config;\nusing WzComparerR2.PluginBase;\nusing WzComparerR2.Network.Contracts;\nusing System.Security.Cryptography;\nusing DevComponents.DotNetBar;\n\n\nnamespace WzComparerR2.Network\n{\n    public class Entry : PluginEntry\n    {\n        static Entry()\n        {\n            DefaultServer = Encoding.UTF8.GetString(Convert.FromBase64String(\"d2Mua2FnYW1pYS5jb20=\"));\n        }\n\n        public static readonly string DefaultServer;\n\n        public Entry(PluginContext context)\n         : base(context)\n        {\n            this.handlers = new Dictionary<Type, Action<object>>();\n            this.RegisterAllHandlers();\n        }\n\n        public WcClient Client { get; private set; }\n\n        private Dictionary<Type, Action<object>> handlers;\n        private Session session;\n        private LoggerForm.LogPrinter logger;\n\n        protected override void OnLoad()\n        {\n            WzComparerR2.Config.ConfigManager.RegisterAllSection();\n            CheckConfig();\n            var config = NetworkConfig.Default;\n\n            var form1 = new LoggerForm();\n            var dockSite = this.Context.DotNetBarManager.BottomDockSite;\n            form1.AttachDockBar(dockSite);\n            form1.OnCommand += Form1_OnCommand;\n\n            this.logger = form1.GetLogger();\n            logger.Level = config.LogLevel;\n            Log.Loggers.Add(logger);\n\n            //TODO: use config file, multi server selection.\n            this.Client = new WcClient();\n            this.Client.Host = DefaultServer;\n            this.Client.Port = 2100;\n            this.Client.AutoReconnect = true;\n            this.Client.Connected += Client_Connected;\n            this.Client.Disconnected += Client_Disconnected;\n            this.Client.OnPackReceived += Client_OnPackReceived;\n            var task = this.Client.Connect();\n        }\n\n        private void CheckConfig()\n        {\n            var config = NetworkConfig.Default;\n\n            Guid guid;\n            bool needSave = false;\n            if (!Guid.TryParse(config.WcID, out guid))\n            {\n                guid = Guid.NewGuid();\n                needSave = true;\n            }\n\n            string nickName = config.NickName;\n            if (string.IsNullOrWhiteSpace(nickName))\n            {\n                nickName = \"No Name #\" + new Random().Next(10000);\n                needSave = true;\n            }\n\n            string servers = config.Servers;\n            if (string.IsNullOrEmpty(servers))\n            {\n                servers = \":2100;:2101;:2102;:2103;:2104\";\n                needSave = true;\n            }\n\n            if (needSave)\n            {\n                ConfigManager.Reload();\n                config = NetworkConfig.Default;\n                config.WcID = guid.ToString();\n                config.NickName = nickName;\n                config.Servers = servers;\n                ConfigManager.Save();\n            }\n        }\n\n        private void Client_Connected(object sender, EventArgs e)\n        {\n            this.session = new Session();\n            //开始加密\n            this.CryptoRequest();\n        }\n\n        private void Client_Disconnected(object sender, EventArgs e)\n        {\n            this.session = null;\n        }\n\n        private void Client_OnPackReceived(object sender, PackEventArgs e)\n        {\n            var type = e.Pack.GetType();\n            Action<object> handler;\n            if (this.handlers.TryGetValue(type, out handler))\n            {\n                handler?.Invoke(e.Pack);\n            }\n        }\n\n        private void Form1_OnCommand(object sender, CommandEventArgs e)\n        {\n            if (e.Command.StartsWith(\"/\"))\n            {\n                string[] args = e.Command.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);\n                switch (args[0].ToLower())\n                {\n                    case \"/users\":\n                        var sb = new StringBuilder();\n                        lock (this.session.Users)\n                        {\n                            sb.AppendFormat(\"Online user count: {0}\", this.session.Users.Count);\n                            var time = DateTime.UtcNow;\n                            foreach (var user in this.session.Users)\n                            {\n                                var loginTime = time - this.session.LocalTimeOffset - user.LoginTimeUTC;\n                                sb.AppendLine().AppendFormat(\"  {0}, online {1} minutes.\", user.NickName, (int)loginTime.TotalMinutes);\n                            }\n                        }\n                        Log.Info(sb.ToString());\n                        break;\n\n                    case \"/name\":\n                        if (Client.IsConnected)\n                        {\n                            string newName = e.Command.Substring(5).Trim();\n                            if (!string.IsNullOrWhiteSpace(newName))\n                            {\n                                ConfigManager.Reload();\n                                NetworkConfig.Default.NickName = newName;\n                                ConfigManager.Save();\n                                var req = new PackUserProfileUpdateReq()\n                                {\n                                    NickName = newName\n                                };\n                                Client.Send(req);\n                            }\n                        }\n                        break;\n                }\n            }\n            else\n            {\n                if (Client.IsConnected)\n                {\n                    var pack = new PackSendChat()\n                    {\n                        Group = ChatGroup.Public,\n                        Message = e.Command\n                    };\n                    Client.Send(pack);\n                }\n                else\n                {\n                    Log.Warn(\"Command failed, Server not connected.\");\n                }\n            }\n        }\n\n        private void RegisterAllHandlers()\n        {\n            var methods = this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)\n                .Where(m => m.Name == \"OnPackReceived\" && m.ReturnParameter.ParameterType == typeof(void));\n            foreach (var method in methods)\n            {\n                var p = method.GetParameters();\n                if (p.Length == 1)\n                {\n                    var type = p[0].ParameterType;\n                    var funcType = typeof(Action<>).MakeGenericType(type);\n                    var handler = method.CreateDelegate(funcType, this);\n                    RegisterHandler(type, o => handler.DynamicInvoke(o));\n                }\n            }\n        }\n\n        private void RegisterHandler<T>(Action<T> handler)\n        {\n            RegisterHandler(typeof(T), obj =>\n            {\n                if (obj is T)\n                {\n                    handler((T)obj);\n                }\n            });\n        }\n\n        private void RegisterHandler(Type packType, Action<object> handler)\n        {\n            this.handlers[packType] = handler;\n        }\n\n        #region PackHandlers\n        private void CryptoRequest()\n        {\n            var rsa = new RSACryptoServiceProvider(2048);\n            this.session.RSA = rsa;\n\n            var rsaParams = rsa.ExportParameters(false);\n            var req = new PackCryptReq()\n            {\n                Exponent = rsaParams.Exponent,\n                Modulus = rsaParams.Modulus\n            };\n            this.Client.Send(req);\n        }\n\n        private void ServerInfoRequest()\n        {\n            var req = new PackGetServerInfoReq();\n            this.Client.Send(req);\n        }\n\n        private void UserListRequest()\n        {\n            var req = new PackGetAllUsersReq();\n            this.Client.Send(req);\n        }\n\n        private void LoginRequest()\n        {\n            CheckConfig();\n            var config = NetworkConfig.Default;\n\n            var req = new PackLoginReq()\n            {\n                WcID = config.WcID,\n                NickName = config.NickName\n            };\n            this.Client.Send(req);\n        }\n\n        private void OnPackReceived(PackHeartBeat pack)\n        {\n            Client.Send(pack);\n        }\n\n        private void OnPackReceived(PackCryptResp pack)\n        {\n            var rc4S2C = RC4.Create();\n            rc4S2C.Key = this.session.RSA.Decrypt(pack.KeyEncryptedS2C, false);\n            var rc4C2S = RC4.Create();\n            rc4C2S.Key = this.session.RSA.Decrypt(pack.KeyEncryptedC2S, false);\n            this.Client.BeginCrypto(rc4S2C.CreateDecryptor(), rc4C2S.CreateEncryptor());\n            this.session.RSA.Dispose();\n            this.session.RSA = null;\n\n            //获取服务器状态\n            ServerInfoRequest();\n            //开始登录协议\n            LoginRequest();\n        }\n\n        private void OnPackReceived(PackGetServerInfoResp pack)\n        {\n            this.session.LocalTimeOffset = DateTime.UtcNow - pack.CurrentTimeUTC;\n\n            Log.Info(\"Server version: {0}, Time: {1:yyyy-MM-dd HH:mm:ss}, {2:%d\\\\d\\\\ h\\\\h\\\\ m\\\\m\\\\ s\\\\s} elapsed, {3} users online.\",\n                pack.Version,\n                pack.CurrentTimeUTC.ToLocalTime(),\n                pack.CurrentTimeUTC - pack.StartTimeUTC,\n                pack.UserCount);\n        }\n\n        private void OnPackReceived(PackLoginResp pack)\n        {\n            Log.Info(\"Login Success.\");\n            this.session.SID = pack.SessionID;\n\n            //获取在线列表\n            UserListRequest();\n        }\n\n        private void OnPackReceived(PackOnChat pack)\n        {\n            //聊天到达\n            string nickName;\n            lock (this.session.Users)\n            {\n                nickName = this.session.Users.FirstOrDefault(u => u.UID == pack.FromID).NickName ?? pack.FromID;\n            }\n            Log.Write(LogLevel.None, \"[{0}] {1}\", nickName, pack.Message);\n            if (!this.Context.MainForm.ContainsFocus)\n            {\n                NativeMethods.FlashWindowEx(this.Context.MainForm);\n            }\n        }\n\n        private void OnPackReceived(PackGetAllUsersResp pack)\n        {\n            Log.Info(\"Get {0} online users.\", pack.Users.Count);\n            lock (this.session.Users)\n            {\n                this.session.Users.Clear();\n                this.session.Users.AddRange(pack.Users);\n            }\n        }\n\n        /// <summary>\n        /// 服务器公告或错误。\n        /// </summary>\n        private void OnPackReceived(PackOnServerMessage pack)\n        {\n            switch (pack.Type)\n            {\n                case MessageType.Normal:\n                    Log.Info(\"(Notice) {0}\", pack.Message);\n                    break;\n\n                case MessageType.Error:\n                    Log.Error(\"(ServerError) {0}\", pack.Message);\n                    break;\n            }\n        }\n\n        /// <summary>\n        /// 用户列表更新。\n        /// </summary>\n        /// <param name=\"pack\"></param>\n        private void OnPackReceived(PackOnUserUpdate pack)\n        {\n            lock (this.session.Users)\n            {\n                var idx = this.session.Users.FindIndex(u => u.UID == pack.UserInfo.UID && u.SID == pack.UserInfo.SID);\n\n                switch (pack.UpdateReason)\n                {\n                    case UserUpdateReason.Online:\n                        this.session.Users.Add(pack.UserInfo);\n                        Log.Info(\"[{0}] is online.\", pack.UserInfo.NickName);\n                        break;\n\n                    case UserUpdateReason.Offline:\n                        if (idx > -1)\n                        {\n                            var oldUser = this.session.Users[idx];\n                            this.session.Users.RemoveAt(idx);\n                            Log.Info(\"[{0}] is offline.\", oldUser.NickName);\n                        }\n                        break;\n\n                    case UserUpdateReason.InfoChanged:\n                        if (idx > -1)\n                        {\n                            var oldUser = this.session.Users[idx];\n                            this.session.Users[idx] = pack.UserInfo;\n                            Log.Info(\"[{0}] changed name to [{1}].\", oldUser.NickName, pack.UserInfo.NickName);\n                        }\n                        else\n                        {\n                            this.session.Users.Add(pack.UserInfo);\n                            Log.Info(\"[{0}] is online.\", pack.UserInfo.NickName);\n                        }\n                       \n                        break;\n                }\n            }\n            \n        }\n        #endregion\n\n        class Session\n        {\n            public RSACryptoServiceProvider RSA;\n            public string SID;\n            public TimeSpan LocalTimeOffset;\n            public List<UserInfo> Users = new List<UserInfo>();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Log.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.Network\n{\n    public class Log\n    {\n        public static IList<ILogger> Loggers { get; private set; } = new List<ILogger>();\n\n        public static void Debug(string format, params object[] args)\n        {\n            Log.Write(LogLevel.Debug, format, args);\n        }\n\n        public static void Info(string format, params object[] args)\n        {\n            Log.Write(LogLevel.Info, format, args);\n        }\n\n        public static void Warn(string format, params object[] args)\n        {\n            Log.Write(LogLevel.Warn, format, args);\n        }\n\n        public static void Error(string format, params object[] args)\n        {\n            Log.Write(LogLevel.Error, format, args);\n        }\n\n        public static void Write(LogLevel logLevel, string format, params object[] args)\n        {\n            foreach(var logger in Loggers)\n            {\n                try\n                {\n                    lock (logger)\n                    {\n                        logger.Write(logLevel, format, args);\n                    }\n                }\n                catch\n                {\n                }\n            }\n        }\n    }\n\n    public interface ILogger\n    {\n        void Write(LogLevel logLevel, string format, params object[] args);\n    }\n\n    public enum LogLevel\n    {\n        All = 0,\n        Debug,\n        Info,\n        Warn,\n        Error,\n        None,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/LoggerForm.Designer.cs",
    "content": "﻿namespace WzComparerR2.Network\n{\n    partial class LoggerForm\n    {\n        /// <summary>\n        /// Required designer variable.\n        /// </summary>\n        private System.ComponentModel.IContainer components = null;\n\n        /// <summary>\n        /// Clean up any resources being used.\n        /// </summary>\n        /// <param name=\"disposing\">true if managed resources should be disposed; otherwise, false.</param>\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing && (components != null))\n            {\n                components.Dispose();\n            }\n            base.Dispose(disposing);\n        }\n\n        #region Windows Form Designer generated code\n\n        /// <summary>\n        /// Required method for Designer support - do not modify\n        /// the contents of this method with the code editor.\n        /// </summary>\n        private void InitializeComponent()\n        {\n            this.bar1 = new DevComponents.DotNetBar.Bar();\n            this.panelDockContainer1 = new DevComponents.DotNetBar.PanelDockContainer();\n            this.richTextBoxEx1 = new DevComponents.DotNetBar.Controls.RichTextBoxEx();\n            this.panelEx1 = new DevComponents.DotNetBar.PanelEx();\n            this.labelX1 = new DevComponents.DotNetBar.LabelX();\n            this.textBoxX1 = new DevComponents.DotNetBar.Controls.TextBoxX();\n            this.dockContainerItem1 = new DevComponents.DotNetBar.DockContainerItem();\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).BeginInit();\n            this.bar1.SuspendLayout();\n            this.panelDockContainer1.SuspendLayout();\n            this.panelEx1.SuspendLayout();\n            this.SuspendLayout();\n            // \n            // bar1\n            // \n            this.bar1.AccessibleDescription = \"DotNetBar Bar (bar1)\";\n            this.bar1.AccessibleName = \"DotNetBar Bar\";\n            this.bar1.AccessibleRole = System.Windows.Forms.AccessibleRole.Grouping;\n            this.bar1.AntiAlias = true;\n            this.bar1.BarType = DevComponents.DotNetBar.eBarType.DockWindow;\n            this.bar1.CanDockLeft = false;\n            this.bar1.CanDockRight = false;\n            this.bar1.CanDockTab = false;\n            this.bar1.CanDockTop = false;\n            this.bar1.Controls.Add(this.panelDockContainer1);\n            this.bar1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.bar1.Font = new System.Drawing.Font(\"宋体\", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));\n            this.bar1.IsMaximized = false;\n            this.bar1.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {\n            this.dockContainerItem1});\n            this.bar1.LayoutType = DevComponents.DotNetBar.eLayoutType.DockContainer;\n            this.bar1.Location = new System.Drawing.Point(0, 0);\n            this.bar1.Name = \"bar1\";\n            this.bar1.Size = new System.Drawing.Size(284, 261);\n            this.bar1.Stretch = true;\n            this.bar1.Style = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.bar1.TabIndex = 0;\n            this.bar1.TabStop = false;\n            // \n            // panelDockContainer1\n            // \n            this.panelDockContainer1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelDockContainer1.Controls.Add(this.richTextBoxEx1);\n            this.panelDockContainer1.Controls.Add(this.panelEx1);\n            this.panelDockContainer1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelDockContainer1.Location = new System.Drawing.Point(3, 3);\n            this.panelDockContainer1.Name = \"panelDockContainer1\";\n            this.panelDockContainer1.Size = new System.Drawing.Size(278, 255);\n            this.panelDockContainer1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelDockContainer1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarBackground;\n            this.panelDockContainer1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.BarDockedBorder;\n            this.panelDockContainer1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.ItemText;\n            this.panelDockContainer1.Style.GradientAngle = 90;\n            this.panelDockContainer1.TabIndex = 1;\n            // \n            // richTextBoxEx1\n            // \n            // \n            // \n            // \n            this.richTextBoxEx1.BackgroundStyle.Class = \"RichTextBoxBorder\";\n            this.richTextBoxEx1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.richTextBoxEx1.Dock = System.Windows.Forms.DockStyle.Fill;\n            this.richTextBoxEx1.Location = new System.Drawing.Point(0, 0);\n            this.richTextBoxEx1.Name = \"richTextBoxEx1\";\n            this.richTextBoxEx1.Rtf = \"{\\\\rtf1\\\\ansi\\\\ansicpg936\\\\deff0\\\\deflang1033\\\\deflangfe2052{\\\\fonttbl{\\\\f0\\\\fnil\\\\fcharset\" +\n    \"134 \\\\\\'cb\\\\\\'ce\\\\\\'cc\\\\\\'e5;}}\\r\\n\\\\viewkind4\\\\uc1\\\\pard\\\\lang2052\\\\f0\\\\fs18\\\\par\\r\\n}\\r\\n\";\n            this.richTextBoxEx1.Size = new System.Drawing.Size(278, 230);\n            this.richTextBoxEx1.TabIndex = 10;\n            // \n            // panelEx1\n            // \n            this.panelEx1.CanvasColor = System.Drawing.SystemColors.Control;\n            this.panelEx1.ColorSchemeStyle = DevComponents.DotNetBar.eDotNetBarStyle.StyleManagerControlled;\n            this.panelEx1.Controls.Add(this.labelX1);\n            this.panelEx1.Controls.Add(this.textBoxX1);\n            this.panelEx1.DisabledBackColor = System.Drawing.Color.Empty;\n            this.panelEx1.Dock = System.Windows.Forms.DockStyle.Bottom;\n            this.panelEx1.Location = new System.Drawing.Point(0, 230);\n            this.panelEx1.Name = \"panelEx1\";\n            this.panelEx1.Size = new System.Drawing.Size(278, 25);\n            this.panelEx1.Style.Alignment = System.Drawing.StringAlignment.Center;\n            this.panelEx1.Style.BackColor1.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground;\n            this.panelEx1.Style.BackColor2.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBackground2;\n            this.panelEx1.Style.Border = DevComponents.DotNetBar.eBorderType.SingleLine;\n            this.panelEx1.Style.BorderColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelBorder;\n            this.panelEx1.Style.ForeColor.ColorSchemePart = DevComponents.DotNetBar.eColorSchemePart.PanelText;\n            this.panelEx1.Style.GradientAngle = 90;\n            this.panelEx1.TabIndex = 6;\n            // \n            // labelX1\n            // \n            this.labelX1.AutoSize = true;\n            // \n            // \n            // \n            this.labelX1.BackgroundStyle.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.labelX1.Location = new System.Drawing.Point(7, 5);\n            this.labelX1.Name = \"labelX1\";\n            this.labelX1.Size = new System.Drawing.Size(25, 16);\n            this.labelX1.TabIndex = 2;\n            this.labelX1.Text = \"Cmd\";\n            // \n            // textBoxX1\n            // \n            this.textBoxX1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) \n            | System.Windows.Forms.AnchorStyles.Right)));\n            // \n            // \n            // \n            this.textBoxX1.Border.Class = \"TextBoxBorder\";\n            this.textBoxX1.Border.CornerType = DevComponents.DotNetBar.eCornerType.Square;\n            this.textBoxX1.Location = new System.Drawing.Point(38, 2);\n            this.textBoxX1.Name = \"textBoxX1\";\n            this.textBoxX1.PreventEnterBeep = true;\n            this.textBoxX1.Size = new System.Drawing.Size(237, 21);\n            this.textBoxX1.TabIndex = 1;\n            this.textBoxX1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBoxX1_KeyDown);\n            // \n            // dockContainerItem1\n            // \n            this.dockContainerItem1.Control = this.panelDockContainer1;\n            this.dockContainerItem1.Name = \"dockContainerItem1\";\n            this.dockContainerItem1.Text = \"NetworkLogger\";\n            // \n            // LoggerForm\n            // \n            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);\n            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;\n            this.ClientSize = new System.Drawing.Size(284, 261);\n            this.Controls.Add(this.bar1);\n            this.Name = \"LoggerForm\";\n            this.Text = \"LoggerForm\";\n            ((System.ComponentModel.ISupportInitialize)(this.bar1)).EndInit();\n            this.bar1.ResumeLayout(false);\n            this.panelDockContainer1.ResumeLayout(false);\n            this.panelEx1.ResumeLayout(false);\n            this.panelEx1.PerformLayout();\n            this.ResumeLayout(false);\n\n        }\n\n        #endregion\n        private DevComponents.DotNetBar.Bar bar1;\n        private DevComponents.DotNetBar.PanelDockContainer panelDockContainer1;\n        private DevComponents.DotNetBar.DockContainerItem dockContainerItem1;\n        private DevComponents.DotNetBar.Controls.RichTextBoxEx richTextBoxEx1;\n        private DevComponents.DotNetBar.PanelEx panelEx1;\n        private DevComponents.DotNetBar.LabelX labelX1;\n        private DevComponents.DotNetBar.Controls.TextBoxX textBoxX1;\n    }\n}"
  },
  {
    "path": "WzComparerR2.Network/LoggerForm.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Drawing;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing DevComponents.DotNetBar;\nusing DevComponents.DotNetBar.Controls;\n\nnamespace WzComparerR2.Network\n{\n    public partial class LoggerForm : Form\n    {\n        public LoggerForm()\n        {\n            InitializeComponent();\n        }\n\n        public event EventHandler<CommandEventArgs> OnCommand;\n\n        public void AttachDockBar(DockSite dockSite)\n        {\n            this.bar1.Controls.Remove(this.panelDockContainer1);\n            this.bar1.Items.Remove(this.dockContainerItem1);\n\n            var bar = dockSite.Controls[0] as Bar;\n            bar.Controls.Add(this.panelDockContainer1);\n            bar.Items.Add(this.dockContainerItem1);\n        }\n\n        internal LogPrinter GetLogger()\n        {\n            return new LogPrinter(this.richTextBoxEx1);\n        }\n\n        public class LogPrinter : ILogger\n        {\n            public LogPrinter(RichTextBoxEx textbox)\n            {\n                this.textbox = textbox;\n            }\n\n            public LogLevel Level { get; set; }\n            RichTextBoxEx textbox;\n\n            void ILogger.Write(LogLevel logLevel, string format, params object[] args)\n            {\n                if (logLevel >= this.Level)\n                {\n                    var color = GetLogColor(logLevel);\n\n                    if (logLevel < LogLevel.None)\n                    {\n                        this.AppendText($\"[{logLevel}]\", color);\n                    }\n                   \n                    this.AppendText($\"[{DateTime.Now:HH:mm:ss}]\", Color.Blue);\n                    if (args == null || args.Length <= 0)\n                    {\n                        this.AppendText(format, color);\n                    }\n                    else\n                    {\n                        this.AppendText(string.Format(format, args), color);\n                    }\n                    this.textbox.AppendText(Environment.NewLine);\n                    this.textbox.ScrollToCaret();\n                }\n            }\n\n            private void AppendText(string text, Color color)\n            {\n                this.textbox.SelectionStart = this.textbox.TextLength;\n                this.textbox.SelectionLength = 0;\n\n                this.textbox.SelectionColor = color;\n                this.textbox.AppendText(text);\n                this.textbox.SelectionColor = this.textbox.ForeColor;\n            }\n\n            private Color GetLogColor(LogLevel level)\n            {\n                switch (level)\n                {\n                    case LogLevel.Debug: return Color.Gray;\n                    case LogLevel.Info: return Color.Black;\n                    case LogLevel.Warn: return Color.Orange;\n                    case LogLevel.Error: return Color.Red;\n                    default: return Color.DarkBlue;\n                }\n            }\n        }\n\n        private void textBoxX1_KeyDown(object sender, KeyEventArgs e)\n        {\n            if (e.KeyCode == Keys.Enter)\n            {\n                string txt = textBoxX1.Text;\n                if (!string.IsNullOrWhiteSpace(txt))\n                {\n                    var ev = new CommandEventArgs(txt);\n                    this.OnCommand?.Invoke(this, ev);\n                }\n                textBoxX1.Clear();\n            }\n        }\n    }\n\n    public sealed class CommandEventArgs : EventArgs\n    {\n        public CommandEventArgs(string command)\n        {\n            this.Command = command;\n        }\n\n        public string Command { get; private set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/LoggerForm.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:import namespace=\"http://www.w3.org/XML/1998/namespace\" />\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" use=\"required\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n              <xsd:attribute ref=\"xml:space\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2.Network/NativeMethods.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Runtime.InteropServices;\nusing System.Windows.Forms;\n\nnamespace WzComparerR2.Network\n{\n    static class NativeMethods\n    {\n        public static bool FlashWindowEx(Form form)\n        {\n            FLASHWINFO fInfo = new FLASHWINFO();\n\n            fInfo.cbSize = Convert.ToUInt32(Marshal.SizeOf(fInfo));\n            fInfo.hwnd = form.Handle;\n            fInfo.dwFlags = FlashType.FLASHW_TIMERNOFG | FlashType.FLASHW_ALL;\n            fInfo.uCount = 3;\n            fInfo.dwTimeout = 0;\n\n            return FlashWindowEx(ref fInfo);\n        }\n\n        [DllImport(\"user32.dll\")]\n        [return: MarshalAs(UnmanagedType.Bool)]\n        public static extern bool FlashWindowEx(ref FLASHWINFO pwfi);\n\n        [StructLayout(LayoutKind.Sequential)]\n        public struct FLASHWINFO\n        {\n            public UInt32 cbSize;\n            public IntPtr hwnd;\n            public FlashType dwFlags;\n            public UInt32 uCount;\n            public UInt32 dwTimeout;\n        }\n\n        public enum FlashType : uint\n        {\n            /// <summary>\n                /// Stop flashing. The system restores the window to its original state. \n                /// </summary>    \n            FLASHW_STOP = 0,\n\n            /// <summary>\n                /// Flash the window caption \n                /// </summary>\n            FLASHW_CAPTION = 1,\n\n            /// <summary>\n                /// Flash the taskbar button. \n                /// </summary>\n            FLASHW_TRAY = 2,\n\n            /// <summary>\n                /// Flash both the window caption and taskbar button.\n                /// This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. \n                /// </summary>\n            FLASHW_ALL = 3,\n\n            FLASHW_PARAM1 = 4,\n            FLASHW_PARAM2 = 12,\n            FLASHW_TIMER = FLASHW_TRAY | FLASHW_PARAM1,\n            FLASHW_TIMERNOFG = FLASHW_TRAY | FLASHW_PARAM2\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/NetworkConfig.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Configuration;\nusing WzComparerR2.Config;\n\nnamespace WzComparerR2.Network\n{\n    [SectionName(\"WcR2.Network\")]\n    public sealed class NetworkConfig : ConfigSectionBase<NetworkConfig>\n    {\n        public NetworkConfig()\n        {\n            this.LogLevel = WzComparerR2.Network.LogLevel.Info;\n        }\n\n        [ConfigurationProperty(\"nickName\")]\n        public ConfigItem<string> NickName\n        {\n            get { return (ConfigItem<string>)this[\"nickName\"]; }\n            set { this[\"nickName\"] = value; }\n        }\n\n        [ConfigurationProperty(\"wcID\")]\n        public ConfigItem<string> WcID\n        {\n            get { return (ConfigItem<string>)this[\"wcID\"]; }\n            set { this[\"wcID\"] = value; }\n        }\n\n        [ConfigurationProperty(\"servers\")]\n        public ConfigItem<string> Servers\n        {\n            get { return (ConfigItem<string>)this[\"servers\"]; }\n            set { this[\"servers\"] = value; }\n        }\n\n        [ConfigurationProperty(\"logLevel\")]\n        public ConfigItem<LogLevel> LogLevel\n        {\n            get { return (ConfigItem<LogLevel>)this[\"logLevel\"]; }\n            set { this[\"logLevel\"] = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的一般信息由以下\n// 控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2.Network\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"WzComparerR2.Network\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2017-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 会使此程序集中的类型\n//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型\n//请将此类型的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"ebda4ae9-4bce-4824-84b5-b15660437141\")]\n\n// 程序集的版本信息由下列四个值组成: \n//\n//      主版本\n//      次版本\n//      生成号\n//      修订号\n//\n// 可以指定所有值，也可以使用以下所示的 \"*\" 预置版本号和修订号\n//通过使用 \"*\"，如下所示:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2.Network/RC4CryptoServiceProvider.cs",
    "content": "﻿using System;\nusing System.Security.Cryptography;\n\n\nnamespace WzComparerR2.Network\n{\n    public abstract class RC4 : SymmetricAlgorithm\n    {\n        protected RC4()\n        {\n        }\n\n        public static new RC4 Create()\n        {\n            return new RC4CryptoServiceProvider();\n        }\n    }\n\n    public sealed class RC4CryptoServiceProvider : RC4\n    {\n        public RC4CryptoServiceProvider()\n        {\n            this.LegalKeySizesValue = new[] { new KeySizes(1, 256, 1) };\n            this.KeySize = DefaultKeyLength;\n        }\n\n        public const Int32 DefaultKeyLength = 8;\n\n        public override ICryptoTransform CreateEncryptor(Byte[] rgbKey, Byte[] rgbIV)\n        {\n            this.Key = rgbKey;\n            return new RC4CryptoTransform(this.Key);\n        }\n\n        public override ICryptoTransform CreateDecryptor(Byte[] rgbKey, Byte[] rgbIV)\n        {\n            return CreateEncryptor(rgbKey, rgbIV);\n        }\n\n        public override ICryptoTransform CreateEncryptor()\n        {\n            return new RC4CryptoTransform(this.Key);\n        }\n\n        public override ICryptoTransform CreateDecryptor()\n        {\n            return CreateEncryptor();\n        }\n\n        public override void GenerateKey()\n        {\n            var rnd = RandomNumberGenerator.Create();\n            this.KeyValue = new Byte[this.KeySize];\n            rnd.GetBytes(this.KeyValue);\n        }\n\n        public override void GenerateIV()\n        {\n            throw new CryptographicException(\"RC4 cipher do not support IV generation\");\n        }\n    }\n\n    public sealed class RC4CryptoTransform : ICryptoTransform\n    {\n        private Byte[] _rgbKey;\n\n        // Bits, encrypted for one iteration\n        public const Int32 BlockSizeInBits = 8;\n\n        public const Int32 BlockSizeInBytes = BlockSizeInBits / 8;\n\n        public const Int32 SBlockSize = 256;\n\n        private Byte[] _sBlock;\n\n        private Int32 _rndI = 0;\n        private Int32 _rndJ = 0;\n\n        public RC4CryptoTransform(byte[] rgbKey)\n        {\n            _rgbKey = rgbKey;\n\n            _sBlock = new Byte[SBlockSize];\n\n            Initialize();\n        }\n\n        private void Initialize()\n        {\n            var blockSize = SBlockSize;\n\n            int keyLength = _rgbKey.Length;\n\n            for (int i = 0; i < blockSize; i++)\n            {\n                _sBlock[i] = (byte)i;\n            }\n\n            int j = 0;\n\n            for (int i = 0; i < blockSize; i++)\n            {\n                j = (j + _sBlock[i] + _rgbKey[i % keyLength]) % blockSize;\n\n                _sBlock.Swap(i, j);\n            }\n        }\n\n        private Byte GetNextPseudoRandomItem()\n        {\n            var blockSize = SBlockSize;\n\n            _rndI = (_rndI + 1) % blockSize;\n            _rndJ = (_rndJ + _sBlock[_rndI]) % blockSize;\n\n            _sBlock.Swap(_rndI, _rndJ);\n\n            return _sBlock[(_sBlock[_rndI] + _sBlock[_rndJ]) % blockSize];\n        }\n\n        public void Dispose()\n        {\n            Array.Clear(_rgbKey, 0, _rgbKey.Length);\n            Array.Clear(_sBlock, 0, _sBlock.Length);\n\n            _rgbKey = null;\n            _sBlock = null;\n        }\n\n        public Int32 TransformBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[] outputBuffer, Int32 outputOffset)\n        {\n            for (long i = 0; i < inputCount; i++)\n            {\n                outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ GetNextPseudoRandomItem());\n            }\n\n            return inputCount;\n        }\n\n        public Byte[] TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)\n        {\n            var encryptedData = new Byte[inputCount];\n\n            TransformBlock(inputBuffer, inputOffset, inputCount, encryptedData, 0);\n\n            return encryptedData;\n        }\n\n\n        public Int32 InputBlockSize { get { return BlockSizeInBytes; } }\n\n        public Int32 OutputBlockSize { get { return BlockSizeInBytes; } }\n\n        public Boolean CanTransformMultipleBlocks { get { return false; } }\n\n        public Boolean CanReuseTransform { get { return false; } }\n    }\n\n    internal static class ByteArrayExtensions\n    {\n        public static void Swap(this Byte[] array, int index1, int index2)\n        {\n            Byte temp = array[index1];\n            array[index1] = array[index2];\n            array[index2] = temp;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/RingBufferStream.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.IO;\n\nnamespace WzComparerR2.Network\n{\n    sealed class RingBufferStream : Stream\n    {\n        public RingBufferStream()\n        {\n            this.buffer = new LinkedList<byte[]>();\n        }\n\n        private bool isDisposed;\n        private long startIndex;\n        private long endIndex;\n        private long readIndex;\n\n        private readonly LinkedList<byte[]> buffer;\n        private const int BlockSize = 4096;\n\n        public override bool CanRead\n        {\n            get { return !this.isDisposed; }\n        }\n\n        public override bool CanSeek\n        {\n            get { return !this.isDisposed; }\n        }\n\n        public override bool CanWrite\n        {\n            get { return !this.isDisposed; }\n        }\n\n        public override long Length\n        {\n            get { return endIndex - startIndex; }\n        }\n\n        public override long Position\n        {\n            get { return readIndex - startIndex; }\n            set { this.Seek(value, SeekOrigin.Begin); }\n        }\n\n        public override void Flush()\n        {\n\n        }\n\n        public override int Read(byte[] buffer, int offset, int count)\n        {\n            if (!this.CanRead)\n            {\n                throw new ObjectDisposedException(nameof(RingBufferStream));\n            }\n\n            var readIndex = this.readIndex;\n            var availData = this.endIndex - this.readIndex;\n            if (availData < 0)\n            {\n                return 0;\n            }\n\n            count = (int)Math.Min(availData, count);\n            int total = 0;\n            LinkedListNode<byte[]> node;\n            int bufferOff;\n            GetBlockNode(readIndex, false, out node, out bufferOff);\n            while (total < count && node != null)\n            {\n                var bufferBlock = node.Value;\n                int copyLength = Math.Min(count - total, bufferBlock.Length - bufferOff);\n                Buffer.BlockCopy(bufferBlock, bufferOff, buffer, offset + total, copyLength);\n                total += copyLength;\n                bufferOff += copyLength;\n                if (bufferOff >= bufferBlock.Length)\n                {\n                    node = node.Next;\n                    bufferOff = 0;\n                }\n            }\n\n            this.readIndex = readIndex + total;\n            return total;\n        }\n\n        public override long Seek(long offset, SeekOrigin origin)\n        {\n            if (!this.CanSeek)\n            {\n                throw new ObjectDisposedException(nameof(RingBufferStream));\n            }\n\n            long pos = 0;\n            switch (origin)\n            {\n                case SeekOrigin.Begin: pos = offset; break;\n                case SeekOrigin.Current: pos = this.Position + offset; break;\n                case SeekOrigin.End: pos = this.Length + offset; break;\n            }\n\n            if (pos < 0)\n            {\n                throw new ArgumentException(\"pos can't less than zero.\");\n            }\n\n            this.readIndex = this.startIndex + pos;\n            return this.Position;\n        }\n\n        public override void SetLength(long value)\n        {\n            if (!this.CanWrite)\n            {\n                throw new ObjectDisposedException(nameof(RingBufferStream));\n            }\n\n            throw new NotImplementedException();\n        }\n\n        public override void Write(byte[] buffer, int offset, int count)\n        {\n            if (!this.CanWrite)\n            {\n                throw new ObjectDisposedException(nameof(RingBufferStream));\n            }\n\n            throw new NotImplementedException();\n        }\n\n        public void Append(byte[] buffer, int offset, int count)\n        {\n            LinkedListNode<byte[]> node;\n            int bufferOff;\n            GetBlockNode(this.endIndex, true, out node, out bufferOff);\n            int total = 0;\n            while (total < count)\n            {\n                if (node == null)\n                {\n                    node = this.buffer.AddLast(new byte[BlockSize]);\n                    bufferOff = 0;\n                }\n                var bufferBlock = node.Value;\n                int copyLength = Math.Min(count - total, bufferBlock.Length - bufferOff);\n                Buffer.BlockCopy(buffer, offset + total, bufferBlock, bufferOff, copyLength);\n                total += copyLength;\n                bufferOff += copyLength;\n                if (bufferOff >= bufferBlock.Length)\n                {\n                    node = node.Next;\n                    bufferOff = 0;\n                }\n            }\n            this.endIndex += total;\n        }\n\n        public void ClearPrevious()\n        {\n            if (!this.CanWrite)\n            {\n                throw new ObjectDisposedException(nameof(RingBufferStream));\n            }\n\n            var readIndex = this.readIndex;\n            LinkedListNode<byte[]> node, prev;\n            int bufferOff;\n            GetBlockNode(readIndex, false, out node, out bufferOff);\n            if (node != null)\n            {\n                while ((prev = node.Previous) != null)\n                {\n                    this.buffer.Remove(prev);\n                    this.startIndex -= prev.Value.Length;\n                    this.endIndex -= prev.Value.Length;\n                    this.readIndex -= prev.Value.Length;\n                    this.buffer.AddLast(prev);\n                }\n\n                this.startIndex = this.readIndex;\n            }\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            this.isDisposed = true;\n        }\n\n        private void GetBlockNode(long pos, bool autoExpand, out LinkedListNode<byte[]> node, out int offset)\n        {\n            int index = (int)(pos / BlockSize);\n            offset = (int)(pos % BlockSize);\n            node = this.buffer.First;\n            for (int i = 0; i < index && node != null; i++)\n            {\n                node = node.Next;\n                if (autoExpand && node == null)\n                {\n                    node = buffer.AddLast(new byte[BlockSize]);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/WcClient.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.Concurrent;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Net;\nusing System.Net.Sockets;\nusing System.Security.Cryptography;\nusing System.IO;\nusing Newtonsoft.Json;\nusing WzComparerR2.Network.Contracts;\n\nnamespace WzComparerR2.Network\n{\n    public class WcClient\n    {\n        static WcClient()\n        {\n            DefaultSerializerSetting = new JsonSerializerSettings()\n            {\n                TypeNameHandling = TypeNameHandling.Objects,\n                SerializationBinder = new TypeNameBinder(),\n                Converters = new[] { new ByteArrayConverter() },\n            };\n        }\n\n        public WcClient()\n        {\n\n        }\n\n        public event EventHandler Connected;\n        public event EventHandler<ErrorEventArgs> ConnectFailed;\n        public event EventHandler Disconnected;\n        public event EventHandler<PackEventArgs> OnPackReceived;\n\n        public static readonly JsonSerializerSettings DefaultSerializerSetting;\n\n        public bool IsConnected => this.client?.Connected ?? false;\n        public string Host { get; set; }\n        public int Port { get; set; }\n        public bool AutoReconnect { get; set; }\n\n        private TcpClient client;\n        private Task connectTask;\n        private Task disconnectTask;\n        private Task readTask;\n        private Task writeTask;\n        private BlockingCollection<object> writeQueue;\n        private ICryptoTransform writeCrypto;\n        private ICryptoTransform readCrypto;\n\n        public async Task Connect()\n        {\n            this.connectTask = BeginConnect();\n            await this.connectTask;\n        }\n\n        private void Reconnect(int delay = 0)\n        {\n            var reConnTask = Task.Run(async () =>\n            {\n                await Task.Delay(delay);\n                Log.Debug(\"Begin reconnect.\");\n                await Connect();\n            });\n        }\n\n        public void BeginCrypto(ICryptoTransform readCrypto, ICryptoTransform writeCrypto)\n        {\n            this.readCrypto = readCrypto;\n            this.writeCrypto = writeCrypto;\n        }\n\n        public void Send(object pack)\n        {\n            this.writeQueue.Add(pack);\n        }\n\n        private async Task BeginConnect()\n        {\n            this.client = new TcpClient()\n            {\n                ReceiveTimeout = 60000,\n                SendTimeout = 10000,\n            };\n            \n            Log.Debug(\"Begin connect.\");\n            while (true)\n            {\n                try\n                {\n                    await this.client.ConnectAsync(this.Host, this.Port);\n                    Log.Info(\"Connect success.\");\n                    break;\n                }\n                catch (Exception ex)\n                {\n                    Log.Error(\"Connect failed: {0}\", ex.Message);\n                    var e = new ErrorEventArgs(ex);\n                    this.ConnectFailed?.Invoke(this, e);\n                    if (AutoReconnect)\n                    {\n                        await Task.Delay(5000);\n                        continue;\n                    }\n                }\n            }\n\n            this.writeQueue = new BlockingCollection<object>(16);\n            this.readCrypto = null;\n            this.writeCrypto = null;\n            var ns = this.client.GetStream();\n            this.readTask = BeginRead(ns);\n            this.writeTask = BeginWrite(ns);\n            this.disconnectTask = WaitForDisconnect();\n\n            this.Connected?.Invoke(this, EventArgs.Empty);\n        }\n\n        private async Task BeginRead(Stream ns)\n        {\n            Log.Debug(\"Begin read loop.\");\n            var readBuffer = new RingBufferStream();\n            ICryptoTransform transform = null;\n\n            var br = new BinaryReader(readBuffer);\n            var buffer = new byte[4096];\n            int packLen = -1;\n            try\n            {\n                while (true)\n                {\n                    int count = await ns.ReadAsync(buffer, 0, buffer.Length);\n                    Log.Debug(\"Read {0} bytes.\", count);\n                    if (count <= 0)\n                        break;\n                    readBuffer.Append(buffer, 0, count);\n                    \n                    while (true)\n                    {\n                        //切换加密\n                        if (transform != this.readCrypto)\n                        {\n                            transform = this.readCrypto;\n                            if (transform == null)\n                            {\n                                br = new BinaryReader(readBuffer);\n                            }\n                            else\n                            {\n                                var cs = new CryptoStream(readBuffer, transform, CryptoStreamMode.Read);\n                                br = new BinaryReader(cs);\n                            }\n                        }\n                        if (packLen < 0)\n                        {\n                            if (readBuffer.Length >= 2)\n                            {\n                                packLen = br.ReadUInt16();\n                                readBuffer.ClearPrevious();\n                            }\n                        }\n                        if (packLen == 0)\n                        {\n                            continue;\n                        }\n                        else if (packLen > 0)\n                        {\n                            if (readBuffer.Length >= packLen)\n                            {\n                                var pack = DecodePack(br.ReadBytes(packLen));\n                                Log.Debug(\"Read pack: {0}.\", pack);\n\n                                if (pack != null)\n                                {\n                                    var e = new PackEventArgs(pack);\n                                    this.OnPackReceived?.Invoke(this, e);\n                                }\n                                readBuffer.ClearPrevious();\n                                packLen = -1;\n                            }\n                            else\n                            {\n                                break;\n                            }\n                        }\n                        else\n                        {\n                            break;\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                Log.Error(\"Read Error: {0}\", ex.Message);\n            }\n            finally\n            {\n                Log.Debug(\"End read.\");\n                try\n                {\n                    this.writeQueue.CompleteAdding();\n                }\n                catch\n                {\n                }\n                ns.Close();\n            }\n        }\n\n        private async Task BeginWrite(Stream ns)\n        {\n            await Task.Run(async () =>\n            {\n                Log.Debug(\"Begin write loop.\");\n                try\n                {\n                    object pack = null;\n                    while (this.writeQueue.TryTake(out pack, Int32.MaxValue))\n                    {\n                        Log.Debug(\"Write Pack: {0}.\", pack);\n                        var packData = EncodePack(pack);\n                        if (this.writeCrypto != null)\n                        {\n                            this.writeCrypto.TransformBlock(packData, 0, packData.Length, packData, 0);\n                        }\n                        await ns.WriteAsync(packData, 0, packData.Length);\n                    }\n                }\n                catch (Exception ex)\n                {\n                    Log.Error(\"Write Error: {0}\", ex.Message);\n                }\n                finally\n                {\n                    this.writeQueue.Dispose();\n                    ns.Close();\n                }\n                Log.Debug(\"End write.\");\n            });\n        }\n\n        private async Task WaitForDisconnect()\n        {\n            await Task.WhenAll(this.readTask, this.writeTask);\n            Log.Info(\"Disconnect.\");\n            this.Disconnected?.Invoke(this, EventArgs.Empty);\n            if (AutoReconnect)\n            {\n                Reconnect();\n            }\n        }\n\n        private object DecodePack(byte[] packData)\n        {\n            var json = Encoding.UTF8.GetString(packData);\n            object pack = null;\n            try\n            {\n                pack = JsonConvert.DeserializeObject(json, DefaultSerializerSetting);\n            }\n            catch\n            {\n                try\n                {\n                    pack = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(json);\n                }\n                catch(Exception ex)\n                {\n                    Log.Warn(\"Decode pack failed: {0}\", ex.Message);\n                }\n            }\n            return pack;\n        }\n\n        private byte[] EncodePack(object pack)\n        {\n            var json = JsonConvert.SerializeObject(pack, DefaultSerializerSetting);\n            var enc = Encoding.UTF8;\n            var packLen = enc.GetByteCount(json);\n            if (packLen > UInt16.MaxValue)\n            {\n                throw new Exception(\"pack too large.\");\n            }\n            var buffer = new byte[packLen + 2];\n            buffer[0] = (byte)(packLen);\n            buffer[1] = (byte)(packLen >> 8);\n            enc.GetBytes(json, 0, json.Length, buffer, 2);\n            return buffer;\n        }\n    }\n\n    public sealed class PackEventArgs : EventArgs\n    {\n        public PackEventArgs(object pack)\n        {\n            this.Pack = pack;\n        }\n\n        public object Pack { get; private set; }\n    }\n\n    public sealed class ErrorEventArgs : EventArgs\n    {\n        public ErrorEventArgs(Exception exception)\n        {\n            this.Exception = exception;\n        }\n\n        public Exception Exception { get; private set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Network/WzComparerR2.Network.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2.Network</AssemblyName>\n    <RootNamespace>WzComparerR2.Network</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <WcR2Plugin>true</WcR2Plugin>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    \n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n    \n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Windows.Forms\" />\n    <Reference Include=\"System.Configuration\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WzComparerR2.Common\\WzComparerR2.Common.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <ProjectReference Include=\"..\\WzComparerR2.PluginBase\\WzComparerR2.PluginBase.csproj\">\n      <Private>false</Private>\n      <ExcludeAssets>all</ExcludeAssets>\n    </ProjectReference>\n    <Reference Include=\"DevComponents.DotNetBar2\">\n      <HintPath>..\\References\\DevComponents.DotNetBar2.dll</HintPath>\n      <Private>false</Private>\n    </Reference>\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.2\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\WcR2Plugin.targets\" />\n</Project>"
  },
  {
    "path": "WzComparerR2.PluginBase/FindWzEventArgs.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2\n{\n    public class FindWzEventArgs:EventArgs\n    {\n        public FindWzEventArgs()\n        {\n        }\n\n        public FindWzEventArgs(Wz_Type type)\n        {\n            this.wzType = type;\n        }\n\n        private string fullPath;\n        private Wz_Type wzType;\n        private Wz_File wzFile;\n        private Wz_Node wzNode;\n\n        /// <summary>\n        /// 获取或设置要查找wz节点的完全名称，用于输入参数。\n        /// </summary>\n        public string FullPath\n        {\n            get { return fullPath; }\n            set { fullPath = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置要查找wz节点的Wz_Type，用于输入参数。\n        /// </summary>\n        public Wz_Type WzType\n        {\n            get { return wzType; }\n            set { wzType = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置要查找wz节点的所属Wz_File，用于输入和输出参数。\n        /// </summary>\n        public Wz_File WzFile\n        {\n            get { return wzFile; }\n            set { wzFile = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置要查找wz节点的Wz_Node，用于输出参数。\n        /// </summary>\n        public Wz_Node WzNode\n        {\n            get { return wzNode; }\n            set { wzNode = value; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/FindWzEventHandler.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2\n{\n    public delegate void FindWzEventHandler(object sender, FindWzEventArgs e);\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/PluginContext.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Windows.Forms;\nusing WzComparerR2.WzLib;\nusing WzComparerR2.Common;\nusing DevComponents.DotNetBar;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.PluginBase\n{\n    public class PluginContext\n    {\n        internal PluginContext(PluginContextProvider contextProvider)\n        {\n            this.contextProvider = contextProvider;\n        }\n\n        private PluginContextProvider contextProvider;\n\n        public Form MainForm\n        {\n            get { return this.contextProvider.MainForm; }\n        }\n\n        public DotNetBarManager DotNetBarManager\n        {\n            get { return this.contextProvider.DotNetBarManager; }\n        }\n\n        public Wz_Node SelectedNode1\n        {\n            get { return this.contextProvider.SelectedNode1; }\n        }\n\n        public Wz_Node SelectedNode2\n        {\n            get { return this.contextProvider.SelectedNode2; }\n        }\n\n        public Wz_Node SelectedNode3\n        {\n            get { return this.contextProvider.SelectedNode3; }\n        }\n\n        public SuperTabItem SelectedTab\n        {\n            get { return this.SuperTabControl1.SelectedTab; }\n        }\n\n        public event EventHandler<WzNodeEventArgs> SelectedNode1Changed\n        {\n            add { contextProvider.SelectedNode1Changed += value; }\n            remove { contextProvider.SelectedNode1Changed -= value; }\n        }\n\n        public event EventHandler<WzNodeEventArgs> SelectedNode2Changed\n        {\n            add { contextProvider.SelectedNode2Changed += value; }\n            remove { contextProvider.SelectedNode2Changed -= value; }\n        }\n\n        public event EventHandler<WzNodeEventArgs> SelectedNode3Changed\n        {\n            add { contextProvider.SelectedNode3Changed += value; }\n            remove { contextProvider.SelectedNode3Changed -= value; }\n        }\n\n        public event EventHandler<WzStructureEventArgs> WzOpened\n        {\n            add { contextProvider.WzOpened += value; }\n            remove { contextProvider.WzOpened-= value; }\n        }\n\n        public event EventHandler<WzStructureEventArgs> WzClosing\n        {\n            add { contextProvider.WzClosing += value; }\n            remove { contextProvider.WzClosing -= value; }\n        }\n\n        public StringLinker DefaultStringLinker\n        {\n            get { return this.contextProvider.DefaultStringLinker; }\n        }\n\n        public AlphaForm DefaultTooltipWindow\n        {\n            get { return this.contextProvider.DefaultTooltipWindow; }\n        }\n\n        private SuperTabControl SuperTabControl1\n        {\n            get\n            {\n                var controls = this.contextProvider.MainForm.Controls.Find(\"superTabControl1\", true);\n                SuperTabControl tabControl = controls.Length > 0 ? (controls[0] as SuperTabControl) : null;\n                return tabControl;\n            }\n        }\n\n        public void AddRibbonBar(string tabName, RibbonBar ribbonBar)\n        {\n            RibbonControl ribbonCtrl = this.MainForm.Controls[\"ribbonControl1\"] as RibbonControl;\n\n            if (ribbonCtrl == null)\n            {\n                throw new Exception(\"无法找到RibbonContainer。\");\n            }\n\n            RibbonPanel ribbonPanel = null;\n            RibbonTabItem tabItem;\n            foreach (BaseItem item in ribbonCtrl.Items)\n            {\n                if ((tabItem = item as RibbonTabItem) != null\n                    && string.Equals(Convert.ToString(tabItem.Tag), tabName, StringComparison.OrdinalIgnoreCase))\n                {\n                    ribbonPanel = tabItem.Panel;\n                    break;\n                }\n            }\n\n            if (ribbonPanel == null)\n            {\n                throw new Exception(\"无法找到RibbonPanel。\");\n            }\n\n            Control lastBar = ribbonPanel.Controls[0];\n            ribbonBar.Location = new System.Drawing.Point(lastBar.Location.X + lastBar.Width, lastBar.Location.Y);\n            ribbonBar.Size = new System.Drawing.Size(Math.Max(1, ribbonBar.Width), lastBar.Height);\n            ribbonPanel.SuspendLayout();\n            ribbonPanel.Controls.Add(ribbonBar);\n            ribbonPanel.Controls.SetChildIndex(ribbonBar, 0);\n            ribbonPanel.ResumeLayout(false);\n        }\n\n        public RibbonBar AddRibbonBar(string tabName, string barText)\n        {\n            RibbonBar bar = new RibbonBar();\n            bar.Text = barText;\n            AddRibbonBar(tabName, bar);\n            return bar;\n        }\n\n        public void AddTab(string tabName, SuperTabControlPanel tabPanel)\n        {\n            SuperTabControl tabControl = this.SuperTabControl1;\n            \n            if (tabControl == null)\n            {\n                throw new Exception(\"无法找到SuperTabControl。\");\n            }\n\n            tabControl.SuspendLayout();\n\n            SuperTabItem tabItem = new SuperTabItem();\n            tabControl.Controls.Add(tabPanel);\n\n            tabControl.Tabs.Add(tabItem);\n            tabPanel.TabItem = tabItem;\n\n            tabItem.Text = tabName;\n            tabItem.AttachedControl = tabPanel;\n            tabControl.ResumeLayout(false);\n        }\n\n        public SuperTabControlPanel AddTab(string tabName)\n        {\n            SuperTabControlPanel panel = new SuperTabControlPanel();\n\n            AddTab(tabName, panel);\n            panel.Controls.Add(new Button());\n            return panel;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/PluginContextProvider.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing DevComponents.DotNetBar;\nusing WzComparerR2.WzLib;\nusing WzComparerR2;\nusing WzComparerR2.Common;\nusing WzComparerR2.Controls;\n\nnamespace WzComparerR2.PluginBase\n{\n    internal interface PluginContextProvider\n    {\n        Office2007RibbonForm MainForm { get; }\n        DotNetBarManager DotNetBarManager { get; }\n        IList<Wz_Structure> LoadedWz { get; }\n        Wz_Node SelectedNode1 { get; }\n        Wz_Node SelectedNode2 { get; }\n        Wz_Node SelectedNode3 { get; }\n        StringLinker DefaultStringLinker { get; }\n        AlphaForm DefaultTooltipWindow { get; }\n\n        event EventHandler<WzNodeEventArgs> SelectedNode1Changed;\n        event EventHandler<WzNodeEventArgs> SelectedNode2Changed;\n        event EventHandler<WzNodeEventArgs> SelectedNode3Changed;\n        event EventHandler<WzStructureEventArgs> WzOpened;\n        event EventHandler<WzStructureEventArgs> WzClosing;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/PluginEntry.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Reflection;\n\nnamespace WzComparerR2.PluginBase\n{\n    public abstract class PluginEntry\n    {\n        public PluginEntry(PluginContext context)\n        {\n            this.Context = context;\n        }\n\n        public PluginContext Context\n        {\n            get;\n            private set;\n        }\n\n        public virtual string Name\n        {\n            get\n            {\n                var attr = GetAsmAttr<AssemblyTitleAttribute>();\n                return attr != null ? attr.Title : null;\n            }\n        }\n\n        public virtual string Author\n        {\n            get\n            {\n                var attr = GetAsmAttr<AssemblyCompanyAttribute>();\n                return attr != null ? attr.Company : null;\n            }\n        }\n\n        public virtual string Version\n        {\n            get\n            {\n                return this.GetType().Assembly.GetName().Version.ToString();\n            }\n        }\n\n        public virtual string FileVersion\n        {\n            get\n            {\n                var attrInfoVersion = GetAsmAttr<AssemblyInformationalVersionAttribute>();\n                if (!string.IsNullOrEmpty(attrInfoVersion?.InformationalVersion))\n                {\n                    return attrInfoVersion.InformationalVersion;\n                }\n\n                var attrFileVersion = GetAsmAttr<AssemblyFileVersionAttribute>();\n                return attrFileVersion?.Version;\n            }\n        }\n\n        private T GetAsmAttr<T>()\n        {\n            object[] attr = this.GetType().Assembly.GetCustomAttributes(typeof(T), true);\n            if (attr != null && attr.Length > 0)\n            {\n                return (T)attr[0];\n            }\n            return default(T);\n        }\n\n        protected internal virtual void OnLoad()\n        {\n\n        }\n\n        protected internal virtual void OnUnload()\n        {\n\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/PluginInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Reflection;\n\nnamespace WzComparerR2.PluginBase\n{\n    internal class PluginInfo\n    {\n        public string FileName { get; set; }\n        public Assembly Assembly { get; set; }\n        public PluginEntry Instance { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/PluginManager.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\nusing System.IO;\nusing System.Reflection;\nusing System.Diagnostics;\nusing System.Collections.ObjectModel;\nusing DevComponents.DotNetBar;\nusing System.Windows.Forms;\nusing System.Linq;\n\nnamespace WzComparerR2.PluginBase\n{\n    public class PluginManager\n    {\n        /// <summary>\n        /// 当执行FindWz函数时发生，用来寻找对应的Wz_File。\n        /// </summary>\n        internal static event FindWzEventHandler WzFileFinding;\n\n        /// <summary>\n        /// 为CharaSim组件提供全局的搜索Wz_File的方法。\n        /// </summary>\n        /// <param Name=\"Type\">要搜索wz文件的Wz_Type。</param>\n        /// <returns></returns>\n        public static Wz_Node FindWz(Wz_Type type)\n        {\n            return FindWz(type, null);\n        }\n\n        public static Wz_Node FindWz(Wz_Type type, Wz_File sourceWzFile)\n        {\n            FindWzEventArgs e = new FindWzEventArgs(type) { WzFile = sourceWzFile };\n            if (WzFileFinding != null)\n            {\n                WzFileFinding(null, e);\n                if (e.WzNode != null)\n                {\n                    return e.WzNode;\n                }\n                if (e.WzFile != null)\n                {\n                    return e.WzFile.Node;\n                }\n            }\n            return null;\n        }\n\n        /// <summary>\n        /// 通过wz完整路径查找对应的Wz_Node，若没有找到则返回null。\n        /// </summary>\n        /// <param name=\"fullPath\">要查找节点的完整路径，可用'/'或者'\\'作分隔符，如\"Mob/8144006.img/die1/6\"。</param>\n        /// <returns></returns>\n        public static Wz_Node FindWz(string fullPath)\n        {\n            return FindWz(fullPath, null);\n        }\n\n        public static Wz_Node FindWz(string fullPath, Wz_File sourceWzFile)\n        {\n            FindWzEventArgs e = new FindWzEventArgs() { FullPath = fullPath, WzFile = sourceWzFile };\n            if (WzFileFinding != null)\n            {\n                WzFileFinding(null, e);\n                if (e.WzNode != null)\n                {\n                    return e.WzNode;\n                }\n                if (e.WzFile != null)\n                {\n                    return e.WzFile.Node;\n                }\n            }\n            return null;\n        }\n\n        public static void LogError(string logger, string format, params object[] args)\n        {\n            LogError(logger, null, format, args);\n        }\n\n        public static void LogError(string logger, Exception ex, string format, params object[] args)\n        {\n            string logText = string.Format(\"[{0:yyyy-MM-dd HH:mm:ss}][Error][{1}] {2}{3}\",\n                DateTime.Now,\n                logger,\n                args == null ? format : string.Format(format, args),\n                ex?.ToString());\n\n            string logFile = Path.Combine(Path.GetDirectoryName(MainExecutorPath), \"error.log\");\n\n            try\n            {\n                File.AppendAllLines(logFile, new[] { logText });\n            }\n            catch\n            {\n            }\n        }\n\n        internal static string MainExecutorPath\n        {\n            get\n            {\n                var asmArray = AppDomain.CurrentDomain.GetAssemblies();\n                foreach (var asm in asmArray)\n                {\n                    string asmName = asm.GetName().Name;\n                    if (string.Equals(asmName, \"WzComparerR2\", StringComparison.OrdinalIgnoreCase))\n                    {\n                        return asm.Location;\n                    }\n                }\n                return \"\";\n            }\n        }\n\n        internal static string[] GetPluginFiles()\n        {\n            List<string> fileList = new List<string>();\n            string baseDir = Path.GetDirectoryName(MainExecutorPath);\n            string pluginDir = Path.Combine(baseDir, \"Plugin\");\n            if (Directory.Exists(pluginDir))\n            {\n                foreach (string file in Directory.GetFiles(pluginDir, \"WzComparerR2.*.dll\", SearchOption.AllDirectories))\n                {\n                    fileList.Add(file);\n                }\n            }\n            else\n            {\n                Directory.CreateDirectory(pluginDir);\n            }\n            return fileList.ToArray();\n        }\n\n        internal static IReadOnlyCollection<PluginInfo> LoadPlugin(Assembly assembly, PluginContext context)\n        {\n            var baseType = typeof(PluginEntry);\n            return assembly.GetExportedTypes().Where(type => type.IsSubclassOf(baseType) && !type.IsAbstract)\n                .Select(type =>\n                {\n                    try\n                    {\n                        var entry = Activator.CreateInstance(type, context) as PluginEntry;\n                        var plugin = new PluginInfo()\n                        {\n                            Assembly = assembly,\n                            FileName = assembly.Location,\n                            Instance = entry,\n                        };\n                        loadedPlugins.Add(plugin);\n                        return plugin;\n                    }\n                    catch\n                    {\n                        return null;\n                    }\n                }).OfType<PluginInfo>()\n                .ToList();\n        }\n\n        internal static void PluginOnLoad()\n        {\n            foreach (var plugin in loadedPlugins)\n            {\n                try\n                {\n                    plugin.Instance.OnLoad();\n                }\n                catch (Exception ex)\n                {\n                    MessageBoxEx.Show(\"插件初始化失败。\\r\\n\" + ex.Message, plugin.Instance.Name, MessageBoxButtons.OK, MessageBoxIcon.Error);\n                }\n            }\n        }\n\n        internal static void PluginOnUnLoad()\n        {\n            foreach (var plugin in loadedPlugins)\n            {\n                try\n                {\n                    plugin.Instance.OnUnload();\n                }\n                catch\n                {\n                }\n            }\n        }\n\n        static List<PluginInfo> loadedPlugins = new List<PluginInfo>();\n        static ReadOnlyCollection<PluginInfo> readonlyLoadedPlugins = new ReadOnlyCollection<PluginInfo>(loadedPlugins);\n        internal static ReadOnlyCollection<PluginInfo> LoadedPlugins\n        {\n            get\n            {\n                return readonlyLoadedPlugins;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的常规信息通过以下\n// 特性集控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2.PluginBase\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Kagamia Studio\")]\n[assembly: AssemblyProduct(\"WzComparerR2.PluginBase\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2013-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n[assembly: InternalsVisibleTo(\"WzComparerR2\")]\n\n// 将 ComVisible 设置为 false 使此程序集中的类型\n// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型，\n// 则将该类型上的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"fb0b169f-e698-4b87-9096-1bdfb2b66901\")]\n\n// 程序集的版本信息由下面四个值组成:\n//\n//      主版本\n//      次版本 \n//      内部版本号\n//      修订号\n//\n// 可以指定所有这些值，也可以使用“内部版本号”和“修订号”的默认值，\n// 方法是按如下所示使用“*”:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2.PluginBase/WzComparerR2.PluginBase.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2.PluginBase</AssemblyName>\n    <RootNamespace>WzComparerR2.PluginBase</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>\n    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n    <LangVersion>latest</LangVersion>\n    <Nullable>disable</Nullable>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n    \n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Xml\" />\n    <Reference Include=\"System.Windows.Forms\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WzComparerR2.Common\\WzComparerR2.Common.csproj\" />\n    <ProjectReference Include=\"..\\WzComparerR2.WzLib\\WzComparerR2.WzLib.csproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Reference Include=\"DevComponents.DotNetBar2\">\n      <HintPath>..\\References\\DevComponents.DotNetBar2.dll</HintPath>\n    </Reference>\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "WzComparerR2.PluginBase/WzNodeEventArgs.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.PluginBase\n{\n    public class WzNodeEventArgs : EventArgs\n    {\n        public WzNodeEventArgs(Wz_Node node)\n        {\n            this.Node = node;\n        }\n\n        public Wz_Node Node { get; private set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.PluginBase/WzStructureEventArgs.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing WzComparerR2.WzLib;\n\nnamespace WzComparerR2.PluginBase\n{\n    public class WzStructureEventArgs : EventArgs\n    {\n        public WzStructureEventArgs(Wz_Structure wz)\n        {\n            this.WzStructure = wz;\n        }\n\n        public Wz_Structure WzStructure { get; private set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Updater/App.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<configuration>\n    <startup> \n        <supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.6.2\" />\n    </startup>\n</configuration>"
  },
  {
    "path": "WzComparerR2.Updater/Program.cs",
    "content": "﻿using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.IO.Compression;\nusing System.Windows.Forms;\n\nclass Program\n{\n    [STAThread]\n    static void Main(string[] args)\n    {\n        if (args.Length == 0)\n        {\n            return;\n        }\n\n        string updateZipPath = args[0];\n        string currentDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);\n        string tempUpdateFolder = Path.Combine(currentDirectory, \"UpdateTemp\");\n        string wzComparerR2Path = Path.Combine(currentDirectory, \"WzComparerR2.exe\");\n\n        try\n        {\n            KillProcess(\"WzComparerR2\");\n            KillProcess(\"WzComparerR2.anycpu\");\n\n            if (Directory.Exists(tempUpdateFolder))\n            {\n                Directory.Delete(tempUpdateFolder, true);\n            }\n            Directory.CreateDirectory(tempUpdateFolder);\n            ZipFile.ExtractToDirectory(updateZipPath, tempUpdateFolder);\n\n            string[] directoriesToDelete = { \"Lib\", \"Plugin\", \"runtimes\" };\n            foreach (var dir in directoriesToDelete)\n            {\n                string dirPath = Path.Combine(currentDirectory, dir);\n                if (Directory.Exists(dirPath))\n                {\n                    Directory.Delete(dirPath, true);\n                }\n            }\n\n            foreach (var file in Directory.GetFiles(currentDirectory, \"WzComparerR2*\"))\n            {\n                if (file.Contains(\"Updater\")) continue;\n                File.Delete(file);\n            }\n\n            foreach (var file in Directory.GetFiles(tempUpdateFolder))\n            {\n                string fileName = Path.GetFileName(file);\n                string destFile = Path.Combine(currentDirectory, fileName);\n                if (File.Exists(destFile))\n                {\n                    File.Delete(destFile);\n                }\n                File.Move(file, destFile);\n            }\n\n            MoveDirectory(tempUpdateFolder, currentDirectory);\n            Directory.Delete(tempUpdateFolder, true);\n\n        }\n        catch (Exception ex)\n        {\n            MessageBox.Show(\"Update failed: \" + ex.Message, \"Error\", MessageBoxButtons.OK, MessageBoxIcon.Error);\n\n            if (File.Exists(updateZipPath))\n            {\n                File.Delete(updateZipPath);\n            }\n            if (Directory.Exists(tempUpdateFolder))\n            {\n                Directory.Delete(tempUpdateFolder, true);\n            }\n        }\n        finally\n        {\n\n            Process.Start(wzComparerR2Path);\n        }\n    }\n\n    static void KillProcess(string processName)\n    {\n        foreach (var process in Process.GetProcessesByName(processName))\n        {\n            process.Kill();\n            process.WaitForExit();\n        }\n    }\n\n    static void MoveDirectory(string sourceDir, string targetDir)\n    {\n        if (!Directory.Exists(targetDir))\n        {\n            Directory.CreateDirectory(targetDir);\n        }\n\n        foreach (var file in Directory.GetFiles(sourceDir))\n        {\n            string fileName = Path.GetFileName(file);\n            string destFile = Path.Combine(targetDir, fileName);\n            if (File.Exists(destFile))\n            {\n                File.Delete(destFile);\n            }\n            File.Move(file, destFile);\n        }\n\n        foreach (var subDir in Directory.GetDirectories(sourceDir))\n        {\n            string dirName = Path.GetFileName(subDir);\n            string destDir = Path.Combine(targetDir, dirName);\n            MoveDirectory(subDir, destDir);\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.Updater/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的一般信息由以下\n// 控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2 Updater\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Kagamia Studio\")]\n[assembly: AssemblyProduct(\"WzComparerR2 Updater\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2011-2026\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 会使此程序集中的类型\n//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型\n//请将此类型的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"308da978-7623-4a29-86d5-6d16727ed4b3\")]\n\n// 程序集的版本信息由下列四个值组成: \n//\n//      主版本\n//      次版本\n//      生成号\n//      修订号\n//\n//可以指定所有这些值，也可以使用“生成号”和“修订号”的默认值\n//通过使用 \"*\"，如下所示:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"2.2.0.0\")]\n[assembly: AssemblyFileVersion(\"2.2.0.10725\")]\n\n"
  },
  {
    "path": "WzComparerR2.Updater/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     此代码由工具生成。\n//     运行时版本:4.0.30319.42000\n//\n//     对此文件的更改可能会导致不正确的行为，并且如果\n//     重新生成代码，这些更改将会丢失。\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace WzComparerR2Updater.Properties {\n    using System;\n    \n    \n    /// <summary>\n    ///   一个强类型的资源类，用于查找本地化的字符串等。\n    /// </summary>\n    // 此类是由 StronglyTypedResourceBuilder\n    // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。\n    // 若要添加或移除成员，请编辑 .ResX 文件，然后重新运行 ResGen\n    // (以 /str 作为命令选项)，或重新生成 VS 项目。\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"17.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources {\n        \n        private static global::System.Resources.ResourceManager resourceMan;\n        \n        private static global::System.Globalization.CultureInfo resourceCulture;\n        \n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources() {\n        }\n        \n        /// <summary>\n        ///   返回此类使用的缓存的 ResourceManager 实例。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager {\n            get {\n                if (object.ReferenceEquals(resourceMan, null)) {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"WzComparerR2Updater.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n        \n        /// <summary>\n        ///   重写当前线程的 CurrentUICulture 属性，对\n        ///   使用此强类型资源类的所有资源查找执行重写。\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture {\n            get {\n                return resourceCulture;\n            }\n            set {\n                resourceCulture = value;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.Updater/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "WzComparerR2.Updater/WzComparerR2.Updater.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <OutputType>WinExe</OutputType>\n    <TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>\n    <UseWindowsForms>true</UseWindowsForms>\n    <AssemblyName>WzComparerR2.Updater</AssemblyName>\n    <RootNamespace>WzComparerR2.Updater</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>\n    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n    <LangVersion>latest</LangVersion>\n    <Nullable>disable</Nullable>\n    <ApplicationIcon>Updater.ico</ApplicationIcon>\n    <AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects>\n    <SignAssembly>False</SignAssembly>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(Platform) == 'AnyCPU'\">\n    <ApplicationManifest>app.manifest</ApplicationManifest>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.IO\" />\n    <Reference Include=\"System.IO.Compression\" />\n    <Reference Include=\"System.IO.Compression.FileSystem\" />\n    <Reference Include=\"System.Windows.Forms\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "WzComparerR2.Updater/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">\n  <asmv3:application>\n    <asmv3:windowsSettings xmlns=\"http://schemas.microsoft.com/SMI/2024/WindowsSettings\">\n      <supportedArchitectures>amd64 arm64</supportedArchitectures>\n    </asmv3:windowsSettings>\n  </asmv3:application>\n</assembly>\n"
  },
  {
    "path": "WzComparerR2.WzLib/Compatibility/WzDirStringReader.cs",
    "content": "using WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib.Compatibility\n{\n    /// <summary>\n    /// Reads directory entry names in PKG2 files, encapsulating version-specific string encoding and key selection.\n    /// </summary>\n    public interface IPkg2DirStringReader\n    {\n        string ReadName(WzBinaryReader reader, bool isFirstEntry);\n    }\n\n    /// <summary>\n    /// PKG2 legacy (KMST 1196-1197): all entries use ReadString with the same key.\n    /// </summary>\n    internal sealed class Pkg2LegacyDirStringReader : IPkg2DirStringReader\n    {\n        public Pkg2LegacyDirStringReader(IWzDecrypter keys)\n        {\n            this.keys = keys;\n        }\n\n        private readonly IWzDecrypter keys;\n\n        public string ReadName(WzBinaryReader reader, bool isFirstEntry)\n        {\n            return reader.ReadString(keys);\n        }\n    }\n\n    /// <summary>\n    /// PKG2 KMST 1198+: first entry uses ReadPkg2DirString with pkg2 key, rest use ReadString with pkg1 key.\n    /// </summary>\n    internal sealed class Pkg2KmstDirStringReader : IPkg2DirStringReader\n    {\n        public Pkg2KmstDirStringReader(IWzDecrypter pkg2Keys, IWzDecrypter pkg1Keys)\n        {\n            this.pkg2Keys = pkg2Keys;\n            this.pkg1Keys = pkg1Keys;\n        }\n\n        private readonly IWzDecrypter pkg2Keys;\n        private readonly IWzDecrypter pkg1Keys;\n\n        public string ReadName(WzBinaryReader reader, bool isFirstEntry)\n        {\n            return isFirstEntry ? reader.ReadPkg2DirString(pkg2Keys) : reader.ReadString(pkg1Keys);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Compatibility/WzOffsetCalc.cs",
    "content": "using System;\nusing WzComparerR2.WzLib.Utilities;\nusing static WzComparerR2.WzLib.Utilities.MathHelper;\n\nnamespace WzComparerR2.WzLib.Compatibility\n{\n    /// <summary>\n    /// Calculates the offset of a wz image/directory entry from its hashed value.\n    /// </summary>\n    public interface IWzImageOffsetCalc\n    {\n        uint CalcOffset(uint filePos, uint hashedOffset);\n    }\n\n    /// <summary>\n    /// Factory delegate for creating offset calculators during version detection.\n    /// </summary>\n    public delegate IWzImageOffsetCalc OffsetCalcFactory(Wz_File wzFile, uint hashVersion);\n\n    /// <summary>\n    /// Extended offset calculator for PKG2, also handles entry count decryption.\n    /// </summary>\n    public interface IPkg2ImageOffsetCalc : IWzImageOffsetCalc\n    {\n        int DecryptEntryCount(int encryptedEntryCount);\n    }\n\n    /// <summary>\n    /// PKG2 offset calculation algorithm version.\n    /// </summary>\n    public enum Pkg2OffsetVersion\n    {\n        /// <summary>KMST 1196-1197</summary>\n        KMST1196 = 1,\n        /// <summary>KMST 1198</summary>\n        KMST1198 = 2,\n        /// <summary>KMST 1199</summary>\n        KMST1199 = 3,\n    }\n\n    /// <summary>\n    /// PKG1 offset calculation (original format).\n    /// </summary>\n    public sealed class Pkg1OffsetCalc : IWzImageOffsetCalc\n    {\n        public Pkg1OffsetCalc(uint headerLen, uint hashVersion)\n        {\n            this.headerLen = headerLen;\n            this.hashVersion = hashVersion;\n        }\n\n        private readonly uint headerLen;\n        private readonly uint hashVersion;\n\n        public uint CalcOffset(uint filePos, uint hashedOffset)\n        {\n            uint offset = filePos - this.headerLen;\n            offset = ~offset;\n            offset *= this.hashVersion;\n            offset -= 0x581C3F6D;\n            int distance = (int)offset & 0x1F;\n            offset = ROL(offset, distance);\n            offset ^= hashedOffset;\n            offset += this.headerLen * 2;\n            return offset;\n        }\n    }\n\n    /// <summary>\n    /// PKG2 offset calculation for KMST 1196-1197 (V1).\n    /// </summary>\n    public sealed class Pkg2OffsetCalcV1 : IPkg2ImageOffsetCalc\n    {\n        public Pkg2OffsetCalcV1(uint headerLen, uint hashVersion, uint hash1)\n        {\n            this.headerLen = headerLen;\n            this.hashVersion = hashVersion;\n            this.hash1 = hash1;\n        }\n\n        private readonly uint headerLen;\n        private readonly uint hashVersion;\n        private readonly uint hash1;\n\n        public uint CalcOffset(uint filePos, uint hashedOffset)\n        {\n            uint offset = filePos - this.headerLen;\n            offset = ~offset;\n            offset *= this.hashVersion;\n            offset -= 0x581C3F6D;\n            offset ^= this.hash1 * 0x01010101;\n            int distance = (byte)((this.hashVersion ^ this.hash1) & 0x1F);\n            offset = ROL(offset, distance);\n            offset ^= hashedOffset;\n            offset += this.headerLen;\n            return offset;\n        }\n\n        public int DecryptEntryCount(int encryptedEntryCount)\n        {\n            return (int)(encryptedEntryCount ^ ((this.hash1 << 24) + (0x7F4A7C15 * this.hashVersion)));\n        }\n    }\n\n    /// <summary>\n    /// PKG2 offset calculation for KMST 1198 (V2).\n    /// </summary>\n    public sealed class Pkg2OffsetCalcV2 : IPkg2ImageOffsetCalc\n    {\n        public Pkg2OffsetCalcV2(uint headerLen, uint hashVersion, uint hash1)\n        {\n            this.headerLen = headerLen;\n            this.hashVersion = hashVersion;\n            this.hash1 = hash1;\n        }\n\n        private readonly uint headerLen;\n        private readonly uint hashVersion;\n        private readonly uint hash1;\n\n        public uint CalcOffset(uint filePos, uint hashedOffset)\n        {\n            uint offset = filePos - this.headerLen;\n            offset = ~offset;\n            offset *= this.hashVersion ^ this.hash1;\n            offset -= 0x581C3F6D;\n            offset ^= this.hash1 * 0x01010101;\n            int distance = (byte)((this.hashVersion ^ this.hash1) & 0x1F);\n            offset = ROL(offset, distance);\n            offset ^= ~hashedOffset;\n            offset += this.headerLen;\n            return offset;\n        }\n\n        public int DecryptEntryCount(int encryptedEntryCount)\n        {\n            return (int)(encryptedEntryCount ^ ((this.hash1 << 16) - (0x21524111 * this.hashVersion)));\n        }\n    }\n\n    /// <summary>\n    /// PKG2 offset calculation for KMST 1199 (V3).\n    /// </summary>\n    public sealed class Pkg2OffsetCalcV3 : IPkg2ImageOffsetCalc\n    {\n        public Pkg2OffsetCalcV3(uint headerLen, uint hashVersion, uint hash1)\n        {\n            this.headerLen = headerLen;\n            this.hashVersion = hashVersion;\n            this.hash1 = hash1;\n\n            uint preHash = hash1 ^ hashVersion;\n            this.preHash = preHash;\n            this.mixedHash = Mix(preHash ^ 0x6D4C3B2A) ^ 0x91E10DA5;\n        }\n\n        private readonly uint headerLen;\n        private readonly uint hashVersion;\n        private readonly uint hash1;\n        private readonly uint preHash;\n        private readonly uint mixedHash;\n\n        public uint CalcOffset(uint filePos, uint hashedOffset)\n        {\n            uint offset = filePos - this.headerLen;\n            offset = ~offset;\n            offset *= this.preHash + (this.mixedHash ^ 0xA7E3C093);\n            offset -= 0x581C3F6D;\n            offset ^= this.hash1 * 0x01010101;\n            offset ^= this.mixedHash * 0x9E3779B9;\n            int distance = (byte)((this.preHash ^ this.mixedHash) & 0x1F);\n            offset = ROL(offset, distance);\n            offset ^= ~hashedOffset;\n            offset += this.headerLen;\n            return offset;\n        }\n\n        public int DecryptEntryCount(int encryptedEntryCount)\n        {\n            return (int)(encryptedEntryCount ^ ((this.hash1 << 16) + (this.mixedHash & 0x7fffffff) - (0x21524111 * this.hashVersion)));\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "WzComparerR2.WzLib/Compatibility/WzPreReadProvider.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib.Compatibility\n{\n    /// <summary>\n    /// Interface for pre-reading wz directory tree structure without decryption.\n    /// </summary>\n    internal interface IWzPreReader\n    {\n        bool TryPreRead(Wz_File wzFile, out WzPreReadResult result);\n    }\n\n    /// <summary>\n    /// Static registry of all pre-reader implementations, ordered by priority.\n    /// </summary>\n    internal static class WzPreReaders\n    {\n        private static readonly IWzPreReader[] readers = new IWzPreReader[]\n        {\n            new Pkg1PreReader(),\n            new Pkg2PreReader(WzFileFormat.Pkg2Kmst1196, isPkg2DirString: false),\n            new Pkg2PreReader(WzFileFormat.Pkg2Kmst1198, isPkg2DirString: true),\n        };\n\n        public static IReadOnlyList<IWzPreReader> All => readers;\n    }\n\n    #region PKG1\n\n    internal sealed class Pkg1PreReader : IWzPreReader\n    {\n        public bool TryPreRead(Wz_File wzFile, out WzPreReadResult result)\n        {\n            result = null;\n            if (!wzFile.Header.IsPkg1)\n                return false;\n\n            try\n            {\n                wzFile.FileStream.Position = wzFile.Header.DirStartPosition;\n                var reader = new WzBinaryReader(wzFile.FileStream, false);\n                long dirStartPos = wzFile.FileStream.Position;\n                result = new WzPreReadResult(WzFileFormat.Pkg1, new List<WzPreReadNodeInfo>(), dirStartPos, 0);\n                ReadTree(reader, result);\n                result.DirEndPosition = reader.BaseStream.Position;\n                return true;\n            }\n            catch\n            {\n                result = null;\n                return false;\n            }\n        }\n\n        private static void ReadTree(WzBinaryReader reader, WzPreReadResult result)\n        {\n            int count = reader.ReadCompressedInt32();\n            int dirCount = 0;\n\n            for (int i = 0; i < count; i++)\n            {\n                byte nodeType = reader.ReadByte();\n                switch (nodeType)\n                {\n                    case 0x02:\n                        reader.ReadInt32();\n                        break;\n                    case 0x03:\n                    case 0x04:\n                        if (result.FirstStringRawBytes == null)\n                        {\n                            result.FirstStringRawBytes = WzPreReadHelper.ReadStringRawBytes(reader, false, out var enc);\n                            result.FirstStringEncoding = enc;\n                        }\n                        else\n                        {\n                            WzPreReadHelper.SkipString(reader);\n                        }\n                        break;\n                    default:\n                        throw new InvalidDataException($\"Unknown type {nodeType} in dir pre-read.\");\n                }\n\n                int size = reader.ReadCompressedInt32();\n                reader.ReadCompressedInt32();\n                uint offsetPos = (uint)reader.BaseStream.Position;\n                uint hashedOffset = reader.ReadUInt32();\n\n                result.Nodes.Add(new WzPreReadNodeInfo\n                {\n                    NodeType = nodeType,\n                    DataLength = size,\n                    HashedOffsetPosition = offsetPos,\n                    HashedOffset = hashedOffset,\n                });\n\n                if (nodeType == 0x03) dirCount++;\n            }\n\n            for (int i = 0; i < dirCount; i++)\n                ReadTree(reader, result);\n        }\n    }\n\n    #endregion\n\n    #region PKG2\n\n    internal sealed class Pkg2PreReader : IWzPreReader\n    {\n        public Pkg2PreReader(WzFileFormat format, bool isPkg2DirString)\n        {\n            this.format = format;\n            this.isPkg2DirString = isPkg2DirString;\n        }\n\n        private readonly WzFileFormat format;\n        private readonly bool isPkg2DirString;\n\n        public bool TryPreRead(Wz_File wzFile, out WzPreReadResult result)\n        {\n            result = null;\n            if (!wzFile.Header.IsPkg2)\n                return false;\n\n            try\n            {\n                wzFile.FileStream.Position = wzFile.Header.DirStartPosition;\n                var reader = new WzBinaryReader(wzFile.FileStream, false);\n                long dirStartPos = wzFile.FileStream.Position;\n                result = new WzPreReadResult(this.format, new List<WzPreReadNodeInfo>(), dirStartPos, 0)\n                {\n                    Pkg2DirEntryCounts = new List<Pkg2DirEntryCount>(),\n                };\n                ReadTree(reader, result, this.isPkg2DirString);\n                result.DirEndPosition = reader.BaseStream.Position;\n                return true;\n            }\n            catch\n            {\n                result = null;\n                return false;\n            }\n        }\n\n        private static void ReadTree(WzBinaryReader reader, WzPreReadResult result, bool isPkg2DirString)\n        {\n            int encryptedEntryCount = reader.ReadCompressedInt32();\n            var temp = new List<Pkg2TempEntry>();\n\n            while (true)\n            {\n                byte nodeType = reader.ReadByte();\n                if (nodeType == 0x03 || nodeType == 0x04)\n                {\n                    if (result.FirstStringRawBytes == null && temp.Count == 0)\n                    {\n                        result.FirstStringRawBytes = WzPreReadHelper.ReadStringRawBytes(reader, isPkg2DirString, out var enc);\n                        result.FirstStringEncoding = enc;\n                    }\n                    else if (isPkg2DirString && result.SecondStringRawBytes == null && temp.Count == 1)\n                    {\n                        result.SecondStringRawBytes = WzPreReadHelper.ReadStringRawBytes(reader, false, out var enc);\n                        result.SecondStringEncoding = enc;\n                    }\n                    else\n                        WzPreReadHelper.SkipString(reader);\n\n                    int size = reader.ReadCompressedInt32();\n                    reader.ReadCompressedInt32();\n                    temp.Add(new Pkg2TempEntry { NodeType = nodeType, DataLength = size });\n                }\n                else if (nodeType == 0x80\n                    || (encryptedEntryCount >= -127 && encryptedEntryCount <= 127 && nodeType == encryptedEntryCount))\n                {\n                    reader.BaseStream.Position--;\n                    break;\n                }\n                else\n                {\n                    throw new InvalidDataException($\"Invalid node type {nodeType} in pkg2 pre-read.\");\n                }\n            }\n\n            int encryptedOffsetCount = reader.ReadCompressedInt32();\n            if (encryptedOffsetCount != encryptedEntryCount)\n                throw new InvalidDataException(\"Offset count does not match entry count.\");\n\n            result.Pkg2DirEntryCounts.Add(new Pkg2DirEntryCount\n            {\n                EncryptedEntryCount = encryptedEntryCount,\n                ActualEntryCount = temp.Count,\n            });\n\n            int dirCount = 0;\n            for (int i = 0; i < temp.Count; i++)\n            {\n                uint offsetPos = (uint)reader.BaseStream.Position;\n                uint hashedOffset = reader.ReadUInt32();\n                result.Nodes.Add(new WzPreReadNodeInfo\n                {\n                    NodeType = temp[i].NodeType,\n                    DataLength = temp[i].DataLength,\n                    HashedOffsetPosition = offsetPos,\n                    HashedOffset = hashedOffset,\n                });\n                if (temp[i].NodeType == 0x03) dirCount++;\n            }\n\n            for (int i = 0; i < dirCount; i++)\n                ReadTree(reader, result, isPkg2DirString);\n        }\n\n        private struct Pkg2TempEntry\n        {\n            public int NodeType;\n            public int DataLength;\n        }\n    }\n\n    #endregion\n\n    #region Shared helpers\n\n    internal static class WzPreReadHelper\n    {\n        public static byte[] ReadStringRawBytes(this WzBinaryReader reader, bool isPkg2DirString, out WzStringEncoding encoding)\n        {\n            sbyte lenPrefix = reader.ReadSByte();\n            if (isPkg2DirString)\n            {\n                encoding = WzStringEncoding.UTF16;\n                if (lenPrefix < 0) return reader.ReadBytes((-lenPrefix) * 2);\n                if (lenPrefix == 0) return Array.Empty<byte>();\n                throw new InvalidDataException(\"Unexpected positive length in pkg2 dir string.\");\n            }\n\n            if (lenPrefix < 0)\n            {\n                encoding = WzStringEncoding.ASCII;\n                int size = lenPrefix == -128 ? reader.ReadInt32() : -lenPrefix;\n                return reader.ReadBytes(size);\n            }\n            if (lenPrefix > 0)\n            {\n                encoding = WzStringEncoding.UTF16;\n                int size = lenPrefix == 127 ? reader.ReadInt32() : lenPrefix;\n                return reader.ReadBytes(size * 2);\n            }\n            encoding = WzStringEncoding.Unknown;\n            return Array.Empty<byte>();\n        }\n\n        public static void SkipString(this WzBinaryReader reader)\n        {\n            sbyte len = reader.ReadSByte();\n            if (len < 0)\n            {\n                int size = len == -128 ? reader.ReadInt32() : -len;\n                reader.BaseStream.Position += size;\n            }\n            else if (len > 0)\n            {\n                int size = len == 127 ? reader.ReadInt32() : len;\n                reader.BaseStream.Position += size * 2;\n            }\n        }\n\n        public static bool IsLegalNodeName(ReadOnlySpan<char> utf16NodeName)\n        {\n            if (utf16NodeName.Length == 0) return false;\n            if (utf16NodeName.EndsWith(\".img\".AsSpan()) || utf16NodeName.EndsWith(\".lua\".AsSpan())) return true;\n            foreach (var c in utf16NodeName)\n            {\n                if (!(0x20 <= c && c <= 0x7f)) return false;\n            }\n            return true;\n        }\n\n        public static bool IsLegalNodeName(ReadOnlySpan<byte> asciiNodeName)\n        {\n            if (asciiNodeName.Length == 0) return false;\n            if (asciiNodeName.EndsWith(\".img\"u8) || asciiNodeName.EndsWith(\".lua\"u8)) return true;\n            foreach (var c in asciiNodeName)\n            {\n                if (!(0x20 <= c && c <= 0x7f)) return false;\n            }\n            return true;\n        }\n    }\n\n    #endregion\n\n    #region Enums and result types\n\n    public enum WzFileFormat\n    {\n        Pkg1,\n        Pkg2Kmst1196,\n        Pkg2Kmst1198,\n    }\n\n    public enum WzStringEncoding\n    {\n        Unknown,\n        ASCII,\n        UTF16,\n    }\n\n    public sealed class WzPreReadResult\n    {\n        public WzPreReadResult(WzFileFormat format, List<WzPreReadNodeInfo> nodes, long dirStartPosition, long dirEndPosition)\n        {\n            this.Format = format;\n            this.Nodes = nodes;\n            this.DirStartPosition = dirStartPosition;\n            this.DirEndPosition = dirEndPosition;\n        }\n\n        public WzFileFormat Format { get; }\n        public List<WzPreReadNodeInfo> Nodes { get; }\n        public long DirStartPosition { get; }\n        public long DirEndPosition { get; internal set; }\n        public WzStringEncoding FirstStringEncoding { get; set; }\n        public byte[] FirstStringRawBytes { get; set; }\n        public WzStringEncoding SecondStringEncoding { get; set; }\n        public byte[] SecondStringRawBytes { get; set; }\n\n        /// <summary>\n        /// Per-directory-level encrypted and actual entry counts for PKG2 files.\n        /// </summary>\n        public List<Pkg2DirEntryCount> Pkg2DirEntryCounts { get; set; }\n    }\n\n    public struct WzPreReadNodeInfo\n    {\n        public int NodeType;\n        public int DataLength;\n        public uint HashedOffsetPosition;\n        public uint HashedOffset;\n    }\n\n    public struct Pkg2DirEntryCount\n    {\n        public int EncryptedEntryCount;\n        public int ActualEntryCount;\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Compatibility/WzVersionProfile.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib.Compatibility\n{\n    public abstract class WzVersionProfile\n    {\n        protected WzVersionProfile(WzFileFormat format, Wz_CryptoKeyType cryptoKeyType)\n        {\n            this.Format = format;\n            this.CryptoKeyType = cryptoKeyType;\n        }\n\n        public WzFileFormat Format { get; }\n        public abstract string Name { get; }\n        public Wz_CryptoKeyType CryptoKeyType { get; }\n        public abstract IWzVersionIterator CreateIterator(Wz_File wzFile);\n        public abstract bool TryDetect(Wz_File wzFile, WzPreReadResult preReadResult, IWzVersionIterator iterator);\n        public abstract IWzImageOffsetCalc CreateOffsetCalc(Wz_File wzFile, uint hashVersion);\n\n        public virtual IWzImageOffsetCalc CreateOffsetCalc(Wz_File wzFile)\n        {\n            return CreateOffsetCalc(wzFile, wzFile.Header.HashVersion);\n        }\n\n        public abstract void DetectCryptoKeyType(Wz_File wzFile, Wz_Crypto crypto, WzPreReadResult preReadResult, out Wz_CryptoKeyType pkg1KeyType, out Wz_CryptoKeyType pkg2KeyType);\n\n        #region Shared crypto detection helpers\n\n        private static readonly Wz_CryptoKeyType[] Pkg1LegacyCandidates = { Wz_CryptoKeyType.BMS, Wz_CryptoKeyType.KMS, Wz_CryptoKeyType.GMS };\n\n        /// <summary>\n        /// Apply wire mask (0xAA+i for ASCII, 0xAAAA+i for UTF16) then try each candidate key.\n        /// Called by both profile detection and Wz_Crypto fallback.\n        /// </summary>\n        internal static Wz_CryptoKeyType DetectPkg1CryptoKeyType(ReadOnlySpan<byte> rawBytes, WzStringEncoding encoding, Wz_Crypto crypto)\n        {\n            Span<byte> masked = rawBytes.Length <= 256 ? stackalloc byte[rawBytes.Length] : new byte[rawBytes.Length];\n            ApplyWireMask(rawBytes, masked, encoding);\n            return TryMatchKeys(masked, encoding, crypto, Pkg1LegacyCandidates);\n        }\n\n        /// <summary>\n        /// Try a single decrypter on raw bytes (no wire mask).\n        /// </summary>\n        protected static bool TryMatchKey(ReadOnlySpan<byte> sourceBytes, WzStringEncoding encoding, IWzDecrypter decrypter)\n        {\n            Span<byte> buf = sourceBytes.Length <= 256 ? stackalloc byte[sourceBytes.Length] : new byte[sourceBytes.Length];\n            decrypter.Decrypt(sourceBytes, buf);\n            return IsDecryptedStringLegal(buf, encoding);\n        }\n\n        private static Wz_CryptoKeyType TryMatchKeys(ReadOnlySpan<byte> sourceBytes, WzStringEncoding encoding, Wz_Crypto crypto, Wz_CryptoKeyType[] candidates)\n        {\n            Span<byte> buf = sourceBytes.Length <= 256 ? stackalloc byte[sourceBytes.Length] : new byte[sourceBytes.Length];\n            foreach (var keyType in candidates)\n            {\n                crypto.GetKeys(keyType).Decrypt(sourceBytes, buf);\n                if (IsDecryptedStringLegal(buf, encoding))\n                    return keyType;\n            }\n            return Wz_CryptoKeyType.Unknown;\n        }\n\n        private static bool IsDecryptedStringLegal(ReadOnlySpan<byte> bytes, WzStringEncoding encoding)\n        {\n            if (encoding == WzStringEncoding.ASCII)\n            {\n                int len = bytes.Length;\n                Span<char> chars = len <= 256 ? stackalloc char[len] : new char[len];\n                for (int i = 0; i < len; i++) chars[i] = (char)bytes[i];\n                return WzPreReadHelper.IsLegalNodeName(chars);\n            }\n            else\n            {\n                return WzPreReadHelper.IsLegalNodeName(MemoryMarshal.Cast<byte, char>(bytes));\n            }\n        }\n\n        private static void ApplyWireMask(ReadOnlySpan<byte> rawBytes, Span<byte> output, WzStringEncoding encoding)\n        {\n            if (encoding == WzStringEncoding.ASCII)\n            {\n                MathHelper.XorBytes(rawBytes, output);\n            }\n            else\n            {\n                MathHelper.XorChars(MemoryMarshal.Cast<byte, char>(rawBytes), MemoryMarshal.Cast<byte, char>(output));\n            }\n        }\n\n        #endregion\n    }\n\n    /// <summary>\n    /// Static registry of all known version profiles.\n    /// Profiles are ordered so that the most likely match for a given WzFileFormat is tried first.\n    /// </summary>\n    public static class WzVersionProfiles\n    {\n        private static readonly WzVersionProfile[] allProfiles = new WzVersionProfile[]\n        {\n            new Pkg1Profile(),\n            new Pkg2Profile(1200, WzFileFormat.Pkg2Kmst1198, Pkg2OffsetVersion.KMST1199, Wz_CryptoKeyType.KMST1199, new Pkg2HashVersionCalcV5()),\n            new Pkg2Profile(1199, WzFileFormat.Pkg2Kmst1198, Pkg2OffsetVersion.KMST1199, Wz_CryptoKeyType.KMST1199, new Pkg2HashVersionCalcV4()),\n            new Pkg2Profile(1198, WzFileFormat.Pkg2Kmst1198, Pkg2OffsetVersion.KMST1198, Wz_CryptoKeyType.KMST1198, new Pkg2HashVersionCalcV3()),\n            new Pkg2Profile(1197, WzFileFormat.Pkg2Kmst1196, Pkg2OffsetVersion.KMST1196, Wz_CryptoKeyType.BMS, new Pkg2HashVersionCalcV2()),  // BMS/KMS/GMS detected separately\n            new Pkg2Profile(1196, WzFileFormat.Pkg2Kmst1196, Pkg2OffsetVersion.KMST1196, Wz_CryptoKeyType.BMS, new Pkg2HashVersionCalcV1()),\n        };\n\n        public static IEnumerable<WzVersionProfile> GetCandidates(WzFileFormat format)\n        {\n            foreach (var p in allProfiles)\n            {\n                if (p.Format == format)\n                    yield return p;\n            }\n        }\n\n        public static WzVersionProfile GetByName(string name)\n        {\n            foreach (var p in allProfiles)\n            {\n                if (string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase))\n                    return p;\n            }\n            return null;\n        }\n    }\n\n    #region PKG1 profile\n\n    public sealed class Pkg1Profile : WzVersionProfile\n    {\n        public Pkg1Profile() : base(WzFileFormat.Pkg1, Wz_CryptoKeyType.Unknown) { }\n\n        public override string Name => \"pkg1\";\n\n        public override IWzVersionIterator CreateIterator(Wz_File wzFile)\n        {\n            var pkg1 = (Wz_Header.WzPkg1Header)wzFile.Header;\n            if (pkg1.IsEncverMissing)\n                return Pkg1VersionIterator.CreateFixed(777);\n            return new Pkg1VersionIterator(pkg1.EncryptedVersion);\n        }\n\n        public override bool TryDetect(Wz_File wzFile, WzPreReadResult preReadResult, IWzVersionIterator iterator)\n        {\n            if (preReadResult.Format != WzFileFormat.Pkg1)\n                return false;\n\n            return WzVersionDetectHelper.FastDetectWithPreReadNodes(wzFile, preReadResult, iterator,\n                (f, hv) => this.CreateOffsetCalc(f, hv));\n        }\n\n        public override IWzImageOffsetCalc CreateOffsetCalc(Wz_File wzFile, uint hashVersion)\n        {\n            return new Pkg1OffsetCalc((uint)wzFile.Header.HeaderSize, hashVersion);\n        }\n\n        public override void DetectCryptoKeyType(Wz_File wzFile, Wz_Crypto crypto, WzPreReadResult preReadResult, out Wz_CryptoKeyType pkg1KeyType, out Wz_CryptoKeyType pkg2KeyType)\n        {\n            pkg2KeyType = Wz_CryptoKeyType.Unknown;\n\n            byte[] rawBytes = preReadResult.FirstStringRawBytes;\n            if (rawBytes == null || rawBytes.Length == 0)\n            {\n                pkg1KeyType = Wz_CryptoKeyType.Unknown;\n                return;\n            }\n\n            pkg1KeyType = DetectPkg1CryptoKeyType(rawBytes, preReadResult.FirstStringEncoding, crypto);\n        }\n    }\n\n    #endregion\n\n    #region PKG2 profiles\n\n    public sealed class Pkg2Profile : WzVersionProfile\n    {\n        public Pkg2Profile(\n            int wzVersion,\n            WzFileFormat format,\n            Pkg2OffsetVersion offsetVersion,\n            Wz_CryptoKeyType cryptoKeyType,\n            IPkg2HashVersionCalc hashVersionCalc)\n            : base(format, cryptoKeyType)\n        {\n            this.WzVersion = wzVersion;\n            this.OffsetVersion = offsetVersion;\n            this.HashVersionCalc = hashVersionCalc;\n        }\n\n        public int WzVersion { get; }\n        public Pkg2OffsetVersion OffsetVersion { get; }\n        public IPkg2HashVersionCalc HashVersionCalc { get; }\n\n        public override string Name => $\"pkg2_kmst{this.WzVersion}\";\n\n        public override IWzVersionIterator CreateIterator(Wz_File wzFile)\n        {\n            var pkg2 = (Wz_Header.WzPkg2Header)wzFile.Header;\n            uint hash1 = pkg2.Hash1, hash2 = pkg2.Hash2;\n            var calc = this.HashVersionCalc;\n            return new Pkg2VersionIterator(\n                this.WzVersion,\n                () => calc.CalcCandidates(hash1, hash2),\n                hv => calc.Verify(hash1, hash2, hv));\n        }\n\n        public override bool TryDetect(Wz_File wzFile, WzPreReadResult preReadResult, IWzVersionIterator iterator)\n        {\n            if (preReadResult.Format != this.Format)\n                return false;\n\n            return WzVersionDetectHelper.FastDetectWithPreReadNodes(wzFile, preReadResult, iterator,\n                (f, hv) => this.CreateOffsetCalc(f, hv));\n        }\n\n        public override IWzImageOffsetCalc CreateOffsetCalc(Wz_File wzFile, uint hashVersion)\n        {\n            uint headerLen = (uint)wzFile.Header.HeaderSize;\n            uint hash1 = ((Wz_Header.WzPkg2Header)wzFile.Header).Hash1;\n            return this.OffsetVersion switch\n            {\n                Pkg2OffsetVersion.KMST1196 => new Pkg2OffsetCalcV1(headerLen, hashVersion, hash1),\n                Pkg2OffsetVersion.KMST1198 => new Pkg2OffsetCalcV2(headerLen, hashVersion, hash1),\n                Pkg2OffsetVersion.KMST1199 => new Pkg2OffsetCalcV3(headerLen, hashVersion, hash1),\n                _ => throw new ArgumentOutOfRangeException(nameof(OffsetVersion)),\n            };\n        }\n\n        public override void DetectCryptoKeyType(Wz_File wzFile, Wz_Crypto crypto, WzPreReadResult preReadResult, out Wz_CryptoKeyType pkg1KeyType, out Wz_CryptoKeyType pkg2KeyType)\n        {\n            pkg1KeyType = Wz_CryptoKeyType.Unknown;\n            pkg2KeyType = Wz_CryptoKeyType.Unknown;\n\n            byte[] rawBytes = preReadResult.FirstStringRawBytes;\n            if (rawBytes == null || rawBytes.Length == 0)\n                return;\n\n            if (this.Format == WzFileFormat.Pkg2Kmst1196)\n            {\n                // Legacy PKG2: same wire-mask + BMS/KMS/GMS probing as PKG1\n                var keyType = DetectPkg1CryptoKeyType(rawBytes, preReadResult.FirstStringEncoding, crypto);\n                pkg1KeyType = keyType;\n                pkg2KeyType = keyType;\n                return;\n            }\n\n            // KMST 1198+: detect pkg2 key from first string\n            if (this.WzVersion == 1198)\n            {\n                if (TryMatchKey(rawBytes, WzStringEncoding.UTF16, crypto.GetKeys(Wz_CryptoKeyType.KMST1198)))\n                    pkg2KeyType = Wz_CryptoKeyType.KMST1198;\n            }\n            else if (this.WzVersion >= 1199 && wzFile.Header.VersionChecked)\n            {\n                uint hash1 = (wzFile.Header as Wz_Header.WzPkg2Header)?.Hash1 ?? 0;\n                uint hashVersion = wzFile.Header.HashVersion;\n                if (TryMatchKey(rawBytes, WzStringEncoding.UTF16, new Wz_Crypto.Pkg2DirStringKeyV2(hash1, hashVersion)))\n                    pkg2KeyType = Wz_CryptoKeyType.KMST1199;\n            }\n\n            // Detect pkg1 key from second string (standard encoding)\n            byte[] secondBytes = preReadResult.SecondStringRawBytes;\n            if (secondBytes != null && secondBytes.Length > 0)\n            {\n                pkg1KeyType = DetectPkg1CryptoKeyType(secondBytes, preReadResult.SecondStringEncoding, crypto);\n            }\n        }\n\n        /// <summary>\n        /// Create a directory string reader for this profile, used by ReadDirTreePkg2.\n        /// Must be called after crypto detection has completed.\n        /// </summary>\n        public IPkg2DirStringReader CreateDirStringReader(Wz_File wzFile, Wz_Crypto crypto)\n        {\n            if (this.Format == WzFileFormat.Pkg2Kmst1196)\n            {\n                return new Pkg2LegacyDirStringReader(crypto.Pkg2Keys);\n            }\n\n            IWzDecrypter pkg2Keys;\n            if (this.CryptoKeyType == Wz_CryptoKeyType.KMST1199)\n            {\n                var pkg2 = (Wz_Header.WzPkg2Header)wzFile.Header;\n                pkg2Keys = new Wz_Crypto.Pkg2DirStringKeyV2(pkg2.Hash1, wzFile.Header.HashVersion);\n            }\n            else\n            {\n                pkg2Keys = crypto.Pkg2Keys;\n            }\n            var pkg1Keys = crypto.Pkg1Keys ?? crypto.GetKeys(Wz_CryptoKeyType.BMS);\n            return new Pkg2KmstDirStringReader(pkg2Keys, pkg1Keys);\n        }\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Compatibility/WzVersionVerifier.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Runtime.InteropServices;\nusing System.Threading.Tasks;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nusing static WzComparerR2.WzLib.Utilities.MathHelper;\n\nnamespace WzComparerR2.WzLib.Compatibility\n{\n    #region Version iterators\n    public interface IWzVersionIterator\n    {\n        bool TryGetNextVersion();\n        void Reset();\n        int WzVersion { get; }\n        uint HashVersion { get; }\n        bool IsMatch(int wzVersion, uint hashVersion);\n    }\n\n    /// <summary>\n    /// PKG1 version iterator. Supports ordinal iteration (matching encrypted version byte)\n    /// and fixed mode (for encverMissing, wzVersion = 777).\n    /// </summary>\n    public class Pkg1VersionIterator : IWzVersionIterator\n    {\n        private readonly int encryptedVersion;\n        private readonly int fixedWzVersion;\n        private readonly uint fixedHashVersion;\n\n        private int startVersion;\n        private bool hasReturned;\n\n        /// <summary>\n        /// Ordinal mode: iterates wzVersions whose encrypted version byte matches.\n        /// </summary>\n        public Pkg1VersionIterator(int encryptedVersion)\n        {\n            this.encryptedVersion = encryptedVersion;\n            this.fixedWzVersion = -1;\n            this.startVersion = -1;\n        }\n\n        private Pkg1VersionIterator(int fixedWzVersion, uint fixedHashVersion)\n        {\n            this.encryptedVersion = -1;\n            this.fixedWzVersion = fixedWzVersion;\n            this.fixedHashVersion = fixedHashVersion;\n            this.startVersion = -1;\n        }\n\n        /// <summary>\n        /// Fixed mode: yields a single known wzVersion (e.g. 777 for encverMissing).\n        /// </summary>\n        public static Pkg1VersionIterator CreateFixed(int wzVersion)\n        {\n            return new Pkg1VersionIterator(wzVersion, Wz_Header.CalcHashVersion(wzVersion));\n        }\n\n        private bool IsFixed => this.fixedWzVersion >= 0;\n\n        public int WzVersion { get; private set; }\n        public uint HashVersion { get; private set; }\n\n        private static uint CalcEncryptedVersion(uint hashVersion)\n        {\n            return 0xff\n                ^ ((hashVersion >> 24) & 0xFF)\n                ^ ((hashVersion >> 16) & 0xFF)\n                ^ ((hashVersion >> 8) & 0xFF)\n                ^ ((hashVersion) & 0xFF);\n        }\n\n        public bool TryGetNextVersion()\n        {\n            if (IsFixed)\n            {\n                if (!hasReturned)\n                {\n                    hasReturned = true;\n                    WzVersion = fixedWzVersion;\n                    HashVersion = fixedHashVersion;\n                    return true;\n                }\n                return false;\n            }\n\n            for (int i = startVersion + 1; i < short.MaxValue; i++)\n            {\n                uint sum = Wz_Header.CalcHashVersion(i);\n                if (CalcEncryptedVersion(sum) == (uint)this.encryptedVersion)\n                {\n                    WzVersion = i;\n                    HashVersion = sum;\n                    startVersion = i;\n                    return true;\n                }\n            }\n\n            return false;\n        }\n\n        public bool IsMatch(int wzVersion, uint hashVersion)\n        {\n            if (IsFixed)\n                return wzVersion == fixedWzVersion && hashVersion == fixedHashVersion;\n\n            if (Wz_Header.CalcHashVersion(wzVersion) != hashVersion)\n                return false;\n            return CalcEncryptedVersion(hashVersion) == (uint)this.encryptedVersion;\n        }\n\n        public void Reset()\n        {\n            startVersion = -1;\n            WzVersion = 0;\n            HashVersion = 0;\n            hasReturned = false;\n        }\n    }\n\n    /// <summary>\n    /// PKG2 version iterator. Candidates are lazily computed on first iteration.\n    /// IsMatch uses a lightweight O(1) verify function, avoiding expensive candidate enumeration.\n    /// </summary>\n    public class Pkg2VersionIterator : IWzVersionIterator\n    {\n        public Pkg2VersionIterator(int wzVersion, Func<IReadOnlyList<uint>> candidatesFactory, Func<uint, bool> verifier)\n        {\n            this.wzVersion = wzVersion;\n            this.candidatesFactory = candidatesFactory;\n            this.verifier = verifier;\n            this.index = -1;\n        }\n\n        private readonly int wzVersion;\n        private readonly Func<IReadOnlyList<uint>> candidatesFactory;\n        private readonly Func<uint, bool> verifier;\n        private IReadOnlyList<uint> candidates;\n        private int index;\n\n        public int WzVersion => wzVersion;\n        public uint HashVersion { get; private set; }\n\n        public bool TryGetNextVersion()\n        {\n            this.candidates ??= this.candidatesFactory();\n            if (++this.index < this.candidates.Count)\n            {\n                this.HashVersion = this.candidates[this.index];\n                return true;\n            }\n            return false;\n        }\n\n        public bool IsMatch(int wzVersion, uint hashVersion)\n        {\n            if (wzVersion != this.wzVersion)\n                return false;\n            return this.verifier(hashVersion);\n        }\n\n        public void Reset()\n        {\n            this.index = -1;\n            this.HashVersion = 0;\n        }\n    }\n\n    /// <summary>\n    /// Computes hash version candidates and verifies cached versions for PKG2 files.\n    /// </summary>\n    public interface IPkg2HashVersionCalc\n    {\n        IReadOnlyList<uint> CalcCandidates(uint hash1, uint hash2);\n        bool Verify(uint hash1, uint hash2, uint hashVersion);\n    }\n\n    /// <summary>\n    /// PKG2 hash version calculation for KMST 1196 (V1).\n    /// </summary>\n    public sealed class Pkg2HashVersionCalcV1 : IPkg2HashVersionCalc\n    {\n        public IReadOnlyList<uint> CalcCandidates(uint hash1, uint hash2)\n        {\n            return new[] { ROL(hash1, 7) ^ hash2 };\n        }\n\n        public bool Verify(uint hash1, uint hash2, uint hashVersion)\n        {\n            return hashVersion == (ROL(hash1, 7) ^ hash2);\n        }\n    }\n\n    /// <summary>\n    /// PKG2 hash version calculation for KMST 1197 (V2). Uses backtrack solver.\n    /// </summary>\n    public sealed class Pkg2HashVersionCalcV2 : IPkg2HashVersionCalc\n    {\n        public IReadOnlyList<uint> CalcCandidates(uint hash1, uint hash2)\n        {\n            List<uint> results = new List<uint>();\n            Pkg2BacktrackSolver.BacktrackParameters p = new Pkg2BacktrackSolver.BacktrackParameters\n            {\n                Hash1 = hash1,\n                LowBitLen = 5,\n                Target = hash2,\n                Carries = stackalloc uint[33],\n                LhsBits = stackalloc uint[32],\n                Validator = (v) => Verify(hash1, hash2, v),\n                Results = results,\n            };\n\n            for (uint sCandidate = 0; sCandidate < 32; sCandidate++)\n            {\n                p.Carries.Clear();\n                p.LhsBits.Clear();\n                p.S = (int)sCandidate;\n                p.LowBits = sCandidate;\n                Pkg2BacktrackSolver.Solve(p, 0, 0);\n            }\n            return results;\n        }\n\n        public bool Verify(uint hash1, uint hash2, uint hashVersion)\n        {\n            uint lt = ROL(hash1 ^ (hashVersion + Pkg2BacktrackSolver.Magic), (int)(hashVersion & 0x1F));\n            return (lt ^ hashVersion) == hash2;\n        }\n    }\n\n    /// <summary>\n    /// PKG2 hash version calculation for KMST 1198 (V3). Uses backtrack solver.\n    /// </summary>\n    public sealed class Pkg2HashVersionCalcV3 : IPkg2HashVersionCalc\n    {\n        public IReadOnlyList<uint> CalcCandidates(uint hash1, uint hash2)\n        {\n            List<uint> results = new List<uint>();\n            Pkg2BacktrackSolver.BacktrackParameters p = new Pkg2BacktrackSolver.BacktrackParameters\n            {\n                Hash1 = hash1,\n                LowBitLen = 4,\n                Target = (~hash2) ^ hash1,\n                Carries = stackalloc uint[33],\n                LhsBits = stackalloc uint[32],\n                Validator = (v) => Verify(hash1, hash2, v),\n                Results = results,\n            };\n\n            for (uint sCandidate = 0; sCandidate < 16; sCandidate++)\n            {\n                p.Carries.Clear();\n                p.LhsBits.Clear();\n                p.S = (int)(sCandidate + (hash1 & 0xF));\n                p.LowBits = sCandidate;\n                Pkg2BacktrackSolver.Solve(p, 0, 0);\n            }\n            return results;\n        }\n\n        public bool Verify(uint hash1, uint hash2, uint hashVersion)\n        {\n            uint lt = ROL(hash1 ^ (hashVersion + Pkg2BacktrackSolver.Magic), (int)((hashVersion & 0xF) + (hash1 & 0xF)));\n            return ~(lt ^ hashVersion ^ hash1) == hash2;\n        }\n    }\n\n    /// <summary>\n    /// PKG2 hash version calculation for KMST 1199 (V4). Uses parallel brute-force with SIMD.\n    /// </summary>\n    public class Pkg2HashVersionCalcV4 : IPkg2HashVersionCalc\n    {\n        private const uint magic = Pkg2BacktrackSolver.Magic;\n\n        public IReadOnlyList<uint> CalcCandidates(uint hash1, uint hash2)\n        {\n            uint target = ~hash2;\n            return CalcCandidatesCore(hash1, target);\n        }\n\n        protected IReadOnlyList<uint> CalcCandidatesCore(uint hash1, uint target)\n        {\n            uint hash1Low4 = hash1 & 0xF;\n\n            List<uint> results = new List<uint>();\n            object lockObj = new object();\n\n            Parallel.For(0, 256, (int chunk) =>\n            {\n                uint start = (uint)chunk << 24;\n                uint count = 1u << 24;\n\n#if NET6_0_OR_GREATER\n                if (Avx2.IsSupported)\n                {\n                    Vector256<uint> hash1Vec = Vector256.Create(hash1);\n                    Vector256<uint> hash1Low4Vec = Vector256.Create(hash1Low4);\n                    Vector256<uint> targetVec = Vector256.Create(target);\n\n                    var hashVersion = Avx2.Add(Vector256.Create((uint)chunk << 24), Vector256.Create(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u));\n\n                    for (uint i = 0; i < count; i += 8)\n                    {\n                        // preHash = hash1 ^ hashVersion\n                        Vector256<uint> preHash = Avx2.Xor(hash1Vec, hashVersion);\n                        // mixedHash = Mix(preHash ^ 0x6D4C3B2A) ^ 0x91E10DA5\n                        Vector256<uint> mixedHash = Avx2.Xor(preHash, Vector256.Create(0x6D4C3B2Au));\n                        mixedHash = Avx2.Xor(mixedHash, Avx2.ShiftRightLogical(mixedHash, 16));\n                        mixedHash = Avx2.MultiplyLow(mixedHash, Vector256.Create(0x7FEB352Du));\n                        mixedHash = Avx2.Xor(mixedHash, Avx2.ShiftRightLogical(mixedHash, 15));\n                        mixedHash = Avx2.MultiplyLow(mixedHash, Vector256.Create(0x846CA68Bu));\n                        mixedHash = Avx2.Xor(mixedHash, Avx2.ShiftRightLogical(mixedHash, 16));\n                        mixedHash = Avx2.Xor(mixedHash, Vector256.Create(0x91E10DA5u));\n\n                        // lt = hash1 ^ ((ushort)mixedHash + hashVersion + magic)\n                        Vector256<uint> lt = Avx2.And(Vector256.Create(0xFFFFu), mixedHash);\n                        lt = Avx2.Add(Avx2.Add(lt, hashVersion), Vector256.Create(magic));\n                        lt = Avx2.Xor(hash1Vec, lt);\n                        // rt = ((mixedHash ^ hashVersion) & 0xF) + hash1Low4\n                        Vector256<uint> rt = Avx2.Xor(mixedHash, hashVersion);\n                        rt = Avx2.And(rt, Vector256.Create(0xFu));\n                        rt = Avx2.Add(rt, hash1Low4Vec);\n                        // lt = ROL(lt, rt)\n                        lt = Avx2.Or(Avx2.ShiftLeftLogicalVariable(lt, rt),\n                                Avx2.ShiftRightLogicalVariable(lt, Avx2.Subtract(Vector256.Create(32u), rt)));\n\n                        // (lt ^ (preHash + mixedHash)) == target\n                        Vector256<uint> actual = Avx2.Xor(lt, Avx2.Add(preHash, mixedHash));\n                        Vector256<uint> cmp = Avx2.CompareEqual(actual, targetVec);\n                        int mask = Avx2.MoveMask(cmp.AsByte());\n\n                        if (mask != 0)\n                        {\n                            for (int lane = 0; lane < 8; lane++)\n                            {\n                                if ((mask & (0xF << (lane * 4))) != 0)\n                                {\n                                    lock (lockObj)\n                                    {\n                                        results.Add(hashVersion.GetElement(lane));\n                                    }\n                                }\n                            }\n                        }\n\n                        hashVersion = Avx2.Add(hashVersion, Vector256.Create(8u));\n                    }\n                }\n                else if (Sse41.IsSupported)\n                {\n                    Vector128<uint> hash1Vec = Vector128.Create(hash1);\n                    Vector128<uint> hash1Low4Vec = Vector128.Create(hash1Low4);\n                    Vector128<uint> targetVec = Vector128.Create(target);\n\n                    var hashVersion = Sse2.Add(Vector128.Create(start), Vector128.Create(0u, 1u, 2u, 3u));\n\n                    for (uint i = 0; i < count; i += 4)\n                    {\n                        // preHash = hash1 ^ hashVersion\n                        Vector128<uint> preHash = Sse2.Xor(hash1Vec, hashVersion);\n                        // mixedHash = Mix(preHash ^ 0x6D4C3B2A) ^ 0x91E10DA5\n                        Vector128<uint> mixedHash = Sse2.Xor(preHash, Vector128.Create(0x6D4C3B2Au));\n                        mixedHash = Sse2.Xor(mixedHash, Sse2.ShiftRightLogical(mixedHash, 16));\n                        mixedHash = Sse41.MultiplyLow(mixedHash, Vector128.Create(0x7FEB352Du));\n                        mixedHash = Sse2.Xor(mixedHash, Sse2.ShiftRightLogical(mixedHash, 15));\n                        mixedHash = Sse41.MultiplyLow(mixedHash, Vector128.Create(0x846CA68Bu));\n                        mixedHash = Sse2.Xor(mixedHash, Sse2.ShiftRightLogical(mixedHash, 16));\n                        mixedHash = Sse2.Xor(mixedHash, Vector128.Create(0x91E10DA5u));\n\n                        // lt = hash1 ^ ((ushort)mixedHash + hashVersion + magic)\n                        Vector128<uint> lt = Sse2.And(Vector128.Create(0xFFFFu), mixedHash);\n                        lt = Sse2.Add(Sse2.Add(lt, hashVersion), Vector128.Create(magic));\n                        lt = Sse2.Xor(hash1Vec, lt);\n                        // rt = ((mixedHash ^ hashVersion) & 0xF) + hash1Low4\n                        Vector128<uint> rt = Sse2.Xor(mixedHash, hashVersion);\n                        rt = Sse2.And(rt, Vector128.Create(0xFu));\n                        rt = Sse2.Add(rt, hash1Low4Vec);\n\n                        // lt = ROL(lt, rt) — no vpsllvd/vpsrlvd in SSE4.1, variable ROL per-lane\n                        lt = Vector128.Create(\n                            ROL(lt.GetElement(0), (int)rt.GetElement(0)),\n                            ROL(lt.GetElement(1), (int)rt.GetElement(1)),\n                            ROL(lt.GetElement(2), (int)rt.GetElement(2)),\n                            ROL(lt.GetElement(3), (int)rt.GetElement(3)));\n\n                        // (lt ^ (preHash + mixedHash)) == target\n                        Vector128<uint> actual = Sse2.Xor(lt, Sse2.Add(preHash, mixedHash));\n                        Vector128<uint> cmp = Sse2.CompareEqual(actual, targetVec);\n                        int mask = Sse2.MoveMask(cmp.AsByte());\n\n                        if (mask != 0)\n                        {\n                            for (int lane = 0; lane < 4; lane++)\n                            {\n                                if ((mask & (0xF << (lane * 4))) != 0)\n                                {\n                                    lock (lockObj)\n                                    {\n                                        results.Add(hashVersion.GetElement(lane));\n                                    }\n                                }\n                            }\n                        }\n\n                        hashVersion = Sse2.Add(hashVersion, Vector128.Create(4u));\n                    }\n                }\n                else\n                {\n#endif\n                    for (uint i = 0; i < count; i++)\n                    {\n                        uint hashVersion = start + i;\n                        uint preHash = hash1 ^ hashVersion;\n                        uint mixedHash = Mix(preHash ^ 0x6D4C3B2A) ^ 0x91E10DA5;\n                        uint lt = ROL(hash1 ^ ((ushort)mixedHash + hashVersion + magic), (int)(((mixedHash ^ hashVersion) & 0xF) + hash1Low4));\n                        if ((lt ^ (preHash + mixedHash)) == target)\n                        {\n                            lock (lockObj)\n                            {\n                                results.Add(hashVersion);\n                            }\n                        }\n                    }\n#if NET6_0_OR_GREATER\n                }\n#endif\n            });\n\n            return results;\n        }\n\n        public bool Verify(uint hash1, uint hash2, uint hashVersion)\n        {\n            uint preHash = hash1 ^ hashVersion;\n            uint mixedHash = Mix(preHash ^ 0x6D4C3B2A) ^ 0x91E10DA5;\n            uint lt = ROL(hash1 ^ ((ushort)mixedHash + hashVersion + magic), (int)(((mixedHash ^ hashVersion) & 0xF) + (hash1 & 0xF)));\n            return (lt ^ (preHash + mixedHash)) == ~hash2;\n        }\n    }\n\n    /// <summary>\n    /// PKG2 hash version calculation for KMST 1200 (V5). Similar to V4.\n    /// </summary>\n    public sealed class Pkg2HashVersionCalcV5 : Pkg2HashVersionCalcV4, IPkg2HashVersionCalc\n    {\n        private const uint magic = Pkg2BacktrackSolver.Magic;\n        private const uint magicV5 = 0x2A2C818B;\n\n        public new IReadOnlyList<uint> CalcCandidates(uint hash1, uint hash2)\n        {\n            uint target = hash2 ^ magicV5;\n            return CalcCandidatesCore(hash1, target);\n        }\n\n        public new bool Verify(uint hash1, uint hash2, uint hashVersion)\n        {\n            uint preHash = hash1 ^ hashVersion;\n            uint mixedHash = Mix(preHash ^ 0x6D4C3B2A) ^ 0x91E10DA5;\n            uint lt = ROL(hash1 ^ ((ushort)mixedHash + hashVersion + magic), (int)(((mixedHash ^ hashVersion) & 0xF) + (hash1 & 0xF)));\n            return (lt ^ (preHash + mixedHash) ^ magicV5) == hash2;\n        }\n    }\n\n    /// <summary>\n    /// Shared backtrack solver used by KMST 1197-1198 hash version calculations.\n    /// </summary>\n    // This function is originally generated by google AI.\n    // Refactor by Kagamia.\n    internal static class Pkg2BacktrackSolver\n    {\n        public const uint Magic = 0x1A2B3C4D;\n\n        public static void Solve(in BacktrackParameters p, int bitIdx, uint vHash)\n        {\n            if (bitIdx == 32)\n            {\n                // full verify\n                if (p.Validator(vHash))\n                {\n                    p.Results.Add(vHash);\n                }\n                return;\n            }\n\n            // initial constraints for the lower bits\n            uint start, end;\n            if (bitIdx < p.LowBitLen)\n            {\n                start = end = (p.LowBits >> bitIdx) & 1;\n            }\n            else\n            {\n                start = 0;\n                end = 1;\n            }\n\n            for (uint vBit = start; vBit <= end; vBit++)\n            {\n                // backward check\n                int prevLhsIdx = (bitIdx - p.S + 32) & 0x1f;\n                if (prevLhsIdx < bitIdx)\n                {\n                    uint v_xor_h2 = vBit ^ ((p.Target >> bitIdx) & 1);\n                    if (v_xor_h2 != p.LhsBits[prevLhsIdx]) continue;\n                }\n\n                uint sum = vBit + ((Magic >> bitIdx) & 1) + p.Carries[bitIdx];\n                uint currentLhsBit = (sum ^ (p.Hash1 >> bitIdx)) & 1;\n\n                // forward check\n                int futureVIdx = (bitIdx + p.S) & 0x1f;\n                if (futureVIdx <= bitIdx)\n                {\n                    uint knownVBit = (uint)((vHash >> futureVIdx) & 1);\n                    uint targetV_xor_H2 = knownVBit ^ ((p.Target >> futureVIdx) & 1);\n                    if (currentLhsBit != targetV_xor_H2) continue;\n                }\n                else if (futureVIdx < p.LowBitLen)\n                {\n                    uint knownVBit = (uint)((p.LowBits >> futureVIdx) & 1);\n                    uint targetV_xor_H2 = knownVBit ^ ((p.Target >> futureVIdx) & 1);\n                    if (currentLhsBit != targetV_xor_H2) continue;\n                }\n\n                p.LhsBits[bitIdx] = currentLhsBit;\n                p.Carries[bitIdx + 1] = sum >> 1;\n                Solve(p, bitIdx + 1, vHash | (vBit << bitIdx));\n            }\n        }\n\n        public ref struct BacktrackParameters\n        {\n            public uint Hash1;\n            public int S;\n            public int LowBitLen;\n            public uint LowBits;\n            public uint Target;\n            public Span<uint> Carries;\n            public Span<uint> LhsBits;\n            public Func<uint, bool> Validator;\n            public List<uint> Results;\n        }\n    }\n\n    #endregion\n\n    #region Shared detection helpers\n\n    public static class WzVersionDetectHelper\n    {\n        public static bool FastDetectWithPreReadNodes(Wz_File wzFile, WzPreReadResult preReadResult,\n            IWzVersionIterator iterator, OffsetCalcFactory createOffsetCalc)\n        {\n            var nodes = preReadResult.Nodes;\n            if (nodes.Count == 0)\n                return false;\n\n            var imageSizes = new SizeRange[nodes.Count];\n            long fileLen = wzFile.FileStream.Length;\n\n            while (iterator.TryGetNextVersion())\n            {\n                var calc = createOffsetCalc(wzFile, iterator.HashVersion);\n\n                if (!ValidateEntryCountsIfPkg2(preReadResult, calc))\n                    continue;\n\n                if (ValidateOffsets(nodes, imageSizes, fileLen, preReadResult.DirStartPosition, preReadResult.DirEndPosition, calc))\n                {\n                    wzFile.Header.WzVersion = iterator.WzVersion;\n                    wzFile.Header.HashVersion = iterator.HashVersion;\n                    wzFile.Header.VersionChecked = true;\n                    wzFile.OffsetCalc = calc;\n                    return true;\n                }\n            }\n\n            return false;\n        }\n\n        public static bool FastDetectSingleVersion(Wz_File wzFile, WzPreReadResult preReadResult,\n            int wzVersion, uint hashVersion, OffsetCalcFactory createOffsetCalc)\n        {\n            var nodes = preReadResult.Nodes;\n            if (nodes.Count == 0)\n                return false;\n\n            var imageSizes = new SizeRange[nodes.Count];\n            long fileLen = wzFile.FileStream.Length;\n\n            var calc = createOffsetCalc(wzFile, hashVersion);\n\n            if (ValidateEntryCountsIfPkg2(preReadResult, calc)\n                && ValidateOffsets(nodes, imageSizes, fileLen, preReadResult.DirStartPosition, preReadResult.DirEndPosition, calc))\n            {\n                wzFile.Header.WzVersion = wzVersion;\n                wzFile.Header.HashVersion = hashVersion;\n                wzFile.Header.VersionChecked = true;\n                wzFile.OffsetCalc = calc;\n                return true;\n            }\n\n            return false;\n        }\n\n        private static bool ValidateEntryCountsIfPkg2(WzPreReadResult preReadResult, IWzImageOffsetCalc calc)\n        {\n            if (preReadResult.Pkg2DirEntryCounts == null || calc is not IPkg2ImageOffsetCalc pkg2Calc)\n                return true;\n\n            foreach (var ec in preReadResult.Pkg2DirEntryCounts)\n            {\n                if (pkg2Calc.DecryptEntryCount(ec.EncryptedEntryCount) != ec.ActualEntryCount)\n                    return false;\n            }\n            return true;\n        }\n\n        private static bool ValidateOffsets(List<WzPreReadNodeInfo> nodes, SizeRange[] imageSizes,\n            long fileLen, long dirStartPosition, long dirEndPosition, IWzImageOffsetCalc calc)\n        {\n            int imgCount = 0;\n\n            foreach (var node in nodes)\n            {\n                uint offs = calc.CalcOffset(node.HashedOffsetPosition, node.HashedOffset);\n\n                if (node.NodeType == 0x04 || node.NodeType == 0x02)\n                {\n                    if (offs < dirEndPosition || offs + node.DataLength > fileLen)\n                        return false;\n                    imageSizes[imgCount++] = new SizeRange { Start = offs, End = offs + node.DataLength };\n                }\n                else if (node.NodeType == 0x03)\n                {\n                    if (offs < dirStartPosition || offs + 1 > dirEndPosition)\n                        return false;\n                    imageSizes[imgCount++] = new SizeRange { Start = offs, End = offs + 1 };\n                }\n            }\n\n            if (imgCount > 1)\n            {\n                Array.Sort(imageSizes, 0, imgCount);\n                for (int i = 1; i < imgCount; i++)\n                {\n                    if (imageSizes[i - 1].End > imageSizes[i].Start)\n                        return false;\n                }\n            }\n\n            return true;\n        }\n\n        private struct SizeRange : IComparable<SizeRange>\n        {\n            public long Start;\n            public long End;\n\n            public int CompareTo(SizeRange sr)\n            {\n                int result = this.Start.CompareTo(sr.Start);\n                if (result == 0)\n                    result = this.End.CompareTo(sr.End);\n                return result;\n            }\n        }\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Cryptography/ChaCha20CryptoTransform.cs",
    "content": "﻿// port from https://github.com/mcraiha/CSharp-ChaCha20-NetStandard/blob/master/src/CSChaCha20.cs\n\n/*\n * Copyright (c) 2015, 2018 Scott Bennett\n *           (c) 2018-2023 Kaarlo Räihä\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\nusing System;\nusing System.Collections.Generic;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nnamespace WzComparerR2.WzLib.Cryptography\n{\n    public class ChaCha20CryptoTransform : ICryptoTransform, IDisposable\n    {\n        /// <summary>\n        /// Only allowed key lenght in bytes\n        /// </summary>\n        public const int AllowedKeyLength = 32;\n\n        /// <summary>\n        /// Only allowed nonce lenght in bytes\n        /// </summary>\n        public const int AllowedNonceLength = 12;\n\n        /// <summary>\n        /// How many bytes are processed per loop\n        /// </summary>\n        public const int ProcessBytesAtTime = 64;\n\n        public const int StateLength = 16;\n\n        public int InputBlockSize => ProcessBytesAtTime;\n\n        public int OutputBlockSize => ProcessBytesAtTime;\n\n        public bool CanTransformMultipleBlocks => true;\n\n        public bool CanReuseTransform => false;\n\n        /// <summary>\n        /// The ChaCha20 state (aka \"context\")\n        /// </summary>\n        private readonly uint[] state = new uint[StateLength];\n\n        /// <summary>\n        /// Determines if the objects in this class have been disposed of. Set to true by the Dispose() method.\n        /// </summary>\n        private bool isDisposed = false;\n\n        public ChaCha20CryptoTransform(ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce, uint counter)\n        {\n            this.KeySetup(key);\n            this.IvSetup(nonce, counter);\n        }\n\n        /// <summary>\n        /// The ChaCha20 state (aka \"context\"). Read-Only.\n        /// </summary>\n        public uint[] State => this.state;\n\n        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)\n        {\n            this.ValidateTransformBlock(inputBuffer, inputOffset, inputCount);\n            int inputBlocks = Math.DivRem(inputCount, this.InputBlockSize, out int rem);\n            int outputCount = inputBlocks * this.OutputBlockSize;\n            if (inputBlocks == 0 || rem != 0)\n            {\n                throw new ArgumentOutOfRangeException(nameof(inputCount));\n            }\n            if (outputBuffer == null)\n            {\n                throw new ArgumentNullException(nameof(outputBuffer));\n            }\n            if (outputCount > outputBuffer.Length - outputOffset)\n            {\n                throw new ArgumentOutOfRangeException(nameof(outputBuffer));\n            }\n            this.TransformBlock(inputBuffer.AsSpan(inputOffset, inputCount - rem), outputBuffer.AsSpan(outputOffset, outputCount), out int byteWritten);\n            return byteWritten;\n        }\n\n        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)\n        {\n            this.ValidateTransformBlock(inputBuffer, inputOffset, inputCount);\n            if (inputCount == 0)\n            {\n                return Array.Empty<byte>();\n            }\n\n            int inputBlocks = Math.DivRem(inputCount, this.InputBlockSize, out int rem);\n            int outputCount = inputBlocks * this.OutputBlockSize;\n            byte[] outputBuffer = new byte[outputCount];\n            this.TransformBlock(inputBuffer.AsSpan(inputOffset, inputCount - rem), outputBuffer.AsSpan(), out _);\n            return outputBuffer;\n        }\n\n        /// <summary>\n        /// Set up the ChaCha state with the given key. A 32-byte key is required and enforced.\n        /// </summary>\n        /// <param name=\"key\">\n        /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers\n        /// </param>\n        private void KeySetup(ReadOnlySpan<byte> key)\n        {\n            if (key.Length != AllowedKeyLength)\n            {\n                throw new ArgumentException($\"Key length must be {AllowedKeyLength}. Actual: {key.Length}\");\n            }\n\n            state[4] = Util.U8To32Little(key, 0);\n            state[5] = Util.U8To32Little(key, 4);\n            state[6] = Util.U8To32Little(key, 8);\n            state[7] = Util.U8To32Little(key, 12);\n\n            // These are the same constants defined in the reference implementation.\n            // http://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c\n            ReadOnlySpan<byte> constants = (key.Length == AllowedKeyLength) ? \"expand 32-byte k\"u8 : \"expand 16-byte k\"u8;\n            int keyIndex = key.Length - 16;\n\n            state[8] = Util.U8To32Little(key, keyIndex + 0);\n            state[9] = Util.U8To32Little(key, keyIndex + 4);\n            state[10] = Util.U8To32Little(key, keyIndex + 8);\n            state[11] = Util.U8To32Little(key, keyIndex + 12);\n\n            state[0] = Util.U8To32Little(constants, 0);\n            state[1] = Util.U8To32Little(constants, 4);\n            state[2] = Util.U8To32Little(constants, 8);\n            state[3] = Util.U8To32Little(constants, 12);\n        }\n\n        /// <summary>\n        /// Set up the ChaCha state with the given nonce (aka Initialization Vector or IV) and block counter. A 12-byte nonce and a 4-byte counter are required.\n        /// </summary>\n        /// <param name=\"nonce\">\n        /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers\n        /// </param>\n        /// <param name=\"counter\">\n        /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer\n        /// </param>\n        private void IvSetup(ReadOnlySpan<byte> nonce, uint counter)\n        {\n            if (nonce.Length != AllowedNonceLength)\n            {\n                // There has already been some state set up. Clear it before exiting.\n                this.Dispose();\n                throw new ArgumentException($\"Nonce length must be {AllowedNonceLength}. Actual: {nonce.Length}\");\n            }\n\n            state[12] = counter;\n            state[13] = Util.U8To32Little(nonce, 0);\n            state[14] = Util.U8To32Little(nonce, 4);\n            state[15] = Util.U8To32Little(nonce, 8);\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount)\n        {\n            if (inputBuffer == null)\n            {\n                throw new ArgumentNullException(nameof(inputBuffer));\n            }\n            if ((uint)inputCount > inputBuffer.Length)\n            {\n                throw new ArgumentOutOfRangeException(nameof(inputCount));\n            }\n            if (inputOffset < 0)\n            {\n                throw new ArgumentOutOfRangeException(nameof(inputOffset));\n            }\n            if (inputBuffer.Length - inputCount < inputOffset)\n            {\n                throw new ArgumentException(\"Offset and length were out of bounds.\");\n            }\n        }\n\n        /// <summary>\n        /// Encrypt or decrypt an arbitrary-length byte array (input), writing the resulting byte array to the output buffer. The number of bytes to read from the input buffer is determined by numBytes.\n        /// </summary>\n        /// <param name=\"output\">Output byte array</param>\n        /// <param name=\"input\">Input byte array</param>\n        /// <param name=\"numBytes\">How many bytes to process</param>\n        /// <param name=\"simdMode\">Chosen SIMD mode (default is auto-detect)</param>\n        private unsafe void TransformBlock(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten)\n        {\n            if (isDisposed)\n            {\n                throw new ObjectDisposedException(\"state\", \"The ChaCha state has been disposed\");\n            }\n\n            Span<uint> x = stackalloc uint[StateLength];    // Working buffer\n            Span<byte> tmp = stackalloc byte[ProcessBytesAtTime];  // Temporary buffer\n\n            bytesWritten = 0;\n            while (input.Length >= ProcessBytesAtTime)\n            {\n                UpdateStateAndGenerateTemporaryBuffer(this.state, x, tmp);\n\n#if NET8_0_OR_GREATER\n                if (Vector512.IsHardwareAccelerated)\n                {\n                    // 1 x 64 bytes\n                    Vector512<byte> inputV = Vector512.Create<byte>(input);\n                    Vector512<byte> tmpV = Vector512.Create<byte>(tmp);\n                    Vector512<byte> outputV = inputV ^ tmpV;\n                    outputV.CopyTo(output);\n                }\n                else\n#endif\n#if NET6_0_OR_GREATER\n                if (Avx2.IsSupported)\n                {\n                    // 2 x 32 bytes\n                    Vector256<byte> inputV, tmpV;\n                    fixed (byte* pInput = input)\n                    fixed (byte* pTmp = tmp)\n                    fixed (byte* pOutput = output)\n                    {\n                        inputV = Avx2.LoadVector256(pInput);\n                        tmpV = Avx2.LoadVector256(pTmp);\n                        Avx2.Store(pOutput, Avx2.Xor(inputV, tmpV));\n\n                        inputV = Avx2.LoadVector256(pInput + 32);\n                        tmpV = Avx2.LoadVector256(pTmp + 32);\n                        Avx2.Store(pOutput + 32, Avx2.Xor(inputV, tmpV));\n                    }\n                }\n                else if (Sse2.IsSupported)\n                {\n                    // 4 x 16 bytes\n                    Vector128<byte> inputV, tmpV;\n                    fixed (byte* pInput = input)\n                    fixed (byte* pTmp = tmp)\n                    fixed (byte* pOutput = output)\n                    {\n                        inputV = Sse2.LoadVector128(pInput);\n                        tmpV = Sse2.LoadVector128(pTmp);\n                        Sse2.Store(pOutput, Sse2.Xor(inputV, tmpV));\n\n                        inputV = Sse2.LoadVector128(pInput + 16);\n                        tmpV = Sse2.LoadVector128(pTmp + 16);\n                        Sse2.Store(pOutput + 16, Sse2.Xor(inputV, tmpV));\n\n                        inputV = Sse2.LoadVector128(pInput + 32);\n                        tmpV = Sse2.LoadVector128(pTmp + 32);\n                        Sse2.Store(pOutput + 32, Sse2.Xor(inputV, tmpV));\n\n                        inputV = Sse2.LoadVector128(pInput + 48);\n                        tmpV = Sse2.LoadVector128(pTmp + 48);\n                        Sse2.Store(pOutput + 48, Sse2.Xor(inputV, tmpV));\n                    }\n                }\n                else\n#endif\n                {\n                    // Small unroll\n                    ReadOnlySpan<uint> inputV = MemoryMarshal.Cast<byte, uint>(input);\n                    ReadOnlySpan<uint> tmpV = MemoryMarshal.Cast<byte, uint>(tmp);\n                    Span<uint> outputV = MemoryMarshal.Cast<byte, uint>(output);\n                    for (int i = 0, j = ProcessBytesAtTime / 4; i < j; i++)\n                    {\n                        outputV[i] = inputV[i] ^ tmpV[i];\n                    }\n                }\n\n                input = input.Slice(ProcessBytesAtTime);\n                output = output.Slice(ProcessBytesAtTime);\n                bytesWritten += ProcessBytesAtTime;\n            }\n        }\n\n        #region Destructor and Disposer\n        /// <summary>\n        /// Clear and dispose of the internal state. The finalizer is only called if Dispose() was never called on this cipher.\n        /// </summary>\n        ~ChaCha20CryptoTransform()\n        {\n            Dispose(false);\n        }\n\n        /// <summary>\n        /// Clear and dispose of the internal state. Also request the GC not to call the finalizer, because all cleanup has been taken care of.\n        /// </summary>\n        public void Dispose()\n        {\n            Dispose(true);\n            /*\n                * The Garbage Collector does not need to invoke the finalizer because Dispose(bool) has already done all the cleanup needed.\n                */\n            GC.SuppressFinalize(this);\n        }\n\n        /// <summary>\n        /// This method should only be invoked from Dispose() or the finalizer. This handles the actual cleanup of the resources.\n        /// </summary>\n        /// <param name=\"disposing\">\n        /// Should be true if called by Dispose(); false if called by the finalizer\n        /// </param>\n        private void Dispose(bool disposing)\n        {\n            if (!isDisposed)\n            {\n                if (disposing)\n                {\n                    /* Cleanup managed objects by calling their Dispose() methods */\n                }\n\n                /* Cleanup any unmanaged objects here */\n                Array.Clear(state, 0, StateLength);\n            }\n\n            isDisposed = true;\n        }\n        #endregion // Destructor and Disposer\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private static void UpdateStateAndGenerateTemporaryBuffer(Span<uint> stateToModify, Span<uint> workingBuffer, Span<byte> temporaryBuffer)\n        {\n            // Copy state to working buffer\n            stateToModify.Slice(0, StateLength).CopyTo(workingBuffer);\n\n            for (int i = 0; i < 10; i++)\n            {\n                QuarterRound(workingBuffer, 0, 4, 8, 12);\n                QuarterRound(workingBuffer, 1, 5, 9, 13);\n                QuarterRound(workingBuffer, 2, 6, 10, 14);\n                QuarterRound(workingBuffer, 3, 7, 11, 15);\n\n                QuarterRound(workingBuffer, 0, 5, 10, 15);\n                QuarterRound(workingBuffer, 1, 6, 11, 12);\n                QuarterRound(workingBuffer, 2, 7, 8, 13);\n                QuarterRound(workingBuffer, 3, 4, 9, 14);\n            }\n\n            for (int i = 0; i < StateLength; i++)\n            {\n                Util.ToBytes(temporaryBuffer, Util.Add(workingBuffer[i], stateToModify[i]), 4 * i);\n            }\n\n            stateToModify[12] = Util.AddOne(stateToModify[12]);\n            if (stateToModify[12] <= 0)\n            {\n                /* Stopping at 2^70 bytes per nonce is the user's responsibility */\n                stateToModify[13] = Util.AddOne(stateToModify[13]);\n            }\n        }\n\n        /// <summary>\n        /// The ChaCha Quarter Round operation. It operates on four 32-bit unsigned integers within the given buffer at indices a, b, c, and d.\n        /// </summary>\n        /// <remarks>\n        /// The ChaCha state does not have four integer numbers: it has 16. So the quarter-round operation works on only four of them -- hence the name. Each quarter round operates on four predetermined numbers in the ChaCha state.\n        /// See <a href=\"https://tools.ietf.org/html/rfc7539#page-4\">ChaCha20 Spec Sections 2.1 - 2.2</a>.\n        /// </remarks>\n        /// <param name=\"x\">A ChaCha state (vector). Must contain 16 elements.</param>\n        /// <param name=\"a\">Index of the first number</param>\n        /// <param name=\"b\">Index of the second number</param>\n        /// <param name=\"c\">Index of the third number</param>\n        /// <param name=\"d\">Index of the fourth number</param>\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private static void QuarterRound(Span<uint> x, int a, int b, int c, int d)\n        {\n            x[a] = Util.Add(x[a], x[b]);\n            x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 16);\n\n            x[c] = Util.Add(x[c], x[d]);\n            x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 12);\n\n            x[a] = Util.Add(x[a], x[b]);\n            x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 8);\n\n            x[c] = Util.Add(x[c], x[d]);\n            x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7);\n        }\n\n        /// <summary>\n        /// Utilities that are used during compression\n        /// </summary>\n        private static class Util\n        {\n            /// <summary>\n            /// n-bit left rotation operation (towards the high bits) for 32-bit integers.\n            /// </summary>\n            /// <param name=\"v\"></param>\n            /// <param name=\"c\"></param>\n            /// <returns>The result of (v LEFTSHIFT c)</returns>\n            [MethodImpl(MethodImplOptions.AggressiveInlining)]\n            public static uint Rotate(uint v, int c)\n            {\n                unchecked\n                {\n                    return (v << c) | (v >> (32 - c));\n                }\n            }\n\n            /// <summary>\n            /// Unchecked integer exclusive or (XOR) operation.\n            /// </summary>\n            /// <param name=\"v\"></param>\n            /// <param name=\"w\"></param>\n            /// <returns>The result of (v XOR w)</returns>\n            [MethodImpl(MethodImplOptions.AggressiveInlining)]\n            public static uint XOr(uint v, uint w)\n            {\n                return unchecked(v ^ w);\n            }\n\n            /// <summary>\n            /// Unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.\n            /// </summary>\n            /// <remarks>\n            /// See <a href=\"https://tools.ietf.org/html/rfc7539#page-4\">ChaCha20 Spec Section 2.1</a>.\n            /// </remarks>\n            /// <param name=\"v\"></param>\n            /// <param name=\"w\"></param>\n            /// <returns>The result of (v + w) modulo 2^32</returns>\n            [MethodImpl(MethodImplOptions.AggressiveInlining)]\n            public static uint Add(uint v, uint w)\n            {\n                return unchecked(v + w);\n            }\n\n            /// <summary>\n            /// Add 1 to the input parameter using unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.\n            /// </summary>\n            /// <remarks>\n            /// See <a href=\"https://tools.ietf.org/html/rfc7539#page-4\">ChaCha20 Spec Section 2.1</a>.\n            /// </remarks>\n            /// <param name=\"v\"></param>\n            /// <returns>The result of (v + 1) modulo 2^32</returns>\n            [MethodImpl(MethodImplOptions.AggressiveInlining)]\n            public static uint AddOne(uint v)\n            {\n                return unchecked(v + 1);\n            }\n\n            /// <summary>\n            /// Convert four bytes of the input buffer into an unsigned 32-bit integer, beginning at the inputOffset.\n            /// </summary>\n            /// <param name=\"p\"></param>\n            /// <param name=\"inputOffset\"></param>\n            /// <returns>An unsigned 32-bit integer</returns>\n            [MethodImpl(MethodImplOptions.AggressiveInlining)]\n            public static uint U8To32Little(ReadOnlySpan<byte> p, int inputOffset)\n            {\n                unchecked\n                {\n                    return ((uint)p[inputOffset]\n                        | ((uint)p[inputOffset + 1] << 8)\n                        | ((uint)p[inputOffset + 2] << 16)\n                        | ((uint)p[inputOffset + 3] << 24));\n                }\n            }\n\n            /// <summary>\n            /// Serialize the input integer into the output buffer. The input integer will be split into 4 bytes and put into four sequential places in the output buffer, starting at the outputOffset.\n            /// </summary>\n            /// <param name=\"output\"></param>\n            /// <param name=\"input\"></param>\n            /// <param name=\"outputOffset\"></param>\n            [MethodImpl(MethodImplOptions.AggressiveInlining)]\n            public static void ToBytes(Span<byte> output, uint input, int outputOffset)\n            {\n                unchecked\n                {\n                    output[outputOffset] = (byte)input;\n                    output[outputOffset + 1] = (byte)(input >> 8);\n                    output[outputOffset + 2] = (byte)(input >> 16);\n                    output[outputOffset + 3] = (byte)(input >> 24);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Cryptography/Snow2CryptoTransform.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Runtime.CompilerServices;\nusing System.Security.Cryptography;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.WzLib.Cryptography\n{\n    public class Snow2CryptoTransform : ICryptoTransform, IDisposable\n    {\n        public Snow2CryptoTransform(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, bool encrypting)\n        {\n            if (key.Length != 16 && key.Length != 32)\n                throw new ArgumentOutOfRangeException(nameof(key), \"Key size must be 16 or 32 bytes.\");\n            if (!iv.IsEmpty && iv.Length != 4)\n                throw new ArgumentOutOfRangeException(nameof(iv), \"Iv size must be 4 bytes.\");\n\n            this.encrypting = encrypting;\n            this.keyStream = new uint[16];\n            this.LoadKey(key, iv);\n            this.RefreshKeyStream();\n            this.curIndex = 0;\n        }\n\n        public int InputBlockSize => 4;\n\n        public int OutputBlockSize => 4;\n\n        public bool CanTransformMultipleBlocks => true;\n\n        public bool CanReuseTransform => false;\n\n        private bool encrypting;\n        private uint s15, s14, s13, s12, s11, s10, s9, s8, s7, s6, s5, s4, s3, s2, s1, s0;\n        private uint r1, r2;\n        private uint[] keyStream;\n        private int curIndex;\n\n        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)\n        {\n            this.ValidateTransformBlock(inputBuffer, inputOffset, inputCount);\n            int inputBlocks = Math.DivRem(inputCount, this.InputBlockSize, out int rem);\n            int outputCount = inputBlocks * this.OutputBlockSize;\n            if (inputBlocks == 0 || rem != 0)\n            {\n                throw new ArgumentOutOfRangeException(nameof(inputCount));\n            }\n            if (outputBuffer == null)\n            {\n                throw new ArgumentNullException(nameof(outputBuffer));\n            }\n            if (outputCount > outputBuffer.Length - outputOffset)\n            {\n                throw new ArgumentOutOfRangeException(nameof(outputBuffer));\n            }\n            this.TransformBlock(inputBuffer.AsSpan(inputOffset, inputCount - rem), outputBuffer.AsSpan(outputOffset, outputCount), out int byteWritten);\n            return byteWritten;\n        }\n\n        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)\n        {\n            this.ValidateTransformBlock(inputBuffer, inputOffset, inputCount);\n            if (inputCount == 0)\n            {\n                return Array.Empty<byte>();\n            }\n\n            int inputBlocks = Math.DivRem(inputCount, this.InputBlockSize, out int rem);\n            int outputCount = inputBlocks * this.OutputBlockSize;\n            byte[] outputBuffer = new byte[outputCount];\n            this.TransformBlock(inputBuffer.AsSpan(inputOffset, inputCount - rem), outputBuffer.AsSpan(), out _);\n            return outputBuffer;\n        }\n\n        public void Dispose()\n        {\n            this.Dispose(true);\n            GC.SuppressFinalize(this);\n        }\n\n        ~Snow2CryptoTransform()\n        {\n            this.Dispose(false);\n        }\n\n        protected void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (this.keyStream != null)\n                {\n                    this.s15 = 0;\n                    this.s14 = 0;\n                    this.s13 = 0;\n                    this.s12 = 0;\n                    this.s11 = 0;\n                    this.s10 = 0;\n                    this.s9 = 0;\n                    this.s8 = 0;\n                    this.s7 = 0;\n                    this.s6 = 0;\n                    this.s5 = 0;\n                    this.s4 = 0;\n                    this.s3 = 0;\n                    this.s2 = 0;\n                    this.s1 = 0;\n                    this.s0 = 0;\n                    this.r1 = 0;\n                    this.r2 = 0;\n                    Array.Clear(this.keyStream, 0, this.keyStream.Length);\n                    this.keyStream = null;\n                }\n            }\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount)\n        {\n            if (inputBuffer == null)\n            {\n                throw new ArgumentNullException(nameof(inputBuffer));\n            }\n            if ((uint)inputCount > inputBuffer.Length)\n            {\n                throw new ArgumentOutOfRangeException(nameof(inputCount));\n            }\n            if (inputOffset < 0)\n            {\n                throw new ArgumentOutOfRangeException(nameof(inputOffset));\n            }\n            if (inputBuffer.Length - inputCount < inputOffset)\n            {\n                throw new ArgumentException(\"Offset and length were out of bounds.\");\n            }\n        }\n\n        private void TransformBlock(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten)\n        {\n            ReadOnlySpan<uint> inputBlocks = MemoryMarshal.Cast<byte, uint>(input);\n            Span<uint> outputBlocks = MemoryMarshal.Cast<byte, uint>(output);\n            int i;\n            for (i = 0; i < inputBlocks.Length; i++)\n            {\n                if (this.encrypting)\n                {\n                    outputBlocks[i] = inputBlocks[i] + this.keyStream[this.curIndex];\n                }\n                else\n                {\n                    outputBlocks[i] = inputBlocks[i] - this.keyStream[this.curIndex];\n                }\n\n                this.curIndex++;\n                if (this.curIndex >= 16)\n                {\n                    this.RefreshKeyStream();\n                    this.curIndex = 0;\n                }\n            }\n            bytesWritten = i * 4;\n        }\n\n        #region Snow Cipher Algorithm\n        // port from https://github.com/ahti/fsbench/blob/master/src/codecs/ecrypt/snow2_fast.c\n        private void LoadKey(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)\n        {\n            ReadOnlySpan<sbyte> signedKey = MemoryMarshal.Cast<byte, sbyte>(key);\n\n            if (signedKey.Length == 16)\n            {\n                this.s15 = (uint)((signedKey[0] << 24) | (signedKey[1] << 16) | (signedKey[2] << 8) | (signedKey[3] << 0));\n                this.s14 = (uint)((signedKey[4] << 24) | (signedKey[5] << 16) | (signedKey[6] << 8) | (signedKey[7] << 0));\n                this.s13 = (uint)((signedKey[8] << 24) | (signedKey[9] << 16) | (signedKey[10] << 8) | (signedKey[11] << 0));\n                this.s12 = (uint)((signedKey[12] << 24) | (signedKey[13] << 16) | (signedKey[14] << 8) | (signedKey[15] << 0));\n                this.s11 = ~this.s15; /* bitwise inverse */\n                this.s10 = ~this.s14;\n                this.s9 = ~this.s13;\n                this.s8 = ~this.s12;\n                this.s7 = this.s15; /* just copy */\n                this.s6 = this.s14;\n                this.s5 = this.s13;\n                this.s4 = this.s12;\n                this.s3 = ~this.s15; /* bitwise inverse */\n                this.s2 = ~this.s14;\n                this.s1 = ~this.s13;\n                this.s0 = ~this.s12;\n            }\n            else\n            {\n                /* assume keysize=256 */\n                this.s15 = (uint)((signedKey[0] << 24) | (signedKey[1] << 16) | (signedKey[2] << 8) | (signedKey[3] << 0));\n                this.s14 = (uint)((signedKey[4] << 24) | (signedKey[5] << 16) | (signedKey[6] << 8) | (signedKey[7] << 0));\n                this.s13 = (uint)((signedKey[8] << 24) | (signedKey[9] << 16) | (signedKey[10] << 8) | (signedKey[11] << 0));\n                this.s12 = (uint)((signedKey[12] << 24) | (signedKey[13] << 16) | (signedKey[14] << 8) | (signedKey[15] << 0));\n                this.s11 = (uint)((signedKey[16] << 24) | (signedKey[17] << 16) | (signedKey[18] << 8) | (signedKey[19] << 0));\n                this.s10 = (uint)((signedKey[20] << 24) | (signedKey[21] << 16) | (signedKey[22] << 8) | (signedKey[23] << 0));\n                this.s9 = (uint)((signedKey[24] << 24) | (signedKey[25] << 16) | (signedKey[26] << 8) | (signedKey[27] << 0));\n                this.s8 = (uint)((signedKey[28] << 24) | (signedKey[29] << 16) | (signedKey[30] << 8) | (signedKey[31] << 0));\n                this.s7 = ~this.s15; /* bitwise inverse */\n                this.s6 = ~this.s14;\n                this.s5 = ~this.s13;\n                this.s4 = ~this.s12;\n                this.s3 = ~this.s11;\n                this.s2 = ~this.s10;\n                this.s1 = ~this.s9;\n                this.s0 = ~this.s8;\n            }\n\n            /* XOR IV values */\n            if (!iv.IsEmpty)\n            {\n                this.s15 ^= iv[0];\n                this.s12 ^= iv[1];\n                this.s10 ^= iv[2];\n                this.s9 ^= iv[3];\n            }\n\n            this.r1 = 0;\n            this.r2 = 0;\n\n            /* Do 32 initial clockings */\n            for (int i = 0; i < 2; i++)\n            {\n                uint outfrom_fsm, fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s15) ^ this.r2;\n                this.s0 = a_mul(this.s0) ^ this.s2 ^ ainv_mul(this.s11) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s5;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s0) ^ this.r2;\n                this.s1 = a_mul(this.s1) ^ this.s3 ^ ainv_mul(this.s12) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s6;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s1) ^ this.r2;\n                this.s2 = a_mul(this.s2) ^ this.s4 ^ ainv_mul(this.s13) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s7;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s2) ^ this.r2;\n                this.s3 = a_mul(this.s3) ^ this.s5 ^ ainv_mul(this.s14) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s8;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s3) ^ this.r2;\n                this.s4 = a_mul(this.s4) ^ this.s6 ^ ainv_mul(this.s15) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s9;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s4) ^ this.r2;\n                this.s5 = a_mul(this.s5) ^ this.s7 ^ ainv_mul(this.s0) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s10;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s5) ^ this.r2;\n                this.s6 = a_mul(this.s6) ^ this.s8 ^ ainv_mul(this.s1) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s11;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s6) ^ this.r2;\n                this.s7 = a_mul(this.s7) ^ this.s9 ^ ainv_mul(this.s2) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s12;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s7) ^ this.r2;\n                this.s8 = a_mul(this.s8) ^ this.s10 ^ ainv_mul(this.s3) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s13;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s8) ^ this.r2;\n                this.s9 = a_mul(this.s9) ^ this.s11 ^ ainv_mul(this.s4) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s14;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s9) ^ this.r2;\n                this.s10 = a_mul(this.s10) ^ this.s12 ^ ainv_mul(this.s5) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s15;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s10) ^ this.r2;\n                this.s11 = a_mul(this.s11) ^ this.s13 ^ ainv_mul(this.s6) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s0;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s11) ^ this.r2;\n                this.s12 = a_mul(this.s12) ^ this.s14 ^ ainv_mul(this.s7) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s1;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s12) ^ this.r2;\n                this.s13 = a_mul(this.s13) ^ this.s15 ^ ainv_mul(this.s8) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s2;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s13) ^ this.r2;\n                this.s14 = a_mul(this.s14) ^ this.s0 ^ ainv_mul(this.s9) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s3;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n\n                outfrom_fsm = (this.r1 + this.s14) ^ this.r2;\n                this.s15 = a_mul(this.s15) ^ this.s1 ^ ainv_mul(this.s10) ^ outfrom_fsm;\n                fsmtmp = this.r2 + this.s4;\n                this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n                this.r1 = fsmtmp;\n            }\n        }\n\n        private void RefreshKeyStream()\n        {\n            uint fsmtmp;\n\n            this.s0 = a_mul(this.s0) ^ this.s2 ^ ainv_mul(this.s11);\n            fsmtmp = this.r2 + this.s5;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[0] = (this.r1 + this.s0) ^ this.r2 ^ this.s1;\n\n            this.s1 = a_mul(this.s1) ^ this.s3 ^ ainv_mul(this.s12);\n            fsmtmp = this.r2 + this.s6;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[1] = (this.r1 + this.s1) ^ this.r2 ^ this.s2;\n\n            this.s2 = a_mul(this.s2) ^ this.s4 ^ ainv_mul(this.s13);\n            fsmtmp = this.r2 + this.s7;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[2] = (this.r1 + this.s2) ^ this.r2 ^ this.s3;\n\n            this.s3 = a_mul(this.s3) ^ this.s5 ^ ainv_mul(this.s14);\n            fsmtmp = this.r2 + this.s8;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[3] = (this.r1 + this.s3) ^ this.r2 ^ this.s4;\n\n            this.s4 = a_mul(this.s4) ^ this.s6 ^ ainv_mul(this.s15);\n            fsmtmp = this.r2 + this.s9;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[4] = (this.r1 + this.s4) ^ this.r2 ^ this.s5;\n\n            this.s5 = a_mul(this.s5) ^ this.s7 ^ ainv_mul(this.s0);\n            fsmtmp = this.r2 + this.s10;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[5] = (this.r1 + this.s5) ^ this.r2 ^ this.s6;\n\n            this.s6 = a_mul(this.s6) ^ this.s8 ^ ainv_mul(this.s1);\n            fsmtmp = this.r2 + this.s11;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[6] = (this.r1 + this.s6) ^ this.r2 ^ this.s7;\n\n            this.s7 = a_mul(this.s7) ^ this.s9 ^ ainv_mul(this.s2);\n            fsmtmp = this.r2 + this.s12;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[7] = (this.r1 + this.s7) ^ this.r2 ^ this.s8;\n\n            this.s8 = a_mul(this.s8) ^ this.s10 ^ ainv_mul(this.s3);\n            fsmtmp = this.r2 + this.s13;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[8] = (this.r1 + this.s8) ^ this.r2 ^ this.s9;\n\n            this.s9 = a_mul(this.s9) ^ this.s11 ^ ainv_mul(this.s4);\n            fsmtmp = this.r2 + this.s14;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[9] = (this.r1 + this.s9) ^ this.r2 ^ this.s10;\n\n            this.s10 = a_mul(this.s10) ^ this.s12 ^ ainv_mul(this.s5);\n            fsmtmp = this.r2 + this.s15;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[10] = (this.r1 + this.s10) ^ this.r2 ^ this.s11;\n\n            this.s11 = a_mul(this.s11) ^ this.s13 ^ ainv_mul(this.s6);\n            fsmtmp = this.r2 + this.s0;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[11] = (this.r1 + this.s11) ^ this.r2 ^ this.s12;\n\n            this.s12 = a_mul(this.s12) ^ this.s14 ^ ainv_mul(this.s7);\n            fsmtmp = this.r2 + this.s1;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[12] = (this.r1 + this.s12) ^ this.r2 ^ this.s13;\n\n            this.s13 = a_mul(this.s13) ^ this.s15 ^ ainv_mul(this.s8);\n            fsmtmp = this.r2 + this.s2;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[13] = (this.r1 + this.s13) ^ this.r2 ^ this.s14;\n\n            this.s14 = a_mul(this.s14) ^ this.s0 ^ ainv_mul(this.s9);\n            fsmtmp = this.r2 + this.s3;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[14] = (this.r1 + this.s14) ^ this.r2 ^ this.s15;\n\n            this.s15 = a_mul(this.s15) ^ this.s1 ^ ainv_mul(this.s10);\n            fsmtmp = this.r2 + this.s4;\n            this.r2 = snow_T0[@byte(0, this.r1)] ^ snow_T1[@byte(1, this.r1)] ^ snow_T2[@byte(2, this.r1)] ^ snow_T3[@byte(3, this.r1)];\n            this.r1 = fsmtmp;\n            this.keyStream[15] = (this.r1 + this.s15) ^ this.r2 ^ this.s0;\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private static byte @byte(int n, uint w) => (byte)(((w) >> (n * 8)) & 0xff);\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private static uint ainv_mul(uint w) => (((w) >> 8) ^ (snow_alphainv_mul[w & 0xff]));\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private static uint a_mul(uint w) => (((w) << 8) ^ (snow_alpha_mul[w >> 24]));\n\n        private static uint[] snow_alpha_mul = {\n                    0x0,0xE19FCF13,0x6B973726,0x8A08F835,0xD6876E4C,0x3718A15F,0xBD10596A,0x5C8F9679,\n              0x5A7DC98,0xE438138B,0x6E30EBBE,0x8FAF24AD,0xD320B2D4,0x32BF7DC7,0xB8B785F2,0x59284AE1,\n              0xAE71199,0xEB78DE8A,0x617026BF,0x80EFE9AC,0xDC607FD5,0x3DFFB0C6,0xB7F748F3,0x566887E0,\n              0xF40CD01,0xEEDF0212,0x64D7FA27,0x85483534,0xD9C7A34D,0x38586C5E,0xB250946B,0x53CF5B78,\n             0x1467229B,0xF5F8ED88,0x7FF015BD,0x9E6FDAAE,0xC2E04CD7,0x237F83C4,0xA9777BF1,0x48E8B4E2,\n             0x11C0FE03,0xF05F3110,0x7A57C925,0x9BC80636,0xC747904F,0x26D85F5C,0xACD0A769,0x4D4F687A,\n             0x1E803302,0xFF1FFC11,0x75170424,0x9488CB37,0xC8075D4E,0x2998925D,0xA3906A68,0x420FA57B,\n             0x1B27EF9A,0xFAB82089,0x70B0D8BC,0x912F17AF,0xCDA081D6,0x2C3F4EC5,0xA637B6F0,0x47A879E3,\n             0x28CE449F,0xC9518B8C,0x435973B9,0xA2C6BCAA,0xFE492AD3,0x1FD6E5C0,0x95DE1DF5,0x7441D2E6,\n             0x2D699807,0xCCF65714,0x46FEAF21,0xA7616032,0xFBEEF64B,0x1A713958,0x9079C16D,0x71E60E7E,\n             0x22295506,0xC3B69A15,0x49BE6220,0xA821AD33,0xF4AE3B4A,0x1531F459,0x9F390C6C,0x7EA6C37F,\n             0x278E899E,0xC611468D,0x4C19BEB8,0xAD8671AB,0xF109E7D2,0x109628C1,0x9A9ED0F4,0x7B011FE7,\n             0x3CA96604,0xDD36A917,0x573E5122,0xB6A19E31,0xEA2E0848, 0xBB1C75B,0x81B93F6E,0x6026F07D,\n             0x390EBA9C,0xD891758F,0x52998DBA,0xB30642A9,0xEF89D4D0, 0xE161BC3,0x841EE3F6,0x65812CE5,\n             0x364E779D,0xD7D1B88E,0x5DD940BB,0xBC468FA8,0xE0C919D1, 0x156D6C2,0x8B5E2EF7,0x6AC1E1E4,\n             0x33E9AB05,0xD2766416,0x587E9C23,0xB9E15330,0xE56EC549, 0x4F10A5A,0x8EF9F26F,0x6F663D7C,\n             0x50358897,0xB1AA4784,0x3BA2BFB1,0xDA3D70A2,0x86B2E6DB,0x672D29C8,0xED25D1FD, 0xCBA1EEE,\n             0x5592540F,0xB40D9B1C,0x3E056329,0xDF9AAC3A,0x83153A43,0x628AF550,0xE8820D65, 0x91DC276,\n             0x5AD2990E,0xBB4D561D,0x3145AE28,0xD0DA613B,0x8C55F742,0x6DCA3851,0xE7C2C064, 0x65D0F77,\n             0x5F754596,0xBEEA8A85,0x34E272B0,0xD57DBDA3,0x89F22BDA,0x686DE4C9,0xE2651CFC, 0x3FAD3EF,\n             0x4452AA0C,0xA5CD651F,0x2FC59D2A,0xCE5A5239,0x92D5C440,0x734A0B53,0xF942F366,0x18DD3C75,\n             0x41F57694,0xA06AB987,0x2A6241B2,0xCBFD8EA1,0x977218D8,0x76EDD7CB,0xFCE52FFE,0x1D7AE0ED,\n             0x4EB5BB95,0xAF2A7486,0x25228CB3,0xC4BD43A0,0x9832D5D9,0x79AD1ACA,0xF3A5E2FF,0x123A2DEC,\n             0x4B12670D,0xAA8DA81E,0x2085502B,0xC11A9F38,0x9D950941,0x7C0AC652,0xF6023E67,0x179DF174,\n             0x78FBCC08,0x9964031B,0x136CFB2E,0xF2F3343D,0xAE7CA244,0x4FE36D57,0xC5EB9562,0x24745A71,\n             0x7D5C1090,0x9CC3DF83,0x16CB27B6,0xF754E8A5,0xABDB7EDC,0x4A44B1CF,0xC04C49FA,0x21D386E9,\n             0x721CDD91,0x93831282,0x198BEAB7,0xF81425A4,0xA49BB3DD,0x45047CCE,0xCF0C84FB,0x2E934BE8,\n             0x77BB0109,0x9624CE1A,0x1C2C362F,0xFDB3F93C,0xA13C6F45,0x40A3A056,0xCAAB5863,0x2B349770,\n             0x6C9CEE93,0x8D032180, 0x70BD9B5,0xE69416A6,0xBA1B80DF,0x5B844FCC,0xD18CB7F9,0x301378EA,\n             0x693B320B,0x88A4FD18, 0x2AC052D,0xE333CA3E,0xBFBC5C47,0x5E239354,0xD42B6B61,0x35B4A472,\n             0x667BFF0A,0x87E43019, 0xDECC82C,0xEC73073F,0xB0FC9146,0x51635E55,0xDB6BA660,0x3AF46973,\n             0x63DC2392,0x8243EC81, 0x84B14B4,0xE9D4DBA7,0xB55B4DDE,0x54C482CD,0xDECC7AF8,0x3F53B5EB};\n\n        static uint[] snow_alphainv_mul = {\n                    0x0,0x180F40CD,0x301E8033,0x2811C0FE,0x603CA966,0x7833E9AB,0x50222955,0x482D6998,\n             0xC078FBCC,0xD877BB01,0xF0667BFF,0xE8693B32,0xA04452AA,0xB84B1267,0x905AD299,0x88559254,\n             0x29F05F31,0x31FF1FFC,0x19EEDF02, 0x1E19FCF,0x49CCF657,0x51C3B69A,0x79D27664,0x61DD36A9,\n             0xE988A4FD,0xF187E430,0xD99624CE,0xC1996403,0x89B40D9B,0x91BB4D56,0xB9AA8DA8,0xA1A5CD65,\n             0x5249BE62,0x4A46FEAF,0x62573E51,0x7A587E9C,0x32751704,0x2A7A57C9, 0x26B9737,0x1A64D7FA,\n             0x923145AE,0x8A3E0563,0xA22FC59D,0xBA208550,0xF20DECC8,0xEA02AC05,0xC2136CFB,0xDA1C2C36,\n             0x7BB9E153,0x63B6A19E,0x4BA76160,0x53A821AD,0x1B854835, 0x38A08F8,0x2B9BC806,0x339488CB,\n             0xBBC11A9F,0xA3CE5A52,0x8BDF9AAC,0x93D0DA61,0xDBFDB3F9,0xC3F2F334,0xEBE333CA,0xF3EC7307,\n             0xA492D5C4,0xBC9D9509,0x948C55F7,0x8C83153A,0xC4AE7CA2,0xDCA13C6F,0xF4B0FC91,0xECBFBC5C,\n             0x64EA2E08,0x7CE56EC5,0x54F4AE3B,0x4CFBEEF6, 0x4D6876E,0x1CD9C7A3,0x34C8075D,0x2CC74790,\n             0x8D628AF5,0x956DCA38,0xBD7C0AC6,0xA5734A0B,0xED5E2393,0xF551635E,0xDD40A3A0,0xC54FE36D,\n             0x4D1A7139,0x551531F4,0x7D04F10A,0x650BB1C7,0x2D26D85F,0x35299892,0x1D38586C, 0x53718A1,\n             0xF6DB6BA6,0xEED42B6B,0xC6C5EB95,0xDECAAB58,0x96E7C2C0,0x8EE8820D,0xA6F942F3,0xBEF6023E,\n             0x36A3906A,0x2EACD0A7, 0x6BD1059,0x1EB25094,0x569F390C,0x4E9079C1,0x6681B93F,0x7E8EF9F2,\n             0xDF2B3497,0xC724745A,0xEF35B4A4,0xF73AF469,0xBF179DF1,0xA718DD3C,0x8F091DC2,0x97065D0F,\n             0x1F53CF5B, 0x75C8F96,0x2F4D4F68,0x37420FA5,0x7F6F663D,0x676026F0,0x4F71E60E,0x577EA6C3,\n             0xE18D0321,0xF98243EC,0xD1938312,0xC99CC3DF,0x81B1AA47,0x99BEEA8A,0xB1AF2A74,0xA9A06AB9,\n             0x21F5F8ED,0x39FAB820,0x11EB78DE, 0x9E43813,0x41C9518B,0x59C61146,0x71D7D1B8,0x69D89175,\n             0xC87D5C10,0xD0721CDD,0xF863DC23,0xE06C9CEE,0xA841F576,0xB04EB5BB,0x985F7545,0x80503588,\n              0x805A7DC,0x100AE711,0x381B27EF,0x20146722,0x68390EBA,0x70364E77,0x58278E89,0x4028CE44,\n             0xB3C4BD43,0xABCBFD8E,0x83DA3D70,0x9BD57DBD,0xD3F81425,0xCBF754E8,0xE3E69416,0xFBE9D4DB,\n             0x73BC468F,0x6BB30642,0x43A2C6BC,0x5BAD8671,0x1380EFE9, 0xB8FAF24,0x239E6FDA,0x3B912F17,\n             0x9A34E272,0x823BA2BF,0xAA2A6241,0xB225228C,0xFA084B14,0xE2070BD9,0xCA16CB27,0xD2198BEA,\n             0x5A4C19BE,0x42435973,0x6A52998D,0x725DD940,0x3A70B0D8,0x227FF015, 0xA6E30EB,0x12617026,\n             0x451FD6E5,0x5D109628,0x750156D6,0x6D0E161B,0x25237F83,0x3D2C3F4E,0x153DFFB0, 0xD32BF7D,\n             0x85672D29,0x9D686DE4,0xB579AD1A,0xAD76EDD7,0xE55B844F,0xFD54C482,0xD545047C,0xCD4A44B1,\n             0x6CEF89D4,0x74E0C919,0x5CF109E7,0x44FE492A, 0xCD320B2,0x14DC607F,0x3CCDA081,0x24C2E04C,\n             0xAC977218,0xB49832D5,0x9C89F22B,0x8486B2E6,0xCCABDB7E,0xD4A49BB3,0xFCB55B4D,0xE4BA1B80,\n             0x17566887, 0xF59284A,0x2748E8B4,0x3F47A879,0x776AC1E1,0x6F65812C,0x477441D2,0x5F7B011F,\n             0xD72E934B,0xCF21D386,0xE7301378,0xFF3F53B5,0xB7123A2D,0xAF1D7AE0,0x870CBA1E,0x9F03FAD3,\n             0x3EA637B6,0x26A9777B, 0xEB8B785,0x16B7F748,0x5E9A9ED0,0x4695DE1D,0x6E841EE3,0x768B5E2E,\n             0xFEDECC7A,0xE6D18CB7,0xCEC04C49,0xD6CF0C84,0x9EE2651C,0x86ED25D1,0xAEFCE52F,0xB6F3A5E2};\n\n        static uint[] snow_T0 = {\n             0xa56363c6,0x847c7cf8,0x997777ee,0x8d7b7bf6, 0xdf2f2ff,0xbd6b6bd6,0xb16f6fde,0x54c5c591,\n             0x50303060, 0x3010102,0xa96767ce,0x7d2b2b56,0x19fefee7,0x62d7d7b5,0xe6abab4d,0x9a7676ec,\n             0x45caca8f,0x9d82821f,0x40c9c989,0x877d7dfa,0x15fafaef,0xeb5959b2,0xc947478e, 0xbf0f0fb,\n             0xecadad41,0x67d4d4b3,0xfda2a25f,0xeaafaf45,0xbf9c9c23,0xf7a4a453,0x967272e4,0x5bc0c09b,\n             0xc2b7b775,0x1cfdfde1,0xae93933d,0x6a26264c,0x5a36366c,0x413f3f7e, 0x2f7f7f5,0x4fcccc83,\n             0x5c343468,0xf4a5a551,0x34e5e5d1, 0x8f1f1f9,0x937171e2,0x73d8d8ab,0x53313162,0x3f15152a,\n              0xc040408,0x52c7c795,0x65232346,0x5ec3c39d,0x28181830,0xa1969637, 0xf05050a,0xb59a9a2f,\n              0x907070e,0x36121224,0x9b80801b,0x3de2e2df,0x26ebebcd,0x6927274e,0xcdb2b27f,0x9f7575ea,\n             0x1b090912,0x9e83831d,0x742c2c58,0x2e1a1a34,0x2d1b1b36,0xb26e6edc,0xee5a5ab4,0xfba0a05b,\n             0xf65252a4,0x4d3b3b76,0x61d6d6b7,0xceb3b37d,0x7b292952,0x3ee3e3dd,0x712f2f5e,0x97848413,\n             0xf55353a6,0x68d1d1b9,       0x0,0x2cededc1,0x60202040,0x1ffcfce3,0xc8b1b179,0xed5b5bb6,\n             0xbe6a6ad4,0x46cbcb8d,0xd9bebe67,0x4b393972,0xde4a4a94,0xd44c4c98,0xe85858b0,0x4acfcf85,\n             0x6bd0d0bb,0x2aefefc5,0xe5aaaa4f,0x16fbfbed,0xc5434386,0xd74d4d9a,0x55333366,0x94858511,\n             0xcf45458a,0x10f9f9e9, 0x6020204,0x817f7ffe,0xf05050a0,0x443c3c78,0xba9f9f25,0xe3a8a84b,\n             0xf35151a2,0xfea3a35d,0xc0404080,0x8a8f8f05,0xad92923f,0xbc9d9d21,0x48383870, 0x4f5f5f1,\n             0xdfbcbc63,0xc1b6b677,0x75dadaaf,0x63212142,0x30101020,0x1affffe5, 0xef3f3fd,0x6dd2d2bf,\n             0x4ccdcd81,0x140c0c18,0x35131326,0x2fececc3,0xe15f5fbe,0xa2979735,0xcc444488,0x3917172e,\n             0x57c4c493,0xf2a7a755,0x827e7efc,0x473d3d7a,0xac6464c8,0xe75d5dba,0x2b191932,0x957373e6,\n             0xa06060c0,0x98818119,0xd14f4f9e,0x7fdcdca3,0x66222244,0x7e2a2a54,0xab90903b,0x8388880b,\n             0xca46468c,0x29eeeec7,0xd3b8b86b,0x3c141428,0x79dedea7,0xe25e5ebc,0x1d0b0b16,0x76dbdbad,\n             0x3be0e0db,0x56323264,0x4e3a3a74,0x1e0a0a14,0xdb494992, 0xa06060c,0x6c242448,0xe45c5cb8,\n             0x5dc2c29f,0x6ed3d3bd,0xefacac43,0xa66262c4,0xa8919139,0xa4959531,0x37e4e4d3,0x8b7979f2,\n             0x32e7e7d5,0x43c8c88b,0x5937376e,0xb76d6dda,0x8c8d8d01,0x64d5d5b1,0xd24e4e9c,0xe0a9a949,\n             0xb46c6cd8,0xfa5656ac, 0x7f4f4f3,0x25eaeacf,0xaf6565ca,0x8e7a7af4,0xe9aeae47,0x18080810,\n             0xd5baba6f,0x887878f0,0x6f25254a,0x722e2e5c,0x241c1c38,0xf1a6a657,0xc7b4b473,0x51c6c697,\n             0x23e8e8cb,0x7cdddda1,0x9c7474e8,0x211f1f3e,0xdd4b4b96,0xdcbdbd61,0x868b8b0d,0x858a8a0f,\n             0x907070e0,0x423e3e7c,0xc4b5b571,0xaa6666cc,0xd8484890, 0x5030306, 0x1f6f6f7,0x120e0e1c,\n             0xa36161c2,0x5f35356a,0xf95757ae,0xd0b9b969,0x91868617,0x58c1c199,0x271d1d3a,0xb99e9e27,\n             0x38e1e1d9,0x13f8f8eb,0xb398982b,0x33111122,0xbb6969d2,0x70d9d9a9,0x898e8e07,0xa7949433,\n             0xb69b9b2d,0x221e1e3c,0x92878715,0x20e9e9c9,0x49cece87,0xff5555aa,0x78282850,0x7adfdfa5,\n             0x8f8c8c03,0xf8a1a159,0x80898909,0x170d0d1a,0xdabfbf65,0x31e6e6d7,0xc6424284,0xb86868d0,\n             0xc3414182,0xb0999929,0x772d2d5a,0x110f0f1e,0xcbb0b07b,0xfc5454a8,0xd6bbbb6d,0x3a16162c};\n\n        static uint[] snow_T1 = {\n             0x6363c6a5,0x7c7cf884,0x7777ee99,0x7b7bf68d,0xf2f2ff0d,0x6b6bd6bd,0x6f6fdeb1,0xc5c59154,\n             0x30306050, 0x1010203,0x6767cea9,0x2b2b567d,0xfefee719,0xd7d7b562,0xabab4de6,0x7676ec9a,\n             0xcaca8f45,0x82821f9d,0xc9c98940,0x7d7dfa87,0xfafaef15,0x5959b2eb,0x47478ec9,0xf0f0fb0b,\n             0xadad41ec,0xd4d4b367,0xa2a25ffd,0xafaf45ea,0x9c9c23bf,0xa4a453f7,0x7272e496,0xc0c09b5b,\n             0xb7b775c2,0xfdfde11c,0x93933dae,0x26264c6a,0x36366c5a,0x3f3f7e41,0xf7f7f502,0xcccc834f,\n             0x3434685c,0xa5a551f4,0xe5e5d134,0xf1f1f908,0x7171e293,0xd8d8ab73,0x31316253,0x15152a3f,\n              0x404080c,0xc7c79552,0x23234665,0xc3c39d5e,0x18183028,0x969637a1, 0x5050a0f,0x9a9a2fb5,\n              0x7070e09,0x12122436,0x80801b9b,0xe2e2df3d,0xebebcd26,0x27274e69,0xb2b27fcd,0x7575ea9f,\n              0x909121b,0x83831d9e,0x2c2c5874,0x1a1a342e,0x1b1b362d,0x6e6edcb2,0x5a5ab4ee,0xa0a05bfb,\n             0x5252a4f6,0x3b3b764d,0xd6d6b761,0xb3b37dce,0x2929527b,0xe3e3dd3e,0x2f2f5e71,0x84841397,\n             0x5353a6f5,0xd1d1b968,       0x0,0xededc12c,0x20204060,0xfcfce31f,0xb1b179c8,0x5b5bb6ed,\n             0x6a6ad4be,0xcbcb8d46,0xbebe67d9,0x3939724b,0x4a4a94de,0x4c4c98d4,0x5858b0e8,0xcfcf854a,\n             0xd0d0bb6b,0xefefc52a,0xaaaa4fe5,0xfbfbed16,0x434386c5,0x4d4d9ad7,0x33336655,0x85851194,\n             0x45458acf,0xf9f9e910, 0x2020406,0x7f7ffe81,0x5050a0f0,0x3c3c7844,0x9f9f25ba,0xa8a84be3,\n             0x5151a2f3,0xa3a35dfe,0x404080c0,0x8f8f058a,0x92923fad,0x9d9d21bc,0x38387048,0xf5f5f104,\n             0xbcbc63df,0xb6b677c1,0xdadaaf75,0x21214263,0x10102030,0xffffe51a,0xf3f3fd0e,0xd2d2bf6d,\n             0xcdcd814c, 0xc0c1814,0x13132635,0xececc32f,0x5f5fbee1,0x979735a2,0x444488cc,0x17172e39,\n             0xc4c49357,0xa7a755f2,0x7e7efc82,0x3d3d7a47,0x6464c8ac,0x5d5dbae7,0x1919322b,0x7373e695,\n             0x6060c0a0,0x81811998,0x4f4f9ed1,0xdcdca37f,0x22224466,0x2a2a547e,0x90903bab,0x88880b83,\n             0x46468cca,0xeeeec729,0xb8b86bd3,0x1414283c,0xdedea779,0x5e5ebce2, 0xb0b161d,0xdbdbad76,\n             0xe0e0db3b,0x32326456,0x3a3a744e, 0xa0a141e,0x494992db, 0x6060c0a,0x2424486c,0x5c5cb8e4,\n             0xc2c29f5d,0xd3d3bd6e,0xacac43ef,0x6262c4a6,0x919139a8,0x959531a4,0xe4e4d337,0x7979f28b,\n             0xe7e7d532,0xc8c88b43,0x37376e59,0x6d6ddab7,0x8d8d018c,0xd5d5b164,0x4e4e9cd2,0xa9a949e0,\n             0x6c6cd8b4,0x5656acfa,0xf4f4f307,0xeaeacf25,0x6565caaf,0x7a7af48e,0xaeae47e9, 0x8081018,\n             0xbaba6fd5,0x7878f088,0x25254a6f,0x2e2e5c72,0x1c1c3824,0xa6a657f1,0xb4b473c7,0xc6c69751,\n             0xe8e8cb23,0xdddda17c,0x7474e89c,0x1f1f3e21,0x4b4b96dd,0xbdbd61dc,0x8b8b0d86,0x8a8a0f85,\n             0x7070e090,0x3e3e7c42,0xb5b571c4,0x6666ccaa,0x484890d8, 0x3030605,0xf6f6f701, 0xe0e1c12,\n             0x6161c2a3,0x35356a5f,0x5757aef9,0xb9b969d0,0x86861791,0xc1c19958,0x1d1d3a27,0x9e9e27b9,\n             0xe1e1d938,0xf8f8eb13,0x98982bb3,0x11112233,0x6969d2bb,0xd9d9a970,0x8e8e0789,0x949433a7,\n             0x9b9b2db6,0x1e1e3c22,0x87871592,0xe9e9c920,0xcece8749,0x5555aaff,0x28285078,0xdfdfa57a,\n             0x8c8c038f,0xa1a159f8,0x89890980, 0xd0d1a17,0xbfbf65da,0xe6e6d731,0x424284c6,0x6868d0b8,\n             0x414182c3,0x999929b0,0x2d2d5a77, 0xf0f1e11,0xb0b07bcb,0x5454a8fc,0xbbbb6dd6,0x16162c3a};\n\n        static uint[] snow_T2 = {\n             0x63c6a563,0x7cf8847c,0x77ee9977,0x7bf68d7b,0xf2ff0df2,0x6bd6bd6b,0x6fdeb16f,0xc59154c5,\n             0x30605030, 0x1020301,0x67cea967,0x2b567d2b,0xfee719fe,0xd7b562d7,0xab4de6ab,0x76ec9a76,\n             0xca8f45ca,0x821f9d82,0xc98940c9,0x7dfa877d,0xfaef15fa,0x59b2eb59,0x478ec947,0xf0fb0bf0,\n             0xad41ecad,0xd4b367d4,0xa25ffda2,0xaf45eaaf,0x9c23bf9c,0xa453f7a4,0x72e49672,0xc09b5bc0,\n             0xb775c2b7,0xfde11cfd,0x933dae93,0x264c6a26,0x366c5a36,0x3f7e413f,0xf7f502f7,0xcc834fcc,\n             0x34685c34,0xa551f4a5,0xe5d134e5,0xf1f908f1,0x71e29371,0xd8ab73d8,0x31625331,0x152a3f15,\n              0x4080c04,0xc79552c7,0x23466523,0xc39d5ec3,0x18302818,0x9637a196, 0x50a0f05,0x9a2fb59a,\n              0x70e0907,0x12243612,0x801b9b80,0xe2df3de2,0xebcd26eb,0x274e6927,0xb27fcdb2,0x75ea9f75,\n              0x9121b09,0x831d9e83,0x2c58742c,0x1a342e1a,0x1b362d1b,0x6edcb26e,0x5ab4ee5a,0xa05bfba0,\n             0x52a4f652,0x3b764d3b,0xd6b761d6,0xb37dceb3,0x29527b29,0xe3dd3ee3,0x2f5e712f,0x84139784,\n             0x53a6f553,0xd1b968d1,       0x0,0xedc12ced,0x20406020,0xfce31ffc,0xb179c8b1,0x5bb6ed5b,\n             0x6ad4be6a,0xcb8d46cb,0xbe67d9be,0x39724b39,0x4a94de4a,0x4c98d44c,0x58b0e858,0xcf854acf,\n             0xd0bb6bd0,0xefc52aef,0xaa4fe5aa,0xfbed16fb,0x4386c543,0x4d9ad74d,0x33665533,0x85119485,\n             0x458acf45,0xf9e910f9, 0x2040602,0x7ffe817f,0x50a0f050,0x3c78443c,0x9f25ba9f,0xa84be3a8,\n             0x51a2f351,0xa35dfea3,0x4080c040,0x8f058a8f,0x923fad92,0x9d21bc9d,0x38704838,0xf5f104f5,\n             0xbc63dfbc,0xb677c1b6,0xdaaf75da,0x21426321,0x10203010,0xffe51aff,0xf3fd0ef3,0xd2bf6dd2,\n             0xcd814ccd, 0xc18140c,0x13263513,0xecc32fec,0x5fbee15f,0x9735a297,0x4488cc44,0x172e3917,\n             0xc49357c4,0xa755f2a7,0x7efc827e,0x3d7a473d,0x64c8ac64,0x5dbae75d,0x19322b19,0x73e69573,\n             0x60c0a060,0x81199881,0x4f9ed14f,0xdca37fdc,0x22446622,0x2a547e2a,0x903bab90,0x880b8388,\n             0x468cca46,0xeec729ee,0xb86bd3b8,0x14283c14,0xdea779de,0x5ebce25e, 0xb161d0b,0xdbad76db,\n             0xe0db3be0,0x32645632,0x3a744e3a, 0xa141e0a,0x4992db49, 0x60c0a06,0x24486c24,0x5cb8e45c,\n             0xc29f5dc2,0xd3bd6ed3,0xac43efac,0x62c4a662,0x9139a891,0x9531a495,0xe4d337e4,0x79f28b79,\n             0xe7d532e7,0xc88b43c8,0x376e5937,0x6ddab76d,0x8d018c8d,0xd5b164d5,0x4e9cd24e,0xa949e0a9,\n             0x6cd8b46c,0x56acfa56,0xf4f307f4,0xeacf25ea,0x65caaf65,0x7af48e7a,0xae47e9ae, 0x8101808,\n             0xba6fd5ba,0x78f08878,0x254a6f25,0x2e5c722e,0x1c38241c,0xa657f1a6,0xb473c7b4,0xc69751c6,\n             0xe8cb23e8,0xdda17cdd,0x74e89c74,0x1f3e211f,0x4b96dd4b,0xbd61dcbd,0x8b0d868b,0x8a0f858a,\n             0x70e09070,0x3e7c423e,0xb571c4b5,0x66ccaa66,0x4890d848, 0x3060503,0xf6f701f6, 0xe1c120e,\n             0x61c2a361,0x356a5f35,0x57aef957,0xb969d0b9,0x86179186,0xc19958c1,0x1d3a271d,0x9e27b99e,\n             0xe1d938e1,0xf8eb13f8,0x982bb398,0x11223311,0x69d2bb69,0xd9a970d9,0x8e07898e,0x9433a794,\n             0x9b2db69b,0x1e3c221e,0x87159287,0xe9c920e9,0xce8749ce,0x55aaff55,0x28507828,0xdfa57adf,\n             0x8c038f8c,0xa159f8a1,0x89098089, 0xd1a170d,0xbf65dabf,0xe6d731e6,0x4284c642,0x68d0b868,\n             0x4182c341,0x9929b099,0x2d5a772d, 0xf1e110f,0xb07bcbb0,0x54a8fc54,0xbb6dd6bb,0x162c3a16};\n\n        static uint[] snow_T3 = {\n             0xc6a56363,0xf8847c7c,0xee997777,0xf68d7b7b,0xff0df2f2,0xd6bd6b6b,0xdeb16f6f,0x9154c5c5,\n             0x60503030, 0x2030101,0xcea96767,0x567d2b2b,0xe719fefe,0xb562d7d7,0x4de6abab,0xec9a7676,\n             0x8f45caca,0x1f9d8282,0x8940c9c9,0xfa877d7d,0xef15fafa,0xb2eb5959,0x8ec94747,0xfb0bf0f0,\n             0x41ecadad,0xb367d4d4,0x5ffda2a2,0x45eaafaf,0x23bf9c9c,0x53f7a4a4,0xe4967272,0x9b5bc0c0,\n             0x75c2b7b7,0xe11cfdfd,0x3dae9393,0x4c6a2626,0x6c5a3636,0x7e413f3f,0xf502f7f7,0x834fcccc,\n             0x685c3434,0x51f4a5a5,0xd134e5e5,0xf908f1f1,0xe2937171,0xab73d8d8,0x62533131,0x2a3f1515,\n              0x80c0404,0x9552c7c7,0x46652323,0x9d5ec3c3,0x30281818,0x37a19696, 0xa0f0505,0x2fb59a9a,\n              0xe090707,0x24361212,0x1b9b8080,0xdf3de2e2,0xcd26ebeb,0x4e692727,0x7fcdb2b2,0xea9f7575,\n             0x121b0909,0x1d9e8383,0x58742c2c,0x342e1a1a,0x362d1b1b,0xdcb26e6e,0xb4ee5a5a,0x5bfba0a0,\n             0xa4f65252,0x764d3b3b,0xb761d6d6,0x7dceb3b3,0x527b2929,0xdd3ee3e3,0x5e712f2f,0x13978484,\n             0xa6f55353,0xb968d1d1,       0x0,0xc12ceded,0x40602020,0xe31ffcfc,0x79c8b1b1,0xb6ed5b5b,\n             0xd4be6a6a,0x8d46cbcb,0x67d9bebe,0x724b3939,0x94de4a4a,0x98d44c4c,0xb0e85858,0x854acfcf,\n             0xbb6bd0d0,0xc52aefef,0x4fe5aaaa,0xed16fbfb,0x86c54343,0x9ad74d4d,0x66553333,0x11948585,\n             0x8acf4545,0xe910f9f9, 0x4060202,0xfe817f7f,0xa0f05050,0x78443c3c,0x25ba9f9f,0x4be3a8a8,\n             0xa2f35151,0x5dfea3a3,0x80c04040, 0x58a8f8f,0x3fad9292,0x21bc9d9d,0x70483838,0xf104f5f5,\n             0x63dfbcbc,0x77c1b6b6,0xaf75dada,0x42632121,0x20301010,0xe51affff,0xfd0ef3f3,0xbf6dd2d2,\n             0x814ccdcd,0x18140c0c,0x26351313,0xc32fecec,0xbee15f5f,0x35a29797,0x88cc4444,0x2e391717,\n             0x9357c4c4,0x55f2a7a7,0xfc827e7e,0x7a473d3d,0xc8ac6464,0xbae75d5d,0x322b1919,0xe6957373,\n             0xc0a06060,0x19988181,0x9ed14f4f,0xa37fdcdc,0x44662222,0x547e2a2a,0x3bab9090, 0xb838888,\n             0x8cca4646,0xc729eeee,0x6bd3b8b8,0x283c1414,0xa779dede,0xbce25e5e,0x161d0b0b,0xad76dbdb,\n             0xdb3be0e0,0x64563232,0x744e3a3a,0x141e0a0a,0x92db4949, 0xc0a0606,0x486c2424,0xb8e45c5c,\n             0x9f5dc2c2,0xbd6ed3d3,0x43efacac,0xc4a66262,0x39a89191,0x31a49595,0xd337e4e4,0xf28b7979,\n             0xd532e7e7,0x8b43c8c8,0x6e593737,0xdab76d6d, 0x18c8d8d,0xb164d5d5,0x9cd24e4e,0x49e0a9a9,\n             0xd8b46c6c,0xacfa5656,0xf307f4f4,0xcf25eaea,0xcaaf6565,0xf48e7a7a,0x47e9aeae,0x10180808,\n             0x6fd5baba,0xf0887878,0x4a6f2525,0x5c722e2e,0x38241c1c,0x57f1a6a6,0x73c7b4b4,0x9751c6c6,\n             0xcb23e8e8,0xa17cdddd,0xe89c7474,0x3e211f1f,0x96dd4b4b,0x61dcbdbd, 0xd868b8b, 0xf858a8a,\n             0xe0907070,0x7c423e3e,0x71c4b5b5,0xccaa6666,0x90d84848, 0x6050303,0xf701f6f6,0x1c120e0e,\n             0xc2a36161,0x6a5f3535,0xaef95757,0x69d0b9b9,0x17918686,0x9958c1c1,0x3a271d1d,0x27b99e9e,\n             0xd938e1e1,0xeb13f8f8,0x2bb39898,0x22331111,0xd2bb6969,0xa970d9d9, 0x7898e8e,0x33a79494,\n             0x2db69b9b,0x3c221e1e,0x15928787,0xc920e9e9,0x8749cece,0xaaff5555,0x50782828,0xa57adfdf,\n              0x38f8c8c,0x59f8a1a1, 0x9808989,0x1a170d0d,0x65dabfbf,0xd731e6e6,0x84c64242,0xd0b86868,\n             0x82c34141,0x29b09999,0x5a772d2d,0x1e110f0f,0x7bcbb0b0,0xa8fc5454,0x6dd6bbbb,0x2c3a1616};\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/IMapleStoryBlob.cs",
    "content": "﻿using System;\n\nnamespace WzComparerR2.WzLib\n{\n    public interface IMapleStoryBlob\n    {\n        int Length { get; }\n        void CopyTo(byte[] buffer, int offset);\n        void CopyTo(Span<byte> span);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/IMapleStoryFile.cs",
    "content": "﻿using System;\nusing System.IO;\n\nnamespace WzComparerR2.WzLib\n{\n    public interface IMapleStoryFile : IDisposable\n    {\n        public Wz_Structure WzStructure { get; }\n        public Stream FileStream { get; }\n        public object ReadLock { get; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/IMapleStoryFileEntry.cs",
    "content": "﻿using System;\n\nnamespace WzComparerR2.WzLib\n{\n    public interface IMapleStoryFileEntry\n    {\n        IMapleStoryFile File { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Interop.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.WzLib\n{\n    public static partial class Interop\n    {\n        /// <summary>\n        /// <see href=\"https://learn.microsoft.com/en-us/windows/win32/api/strmif/ns-strmif-am_media_type\" />\n        /// </summary>\n        [StructLayout(LayoutKind.Sequential)]\n        public sealed class AM_MEDIA_TYPE\n        {\n            public Guid MajorType;\n            public Guid SubType;\n            [MarshalAs(UnmanagedType.Bool)]\n            public bool FixedSizeSamples;\n            [MarshalAs(UnmanagedType.Bool)]\n            public bool TemporalCompression;\n            public uint SampleSize;\n            public Guid FormatType;\n            [MarshalAs(UnmanagedType.IUnknown)]\n            public object Unknown;\n            public uint CbFormat;\n            // this should be IntPtr, but we don't really marshal it.\n            public object PbFormat;\n        }\n\n        /// <summary>\n        /// <see href=\"https://learn.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatex\" />\n        /// </summary>\n        [StructLayout(LayoutKind.Sequential, Pack = 1)]\n        public struct WAVEFORMATEX\n        {\n            public ushort FormatTag;\n            public ushort Channels;\n            public uint SamplesPerSec;\n            public uint AvgBytesPerSec;\n            public ushort BlockAlign;\n            public ushort BitsPerSample;\n            public ushort CbSize;\n        }\n\n        /// <summary>\n        /// <see href=\"https://learn.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-mpeglayer3waveformat\" />\n        /// </summary>\n        [StructLayout(LayoutKind.Sequential, Pack = 1)]\n        public struct MPEGLAYER3WAVEFORMAT\n        {\n            public WAVEFORMATEX Wfx;\n            public ushort ID;\n            public uint Flags;\n            public ushort BlockSize;\n            public ushort FramesPerBlock;\n            public ushort CodecDelay;\n        }\n\n        public static readonly Guid MEDIATYPE_Stream = Guid.Parse(\"{e436eb83-524f-11ce-9f53-0020af0ba770}\");\n        public static readonly Guid MEDIASUBTYPE_MPEG1Audio = Guid.Parse(\"{e436eb87-524f-11ce-9f53-0020af0ba770}\");\n        public static readonly Guid MEDIASUBTYPE_WAVE = Guid.Parse(\"{e436eb8b-524f-11ce-9f53-0020af0ba770}\");\n        public static readonly Guid FORMAT_WaveFormatEx = Guid.Parse(\"{05589f81-c356-11ce-bf01-00aa0055595a}\");\n        public static readonly int WAVEFORMATEX_SIZE = Marshal.SizeOf<WAVEFORMATEX>();\n        public static readonly int MPEGLAYER3WAVEFORMAT_SIZE = Marshal.SizeOf<MPEGLAYER3WAVEFORMAT>();\n        public const ushort WAVE_FORMAT_PCM = 0x0001;\n        public const ushort WAVE_FORMAT_MPEGLAYER3 = 0x0055;\n        public const uint MPEGLAYER3_WFX_EXTRA_BYTES = 12;\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Mcv_Types.cs",
    "content": "﻿using System;\n\nnamespace WzComparerR2.WzLib\n{\n    public class McvHeader\n    {\n        public string Signature { get; set; }\n        public int HeaderLength { get; set; }\n        public uint FourCC { get; set; }\n        public int Width { get; set; }\n        public int Height { get; set; }\n        public int FrameCount { get; set; }\n        public McvDataFlags DataFlag { get; set; }\n        public McvFrameInfo[] Frames { get; set; }\n    }\n\n    [Flags]\n    public enum McvDataFlags\n    {\n        Default = 0,\n        AlphaMap = 1,\n        PerFrameDelay = 2,\n        PerFrameTimeline = 4,\n    }\n\n    public class McvFrameInfo\n    {\n        public McvFrameInfo()\n        {\n            this.DataOffset = -1;\n            this.AlphaDataOffset = -1;\n        }\n\n        public long DataOffset { get; set; }\n        public int DataCount { get; set; }\n        public long AlphaDataOffset { get; set; }\n        public int AlphaDataCount { get; set; }\n        public long DelayInNanoseconds { get; set; }\n        public long StartTimeInNanoseconds { get; set; }\n\n        public TimeSpan Delay => TimeSpan.FromTicks(this.DelayInNanoseconds / 100);\n        public TimeSpan StartTime => TimeSpan.FromTicks(this.StartTimeInNanoseconds / 100);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Ms_Entry.cs",
    "content": "﻿using System;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Ms_Entry\n    {\n        public Ms_Entry(string name, int checkSum, int flags, int startPos, int size, int sizeAligned, int unk1, int unk2, byte[] key)\n            : this(name, checkSum, flags, startPos, size, sizeAligned, unk1, unk2, key, 0, 0)\n        {\n        }\n\n        public Ms_Entry(string name, int checkSum, int flags, int startPos, int size, int sizeAligned, int unk1, int unk2, byte[] key, int unk3, int unk4)\n        {\n            this.Name = name;\n            this.CheckSum = checkSum;\n            this.Flags = flags;\n            this.StartPos = startPos;\n            this.Size = size;\n            this.SizeAligned = sizeAligned;\n            this.Unknown1 = unk1;\n            this.Unknown2 = unk2;\n            this.Key = key;\n            this.Unknown3 = unk3;\n            this.Unknown4 = unk4;\n        }\n\n        public string Name { get; internal set; }\n        public int CheckSum { get; internal set; }\n        public int Flags { get; internal set; }\n        public long StartPos { get; internal set; }\n        public int Size { get; internal set; }\n        public int SizeAligned { get; internal set; }\n        public int Unknown1 { get; internal set; }\n        public int Unknown2 { get; internal set; }\n        public byte[] Key { get; internal set; }\n        public int Unknown3 { get; internal set; }  // for ms file v2 only\n        public int Unknown4 { get; internal set; }  // for ms file v2 only\n\n        public int CalculatedCheckSum { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Ms_File.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\nusing System.Text;\nusing WzComparerR2.WzLib.Cryptography;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Ms_File : IMapleStoryFile, IDisposable\n    {\n        public Ms_File(string fileName, Wz_Structure wzs)\n        {\n            var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);\n            this.Init(fileStream, fileName, wzs, false);\n        }\n\n        public Ms_File(Stream baseStream, string originalFileName, Wz_Structure wzs, bool leaveOpen = false)\n        {\n            this.Init(baseStream, originalFileName, wzs, leaveOpen);\n        }\n\n        private void Init(Stream baseStream, string originalFileName, Wz_Structure wzs, bool leaveOpen)\n        {\n            if (baseStream == null)\n            {\n                throw new ArgumentNullException(nameof(baseStream));\n            }\n            if (originalFileName == null)\n            {\n                throw new ArgumentNullException(nameof(originalFileName));\n            }\n\n            this.BaseStream = baseStream;\n            this.WzStructure = wzs;\n            this.leaveOpen = leaveOpen;\n            try\n            {\n                this.ReadHeader(originalFileName);\n            }\n            catch\n            {\n                if (!leaveOpen)\n                {\n                    baseStream.Dispose();\n                }\n                throw;\n            }\n            this.Entries = new List<Ms_Entry>(0);\n        }\n\n        public Stream BaseStream { get; private set; }\n        public Wz_Structure WzStructure { get; private set; }\n        public Ms_Header Header { get; private set; }\n        public List<Ms_Entry> Entries { get; private set; }\n\n        Stream IMapleStoryFile.FileStream => this.BaseStream;\n        object IMapleStoryFile.ReadLock => this.BaseStream;\n\n        private bool leaveOpen;\n\n        private void ReadHeader(string fullFileName)\n        {\n            string fileName = Path.GetFileName(fullFileName).ToLower();\n            this.BaseStream.Position = 0;\n            using var bReader = new BinaryReader(this.BaseStream, Encoding.ASCII, true);\n\n            // 1. random bytes\n            int randByteCount = fileName.Sum(c => (int)c) % 312 + 30;\n            byte[] randBytes = bReader.ReadBytes(randByteCount);\n\n            // 2. encrypted snowKeySalt\n            int hashedSaltLen = bReader.ReadInt32();\n            int saltLen = (byte)hashedSaltLen ^ randBytes[0];\n            byte[] saltBytes = bReader.ReadBytes(saltLen * 2);\n            char[] saltChars = new char[saltLen];\n            for (int i = 0; i < saltLen; i++)\n            {\n                saltChars[i] = (char)(randBytes[i] ^ saltBytes[i * 2]);\n            }\n            string saltStr = new string(saltChars);\n\n            // 3. decrypt 9 bytes header with snow cipher\n            // generate snow key based on filename+keySalt\n            string fileNameWithSalt = fileName + saltStr;\n            Span<byte> snowCipherKey = stackalloc byte[16]; // should be 128 but we only use front 16 bytes\n            for (int i = 0; i < snowCipherKey.Length; i++)\n            {\n                snowCipherKey[i] = (byte)(fileNameWithSalt[i % fileNameWithSalt.Length] + i);\n            }\n            long headerStartPos = this.BaseStream.Position;\n            var snowCipher = new Snow2CryptoTransform(snowCipherKey, null, false);\n            var snowDecoderStream = new CryptoStream(this.BaseStream, snowCipher, CryptoStreamMode.Read);\n            var snowReader = new BinaryReader(snowDecoderStream);\n            int hash = snowReader.ReadInt32();\n            byte version = snowReader.ReadByte();\n            int entryCount = snowReader.ReadInt32();\n\n            // verify version and hash\n            const int supportedVersion = 2;\n            if (version != supportedVersion)\n                throw new Exception($\"Version check failed. (expected: {supportedVersion}, actual {version})\");\n            int actualHash = hashedSaltLen + version + entryCount;\n            ReadOnlySpan<ushort> u16SaltBytes = MemoryMarshal.Cast<byte, ushort>(saltBytes);\n            for (int i = 0; i < u16SaltBytes.Length; i++)\n            {\n                actualHash += u16SaltBytes[i];\n            }\n            if (hash != actualHash)\n            {\n                throw new Exception($\"Hash check failed. (expected: {hash}, actual: {actualHash})\");\n            }\n\n            // 4. skip random bytes\n            long entryStartPos = headerStartPos + 9 + fileName.Select(v => (int)v * 3).Sum() % 212 + 33;\n            var header = new Ms_Header(fullFileName, saltStr, fileNameWithSalt, hash, version, entryCount, headerStartPos, entryStartPos);\n            this.Header = header;\n        }\n\n        public void ReadEntries()\n        {\n            if (this.Header == null || this.Header.EntryCount == 0 || this.Header.EntryCount == this.Entries.Count)\n            {\n                return;\n            }\n            this.Entries.Clear();\n            int entryCount = this.Header.EntryCount;\n            if (this.Entries.Capacity < entryCount)\n            {\n                this.Entries.Capacity = entryCount;\n            }\n\n            // decrypt with another snow key\n            string fileNameWithSalt = this.Header.FileNameWithSalt;\n            Span<byte> snowCipherKey2 = stackalloc byte[16];\n            for (int i = 0; i < snowCipherKey2.Length; i++)\n            {\n                snowCipherKey2[i] = (byte)(i + (i % 3 + 2) * fileNameWithSalt[fileNameWithSalt.Length - 1 - i % fileNameWithSalt.Length]);\n            }\n            var snowCipher = new Snow2CryptoTransform(snowCipherKey2, null, false);\n            this.BaseStream.Position = this.Header.EntryStartPosition;\n            var snowDecoderStream = new CryptoStream(this.BaseStream, snowCipher, CryptoStreamMode.Read);\n            var snowReader = new BinaryReader(snowDecoderStream, Encoding.Unicode, true);\n\n            for (int i = 0; i < entryCount; i++)\n            {\n                int entryNameLen = snowReader.ReadInt32();\n                string entryName = new string(snowReader.ReadChars(entryNameLen));\n                int checkSum = snowReader.ReadInt32();\n                int flags = snowReader.ReadInt32();\n                int startPos = snowReader.ReadInt32();\n                int size = snowReader.ReadInt32();\n                int sizeAligned = snowReader.ReadInt32();\n                int unk1 = snowReader.ReadInt32();\n                int unk2 = snowReader.ReadInt32();\n                byte[] entryKey = snowReader.ReadBytes(16);\n                \n                var entry = new Ms_Entry(entryName, checkSum, flags, startPos, size, sizeAligned, unk1, unk2, entryKey);\n                entry.CalculatedCheckSum = flags + startPos + size + sizeAligned + unk1 + entryKey.Sum(b => (int)b);\n                this.Entries.Add(entry);\n            }\n\n            long dataStartPos = this.BaseStream.Position;\n            // align to 1024 bytes\n            if ((dataStartPos & 0x3ff) != 0)\n            {\n                dataStartPos = dataStartPos - (dataStartPos & 0x3ff) + 0x400;\n            }\n            this.Header.DataStartPosition = dataStartPos;\n            // recalculate startPos\n            foreach(var entry in this.Entries)\n            {\n                entry.StartPos = dataStartPos + entry.StartPos * 1024;\n            }\n        }\n\n        public void GetDirTree(Wz_Node parent)\n        {\n            foreach (var entry in this.Entries)\n            {\n                Wz_Node root = parent;\n                string[] fullPath = entry.Name.Split('/');\n                foreach (var segment in fullPath)\n                {\n                    root = root.Nodes[segment] ?? root.Nodes.Add(segment);\n                }\n\n                // always override existing value if already exists?\n                //if (root.Value == null)\n                {\n                    var msImage = new Ms_Image(fullPath[fullPath.Length - 1], entry, this);\n                    root.Value = msImage;\n                    msImage.OwnerNode = root;\n                }\n            }\n        }\n\n        public void Close()\n        {\n            if (this.BaseStream != null)\n            {\n                if (!this.leaveOpen)\n                {\n                    this.BaseStream.Dispose();\n                }\n                this.BaseStream = null;\n            }\n        }\n\n        void IDisposable.Dispose()\n        {\n            this.Close();\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Ms_FileV2.cs",
    "content": "﻿using System;\nusing System.Buffers.Binary;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\nusing System.Text;\nusing WzComparerR2.WzLib.Cryptography;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Ms_FileV2 : IMapleStoryFile, IDisposable\n    {\n        public Ms_FileV2(string fileName, Wz_Structure wzs)\n        {\n            var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);\n            this.Init(fileStream, fileName, wzs, false);\n        }\n\n        public Ms_FileV2(Stream baseStream, string originalFileName, Wz_Structure wzs, bool leaveOpen = false)\n        {\n            this.Init(baseStream, originalFileName, wzs, leaveOpen);\n        }\n\n        public Stream BaseStream { get; private set; }\n        public Wz_Structure WzStructure { get; private set; }\n        public Ms_Header Header { get; private set; }\n        public List<Ms_Entry> Entries { get; private set; }\n\n        Stream IMapleStoryFile.FileStream => this.BaseStream;\n        object IMapleStoryFile.ReadLock => this.BaseStream;\n\n        private bool leaveOpen;\n\n        private void Init(Stream baseStream, string originalFileName, Wz_Structure wzs, bool leaveOpen)\n        {\n            if (baseStream == null)\n            {\n                throw new ArgumentNullException(nameof(baseStream));\n            }\n            if (originalFileName == null)\n            {\n                throw new ArgumentNullException(nameof(originalFileName));\n            }\n\n            this.BaseStream = baseStream;\n            this.WzStructure = wzs;\n            this.leaveOpen = leaveOpen;\n            try\n            {\n                this.ReadHeader(originalFileName);\n            }\n            catch\n            {\n                if (!leaveOpen)\n                {\n                    baseStream.Dispose();\n                }\n                throw;\n            }\n            this.Entries = new List<Ms_Entry>(0);\n        }\n\n        private void ReadHeader(string fullFileName)\n        {\n            string fileName = Path.GetFileName(fullFileName).ToLower();\n            this.BaseStream.Position = 0;\n            using var bReader = new BinaryReader(this.BaseStream, Encoding.ASCII, true);\n\n            // 1. random bytes\n            int randByteCount = fileName.Sum(c => (int)c) % 312 + 30;\n            byte[] randBytes = bReader.ReadBytes(randByteCount);\n            for (int i = 0; i < randBytes.Length; ++i)\n            {\n                randBytes[i] = (byte)((sbyte)randBytes[i] >> 1);\n            }\n\n            // 2. check file version\n            const int supportedVersion = 4;\n            var version = bReader.ReadByte() ^ randBytes[0];\n            if (version != supportedVersion)\n                throw new Exception($\"Version check failed. (expected: {supportedVersion}, actual {version})\");\n\n            // 3. encrypted chacha20 key\n            int hashedSaltLen = bReader.ReadInt32();\n            int saltLen = (byte)hashedSaltLen ^ randBytes[0];\n            byte[] saltBytes = bReader.ReadBytes(saltLen * 2);\n            char[] saltChars = new char[saltLen];\n            for (int i = 0; i < saltLen; i++)\n            {\n                int a = randBytes[i] ^ saltBytes[i * 2];\n                int b = ((a | 0x4B) << 1) - a - 75;\n                saltChars[i] = (char)b;\n            }\n            string saltStr = new string(saltChars);\n\n            long headerStartPos = this.BaseStream.Position;\n\n            // 4. decrypt 8 bytes header with chacha20 cipher\n            // generate chacha20 key based on filename+keySalt\n            string fileNameWithSalt = fileName + saltStr;\n\n            Span<byte> chacha20Key = stackalloc byte[ChaCha20CryptoTransform.AllowedKeyLength];\n            for (int i = 0; i < chacha20Key.Length; ++i)\n            {\n                chacha20Key[i] = (byte)(fileNameWithSalt[i % fileNameWithSalt.Length] + i);\n                chacha20Key[i] ^= chacha20KeyObscure[i];\n            }\n\n            Span<byte> emptyNonce = stackalloc byte[ChaCha20CryptoTransform.AllowedNonceLength];\n            using var chacha20Cipher = new ChaCha20CryptoTransform(chacha20Key, emptyNonce, 0);\n            var chacha20DecoderStream = new CryptoStream(this.BaseStream, chacha20Cipher, CryptoStreamMode.Read);\n            var chacha20Reader = new BinaryReader(chacha20DecoderStream);\n            int hash = chacha20Reader.ReadInt32();\n            int entryCount = chacha20Reader.ReadInt32();\n\n            ReadOnlySpan<ushort> u16SaltBytes = MemoryMarshal.Cast<byte, ushort>(saltBytes);\n            // TODO: validate file hash\n\n            // 5. skip random bytes\n            long entryStartPos = headerStartPos + 8 + fileName.Select(v => (int)v * 3).Sum() % 212 + 64;\n            var header = new Ms_Header(fullFileName, saltStr, fileNameWithSalt, hash, version, entryCount, headerStartPos, entryStartPos);\n            this.Header = header;\n        }\n\n        public void ReadEntries()\n        {\n            if (this.Header == null || this.Header.EntryCount == 0 || this.Header.EntryCount == this.Entries.Count)\n            {\n                return;\n            }\n            this.Entries.Clear();\n            int entryCount = this.Header.EntryCount;\n            if (this.Entries.Capacity < entryCount)\n            {\n                this.Entries.Capacity = entryCount;\n            }\n\n            // decrypt with another snow key\n            string fileNameWithSalt = this.Header.FileNameWithSalt;\n            Span<byte> chacha20Key2 = stackalloc byte[ChaCha20CryptoTransform.AllowedKeyLength];\n            for (int i = 0; i < chacha20Key2.Length; ++i)\n            {\n                chacha20Key2[i] = (byte)(i + (i % 3 + 2) * fileNameWithSalt[fileNameWithSalt.Length - 1 - i % fileNameWithSalt.Length]);\n                chacha20Key2[i] ^= chacha20KeyObscure[i];\n            }\n            Span<byte> emptyNonce = stackalloc byte[ChaCha20CryptoTransform.AllowedNonceLength];\n            this.BaseStream.Position = this.Header.EntryStartPosition;\n            using var chacha20Reader = new ChaCha20Reader(this.BaseStream, chacha20Key2, emptyNonce, true);\n\n            for (int i = 0; i < entryCount; i++)\n            {\n                string entryName = chacha20Reader.ReadString();\n                int checkSum = chacha20Reader.ReadInt32();\n                int flags = chacha20Reader.ReadInt32();\n                int startPos = chacha20Reader.ReadInt32();\n                int size = chacha20Reader.ReadInt32();\n                int sizeAligned = chacha20Reader.ReadInt32();\n                int unk1 = chacha20Reader.ReadInt32();\n                int unk2 = chacha20Reader.ReadInt32();\n                byte[] entryKey = chacha20Reader.ReadBytes(16);\n                int unk3 = chacha20Reader.ReadInt32();\n                int unk4 = chacha20Reader.ReadInt32();\n\n                var entry = new Ms_Entry(entryName, checkSum, flags, startPos, size, sizeAligned, unk1, unk2, entryKey, unk3, unk4);\n                //TODO: calcuate ms_image checksum\n                this.Entries.Add(entry);\n            }\n\n            long dataStartPos = this.BaseStream.Position;\n            // align to 1024 bytes\n            if ((dataStartPos & 0x3ff) != 0)\n            {\n                dataStartPos = dataStartPos - (dataStartPos & 0x3ff) + 0x400;\n            }\n            this.Header.DataStartPosition = dataStartPos;\n            // recalculate startPos\n            foreach (var entry in this.Entries)\n            {\n                entry.StartPos = dataStartPos + entry.StartPos * 1024;\n            }\n        }\n\n        public void GetDirTree(Wz_Node parent)\n        {\n            foreach (var entry in this.Entries)\n            {\n                Wz_Node root = parent;\n                string[] fullPath = entry.Name.Split('/');\n                foreach (var segment in fullPath)\n                {\n                    root = root.Nodes[segment] ?? root.Nodes.Add(segment);\n                }\n\n                // always override existing value if already exists?\n                //if (root.Value == null)\n                {\n                    var msImage = new Ms_ImageV2(fullPath[fullPath.Length - 1], entry, this);\n                    root.Value = msImage;\n                    msImage.OwnerNode = root;\n                }\n            }\n        }\n\n        public void Close()\n        {\n            if (this.BaseStream != null)\n            {\n                if (!this.leaveOpen)\n                {\n                    this.BaseStream.Dispose();\n                }\n                this.BaseStream = null;\n            }\n        }\n\n        void IDisposable.Dispose()\n        {\n            this.Close();\n        }\n\n        internal static readonly byte[] chacha20KeyObscure = {\n            0x7B, 0x2F, 0x35, 0x48, 0x43, 0x95, 0x02, 0xB9,\n            0xAE, 0x91, 0xA6, 0xE1, 0xD8, 0xD6, 0x24, 0xB4,\n            0x33, 0x10, 0x1D, 0x3D, 0xC1, 0xBB, 0xC6, 0xF4,\n            0xA5, 0xFE, 0xB3, 0x69, 0x6B, 0x56, 0xE4, 0x75\n        };\n\n        internal class ChaCha20Reader : IDisposable\n        {\n            private readonly Stream baseStream;\n            private readonly ChaCha20CryptoTransform chacha20Cipher;\n\n            private byte[] _buffer;\n            private int _readed;\n            private bool leaveOpen;\n\n            public ChaCha20Reader(Stream baseStream, ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce, bool leaveOpen = false)\n            {\n                this.baseStream = baseStream;\n                this.leaveOpen = leaveOpen;\n                this.chacha20Cipher = new ChaCha20CryptoTransform(key, nonce, 0);\n                this._buffer = new byte[ChaCha20CryptoTransform.ProcessBytesAtTime];\n                this._readed = this._buffer.Length;\n            }\n\n            public byte[] ReadBytes(int count)\n            {\n                byte[] buffer = new byte[count];\n                this.ReadBytes(buffer);\n                return buffer;\n            }\n\n            public void ReadBytes(Span<byte> buffer)\n            {\n                while (buffer.Length > 0)\n                {\n                    if (this._readed >= this._buffer.Length)\n                    {\n                        this.baseStream.ReadExactly(this._buffer, 0, this._buffer.Length);\n                        this.chacha20Cipher.TransformBlock(this._buffer, 0, this._buffer.Length, this._buffer, 0);\n                        _readed = 0;\n                    }\n\n                    int readCount = Math.Min(buffer.Length, this._buffer.Length - this._readed);\n                    this._buffer.AsSpan(this._readed, readCount).CopyTo(buffer);\n                    buffer = buffer.Slice(readCount);\n                    this._readed += readCount;\n                }\n\n                if (_readed >= this._buffer.Length)\n                {\n                    this.ResetCounter();\n                }\n            }\n\n            public int ReadInt32()\n            {\n                Span<byte> temp = stackalloc byte[4];\n                this.ReadBytes(temp);\n                return MemoryMarshal.Read<int>(temp);\n            }\n\n            public string ReadString()\n            {\n                var strLen = this.ReadInt32();\n#if NET6_0_OR_GREATER\n                return string.Create(strLen, this, (strBuf, reader) =>\n                {\n                    reader.ReadBytes(MemoryMarshal.Cast<char, byte>(strBuf));\n                });\n#else\n                char[] strBuf = new char[strLen];\n                this.ReadBytes(MemoryMarshal.Cast<char, byte>(strBuf.AsSpan()));\n                return new string(strBuf);\n#endif\n            }\n\n            private void ResetCounter()\n            {\n                this.chacha20Cipher.State[12] = 0;\n            }\n\n            public void Dispose()\n            {\n                if (!this.leaveOpen)\n                {\n                    this.baseStream.Dispose();\n                }\n                this.chacha20Cipher.Dispose();\n                this._buffer = null;\n                this._readed = 0;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Ms_Header.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Ms_Header\n    {\n        public Ms_Header(string fullFileName, string keySalt, string fileNameWithSalt, int headerHash, int fileVer, int entryCount, long headerStartPos, long entryStartPos)\n        {\n            this.FullFileName = fullFileName;\n            this.KeySalt = keySalt;\n            this.FileNameWithSalt = fileNameWithSalt;\n            this.HeaderHash = headerHash;\n            this.Version = fileVer;\n            this.EntryCount = entryCount;\n            this.HeaderStartPosition = headerStartPos;\n            this.EntryStartPosition = entryStartPos;\n            this.DataStartPosition = -1;\n        }\n\n        public string FullFileName { get; private set; }\n        public string KeySalt { get; private set; }\n        public string FileNameWithSalt { get; private set; }\n        public int HeaderHash { get; private set; }\n        public int Version { get; private set; }\n        public int EntryCount { get; private set; }\n        public long HeaderStartPosition { get; private set; }\n        public long EntryStartPosition { get; private set; }\n\n        public long DataStartPosition { get; internal set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Ms_Image.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Security.Cryptography;\nusing WzComparerR2.WzLib.Cryptography;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Ms_Image : Wz_Image\n    {\n        public Ms_Image(string name, Ms_Entry msEntry, IMapleStoryFile msFile)\n            : base(name, msEntry.Size, msEntry.CheckSum, 0, 0, msFile)\n        {\n            this.MsEntry = msEntry;\n            this.Offset = msEntry.StartPos;\n        }\n\n        public Ms_Entry MsEntry { get; private set; }\n\n        public override int CalcCheckSum(Stream stream)\n        {\n            return this.MsEntry.CalculatedCheckSum;\n        }\n\n        public override Stream OpenRead()\n        {\n            // calc snow key for entry\n            uint keyHash = 0x811C9DC5;\n            foreach (var c in (this.WzFile as Ms_File)?.Header.KeySalt)\n            {\n                keyHash = (keyHash ^ c) * 0x1000193;\n            }\n            byte[] keyHashDigits = keyHash.ToString().Select(v => (byte)(v - '0')).ToArray();\n\n            Span<byte> imgKey = stackalloc byte[16];\n            string entryName = this.MsEntry.Name;\n            ReadOnlySpan<byte> entryKey = this.MsEntry.Key;\n            for (int i = 0; i < imgKey.Length; i++)\n            {\n                imgKey[i] = (byte)(i + entryName[i % entryName.Length] * (\n                    keyHashDigits[i % keyHashDigits.Length] % 2\n                    + entryKey[(keyHashDigits[(i + 2) % keyHashDigits.Length] + i) % entryKey.Length]\n                    + (keyHashDigits[(i + 1) % keyHashDigits.Length] + i) % 5\n                    ));\n            }\n\n            using var ps = new PartialStream(this.WzFile.FileStream, this.MsEntry.StartPos, this.MsEntry.SizeAligned, true);\n            var buffer = new byte[this.MsEntry.Size];\n            Span<byte> span = buffer;\n            ps.Position = 0;\n            var cs = new CryptoStream(ps, new Snow2CryptoTransform(imgKey, null, false), CryptoStreamMode.Read);\n\n            // decrypt initial 1024 bytes twice\n            {\n                var cs2 = new CryptoStream(cs, new Snow2CryptoTransform(imgKey, null, false), CryptoStreamMode.Read);\n                int dataLen = Math.Min(span.Length, 1024);\n                cs2.ReadExactly(span.Slice(0, dataLen));\n                span = span.Slice(dataLen);\n            }\n\n            // decrypt subsequent bytes\n            if (span.Length > 0)\n            {\n                cs.ReadExactly(span);\n            }\n\n            var ms = new MemoryStream(buffer);\n            return ms;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Ms_ImageV2.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\nusing WzComparerR2.WzLib.Cryptography;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Ms_ImageV2 : Wz_Image\n    {\n        public Ms_ImageV2(string name, Ms_Entry msEntry, IMapleStoryFile msFile)\n            : base(name, msEntry.Size, msEntry.CheckSum, 0, 0, msFile)\n        {\n            this.MsEntry = msEntry;\n            this.Offset = msEntry.StartPos;\n            this.IsChecksumChecked = true; // disable checksum check\n        }\n\n        public Ms_Entry MsEntry { get; private set; }\n\n        public override int CalcCheckSum(Stream stream)\n        {\n            return this.MsEntry.CalculatedCheckSum;\n        }\n\n        public override Stream OpenRead()\n        {\n            // calc chacha20 key for entry\n            uint keyHash = 0x811C9DC5;\n            foreach (var c in (this.WzFile as Ms_FileV2)?.Header.KeySalt)\n            {\n                keyHash = (keyHash ^ c) * 0x1000193;\n            }\n            uint keyHash2 = keyHash >> 1;\n            uint keyHash3 = keyHash2 ^ 0x6C;\n            uint keyHash4 = keyHash3 << 2; // not used\n            byte[] keyHashDigits = keyHash.ToString().Select(v => (byte)(v - '0')).ToArray();\n\n            // key\n            Span<byte> imgKey = stackalloc byte[32];\n            string entryName = this.MsEntry.Name;\n            ReadOnlySpan<byte> entryKey = this.MsEntry.Key;\n            for (int i = 0; i < imgKey.Length; i++)\n            {\n                imgKey[i] = (byte)(i + entryName[i % entryName.Length] * (\n                    keyHashDigits[i % keyHashDigits.Length] % 2\n                    + entryKey[(keyHashDigits[(i + 2) % keyHashDigits.Length] + i) % entryKey.Length]\n                    + (keyHashDigits[(i + 1) % keyHashDigits.Length] + i) % 5\n                    ));\n            }\n            for (int i = 0; i < imgKey.Length; ++i)\n            {\n                imgKey[i] ^= Ms_FileV2.chacha20KeyObscure[i];\n            }\n\n            // nonce and counter\n            Span<byte> keyHashData = stackalloc byte[12];\n            Span<uint> keyHashDataUInt32 = MemoryMarshal.Cast<byte, uint>(keyHashData);\n            keyHashDataUInt32[0] = keyHash;\n            keyHashDataUInt32[1] = keyHash2;\n            keyHashDataUInt32[2] = keyHash3;\n            for (int i = 0, a = 0, b = 0, c = 90, d = 0; i < 12; ++i)\n            {\n                keyHashData[i] ^= (byte)(d + 11 * ((uint)i / 11) + (c ^ ((uint)i >> 2)) + (a ^ b));\n                --d;\n                a += 8;\n                b += 17;\n                c += 43;\n            }\n            Span<byte> nonce = stackalloc byte[ChaCha20CryptoTransform.AllowedNonceLength];\n            keyHashData.Slice(0, 8).CopyTo(nonce.Slice(4));\n            uint counter = MemoryMarshal.Read<uint>(keyHashData.Slice(8, 4));\n\n            using var ps = new PartialStream(this.WzFile.FileStream, this.MsEntry.StartPos, this.MsEntry.SizeAligned, true);\n            int cryptedSize = Math.Min(this.MsEntry.Size, 1024);\n            var buffer = new byte[cryptedSize];\n            var part1 = new MemoryStream(buffer);\n            ps.Position = 0;\n\n            // decrypt initial 1024 bytes\n            {\n                var cs = new CryptoStream(ps, new ChaCha20CryptoTransform(imgKey, nonce, counter), CryptoStreamMode.Read);\n                cs.ReadExactly(buffer);\n            }\n\n            if (this.MsEntry.Size <= 1024)\n            {\n                return part1;\n            }\n            else\n            {\n                var part2 = new PartialStream(this.WzFile.FileStream, this.MsEntry.StartPos + 1024, this.MsEntry.SizeAligned - 1024, true);\n                return new ConcatenatedStream(part1, part2);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的常规信息通过以下\n// 特性集控制。更改这些特性值可修改\n// 与程序集关联的信息。\n[assembly: AssemblyTitle(\"WzComparerR2.WzLib\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"Kagamia Studio\")]\n[assembly: AssemblyProduct(\"WzComparerR2.WzLib\")]\n[assembly: AssemblyCopyright(\"Copyright © Kagamia Studio 2013-2025\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// 将 ComVisible 设置为 false 使此程序集中的类型\n// 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型，\n// 则将该类型上的 ComVisible 特性设置为 true。\n[assembly: ComVisible(false)]\n\n// 如果此项目向 COM 公开，则下列 GUID 用于类型库的 ID\n[assembly: Guid(\"fcd87fcc-803e-4fbc-9626-c7bee8288feb\")]\n\n// 程序集的版本信息由下面四个值组成:\n//\n//      主版本\n//      次版本 \n//      内部版本号\n//      修订号\n//\n// 可以指定所有这些值，也可以使用“内部版本号”和“修订号”的默认值，\n// 方法是按如下所示使用“*”:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"3.0.0.0\")]\n[assembly: AssemblyFileVersion(\"3.0.0.10725\")]\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/ChunkedEncryptedInputStream.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public class ChunkedEncryptedInputStream : Stream\n    {\n        private Stream baseStream;\n        private IWzDecrypter decrypter;\n        private bool leaveOpen;\n        private int nextChunkLength;\n        private int keyOffset;\n\n        public ChunkedEncryptedInputStream(Stream baseStream, IWzDecrypter decrypter, bool leaveOpen = false)\n        {\n            this.baseStream = baseStream;\n            this.decrypter = decrypter;\n            this.leaveOpen = leaveOpen;\n            this.nextChunkLength = 0;\n        }\n\n        public override bool CanRead => true;\n        public override bool CanSeek => false;\n        public override bool CanWrite => false;\n        public override long Length => throw new NotSupportedException();\n        public override long Position\n        {\n            get => throw new NotSupportedException();\n            set => throw new NotSupportedException();\n        }\n\n        public override void Flush()\n        {\n            throw new NotSupportedException();\n        }\n\n        public override int Read(byte[] buffer, int offset, int count)\n        {\n            if (buffer == null) throw new ArgumentNullException(nameof(buffer));\n            if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException();\n            return this.Read(buffer.AsSpan(offset, count));\n        }\n\n#if NETFRAMEWORK\n        private int Read(Span<byte> buffer)\n#elif NET6_0_OR_GREATER                  \n        public override int Read(Span<byte> buffer)\n#endif\n        {\n            int bytesRead = 0;\n\n            while (buffer.Length > 0)\n            {\n                int readLen;\n                if (this.nextChunkLength == 0)\n                {\n                    Span<byte> chunkLen = stackalloc byte[4];\n                    readLen = this.baseStream.ReadAvailableBytes(chunkLen);\n                    if (readLen == 0)\n                    {\n                        break;\n                    }\n                    else if (readLen != chunkLen.Length)\n                    {\n                        throw new IOException(\"Failed to read chunk length.\");\n                    }\n                    this.nextChunkLength = MemoryMarshal.Read<int>(chunkLen);\n                    if (this.nextChunkLength <= 0)\n                    {\n                        break;\n                    }\n                    this.keyOffset = 0;\n                }\n\n                readLen = this.baseStream.Read(buffer.Slice(0, Math.Min(buffer.Length, this.nextChunkLength)));\n                if (readLen == 0)\n                {\n                    break;\n                }\n                this.decrypter?.Decrypt(buffer.Slice(0, readLen), this.keyOffset);\n\n                this.keyOffset += readLen;\n                this.nextChunkLength -= readLen;\n                buffer = buffer.Slice(readLen);\n                bytesRead += readLen;\n            }\n\n            return bytesRead;\n        }\n\n        public override long Seek(long offset, SeekOrigin origin)\n        {\n            throw new NotSupportedException();\n        }\n\n        public override void SetLength(long value)\n        {\n            throw new NotSupportedException();\n        }\n\n        public override void Write(byte[] buffer, int offset, int count)\n        {\n            throw new NotSupportedException();\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (!this.leaveOpen)\n                {\n                    baseStream?.Dispose();\n                }\n            }\n            base.Dispose(disposing);\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/CollectionsMarshal.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n#if NETFRAMEWORK\n    internal static class CollectionsMarshal\n    {\n        public static Span<T> AsSpan<T>(List<T> list)\n        {\n            Span<T> result = default(Span<T>);\n            if (list != null)\n            {\n                int size = list.Count;\n                T[] items = typeof(List<T>).GetField(\"_items\", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(list) as T[];\n                if (items == null || (uint)size > (uint)items.Length)\n                {\n                    throw new InvalidOperationException();\n                }\n                return items.AsSpan(0, size);\n            }\n            return result;\n        }\n    }\n#endif\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/ConcatenatedStream.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public class ConcatenatedStream : Stream\n    {\n        private readonly Stream first;\n        private readonly Stream second;\n        private readonly bool canSeek;\n        private readonly bool leaveOpen;\n        private long position;\n        private Stream current;\n\n        public ConcatenatedStream(Stream first, Stream second, bool leaveOpen = false)\n        {\n            this.first = first ?? throw new ArgumentNullException(nameof(first));\n            this.second = second ?? throw new ArgumentNullException(nameof(second));\n            this.canSeek = first.CanSeek && second.CanSeek;\n            this.leaveOpen = leaveOpen;\n            this.current = first;\n            this.position = 0;\n        }\n\n        public override bool CanRead => this.first.CanRead && this.second.CanRead;\n        public override bool CanSeek => this.canSeek;\n        public override bool CanWrite => false;\n        public override long Length => this.first.Length + this.second.Length;\n        public override long Position\n        {\n            get\n            {\n                if (!this.canSeek)\n                {\n                    throw new NotSupportedException();\n                }\n                return this.position;\n            }\n            set\n            {\n                if (!this.canSeek)\n                {\n                    throw new NotSupportedException();\n                }\n                this.Seek(value, SeekOrigin.Begin);\n            }\n        }\n\n        public override void Flush() => throw new NotSupportedException();\n        public override int Read(byte[] buffer, int offset, int count)\n        {\n            int totalRead = 0;\n\n            if (this.canSeek)\n            {\n                if (this.position < this.first.Length)\n                {\n                    this.first.Position = this.position;\n                    int readBytes = this.first.Read(buffer, offset, count);\n                    this.position += readBytes;\n                    totalRead += readBytes;\n                    offset += readBytes;\n                    count -= readBytes;\n                }\n\n                if (count > 0 && this.position >= this.first.Length)\n                {\n                    this.second.Position = this.position - this.first.Length;\n                    int readBytes = this.second.Read(buffer, offset, count);\n                    this.position += readBytes;\n                    totalRead += readBytes;\n                }\n            }\n            else\n            {\n                int readBytes = this.current.Read(buffer, offset, count);\n                totalRead += readBytes;\n                offset += readBytes;\n                count -= readBytes;\n                if (count > 0 && this.current == first)\n                {\n                    this.current = this.second;\n                    readBytes = this.current.Read(buffer, offset, count);\n                    totalRead += readBytes;\n                }\n            }\n\n            return totalRead;\n        }\n\n        public override long Seek(long offset, SeekOrigin origin)\n        {\n            if (!this.CanSeek)\n            {\n                throw new NotSupportedException();\n            }\n\n            long newPos = origin switch\n            {\n                SeekOrigin.Begin => offset,\n                SeekOrigin.Current => this.position + offset,\n                SeekOrigin.End => this.Length + offset,\n                _ => throw new ArgumentOutOfRangeException(nameof(origin), \"Invalid SeekOrigin\"),\n            };\n\n            if (newPos < 0 || newPos > this.Length)\n                throw new ArgumentOutOfRangeException(nameof(offset), \"Seek position out of bounds\");\n\n            this.position = newPos;\n            return this.position;\n        }\n\n        public override void SetLength(long value) => throw new NotSupportedException();\n\n        public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (!this.leaveOpen)\n                {\n                    first.Dispose();\n                    second.Dispose();\n                }\n            }\n            this.current = null;\n            base.Dispose(disposing);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/IWzDecrypter.cs",
    "content": "﻿using System;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public interface IWzDecrypter\n    {\n        byte this[int index] { get; }\n        void Decrypt(Span<byte> data);\n        void Decrypt(Span<byte> data, int keyOffset);\n        void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer);\n        void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer, int keyOffset);\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/IWzStringPool.cs",
    "content": "﻿using System;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public interface IWzStringPool\n    {\n        bool TryGet(long offset, out string s);\n        string GetOrAdd(long offset, ReadOnlySpan<Char> chars);\n        void Reset();\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/ImageCodec.cs",
    "content": "﻿using System;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Diagnostics;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public static class ImageCodec\n    {\n        public static void BGRA4444ToBGRA32(ReadOnlySpan<byte> bgra4444Pixels, Span<byte> outputBgraPixels)\n        {\n#if NET6_0_OR_GREATER\n            /*\n                      0        1        2        3\n              data    ggggbbbb aaaarrrr -------- --------\n              xmm0 = unpack_low(data, data)\n                      ggggbbbb ggggbbbb aaaarrrr aaaarrrr\n              xmm1 = (ushort[])xmm0 >> 4\n                      bbbbgggg 0000gggg rrrraaaa 0000aaaa\n              xmm0 &= 0F F0 0F F0\n                      0000bbbb gggg0000 0000rrrr aaaa0000\n              xmm1 &= F0 0F F0 0F\n                      bbbb0000 0000gggg rrrr0000 0000aaaa\n              xmm0 |= xmm1\n                      bbbbbbbb gggggggg rrrrrrrr aaaaaaaa\n            */\n            if (bgra4444Pixels.Length >= 16 && Avx2.IsSupported)\n            {\n                var mask0 = Vector256.Create(\n                        0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0,\n                        0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0,\n                        0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0,\n                        0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0);\n                var mask1 = Vector256.Create(\n                        0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f,\n                        0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f,\n                        0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f,\n                        0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f);\n                Vector128<byte> xmm;\n\n                unsafe\n                {\n                    while (bgra4444Pixels.Length >= 16)\n                    {\n                        fixed (byte* pInput = bgra4444Pixels)\n                            xmm = Sse2.LoadVector128(pInput);\n                        var ymm0 = Vector256.Create(Avx.UnpackLow(xmm, xmm), Avx.UnpackHigh(xmm, xmm));\n                        var ymm1 = Avx2.ShiftRightLogical(ymm0.AsUInt16(), 4).AsByte();\n                        var ymm2 = Avx2.Or(Avx2.And(ymm0, mask0), Avx2.And(ymm1, mask1));\n                        fixed (byte* pOutput = outputBgraPixels)\n                            Avx.Store(pOutput, ymm2);\n                        bgra4444Pixels = bgra4444Pixels.Slice(16);\n                        outputBgraPixels = outputBgraPixels.Slice(32);\n                    }\n                }\n            }\n            if (bgra4444Pixels.Length >= 8 && Sse2.IsSupported)\n            {\n                var mask0 = Vector128.Create(\n                        (byte)0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0,\n                        0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0);\n                var mask1 = Vector128.Create(\n                        (byte)0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f,\n                        0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f);\n\n                unsafe\n                {\n                    while (bgra4444Pixels.Length >= 8)\n                    {\n                        Vector128<byte> xmm;\n                        fixed (byte* pInput = bgra4444Pixels)\n                            xmm = Sse2.LoadScalarVector128((long*)pInput).AsByte();\n                        var xmm0 = Sse2.UnpackLow(xmm, xmm);\n                        var xmm1 = Sse2.ShiftRightLogical(xmm0.AsUInt16(), 4).AsByte();\n                        var xmm2 = Sse2.Or(Sse2.And(xmm0, mask0), Sse2.And(xmm1, mask1));\n                        fixed (byte* pOutput = outputBgraPixels)\n                            Sse2.Store(pOutput, xmm2);\n                        bgra4444Pixels = bgra4444Pixels.Slice(8);\n                        outputBgraPixels = outputBgraPixels.Slice(16);\n                    }\n                }\n            }\n#endif\n            int p;\n            for (int i = 0; i < bgra4444Pixels.Length; i++)\n            {\n                p = bgra4444Pixels[i] & 0x0F; p |= (p << 4); outputBgraPixels[i * 2] = (byte)p;\n                p = bgra4444Pixels[i] & 0xF0; p |= (p >> 4); outputBgraPixels[i * 2 + 1] = (byte)p;\n            }\n        }\n\n        public static void ScalePixels(ReadOnlySpan<byte> srcPixels, int bytesPerPixel, int width, int stride, int height, int scaleX, int scaleY, Span<byte> outputPixels, int outputStride)\n        {\n            for (int y = 0; y < height; y++)\n            {\n                ReadOnlySpan<byte> srcRow = srcPixels.Slice(0, stride);\n                Span<byte> dstRow = outputPixels.Slice(0, outputStride);\n                // copy pixels\n                for (int x = 0; x < width; x++)\n                {\n                    ReadOnlySpan<byte> srcPixel = srcRow.Slice(0, bytesPerPixel);\n                    int writeBytes = 0;\n                    for (int s = 0; s < scaleX; s++)\n                    {\n                        srcPixel.CopyTo(dstRow.Slice(writeBytes));\n                        writeBytes += bytesPerPixel;\n                    }\n                    srcRow = srcRow.Slice(bytesPerPixel);\n                    dstRow = dstRow.Slice(writeBytes);\n                }\n                srcPixels = srcPixels.Slice(stride);\n\n                // duplicate rows\n                int rowSize = width * bytesPerPixel * scaleX;\n                for (int s = 1; s < scaleY; s++)\n                {\n                    outputPixels.Slice(0, rowSize).CopyTo(outputPixels.Slice(outputStride * s, rowSize));\n                }\n                outputPixels = outputPixels.Slice(scaleY * outputStride);\n            }\n        }\n\n        public static void DXT3ToBGRA32(ReadOnlySpan<byte> blockData, Span<byte> outputBgraPixels, int width, int stride, int height)\n        {\n            Span<ColorBgra> colorTable = stackalloc ColorBgra[4];\n            Span<int> colorIdxTable = stackalloc int[16];\n            Span<byte> alphaTable = stackalloc byte[16];\n            Span<ColorBgra> outputPixels = stackalloc ColorBgra[16];\n\n            for (int y = 0; y < height; y += 4)\n            {\n                for (int x = 0; x < width; x += 4)\n                {\n                    ExpandAlphaTableDXT3(blockData.Slice(0, 8), alphaTable);\n                    ReadOnlySpan<ushort> baseColor = MemoryMarshal.Cast<byte, ushort>(blockData.Slice(8, 4));\n                    ExpandColorTable(baseColor[0], baseColor[1], colorTable);\n                    ExpandColorIndexTable(blockData.Slice(12, 4), colorIdxTable);\n                    for(int i = 0; i < 16; i++)\n                    {\n                        outputPixels[i] = new ColorBgra(alphaTable[i], colorTable[colorIdxTable[i]]);\n                    }\n\n                    ReadOnlySpan<byte> bgraBytes = MemoryMarshal.AsBytes(outputPixels);\n                    bgraBytes.Slice(0, 16).CopyTo(outputBgraPixels.Slice(y * stride + x * 4));\n                    bgraBytes.Slice(16, 16).CopyTo(outputBgraPixels.Slice((y + 1) * stride + x * 4));\n                    bgraBytes.Slice(32, 16).CopyTo(outputBgraPixels.Slice((y + 2) * stride + x * 4));\n                    bgraBytes.Slice(48, 16).CopyTo(outputBgraPixels.Slice((y + 3) * stride + x * 4));\n\n                    blockData = blockData.Slice(16);\n                }\n            }\n        }\n\n        public static void DXT5ToBGRA32(ReadOnlySpan<byte> blockData, Span<byte> outputBgraPixels, int width, int stride, int height)\n        {\n            Span<ColorBgra> colorTable = stackalloc ColorBgra[4];\n            Span<int> colorIdxTable = stackalloc int[16];\n            Span<byte> alphaTable = stackalloc byte[8];\n            Span<int> alphaIdxTable = stackalloc int[16];\n            Span<ColorBgra> outputPixels = stackalloc ColorBgra[16];\n\n            for (int y = 0; y < height; y += 4)\n            {\n                for (int x = 0; x < width; x += 4)\n                {\n                    ExpandAlphaTableDXT5(blockData[0], blockData[1], alphaTable);\n                    ExpandAlphaIndexTableDXT5(blockData.Slice(2, 6), alphaIdxTable);\n                    ReadOnlySpan<ushort> baseColor = MemoryMarshal.Cast<byte, ushort>(blockData.Slice(8, 4));\n                    ExpandColorTable(baseColor[0], baseColor[1], colorTable);\n                    ExpandColorIndexTable(blockData.Slice(12, 4), colorIdxTable);\n                    for (int i = 0; i < 16; i++)\n                    {\n                        outputPixels[i] = new ColorBgra(alphaTable[alphaIdxTable[i]], colorTable[colorIdxTable[i]]);\n                    }\n\n                    ReadOnlySpan<byte> bgraBytes = MemoryMarshal.AsBytes(outputPixels);\n                    bgraBytes.Slice(0, 16).CopyTo(outputBgraPixels.Slice(y * stride + x * 4));\n                    bgraBytes.Slice(16, 16).CopyTo(outputBgraPixels.Slice((y + 1) * stride + x * 4));\n                    bgraBytes.Slice(32, 16).CopyTo(outputBgraPixels.Slice((y + 2) * stride + x * 4));\n                    bgraBytes.Slice(48, 16).CopyTo(outputBgraPixels.Slice((y + 3) * stride + x * 4));\n\n                    blockData = blockData.Slice(16);\n                }\n            }\n        }\n\n        public static void BC7ToRGBA32(ReadOnlySpan<byte> blockData, int srcStride, Span<byte> outputRgbaPixels, int width, int stride, int height)\n        {\n            Span<BC7Decomp.color_rgba> rgba = stackalloc BC7Decomp.color_rgba[16];\n            Span<byte> rgbaBytes = MemoryMarshal.AsBytes(rgba);\n\n            int blocksPerRow = width & ~3;\n            for (int y = 0, dataStart = 0; y < height; y += 4, dataStart += srcStride)\n            {\n                ReadOnlySpan<byte> dataRow = blockData.Slice(dataStart, srcStride);\n                for (int x = 0; x < width; x += 4)\n                {\n                    BC7Decomp.unpack_bc7(dataRow, rgba);\n                    rgbaBytes.Slice(0, 16).CopyTo(outputRgbaPixels.Slice(y * stride + x * 4));\n                    rgbaBytes.Slice(16, 16).CopyTo(outputRgbaPixels.Slice((y + 1) * stride + x * 4));\n                    rgbaBytes.Slice(32, 16).CopyTo(outputRgbaPixels.Slice((y + 2) * stride + x * 4));\n                    rgbaBytes.Slice(48, 16).CopyTo(outputRgbaPixels.Slice((y + 3) * stride + x * 4));\n                    dataRow = dataRow.Slice(16);\n                }\n            }\n        }\n\n        public static void RGBA32ToBGRA32(ReadOnlySpan<byte> rgbaPixels, Span<byte> outputBgraPixels)\n        {\n#if NET6_0_OR_GREATER\n            if (Avx2.IsSupported && rgbaPixels.Length >= 32)\n            {\n                var mask = Vector256.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15,\n                    2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15);\n                unsafe\n                {\n                    Vector256<byte> ymm;\n                    while (rgbaPixels.Length >= 32)\n                    {\n                        fixed (byte* pInput = rgbaPixels)\n                            ymm = Avx2.LoadVector256(pInput);\n                        ymm = Avx2.Shuffle(ymm, mask);\n                        fixed (byte* pOutput = outputBgraPixels)\n                            Avx.Store(pOutput, ymm);\n                        rgbaPixels = rgbaPixels.Slice(32);\n                        outputBgraPixels = outputBgraPixels.Slice(32);\n                    }\n                }\n            }\n            if (Ssse3.IsSupported && rgbaPixels.Length >= 16)\n            {\n                var mask = Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15);\n                unsafe\n                {\n                    Vector128<byte> xmm;\n                    while (rgbaPixels.Length >= 16)\n                    {\n                        fixed (byte* pInput = rgbaPixels)\n                            xmm = Sse2.LoadVector128(pInput);\n                        xmm = Ssse3.Shuffle(xmm, mask);\n                        fixed (byte* pOutput = outputBgraPixels)\n                            Sse2.Store(pOutput, xmm);\n                        rgbaPixels = rgbaPixels.Slice(16);\n                        outputBgraPixels = outputBgraPixels.Slice(16);\n                    }\n                }\n            }\n#endif\n            if (rgbaPixels.Length >= 4)\n            {\n                Span<byte> buffer = stackalloc byte[4];\n                while (rgbaPixels.Length >= 4)\n                {\n                    rgbaPixels.Slice(0, 4).CopyTo(buffer);\n                    var temp = buffer[0];\n                    buffer[0] = buffer[2];\n                    buffer[2] = temp;\n                    buffer.CopyTo(outputBgraPixels);\n\n                    rgbaPixels = rgbaPixels.Slice(4);\n                    outputBgraPixels = outputBgraPixels.Slice(4);\n                }\n            }\n        }\n\n        public static void R10G10B10A2ToBGRA32(ReadOnlySpan<byte> r10g10b10a2Pixels, Span<byte> outputBgraPixels)\n        {\n            ReadOnlySpan<uint> input32 = MemoryMarshal.Cast<byte, uint>(r10g10b10a2Pixels);\n            Span<uint> output32 = MemoryMarshal.Cast<byte, uint>(outputBgraPixels);\n#if NET6_0_OR_GREATER\n            // we know that shift right and shift left instructions can be merged, but for better readiness we won't deep optimize it.\n            if (input32.Length >= 8 && Avx2.IsSupported)\n            {\n                var maskR = Vector256.Create(0x3ffu);\n                var maskG = Vector256.Create(0x3ffu << 10);\n                var maskB = Vector256.Create(0x3ffu << 20);\n                var maskA = Vector256.Create(0x3ffu << 30);\n\n                unsafe\n                {\n                    while (input32.Length >= 8)\n                    {\n                        Vector256<uint> pixels;\n                        fixed (uint* pInput = input32)\n                            pixels = Avx.LoadVector256(pInput);\n                        Vector256<uint> r = Avx2.And(pixels, maskR);\n                        Vector256<uint> g = Avx2.And(pixels, maskG);\n                        Vector256<uint> b = Avx2.And(pixels, maskB);\n                        Vector256<uint> a = Avx2.And(pixels, maskA);\n                        r = Avx2.ShiftRightLogical(r, 2); // r>>2\n                        g = Avx2.ShiftRightLogical(g, 12); // g>>10>>2\n                        b = Avx2.ShiftRightLogical(b, 22); // b>>20>>2\n                        a = Avx2.MultiplyLow(Avx2.ShiftRightLogical(a, 30), Vector256.Create(85u)); //(a>>30)*255/3\n\n                        Vector256<uint> result = Avx2.Or(Avx2.Or(b, Avx2.ShiftLeftLogical(g, 8)), Avx2.Or(Avx2.ShiftLeftLogical(r, 16), Avx2.ShiftLeftLogical(a, 24)));\n                        fixed (uint* pOutput = output32)\n                            Avx.Store(pOutput, result);\n\n                        input32 = input32.Slice(8);\n                        output32 = output32.Slice(8);\n                    }\n                }\n            }\n\n            if (input32.Length >= 4 && Sse41.IsSupported)\n            {\n                var maskR = Vector128.Create(0x3ffu);\n                var maskG = Vector128.Create(0x3ffu << 10);\n                var maskB = Vector128.Create(0x3ffu << 20);\n                var maskA = Vector128.Create(0x3u << 30);\n\n                unsafe\n                {\n                    while (input32.Length >= 4)\n                    {\n                        Vector128<uint> pixels;\n                        fixed (uint* pInput = input32)\n                            pixels = Sse2.LoadVector128(pInput);\n                        Vector128<uint> r = Sse2.And(pixels, maskR);\n                        Vector128<uint> g = Sse2.And(pixels, maskG);\n                        Vector128<uint> b = Sse2.And(pixels, maskB);\n                        Vector128<uint> a = Sse2.And(pixels, maskA);\n                        r = Sse2.ShiftRightLogical(r, 2); // r>>2\n                        g = Sse2.ShiftRightLogical(g, 12); // g>>10>>2\n                        b = Sse2.ShiftRightLogical(b, 22); // b>>20>>2\n                        a = Sse41.MultiplyLow(Sse2.ShiftRightLogical(a, 30), Vector128.Create(85u)); //(a>>30)*255/3\n\n                        Vector128<uint> result = Sse2.Or(Sse2.Or(b, Sse2.ShiftLeftLogical(g, 8)), Sse2.Or(Sse2.ShiftLeftLogical(r, 16), Sse2.ShiftLeftLogical(a, 24)));\n                        fixed (uint* pOutput = output32)\n                            Sse2.Store(pOutput, result);\n\n                        input32 = input32.Slice(4);\n                        output32 = output32.Slice(4);\n                    }\n                }\n            }\n#endif\n            for (int i = 0; i < input32.Length; i++)\n            {\n                // rrrrrrrr rrgggggg ggggbbbb bbbbbbaa\n                uint pixel = input32[i];\n                uint r = pixel & 0x3ff;\n                uint g = (pixel >> 10) & 0x3ff;\n                uint b = (pixel >> 20) & 0x3ff;\n                uint a = (pixel >> 30) & 0x03;\n\n                uint r8 = r >> 2;\n                uint g8 = g >> 2;\n                uint b8 = b >> 2;\n                uint a8 = a * 85;\n                uint outputPixel = b8 | (g8 << 8) | (r8 << 16) | (a8 << 24);\n                output32[i] = outputPixel;\n            }\n        }\n\n        public static void R16ToBGRA64(ReadOnlySpan<byte> r16Pixels, Span<byte> outputBgraPixels)\n        {\n#if NET6_0_OR_GREATER\n            //    0                        8                        16                       24\n            //    r1 r1 r2 r2 r3 r3 r4 r4  r1 r1 r2 r2 r3 r3 r4 r4  r1 r1 r2 r2 r3 r3 r4 r4  r1 r1 r2 r2 r3 r3 r4 r4\n            // => 00 00 00 00 r1 r1 ff ff  00 00 00 00 r2 r2 ff ff  00 00 00 00 r3 r3 ff ff  00 00 00 00 r4 r4 ff ff\n            if (r16Pixels.Length >= 8 && Avx2.IsSupported)\n            {\n                var mask1 = Vector256.Create((byte)255, 255, 255, 255, 0, 1, 255, 255, \n                    255, 255, 255, 255, 2, 3, 255, 255,\n                    255, 255, 255, 255, 4, 5, 255, 255, \n                    255, 255, 255, 255, 6, 7, 255, 255);\n                var mask2 = Vector256.Create(0xffff_0000_0000_0000u).AsByte();\n                unsafe\n                {\n                    while (r16Pixels.Length >= 8)\n                    {\n                        ulong vec64 = MemoryMarshal.Read<ulong>(r16Pixels);\n                        var ymm0 = Vector256.Create(vec64);\n                        var ymm1 = Avx2.Shuffle(ymm0.AsByte(), mask1);\n                        ymm1 = Avx2.Or(ymm1, mask2);\n                        fixed (byte* pOutput = outputBgraPixels)\n                            Avx.Store(pOutput, ymm1);\n                        r16Pixels = r16Pixels.Slice(8);\n                        outputBgraPixels = outputBgraPixels.Slice(32);\n                    }\n                }\n            }\n            if (r16Pixels.Length >= 4 && Ssse3.IsSupported)\n            {\n                //    0                        8                      \n                //    r1 r1 r2 r2 r1 r1 r2 r2  r1 r1 r2 r2 r1 r1 r2 r2\n                // => 00 00 00 00 r1 r1 ff ff  00 00 00 00 r2 r2 ff ff\n                var mask1 = Vector128.Create((byte)255, 255, 255, 255, 0, 1, 255, 255, \n                    255, 255, 255, 255, 2, 3, 255, 255);\n                var mask2 = Vector128.Create(0xffff_0000_0000_0000u).AsByte();\n                unsafe\n                {\n                    while (r16Pixels.Length >= 4)\n                    {\n                        uint vec32 = MemoryMarshal.Read<uint>(r16Pixels);\n                        var xmm0 = Vector128.Create(vec32);\n                        var xmm1 = Ssse3.Shuffle(xmm0.AsByte(), mask1);\n                        xmm1 = Sse2.Or(xmm1, mask2);\n                        fixed (byte* pOutput = outputBgraPixels)\n                            Sse2.Store(pOutput, xmm1);\n                        r16Pixels = r16Pixels.Slice(4);\n                        outputBgraPixels = outputBgraPixels.Slice(16);\n                    }\n                }\n            }\n#endif\n            if (r16Pixels.Length >= 2)\n            {\n                while (r16Pixels.Length >= 2)\n                {\n                    ulong pixel = 0xffff0000_00000000 | ((ulong)MemoryMarshal.Read<ushort>(r16Pixels) << 32);\n                    MemoryMarshal.Write(outputBgraPixels, ref pixel);\n                    r16Pixels = r16Pixels.Slice(2);\n                    outputBgraPixels = outputBgraPixels.Slice(8);\n                }\n            }\n        }\n\n        #region DXT1 Color\n        private static ColorBgra RGB565ToBGRA32(ushort val)\n        {\n            const uint rgb565_mask_r = 0xf800;\n            const uint rgb565_mask_g = 0x07e0;\n            const uint rgb565_mask_b = 0x001f;\n            uint r = (val & rgb565_mask_r) >> 11;\n            uint g = (val & rgb565_mask_g) >> 5;\n            uint b = (val & rgb565_mask_b);\n            r = (r << 3) | (r >> 2);\n            g = (g << 2) | (g >> 4);\n            b = (b << 3) | (b >> 2);\n            return new ColorBgra(0xff, (byte)r, (byte)g, (byte)b);\n        }\n\n        private struct ColorBgra\n        {\n            public ColorBgra(uint value)\n            {\n                this.Value = value;\n            }\n\n            public ColorBgra(byte a, ColorBgra baseColor)\n            {\n                this.Value = (uint)(a << 24) | (baseColor.Value & 0x00ffffffu);\n            }\n\n            public ColorBgra(byte a, byte r, byte g, byte b)\n            {\n                this.Value = (uint)((a << 24) | (r << 16) | (g << 8) | b);\n            }\n\n            public uint Value { get; set; }\n\n            public byte B => (byte)(this.Value);\n            public byte G => (byte)(this.Value >> 8);\n            public byte R => (byte)(this.Value >> 16);\n            public byte A => (byte)(this.Value >> 24);\n        }\n\n        private static void ExpandColorTable(ushort c0, ushort c1, Span<ColorBgra> colorTable)\n        {\n            colorTable[0] = RGB565ToBGRA32(c0);\n            colorTable[1] = RGB565ToBGRA32(c1);\n            if (c0 > c1)\n            {\n                colorTable[2] = new ColorBgra(0xff,\n                    (byte)((colorTable[0].R * 2 + colorTable[1].R + 1) / 3),\n                    (byte)((colorTable[0].G * 2 + colorTable[1].G + 1) / 3),\n                    (byte)((colorTable[0].B * 2 + colorTable[1].B + 1) / 3));\n                colorTable[3] = new ColorBgra(0xff,\n                    (byte)((colorTable[0].R + colorTable[1].R * 2 + 1) / 3),\n                    (byte)((colorTable[0].G + colorTable[1].G * 2 + 1) / 3),\n                    (byte)((colorTable[0].B + colorTable[1].B * 2 + 1) / 3));\n            }\n            else\n            {\n                colorTable[2] = new ColorBgra(0xff,\n                    (byte)((colorTable[0].R + colorTable[1].R) / 2),\n                    (byte)((colorTable[0].G + colorTable[1].G) / 2),\n                    (byte)((colorTable[0].B + colorTable[1].B) / 2));\n                colorTable[3] = new ColorBgra(0xff, 0, 0, 0);\n            }\n        }\n\n        private static void ExpandColorIndexTable(ReadOnlySpan<byte> blockData, Span<int> colorIndexTable)\n        {\n            for (int i = 0, j = 0; i < 16; i += 4, j++)\n            {\n                colorIndexTable[i + 0] = (blockData[j] & 0x03);\n                colorIndexTable[i + 1] = (blockData[j] & 0x0c) >> 2;\n                colorIndexTable[i + 2] = (blockData[j] & 0x30) >> 4;\n                colorIndexTable[i + 3] = (blockData[j] & 0xc0) >> 6;\n            }\n        }\n        #endregion\n\n        #region DXT3/DXT5 Alpha\n        private static void ExpandAlphaTableDXT3(ReadOnlySpan<byte> blockData, Span<byte> alphaTable)\n        {\n            for (int i = 0, j = 0; i < 16; i += 2, j++)\n            {\n                alphaTable[i + 0] = (byte)(blockData[j] & 0x0f);\n                alphaTable[i + 1] = (byte)((blockData[j] & 0xf0) >> 4);\n            }\n            for (int i = 0; i < 16; i++)\n            {\n                alphaTable[i] = (byte)(alphaTable[i] | (alphaTable[i] << 4));\n            }\n        }\n\n        private static void ExpandAlphaTableDXT5(byte a0, byte a1, Span<byte> alphaTable)\n        {\n            alphaTable[0] = a0;\n            alphaTable[1] = a1;\n            if (a0 > a1)\n            {\n                for (int i = 2; i < 8; i++)\n                {\n                    alphaTable[i] = (byte)(((8 - i) * a0 + (i - 1) * a1 + 3) / 7);\n                }\n            }\n            else\n            {\n                for (int i = 2; i < 6; i++)\n                {\n                    alphaTable[i] = (byte)(((6 - i) * a0 + (i - 1) * a1 + 2) / 5);\n                }\n                alphaTable[6] = 0;\n                alphaTable[7] = 255;\n            }\n        }\n\n        private static void ExpandAlphaIndexTableDXT5(ReadOnlySpan<byte> blockData, Span<int> alphaIndexTable)\n        {\n            for (int i = 0, i2 = 0; i < 16; i += 8, i2 += 3)\n            {\n                int flags = blockData[i2 + 0]\n                    | (blockData[i2 + 1] << 8)\n                    | (blockData[i2 + 2] << 16);\n                for (int j = 0, j2 = 0; j < 8; j++, j2 += 3)\n                {\n                    int mask = 0x07 << j2;\n                    alphaIndexTable[i + j] = (flags & mask) >> j2;\n                }\n            }\n        }\n        #endregion\n    }\n\n    #region BC7\n    /// <summary>\n    /// This class is ported from https://github.com/richgel999/bc7enc_rdo/blob/master/bc7decomp.cpp under MIT license.\n    /// </summary>\n    /// <remarks>\n    /// File: bc7decomp.c - Richard Geldreich, Jr. 3/31/2020\n    /// </remarks>\n    internal static class BC7Decomp\n    {\n        public struct color_rgba\n        {\n            public byte r;\n            public byte g;\n            public byte b;\n            public byte a;\n\n            public ref byte this[int index]\n            {\n                get => ref this.AsBytes()[index];\n            }\n\n            private Span<byte> AsBytes()\n            {\n#if NET6_0_OR_GREATER\n            var pThis = MemoryMarshal.CreateSpan(ref this, 1);\n            return MemoryMarshal.Cast<color_rgba, byte>(pThis);\n#else\n                unsafe\n                {\n                    fixed (color_rgba* p = &this)\n                    {\n                        return new Span<byte>(p, sizeof(color_rgba));\n                    }\n                }\n#endif\n            }\n\n            public void set_noclamp_rgba(uint vr, uint vg, uint vb, uint va) => this.set(vr, vg, vb, va);\n            private void set(uint vr, uint vg, uint vb, uint va) { r = (byte)vr; g = (byte)vg; b = (byte)vb; a = (byte)va; }\n        }\n\n#if NET6_0_OR_GREATER\n        static readonly Vector128<short>[] g_bc7_weights4_sse2 = {\n            Vector128.Create(0, 0, 0, 0, 4, 4, 4, 4),\n            Vector128.Create(9, 9, 9, 9, 13, 13, 13, 13),\n            Vector128.Create(17, 17, 17, 17, 21, 21, 21, 21),\n            Vector128.Create(26, 26, 26, 26, 30, 30, 30, 30),\n            Vector128.Create(34, 34, 34, 34, 38, 38, 38, 38),\n            Vector128.Create(43, 43, 43, 43, 47, 47, 47, 47),\n            Vector128.Create(51, 51, 51, 51, 55, 55, 55, 55),\n            Vector128.Create(60, 60, 60, 60, 64, 64, 64, 64),\n        };\n#endif\n\n        static readonly uint[] g_bc7_weights2 = { 0, 21, 43, 64 };\n        static readonly uint[] g_bc7_weights3 = { 0, 9, 18, 27, 37, 46, 55, 64 };\n        static readonly uint[] g_bc7_weights4 = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 };\n\n        static readonly byte[] g_bc7_partition2 =\n        {\n            0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,        0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,        0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,        0,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1,        0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1,        0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1,        0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1,        0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1,\n            0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,        0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,        0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,        0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,        0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,        0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,        0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,        0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,\n            0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,1,        0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,        0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0,        0,1,1,1,0,0,1,1,0,0,0,1,0,0,0,0,        0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,        0,0,0,0,1,0,0,0,1,1,0,0,1,1,1,0,        0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,        0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,\n            0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,        0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0,        0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,        0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,        0,0,0,1,0,1,1,1,1,1,1,0,1,0,0,0,        0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,        0,1,1,1,0,0,0,1,1,0,0,0,1,1,1,0,        0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0,\n            0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,        0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,        0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,        0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0,        0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,        0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0,        0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,        0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1,\n            0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,0,        0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0,        0,0,1,1,0,0,1,0,0,1,0,0,1,1,0,0,        0,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0,        0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,        0,0,1,1,1,1,0,0,1,1,0,0,0,0,1,1,        0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,        0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,\n            0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0,        0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,        0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,        0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0,        0,1,1,0,1,1,0,0,1,0,0,1,0,0,1,1,        0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1,        0,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0,        0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0,\n            0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,1,        0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1,        0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,        0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1,        0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1,        0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0,        0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0,        0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1\n        };\n\n        static readonly byte[] g_bc7_partition3 =\n        {\n            0,0,1,1,0,0,1,1,0,2,2,1,2,2,2,2,        0,0,0,1,0,0,1,1,2,2,1,1,2,2,2,1,        0,0,0,0,2,0,0,1,2,2,1,1,2,2,1,1,        0,2,2,2,0,0,2,2,0,0,1,1,0,1,1,1,        0,0,0,0,0,0,0,0,1,1,2,2,1,1,2,2,        0,0,1,1,0,0,1,1,0,0,2,2,0,0,2,2,        0,0,2,2,0,0,2,2,1,1,1,1,1,1,1,1,        0,0,1,1,0,0,1,1,2,2,1,1,2,2,1,1,\n            0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,        0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,        0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,        0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,        0,1,1,2,0,1,1,2,0,1,1,2,0,1,1,2,        0,1,2,2,0,1,2,2,0,1,2,2,0,1,2,2,        0,0,1,1,0,1,1,2,1,1,2,2,1,2,2,2,        0,0,1,1,2,0,0,1,2,2,0,0,2,2,2,0,\n            0,0,0,1,0,0,1,1,0,1,1,2,1,1,2,2,        0,1,1,1,0,0,1,1,2,0,0,1,2,2,0,0,        0,0,0,0,1,1,2,2,1,1,2,2,1,1,2,2,        0,0,2,2,0,0,2,2,0,0,2,2,1,1,1,1,        0,1,1,1,0,1,1,1,0,2,2,2,0,2,2,2,        0,0,0,1,0,0,0,1,2,2,2,1,2,2,2,1,        0,0,0,0,0,0,1,1,0,1,2,2,0,1,2,2,        0,0,0,0,1,1,0,0,2,2,1,0,2,2,1,0,\n            0,1,2,2,0,1,2,2,0,0,1,1,0,0,0,0,        0,0,1,2,0,0,1,2,1,1,2,2,2,2,2,2,        0,1,1,0,1,2,2,1,1,2,2,1,0,1,1,0,        0,0,0,0,0,1,1,0,1,2,2,1,1,2,2,1,        0,0,2,2,1,1,0,2,1,1,0,2,0,0,2,2,        0,1,1,0,0,1,1,0,2,0,0,2,2,2,2,2,        0,0,1,1,0,1,2,2,0,1,2,2,0,0,1,1,        0,0,0,0,2,0,0,0,2,2,1,1,2,2,2,1,\n            0,0,0,0,0,0,0,2,1,1,2,2,1,2,2,2,        0,2,2,2,0,0,2,2,0,0,1,2,0,0,1,1,        0,0,1,1,0,0,1,2,0,0,2,2,0,2,2,2,        0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,0,        0,0,0,0,1,1,1,1,2,2,2,2,0,0,0,0,        0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0,        0,1,2,0,2,0,1,2,1,2,0,1,0,1,2,0,        0,0,1,1,2,2,0,0,1,1,2,2,0,0,1,1,\n            0,0,1,1,1,1,2,2,2,2,0,0,0,0,1,1,        0,1,0,1,0,1,0,1,2,2,2,2,2,2,2,2,        0,0,0,0,0,0,0,0,2,1,2,1,2,1,2,1,        0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2,        0,0,2,2,0,0,1,1,0,0,2,2,0,0,1,1,        0,2,2,0,1,2,2,1,0,2,2,0,1,2,2,1,        0,1,0,1,2,2,2,2,2,2,2,2,0,1,0,1,        0,0,0,0,2,1,2,1,2,1,2,1,2,1,2,1,\n            0,1,0,1,0,1,0,1,0,1,0,1,2,2,2,2,        0,2,2,2,0,1,1,1,0,2,2,2,0,1,1,1,        0,0,0,2,1,1,1,2,0,0,0,2,1,1,1,2,        0,0,0,0,2,1,1,2,2,1,1,2,2,1,1,2,        0,2,2,2,0,1,1,1,0,1,1,1,0,2,2,2,        0,0,0,2,1,1,1,2,1,1,1,2,0,0,0,2,        0,1,1,0,0,1,1,0,0,1,1,0,2,2,2,2,        0,0,0,0,0,0,0,0,2,1,1,2,2,1,1,2,\n            0,1,1,0,0,1,1,0,2,2,2,2,2,2,2,2,        0,0,2,2,0,0,1,1,0,0,1,1,0,0,2,2,        0,0,2,2,1,1,2,2,1,1,2,2,0,0,2,2,        0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,2,        0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,1,        0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2,        0,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2,        0,1,1,1,2,0,1,1,2,2,0,1,2,2,2,0,\n        };\n\n        static readonly byte[] g_bc7_table_anchor_index_second_subset = \n        {\n            15,15,15,15,15,15,15,15,\n            15,15,15,15,15,15,15,15,\n            15, 2, 8, 2, 2, 8, 8,15,\n            2, 8, 2, 2, 8, 8, 2, 2,\n            15,15, 6, 8, 2, 8,15,15,\n            2, 8, 2, 2, 2,15,15, 6,\n            6, 2, 6, 8,15,15, 2, 2,\n            15,15,15,15,15, 2, 2,15\n        };\n\n        static readonly byte[] g_bc7_table_anchor_index_third_subset_1 =\n        {\n            3, 3,15,15, 8, 3,15,15,\n            8, 8, 6, 6, 6, 5, 3, 3,\n            3, 3, 8,15, 3, 3, 6,10,\n            5, 8, 8, 6, 8, 5,15,15,\n            8,15, 3, 5, 6,10, 8,15,\n            15, 3,15, 5,15,15,15,15,\n            3,15, 5, 5, 5, 8, 5,10,\n            5,10, 8,13,15,12, 3, 3\n        };\n\n        static readonly byte[] g_bc7_table_anchor_index_third_subset_2 =\n        {\n            15, 8, 8, 3,15,15, 3, 8,\n            15,15,15,15,15,15,15, 8,\n            15, 8,15, 3,15, 8,15, 8,\n            3,15, 6,10,15,15,10, 8,\n            15, 3,15,10,10, 8, 9,10,\n            6,15, 8,15, 3, 6, 6, 8,\n            15, 3,15,15,15,15,15,15,\n            15,15,15,15, 3,15,15, 8\n        };\n\n        static readonly byte[] g_bc7_first_byte_to_mode =\n        {\n            8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n            4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,\n        };\n\n        static void insert_weight_zero(ref ulong index_bits, int bits_per_index, int offset)\n        {\n            ulong LOW_BIT_MASK = (1UL << ((bits_per_index * (offset + 1)) - 1)) - 1;\n            ulong HIGH_BIT_MASK = ~LOW_BIT_MASK;\n\n            index_bits = ((index_bits & HIGH_BIT_MASK) << 1) | (index_bits & LOW_BIT_MASK);\n        }\n\n        static uint bc7_dequant(uint val, uint pbit, int val_bits)\n        {\n            Debug.Assert(val < (1U << val_bits));\n            Debug.Assert(pbit < 2);\n            Debug.Assert(val_bits >= 4 && val_bits <= 8);\n            int total_bits = val_bits + 1;\n            val = (val << 1) | pbit;\n            val <<= (8 - total_bits);\n            val |= (val >> total_bits);\n            Debug.Assert(val <= 255);\n            return val;\n        }\n\n        static uint bc7_dequant(uint val, int val_bits)\n        {\n            Debug.Assert(val < (1U << val_bits));\n            Debug.Assert(val_bits >= 4 && val_bits <= 8);\n            val <<= (8 - val_bits);\n            val |= (val >> val_bits);\n            Debug.Assert(val <= 255);\n            return val;\n        }\n\n        static uint bc7_interp2(uint l, uint h, int w)\n        {\n            Debug.Assert(w < 4);\n            return (l * (64 - g_bc7_weights2[w]) + h * g_bc7_weights2[w] + 32) >> 6;\n        }\n        static uint bc7_interp3(uint l, uint h, int w)\n        {\n            Debug.Assert(w < 8);\n            return (l * (64 - g_bc7_weights3[w]) + h * g_bc7_weights3[w] + 32) >> 6;\n        }\n        static uint bc7_interp4(uint l, uint h, int w)\n        {\n            Debug.Assert(w < 16);\n            return (l * (64 - g_bc7_weights4[w]) + h * g_bc7_weights4[w] + 32) >> 6;\n        }\n        static uint bc7_interp(uint l, uint h, int w, int bits)\n        {\n            Debug.Assert(l <= 255 && h <= 255);\n            switch (bits)\n            {\n                case 2: return bc7_interp2(l, h, w);\n                case 3: return bc7_interp3(l, h, w);\n                case 4: return bc7_interp4(l, h, w);\n                default:\n                    break;\n            }\n            return 0;\n        }\n\n#if NET6_0_OR_GREATER\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        static Vector128<short> bc7_interp_sse2(Vector128<short> l, Vector128<short> h, Vector128<short> w, Vector128<short> iw)\n        {\n            return Sse2.ShiftRightLogical(Sse2.Add(Sse2.Add(Sse2.MultiplyLow(l, iw), Sse2.MultiplyLow(h, w)), Vector128.Create((short)32)), 6);\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        static Vector256<short> bc7_interp_avx2(Vector256<short> l, Vector256<short> h, Vector256<short> w, Vector256<short> iw)\n        {\n            return Avx2.ShiftRightLogical(Avx2.Add(Avx2.Add(Avx2.MultiplyLow(l, iw), Avx2.MultiplyLow(h, w)), Vector256.Create((short)32)), 6);\n        }\n\n        static unsafe void bc7_interp2_sse2(ReadOnlySpan<color_rgba> endpoint_pair, Span<color_rgba> out_colors)\n        {\n            Vector128<byte> endpoints;\n            fixed (color_rgba* pInput = endpoint_pair)\n                endpoints = Sse2.LoadScalarVector128((long*)pInput).AsByte();\n            Vector128<short> endpoints_16 = Sse2.UnpackLow(endpoints, new Vector128<byte>()).AsInt16();\n            Vector128<short> endpoints_16_swapped = Sse2.Shuffle(endpoints_16.AsInt32(), 0b_01_00_11_10).AsInt16();\n\n            // Interpolated colors will be color 1 and 2\n            Vector128<short> interpolated_colors = bc7_interp_sse2(endpoints_16, endpoints_16_swapped, Vector128.Create((short)21), Vector128.Create((short)43));\n\n            // all_colors will be 1, 2, 0, 3\n            Vector128<byte> all_colors = Sse2.PackUnsignedSaturate(interpolated_colors, endpoints_16);\n\n            all_colors = Sse2.Shuffle(all_colors.AsInt32(), 0b_11_01_00_10).AsByte();\n\n            fixed (color_rgba* pOutput = out_colors)\n                Sse2.Store((byte*)pOutput, all_colors);\n        }\n\n        static unsafe void bc7_interp3_sse2(ReadOnlySpan<color_rgba> endpoint_pair, Span<color_rgba> out_colors)\n        {\n            Vector128<byte> endpoints;\n            fixed (color_rgba* pInput = endpoint_pair)\n                endpoints = Sse2.LoadScalarVector128((long*)pInput).AsByte();\n            Vector128<short> endpoints_16bit = Sse2.UnpackLow(endpoints, new Vector128<byte>()).AsInt16();\n            Vector128<short> endpoints_16bit_swapped = Sse2.Shuffle(endpoints_16bit.AsInt32(), 0b_01_00_11_10).AsInt16();\n\n            Vector128<short> interpolated_16 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, Vector128.Create((short)9), Vector128.Create((short)55));\n            Vector128<short> interpolated_23 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, Vector128.Create(18, 18, 18, 18, 37, 37, 37, 37), Vector128.Create(46, 46, 46, 46, 27, 27, 27, 27));\n            Vector128<short> interpolated_45 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, Vector128.Create(37, 37, 37, 37, 18, 18, 18, 18), Vector128.Create(27, 27, 27, 27, 46, 46, 46, 46));\n\n            Vector128<short> interpolated_01 = Sse2.UnpackLow(endpoints_16bit.AsInt64(), interpolated_16.AsInt64()).AsInt16();\n            Vector128<short> interpolated_67 = Sse2.UnpackHigh(interpolated_16.AsInt64(), endpoints_16bit.AsInt64()).AsInt16();\n\n            Vector128<byte> all_colors_0 = Sse2.PackUnsignedSaturate(interpolated_01, interpolated_23);\n            Vector128<byte> all_colors_1 = Sse2.PackUnsignedSaturate(interpolated_45, interpolated_67);\n\n            fixed (color_rgba* pOutput = out_colors)\n            {\n                Sse2.Store((byte*)pOutput, all_colors_0);\n                Sse2.Store((byte*)(pOutput + 4), all_colors_1);\n            }\n        }\n\n        static unsafe void bc7_interp3_avx2(ReadOnlySpan<color_rgba> endpoint_pair, Span<color_rgba> out_colors)\n        {\n            Vector128<byte> endpoints;\n            fixed (color_rgba* pInput = endpoint_pair)\n                endpoints = Sse2.LoadScalarVector128((long*)pInput).AsByte();\n            Vector128<short> endpoints_16bit = Sse2.UnpackLow(endpoints, new Vector128<byte>()).AsInt16();\n            Vector128<short> endpoints_16bit_swapped = Sse2.Shuffle(endpoints_16bit.AsInt32(), 0b_01_00_11_10).AsInt16();\n\n            // Colors 1 and 6\n            Vector128<short> interpolated_16 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, Vector128.Create((short)9), Vector128.Create((short)55));\n\n            // Colors 2,3 and 4,5 (batched with AVX2)\n            Vector256<short> ep_256 = Vector256.Create(endpoints_16bit, endpoints_16bit);\n            Vector256<short> eps_256 = Vector256.Create(endpoints_16bit_swapped, endpoints_16bit_swapped);\n            Vector256<short> interpolated_2345 = bc7_interp_avx2(ep_256, eps_256,\n                Vector256.Create((short)18, 18, 18, 18, 37, 37, 37, 37, 37, 37, 37, 37, 18, 18, 18, 18),\n                Vector256.Create((short)46, 46, 46, 46, 27, 27, 27, 27, 27, 27, 27, 27, 46, 46, 46, 46));\n\n            Vector128<short> interpolated_23 = interpolated_2345.GetLower();\n            Vector128<short> interpolated_45 = interpolated_2345.GetUpper();\n\n            Vector128<short> interpolated_01 = Sse2.UnpackLow(endpoints_16bit.AsInt64(), interpolated_16.AsInt64()).AsInt16();\n            Vector128<short> interpolated_67 = Sse2.UnpackHigh(interpolated_16.AsInt64(), endpoints_16bit.AsInt64()).AsInt16();\n\n            Vector128<byte> all_colors_0 = Sse2.PackUnsignedSaturate(interpolated_01, interpolated_23);\n            Vector128<byte> all_colors_1 = Sse2.PackUnsignedSaturate(interpolated_45, interpolated_67);\n\n            fixed (color_rgba* pOutput = out_colors)\n            {\n                Sse2.Store((byte*)pOutput, all_colors_0);\n                Sse2.Store((byte*)(pOutput + 4), all_colors_1);\n            }\n        }\n#endif\n\n        static void unpack_bc7_mode0_2(int mode, ReadOnlySpan<ulong> data_chunks, Span<color_rgba> pPixels)\n        {\n            //const uint SUBSETS = 3;\n            const int ENDPOINTS = 6;\n            const int COMPS = 3;\n            int WEIGHT_BITS = (mode == 0) ? 3 : 2;\n            uint WEIGHT_MASK = (1u << WEIGHT_BITS) - 1;\n            int ENDPOINT_BITS = (mode == 0) ? 4 : 5;\n            uint ENDPOINT_MASK = (1u << ENDPOINT_BITS) - 1;\n            int PBITS = (mode == 0) ? 6 : 0;\n            uint WEIGHT_VALS = 1u << WEIGHT_BITS;\n            int PART_BITS = (mode == 0) ? 4 : 6;\n            uint PART_MASK = (1u << PART_BITS) - 1;\n\n            ulong low_chunk = data_chunks[0];\n            ulong high_chunk = data_chunks[1];\n\n            uint part = (uint)((low_chunk >> (mode + 1)) & PART_MASK);\n\n            Span<ulong> channel_read_chunks = stackalloc ulong[3] { 0, 0, 0 };\n\n            if (mode == 0)\n            {\n                channel_read_chunks[0] = low_chunk >> 5;\n                channel_read_chunks[1] = low_chunk >> 29;\n                channel_read_chunks[2] = ((low_chunk >> 53) | (high_chunk << 11));\n            }\n            else\n            {\n                channel_read_chunks[0] = low_chunk >> 9;\n                channel_read_chunks[1] = ((low_chunk >> 39) | (high_chunk << 25));\n                channel_read_chunks[2] = high_chunk >> 5;\n            }\n\n            Span<color_rgba> endpoints = stackalloc color_rgba[ENDPOINTS];\n            for (int c = 0; c < COMPS; c++)\n            {\n                ulong channel_read_chunk = channel_read_chunks[c];\n                for (int e = 0; e < ENDPOINTS; e++)\n                {\n                    endpoints[e][c] = (byte)(channel_read_chunk & ENDPOINT_MASK);\n                    channel_read_chunk >>= ENDPOINT_BITS;\n                }\n            }\n\n            Span<uint> pbits = stackalloc uint[6];\n            if (mode == 0)\n            {\n                byte p_bits_chunk = (byte)((high_chunk >> 13) & 0xff);\n\n                for (int p = 0; p < PBITS; p++)\n                    pbits[p] = (uint)(p_bits_chunk >> p) & 1;\n            }\n\n            ulong weights_read_chunk = high_chunk >> (67 - 16 * WEIGHT_BITS);\n            insert_weight_zero(ref weights_read_chunk, WEIGHT_BITS, 0);\n            insert_weight_zero(ref weights_read_chunk, WEIGHT_BITS, Math.Min(g_bc7_table_anchor_index_third_subset_1[part], g_bc7_table_anchor_index_third_subset_2[part]));\n            insert_weight_zero(ref weights_read_chunk, WEIGHT_BITS, Math.Max(g_bc7_table_anchor_index_third_subset_1[part], g_bc7_table_anchor_index_third_subset_2[part]));\n\n            Span<uint> weights = stackalloc uint[16];\n            for (int i = 0; i < 16; i++)\n            {\n                weights[i] = (uint)(weights_read_chunk & WEIGHT_MASK);\n                weights_read_chunk >>= WEIGHT_BITS;\n            }\n\n            for (int e = 0; e < ENDPOINTS; e++)\n                for (int c = 0; c < 4; c++)\n                    endpoints[e][c] = (byte)((c == 3) ? 255 : (PBITS != 0 ? bc7_dequant(endpoints[e][c], pbits[e], ENDPOINT_BITS) : bc7_dequant(endpoints[e][c], ENDPOINT_BITS)));\n\n            Span<color_rgba> block_colors = stackalloc color_rgba[3 * 8];\n\n#if NET6_0_OR_GREATER\n            if (Sse2.IsSupported)\n            {\n                for (int s = 0; s < 3; s++)\n                {\n                    if (WEIGHT_BITS == 2)\n                        bc7_interp2_sse2(endpoints.Slice(s * 2), block_colors.Slice(s * 8));\n                    else if (Avx2.IsSupported)\n                        bc7_interp3_avx2(endpoints.Slice(s * 2), block_colors.Slice(s * 8));\n                    else\n                        bc7_interp3_sse2(endpoints.Slice(s * 2), block_colors.Slice(s * 8));\n                }\n            }\n            else\n#endif\n            {\n                for (int s = 0; s < 3; s++)\n                    for (int i = 0; i < WEIGHT_VALS; i++)\n                    {\n                        for (int c = 0; c < 3; c++)\n                            block_colors[s * 8 + i][c] = (byte)(bc7_interp(endpoints[s * 2 + 0][c], endpoints[s * 2 + 1][c], i, WEIGHT_BITS));\n                        block_colors[s * 8 + i][3] = 255;\n                    }\n            }\n\n            for (int i = 0; i < 16; i++)\n                pPixels[i] = block_colors[g_bc7_partition3[part * 16 + i] * 8 + (int)weights[i]];\n        }\n\n        static void unpack_bc7_mode1_3_7(int mode, ReadOnlySpan<ulong> data_chunks, Span<color_rgba> pPixels)\n        {\n            //const uint SUBSETS = 2;\n            const int ENDPOINTS = 4;\n            int COMPS = (mode == 7) ? 4 : 3;\n            int WEIGHT_BITS = (mode == 1) ? 3 : 2;\n            uint WEIGHT_MASK = (1u << WEIGHT_BITS) - 1;\n            int ENDPOINT_BITS = (mode == 7) ? 5 : ((mode == 1) ? 6 : 7);\n            uint ENDPOINT_MASK = (1u << ENDPOINT_BITS) - 1;\n            int PBITS = (mode == 1) ? 2 : 4;\n            bool SHARED_PBITS = (mode == 1) ? true : false;\n            uint WEIGHT_VALS = 1u << WEIGHT_BITS;\n\n            ulong low_chunk = data_chunks[0];\n            ulong high_chunk = data_chunks[1];\n\n            uint part = (uint)((low_chunk >> (mode + 1)) & 0x3f);\n\n            Span<color_rgba> endpoints = stackalloc color_rgba[ENDPOINTS];\n\n            Span<ulong> channel_read_chunks = stackalloc ulong[] { 0, 0, 0, 0 };\n            ulong p_read_chunk = 0;\n            channel_read_chunks[0] = (low_chunk >> (mode + 7));\n            ulong weight_read_chunk;\n\n            switch (mode)\n            {\n                case 1:\n                    channel_read_chunks[1] = (low_chunk >> 32);\n                    channel_read_chunks[2] = ((low_chunk >> 56) | (high_chunk << 8));\n                    p_read_chunk = high_chunk >> 16;\n                    weight_read_chunk = high_chunk >> 18;\n                    break;\n                case 3:\n                    channel_read_chunks[1] = ((low_chunk >> 38) | (high_chunk << 26));\n                    channel_read_chunks[2] = high_chunk >> 2;\n                    p_read_chunk = high_chunk >> 30;\n                    weight_read_chunk = high_chunk >> 34;\n                    break;\n                case 7:\n                    channel_read_chunks[1] = low_chunk >> 34;\n                    channel_read_chunks[2] = ((low_chunk >> 54) | (high_chunk << 10));\n                    channel_read_chunks[3] = high_chunk >> 10;\n                    p_read_chunk = (high_chunk >> 30);\n                    weight_read_chunk = (high_chunk >> 34);\n                    break;\n                default:\n                    throw new ArgumentOutOfRangeException(nameof(mode));\n            };\n\n            for (int c = 0; c < COMPS; c++)\n            {\n                ulong channel_read_chunk = channel_read_chunks[c];\n                for (int e = 0; e < ENDPOINTS; e++)\n                {\n                    endpoints[e][c] = (byte)(channel_read_chunk & ENDPOINT_MASK);\n                    channel_read_chunk >>= ENDPOINT_BITS;\n                }\n            }\n\n            Span<uint> pbits = stackalloc uint[4];\n            for (int p = 0; p < PBITS; p++)\n                pbits[p] = (uint)(p_read_chunk >> p) & 1;\n\n            insert_weight_zero(ref weight_read_chunk, WEIGHT_BITS, 0);\n            insert_weight_zero(ref weight_read_chunk, WEIGHT_BITS, g_bc7_table_anchor_index_second_subset[part]);\n\n            Span<uint> weights = stackalloc uint[16];\n            for (int i = 0; i < 16; i++)\n            {\n                weights[i] = (uint)(weight_read_chunk & WEIGHT_MASK);\n                weight_read_chunk >>= WEIGHT_BITS;\n            }\n\n            for (int e = 0; e < ENDPOINTS; e++)\n                for (int c = 0; c < 4; c++)\n                    endpoints[e][c] = (byte)((mode != 7U && c == 3U) ? 255 : bc7_dequant(endpoints[e][c], pbits[SHARED_PBITS ? (e >> 1) : e], ENDPOINT_BITS));\n\n            Span<color_rgba> block_colors = stackalloc color_rgba[2 * 8];\n#if NET6_0_OR_GREATER\n            if (Sse2.IsSupported)\n            {\n                for (int s = 0; s < 2; s++)\n                {\n                    if (WEIGHT_BITS == 2)\n                        bc7_interp2_sse2(endpoints.Slice(s * 2), block_colors.Slice(s * 8));\n                    else if (Avx2.IsSupported)\n                        bc7_interp3_avx2(endpoints.Slice(s * 2), block_colors.Slice(s * 8));\n                    else\n                        bc7_interp3_sse2(endpoints.Slice(s * 2), block_colors.Slice(s * 8));\n                }\n            }\n            else\n#endif\n            {\n                for (int s = 0; s < 2; s++)\n                    for (int i = 0; i < WEIGHT_VALS; i++)\n                    {\n                        for (int c = 0; c < COMPS; c++)\n                            block_colors[s * 8 + i][c] = (byte)(bc7_interp(endpoints[s * 2 + 0][c], endpoints[s * 2 + 1][c], i, WEIGHT_BITS));\n                        block_colors[s * 8 + i][3] = (COMPS == 3) ? (byte)255 : block_colors[s * 8 + i][3];\n                    }\n            }\n\n            for (int i = 0; i < 16; i++)\n                pPixels[i] = block_colors[(int)(g_bc7_partition2[part * 16 + i] * 8 + weights[i])];\n        }\n\n        static void unpack_bc7_mode4_5(int mode, ReadOnlySpan<ulong> data_chunks, Span<color_rgba> pPixels)\n        {\n            const int ENDPOINTS = 2;\n            //const uint COMPS = 4;\n            const int WEIGHT_BITS = 2;\n            uint WEIGHT_MASK = (1u << WEIGHT_BITS) - 1;\n            int A_WEIGHT_BITS = (mode == 4) ? 3 : 2;\n            uint A_WEIGHT_MASK = (1u << A_WEIGHT_BITS) - 1;\n            int ENDPOINT_BITS = (mode == 4) ? 5 : 7;\n            uint ENDPOINT_MASK = (1u << ENDPOINT_BITS) - 1;\n            int A_ENDPOINT_BITS = (mode == 4) ? 6 : 8;\n            uint A_ENDPOINT_MASK = (1u << A_ENDPOINT_BITS) - 1;\n            //const uint WEIGHT_VALS = 1 << WEIGHT_BITS;\n            //const uint A_WEIGHT_VALS = 1 << A_WEIGHT_BITS;\n\n            ulong low_chunk = data_chunks[0];\n            ulong high_chunk = data_chunks[1];\n\n            uint comp_rot = (uint)(low_chunk >> (mode + 1)) & 0x3;\n            uint index_mode = (mode == 4) ? (uint)((low_chunk >> 7) & 1) : 0;\n\n            ulong color_read_bits = low_chunk >> 8;\n\n            Span<color_rgba> endpoints = stackalloc color_rgba[ENDPOINTS];\n            for (int c = 0; c < 3; c++)\n            {\n                for (int e = 0; e < ENDPOINTS; e++)\n                {\n                    endpoints[e][c] = (byte)(color_read_bits & ENDPOINT_MASK);\n                    color_read_bits >>= ENDPOINT_BITS;\n                }\n            }\n\n            endpoints[0][3] = (byte)(color_read_bits & ENDPOINT_MASK);\n\n            ulong rgb_weights_chunk;\n            ulong a_weights_chunk;\n            if (mode == 4)\n            {\n                endpoints[0][3] = (byte)(color_read_bits & A_ENDPOINT_MASK);\n                endpoints[1][3] = (byte)((color_read_bits >> A_ENDPOINT_BITS) & A_ENDPOINT_MASK);\n                rgb_weights_chunk = ((low_chunk >> 50) | (high_chunk << 14));\n                a_weights_chunk = high_chunk >> 17;\n            }\n            else if (mode == 5)\n            {\n                endpoints[0][3] = (byte)(color_read_bits & A_ENDPOINT_MASK);\n                endpoints[1][3] = (byte)(((low_chunk >> 58) | (high_chunk << 6)) & A_ENDPOINT_MASK);\n                rgb_weights_chunk = high_chunk >> 2;\n                a_weights_chunk = high_chunk >> 33;\n            }\n            else\n                throw new ArgumentOutOfRangeException(nameof(mode));\n\n            insert_weight_zero(ref rgb_weights_chunk, WEIGHT_BITS, 0);\n            insert_weight_zero(ref a_weights_chunk, A_WEIGHT_BITS, 0);\n\n            Span<int> weight_bits = stackalloc int[2] { index_mode != 0 ? A_WEIGHT_BITS : WEIGHT_BITS, index_mode != 0 ? WEIGHT_BITS : A_WEIGHT_BITS };\n            Span<uint> weight_mask = stackalloc uint[2] { index_mode != 0 ? A_WEIGHT_MASK : WEIGHT_MASK, index_mode != 0 ? WEIGHT_MASK : A_WEIGHT_MASK };\n\n            Span<uint> weights = stackalloc uint[16];\n            Span<uint> a_weights = stackalloc uint[16];\n\n            if (index_mode != 0)\n                std_swap(ref a_weights_chunk, ref rgb_weights_chunk);\n\n\n            for (int i = 0; i < 16; i++)\n            {\n                weights[i] = (uint)(rgb_weights_chunk & weight_mask[0]);\n                rgb_weights_chunk >>= weight_bits[0];\n            }\n\n            for (int i = 0; i < 16; i++)\n            {\n                a_weights[i] = (uint)(a_weights_chunk & weight_mask[1]);\n                a_weights_chunk >>= weight_bits[1];\n            }\n\n            for (int e = 0; e < ENDPOINTS; e++)\n                for (int c = 0; c < 4; c++)\n                    endpoints[e][c] = (byte)(bc7_dequant(endpoints[e][c], (c == 3) ? A_ENDPOINT_BITS : ENDPOINT_BITS));\n\n            Span<color_rgba> block_colors = stackalloc color_rgba[8];\n#if NET6_0_OR_GREATER\n            if (Sse2.IsSupported)\n            {\n                if (weight_bits[0] == 3)\n                {\n                    if (Avx2.IsSupported)\n                        bc7_interp3_avx2(endpoints, block_colors);\n                    else\n                        bc7_interp3_sse2(endpoints, block_colors);\n                }\n                else\n                    bc7_interp2_sse2(endpoints, block_colors);\n            }\n            else\n#endif\n            {\n                for (int i = 0; i < (1U << (int)weight_bits[0]); i++)\n                    for (int c = 0; c < 3; c++)\n                        block_colors[i][c] = (byte)(bc7_interp(endpoints[0][c], endpoints[1][c], i, weight_bits[0]));\n            }\n\n            for (int i = 0; i < (1U << weight_bits[1]); i++)\n                block_colors[i][3] = (byte)(bc7_interp(endpoints[0][3], endpoints[1][3], i, weight_bits[1]));\n\n            for (int i = 0; i < 16; i++)\n            {\n                pPixels[i] = block_colors[(int)weights[i]];\n                pPixels[i].a = block_colors[(int)a_weights[i]].a;\n                if (comp_rot >= 1)\n                {\n                    std_swap(ref pPixels[i][(int)comp_rot - 1], ref pPixels[i].a);\n                }\n            }\n        }\n\n        public struct bc7_mode_6\n        {\n            public ulong m_lo;\n            public ulong m_hi;\n\n            public ulong m_mode => this.m_lo & 0x7f;\n            public ulong m_r0 => (this.m_lo >> 7) & 0x7f;\n            public ulong m_r1 => (this.m_lo >> 14) & 0x7f;\n            public ulong m_g0 => (this.m_lo >> 21) & 0x7f;\n            public ulong m_g1 => (this.m_lo >> 28) & 0x7f;\n            public ulong m_b0 => (this.m_lo >> 35) & 0x7f;\n            public ulong m_b1 => (this.m_lo >> 42) & 0x7f;\n            public ulong m_a0 => (this.m_lo >> 49) & 0x7f;\n            public ulong m_a1 => (this.m_lo >> 56) & 0x7f;\n            public ulong m_p0 => (this.m_lo >> 63) & 0x7f;\n\n            public ulong m_p1 => this.m_hi & 0x01;\n            public ulong m_s00 => (this.m_hi >> 1) & 0x07;\n            public ulong m_s10 => (this.m_hi >> 4) & 0x0f;\n            public ulong m_s20 => (this.m_hi >> 8) & 0x0f;\n            public ulong m_s30 => (this.m_hi >> 12) & 0x0f;\n\n            public ulong m_s01 => (this.m_hi >> 16) & 0x0f;\n            public ulong m_s11 => (this.m_hi >> 20) & 0x0f;\n            public ulong m_s21 => (this.m_hi >> 24) & 0x0f;\n            public ulong m_s31 => (this.m_hi >> 28) & 0x0f;\n\n            public ulong m_s02 => (this.m_hi >> 32) & 0x0f;\n            public ulong m_s12 => (this.m_hi >> 36) & 0x0f;\n            public ulong m_s22 => (this.m_hi >> 40) & 0x0f;\n            public ulong m_s32 => (this.m_hi >> 44) & 0x0f;\n\n            public ulong m_s03 => (this.m_hi >> 48) & 0x0f;\n            public ulong m_s13 => (this.m_hi >> 52) & 0x0f;\n            public ulong m_s23 => (this.m_hi >> 56) & 0x0f;\n            public ulong m_s33 => (this.m_hi >> 60) & 0x0f;\n        };\n\n        static unsafe void unpack_bc7_mode6(ReadOnlySpan<ulong> pBlock_bits, Span<color_rgba> pPixels)\n        {\n            ref readonly bc7_mode_6 block = ref MemoryMarshal.Cast<ulong, bc7_mode_6>(pBlock_bits)[0];\n\n            if (block.m_mode != (1 << 6))\n                throw new ArgumentOutOfRangeException(\"mode\");\n\n            uint r0 = (uint)((block.m_r0 << 1) | block.m_p0);\n            uint g0 = (uint)((block.m_g0 << 1) | block.m_p0);\n            uint b0 = (uint)((block.m_b0 << 1) | block.m_p0);\n            uint a0 = (uint)((block.m_a0 << 1) | block.m_p0);\n            uint r1 = (uint)((block.m_r1 << 1) | block.m_p1);\n            uint g1 = (uint)((block.m_g1 << 1) | block.m_p1);\n            uint b1 = (uint)((block.m_b1 << 1) | block.m_p1);\n            uint a1 = (uint)((block.m_a1 << 1) | block.m_p1);\n\n            Span<color_rgba> vals = stackalloc color_rgba[16];\n#if NET6_0_OR_GREATER\n            if (Sse2.IsSupported)\n            {\n                Vector128<short> vep0 = Vector128.Create((short)r0, (short)g0, (short)b0, (short)a0, (short)r0, (short)g0, (short)b0, (short)a0);\n                Vector128<short> vep1 = Vector128.Create((short)r1, (short)g1, (short)b1, (short)a1, (short)r1, (short)g1, (short)b1, (short)a1);\n\n                for (int i = 0; i < 16; i += 4)\n                {\n                    Vector128<short> w0 = g_bc7_weights4_sse2[i / 4 * 2 + 0];\n                    Vector128<short> w1 = g_bc7_weights4_sse2[i / 4 * 2 + 1];\n\n                    Vector128<short> iw0 = Sse2.Subtract(Vector128.Create((short)64), w0);\n                    Vector128<short> iw1 = Sse2.Subtract(Vector128.Create((short)64), w1);\n\n                    Vector128<short> first_half = Sse2.ShiftRightLogical(Sse2.Add(Sse2.Add(Sse2.MultiplyLow(vep0, iw0), Sse2.MultiplyLow(vep1, w0)), Vector128.Create((short)32)), 6);\n                    Vector128<short> second_half = Sse2.ShiftRightLogical(Sse2.Add(Sse2.Add(Sse2.MultiplyLow(vep0, iw1), Sse2.MultiplyLow(vep1, w1)), Vector128.Create((short)32)), 6);\n                    Vector128<byte> combined = Sse2.PackUnsignedSaturate(first_half, second_half);\n\n                    fixed (color_rgba* pVals = vals)\n                        Sse2.Store((byte*)(pVals + i), combined);\n                }\n            }\n            else\n#endif\n            {\n                for (int i = 0; i < 16; i++)\n                {\n                    uint w = g_bc7_weights4[i];\n                    uint iw = 64 - w;\n                    vals[i].set_noclamp_rgba(\n                        (r0 * iw + r1 * w + 32) >> 6,\n                        (g0 * iw + g1 * w + 32) >> 6,\n                        (b0 * iw + b1 * w + 32) >> 6,\n                        (a0 * iw + a1 * w + 32) >> 6);\n                }\n            }\n\n            pPixels[0] = vals[(int)block.m_s00];\n            pPixels[1] = vals[(int)block.m_s10];\n            pPixels[2] = vals[(int)block.m_s20];\n            pPixels[3] = vals[(int)block.m_s30];\n\n            pPixels[4] = vals[(int)block.m_s01];\n            pPixels[5] = vals[(int)block.m_s11];\n            pPixels[6] = vals[(int)block.m_s21];\n            pPixels[7] = vals[(int)block.m_s31];\n\n            pPixels[8] = vals[(int)block.m_s02];\n            pPixels[9] = vals[(int)block.m_s12];\n            pPixels[10] = vals[(int)block.m_s22];\n            pPixels[11] = vals[(int)block.m_s32];\n\n            pPixels[12] = vals[(int)block.m_s03];\n            pPixels[13] = vals[(int)block.m_s13];\n            pPixels[14] = vals[(int)block.m_s23];\n            pPixels[15] = vals[(int)block.m_s33];\n        }\n\n        public static void unpack_bc7(ReadOnlySpan<byte> block_bytes, Span<color_rgba> pPixels)\n        {\n            byte mode = g_bc7_first_byte_to_mode[block_bytes[0]];\n            ReadOnlySpan<ulong> data_chunks = MemoryMarshal.Cast<byte, ulong>(block_bytes).Slice(0, 2);\n            // skip endianess checking\n            switch (mode)\n            {\n                case 0:\n                case 2:\n                    unpack_bc7_mode0_2(mode, data_chunks, pPixels);\n                    break;\n                case 1:\n                case 3:\n                case 7:\n                    unpack_bc7_mode1_3_7(mode, data_chunks, pPixels);\n                    break;\n                case 4:\n                case 5:\n                    unpack_bc7_mode4_5(mode, data_chunks, pPixels);\n                    break;\n                case 6:\n                    unpack_bc7_mode6(data_chunks, pPixels);\n                    break;\n                default:\n                    Unsafe.InitBlockUnaligned(ref MemoryMarshal.AsBytes(pPixels)[0], 0, 4 * 16);\n                    break;\n            }\n        }\n\n        static void std_swap<T>(ref T left, ref T right)\n        {\n            T temp = left;\n            left = right;\n            right = temp;\n        }\n    }\n    #endregion\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/MathHelper.cs",
    "content": "using System;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    internal static class MathHelper\n    {\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static uint Mix(uint v)\n        {\n            v ^= v >> 16;\n            v *= 0x7FEB352D;\n            v ^= v >> 15;\n            v *= 0x846CA68B;\n            v ^= v >> 16;\n            return v;\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        public static uint ROL(uint v, int n) => (v << n) | (v >> (32 - n));\n\n        const byte M8 = 0xAA;\n        const ushort M16 = 0xAAAA;\n\n        /// <summary>\n        /// XOR each byte with (byte)(0xAA + i), widening to char.\n        /// </summary>\n        public static unsafe void XorWidenToChar(ReadOnlySpan<byte> bytes, Span<char> chars)\n        {\n            int length = bytes.Length;\n            int i = 0;\n\n#if NET6_0_OR_GREATER\n            if (Avx2.IsSupported && length >= 32)\n            {\n                var increment = Vector256.Create((byte)32);\n                var mask = Vector256.Create(\n                    (byte)(M8 + 0),  (byte)(M8 + 1),  (byte)(M8 + 2),  (byte)(M8 + 3),\n                    (byte)(M8 + 4),  (byte)(M8 + 5),  (byte)(M8 + 6),  (byte)(M8 + 7),\n                    (byte)(M8 + 8),  (byte)(M8 + 9),  (byte)(M8 + 10), (byte)(M8 + 11),\n                    (byte)(M8 + 12), (byte)(M8 + 13), (byte)(M8 + 14), (byte)(M8 + 15),\n                    (byte)(M8 + 16), (byte)(M8 + 17), (byte)(M8 + 18), (byte)(M8 + 19),\n                    (byte)(M8 + 20), (byte)(M8 + 21), (byte)(M8 + 22), (byte)(M8 + 23),\n                    (byte)(M8 + 24), (byte)(M8 + 25), (byte)(M8 + 26), (byte)(M8 + 27),\n                    (byte)(M8 + 28), (byte)(M8 + 29), (byte)(M8 + 30), (byte)(M8 + 31));\n\n                fixed (byte* pIn = bytes)\n                fixed (char* pOut = chars)\n                {\n                    var output = (ushort*)pOut;\n                    for (; i + 32 <= length; i += 32)\n                    {\n                        var data = Avx.LoadVector256(pIn + i);\n                        var xored = Avx2.Xor(data, mask);\n\n                        var lo = Avx2.ConvertToVector256Int16(xored.GetLower()).AsUInt16();\n                        var hi = Avx2.ConvertToVector256Int16(xored.GetUpper()).AsUInt16();\n\n                        Avx.Store(output + i, lo);\n                        Avx.Store(output + i + 16, hi);\n\n                        mask = Avx2.Add(mask, increment);\n                    }\n                }\n            }\n            else if (Sse2.IsSupported && length >= 16)\n            {\n                var increment = Vector128.Create((byte)16);\n                var mask = Vector128.Create(\n                    (byte)(M8 + 0), (byte)(M8 + 1), (byte)(M8 + 2), (byte)(M8 + 3),\n                    (byte)(M8 + 4), (byte)(M8 + 5), (byte)(M8 + 6), (byte)(M8 + 7),\n                    (byte)(M8 + 8), (byte)(M8 + 9), (byte)(M8 + 10), (byte)(M8 + 11),\n                    (byte)(M8 + 12), (byte)(M8 + 13), (byte)(M8 + 14), (byte)(M8 + 15));\n\n                fixed (byte* pIn = bytes)\n                fixed (char* pOut = chars)\n                {\n                    var output = (ushort*)pOut;\n                    for (; i + 16 <= length; i += 16)\n                    {\n                        var data = Sse2.LoadVector128(pIn + i);\n                        var xored = Sse2.Xor(data, mask);\n\n                        var lo = Sse2.UnpackLow(xored, Vector128<byte>.Zero);\n                        var hi = Sse2.UnpackHigh(xored, Vector128<byte>.Zero);\n\n                        Sse2.Store(output + i, lo.AsUInt16());\n                        Sse2.Store(output + i + 8, hi.AsUInt16());\n\n                        mask = Sse2.Add(mask, increment);\n                    }\n                }\n            }\n#endif\n            for (; i < length; i++)\n            {\n                chars[i] = (char)(bytes[i] ^ (byte)(M8 + i));\n            }\n        }\n\n        /// <summary>\n        /// XOR each char with (ushort)(0xAAAA + i).\n        /// </summary>\n        public static unsafe void XorChars(ReadOnlySpan<char> input, Span<char> output)\n        {\n            int length = input.Length;\n            int i = 0;\n\n#if NET6_0_OR_GREATER\n            if (Avx2.IsSupported && length >= 16)\n            {\n                var increment = Vector256.Create((ushort)16);\n                var mask = Vector256.Create(\n                    (ushort)(M16 + 0),  (ushort)(M16 + 1),  (ushort)(M16 + 2),  (ushort)(M16 + 3),\n                    (ushort)(M16 + 4),  (ushort)(M16 + 5),  (ushort)(M16 + 6),  (ushort)(M16 + 7),\n                    (ushort)(M16 + 8),  (ushort)(M16 + 9),  (ushort)(M16 + 10), (ushort)(M16 + 11),\n                    (ushort)(M16 + 12), (ushort)(M16 + 13), (ushort)(M16 + 14), (ushort)(M16 + 15));\n\n                fixed (char* pIn = input, pOut = output)\n                {\n                    for (; i + 16 <= length; i += 16)\n                    {\n                        var vec = Avx.LoadVector256((ushort*)(pIn + i));\n                        var result = Avx2.Xor(vec, mask);\n                        Avx.Store((ushort*)(pOut + i), result);\n                        mask = Avx2.Add(mask, increment);\n                    }\n                }\n            }\n            else if (Sse2.IsSupported && length >= 8)\n            {\n                var increment = Vector128.Create((ushort)8);\n                var mask = Vector128.Create(\n                    (ushort)(M16 + 0), (ushort)(M16 + 1), (ushort)(M16 + 2), (ushort)(M16 + 3),\n                    (ushort)(M16 + 4), (ushort)(M16 + 5), (ushort)(M16 + 6), (ushort)(M16 + 7));\n\n                fixed (char* pIn = input, pOut = output)\n                {\n                    for (; i + 8 <= length; i += 8)\n                    {\n                        var vec = Sse2.LoadVector128((ushort*)(pIn + i));\n                        var result = Sse2.Xor(vec, mask);\n                        Sse2.Store((ushort*)(pOut + i), result);\n                        mask = Sse2.Add(mask, increment);\n                    }\n                }\n            }\n#endif\n            for (; i < length; i++)\n            {\n                output[i] = (char)(input[i] ^ (char)(M16 + i));\n            }\n        }\n\n        /// <summary>\n        /// XOR each byte with (byte)(0xAA + i), writing to output.\n        /// </summary>\n        public static unsafe void XorBytes(ReadOnlySpan<byte> input, Span<byte> output)\n        {\n            int length = input.Length;\n            int i = 0;\n\n#if NET6_0_OR_GREATER\n            if (Avx2.IsSupported && length >= 32)\n            {\n                var increment = Vector256.Create((byte)32);\n                var mask = Vector256.Create(\n                    (byte)(M8 + 0),  (byte)(M8 + 1),  (byte)(M8 + 2),  (byte)(M8 + 3),\n                    (byte)(M8 + 4),  (byte)(M8 + 5),  (byte)(M8 + 6),  (byte)(M8 + 7),\n                    (byte)(M8 + 8),  (byte)(M8 + 9),  (byte)(M8 + 10), (byte)(M8 + 11),\n                    (byte)(M8 + 12), (byte)(M8 + 13), (byte)(M8 + 14), (byte)(M8 + 15),\n                    (byte)(M8 + 16), (byte)(M8 + 17), (byte)(M8 + 18), (byte)(M8 + 19),\n                    (byte)(M8 + 20), (byte)(M8 + 21), (byte)(M8 + 22), (byte)(M8 + 23),\n                    (byte)(M8 + 24), (byte)(M8 + 25), (byte)(M8 + 26), (byte)(M8 + 27),\n                    (byte)(M8 + 28), (byte)(M8 + 29), (byte)(M8 + 30), (byte)(M8 + 31));\n\n                fixed (byte* pIn = input, pOut = output)\n                {\n                    for (; i + 32 <= length; i += 32)\n                    {\n                        var data = Avx.LoadVector256(pIn + i);\n                        var result = Avx2.Xor(data, mask);\n                        Avx.Store(pOut + i, result);\n                        mask = Avx2.Add(mask, increment);\n                    }\n                }\n            }\n            else if (Sse2.IsSupported && length >= 16)\n            {\n                var increment = Vector128.Create((byte)16);\n                var mask = Vector128.Create(\n                    (byte)(M8 + 0), (byte)(M8 + 1), (byte)(M8 + 2), (byte)(M8 + 3),\n                    (byte)(M8 + 4), (byte)(M8 + 5), (byte)(M8 + 6), (byte)(M8 + 7),\n                    (byte)(M8 + 8), (byte)(M8 + 9), (byte)(M8 + 10), (byte)(M8 + 11),\n                    (byte)(M8 + 12), (byte)(M8 + 13), (byte)(M8 + 14), (byte)(M8 + 15));\n\n                fixed (byte* pIn = input, pOut = output)\n                {\n                    for (; i + 16 <= length; i += 16)\n                    {\n                        var data = Sse2.LoadVector128(pIn + i);\n                        var result = Sse2.Xor(data, mask);\n                        Sse2.Store(pOut + i, result);\n                        mask = Sse2.Add(mask, increment);\n                    }\n                }\n            }\n#endif\n            for (; i < length; i++)\n            {\n                output[i] = (byte)(input[i] ^ (byte)(M8 + i));\n            }\n        }\n\n        public static unsafe int SumBytes(ReadOnlySpan<byte> data)\n        {\n            int cs = 0;\n            int i = 0;\n            int count = data.Length;\n\n            fixed (byte* pBuffer = data)\n            {\n#if NET6_0_OR_GREATER\n                if (Avx2.IsSupported)\n                {\n                    var sum = Vector256<long>.Zero;\n                    var zero = Vector256<byte>.Zero;\n                    for (; i + 32 <= count; i += 32)\n                    {\n                        var v = Avx.LoadVector256(pBuffer + i);\n                        sum = Avx2.Add(sum, Avx2.SumAbsoluteDifferences(v, zero).AsInt64());\n                    }\n                    var lo = sum.GetLower();\n                    var hi = sum.GetUpper();\n                    var total = Sse2.Add(lo, hi);\n                    cs = (int)(total.GetElement(0) + total.GetElement(1));\n                }\n                else if (Sse2.IsSupported)\n                {\n                    var sum = Vector128<long>.Zero;\n                    var zero = Vector128<byte>.Zero;\n                    for (; i + 16 <= count; i += 16)\n                    {\n                        var v = Sse2.LoadVector128(pBuffer + i);\n                        sum = Sse2.Add(sum, Sse2.SumAbsoluteDifferences(v, zero).AsInt64());\n                    }\n                    cs = (int)(sum.GetElement(0) + sum.GetElement(1));\n                }\n#endif\n                for (; i < count; i++)\n                {\n                    cs += pBuffer[i];\n                }\n            }\n\n            return cs;\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/PartialStream.cs",
    "content": "﻿using System;\nusing System.IO;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public class PartialStream : Stream\n    {\n        public PartialStream(Stream baseStream, long offset, long length, bool leaveOpen = false)\n        {\n            if (baseStream == null)\n            {\n                throw new ArgumentNullException(\"baseStream\", \"BaseStream cannot be null.\");\n            }\n            if (offset < 0)\n            {\n                throw new ArgumentOutOfRangeException(\"offset\", \"Offset cannot be negative.\");\n            }\n            if (length < 0)\n            {\n                throw new ArgumentOutOfRangeException(\"offset\", \"Length cannot be negative.\");\n            }\n            this.baseStream = baseStream;\n            this.offset = offset;\n            this.length = length;\n            this.leaveOpen = leaveOpen;\n        }\n\n        private Stream baseStream;\n        private long offset;\n        private long length;\n        private bool leaveOpen;\n\n        public Stream BaseStream\n        {\n            get { return baseStream; }\n        }\n\n        public override bool CanRead\n        {\n            get { return baseStream.CanRead; }\n        }\n\n        public override bool CanSeek\n        {\n            get { return baseStream.CanSeek; }\n        }\n\n        public override bool CanWrite\n        {\n            get { return baseStream.CanWrite; }\n        }\n\n        public override void Flush()\n        {\n            baseStream.Flush();\n        }\n\n        public override long Length\n        {\n            get { return this.length; }\n        }\n\n        public virtual long Offset\n        {\n            get { return this.offset; }\n        }\n\n        public override long Position\n        {\n            get\n            {\n                return baseStream.Position - this.offset;\n            }\n            set\n            {\n                baseStream.Position = value + this.offset;\n            }\n        }\n\n        public override int Read(byte[] buffer, int offset, int count)\n        {\n            long curPos = this.Position;\n            if (curPos < 0)\n                return 0;\n            long maxCount = this.length - curPos;\n            if (maxCount < 0)\n                return 0;\n            return baseStream.Read(buffer, offset, (int)Math.Min(count, maxCount));\n        }\n\n        public override long Seek(long offset, SeekOrigin origin)\n        {\n            switch (origin)\n            {\n                case SeekOrigin.Begin:\n                    offset += this.offset;\n                    break;\n                case SeekOrigin.Current:\n                    offset += this.Position;\n                    break;\n                case SeekOrigin.End:\n                    offset += this.offset + this.length;\n                    break;\n                default:\n                    throw new ArgumentException(\"Unknown SeekOrigin.\", \"origin\");\n            }\n            if (offset < this.offset)\n                throw new IOException(\"Attempt to seek front of the stream.\");\n            return baseStream.Seek(offset, SeekOrigin.Begin) - this.offset;\n        }\n\n        public override void SetLength(long value)\n        {\n            throw new NotSupportedException(\"Cannot set the length of PartialStream.\");\n        }\n\n        public override void Write(byte[] buffer, int offset, int count)\n        {\n            if (this.Position + count > this.length)\n                throw new IOException(\"Cannot write out of bound.\");\n            baseStream.Write(buffer, offset, count);\n        }\n\n        public override void Close()\n        {\n            this.Dispose(true);\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            if (disposing)\n            {\n                if (!this.leaveOpen)\n                {\n                    baseStream.Dispose();\n                }\n            }\n        }\n\n        public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)\n        {\n            if (this.Position < 0)\n                throw new IOException(\"Cannot read out of bound.\");\n            return baseStream.BeginRead(buffer, offset, count, callback, state);\n        }\n\n        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)\n        {\n            if (this.Position + count > this.length)\n                throw new IOException(\"Cannot write out of bound.\");\n            return baseStream.BeginWrite(buffer, offset, count, callback, state);\n        }\n\n        public override int EndRead(IAsyncResult asyncResult)\n        {\n            return base.EndRead(asyncResult);\n        }\n\n        public override void EndWrite(IAsyncResult asyncResult)\n        {\n            baseStream.EndWrite(asyncResult);\n        }\n\n        public override int ReadByte()\n        {\n            long curPos = this.Position;\n            if (curPos >= 0 && curPos < this.length)\n                return baseStream.ReadByte();\n            else\n                return -1;\n        }\n\n        public override void WriteByte(byte value)\n        {\n            if (this.Position >= 0 && this.Position < this.length)\n                baseStream.WriteByte(value);\n        }\n\n        public override bool CanTimeout\n        {\n            get\n            {\n                return baseStream.CanTimeout;\n            }\n        }\n\n        public override int ReadTimeout\n        {\n            get\n            {\n                return baseStream.ReadTimeout;\n            }\n            set\n            {\n                baseStream.ReadTimeout = value;\n            }\n        }\n\n        public override int WriteTimeout\n        {\n            get\n            {\n                return baseStream.WriteTimeout;\n            }\n            set\n            {\n                baseStream.WriteTimeout = value;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/SimpleWzStringPool.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    internal class SimpleWzStringPool : IWzStringPool\n    {\n        public SimpleWzStringPool() \n        {\n            this.stringTable = new Dictionary<long, string>();\n            this.asciiStringTable = new Dictionary<long, string>();\n        }\n\n        private Dictionary<long, string> stringTable;\n        private Dictionary<long, string> asciiStringTable;\n\n        public string GetOrAdd(long offset, ReadOnlySpan<char> chars)\n        {\n            if (this.TryGet(offset, out string s))\n            {\n                if (!s.AsSpan().SequenceEqual(chars))\n                {\n                    throw new Exception(\"The cached string does not match the input string\");\n                }\n            }\n            else if (chars == null || chars.Length == 0)\n            {\n                return string.Empty;\n            }\n            else\n            {\n                // usually wz won't compress string with length less than 8 into reference.\n                if (chars.Length < 8 && IsAsciiString(chars))\n                {\n                    long hash = 0;\n                    for (int i = 0; i < chars.Length; i++)\n                    {\n                        hash |= (long)(chars[i] & 0xff) << (i * 8);\n                    }\n                    if (!this.asciiStringTable.TryGetValue(hash, out s))\n                    {\n                        s = chars.ToString();\n                        this.asciiStringTable.Add(hash, s);\n                    }\n                }\n                else\n                {\n                    s = chars.ToString();\n                }\n                this.stringTable.Add(offset, s);\n            }\n            return s;\n        }\n\n        public bool TryGet(long offset, out string s)\n        {\n            return this.stringTable.TryGetValue(offset, out s);\n        }\n\n        public void Reset()\n        {\n            this.stringTable.Clear();\n        }\n\n        private static bool IsAsciiString(ReadOnlySpan<char> chars)\n        {\n            for(int i = 0; i < chars.Length; i++)\n            {\n                if ((uint)(chars[i] - 0x20) >= 0x60)\n                {\n                    return false;\n                }\n            }\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/StreamExtension.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.IO;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public static class StreamExtension\n    {\n#if NETFRAMEWORK\n        public static int Read(this Stream stream, Span<byte> buffer)\n        {\n            byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length);\n            try\n            {\n                int numRead = stream.Read(sharedBuffer, 0, buffer.Length);\n                if ((uint)numRead > (uint)buffer.Length)\n                {\n                    throw new IOException();\n                }\n                new Span<byte>(sharedBuffer, 0, numRead).CopyTo(buffer);\n                return numRead;\n            }\n            finally\n            {\n                ArrayPool<byte>.Shared.Return(sharedBuffer);\n            }\n        }\n#endif\n\n#if NETFRAMEWORK || NET6_0\n        public static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count)\n        {\n            while (count > 0)\n            {\n                int actual = stream.Read(buffer, offset, count);\n                if (actual == 0)\n                    throw new System.IO.EndOfStreamException();\n                offset += actual;\n                count -= actual;\n            }\n        }\n#endif\n\n#if NETFRAMEWORK\n        public static void ReadExactly(this Stream stream, Span<byte> buffer)\n        {\n            byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(Math.Min(buffer.Length, 16384));\n            try\n            {\n                while (buffer.Length > 0)\n                {\n                    int actual = stream.Read(sharedBuffer, 0, Math.Min(sharedBuffer.Length, buffer.Length));\n                    if (actual == 0)\n                        throw new System.IO.EndOfStreamException();\n                    new Span<byte>(sharedBuffer, 0, actual).CopyTo(buffer);\n                    buffer = buffer.Slice(actual);\n                }\n            }\n            finally\n            {\n                ArrayPool<byte>.Shared.Return(sharedBuffer);\n            }\n        }\n#elif NET6_0\n        public static void ReadExactly(this Stream stream, Span<byte> buffer)\n        {\n            while (buffer.Length > 0)\n            {\n                int actual = stream.Read(buffer);\n                if (actual == 0)\n                    throw new System.IO.EndOfStreamException();\n                buffer = buffer.Slice(actual);\n            }\n        }\n#endif\n\n        public static int ReadAvailableBytes(this Stream stream, Span<byte> buffer)\n        {\n            int totalRead = 0;\n            while (buffer.Length > 0)\n            {\n                int bytesRead = stream.Read(buffer);\n                if (bytesRead == 0) break;\n                totalRead += bytesRead;\n                buffer = buffer.Slice(bytesRead);\n            }\n            return totalRead;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/WzBinaryReader.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.IO;\nusing System.Runtime.InteropServices;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    public class WzBinaryReader\n    {\n        public WzBinaryReader(Stream stream, bool useStringPool)\n            : this(stream, useStringPool ? new SimpleWzStringPool() : null)\n        {\n        }\n\n        public WzBinaryReader(Stream stream, IWzStringPool stringPool)\n        {\n            this.BaseStream = stream;\n            this.bReader = new BinaryReader(this.BaseStream, System.Text.Encoding.ASCII, true);\n            this.stringPool = stringPool;\n        }\n\n        public Stream BaseStream { get; private set; }\n        public int StringReferenceOffsetBytes { get; set; }\n        private BinaryReader bReader;\n        private IWzStringPool stringPool;\n\n        public byte ReadByte()\n        {\n            return this.bReader.ReadByte();\n        }\n\n        public sbyte ReadSByte()\n        {\n            return this.bReader.ReadSByte();\n        }\n\n        public short ReadInt16()\n        {\n            return this.bReader.ReadInt16();\n        }\n\n        public ushort ReadUInt16()\n        {\n            return this.bReader.ReadUInt16();\n        }\n\n        public int ReadCompressedInt32()\n        {\n            int s = this.bReader.ReadSByte();\n            return (s == -128) ? this.bReader.ReadInt32() : s;\n        }\n\n        public int ReadInt32()\n        {\n            return this.bReader.ReadInt32();\n        }\n\n        public uint ReadUInt32()\n        {\n            return this.bReader.ReadUInt32();\n        }\n\n        public long ReadCompressedInt64()\n        {\n            int s = this.bReader.ReadSByte();\n            return (s == -128) ? this.bReader.ReadInt64() : s;\n        }\n\n        public long ReadInt64()\n        {\n            return this.bReader.ReadInt64();\n        }\n\n        public float ReadCompressedSingle()\n        {\n            float fl = this.bReader.ReadSByte();\n            return (fl == -128) ? this.bReader.ReadSingle() : fl;\n        }\n\n        public double ReadDouble()\n        {\n            return this.bReader.ReadDouble();\n        }\n\n        public char[] ReadChars(int count)\n        {\n            return this.bReader.ReadChars(count);\n        }\n\n        public string ReadString(IWzDecrypter decrypter)\n        {\n            long currentPos = this.BaseStream.Position;\n\n            int size = this.ReadSByte();\n            if (size < 0) // read ASCII/cp1252 string\n            {\n                size = (size == -128) ? this.ReadInt32() : -size;\n\n                // for net6+ we can use Stream.Read(Span<byte>) instead, the array buffer is not needed.\n                var buffer = ArrayPool<byte>.Shared.Rent(size);\n                try\n                {\n                    this.BaseStream.ReadExactly(buffer, 0, size);\n                    decrypter.Decrypt(buffer.AsSpan(0, size));\n\n                    using var charBuffer = MemoryPool<char>.Shared.Rent(size);\n                    Span<char> chars = charBuffer.Memory.Span.Slice(0, size);\n                    MathHelper.XorWidenToChar(buffer.AsSpan(0, size), chars);\n                    return this.stringPool != null ? this.stringPool.GetOrAdd(currentPos, chars) : chars.ToString();\n                }\n                finally\n                {\n                    ArrayPool<byte>.Shared.Return(buffer);\n                }\n            }\n            else if (size > 0) // read UTF-16LE string\n            {\n                if (size == 127)\n                {\n                    size = this.bReader.ReadInt32();\n                }\n                int byteSize = size * 2;\n                var buffer = ArrayPool<byte>.Shared.Rent(byteSize);\n                try\n                {\n                    this.BaseStream.ReadExactly(buffer, 0, byteSize);\n                    decrypter.Decrypt(buffer.AsSpan(0, byteSize));\n\n                    Span<char> chars = MemoryMarshal.Cast<byte, char>(buffer.AsSpan(0, byteSize));\n                    MathHelper.XorChars(chars, chars);\n                    return this.stringPool != null ? this.stringPool.GetOrAdd(currentPos, chars) : chars.ToString();\n                }\n                finally\n                {\n                    ArrayPool<byte>.Shared.Return(buffer);\n                }\n            }\n            else\n            {\n                return string.Empty;\n            }\n        }\n\n        // Introduced in KMST1198\n        public string ReadPkg2DirString(IWzDecrypter decrypter)\n        {\n            long currentPos = this.BaseStream.Position;\n\n            int size = this.ReadSByte();\n            if (size < 0)\n            {\n                size = -size;\n                int byteSize = size * 2;\n                var buffer = ArrayPool<byte>.Shared.Rent(byteSize);\n                try\n                {\n                    this.BaseStream.ReadExactly(buffer, 0, byteSize);\n                    decrypter.Decrypt(buffer.AsSpan(0, byteSize));\n                    Span<char> chars = MemoryMarshal.Cast<byte, char>(buffer.AsSpan(0, byteSize));\n                    return this.stringPool != null ? this.stringPool.GetOrAdd(currentPos, chars) : chars.ToString();\n                }\n                finally\n                {\n                    ArrayPool<byte>.Shared.Return(buffer);\n                }\n            }\n            else if (size > 0)\n            {\n                throw new Exception($\"Unexpected string length: {size}\");\n            }\n            else\n            {\n                return string.Empty;\n            }\n        }\n\n        public string ReadImageObjectTypeName(IWzDecrypter decrypter)\n        {\n            int flag = this.bReader.ReadByte();\n            switch (flag)\n            {\n                case 0x73:\n                    return this.ReadString(decrypter);\n                case 0x1B:\n                    return this.ReadStringAt(this.ReadInt32() + this.StringReferenceOffsetBytes, decrypter);\n                default:\n                    throw new Exception($\"Unexpected flag '{flag}' when reading string at {this.BaseStream.Position}.\");\n            }\n        }\n\n        public string ReadImageString(IWzDecrypter decrypter)\n        {\n            int flag = this.bReader.ReadByte();\n            switch (flag)\n            {\n                case 0x00:\n                    return this.ReadString(decrypter);\n                case 0x01:\n                    return this.ReadStringAt(this.ReadInt32() + this.StringReferenceOffsetBytes, decrypter);\n                case 0x04: \n                    this.SkipBytes(8);\n                    return null;\n                default:\n                    throw new Exception($\"Unexpected flag '{flag}' when reading string at {this.BaseStream.Position}.\");\n            }\n        }\n\n        public string ReadStringAt(long offset, IWzDecrypter decrypter)\n        {\n            if (this.stringPool != null && this.stringPool.TryGet(offset, out string s))\n            {\n                return s;\n            }\n            long currentPos = this.BaseStream.Position;\n            this.BaseStream.Position = offset;\n            s = this.ReadString(decrypter);\n            this.BaseStream.Position = currentPos;\n            return s;\n        }\n\n        public byte[] ReadBytes(int count)\n        {\n            return this.bReader.ReadBytes(count);\n        }\n\n        public void SkipBytes(int count)\n        {\n            if (this.BaseStream.CanSeek)\n            {\n                this.BaseStream.Position += count;\n            }\n            else\n            {\n                var buffer = ArrayPool<byte>.Shared.Rent(Math.Min(count, 16384));\n                try\n                {\n                    while (count > 0)\n                    {\n                        int actual = this.BaseStream.Read(buffer, 0, Math.Min(count, buffer.Length));\n                        if (actual == 0)\n                        {\n                            throw new EndOfStreamException();\n                        }\n                        count -= actual;\n                    }\n                }\n                finally\n                {\n                    ArrayPool<byte>.Shared.Return(buffer);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Utilities/WzStreamReader.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Text;\n\nnamespace WzComparerR2.WzLib.Utilities\n{\n    internal class WzStreamReader\n    {\n        public WzStreamReader(Stream stream)\n        {\n            this.BaseStream = stream;\n            this.streamReader = new StreamReader(stream, encoding: Encoding.UTF8, true, 1024, true);\n            this.charBuffer = new StringBuilder(64);\n        }\n\n        public Stream BaseStream { get; private set; }\n        private readonly StreamReader streamReader;\n        private readonly StringBuilder charBuffer;\n\n        public bool EndOfStream => this.streamReader.EndOfStream;\n        public int Read() => this.streamReader.Read();\n        public int Peek() => this.streamReader.Peek();\n        public string ReadLine() => this.streamReader.ReadLine();\n\n        public void SkipLine()\n        {\n            while (!this.streamReader.EndOfStream)\n            {\n                int nextChar = streamReader.Read();\n                if (nextChar == '\\n')\n                {\n                    break;\n                }\n            }\n        }\n\n        public bool SkipLineAndCheckEmpty()\n        {\n            bool allWhiteSpace = true;\n            while (!this.streamReader.EndOfStream)\n            {\n                int nextChar = this.streamReader.Read();\n                if (nextChar == '\\n')\n                {\n                    break;\n                }\n                else if (!char.IsWhiteSpace((char)nextChar))\n                {\n                    allWhiteSpace = false;\n                }\n            }\n            return allWhiteSpace;\n        }\n\n        public void SkipWhitespaceExceptLineEnding()\n        {\n            while (!this.streamReader.EndOfStream)\n            {\n                int nextChar = this.streamReader.Peek();\n                if (nextChar == '\\n' || !char.IsWhiteSpace((char)nextChar))\n                {\n                    break;\n                }\n                this.streamReader.Read();\n            }\n        }\n\n        public string ReadUntilWhitespace()\n        {\n            this.charBuffer.Clear();\n            while (!this.streamReader.EndOfStream)\n            {\n                int nextChar = this.streamReader.Peek();\n                if (char.IsWhiteSpace((char)nextChar))\n                {\n                    break;\n                }\n                this.charBuffer.Append((char)this.streamReader.Read());\n            }\n            return this.charBuffer.ToString();\n        }\n\n        public int ReadRepeatChars(char c, int maxLength = -1)\n        {\n            int totalLen = 0;\n            while (!this.streamReader.EndOfStream)\n            {\n                int nextChar = this.streamReader.Peek();\n                if (nextChar != c)\n                {\n                    break;\n                }\n\n                this.streamReader.Read();\n                ++totalLen;\n                if (maxLength > 0 && totalLen >= maxLength)\n                {\n                    break;\n                }\n            }\n            return totalLen;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/WzComparerR2.WzLib.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net462;net6.0;net8.0</TargetFrameworks>\n    <AssemblyName>WzComparerR2.WzLib</AssemblyName>\n    <RootNamespace>WzComparerR2.WzLib</RootNamespace>\n    <IsPublishable>false</IsPublishable>\n    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>\n    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n    <LangVersion>latest</LangVersion>\n    <Nullable>disable</Nullable>\n  </PropertyGroup>\n  <Import Project=\"$(ProjectDir)..\\Build\\Common.props\" />\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'core'\">\n  </PropertyGroup>\n  <PropertyGroup Condition=\"$(DotnetEdition) == 'framework'\">\n  </PropertyGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'core'\">\n    <PackageReference Include=\"System.Drawing.Common\" Version=\"$(SystemDrawingCommonVersion)\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"$(DotnetEdition) == 'framework'\">\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Drawing\" />\n    <Reference Include=\"System.Xml\" />\n    <PackageReference Include=\"System.Memory\" Version=\"4.5.5\" />\n  </ItemGroup>\n  <ItemGroup Condition=\"Exists('..\\Build\\CommonAssemblyInfo.cs')\">\n    <Compile Include=\"..\\Build\\CommonAssemblyInfo.cs\">\n      <Link>Properties\\CommonAssemblyInfo.cs</Link>\n    </Compile>\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Capabilities.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.WzLib\n{\n    [Flags]\n    public enum Wz_Capabilities\n    {\n        Default = 0,\n        EncverMissing = 1,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Convex.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Convex\n    {\n        public Wz_Convex(Wz_Vector[] points)\n        {\n            this.Points = points;\n        }\n\n        public Wz_Vector[] Points { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Crypto.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.Security.Cryptography;\nusing AES = System.Security.Cryptography.Aes;\nusing System.Collections.Specialized;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.WzLib.Utilities;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nusing static WzComparerR2.WzLib.Utilities.MathHelper;\n\nnamespace WzComparerR2.WzLib\n{\n    public enum Wz_CryptoKeyType\n    {\n        Unknown = 0,\n        BMS = 1,\n        KMS = 2,\n        GMS = 3,\n        KMST1198 = 4,\n        KMST1199 = 5,\n    }\n\n    public class Wz_Crypto\n    {\n        public Wz_Crypto()\n        {\n            this.keys_bms = Wz_NonOpCryptoKey.Instance;\n            this.keys_kms = new Wz_CryptoKey(iv_kms);\n            this.keys_gms = new Wz_CryptoKey(iv_gms);\n            this.keys_kmst1198 = new Pkg2DirStringKey(0xDEADBEEF);\n            this.UseListWz = false;\n            this.Pkg1EncType = Wz_CryptoKeyType.Unknown;\n            this.List = new StringCollection();\n        }\n\n        public void Reset()\n        {\n            this.UseListWz = false;\n            this.Pkg1EncType = Wz_CryptoKeyType.Unknown;\n            this.List.Clear();\n            this.KnownProfiles.Clear();\n        }\n\n        // Known version cache: populated after successful detection, used as fast path for subsequent files.\n        public List<KnownProfileEntry> KnownProfiles { get; } = new();\n\n        public bool ListContains(string name)\n        {\n            bool contains = this.List.Contains(name);\n            if (contains)\n                this.List.Remove(name);\n            return contains;\n        }\n\n        public void LoadListWz(string path)\n        {\n            path = Path.Combine(path, \"List.wz\");\n            if (File.Exists(path))\n            {\n                this.UseListWz = true;\n                using (FileStream list_file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))\n                {\n                    BinaryReader listwz = new BinaryReader(list_file);\n                    int length = (int)list_file.Length;\n                    int len = 0;\n                    byte b = 0;\n                    string folder = \"\";\n                    list_file.Position += 4;\n                    byte check_for_d = listwz.ReadByte();\n\n                    if ((char)(check_for_d ^ this.keys_gms[0]) == 'd')\n                    {\n                        this.Pkg1EncType = Wz_CryptoKeyType.GMS;\n                    }\n                    else if ((char)(check_for_d ^ this.keys_kms[0]) == 'd')\n                    {\n                        this.Pkg1EncType = Wz_CryptoKeyType.KMS;\n                    }\n\n                    list_file.Position = 0;\n                    while (list_file.Position < length)\n                    {\n                        len = listwz.ReadInt32() * 2;\n                        for (int i = 0; i < len; i += 2)\n                        {\n                            b = (byte)(listwz.ReadByte() ^ this.Pkg1Keys[i]);\n                            folder += (char)(b);\n                            list_file.Position++;\n                        }\n                        list_file.Position += 2;\n                        folder.Replace(\".im/\", \".img\");\n                        this.List.Add(folder);\n                        folder = \"\";\n                    }\n                    this.List.Remove(\"dummy\");\n                }\n            }\n        }\n\n        static readonly byte[] iv_gms = { 0x4d, 0x23, 0xc7, 0x2b };\n        static readonly byte[] iv_kms = { 0xb9, 0x7d, 0x63, 0xe9 };\n\n        private IWzDecrypter keys_bms;\n        private Wz_CryptoKey keys_gms, keys_kms;\n        private IWzDecrypter keys_kmst1198;\n\n        public bool UseListWz { get; private set; }\n        public StringCollection List { get; private set; }\n\n        public Wz_CryptoKeyType Pkg1EncType { get; set; }\n        public Wz_CryptoKeyType Pkg2EncType { get; set; }\n        public bool Pkg1DirEncDetected => this.Pkg1EncType != Wz_CryptoKeyType.Unknown;\n        public bool Pkg2DirEncDetected => this.Pkg2EncType != Wz_CryptoKeyType.Unknown;\n        public IWzDecrypter Pkg1Keys => this.GetKeys(this.Pkg1EncType);\n        public IWzDecrypter Pkg2Keys => this.GetKeys(this.Pkg2EncType);\n\n        public bool IsDirEncDetected(Wz_File wzFile)\n        {\n            if (wzFile.Header.IsPkg1) return this.Pkg1DirEncDetected;\n            if (wzFile.Header.IsPkg2) return this.Pkg2DirEncDetected;\n            throw new Exception($\"Unknown wzfile signature: {wzFile.Header.Signature}\");\n        }\n\n        public IWzDecrypter GetKeys(Wz_CryptoKeyType keyType)\n        {\n            switch (keyType)\n            {\n                case Wz_CryptoKeyType.Unknown: return null;\n                case Wz_CryptoKeyType.BMS: return this.keys_bms;\n                case Wz_CryptoKeyType.KMS: return this.keys_kms;\n                case Wz_CryptoKeyType.GMS: return this.keys_gms;\n                case Wz_CryptoKeyType.KMST1198 : return this.keys_kmst1198;\n                case Wz_CryptoKeyType.KMST1199 : throw new NotSupportedException($\"KMST1199 PKG2 directory encryption is not supported.\");\n                default: throw new ArgumentOutOfRangeException(nameof(keyType));\n            }\n        }\n\n        public class Wz_CryptoKey : IWzDecrypter\n        {\n            public Wz_CryptoKey(byte[] iv)\n            {\n                this.iv = iv;\n            }\n\n            private byte[] keys;\n            private byte[] iv;\n\n            public byte this[int index]\n            {\n                get\n                {\n                    if (keys == null || keys.Length <= index)\n                    {\n                        EnsureKeySize(index + 1);\n                    }\n                    return this.keys[index];\n                }\n            }\n\n            public void EnsureKeySize(int size)\n            {\n                if (this.keys != null && this.keys.Length >= size)\n                {\n                    return;\n                }\n\n                size = (size + 63) & ~63;\n                int startIndex = 0;\n\n                if (this.keys == null)\n                {\n                    keys = new byte[size];\n                }\n                else\n                {\n                    startIndex = this.keys.Length;\n                    Array.Resize(ref this.keys, size);\n                }\n\n                using var aes = AES.Create();\n                aes.KeySize = 256;\n                aes.BlockSize = 128;\n                aes.Key = aesKey;\n                aes.Mode = CipherMode.ECB;\n                MemoryStream ms = new MemoryStream(keys, startIndex, keys.Length - startIndex, true);\n                CryptoStream s = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write);\n\n                for (int i = startIndex; i < size; i += 16)\n                {\n                    if (i == 0)\n                    {\n                        byte[] block = new byte[16];\n                        for (int j = 0; j < block.Length; j++)\n                        {\n                            block[j] = iv[j % 4];\n                        }\n                        s.Write(block, 0, block.Length);\n                    }\n                    else\n                    {\n                        s.Write(keys, i - 16, 16);\n                    }\n                }\n\n                s.Flush();\n                ms.Close();\n            }\n\n            public void Decrypt(Span<byte> data)\n            {\n                this.Decrypt(data, 0);\n            }\n\n            public void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)\n            {\n                this.Decrypt(inputBuffer, outputBuffer, 0);\n            }\n\n            public void Decrypt(Span<byte> data, int keyOffset)\n            {\n                this.Decrypt((ReadOnlySpan<byte>)data, data, keyOffset);\n            }\n\n            public unsafe void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer, int keyOffset)\n            {\n                if (inputBuffer.Length != outputBuffer.Length)\n                {\n                    throw new ArgumentException(\"Input and output buffer lengths must match.\");\n                }\n\n                this.EnsureKeySize(keyOffset + inputBuffer.Length);\n                ReadOnlySpan<byte> keys = this.keys.AsSpan(keyOffset, inputBuffer.Length);\n\n#if NET6_0_OR_GREATER\n                if (Avx2.IsSupported && inputBuffer.Length >= 32)\n                {\n                    Vector256<byte> ymm0, ymm1;\n                    while (inputBuffer.Length >= 32)\n                    {\n                        fixed (byte* pInput = inputBuffer, pOutput = outputBuffer, pKeys = keys)\n                        {\n                            ymm0 = Avx.LoadVector256(pInput);\n                            ymm1 = Avx.LoadVector256(pKeys);\n                            Avx.Store(pOutput, Avx2.Xor(ymm0, ymm1));\n                        }\n                        inputBuffer = inputBuffer.Slice(32);\n                        outputBuffer = outputBuffer.Slice(32);\n                        keys = keys.Slice(32);\n                    }\n                }\n\n                if (Sse2.IsSupported && inputBuffer.Length >= 16)\n                {\n                    Vector128<byte> xmm0, xmm1;\n                    while (inputBuffer.Length >= 16)\n                    {\n                        fixed (byte* pInput = inputBuffer, pOutput = outputBuffer, pKeys = keys)\n                        {\n                            xmm0 = Sse2.LoadVector128(pInput);\n                            xmm1 = Sse2.LoadVector128(pKeys);\n                            Sse2.Store(pOutput, Sse2.Xor(xmm0, xmm1));\n                        }\n                        inputBuffer = inputBuffer.Slice(16);\n                        outputBuffer = outputBuffer.Slice(16);\n                        keys = keys.Slice(16);\n                    }\n                }\n#endif\n                while (inputBuffer.Length >= 4)\n                {\n                    MemoryMarshal.Cast<byte, int>(outputBuffer)[0] =\n                        MemoryMarshal.Cast<byte, int>(inputBuffer)[0] ^\n                        MemoryMarshal.Cast<byte, int>(keys)[0];\n                    inputBuffer = inputBuffer.Slice(4);\n                    outputBuffer = outputBuffer.Slice(4);\n                    keys = keys.Slice(4);\n                }\n\n                for (int i = 0; i < inputBuffer.Length; i++)\n                {\n                    outputBuffer[i] = (byte)(inputBuffer[i] ^ keys[i]);\n                }\n            }\n\n            static readonly byte[] aesKey = {0x13, 0x00, 0x00, 0x00,\n                                        0x08, 0x00, 0x00, 0x00,\n                                        0x06, 0x00, 0x00, 0x00,\n                                        0xB4, 0x00, 0x00, 0x00,\n                                        0x1B, 0x00, 0x00, 0x00,\n                                        0x0F, 0x00, 0x00, 0x00,\n                                        0x33, 0x00, 0x00, 0x00,\n                                        0x52, 0x00, 0x00, 0x00 };\n        }\n\n        public sealed class Wz_NonOpCryptoKey : IWzDecrypter\n        {\n            public static readonly IWzDecrypter Instance = new Wz_NonOpCryptoKey();\n\n            public Wz_NonOpCryptoKey()\n            {\n            }\n\n            public byte this[int index] => 0;\n\n            public void Decrypt(Span<byte> data)\n            {\n            }\n\n            public void Decrypt(Span<byte> data, int keyOffset)\n            {\n            }\n\n            public void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)\n            {\n                this.Decrypt(inputBuffer, outputBuffer, 0);\n            }\n\n            public void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer, int keyOffset)\n            {\n                if (inputBuffer.Length != outputBuffer.Length)\n                {\n                    throw new ArgumentException(\"Input and output buffer lengths must match.\");\n                }\n\n                inputBuffer.CopyTo(outputBuffer);\n            }\n        }\n\n        public class Pkg2DirStringKey : IWzDecrypter\n        {\n            public Pkg2DirStringKey(uint baseKey)\n            {\n                this.baseKey = baseKey;\n            }\n\n            private uint baseKey;\n            private byte[] keys;\n\n            public byte this[int index]\n            {\n                get\n                {\n                    this.CreateKeyIfNotExist();\n                    return this.keys[index % this.keys.Length];\n                }\n            }\n\n            private void CreateKeyIfNotExist()\n            {\n                if (this.keys != null) \n                { \n                    return; \n                }\n                byte[] keys = new byte[8];\n                Span<ushort> u16Keys = MemoryMarshal.Cast<byte, ushort>(keys.AsSpan());\n                for(int i = 0; i < 4; i++)\n                {\n                    u16Keys[i] = (ushort)(this.baseKey >> (8 * i));\n                }\n                this.keys = keys;\n            }\n\n            public void Decrypt(Span<byte> data)\n            {\n                this.Decrypt(data, 0);\n            }\n\n            public void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer)\n            {\n                this.Decrypt(inputBuffer, outputBuffer, 0);\n            }\n\n            public void Decrypt(Span<byte> data, int keyOffset)\n            {\n                this.Decrypt((ReadOnlySpan<byte>)data, data, keyOffset);\n            }\n\n            public unsafe void Decrypt(ReadOnlySpan<byte> inputBuffer, Span<byte> outputBuffer, int keyOffset)\n            {\n                if (inputBuffer.Length != outputBuffer.Length)\n                {\n                    throw new ArgumentException(\"Input and output buffer lengths must match.\");\n                }\n                if ((inputBuffer.Length & 1) != 0)\n                {\n                    throw new ArgumentException(\"Data length must be a multiple of 2.\", nameof(inputBuffer));\n                }\n                if ((keyOffset & 7) != 0)\n                {\n                    throw new ArgumentException(\"KeyOffset must be a multiple of 8.\", nameof(keyOffset));\n                }\n\n                this.CreateKeyIfNotExist();\n\n                long keyVal = MemoryMarshal.Cast<byte, long>(this.keys)[0];\n                while (inputBuffer.Length >= 8)\n                {\n                    MemoryMarshal.Cast<byte, long>(outputBuffer)[0] =\n                        MemoryMarshal.Cast<byte, long>(inputBuffer)[0] ^ keyVal;\n                    inputBuffer = inputBuffer.Slice(8);\n                    outputBuffer = outputBuffer.Slice(8);\n                }\n                for (int i = 0; i < inputBuffer.Length; i++)\n                {\n                    outputBuffer[i] = (byte)(inputBuffer[i] ^ keys[i % keys.Length]);\n                }\n            }\n        }\n\n        // KMST1199\n        public class Pkg2DirStringKeyV2 : Pkg2DirStringKey, IWzDecrypter\n        {\n            public Pkg2DirStringKeyV2(uint hash1, uint hashVersion) : base(ConvertKey(hash1, hashVersion))\n            {\n            }\n\n            private static uint ConvertKey(uint hash1, uint hashVersion)\n            {\n                uint baseHash = hash1 ^ hashVersion ^ 0x6D4C3B2A;\n                return Mix(Mix(baseHash) ^ 0x4F4CB34A);\n            }\n        }\n\n        public class KnownProfileEntry\n        {\n            public KnownProfileEntry(string profileName, int wzVersion, uint hashVersion)\n            {\n                this.ProfileName = profileName;\n                this.WzVersion = wzVersion;\n                this.HashVersion = hashVersion;\n            }\n\n            public string ProfileName { get; }\n            public int WzVersion { get; }\n            public uint HashVersion { get; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Directory.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Directory\n    {\n        public Wz_Directory(string name, int size, int cs32, uint hashOff, uint hashPos, Wz_File wz_f)\n        {\n            this.Name = name;\n            this.WzFile = wz_f;\n            this.Size = size;\n            this.Checksum = cs32;\n            this.HashedOffset = hashOff;\n            this.HashedOffsetPosition = hashPos;\n        }\n\n        public string Name { get; set; }\n        public Wz_File WzFile { get; set; }\n        public int Size { get; set; }\n        public int Checksum { get; set; }\n        public uint HashedOffset { get; set; }\n        public uint HashedOffsetPosition { get; set; }\n        public long Offset { get; set; }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_File.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing WzComparerR2.WzLib.Utilities;\nusing WzComparerR2.WzLib.Compatibility;\nusing static WzComparerR2.WzLib.Utilities.MathHelper;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_File : IMapleStoryFile, IDisposable\n    {\n        public Wz_File(string fileName, Wz_Structure wz)\n        {\n            this.imageCount = 0;\n            this.wzStructure = wz;\n            this.fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);\n            this.loaded = this.GetHeader(fileName);\n        }\n\n        private FileStream fileStream;\n        private Wz_Structure wzStructure;\n        private Wz_Header header;\n        private Wz_Node node;\n        private int imageCount;\n        private bool loaded;\n        private bool isSubDir;\n        private Wz_Type type;\n        private List<Wz_File> mergedWzFiles;\n        private Wz_File ownerWzFile;\n\n        /// <summary>\n        /// The offset calculator assigned during version detection, used for dir tree reading and image offset calculation.\n        /// </summary>\n        internal IWzImageOffsetCalc OffsetCalc { get; set; }\n\n        public Encoding TextEncoding { get; set; }\n\n        public object ReadLock => this.fileStream;\n\n        public FileStream FileStream\n        {\n            get { return fileStream; }\n        }\n\n        public Wz_Structure WzStructure\n        {\n            get { return wzStructure; }\n            set { wzStructure = value; }\n        }\n\n        public Wz_Header Header\n        {\n            get { return header; }\n            private set { header = value; }\n        }\n\n        public Wz_Node Node\n        {\n            get { return node; }\n            set { node = value; }\n        }\n\n        public int ImageCount\n        {\n            get { return imageCount; }\n        }\n\n        public bool Loaded\n        {\n            get { return loaded; }\n        }\n\n        public bool IsSubDir\n        {\n            get { return this.isSubDir; }\n        }\n\n        public Wz_Type Type\n        {\n            get { return type; }\n            set { type = value; }\n        }\n\n        public IEnumerable<Wz_File> MergedWzFiles\n        {\n            get { return this.mergedWzFiles ?? Enumerable.Empty<Wz_File>(); }\n        }\n\n        public Wz_File OwnerWzFile\n        {\n            get { return this.ownerWzFile; }\n        }\n\n        Wz_Structure IMapleStoryFile.WzStructure => this.wzStructure;\n\n        Stream IMapleStoryFile.FileStream => this.fileStream;\n\n        object IMapleStoryFile.ReadLock => this.ReadLock;\n\n        public void Close()\n        {\n            if (this.fileStream != null)\n                this.fileStream.Close();\n        }\n\n        void IDisposable.Dispose()\n        {\n            this.Close();\n        }\n\n        private bool GetHeader(string fileName)\n        {\n            this.fileStream.Position = 0;\n            var br = new WzBinaryReader(this.fileStream, false);\n\n            long filesize = this.FileStream.Length;\n            if (filesize < 4) { goto __failed; }\n\n            string signature = new string(br.ReadChars(4));\n            if (signature != Wz_Header.PKG1 && signature != Wz_Header.PKG2) { goto __failed; }\n\n            long dataSize = br.ReadInt64();\n            int headerSize = br.ReadInt32();\n            string copyright = new string(br.ReadChars(headerSize - (int)this.FileStream.Position));\n\n            if (signature == Wz_Header.PKG1)\n            {\n                // encver detecting:\n                // Since KMST1132, wz removed the 2 bytes encver, and use a fixed wzver '777'.\n                // Here we try to read the first 2 bytes from data part and guess if it looks like an encver.\n                bool encverMissing = false;\n                int encver = -1;\n                if (dataSize >= 2)\n                {\n                    this.fileStream.Position = headerSize;\n                    encver = br.ReadUInt16();\n                    // encver always less than 256\n                    if (encver > 0xff)\n                    {\n                        encverMissing = true;\n                    }\n                    else if (encver == 0x80)\n                    {\n                        // there's an exceptional case that the first field of data part is a compressed int which determined property count,\n                        // if the value greater than 127 and also to be a multiple of 256, the first 5 bytes will become to\n                        //   80 00 xx xx xx\n                        // so we additional check the int value, at most time the child node count in a wz won't greater than 65536.\n                        if (dataSize >= 5)\n                        {\n                            this.fileStream.Position = headerSize;\n                            int propCount = br.ReadCompressedInt32();\n                            if (propCount > 0 && (propCount & 0xff) == 0 && propCount <= 0xffff)\n                            {\n                                encverMissing = true;\n                            }\n                        }\n                    }\n                }\n                else\n                {\n                    // Obviously, if data part have only 1 byte, encver must be deleted.\n                    encverMissing = true;\n                }\n\n                int dataStartPos = headerSize + (encverMissing ? 0 : 2);\n                this.Header = new Wz_Header.WzPkg1Header(signature, copyright, fileName, headerSize, dataSize, filesize, dataStartPos, encverMissing, encver);\n            }\n            else if (signature == Wz_Header.PKG2)\n            {\n                uint hash1 = br.ReadUInt32();\n                uint hash2 = br.ReadUInt32();\n                int dataStartPos = (int)this.fileStream.Position;\n                this.Header = new Wz_Header.WzPkg2Header(signature, copyright, fileName, headerSize, dataSize, filesize, dataStartPos, hash1, hash2);\n            }\n            else\n            {\n                goto __failed;\n            }\n\n            return true;\n\n        __failed:\n            this.header = new Wz_Header(null, null, fileName, 0, 0, filesize, 0);\n            return false;\n        }\n\n        public void GetDirTree(Wz_Node parent, bool useBaseWz = false, bool loadWzAsFolder = false)\n        {\n            var ps = new PartialStream(this.FileStream, this.header.DirStartPosition, this.fileStream.Length - this.header.DirStartPosition, true);\n            ps.Position = 0;\n            var reader = new WzBinaryReader(ps, false);\n            this.GetDirTree(reader, parent, useBaseWz, loadWzAsFolder);\n        }\n\n        private void GetDirTree(WzBinaryReader reader, Wz_Node parent, bool useBaseWz = false, bool loadWzAsFolder = false)\n        {\n            List<Wz_Directory> dirs = new List<Wz_Directory>();\n\n            if (this.header.IsPkg1)\n            {\n                this.ReadDirTree(reader, parent, ref dirs);\n            }\n            else if (this.header.IsPkg2)\n            {\n                this.ReadDirTreePkg2(reader, parent, ref dirs);\n            }\n            else\n            {\n                throw new Exception($\"Unknown signature: {this.header.Signature}\");\n            }\n\n            int dirCount = dirs.Count;\n            bool willLoadBaseWz = useBaseWz ? parent.Text.Equals(\"base.wz\", StringComparison.OrdinalIgnoreCase) : false;\n\n            var baseFolder = Path.GetDirectoryName(this.header.FileName);\n\n            if (willLoadBaseWz && this.WzStructure.AutoDetectExtFiles)\n            {\n                for (int i = 0; i < dirCount; i++)\n                {\n                    //检测文件名\n                    var m = Regex.Match(dirs[i].Name, @\"^([A-Za-z]+)$\");\n                    if (m.Success)\n                    {\n                        string wzTypeName = m.Result(\"$1\");\n\n                        //检测扩展wz文件\n                        for (int fileID = 2; ; fileID++)\n                        {\n                            string extDirName = wzTypeName + fileID;\n                            string extWzFile = Path.Combine(baseFolder, extDirName + \".wz\");\n                            if (File.Exists(extWzFile))\n                            {\n                                if (!dirs.Take(dirCount).Any(dir => extDirName.Equals(dir.Name, StringComparison.OrdinalIgnoreCase)))\n                                {\n                                    dirs.Add(new Wz_Directory(extDirName, 0, 0, 0, 0, this));\n                                }\n                            }\n                            else\n                            {\n                                break;\n                            }\n                        }\n                        //检测KMST1058的wz文件\n                        for (int fileID = 1; ; fileID++)\n                        {\n                            string extDirName = wzTypeName + fileID.ToString(\"D3\");\n                            string extWzFile = Path.Combine(baseFolder, extDirName + \".wz\");\n                            if (File.Exists(extWzFile))\n                            {\n                                if (!dirs.Take(dirCount).Any(dir => extDirName.Equals(dir.Name, StringComparison.OrdinalIgnoreCase)))\n                                {\n                                    dirs.Add(new Wz_Directory(extDirName, 0, 0, 0, 0, this));\n                                }\n                            }\n                            else\n                            {\n                                break;\n                            }\n                        }\n                    }\n                }\n            }\n\n            for (int i = 0; i < dirs.Count; i++)\n            {\n                string dir = dirs[i].Name;\n                Wz_Node t = parent.Nodes.Add(dir);\n                if (i < dirCount)\n                {\n                    this.GetDirTree(reader, t, false);\n                }\n\n                if (t.Nodes.Count == 0)\n                {\n                    this.WzStructure.has_basewz |= willLoadBaseWz;\n\n                    try\n                    {\n                        if (loadWzAsFolder)\n                        {\n                            string wzFolder = willLoadBaseWz ? Path.Combine(Path.GetDirectoryName(baseFolder), dir) : Path.Combine(baseFolder, dir);\n                            if (Directory.Exists(wzFolder))\n                            {\n                                this.wzStructure.LoadWzFolder(wzFolder, ref t, false);\n                                if (!willLoadBaseWz)\n                                {\n                                    var dirWzFile = t.GetValue<Wz_File>();\n                                    dirWzFile.Type = Wz_Type.Unknown;\n                                    dirWzFile.isSubDir = true;\n                                }\n                            }\n                        }\n                        else if (willLoadBaseWz)\n                        {\n                            string filePath = Path.Combine(baseFolder, dir + \".wz\");\n                            if (File.Exists(filePath))\n                            {\n                                this.WzStructure.LoadFile(filePath, t, false, loadWzAsFolder);\n                            }\n                        }\n                    }\n                    catch (Exception ex)\n                    {\n                    }\n                }\n            }\n\n            parent.Nodes.Trim();\n        }\n\n        private void ReadDirTree(WzBinaryReader reader, Wz_Node parent, ref List<Wz_Directory> dirs)\n        {\n            var cryptoKey = this.WzStructure.encryption.Pkg1Keys;\n            int count = reader.ReadCompressedInt32();\n\n            for (int i = 0; i < count; i++)\n            {\n                byte nodeType = reader.ReadByte();\n                string name;\n                switch (nodeType)\n                {\n                    case 0x02:\n                        int stringOffAdd = this.Header.HasCapabilities(Wz_Capabilities.EncverMissing) ? 2 : -1;\n                        name = reader.ReadStringAt(reader.ReadInt32() + stringOffAdd, cryptoKey);\n                        break;\n                    case 0x04:\n                    case 0x03:\n                        name = reader.ReadString(cryptoKey);\n                        break;\n                    default:\n                        throw new Exception($\"Unknown type {nodeType} in WzDirTree.\");\n                }\n\n                int size = reader.ReadCompressedInt32();\n                int cs32 = reader.ReadCompressedInt32();\n                uint pos = (uint)this.fileStream.Position;\n                uint hashOffset = reader.ReadUInt32();\n\n                switch (nodeType)\n                {\n                    case 0x02:\n                    case 0x04:\n                        Wz_Image img = new Wz_Image(name, size, cs32, hashOffset, pos, this);\n                        if (this.OffsetCalc != null)\n                            img.Offset = this.OffsetCalc.CalcOffset(pos, hashOffset);\n                        Wz_Node childNode = parent.Nodes.Add(name);\n                        childNode.Value = img;\n                        img.OwnerNode = childNode;\n                        this.imageCount++;\n                        break;\n\n                    case 0x03:\n                        var dir = new Wz_Directory(name, size, cs32, hashOffset, pos, this);\n                        if (this.OffsetCalc != null)\n                            dir.Offset = this.OffsetCalc.CalcOffset(pos, hashOffset);\n                        dirs.Add(dir);\n                        break;\n                }\n            }\n        }\n\n        private void ReadDirTreePkg2(WzBinaryReader reader, Wz_Node parent, ref List<Wz_Directory> dirs)\n        {\n            var dirReader = ((Wz_Header.WzPkg2Header)this.Header).DirStringReader;\n            var pkg2Calc = this.OffsetCalc as IPkg2ImageOffsetCalc;\n            int encryptedEntryCount = reader.ReadCompressedInt32();\n            int entryCount = pkg2Calc?.DecryptEntryCount(encryptedEntryCount) ?? 0;\n\n            List<Pkg2DirEntry> entries = new();\n            for (int i = 0; i < entryCount; i++)\n            {\n                byte nodeType = reader.ReadByte();\n                string name;\n                if (nodeType == 0x03 || nodeType == 0x04)\n                {\n                    name = dirReader.ReadName(reader, entries.Count == 0);\n                }\n                else\n                {\n                    throw new Exception($\"Unknown type {nodeType} in WzDirTree.\");\n                }\n\n                int size = reader.ReadCompressedInt32();\n                int cs32 = reader.ReadCompressedInt32();\n                entries.Add(new Pkg2DirEntry\n                {\n                    NodeType = nodeType,\n                    Name = name,\n                    DataLength = size,\n                    Checksum = cs32\n                });\n            }\n\n            int encryptedOffsetCount = reader.ReadCompressedInt32();\n            if (encryptedOffsetCount == encryptedEntryCount && entries.Count > 0)\n            {\n                Span<Pkg2DirEntry> list = CollectionsMarshal.AsSpan(entries);\n                for (int i = 0; i < list.Length; i++)\n                {\n                    uint pos = (uint)this.fileStream.Position;\n                    uint hashOffset = reader.ReadUInt32();\n                    ref Pkg2DirEntry entry = ref list[i];\n                    switch (entry.NodeType)\n                    {\n                        case 0x04:\n                            Wz_Image img = new Wz_Image(entry.Name, entry.DataLength, entry.Checksum, hashOffset, pos, this);\n                            if (this.OffsetCalc != null)\n                                img.Offset = this.OffsetCalc.CalcOffset(pos, hashOffset);\n                            Wz_Node childNode = parent.Nodes.Add(entry.Name);\n                            childNode.Value = img;\n                            img.OwnerNode = childNode;\n                            this.imageCount++;\n                            break;\n\n                        case 0x03:\n                            var dir = new Wz_Directory(entry.Name, entry.DataLength, entry.Checksum, hashOffset, pos, this);\n                            if (this.OffsetCalc != null)\n                                dir.Offset = this.OffsetCalc.CalcOffset(pos, hashOffset);\n                            dirs.Add(dir);\n                            break;\n                    }\n                }\n            }\n        }\n\n        private string getFullPath(Wz_Node parent, string name)\n        {\n            List<string> path = new List<string>(5);\n            path.Add(name.ToLower());\n            while (parent != null && !(parent.Value is Wz_File))\n            {\n                path.Insert(0, parent.Text.ToLower());\n                parent = parent.ParentNode;\n            }\n            if (parent != null)\n            {\n                path.Insert(0, parent.Text.ToLower().Replace(\".wz\", \"\"));\n            }\n            return string.Join(\"/\", path.ToArray());\n        }\n\n        public void DetectWzType()\n        {\n            this.type = Wz_Type.Unknown;\n            if (this.node == null)\n            {\n                return;\n            }\n\n            if (this.node.Nodes[\"smap.img\"] != null\n                || this.node.Nodes[\"zmap.img\"] != null)\n            {\n                this.type = Wz_Type.Base;\n            }\n            else if (this.node.Nodes[\"00002000.img\"] != null\n                || this.node.Nodes[\"Accessory\"] != null\n                || this.node.Nodes[\"Weapon\"] != null)\n            {\n                this.type = Wz_Type.Character;\n            }\n            else if (this.node.Nodes[\"BasicEff.img\"] != null\n                || this.node.Nodes[\"SetItemInfoEff.img\"] != null)\n            {\n                this.type = Wz_Type.Effect;\n            }\n            else if (this.node.Nodes[\"Commodity.img\"] != null\n                || this.node.Nodes[\"Curse.img\"] != null)\n            {\n                this.type = Wz_Type.Etc;\n            }\n            else if (this.node.Nodes[\"Cash\"] != null\n                || this.node.Nodes[\"Consume\"] != null)\n            {\n                this.type = Wz_Type.Item;\n            }\n            else if (this.node.Nodes[\"Back\"] != null\n                || this.node.Nodes[\"Obj\"] != null\n                || this.node.Nodes[\"Physics.img\"] != null)\n            {\n                this.type = Wz_Type.Map;\n            }\n            else if (this.node.Nodes[\"PQuest.img\"] != null\n                || this.node.Nodes[\"QuestData\"] != null)\n            {\n                this.type = Wz_Type.Quest;\n            }\n            else if (this.node.Nodes[\"Attacktype.img\"] != null\n                || this.node.Nodes[\"Recipe_9200.img\"] != null)\n            {\n                this.type = Wz_Type.Skill;\n            }\n            else if (this.node.Nodes[\"Bgm00.img\"] != null\n                || this.node.Nodes[\"BgmUI.img\"] != null)\n            {\n                this.type = Wz_Type.Sound;\n            }\n            else if (this.node.Nodes[\"MonsterBook.img\"] != null\n                || this.node.Nodes[\"EULA.img\"] != null)\n            {\n                this.type = Wz_Type.String;\n            }\n            else if (this.node.Nodes[\"CashShop.img\"] != null\n                || this.node.Nodes[\"UIWindow.img\"] != null)\n            {\n                this.type = Wz_Type.UI;\n            }\n\n            if (this.type == Wz_Type.Unknown) //用文件名来判断\n            {\n                string wzName = this.node.Text;\n\n                Match m = Regex.Match(wzName, @\"^([A-Za-z]+)_?(\\d+)?(?:\\.wz)?$\");\n                if (m.Success)\n                {\n                    wzName = m.Result(\"$1\");\n                }\n                this.type = Enum.TryParse<Wz_Type>(wzName, true, out var result) ? result : Wz_Type.Unknown;\n            }\n        }\n\n        public void MergeWzFile(Wz_File wz_File)\n        {\n            var children = wz_File.node.Nodes.ToList();\n            wz_File.node.Nodes.Clear();\n            foreach (var child in children)\n            {\n                this.node.Nodes.Add(child);\n            }\n\n            if (this.mergedWzFiles == null)\n            {\n                this.mergedWzFiles = new List<Wz_File>();\n            }\n            this.mergedWzFiles.Add(wz_File);\n\n            wz_File.ownerWzFile = this;\n        }\n\n\n        private struct Pkg2DirEntry\n        {\n            public int NodeType;\n            public string Name;\n            public int DataLength;\n            public int Checksum;\n        }\n    }\n\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Header.cs",
    "content": "﻿using System;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing WzComparerR2.WzLib.Compatibility;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Header\n    {\n        public const string PKG1 = \"PKG1\";\n        public const string PKG2 = \"PKG2\";\n\n        public Wz_Header(string signature, string copyright, string file_name, int head_size, long data_size, long file_size, long dataStartPosition)\n        {\n            this.Signature = signature;\n            this.Copyright = copyright;\n            this.FileName = file_name;\n            this.HeaderSize = head_size;\n            this.DataSize = data_size;\n            this.FileSize = file_size;\n            this.DirStartPosition = dataStartPosition;\n            this.VersionChecked = false;\n        }\n\n        public class WzPkg1Header : Wz_Header\n        {\n            public WzPkg1Header(string signature, string copyright, string fileName, int headerSize, long dataSize, long fileSize, long dataStartPosition, bool encverMissing, int encryptedVersion)\n                : base(signature, copyright, fileName, headerSize, dataSize, fileSize, dataStartPosition)\n            {\n                this.EncryptedVersion = encryptedVersion;\n                this.IsEncverMissing = encverMissing;\n\n                if (encverMissing)\n                {\n                    this.WzVersion = 777;\n                    this.HashVersion = CalcHashVersion(777);\n                    this.VersionChecked = true;\n                    this.Capabilities |= Wz_Capabilities.EncverMissing;\n                }\n            }\n\n            public int EncryptedVersion { get; }\n            public bool IsEncverMissing { get; }\n        }\n\n        public class WzPkg2Header : Wz_Header\n        {\n            public WzPkg2Header(string signature, string copyright, string fileName, int headerSize, long dataSize, long fileSize, long dataStartPosition, uint hash1, uint hash2)\n                : base(signature, copyright, fileName, headerSize, dataSize, fileSize, dataStartPosition)\n            {\n                this.Hash1 = hash1;\n                this.Hash2 = hash2;\n            }\n\n            public uint Hash1 { get; }\n            public uint Hash2 { get; }\n\n            /// <summary>\n            /// The PKG2 directory string reader assigned during crypto detection, used for dir tree reading.\n            /// </summary>\n            internal IPkg2DirStringReader DirStringReader { get; set; }\n        }\n\n        public string Signature { get; private set; }\n        public string Copyright { get; private set; }\n        public string FileName { get; private set; }\n\n        public int HeaderSize { get; private set; }\n        public long DataSize { get; private set; }\n        public long FileSize { get; private set; }\n        public long DirStartPosition { get; private set; }\n\n        public bool IsPkg1 => this.Signature == PKG1;\n        public bool IsPkg2 => this.Signature == PKG2;\n\n        public bool VersionChecked { get; set; }\n        public Wz_Capabilities Capabilities { get; internal set; }\n\n        public int WzVersion { get; internal set; }\n        public uint HashVersion { get; internal set; }\n\n        public bool HasCapabilities(Wz_Capabilities cap)\n        {\n            return cap == (this.Capabilities & cap);\n        }\n\n        public static uint CalcHashVersion(int wzVersion)\n        {\n            uint sum = 0;\n#if NET6_0_OR_GREATER\n            Span<char> versionStr = stackalloc char[11];\n            wzVersion.TryFormat(versionStr, out int charsWritten, provider: System.Globalization.CultureInfo.InvariantCulture);\n            versionStr = versionStr.Slice(0, charsWritten);\n#else\n            string versionStr = wzVersion.ToString(System.Globalization.CultureInfo.InvariantCulture);\n#endif\n            for (int j = 0; j < versionStr.Length; j++)\n            {\n                sum <<= 5;\n                sum += (uint)versionStr[j] + 1;\n            }\n            \n            return sum;\n        }\n\n        // For pkg2 wz files, the version is a string that stored in MapleStory.exe, we can't find it without disassembling.\n        public static uint CalcHashVersionPkg2(string wzVersion)\n        {\n            ReadOnlySpan<byte> strBytes = MemoryMarshal.Cast<char, byte>(wzVersion.AsSpan());\n            uint hash = 0x811C9DC5;\n            foreach (var c in strBytes)\n            {\n                hash = (hash ^ c) * 0x1000193;\n            }\n            hash = 0x85EBCA6B * (hash ^ (hash >> 13));\n            return hash ^ (hash >> 16);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Image.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Runtime.InteropServices;\nusing System.Text;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Image\n    {\n        public Wz_Image(string name, int size, int cs32, uint hashOff, uint hashPos, IMapleStoryFile wz_f)\n        {\n            this.Name = name;\n            this.WzFile = wz_f;\n            this.Size = size;\n            this.Checksum = cs32;\n            this.HashedOffset = hashOff;\n            this.HashedOffsetPosition = hashPos;\n            this.Node = new Wz_ImageNode(name, this);\n\n            this.extr = false;\n            this.chec = false;\n            this.checEnc = false;\n        }\n\n        private bool extr;\n        private bool chec;\n        private bool checEnc;\n        private Stream stream;\n        private Wz_CryptoKeyType encType;\n\n        public string Name { get; set; }\n        public IMapleStoryFile WzFile { get; set; }\n        public int Size { get; set; }\n        public int Checksum { get; set; }\n        public uint HashedOffset { get; set; }\n        public uint HashedOffsetPosition { get; set; }\n        public long Offset { get; set; }\n        \n        public Wz_Node Node { get; private set; }\n\n        public Wz_Node OwnerNode { get; set; }\n\n        public bool IsChecksumChecked\n        {\n            get { return this.chec; }\n            internal set { this.chec = value; }\n        }\n\n        public bool IsLuaImage\n        {\n            get { return this.Name.EndsWith(\".lua\"); }\n        }\n\n        public IWzDecrypter EncKeys\n        {\n            get\n            {\n                var crypto = this.WzFile.WzStructure.encryption;\n                if (this.checEnc && this.encType != default)\n                {\n                    return crypto.GetKeys(this.encType);\n                }\n                return crypto.Pkg1Keys;\n            }\n        }\n\n        public bool TryExtract()\n        {\n            Exception ex;\n            return TryExtract(out ex);\n        }\n\n        public bool TryExtract(out Exception e)\n        {\n            if (!this.extr)\n            {\n                if (this.stream == null)\n                {\n                    this.stream = this.OpenRead();\n                }\n\n                bool disabledChec = this.WzFile?.WzStructure?.ImgCheckDisabled ?? false;\n                if (!disabledChec && !this.chec)\n                {\n                    if (this.Checksum != this.CalcCheckSum(this.stream))\n                    {\n                        e = new ArgumentException(\"checksum error\");\n                        return false;\n                    }\n                    this.chec = true;\n                }\n\n                if (TextImageReaderV1.PreCheck(this.stream))\n                {\n                    try\n                    {\n                        lock (this.WzFile.ReadLock)\n                        {\n                            this.stream.Position = 0;\n                            var reader = new WzStreamReader(this.stream);\n                            TextImageReaderV1.ExtractImg(reader, this.Node);\n                            this.extr = true;\n                        }\n                    }\n                    catch (Exception ex)\n                    {\n                        e = ex;\n                        this.Unextract();\n                        return false;\n                    }\n                }\n                else if (TextImageReaderV2.PreCheck(this.stream))\n                {\n                    try\n                    {\n                        lock (this.WzFile.ReadLock)\n                        {\n                            this.stream.Position = 0;\n                            var reader = new WzStreamReader(this.stream);\n                            TextImageReaderV2.ExtractImg(reader, this.Node);\n                            this.extr = true;\n                        }\n                    }\n                    catch (Exception ex)\n                    {\n                        e = ex;\n                        this.Unextract();\n                        return false;\n                    }\n                }\n                else\n                {\n                    if (!this.checEnc)\n                    {\n                        if (!this.IsLuaImage)\n                        {\n                            try\n                            {\n                                this.TryDetectEnc();\n                                if (!this.checEnc)\n                                {\n                                    e = null;\n                                    return false;\n                                }\n                            }\n                            catch (Exception ex)\n                            {\n                                e = ex;\n                                this.Unextract();\n                                return false;\n                            }\n                        }\n                    }\n\n                    try\n                    {\n                        lock (this.WzFile.ReadLock)\n                        {\n                            var reader = new WzBinaryReader(this.stream, true);\n                            reader.BaseStream.Position = 0;\n\n                            if (!this.IsLuaImage)\n                            {\n                                ExtractImg(reader, this.Node);\n                            }\n                            else\n                            {\n                                ExtractLua(reader);\n                            }\n                            this.extr = true;\n                        }\n                    }\n                    catch (Exception ex)\n                    {\n                        e = ex;\n                        this.Unextract();\n                        return false;\n                    }\n                }\n            }\n            e = null;\n            return true;\n        }\n\n        public void Unextract()\n        {\n            this.extr = false;\n            if (this.stream != null)\n            {\n                this.stream.Dispose();\n                this.stream = null;\n            }\n            this.Node.Nodes.Clear();\n        }\n\n        public virtual int CalcCheckSum(Stream stream)\n        {\n            lock (this.WzFile.ReadLock)\n            {\n                stream.Position = 0;\n                int cs = 0;\n                int size = this.Size;\n                var buffer = ArrayPool<byte>.Shared.Rent(4096);\n\n                try\n                {\n                    int count;\n                    while ((count = stream.Read(buffer, 0, Math.Min(size, buffer.Length))) > 0)\n                    {\n                        cs += MathHelper.SumBytes(buffer.AsSpan(0, count));\n                        size -= count;\n                    }\n                }\n                finally\n                {\n                    ArrayPool<byte>.Shared.Return(buffer);\n                }\n\n                if (size > 0)\n                {\n                    throw new EndOfStreamException();\n                }\n                return cs;\n            }\n        }\n\n        public virtual Stream OpenRead()\n        {\n            if (this.stream == null)\n            {\n                this.stream = new PartialStream(this.WzFile.FileStream, this.Offset, this.Size, true);\n            }\n            return this.stream;\n        }\n\n        private void ExtractImg(WzBinaryReader reader, Wz_Node parent)\n        {\n            int entries;\n            string tag = reader.ReadImageObjectTypeName(this.EncKeys);\n            switch (tag)\n            {\n                case \"Property\":\n                    reader.SkipBytes(2);\n                    entries = reader.ReadCompressedInt32();\n                    for (int i = 0; i < entries; i++)\n                    {\n                        ExtractValue(reader, parent);\n                    }\n                    break;\n\n                case \"Shape2D#Vector2D\":\n                    parent.Value = new Wz_Vector(reader.ReadCompressedInt32(), reader.ReadCompressedInt32());\n                    break;\n\n                case \"Canvas\":\n                    reader.SkipBytes(1);\n                    if (reader.ReadByte() == 0x01)\n                    {\n                        // read a mini Property\n                        reader.SkipBytes(2);\n                        entries = reader.ReadCompressedInt32();\n                        for (int i = 0; i < entries; i++)\n                        {\n                            ExtractValue(reader, parent);\n                        }\n                    }\n                    int w = reader.ReadCompressedInt32();\n                    int h = reader.ReadCompressedInt32();\n                    int form = reader.ReadCompressedInt32();\n                    int scale = reader.ReadByte();\n                    int pages = reader.ReadCompressedInt32(); // introduced in KMST 1186\n                    int unknown1 = reader.ReadCompressedInt32(); // introduced in KMST1198\n                    reader.SkipBytes(2); //TBD\n                    int dataLen = reader.ReadInt32();\n                    parent.Value = new Wz_Png(w, h, dataLen, (Wz_TextureFormat)form, scale, pages, unknown1, (uint)reader.BaseStream.Position, this);\n                    reader.SkipBytes(dataLen);\n                    break;\n\n                case \"Shape2D#Convex2D\":\n                    entries = reader.ReadCompressedInt32();\n                    Wz_Vector[] points = new Wz_Vector[entries];\n                    Wz_Node virtualNode = new Wz_Node();\n                    for (int i = 0; i < entries; i++)\n                    {\n                        ExtractImg(reader, virtualNode);\n                        if (virtualNode.Value is Wz_Vector point)\n                        {\n                            points[i] = point;\n                        }\n                        else\n                        {\n                            throw new Exception(\"Convex2D contains non vector2D items.\");\n                        }\n                    }\n                    parent.Value = new Wz_Convex(points);\n                    break;\n\n                case \"Sound_DX8\":\n                    int soundDX8Ver = reader.ReadByte();\n                    if (soundDX8Ver == 1) // introduced in KMST 1184\n                    {\n                        if (reader.ReadByte() == 0x01) // read sub property\n                        {\n                            reader.SkipBytes(2);\n                            entries = reader.ReadCompressedInt32();\n                            for (int i = 0; i < entries; i++)\n                            {\n                                ExtractValue(reader, parent);\n                            }\n                        }\n                    }\n                    dataLen = reader.ReadCompressedInt32();\n                    int duration = reader.ReadCompressedInt32();\n                    int soundDecl = reader.ReadByte();\n                    var mediaType = new Interop.AM_MEDIA_TYPE();\n                    mediaType.MajorType = new Guid(reader.ReadBytes(16));\n                    mediaType.SubType = new Guid(reader.ReadBytes(16));\n                    mediaType.FixedSizeSamples = reader.ReadByte() != 0;\n                    mediaType.TemporalCompression = reader.ReadByte() != 0;\n                    mediaType.FormatType = new Guid(reader.ReadBytes(16));\n                    switch(soundDecl)\n                    {\n                        case 2:\n                            int fmtExLen = reader.ReadCompressedInt32();\n                            var fmtExData = reader.ReadBytes(fmtExLen);\n                            mediaType.CbFormat = (uint)fmtExLen;\n                            \n                            if (!this.TryDecryptWaveFormatEx(fmtExData, out Interop.WAVEFORMATEX waveFormatEx))\n                            {\n                                throw new Exception($\"Failed to parse WAVEFORMATEX struct at offset {this.Offset}+{reader.BaseStream.Position}.\");\n                            }\n                            switch (waveFormatEx.FormatTag)\n                            {\n                                case Interop.WAVE_FORMAT_PCM:\n                                    mediaType.PbFormat = waveFormatEx;\n                                    break;\n\n                                case Interop.WAVE_FORMAT_MPEGLAYER3:\n                                    if (fmtExLen == Interop.MPEGLAYER3WAVEFORMAT_SIZE)\n                                    {\n                                        mediaType.PbFormat = MemoryMarshal.Read<Interop.MPEGLAYER3WAVEFORMAT>(fmtExData);\n                                    }\n                                    else\n                                    {\n                                        // workaround for KMST1185\n                                        mediaType.PbFormat = new Interop.MPEGLAYER3WAVEFORMAT\n                                        {\n                                            Wfx = waveFormatEx\n                                        };\n                                    }\n                                    break;\n\n                                default:\n                                    throw new Exception($\"Unknown WAVEFORMATEX.FormatTag {waveFormatEx.FormatTag} at offset {this.Offset}+{reader.BaseStream.Position}.\");\n                            }\n                            break;\n                    }\n                    parent.Value = new Wz_Sound((uint)reader.BaseStream.Position, dataLen, duration, mediaType, this);\n                    reader.SkipBytes(dataLen);\n                    break;\n\n                case \"UOL\":\n                    reader.SkipBytes(1);\n                    parent.Value = new Wz_Uol(reader.ReadImageString(this.EncKeys));\n                    break;\n\n                case \"RawData\": // introduced in GMS v243\n                    int rawDataVer = reader.ReadByte();\n                    if (rawDataVer == 1) // introduced in KMST 1177\n                    {\n                        if (reader.ReadByte() == 0x01) // read sub property\n                        {\n                            reader.SkipBytes(2);\n                            entries = reader.ReadCompressedInt32();\n                            for (int i = 0; i < entries; i++)\n                            {\n                                ExtractValue(reader, parent);\n                            }\n                        }\n                    }\n                    int rawDataLen = reader.ReadCompressedInt32();\n                    parent.Value = new Wz_RawData((uint)reader.BaseStream.Position, rawDataLen, this);\n                    reader.SkipBytes(rawDataLen);\n                    break;\n\n                case \"Canvas#Video\": // introduced in KMST v1181\n                    reader.SkipBytes(1);\n                    if (reader.ReadByte() == 0x01) // introduced in KMST 1188, read sub property\n                    {\n                        reader.SkipBytes(2);\n                        entries = reader.ReadCompressedInt32();\n                        for (int i = 0; i < entries; i++)\n                        {\n                            ExtractValue(reader, parent);\n                        }\n                    }\n                    int unknown = reader.ReadByte();\n                    int videoLen = reader.ReadCompressedInt32();\n                    parent.Value = new Wz_Video((uint)reader.BaseStream.Position, videoLen, this);\n                    reader.SkipBytes(videoLen);\n                    break;\n\n                default:\n                    throw new Exception(\"unknown wz tag: \" + tag);\n            }\n        }\n\n        private void TryDetectEnc()\n        {\n            this.encType = default;\n            this.checEnc = false;\n\n            var wzsEncType = this.WzFile.WzStructure.encryption.Pkg1EncType;\n            if (wzsEncType != default)\n            {\n                if (this.IsIllegalTag(wzsEncType))\n                {\n                    this.encType = wzsEncType;\n                    this.checEnc = true;\n                    return;\n                }\n            }\n\n            foreach (var enc in new[] {\n                Wz_CryptoKeyType.BMS,\n                Wz_CryptoKeyType.KMS,\n                Wz_CryptoKeyType.GMS,\n            })\n            {\n                if (this.IsIllegalTag(enc))\n                {\n                    this.encType = enc;\n                    this.checEnc = true;\n                    return;\n                }\n            }\n        }\n\n        private bool IsIllegalTag(Wz_CryptoKeyType keyType)\n        {\n            this.stream.Position = 0;\n            var reader = new WzBinaryReader(this.stream, false);\n            var encKey = this.WzFile.WzStructure.encryption.GetKeys(keyType);\n            switch (reader.ReadImageObjectTypeName(encKey))\n            {\n                case \"Property\":\n                case \"Shape2D#Vector2D\":\n                case \"Canvas\":\n                case \"Shape2D#Convex2D\":\n                case \"Sound_DX8\":\n                case \"UOL\":\n                case \"RawData\":\n                    return true;\n                default:\n                    return false;\n            }\n        }\n\n        private void ExtractValue(WzBinaryReader reader, Wz_Node parent)\n        {\n            parent = parent.Nodes.Add(reader.ReadImageString(this.EncKeys));\n            byte flag = reader.ReadByte();\n            switch (flag)\n            {\n                case 0x00:\n                    parent.Value = null;\n                    break;\n\n                case 0x02:\n                case 0x0B:\n                    parent.Value = reader.ReadInt16();\n                    break;\n\n                case 0x03:\n                case 0x13:\n                    // case 0x14:\n                    parent.Value = reader.ReadCompressedInt32();\n                    break;\n\n                case 0x14:\n                    parent.Value = reader.ReadCompressedInt64();\n                    break;\n\n                case 0x04:\n                    parent.Value = reader.ReadCompressedSingle();\n                    break;\n\n                case 0x05:\n                    parent.Value = reader.ReadDouble();\n                    break;\n\n                case 0x08:\n                    parent.Value = reader.ReadImageString(this.EncKeys);\n                    break;\n\n                case 0x09:\n                    int objDataLen = reader.ReadInt32();\n                    long eob = reader.BaseStream.Position + objDataLen;\n                    this.ExtractImg(reader, parent);\n                    if (reader.BaseStream.Position != eob)\n                    {\n                        throw new Exception($\"Object is not fully loaded at offset {this.Offset}+{reader.BaseStream.Position}.\");\n                    }\n                    break;\n\n                default:\n                    throw new Exception($\"Unknown value type {flag} at offset {this.Offset}+{reader.BaseStream.Position}.\");\n            }\n        }\n\n        private bool TryDecryptWaveFormatEx(Span<byte> data, out Interop.WAVEFORMATEX waveFormatEx)\n        {\n            // GMSv256: wz uses different keys on property name and waveFormatEx encryption.\n            Span<byte> dataCopy = stackalloc byte[data.Length];\n            foreach (var enc in new[] {\n                Wz_CryptoKeyType.BMS,\n                Wz_CryptoKeyType.KMS,\n                Wz_CryptoKeyType.GMS,\n            })\n            {\n                data.CopyTo(dataCopy);\n                this.WzFile.WzStructure.encryption.GetKeys(enc).Decrypt(dataCopy);\n                if (MemoryMarshal.TryRead(dataCopy, out waveFormatEx))\n                {\n                    if ((data.Length == waveFormatEx.CbSize + Interop.WAVEFORMATEX_SIZE)\n                        // workaround for KMST1185, waveFormatEx only has 18 bytes but cbsize is also 18.\n                        || (data.Length == waveFormatEx.CbSize && waveFormatEx.FormatTag == Interop.WAVE_FORMAT_MPEGLAYER3)\n                        )\n                    {\n                        // copy back to the original buffer\n                        dataCopy.CopyTo(data);\n                        return true;\n                    } \n                }\n            }\n            waveFormatEx = default;\n            return false;\n        }\n\n        private void ExtractLua(WzBinaryReader reader)\n        {\n            while (reader.BaseStream.Position < reader.BaseStream.Length)\n            {\n                var flag = reader.ReadByte();\n\n                switch (flag)\n                {\n                    case 0x01:\n                        ExtractLuaValue(reader, this.Node);\n                        break;\n\n                    default:\n                        throw new Exception($\"Unknown Lua flag {flag} at Offset {this.Offset}+{reader.BaseStream.Position}.\");\n                }\n            }\n        }\n\n        private void ExtractLuaValue(WzBinaryReader reader, Wz_Node parent)\n        {\n            int len = reader.ReadCompressedInt32();\n            byte[] data = reader.ReadBytes(len);\n            if (!this.checEnc)\n            {\n                TryDetectLuaEnc(data);\n            }\n            this.EncKeys.Decrypt(data.AsSpan());\n            string luaCode = Encoding.UTF8.GetString(data);\n            parent.Value = luaCode;\n        }\n\n        private void TryDetectLuaEnc(byte[] luaBinary)\n        {\n            byte[] tempBuffer = new byte[Math.Min(luaBinary.Length, 64)];\n            char[] tempStr = new char[tempBuffer.Length];\n\n            //测试各种加密方式 判断符合度最高的\n            int maxCharCount = 0;\n            var maxCharEnc = Wz_CryptoKeyType.Unknown;\n\n            foreach (var enc in new[] {\n                Wz_CryptoKeyType.BMS,\n                Wz_CryptoKeyType.KMS,\n                Wz_CryptoKeyType.GMS,\n            })\n            {\n                Buffer.BlockCopy(luaBinary, 0, tempBuffer, 0, tempBuffer.Length);\n\n                this.WzFile.WzStructure.encryption.GetKeys(enc).Decrypt(tempBuffer.AsSpan());\n                int count = Encoding.UTF8.GetChars(tempBuffer, 0, tempBuffer.Length, tempStr, 0);\n                int asciiCount = tempStr.Take(count).Count(chr => 32 <= chr && chr <= 127);\n\n                if (maxCharCount < asciiCount)\n                {\n                    maxCharEnc = enc;\n                    maxCharCount = asciiCount;\n                }\n            }\n            this.encType = maxCharEnc;\n            this.checEnc = true;\n        }\n\n        internal class TextImageReaderV1\n        {\n            public static bool PreCheck(Stream stream)\n            {\n                ReadOnlySpan<byte> signatureBytes = \"#Property\"u8;\n                if (stream.Length < signatureBytes.Length)\n                {\n                    return false;\n                }\n\n                stream.Position = 0;\n                Span<byte> buffer = stackalloc byte[signatureBytes.Length];\n                stream.ReadExactly(buffer);\n\n                return buffer.SequenceEqual(signatureBytes);\n            }\n\n            public static void ExtractImg(WzStreamReader reader, Wz_Node parent)\n            {\n                reader.SkipLine();\n                ReadProperty(reader, parent, true);\n            }\n\n            private static void ReadProperty(WzStreamReader reader, Wz_Node parent, bool isTopLevel = false)\n            {\n                while (!reader.EndOfStream)\n                {\n                    reader.SkipWhitespaceExceptLineEnding();\n                    string key = reader.ReadUntilWhitespace();\n\n                    if (string.IsNullOrEmpty(key)) // skip empty line\n                    {\n                        reader.SkipLine();\n                        continue;\n                    }\n                    else if (key == \"}\" && !isTopLevel) // end property\n                    {\n                        if (!reader.SkipLineAndCheckEmpty())\n                        {\n                            throw new Exception(\"Incorrect property end line.\");\n                        }\n                        return;\n                    }\n\n                    reader.SkipWhitespaceExceptLineEnding();\n                    int equalSign = reader.Read();\n                    if (equalSign != '=')\n                        throw new Exception($\"Expect '=' sign but got '{(char)equalSign}'.\");\n                    reader.SkipWhitespaceExceptLineEnding();\n\n                    string stringVal = reader.ReadLine();\n\n                    if (string.IsNullOrEmpty(stringVal))\n                    {\n                        parent.Nodes.Add(key);\n                    }\n                    else if (stringVal == \"{\") // start property\n                    {\n                        Wz_Node child = parent.Nodes.Add(key);\n                        ReadProperty(reader, child, false);\n                    }\n                    else if (int.TryParse(stringVal, out var intVal))\n                    {\n                        parent.Nodes.Add(key).Value = intVal;\n                    }\n                    else if (long.TryParse(stringVal, out var longVal))\n                    {\n                        parent.Nodes.Add(key).Value = longVal;\n                    }\n                    else if (double.TryParse(stringVal, out var doubleVal))\n                    {\n                        parent.Nodes.Add(key).Value = doubleVal;\n                    }\n                    else\n                    {\n                        parent.Nodes.Add(key).Value = stringVal;\n                    }\n                }\n            }\n        }\n\n        internal class TextImageReaderV2\n        {\n            public static bool PreCheck(Stream stream)\n            {\n                ReadOnlySpan<byte> signatureBytes = \"Root\"u8;\n                if (stream.Length < signatureBytes.Length)\n                {\n                    return false;\n                }\n\n                stream.Position = 0;\n                Span<byte> buffer = stackalloc byte[signatureBytes.Length];\n                stream.ReadExactly(buffer);\n\n                return buffer.SequenceEqual(signatureBytes);\n            }\n\n            public static void ExtractImg(WzStreamReader reader, Wz_Node parent)\n            {\n                ReadNode(reader, out int indent, out string name, out NodeType type, out object value);\n                if (indent != 0 || name != \"Root\")\n                {\n                    throw new Exception($\"Unknown top level node '{name}'.\");\n                }\n                if (type != NodeType.Property)\n                {\n                    throw new Exception($\"Unexpected top level node type '{type}'.\");\n                }\n\n                Stack<Wz_Node> nodePath = new();\n                nodePath.Push(parent);\n                while (!reader.EndOfStream)\n                {\n                    ReadNode(reader, out indent, out name, out type, out value);\n                    if (indent == 0)\n                    {\n                        throw new Exception($\"More than one top level node '{name}' found.\");\n                    }\n                    while (indent < nodePath.Count)\n                    {\n                        nodePath.Pop();\n                    }\n                    if (indent != nodePath.Count)\n                    {\n                        throw new Exception($\"Unexpected indent {indent} when inserting node '{name}'.\");\n                    }\n                    Wz_Node child = nodePath.Peek().Nodes.Add(name);\n                    if (value != null)\n                    {\n                        child.Value = value;\n                    }\n                    if (type == NodeType.Property)\n                    {\n                        nodePath.Push(child);\n                    }\n                }\n            }\n\n            private static void ReadNode(WzStreamReader reader, out int indent, out string name, out NodeType type, out object value)\n            {\n                indent = reader.ReadRepeatChars('\\t');\n                name = reader.ReadUntilWhitespace();\n                if (reader.ReadRepeatChars(' ') == 0)\n                {\n                    int nextChar = reader.Peek();\n                    throw new FormatException($\"Expect space char after node name but get {(char)nextChar}({nextChar}).\");\n                }\n                string typeName = reader.ReadUntilWhitespace();\n                if (!TryParseNodeType(typeName, out type))\n                {\n                    throw new FormatException($\"Unknown type name ${typeName}.\");\n                }\n                if (reader.Peek() == '\\t')\n                {\n                    reader.Read();\n                    string valueStr;\n                    switch (type)\n                    {\n                        default:\n                        case NodeType.Empty:\n                            reader.ReadLine();\n                            value = null;\n                            break;\n\n                        case NodeType.I4:\n                            valueStr = reader.ReadLine();\n                            if (!int.TryParse(valueStr, out var intValue))\n                                throw new FormatException($\"Failed to parse I4 value {valueStr}.\");\n                            value = intValue;\n                            break;\n\n                        case NodeType.I8:\n                            valueStr = reader.ReadLine();\n                            if (!long.TryParse(valueStr, out var longValue))\n                                throw new FormatException($\"Failed to parse I8 value {valueStr}.\");\n                            value = longValue;\n                            break;\n\n                        case NodeType.R8:\n                            valueStr = reader.ReadLine();\n                            if (!double.TryParse(valueStr, out var doubleValue))\n                                throw new FormatException($\"Failed to parse R8 value {valueStr}.\");\n                            value = doubleValue;\n                            break;\n\n                        case NodeType.String:\n                            const string MultiLineStart = \"<Multi-Line/>\";\n                            const string MultiLineEnd = \"</Multi-Line>\";\n                            valueStr = reader.ReadLine();\n                            if (valueStr == MultiLineStart)\n                            {\n                                StringBuilder sb = new StringBuilder();\n                                while (!reader.EndOfStream)\n                                {\n                                    int strIndent = reader.ReadRepeatChars('\\t', indent + 1);\n                                    if (strIndent < indent + 1)\n                                    {\n                                        throw new FormatException($\"Unexpected indent size {strIndent} for multiline string (should be {indent + 1}).\");\n                                    }\n                                    valueStr = reader.ReadLine();\n                                    if (valueStr.EndsWith(MultiLineEnd))\n                                    {\n                                        sb.Append(valueStr, 0, valueStr.Length - MultiLineEnd.Length);\n                                        break;\n                                    }\n                                    else\n                                    {\n                                        sb.Append(valueStr).Append(\"\\r\\n\");\n                                    }\n                                }\n                                value = sb.ToString();\n                            }\n                            else\n                            {\n                                value = valueStr;\n                            }\n                            break;\n\n                        case NodeType.Vector:\n                            valueStr = reader.ReadLine();\n                            int commaIdx = valueStr.IndexOf(',');\n                            if (commaIdx == -1\n                                || !int.TryParse(valueStr.Substring(0, commaIdx), out int x)\n                                || !int.TryParse(valueStr.Substring(commaIdx + 1), out int y))\n                            {\n                                throw new FormatException($\"Failed to parse Vector value {valueStr}.\");\n                            }\n                            value = new Wz_Vector(x, y);\n                            break;\n\n                        case NodeType.Property:\n                            if (reader.Peek() == '[')\n                            {\n                                valueStr = reader.ReadLine();\n                                if (valueStr == \"[no_binary]\")\n                                {\n                                    // ignore flags\n                                }\n                            }\n                            value = null;\n                            break;\n                    }\n                }\n                else\n                {\n                    value = null;\n                    reader.SkipLine();\n                }\n            }\n\n            public enum NodeType\n            {\n                Unknown = 0,\n                Empty,\n                I4,\n                I8,\n                R8,\n                String,\n                Vector,\n                Property,\n            }\n\n            public static bool TryParseNodeType(string s, out NodeType type)\n            {\n                switch (s)\n                {\n                    case \"<Empty>\": type = NodeType.Empty; return true;\n                    case \"<I4>\": type = NodeType.I4; return true;\n                    case \"<I8>\": type = NodeType.I8; return true;\n                    case \"<R8>\": type = NodeType.R8; return true;\n                    case \"<String>\": type = NodeType.String; return true;\n                    case \"<Vector>\": type = NodeType.Vector; return true;\n                    case \"<Property>\": type = NodeType.Property; return true;\n                    default: type = NodeType.Unknown; return false;\n                }\n            }\n        }\n\n        internal class Wz_ImageNode : Wz_Node\n        {\n            public Wz_ImageNode(string nodeText, Wz_Image image) : base(nodeText)\n            {\n                this.Image = image;\n            }\n\n            public Wz_Image Image { get; private set; }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Node.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.IO;\nusing System.Xml;\nusing System.Reflection;\nusing System.Text.RegularExpressions;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Node : ICloneable, IComparable, IComparable<Wz_Node>\n    {\n        public Wz_Node()\n        {\n            this.nodes = new WzNodeCollection(this);\n        }\n\n        public Wz_Node(string nodeText)\n            : this()\n        {\n            this.text = nodeText;\n        }\n\n        //fields\n        private object value;\n        private string text;\n        private WzNodeCollection nodes;\n        private Wz_Node parentNode;\n\n        //properties\n        public object Value\n        {\n            get { return value; }\n            set { this.value = value; }\n        }\n\n        public string Text\n        {\n            get { return this.text; }\n            set { this.text = value; }\n        }\n\n        public string FullPath\n        {\n            get\n            {\n                Stack<string> path = new Stack<string>();\n                Wz_Node node = this;\n                do\n                {\n                    path.Push(node.text);\n                    node = node.parentNode;\n                } while (node != null);\n                return string.Join(\"\\\\\", path.ToArray());\n            }\n        }\n\n        public string FullPathToFile\n        {\n            get\n            {\n                Stack<string> path = new Stack<string>();\n                Wz_Node node = this;\n                do\n                {\n                    if (node.value is Wz_File wzf && !wzf.IsSubDir)\n                    {\n                        if (node.text.EndsWith(\".wz\", StringComparison.OrdinalIgnoreCase))\n                        {\n                            path.Push(node.text.Substring(0, node.text.Length - 3));\n                        }\n                        else\n                        {\n                            path.Push(node.text);\n                        }\n                        break;\n                    }\n\n                    path.Push(node.text);\n\n                    var img = node.GetValue<Wz_Image>();\n                    if (img != null)\n                    {\n                        node = img.OwnerNode;\n                    }\n\n                    if (node != null)\n                    {\n                        node = node.parentNode;\n                    }\n                } while (node != null);\n                return string.Join(\"\\\\\", path.ToArray());\n            }\n        }\n\n        public WzNodeCollection Nodes\n        {\n            get { return this.nodes; }\n        }\n\n        public Wz_Node ParentNode\n        {\n            get { return parentNode; }\n            private set { parentNode = value; }\n        }\n\n        //methods\n        public override string ToString()\n        {\n            return this.Text + \" \" + (this.value != null ? this.value.ToString() : \"-\") + \" \" + this.nodes.Count;\n        }\n\n        public Wz_Node FindNodeByPath(string fullPath)\n        {\n            return FindNodeByPath(fullPath, false);\n        }\n\n        public Wz_Node FindNodeByPath(string fullPath, bool extractImage)\n        {\n            string[] patten = fullPath.Split('\\\\');\n            return FindNodeByPath(extractImage, patten);\n        }\n\n        public Wz_Node FindNodeByPath(bool extractImage, params string[] fullPath)\n        {\n            return FindNodeByPath(extractImage, false, fullPath);\n        }\n\n        public Wz_Node FindNodeByPath(bool extractImage, bool ignoreCase, params string[] fullPath)\n        {\n            Wz_Node node = this;\n\n            Wz_Image img;\n\n            //首次解压\n            if (extractImage && (img = this.GetValue<Wz_Image>()) != null)\n            {\n                if (img.TryExtract())\n                {\n                    node = img.Node;\n                }\n            }\n\n            foreach (string txt in fullPath)\n            {\n                if (ignoreCase)\n                {\n                    bool find = false;\n\n                    foreach (Wz_Node subNode in node.nodes)\n                    {\n                        if (string.Equals(subNode.text, txt, StringComparison.OrdinalIgnoreCase))\n                        {\n                            find = true;\n                            node = subNode;\n                        }\n                    }\n                    if (!find)\n                        node = null;\n                }\n                else\n                {\n                    node = node.nodes[txt];\n                }\n\n                if (node == null)\n                    return null;\n\n                if (extractImage)\n                {\n                    img = node.GetValue<Wz_Image>();\n                    if (img != null && img.TryExtract()) //判断是否是img\n                    {\n                        node = img.Node;\n                    }\n                }\n            }\n            return node;\n        }\n\n        public T GetValue<T>(T defaultValue)\n        {\n            var typeT = typeof(T);\n            if (typeof(Wz_Image) == typeT)\n            {\n                if (this is Wz_Image.Wz_ImageNode)\n                {\n                    return (T)(object)(((Wz_Image.Wz_ImageNode)this).Image);\n                }\n                else\n                {\n                    return (this.value is T) ? (T)this.value : default(T);\n                }\n            }\n            if (this.value == null)\n                return defaultValue;\n            if (this.value is T)\n                return (T)this.value;\n\n\n            if (this.value is string s)\n            {\n                if (ObjectConverter.TryParse<T>(s, out T result, out bool hasTryParse))\n                {\n                    return result;\n                }\n                if (hasTryParse)\n                {\n                    return defaultValue;\n                }\n            }\n\n            if (this.value is IConvertible iconvertible)\n            {\n                if (typeT.IsGenericType && typeT.GetGenericTypeDefinition() == typeof(Nullable<>))\n                {\n                    typeT = typeT.GetGenericArguments()[0];\n                }\n\n                try\n                {\n                    T result = (T)iconvertible.ToType(typeT, null);\n                    return result;\n                }\n                catch\n                {\n                }\n            }\n            return defaultValue;\n        }\n\n        public T GetValue<T>()\n        {\n            return GetValue<T>(default(T));\n        }\n\n        //innerClass\n        public class WzNodeCollection : IEnumerable<Wz_Node>\n        {\n            public WzNodeCollection(Wz_Node owner)\n\n            {\n                this.owner = owner;\n                this.innerCollection = null;\n            }\n\n            private readonly Wz_Node owner;\n            private InnerCollection innerCollection;\n\n            public Wz_Node this[int index]\n            {\n                get { return this.innerCollection?[index]; }\n            }\n\n            public Wz_Node this[string key]\n            {\n                get { return this.innerCollection?[key]; }\n            }\n\n            public int Count\n            {\n                get { return this.innerCollection?.Count ?? 0; }\n            }\n\n            public Wz_Node Add(string nodeText)\n            {\n                this.EnsureInnerCollection();\n                return this.innerCollection.Add(nodeText);\n            }\n\n            public void Add(Wz_Node item)\n            {\n                this.EnsureInnerCollection();\n                this.innerCollection.Add(item);\n            }\n\n            public void Sort()\n            {\n                this.innerCollection?.Sort();\n            }\n\n            public void Sort<T>(Func<Wz_Node, T> getKeyFunc) where T : IComparable<T>\n            {\n                if (getKeyFunc == null)\n                {\n                    this.Sort();\n                }\n                else if (this.innerCollection != null)\n                {\n                    this.innerCollection.Sort(getKeyFunc);\n                }\n            }\n\n            public void Trim()\n            {\n                this.innerCollection?.Trim();\n            }\n\n            public void Clear()\n            {\n                this.innerCollection?.Clear();\n            }\n\n            public IEnumerator<Wz_Node> GetEnumerator()\n            {\n                return this.innerCollection?.GetEnumerator() ?? System.Linq.Enumerable.Empty<Wz_Node>().GetEnumerator();\n            }\n\n            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\n            {\n                return this.GetEnumerator();\n            }\n\n            private void EnsureInnerCollection()\n            {\n                if (this.innerCollection == null)\n                {\n                    this.innerCollection = new InnerCollection(this.owner);\n                }\n            }\n\n            private class InnerCollection : KeyedCollection<string, Wz_Node>\n            {\n                public InnerCollection(Wz_Node owner)\n                    : base(null, 12)\n                {\n                    this.parentNode = owner;\n                }\n\n                public Wz_Node Add(string nodeText)\n                {\n                    Wz_Node newNode = new Wz_Node(nodeText);\n                    this.Add(newNode);\n                    return newNode;\n                }\n\n                public new void Add(Wz_Node item)\n                {\n                    base.Add(item);\n                    if (item.parentNode != null)\n                    {\n                        int index = item.parentNode.nodes.innerCollection.Items.IndexOf(item);\n                        if (index > -1)\n                        {\n                            item.parentNode.nodes.innerCollection.RemoveItem(index);\n                        }\n                    }\n                    item.parentNode = this.parentNode;\n                }\n\n                protected override void RemoveItem(int index)\n                {\n                    var item = this[index];\n                    if (item != null)\n                    {\n                        item.parentNode = null;\n                    }\n                    base.RemoveItem(index);\n                }\n\n                private readonly Wz_Node parentNode;\n\n                public void Sort()\n                {\n                    (base.Items as List<Wz_Node>)?.Sort();\n                }\n\n                public void Sort<T>(Func<Wz_Node, T> getKeyFunc) where T : IComparable<T>\n                {\n                    ListSorter.Sort(base.Items as List<Wz_Node>, getKeyFunc);\n                }\n\n                public void Trim()\n                {\n                    (base.Items as List<Wz_Node>)?.TrimExcess();\n                }\n\n                public new Wz_Node this[string key]\n                {\n                    get\n                    {\n                        if (key == null)\n                        {\n                            return null;\n                        }\n                        if (this.Dictionary != null)\n                        {\n                            Wz_Node node;\n                            this.Dictionary.TryGetValue(key, out node);\n                            return node;\n                        }\n                        else\n                        {\n                            List<Wz_Node> list = this.Items as List<Wz_Node>;\n                            foreach (var node in list)\n                            {\n                                if (this.Comparer.Equals(this.GetKeyForItem(node), key))\n                                {\n                                    return node;\n                                }\n                            }\n                            return null;\n                        }\n                    }\n                }\n\n                protected override string GetKeyForItem(Wz_Node item)\n                {\n                    return item.text;\n                }\n            }\n\n            internal static class ListSorter\n            {\n                public static void Sort<T, TKey>(List<T> list, Func<T, TKey> getKeyFunc)\n                {\n                    T[] innerArray = list.GetType()\n                        .GetField(\"_items\", BindingFlags.Instance | BindingFlags.NonPublic)\n                        .GetValue(list) as T[];\n\n                    TKey[] keys = new TKey[list.Count];\n\n                    for (int i = 0; i < keys.Length; i++)\n                    {\n                        keys[i] = getKeyFunc(innerArray[i]);\n                    }\n\n                    Array.Sort(keys, innerArray, 0, keys.Length);\n                }\n            }\n        }\n\n        public object Clone()\n        {\n            Wz_Node newNode = new Wz_Node(this.text);\n            newNode.value = this.value;\n            foreach (Wz_Node node in this.nodes)\n            {\n                Wz_Node newChild = node.Clone() as Wz_Node;\n                newNode.nodes.Add(newChild);\n            }\n            return newNode;\n        }\n\n        int IComparable.CompareTo(object obj)\n        {\n            return ((IComparable<Wz_Node>)this).CompareTo(obj as Wz_Node);\n        }\n\n        int IComparable<Wz_Node>.CompareTo(Wz_Node other)\n        {\n            if (other != null)\n            {\n                return string.Compare(this.Text, other.Text, StringComparison.Ordinal);\n            }\n            else\n            {\n                return 1;\n            }\n        }\n    }\n\n    public static class Wz_NodeExtension\n    {\n        public static T GetValueEx<T>(this Wz_Node node, T defaultValue)\n        {\n            if (node == null)\n                return defaultValue;\n            return node.GetValue<T>(defaultValue);\n        }\n\n        public static T? GetValueEx<T>(this Wz_Node node) where T : struct\n        {\n            if (node == null)\n                return null;\n            return node.GetValue<T>();\n        }\n\n        public static Wz_Node ResolveUol(this Wz_Node node)\n        {\n            if (node == null)\n                return null;\n            while (node?.Value is Wz_Uol uol)\n            {\n                node = uol.HandleUol(node);\n            }\n            return node;\n        }\n\n        /// <summary>\n        /// 搜索node所属的wz_file，若搜索不到则返回null。\n        /// </summary>\n        /// <param Name=\"node\">要搜索的wznode。</param>\n        /// <returns></returns>\n        public static Wz_File GetNodeWzFile(this Wz_Node node)\n        {\n            Wz_File wzfile = null;\n            while (node != null)\n            {\n                if ((wzfile = node.Value as Wz_File) != null)\n                {\n                    while (wzfile.OwnerWzFile != null)\n                    {\n                        wzfile = wzfile.OwnerWzFile;\n                        node = wzfile.Node;\n                    }\n                    if (!wzfile.IsSubDir)\n                    {\n                        return wzfile;\n                    }\n                }\n                else if (node.Value is Wz_Image wzImg || (wzImg = (node as Wz_Image.Wz_ImageNode)?.Image) != null)\n                {\n                    switch (wzImg.WzFile)\n                    {\n                        case Wz_File wzfile2:\n                            node = wzfile2.Node;\n                            continue;\n\n                        default:\n                            if (node.ParentNode == null)\n                            {\n                                node = wzImg.OwnerNode;\n                                continue;\n                            }\n                            break;\n                    }\n                }\n                node = node.ParentNode;\n            }\n            return wzfile;\n        }\n\n        public static int GetMergedVersion(this Wz_File wzFile)\n        {\n            if (wzFile.Header.WzVersion != 0)\n            {\n                return wzFile.Header.WzVersion;\n            }\n            foreach (var subFile in wzFile.MergedWzFiles)\n            {\n                if (subFile.Header.WzVersion != 0)\n                {\n                    return subFile.Header.WzVersion;\n                }\n            }\n            return 0;\n        }\n\n        public static Wz_Image GetNodeWzImage(this Wz_Node node)\n        {\n            Wz_Image wzImg = null;\n            while (node != null)\n            {\n                if ((wzImg = node.Value as Wz_Image) != null\n                    || (wzImg = (node as Wz_Image.Wz_ImageNode)?.Image) != null)\n                {\n                    break;\n                }\n                node = node.ParentNode;\n            }\n            return wzImg;\n        }\n\n        public static void DumpAsXml(this Wz_Node node, XmlWriter writer) \n        {\n            DumpAsXml(node, writer, null);\n        }\n\n        public static void DumpAsXml(this Wz_Node node, XmlWriter writer, IList<string> filterTags)\n        {\n            object value = node.Value;\n\n            // 过滤：根据 tag 名称（类名小写）匹配\n            if (filterTags != null && value != null) {\n                var currentTag = value.GetType().Name.ToLower();  // 例如 \"wz_int\"\n                foreach (var tag in filterTags) {\n                    if (tag != null && currentTag == tag.Trim().ToLower()) {\n                        return; // 匹配到，跳过输出\n                    }\n                }\n            }\n            \n            if (value == null || value is Wz_Image)\n            {\n                writer.WriteStartElement(\"dir\");\n                writer.WriteAttributeString(\"name\", node.Text);\n            }\n            else if (value is Wz_Png png)\n            {\n                writer.WriteStartElement(\"png\");\n                writer.WriteAttributeString(\"name\", node.Text);\n                writer.WriteAttributeString(\"width\", png.Width.ToString());\n                writer.WriteAttributeString(\"height\", png.Height.ToString());\n                writer.WriteAttributeString(\"format\", ((int)png.Format).ToString());\n                writer.WriteAttributeString(\"scale\", png.Scale.ToString());\n                writer.WriteAttributeString(\"pages\", png.Pages.ToString());\n                for(int i = 0; i < png.ActualPages; i++)\n                {\n                    using (var bmp = png.ExtractPng())\n                    {\n                        using (var ms = new MemoryStream())\n                        {\n                            bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);\n                            byte[] data = ms.ToArray();\n                            string attrName = \"value\" + (i > 0 ? (i + 1).ToString() : null);\n                            writer.WriteAttributeString(attrName, Convert.ToBase64String(data));\n                        }\n                    }\n                }\n            }\n            else if (value is Wz_Uol uol)\n            {\n                writer.WriteStartElement(\"uol\");\n                writer.WriteAttributeString(\"name\", node.Text);\n                writer.WriteAttributeString(\"value\", uol.Uol);\n            }\n            else if (value is Wz_Vector vector)\n            {\n                writer.WriteStartElement(\"vector\");\n                writer.WriteAttributeString(\"name\", node.Text);\n                writer.WriteAttributeString(\"value\", $\"{vector.X}, {vector.Y}\");\n            }\n            else if (value is Wz_Sound sound)\n            {\n                writer.WriteStartElement(\"sound\");\n                writer.WriteAttributeString(\"name\", node.Text);\n                byte[] data = sound.ExtractSound();\n                if (data == null)\n                {\n                    data = new byte[sound.DataLength];\n                    sound.CopyTo(data, 0);\n                }\n                writer.WriteAttributeString(\"value\", Convert.ToBase64String(data));\n            }\n            else if (value is Wz_Convex contex)\n            {\n                writer.WriteStartElement(\"convex\");\n                writer.WriteAttributeString(\"name\", node.Text);\n                foreach (var point in contex.Points)\n                {\n                    writer.WriteStartElement(\"vector\");\n                    writer.WriteAttributeString(\"value\", $\"{point.X}, {point.Y}\");\n                    writer.WriteEndElement();\n                }\n            }\n            else if (value is Wz_RawData rawdata)\n            {\n                writer.WriteStartElement(\"rawdata\");\n                writer.WriteAttributeString(\"name\", node.Text);\n                writer.WriteAttributeString(\"length\", rawdata.Length.ToString());\n                byte[] data = new byte[rawdata.Length];\n                rawdata.CopyTo(data, 0);\n                writer.WriteAttributeString(\"value\", Convert.ToBase64String(data));\n            }\n            else if (value is Wz_Video video)\n            {\n                writer.WriteStartElement(\"video\");\n                writer.WriteAttributeString(\"name\", node.Text);\n                writer.WriteAttributeString(\"length\", video.Length.ToString());\n                byte[] data = new byte[video.Length];\n                video.CopyTo(data, 0);\n                writer.WriteAttributeString(\"value\", Convert.ToBase64String(data));\n            }\n            else\n            {\n                var tag = value.GetType().Name.ToLower();\n                writer.WriteStartElement(tag);\n                writer.WriteAttributeString(\"name\", node.Text);\n                writer.WriteAttributeString(\"value\", value.ToString());\n            }\n\n            //输出子节点\n            foreach (var child in node.Nodes)\n            {\n                DumpAsXml(child, writer, filterTags);\n            }\n\n            //结束标识\n            writer.WriteEndElement();\n        }\n\n        public static void SortByImgID(this Wz_Node.WzNodeCollection nodes)\n        {\n            if (regexImgID == null)\n            {\n                regexImgID = new Regex(@\"^(\\d+)\\.img$\", RegexOptions.Compiled);\n            }\n\n            nodes.Sort(GetKey);\n        }\n\n        private static Regex regexImgID;\n\n        private static SortKey GetKey(Wz_Node node)\n        {\n            var key = new SortKey();\n            var m = regexImgID.Match(node.Text);\n            if (m.Success)\n            {\n                key.HasID = Int32.TryParse(m.Result(\"$1\"), out key.ImgID);\n            }\n            key.Text = node.Text;\n            return key;\n        }\n\n        private struct SortKey : IComparable<SortKey>\n        {\n            public bool HasID;\n            public int ImgID;\n            public string Text;\n\n            public int CompareTo(SortKey other)\n            {\n                if (this.HasID && other.HasID) return this.ImgID.CompareTo(other.ImgID);\n                return StringComparer.Ordinal.Compare(this.Text, other.Text);\n            }\n        }\n    }\n\n    public static class ObjectConverter\n    {\n        private static readonly Dictionary<Type, Delegate> cache = new Dictionary<Type, Delegate>();\n        private delegate bool TryParseFunc<T>(string s, out T value);\n\n        public static bool TryParse<T>(string s, out T value, out bool hasTryParse)\n        {\n            var typeT = typeof(T);\n\n            TryParseFunc<T> tryParseFunc = null;\n            if (!cache.TryGetValue(typeT, out var dele))\n            {\n                bool isNullable = false;\n                Type innerType;\n                if (typeT.IsGenericType && typeT.GetGenericTypeDefinition() == typeof(Nullable<>))\n                {\n                    isNullable = true;\n                    innerType = typeT.GetGenericArguments()[0];\n                }\n                else\n                {\n                    innerType = typeT;\n                }\n\n                var methodInfo = innerType.GetMethod(\"TryParse\",\n                    BindingFlags.Static | BindingFlags.Public,\n                    null,\n                    new[] { typeof(string), innerType.MakeByRefType() },\n                    null);\n\n                if (methodInfo != null && methodInfo.ReturnType == typeof(bool))\n                {\n                    if (isNullable)\n                    {\n                        dele = Delegate.CreateDelegate(typeof(TryParseFunc<>).MakeGenericType(innerType), methodInfo);\n                        var proxyType = typeof(NullableTryParse<>).MakeGenericType(innerType);\n                        var proxyInstance = Activator.CreateInstance(proxyType, dele);\n                        var proxyParseFunc = proxyType.GetMethod(\"TryParse\", BindingFlags.Public | BindingFlags.Instance);\n                        cache[typeT] = tryParseFunc = (TryParseFunc<T>)Delegate.CreateDelegate(typeof(TryParseFunc<T>), proxyInstance, proxyParseFunc);\n                    }\n                    else\n                    {\n                        cache[typeT] = tryParseFunc = (TryParseFunc<T>)Delegate.CreateDelegate(typeof(TryParseFunc<T>), methodInfo);\n                    }\n                }\n                else\n                {\n                    cache[typeT] = null;\n                }\n            }\n            else\n            {\n                tryParseFunc = dele as TryParseFunc<T>;\n            }\n\n            if (tryParseFunc != null)\n            {\n                hasTryParse = true;\n                return tryParseFunc(s, out value);\n            }\n            else\n            {\n                hasTryParse = false;\n                value = default(T);\n                return false;\n            }\n        }\n\n        private class NullableTryParse<T> where T : struct\n        {\n            public NullableTryParse(TryParseFunc<T> func)\n            {\n                this.func = func;\n            }\n\n            private readonly TryParseFunc<T> func;\n\n            public bool TryParse(string s, out T? value)\n            {\n                if (this.func(s, out var v))\n                {\n                    value = v;\n                    return true;\n                }\n                else\n                {\n                    value = default(T?);\n                    return false;\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Png.cs",
    "content": "﻿using System;\nusing System.Buffers;\nusing System.Collections.Generic;\nusing System.Drawing;\nusing System.IO;\nusing System.IO.Compression;\nusing System.Drawing.Imaging;\nusing System.Runtime.InteropServices;\nusing WzComparerR2.WzLib.Utilities;\n\n#if NET6_0_OR_GREATER\nusing System.Runtime.Intrinsics;\nusing System.Runtime.Intrinsics.X86;\n#endif\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Png\n    {\n        public Wz_Png(int w, int h, int data_length, Wz_TextureFormat format, int scale, int pages, int unknown1, uint offs, Wz_Image wz_i)\n        {\n            this.Width = w;\n            this.Height = h;\n            this.DataLength = data_length;\n            this.Format = format;\n            this.Scale = scale;\n            this.Pages = pages;\n            this.Unknown1 = unknown1;\n            this.Offset = offs;\n            this.WzImage = wz_i;\n        }\n\n        /// <summary>\n        /// 获取或设置图片的宽度。\n        /// </summary>\n        public int Width { get; set; }\n\n        /// <summary>\n        /// 获取或设置图片的高度。\n        /// </summary>\n        public int Height { get; set; }\n\n        /// <summary>\n        /// 获取或设置数据块的长度。\n        /// </summary>\n        public int DataLength { get; set; }\n\n        /// <summary>\n        /// 获取或设置数据块对于文件的偏移。\n        /// </summary>\n        public uint Offset { get; set; }\n\n        /// <summary>\n        /// 获取或设置图片的数据压缩方式。\n        /// </summary>\n        [Obsolete]\n        public int Form => (int)this.Format + this.Scale;\n\n        public Wz_TextureFormat Format { get; set; }\n\n        public int Scale { get; set; }\n\n        /// <summary>\n        /// The actual width and height sacale is pow(2, value).\n        /// </summary>\n        public int ActualScale => this.Scale > 0 ? (1 << this.Scale) : 1;\n\n        public int Pages { get; set; }\n\n        public int ActualPages => this.Pages > 0 ? this.Pages : 1;\n\n        public int Unknown1 { get; set; }\n\n        /// <summary>\n        /// 获取或设置图片所属的WzFile\n        /// </summary>\n        public IMapleStoryFile WzFile\n        {\n            get { return this.WzImage?.WzFile; }\n        }\n\n        /// <summary>\n        /// 获取或设置图片所属的WzImage\n        /// </summary>\n        public Wz_Image WzImage { get; set; }\n\n        public int GetRawDataSize() => this.GetRawDataSizePerPage() * this.ActualPages;\n\n        public int GetRawDataSizePerPage() => GetUncompressedDataSize(this.Format, this.ActualScale, this.Width, this.Height);\n\n        public byte[] GetRawData()\n        {\n            int dataSize = this.GetRawDataSize();\n            byte[] rawData = new byte[dataSize];\n            int count = this.GetRawData(rawData);\n            if (count != dataSize)\n            {\n                throw new Exception($\"Data size mismatch. (expected: {dataSize}, actual: {count})\");\n            }\n            return rawData;\n        }\n\n        public int GetRawData(Span<byte> buffer)\n        {\n            return this.GetRawData(0, buffer);\n        }\n\n        public int GetRawData(int skipBytes, Span<byte> buffer)\n        {\n            lock (this.WzFile.ReadLock)\n            {\n                using (var zlib = this.UnsafeOpenRead())\n                {\n                    if (skipBytes > 0)\n                    {\n                        var pool = ArrayPool<byte>.Shared;\n                        byte[] tempBuffer = pool.Rent(4096);\n                        try\n                        {\n                            while (skipBytes > 0)\n                            {\n                                int len = zlib.Read(tempBuffer, 0, (int)Math.Min(skipBytes, tempBuffer.Length));\n                                if (len == 0)\n                                {\n                                    break;\n                                }\n                                skipBytes -= len;\n                            }\n                        }\n                        finally\n                        {\n                            pool.Return(tempBuffer);\n                        }\n                    }\n\n                    return zlib.ReadAvailableBytes(buffer);\n                }\n            }\n        }\n\n        public Stream UnsafeOpenRead()\n        {\n            DeflateStream zlib;\n\n            var stream = this.WzImage.OpenRead();\n            long endPosition = this.Offset + this.DataLength;\n            stream.Position = this.Offset + 1; // skip the first byte\n            Span<byte> zlibHeader = stackalloc byte[2];\n            stream.ReadExactly(zlibHeader);\n\n            if (MemoryMarshal.Read<ushort>(zlibHeader) == 0x9C78) //TODO: more generic zlib header validation\n            {\n                Stream dataStream = new PartialStream(stream, stream.Position, endPosition - stream.Position, true);\n                zlib = new DeflateStream(dataStream, CompressionMode.Decompress);\n            }\n            else\n            {\n                stream.Position -= 2;\n                Stream dataStream = new PartialStream(stream, stream.Position, endPosition - stream.Position, true);\n                Stream chunkStream = new ChunkedEncryptedInputStream(dataStream, this.WzImage.EncKeys);\n                chunkStream.ReadExactly(zlibHeader);\n                zlib = new DeflateStream(chunkStream, CompressionMode.Decompress);\n            }\n\n            return zlib;\n        }\n\n        public Bitmap ExtractPng()\n        {\n            return this.ExtractPng(page: 0);\n        }\n\n        public Bitmap ExtractPng(int page)\n        {\n            if (this.Pages > 0)\n            {\n                if (page < 0 || page >= this.Pages)\n                {\n                    throw new ArgumentOutOfRangeException(nameof(page));\n                }\n            }\n            else\n            {\n                // ignore it, always pick the first page.\n                page = 0;\n            }\n\n            int dataSizePerPage = this.GetRawDataSizePerPage();\n            byte[] pixel = new byte[dataSizePerPage];\n            int actualBytes = this.GetRawData(page * dataSizePerPage, pixel);\n            if (actualBytes != dataSizePerPage)\n                throw new ArgumentException($\"Not enough bytes have been read. (actual:{actualBytes}, expected:{dataSizePerPage})\");\n            Bitmap pngDecoded = null;\n            BitmapData bmpdata;\n\n            switch (this.Format)\n            {\n                case Wz_TextureFormat.ARGB4444 when this.ActualScale == 1:  // there's no form(3) any more.\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);\n                    unsafe\n                    {\n                        Span<byte> output = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                        ImageCodec.BGRA4444ToBGRA32(pixel, output);\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.ARGB8888 when this.ActualScale == 1:\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);\n                    unsafe\n                    {\n                        Span<byte> output = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                        pixel.CopyTo(output);\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.ARGB1555 when this.ActualScale == 1:\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format16bppArgb1555);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555);\n                    CopyBmpDataWithStride(pixel, pngDecoded.Width * 2, bmpdata);\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.RGB565 when this.ActualScale == 1 || this.ActualScale == 16:\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format16bppRgb565);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb565);\n                    if (this.ActualScale == 1) // old form(513)\n                    {\n                        CopyBmpDataWithStride(pixel, pngDecoded.Width * 2, bmpdata);\n                    }\n                    else if (this.ActualScale == 16) // old form(517)\n                    {\n                        unsafe\n                        {\n                            Span<byte> output = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                            int rawDataWidth = this.Width / this.ActualScale;\n                            int rawDataHeight = this.Height / this.ActualScale;\n                            ImageCodec.ScalePixels(pixel, 2, rawDataWidth, rawDataWidth * 2, rawDataHeight, this.ActualScale, this.ActualScale, output, bmpdata.Stride);\n                        }\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.DXT3:\n                    if (this.ActualScale != 1)\n                        throw new Exception(\"DXT3 does not support scale.\");\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(new Point(), pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);\n                    unsafe\n                    {\n                        Span<byte> output = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                        ImageCodec.DXT3ToBGRA32(pixel, output, this.Width, this.Width * 4, this.Height);\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.DXT5:\n                    if (this.ActualScale != 1)\n                        throw new Exception(\"DXT5 does not support scale.\");\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(new Point(), pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);\n                    unsafe\n                    {\n                        Span<byte> output = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                        ImageCodec.DXT5ToBGRA32(pixel, output, this.Width, this.Width * 4, this.Height);\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.RGBA1010102 when this.ActualScale == 1:\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(0, 0, this.Width, this.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);\n                    unsafe\n                    {\n                        int pageSize = this.Width * this.Height * 4;\n                        Span<byte> outputPixels = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                        ImageCodec.R10G10B10A2ToBGRA32(pixel, outputPixels);\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.BC7:\n                    if (this.ActualScale != 1)\n                        throw new Exception(\"BC7 does not support scale.\");\n                    pngDecoded = new Bitmap(this.Width & ~3, this.Height & ~3, PixelFormat.Format32bppArgb);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);\n                    unsafe\n                    {\n                        Span<byte> outputPixels = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                        ImageCodec.BC7ToRGBA32(pixel, this.Width * 4, outputPixels, bmpdata.Width, bmpdata.Stride, bmpdata.Height);\n                        ImageCodec.RGBA32ToBGRA32(outputPixels, outputPixels);\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                case Wz_TextureFormat.R16:\n                    pngDecoded = new Bitmap(this.Width, this.Height, PixelFormat.Format64bppArgb);\n                    bmpdata = pngDecoded.LockBits(new Rectangle(Point.Empty, pngDecoded.Size), ImageLockMode.WriteOnly, pngDecoded.PixelFormat);\n                    unsafe\n                    {\n                        Span<byte> output = new Span<byte>(bmpdata.Scan0.ToPointer(), bmpdata.Stride * bmpdata.Height);\n                        ImageCodec.R16ToBGRA64(pixel, output);\n                    }\n                    pngDecoded.UnlockBits(bmpdata);\n                    break;\n\n                default:\n                    throw new Exception($\"Unsupported format ({this.Format}, scale={this.ActualScale}).\");\n            }\n\n            return pngDecoded;\n        }\n\n        private static void CopyBmpDataWithStride(byte[] source, int stride, BitmapData bmpData)\n        {\n            if (bmpData.Stride == stride)\n            {\n                Marshal.Copy(source, 0, bmpData.Scan0, source.Length);\n            }\n            else\n            {\n                for (int y = 0; y < bmpData.Height; y++)\n                {\n                    Marshal.Copy(source, stride * y, bmpData.Scan0 + bmpData.Stride * y, stride);\n                }\n            }\n        }\n\n        public static int GetUncompressedDataSize(Wz_TextureFormat format, int scale, int width, int height)\n        {\n            if (scale > 1)\n            {\n                if ((width % scale) != 0 || (height % scale) != 0)\n                {\n                    throw new ArgumentException(\"Width or height cannot be divided by scale\");\n                }\n                width /= scale;\n                height /= scale;\n            }\n            return GetUncompressedDataSize(format, width, height);\n        }\n\n        public static int GetUncompressedDataSize(Wz_TextureFormat format, int width, int height)\n        {\n            return format switch\n            {\n                Wz_TextureFormat.ARGB4444 or\n                Wz_TextureFormat.ARGB1555 or\n                Wz_TextureFormat.RGB565 or\n                Wz_TextureFormat.R16 => width * height * 2,\n\n                Wz_TextureFormat.ARGB8888 or\n                Wz_TextureFormat.RGBA1010102 => width * height * 4,\n\n                Wz_TextureFormat.DXT3 or\n                Wz_TextureFormat.DXT5 => ((width + 3) / 4) * ((height + 3) / 4) * 16,\n\n                // TMST v1272, width and height for BC7 format are not always multiples of 4, NX will add row padding and discard the tail rows.\n                Wz_TextureFormat.BC7 => width * (height & ~3),\n\n                Wz_TextureFormat.DXT1 => ((width + 3) / 4) * ((height + 3) / 4) * 8,\n\n                Wz_TextureFormat.A8 => width * height,\n\n                Wz_TextureFormat.RGBA32Float => width * height * 16,\n\n                _ => throw new ArgumentException($\"Unknown texture format {(int)format}.\")\n            };\n        }\n    }\n\n    public enum Wz_TextureFormat\n    {\n        Unknown = 0,\n        ARGB4444 = 1,\n        ARGB8888 = 2,\n        ARGB1555 = 257,\n        RGB565 = 513,\n        /* introduced in KMST 1197 */\n        R16 = 769,\n        DXT3 = 1026,\n        DXT5 = 2050,\n        /* introduced in KMST 1186 */\n        A8 = 2304,\n        RGBA1010102 = 2562,\n        DXT1 = 4097,\n        BC7 = 4098,\n        RGBA32Float = 4100,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_RawData.cs",
    "content": "﻿using System;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_RawData : IMapleStoryBlob\n    {\n        public Wz_RawData(uint offset, int length, Wz_Image wz_Image)\n        {\n            this.Offset = offset;\n            this.Length = length;\n            this.WzImage = wz_Image;\n        }\n\n        public uint Offset { get; set; }\n        public int Length { get; set; }\n        public Wz_Image WzImage { get; set; }\n        public IMapleStoryFile WzFile => this.WzImage?.WzFile;\n\n        public void CopyTo(byte[] buffer, int offset)\n        {\n            if (buffer.Length - offset < this.Length)\n            {\n                throw new ArgumentException(\"Insufficient buffer size\");\n            }\n            lock (this.WzFile.ReadLock)\n            {\n                var s = this.WzImage.OpenRead();\n                s.Position = this.Offset;\n                s.ReadExactly(buffer, offset, this.Length);\n            }\n        }\n\n        public void CopyTo(Span<byte> span)\n        {\n            if (span.Length < this.Length)\n            {\n                throw new ArgumentException(\"Insufficient buffer size\");\n            }\n            lock (this.WzFile.ReadLock)\n            {\n                var s = this.WzImage.OpenRead();\n                s.Position = this.Offset;\n                s.ReadExactly(span.Slice(0, this.Length));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Sound.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Sound : IMapleStoryBlob\n    {\n        public Wz_Sound(uint offset, int length, int ms, Interop.AM_MEDIA_TYPE mediaType, Wz_Image wz_i)\n        {\n            this.offset = offset;\n            this.dataLength = length;\n            this.ms = ms;\n            this.mediaType = mediaType;\n            this.wz_i = wz_i;\n        }\n\n        private uint offset;\n        private int dataLength;\n        private int ms;\n        private Interop.AM_MEDIA_TYPE mediaType;\n        private Wz_Image wz_i;\n\n        /// <summary>\n        /// 获取或设置数据块对于文件的偏移。\n        /// </summary>\n        public uint Offset\n        {\n            get { return offset; }\n            set { offset = value; }\n        }\n\n        public int Channels => this.mediaType?.PbFormat switch\n        {\n            Interop.WAVEFORMATEX waveFmtEx => (int)waveFmtEx.Channels,\n            Interop.MPEGLAYER3WAVEFORMAT mp3WaveFmt => (int)mp3WaveFmt.Wfx.Channels,\n            _ => 0,\n        };\n\n        public int Frequency => this.mediaType?.PbFormat switch\n        {\n            Interop.WAVEFORMATEX waveFmtEx => (int)waveFmtEx.SamplesPerSec,\n            Interop.MPEGLAYER3WAVEFORMAT mp3WaveFmt => (int)mp3WaveFmt.Wfx.SamplesPerSec,\n            _ => 0,\n        };\n\n        /// <summary>\n        /// 获取或设置数据块的长度。\n        /// </summary>\n        public int DataLength\n        {\n            get { return dataLength; }\n            set { dataLength = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置Mp3的声音毫秒数。\n        /// </summary>\n        public int Ms\n        {\n            get { return ms; }\n            set { ms = value; }\n        }\n\n        public Interop.AM_MEDIA_TYPE MediaType\n        {\n            get { return mediaType; }\n            set { mediaType = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置图片所属的WzFile。\n        /// </summary>\n        public IMapleStoryFile WzFile\n        {\n            get { return wz_i?.WzFile; }\n        }\n\n        /// <summary>\n        /// 获取或设置图片所属的WzImage。\n        /// </summary>\n        public Wz_Image WzImage\n        {\n            get { return wz_i; }\n            set { wz_i = value; }\n        }\n\n        public Wz_SoundType SoundType\n        {\n            get\n            {\n                if (this.mediaType?.MajorType == Interop.MEDIATYPE_Stream)\n                {\n                    if (this.mediaType.SubType == Interop.MEDIASUBTYPE_MPEG1Audio)\n                    {\n                        return Wz_SoundType.Mp3;\n                    }\n                    else if (this.mediaType.SubType == Interop.MEDIASUBTYPE_WAVE)\n                    {\n                        if (this.mediaType.PbFormat is Interop.MPEGLAYER3WAVEFORMAT)\n                        {\n                            return Wz_SoundType.Mp3;\n                        }\n                        else if (this.mediaType.PbFormat is Interop.WAVEFORMATEX waveFmtEx && waveFmtEx.FormatTag == Interop.WAVE_FORMAT_PCM)\n                        {\n                            if (this.Ms == 1000 && this.Frequency == this.dataLength)\n                            {\n                                return Wz_SoundType.Binary;\n                            }\n                            else\n                            {\n                                return Wz_SoundType.Pcm;\n                            }\n                        }\n                    }\n                }\n\n                return Wz_SoundType.Unknown;\n            }\n        }\n\n        int IMapleStoryBlob.Length => this.DataLength;\n\n        public byte[] ExtractSound()\n        {\n            switch (this.SoundType)\n            {\n                case Wz_SoundType.Mp3:\n                    {\n                        byte[] data = new byte[this.dataLength];\n                        this.CopyTo(data, 0);\n                        return data;\n                    }\n                case Wz_SoundType.Pcm:\n                    {\n                        var waveFmtEx = (Interop.WAVEFORMATEX)this.mediaType.PbFormat;\n                        byte[] data = new byte[this.dataLength + 44];\n                        using var ms = new MemoryStream(data, true);\n                        using var br = new BinaryWriter(ms);\n                        br.Write(new byte[] { 0x52, 0x49, 0x46, 0x46 }); //\"RIFF\"\n                        br.Write(this.dataLength + 36); //chunkSize\n                        br.Write(new byte[] { 0x57, 0x41, 0x56, 0x45 }); //\"WAVE\"\n                        br.Write(new byte[] { 0x66, 0x6d, 0x74, 0x20 }); //\"fmt \"\n                        br.Write(16); //chunk1Size\n                        br.Write(waveFmtEx.FormatTag);\n                        br.Write(waveFmtEx.Channels);\n                        br.Write(waveFmtEx.SamplesPerSec);\n                        br.Write(waveFmtEx.AvgBytesPerSec);\n                        br.Write(waveFmtEx.BlockAlign);\n                        br.Write(waveFmtEx.BitsPerSample);\n                        br.Write(new byte[] { 0x64, 0x61, 0x74, 0x61 }); //\"data\"\n                        br.Write(this.dataLength); //chunk2Size\n                        this.CopyTo(data, 44);\n                        return data;\n                    }\n            }\n            return null;\n        }\n\n        public void CopyTo(byte[] buffer, int offset)\n        {\n            if (buffer.Length - offset < this.DataLength)\n            {\n                throw new ArgumentException(\"Insufficient buffer size\");\n            }\n            lock (this.WzFile.ReadLock)\n            {\n                var s = this.WzImage.OpenRead();\n                s.Position = this.Offset;\n                s.ReadExactly(buffer, offset, this.DataLength);\n            }\n        }\n\n        public void CopyTo(Span<byte> span)\n        {\n            if (span.Length < this.DataLength)\n            {\n                throw new ArgumentException(\"Insufficient buffer size\");\n            }\n            lock (this.WzFile.ReadLock)\n            {\n                var s = this.WzImage.OpenRead();\n                s.Position = this.Offset;\n                s.ReadExactly(span.Slice(0, this.DataLength));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_SoundType.cs",
    "content": "﻿using System;\n\nnamespace WzComparerR2.WzLib\n{\n    public enum Wz_SoundType\n    {\n        Unknown = 0,\n        Mp3,\n        Pcm,\n        Binary\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Structure.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.IO;\nusing System.Linq;\nusing WzComparerR2.WzLib.Compatibility;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Structure\n    {\n        public Wz_Structure()\n        {\n            this.wz_files = new List<Wz_File>();\n            this.ms_files = new List<IMapleStoryFile>();\n            this.encryption = new Wz_Crypto();\n            this.img_number = 0;\n            this.has_basewz = false;\n            this.TextEncoding = Wz_Structure.DefaultEncoding;\n            this.AutoDetectExtFiles = Wz_Structure.DefaultAutoDetectExtFiles;\n            this.ImgCheckDisabled = Wz_Structure.DefaultImgCheckDisabled;\n        }\n\n        public List<Wz_File> wz_files;\n        public List<IMapleStoryFile> ms_files;\n        public Wz_Crypto encryption;\n        public Wz_Node WzNode;\n        public int img_number;\n        public bool has_basewz;\n        public bool sorted; //暂时弃用\n\n        public Encoding TextEncoding { get; set; }\n        public bool AutoDetectExtFiles { get; set; }\n        public bool ImgCheckDisabled { get; set; }\n\n        public void Clear()\n        {\n            foreach (Wz_File f in this.wz_files)\n            {\n                f.Close();\n            }\n            this.wz_files.Clear();\n            foreach (IMapleStoryFile f in this.ms_files)\n            {\n                f.Dispose();\n            }\n            this.ms_files.Clear();\n            this.encryption.Reset();\n            this.img_number = 0;\n            this.has_basewz = false;\n            this.WzNode = null;\n            this.sorted = false;\n        }\n\n        public void calculate_img_count()\n        {\n            foreach (Wz_File f in this.wz_files)\n            {\n                this.img_number += f.ImageCount;\n            }\n        }\n\n        public void Load(string fileName, bool useBaseWz = false)\n        {\n            //现在我们已经不需要list了\n            this.WzNode = new Wz_Node(Path.GetFileName(fileName));\n            if (Path.GetFileName(fileName).ToLower() == \"list.wz\")\n            {\n                this.encryption.LoadListWz(Path.GetDirectoryName(fileName));\n                foreach (string list in this.encryption.List)\n                {\n                    WzNode.Nodes.Add(list);\n                }\n            }\n            else\n            {\n                LoadFile(fileName, WzNode, useBaseWz);\n            }\n            calculate_img_count();\n        }\n\n        public Wz_File LoadFile(string fileName, Wz_Node node, bool useBaseWz = false, bool loadWzAsFolder = false)\n        {\n            Wz_File file = null;\n\n            try\n            {\n                file = new Wz_File(fileName, this);\n                if (!file.Loaded)\n                {\n                    throw new Exception(\"The file is not a valid wz file.\");\n                }\n                this.wz_files.Add(file);\n                file.TextEncoding = this.TextEncoding;\n\n                // 1. pre-read\n                WzPreReadResult preReadResult = null;\n                foreach (var preReader in WzPreReaders.All)\n                {\n                    if (preReader.TryPreRead(file, out var result))\n                    {\n                        preReadResult = result;\n                        break;\n                    }\n                }\n\n                WzVersionProfile matchedProfile = null;\n\n                // 2. detect version and assign OffsetCalc to wz_file\n                if (preReadResult != null)\n                {\n                    // Try cached profiles first\n                    foreach (var cached in this.encryption.KnownProfiles)\n                    {\n                        var cachedProfile = WzVersionProfiles.GetByName(cached.ProfileName);\n                        if (cachedProfile != null && cachedProfile.Format == preReadResult.Format)\n                        {\n                            var iter = cachedProfile.CreateIterator(file);\n                            if (iter.IsMatch(cached.WzVersion, cached.HashVersion)\n                                && WzVersionDetectHelper.FastDetectSingleVersion(file, preReadResult,\n                                    cached.WzVersion, cached.HashVersion,\n                                    (f, hv) => cachedProfile.CreateOffsetCalc(f, hv)))\n                            {\n                                matchedProfile = cachedProfile;\n                                break;\n                            }\n                        }\n                    }\n\n                    // Full detection: iterate candidate profiles\n                    if (matchedProfile == null)\n                    {\n                        foreach (var profile in WzVersionProfiles.GetCandidates(preReadResult.Format))\n                        {\n                            var iter = profile.CreateIterator(file);\n                            if (profile.TryDetect(file, preReadResult, iter))\n                            {\n                                matchedProfile = profile;\n                                break;\n                            }\n                        }\n                    }\n\n                    // Cache on success\n                    if (matchedProfile != null)\n                    {\n                        var entry = new Wz_Crypto.KnownProfileEntry(matchedProfile.Name, file.Header.WzVersion, file.Header.HashVersion);\n                        int idx = this.encryption.KnownProfiles.FindIndex(e => e.ProfileName == entry.ProfileName\n                            && e.WzVersion == file.Header.WzVersion\n                            && e.HashVersion == file.Header.HashVersion);\n                        if (idx >= 0)\n                            this.encryption.KnownProfiles[idx] = entry;\n                        else\n                            this.encryption.KnownProfiles.Add(entry);\n                    }\n                }\n\n                // 3. detect string encryption, assign to crypto\n                if (preReadResult != null && matchedProfile != null)\n                {\n                    if (!this.encryption.IsDirEncDetected(file))\n                    {\n                        this.DetectCryptoKeyType(file, matchedProfile, preReadResult);\n                    }\n\n                    if (matchedProfile is Pkg2Profile pkg2Profile && file.Header is Wz_Header.WzPkg2Header pkg2Header)\n                    {\n                        pkg2Header.DirStringReader = pkg2Profile.CreateDirStringReader(file, this.encryption);\n                    }\n                }\n\n                // 4. full dir tree read\n                node.Value = file;\n                file.Node = node;\n                file.FileStream.Position = file.Header.DirStartPosition;\n                file.GetDirTree(node, useBaseWz, loadWzAsFolder);\n                file.DetectWzType();\n                return file;\n            }\n            catch\n            {\n                if (file != null)\n                {\n                    file.Close();\n                    this.wz_files.Remove(file);\n                }\n                throw;\n            }\n        }\n\n        public void LoadImg(string fileName)\n        {\n            this.WzNode = new Wz_Node(Path.GetFileName(fileName));\n            this.LoadImg(fileName, WzNode);\n        }\n\n        public void LoadImg(string fileName, Wz_Node node)\n        {\n            Wz_File file = null;\n\n            try\n            {\n                file = new Wz_File(fileName, this);\n                file.TextEncoding = this.TextEncoding;\n                file.Node = node;\n                var imgNode = new Wz_Node(node.Text);\n                //跳过checksum检测\n                var img = new Wz_Image(node.Text, (int)file.FileStream.Length, 0, 0, 0, file)\n                {\n                    OwnerNode = imgNode,\n                    Offset = 0,\n                    IsChecksumChecked = true\n                };\n                imgNode.Value = img;\n\n                node.Nodes.Add(imgNode);\n                node.Value = file;\n                this.wz_files.Add(file);\n            }\n            catch\n            {\n                file?.Close();\n                throw;\n            }\n        }\n\n        public void LoadKMST1125DataWz(string fileName)\n        {\n            this.LoadWzFolder(Path.GetDirectoryName(fileName), ref this.WzNode, true);\n            calculate_img_count();\n        }\n\n        public bool IsKMST1125WzFormat(string fileName)\n        {\n            if (!string.Equals(Path.GetExtension(fileName), \".wz\", StringComparison.OrdinalIgnoreCase))\n            {\n                return false;\n            }\n\n            string iniFile = Path.ChangeExtension(fileName, \".ini\");\n            if (!File.Exists(iniFile))\n            {\n                return false;\n            }\n\n            // check if the file is an empty wzfile via pre-read\n            using (var file = new Wz_File(fileName, this))\n            {\n                if (!file.Loaded)\n                {\n                    return false;\n                }\n                foreach (var preReader in WzPreReaders.All)\n                {\n                    if (preReader.TryPreRead(file, out var result))\n                    {\n                        return !result.Nodes.Any(n => n.NodeType == 0x02 || n.NodeType == 0x04);\n                    }\n                }\n                return false;\n            }\n        }\n\n        public void LoadWzFolder(string folder, ref Wz_Node node, bool useBaseWz = false)\n        {\n            string baseName = Path.Combine(folder, Path.GetFileName(folder));\n            string entryWzFileName = Path.ChangeExtension(baseName, \".wz\");\n            string iniFileName = Path.ChangeExtension(baseName, \".ini\");\n            Func<int, string> extraWzFileName = _index => Path.ChangeExtension($\"{baseName}_{_index:D3}\", \".wz\");\n\n            // load iniFile\n            int? lastWzIndex = null;\n            if (File.Exists(iniFileName))\n            {\n                var iniConf = File.ReadAllLines(iniFileName).Select(row =>\n                {\n                    string[] columns = row.Split('|');\n                    string key = columns.Length > 0 ? columns[0] : null;\n                    string value = columns.Length > 1 ? columns[1] : null;\n                    return new KeyValuePair<string, string>(key, value);\n                });\n                if (int.TryParse(iniConf.FirstOrDefault(kv => kv.Key == \"LastWzIndex\").Value, out var indexFromIni))\n                {\n                    lastWzIndex = indexFromIni;\n                }\n            }\n\n            // ini file missing or unexpected format\n            if (lastWzIndex == null)\n            {\n                for (int i = 0; ; i++)\n                {\n                    string extraFile = extraWzFileName(i);\n                    if (!File.Exists(extraFile))\n                    {\n                        break;\n                    }\n                    lastWzIndex = i;\n                }\n            }\n\n            // load entry file\n            if (node == null)\n            {\n                node = new Wz_Node(Path.GetFileName(entryWzFileName));\n            }\n            var entryWzf = this.LoadFile(entryWzFileName, node, useBaseWz, true);\n\n            // load extra file\n            if (lastWzIndex != null)\n            {\n                for (int i = 0, j = lastWzIndex.Value; i <= j; i++)\n                {\n                    string extraFile = extraWzFileName(i);\n                    var tempNode = new Wz_Node(Path.GetFileName(extraFile));\n                    var extraWzf = this.LoadFile(extraFile, tempNode, false, true);\n\n                    /*\n                     * there is a little hack here, we'll move all img to the entry file, and each img still refers to the original wzfile.\n                     * before:\n                     *   base.wz (Wz_File)\n                     *   |- a.img (Wz_Image)\n                     *   base_000.wz (Wz_File)\n                     *   |- b.img (Wz_Image) { wz_f = base_000.wz }\n                     *   \n                     * after:\n                     *   base.wz (Wz_File) { mergedFiles = [base_000.wz] }\n                     *   |- a.img (Wz_Image)  { wz_f = base.wz }\n                     *   |- b.img (Wz_Image)  { wz_f = base_000.wz }\n                     *   \n                     * this.wz_files references all opened files so they can be closed correctly.\n                     */\n\n                    entryWzf.MergeWzFile(extraWzf);\n                }\n            }\n        }\n\n        public void LoadMsFile(string fileName)\n        {\n            this.LoadMsFile(fileName, ref this.WzNode);\n        }\n\n        private void LoadMsFile(string fileName, ref Wz_Node node)\n        {\n            List<Exception> exceptions = new(2);\n            if (node == null)\n            {\n                node = new Wz_Node(Path.GetFileName(fileName));\n            }\n\n            bool loaded = false;\n            // try ms file v1\n            if (!loaded)\n            {\n                Ms_File file = null;\n                try\n                {\n                    file = new Ms_File(fileName, this);\n                    file.ReadEntries();\n                    file.GetDirTree(node);\n                    this.ms_files.Add(file);\n                    loaded = true;\n                }\n                catch(Exception ex)\n                {\n                    if (file != null)\n                    {\n                        file.Close();\n                        this.ms_files.Remove(file);\n                    }\n                    exceptions.Add(ex);\n                }\n            }\n\n            // try ms file v2\n            if (!loaded)\n            {\n                Ms_FileV2 file = null;\n                try\n                {\n                    file = new Ms_FileV2(fileName, this);\n                    file.ReadEntries();\n                    file.GetDirTree(node);\n                    this.ms_files.Add(file);\n                    loaded = true;\n                }\n                catch (Exception ex)\n                {\n                    if (file != null)\n                    {\n                        file.Close();\n                        this.ms_files.Remove(file);\n                    }\n                    exceptions.Add(ex);\n                }\n            }\n            \n            // return errors\n            if (!loaded)\n            {\n                throw new AggregateException(\"Failed to load ms files.\", exceptions.ToArray());\n            }\n        }\n\n        private void DetectCryptoKeyType(Wz_File file, WzVersionProfile profile, WzPreReadResult preReadResult)\n        {\n            profile.DetectCryptoKeyType(file, this.encryption, preReadResult, out var pkg1KeyType, out var pkg2KeyType);\n            if (pkg1KeyType != Wz_CryptoKeyType.Unknown)\n                this.encryption.Pkg1EncType = pkg1KeyType;\n            if (pkg2KeyType != Wz_CryptoKeyType.Unknown)\n                this.encryption.Pkg2EncType = pkg2KeyType;\n        }\n\n        #region Global Settings\n        public static Encoding DefaultEncoding\n        {\n            get { return _defaultEncoding ?? Encoding.Default; }\n            set { _defaultEncoding = value; }\n        }\n\n        private static Encoding _defaultEncoding;\n\n        public static bool DefaultAutoDetectExtFiles { get; set; }\n\n        public static bool DefaultImgCheckDisabled { get; set; }\n\n        #endregion\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Type.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.WzLib\n{\n    /// <summary>\n    /// 标识Wz_File的内部类型。\n    /// </summary>\n    public enum Wz_Type\n    {\n        Unknown = 0,\n        Base,\n        Character,\n        Effect,\n        Etc,\n        Item,\n        Map,\n        Mob,\n        Morph,\n        Npc,\n        Quest,\n        Reactor,\n        Skill,\n        Sound,\n        String,\n        TamingMob,\n        UI,\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Uol.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Uol\n    {\n        public Wz_Uol(string uol)\n        {\n            this.uol = uol;\n        }\n\n        private string uol;\n\n        /// <summary>\n        /// 获取或设置连接路径字符串。\n        /// </summary>\n        public string Uol\n        {\n            get { return uol; }\n            set { uol = value; }\n        }\n\n        public Wz_Node HandleUol(Wz_Node currentNode)\n        {\n            if (currentNode == null || currentNode.ParentNode == null || string.IsNullOrEmpty(uol))\n                return null;\n            string[] dirs = this.uol.Split('/');\n            currentNode = currentNode.ParentNode;\n\n            bool outImg = false;\n\n            for (int i = 0; i < dirs.Length; i++)\n            {\n                string dir = dirs[i];\n                if (dir == \"..\")\n                {\n                    if (currentNode.ParentNode == null)\n                    {\n                        Wz_Image img = currentNode.GetValueEx<Wz_Image>(null);\n                        if (img != null)\n                        {\n                            currentNode = img.OwnerNode.ParentNode;\n                            outImg = true;\n                        }\n                        else\n                        {\n                            currentNode = null;\n                        }\n                    }\n                    else\n                    {\n                        currentNode = currentNode.ParentNode;\n                    }\n                }\n                else\n                {\n                    var dirNode = currentNode.FindNodeByPath(dir);\n\n                    if (dirNode == null && outImg)\n                    {\n                        dirNode = currentNode.FindNodeByPath(true, dir + \".img\");\n                        if (dirNode.GetValueEx<Wz_Image>(null) != null)\n                        {\n                            outImg = false;\n                        }\n                    }\n\n                    currentNode = dirNode;\n                }\n                if (currentNode == null)\n                    return null;\n            }\n            return currentNode;\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Vector.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\nusing System.Drawing;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Vector\n    {\n        public Wz_Vector(int x, int y)\n        {\n            this.x = x;\n            this.y = y;\n        }\n\n        private int x;\n        private int y;\n\n        /// <summary>\n        /// 获取或设置向量的X值。\n        /// </summary>\n        public int X\n        {\n            get { return x; }\n            set { x = value; }\n        }\n\n        /// <summary>\n        /// 获取或设置向量的Y值。\n        /// </summary>\n        public int Y\n        {\n            get { return y; }\n            set { y = value; }\n        }\n\n        public static implicit operator Point(Wz_Vector vector)\n        {\n            return vector == null ? new Point() : new Point(vector.x, vector.y);\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.WzLib/Wz_Video.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Text;\nusing WzComparerR2.WzLib.Utilities;\n\nnamespace WzComparerR2.WzLib\n{\n    public class Wz_Video : IMapleStoryBlob\n    {\n        public Wz_Video(uint offset, int length, Wz_Image wz_Image)\n        {\n            this.Offset = offset;\n            this.Length = length;\n            this.WzImage = wz_Image;\n        }\n\n        public uint Offset { get; set; }\n        public int Length { get; set; }\n        public Wz_Image WzImage { get; set; }\n        public IMapleStoryFile WzFile => this.WzImage?.WzFile;\n\n        public McvHeader ReadVideoFileHeader()\n        {\n            lock (this.WzFile.ReadLock)\n            {\n                var s = this.WzImage.OpenRead();\n                s.Position = this.Offset;\n                var br = new BinaryReader(s, Encoding.ASCII);\n                string signature = new string(br.ReadChars(4));\n                if (signature != \"MCV0\")\n                {\n                    throw new Exception(\"File signature does not match.\");\n                }\n                s.Position += 2;\n                int headerLen = br.ReadUInt16();\n                uint fourcc = br.ReadUInt32() ^ 0xa5a5a5a5;\n                int width = br.ReadUInt16();\n                int height = br.ReadUInt16();\n                int frameCount = br.ReadInt32();\n                McvDataFlags dataFlag = (McvDataFlags)br.ReadByte();\n                s.Position += 3;\n                long frameDelayUnit = br.ReadInt64();\n                int defaultDelay = br.ReadInt32();\n                s.Position = this.Offset + headerLen;\n\n                McvFrameInfo[] frames = new McvFrameInfo[frameCount];\n                // read base data\n                for (int i = 0; i < frames.Length; i++)\n                {\n                    var fi = new McvFrameInfo();\n                    fi.DataOffset = br.ReadInt32();\n                    fi.DataCount = br.ReadInt32();\n                    frames[i] = fi;\n                }\n                // read alpha map data\n                if ((dataFlag & McvDataFlags.AlphaMap) != 0)\n                {\n                    foreach (var fi in frames)\n                    {\n                        fi.AlphaDataOffset = br.ReadInt32();\n                        fi.AlphaDataCount = br.ReadInt32();\n                    }\n                }\n                // read frame delay\n                if ((dataFlag & McvDataFlags.PerFrameDelay) != 0)\n                {\n                    foreach (var fi in frames)\n                    {\n                        fi.DelayInNanoseconds = br.ReadInt32() * frameDelayUnit;\n                    }\n                }\n                else\n                {\n                    foreach (var fi in frames)\n                    {\n                        fi.DelayInNanoseconds = defaultDelay * frameDelayUnit;\n                    }\n                }\n                // read frame timeline\n                if ((dataFlag & McvDataFlags.PerFrameTimeline) != 0)\n                {\n                    foreach (var fi in frames)\n                    {\n                        fi.StartTimeInNanoseconds = br.ReadInt64() * frameDelayUnit;\n                    }\n                }\n                else\n                {\n                    long time = 0;\n                    foreach (var fi in frames)\n                    {\n                        fi.StartTimeInNanoseconds = time;\n                        time += fi.DelayInNanoseconds;\n                    }\n                }\n\n                // reassign data offset\n                long dataStartPosition = s.Position - this.Offset;\n                foreach (var fi in frames)\n                {\n                    fi.DataOffset += dataStartPosition;\n                    if (fi.AlphaDataCount > 0 && fi.AlphaDataOffset > -1)\n                    {\n                        fi.AlphaDataOffset += dataStartPosition;\n                    }\n                }\n\n                return new McvHeader()\n                {\n                    Signature = signature,\n                    HeaderLength = headerLen,\n                    FourCC = fourcc,\n                    Width = width,\n                    Height = height,\n                    FrameCount = frameCount,\n                    DataFlag = dataFlag,\n                    Frames = frames,\n                };\n            }\n        }\n\n        public void CopyTo(byte[] buffer, int offset)\n        {\n            if (buffer.Length - offset < this.Length)\n            {\n                throw new ArgumentException(\"Insufficient buffer size\");\n            }\n            lock (this.WzFile.ReadLock)\n            {\n                var s = this.WzImage.OpenRead();\n                s.Position = this.Offset;\n                s.ReadExactly(buffer, offset, this.Length);\n            }\n        }\n\n        public void CopyTo(Span<byte> span)\n        {\n            if (span.Length < this.Length)\n            {\n                throw new ArgumentException(\"Insufficient buffer size\");\n            }\n            lock (this.WzFile.ReadLock)\n            {\n                var s = this.WzImage.OpenRead();\n                s.Position = this.Offset;\n                s.ReadExactly(span.Slice(0, this.Length));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "WzComparerR2.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.31624.102\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2\", \"WzComparerR2\\WzComparerR2.csproj\", \"{5E883BE2-2009-4517-8026-4B90DEB83884}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"CharaSimResource\", \"CharaSimResource\\CharaSimResource.csproj\", \"{54797F38-A12C-4202-92A4-1A3DDCE914B7}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.PluginBase\", \"WzComparerR2.PluginBase\\WzComparerR2.PluginBase.csproj\", \"{FA74A2FD-0250-4182-845D-DD98D829B525}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.WzLib\", \"WzComparerR2.WzLib\\WzComparerR2.WzLib.csproj\", \"{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Plugin\", \"Plugin\", \"{495B5D02-38A2-48C0-B8D2-C2A1783EC231}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.LuaConsole\", \"WzComparerR2.LuaConsole\\WzComparerR2.LuaConsole.csproj\", \"{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.MapRender\", \"WzComparerR2.MapRender\\WzComparerR2.MapRender.csproj\", \"{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.Common\", \"WzComparerR2.Common\\WzComparerR2.Common.csproj\", \"{818060BC-404C-470A-94B3-5160716C5247}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.Avatar\", \"WzComparerR2.Avatar\\WzComparerR2.Avatar.csproj\", \"{A0753218-2C58-4E4A-9017-A435D2E5F639}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.Network\", \"WzComparerR2.Network\\WzComparerR2.Network.csproj\", \"{EBDA4AE9-4BCE-4824-84B5-B15660437141}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"WzComparerR2.Updater\", \"WzComparerR2.Updater\\WzComparerR2.Updater.csproj\", \"{C884A04D-D8B9-4F68-BD9D-FD701A27E661}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|x64 = Debug|x64\n\t\tDebug|x86 = Debug|x86\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|x64 = Release|x64\n\t\tRelease|x86 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x64.Build.0 = Debug|x64\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x86.ActiveCfg = Debug|x86\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x86.Build.0 = Debug|x86\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x64.ActiveCfg = Release|x64\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x64.Build.0 = Release|x64\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x86.ActiveCfg = Release|x86\n\t\t{5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x86.Build.0 = Release|x86\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x64.Build.0 = Release|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x86.Build.0 = Release|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x64.Build.0 = Release|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x86.Build.0 = Release|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x64.Build.0 = Release|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x86.Build.0 = Release|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Release|x64.Build.0 = Release|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4}.Release|x86.Build.0 = Release|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x64.Build.0 = Release|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x86.Build.0 = Release|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Release|x64.Build.0 = Release|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{818060BC-404C-470A-94B3-5160716C5247}.Release|x86.Build.0 = Release|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x64.Build.0 = Release|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x86.Build.0 = Release|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Release|x64.Build.0 = Release|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141}.Release|x86.Build.0 = Release|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Release|x64.Build.0 = Release|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{C884A04D-D8B9-4F68-BD9D-FD701A27E661}.Release|x86.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{1995B977-CD5E-4A8B-BE3D-15A2AFE1BFB4} = {495B5D02-38A2-48C0-B8D2-C2A1783EC231}\n\t\t{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002} = {495B5D02-38A2-48C0-B8D2-C2A1783EC231}\n\t\t{A0753218-2C58-4E4A-9017-A435D2E5F639} = {495B5D02-38A2-48C0-B8D2-C2A1783EC231}\n\t\t{EBDA4AE9-4BCE-4824-84B5-B15660437141} = {495B5D02-38A2-48C0-B8D2-C2A1783EC231}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {264DC78F-E4A0-4F7E-930A-06AAF2B907F3}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "# .NET Desktop\n# Build and run tests for .NET Desktop or Windows classic desktop solutions.\n# Add steps that publish symbols, save build artifacts, and more:\n# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net\n\ntrigger:\n- master\n\npool:\n  vmImage: 'windows-latest'\n\nvariables:\n  solution: 'WzComparerR2.sln'\n  buildPlatform_anycpu: 'Any CPU'\n  buildPlatform_x86: 'x86'\n  buildConfiguration: 'Release'\n  outputDir_netfx_anycpu: 'WzComparerR2/bin/release/net462'\n  outputDir_netfx_x86: 'WzComparerR2/bin/x86/release/net462'\n  outputDir_net6_anycpu: 'WzComparerR2/bin/release/net6.0-windows'\n  outputDir_net8_anycpu: 'WzComparerR2/bin/release/net8.0-windows'\n  outputFileName_netfx: 'WcR2_With_Plugins_$(Build.BuildNumber)'\n  outputFileName_net6: 'WcR2_With_Plugins_net6_$(Build.BuildNumber)'\n  outputFileName_net8: 'WcR2_With_Plugins_net8_$(Build.BuildNumber)'\n  isMain: $[eq(variables['Build.SourceBranch'], 'refs/heads/master')]\n  accountName: 'wcr2publish'\n  containerName: 'ci-build'\n\nsteps:\n- checkout: self\n  submodules: true\n  fetchDepth: 1\n  path: s/WzComparerR2\n  persistCredentials: true\n\n- task: CmdLine@2\n  displayName: Generate version file\n  inputs:\n    script: |\n      echo [assembly: global::System.Reflection.AssemblyInformationalVersion(\"2.2.$(Build.BuildNumber)\")] >> Build\\CommonAssemblyInfo.cs\n\n- task: DotNetCoreCLI@2\n  displayName: Restore NuGet packages\n  inputs:\n    command: 'restore'\n    projects: '$(solution)'\n    feedsToUse: 'select'\n\n- task: DotNetCoreCLI@2\n  displayName: Build anycpu\n  inputs:\n    command: 'build'\n    projects: '$(solution)'\n    arguments: '/p:Platform=\"$(buildPlatform_anycpu)\" -c $(buildConfiguration)'\n\n- task: DotNetCoreCLI@2\n  displayName: Build x86\n  inputs:\n    command: 'build'\n    projects: '$(solution)'\n    arguments: '/p:Platform=\"$(buildPlatform_x86)\" -c $(buildConfiguration)'\n\n- task: CmdLine@2\n  displayName: Prepare output files\n  inputs:\n    script: |\n      move \"$(outputDir_netfx_anycpu)\\*.dll\" \"$(outputDir_netfx_anycpu)\\Lib\"\n      del \"$(outputDir_netfx_anycpu)\\*.pdb\"\n      rename \"$(outputDir_netfx_anycpu)\\WzComparerR2.exe\" \"WzComparerR2.anycpu.exe\"\n      rename \"$(outputDir_netfx_anycpu)\\WzComparerR2.exe.config\" \"WzComparerR2.anycpu.exe.config\"\n      copy /y \"$(outputDir_netfx_x86)\\WzComparerR2.exe\" \"$(outputDir_netfx_anycpu)\"\n      copy /y \"$(outputDir_netfx_x86)\\WzComparerR2.exe.config\" \"$(outputDir_netfx_anycpu)\"\n      \n      move \"$(outputDir_net6_anycpu)\\*.dll\" \"$(outputDir_net6_anycpu)\\Lib\"\n      del \"$(outputDir_net6_anycpu)\\*.pdb\"\n      move \"$(outputDir_net6_anycpu)\\Lib\\WzComparerR2.dll\" \"$(outputDir_net6_anycpu)\"\n\n      move \"$(outputDir_net8_anycpu)\\*.dll\" \"$(outputDir_net8_anycpu)\\Lib\"\n      del \"$(outputDir_net8_anycpu)\\*.pdb\"\n      move \"$(outputDir_net8_anycpu)\\Lib\\WzComparerR2.dll\" \"$(outputDir_net8_anycpu)\"\n    failOnStderr: true\n\n- task: ArchiveFiles@2\n  displayName: Compress netfx release\n  inputs:\n    rootFolderOrFile: '$(outputDir_netfx_anycpu)'\n    includeRootFolder: false\n    archiveType: 'zip'\n    archiveFile: '$(Build.ArtifactStagingDirectory)/$(outputFileName_netfx).zip'\n    replaceExistingArchive: true\n \n- task: ArchiveFiles@2\n  displayName: Compress net6 release\n  inputs:\n    rootFolderOrFile: '$(outputDir_net6_anycpu)'\n    includeRootFolder: false\n    archiveType: 'zip'\n    archiveFile: '$(Build.ArtifactStagingDirectory)/$(outputFileName_net6).zip'\n    replaceExistingArchive: true\n\n- task: ArchiveFiles@2\n  displayName: Compress net8 release\n  inputs:\n    rootFolderOrFile: '$(outputDir_net8_anycpu)'\n    includeRootFolder: false\n    archiveType: 'zip'\n    archiveFile: '$(Build.ArtifactStagingDirectory)/$(outputFileName_net8).zip'\n    replaceExistingArchive: true\n\n- task: AzureCLI@2\n  displayName: Upload to blob\n  inputs:\n    azureSubscription: 'Visual Studio Enterprise(66312bd9-1264-4b34-872d-2e557fc1bc0d)'\n    scriptType: 'ps'\n    scriptLocation: 'inlineScript'\n    inlineScript: |\n      $files = @(\n        @{\n          \"srcFile\"=\"$(outputFileName_netfx).zip\"\n          \"outputVariableName\"=\"sasurl_netfx\"\n        }\n        @{\n          \"srcFile\"=\"$(outputFileName_net6).zip\"\n          \"outputVariableName\"=\"sasurl_net6\"\n        }\n        @{\n          \"srcFile\"=\"$(outputFileName_net8).zip\"\n          \"outputVariableName\"=\"sasurl_net8\"\n        }\n      )\n      \n      $time = (Get-Date).ToUniversalTime()\n      $expireDate = $time.AddYears(1).ToString(\"yyyy-MM-dd'T'HH:mm:ss'Z'\")\n      $keys = ConvertFrom-Json ((az storage account keys list --account-name $(accountName)) -join \"\")\n      $accountKey = $keys[0].value\n      $commitID = \"$(Build.SourceVersion)\"\n      $buildID = \"$(Build.BuildNumber)\"\n      $uploadTime = $time.ToString(\"yyyy-MM-dd'T'HH:mm:ss'Z'\")\n\n      foreach ($f in $files) {\n        $srcFile = \"$(Build.ArtifactStagingDirectory)/{0}\" -f $f.srcFile\n        $dstFile = $f.srcFile\n        $varName = $f.outputVariableName\n        \n        Write-Host \"Uploading ${srcFile} ...\"\n        az storage blob upload --auth-mode login --account-name $(accountName) --container-name $(containerName) --name $dstFile --file $srcFile --tags \"commitID=$commitID\" \"buildID=$buildID\" \"uploadTime=$uploadTime\"\n      \n        Write-Host \"Generate sas url...\"\n        $url = ConvertFrom-Json (az storage blob url --auth-mode login --account-name $(accountName) --container-name $(containerName) --name $dstFile)\n        $sas = ConvertFrom-Json (az storage blob generate-sas --account-name $(accountName) --account-key \"$accountKey\" --container-name $(containerName) --name $dstFile --expiry \"$expireDate\" --permissions \"r\")\n        $sasurl = \"${url}?${sas}\"\n        Write-Host \"${sasurl}\"\n        Write-Host \"##vso[task.setvariable variable=${varName}]${sasurl}\"\n      }\n\n- task: GitHubRelease@1\n  condition: and(succeeded(), eq(variables.isMain, true))\n  continueOnError: true\n  displayName: Delete last build\n  inputs:\n    gitHubConnection: 'Kagamia'\n    repositoryName: '$(Build.Repository.Name)'\n    action: 'delete'\n    tag: 'ci-build'\n\n- task: CmdLine@2\n  condition: and(succeeded(), eq(variables.isMain, true))\n  continueOnError: true\n  displayName: Delete ci-build tag\n  inputs:\n    script: 'git push --delete origin ci-build'\n    failOnStderr: false\n\n- task: GitHubRelease@1\n  condition: and(succeeded(), eq(variables.isMain, true))\n  inputs:\n    gitHubConnection: 'Kagamia'\n    repositoryName: '$(Build.Repository.Name)'\n    action: 'edit'\n    target: '$(Build.SourceVersion)'\n    tag: 'ci-build'\n    title: 'CI-Build-$(Build.BuildNumber)'\n    releaseNotesSource: 'inline'\n    releaseNotesInline: |\n      Automation build from azure-pipeline.\n      Mirror: [[WcR2.netfx.zip]($(sasurl_netfx))] | [[WcR2.net6.zip]($(sasurl_net6))] | [[WcR2.net8.zip]($(sasurl_net8))]\n    isPreRelease: false\n    addChangeLog: false\n"
  }
]