├── .gitignore ├── .gitmodules ├── LICENSE ├── MapRender.Invoker ├── MapRender.Invoker.csproj ├── MapRender.cs ├── MapRenderInvoker.cs ├── ObjectClass.cs ├── ScreenShotData.cs └── TargetItem.cs ├── MapleStory.Common ├── Exceptions │ └── WzImgNotFoundException.cs ├── MapleStory.Common.csproj ├── MapleStoryPathHelper.cs └── WzTreeSearcher.cs ├── MapleStory.MachineLearningSampleGenerator ├── MapleStory.MachineLearningSampleGenerator.csproj ├── OutputFormat.cs └── Program.cs ├── MapleStory.Sampler ├── CocoWriter.cs ├── DarknetWriter.cs ├── IDatasetWriter.cs ├── MapleStory.Sampler.csproj ├── PostProcessor │ ├── IPostProcessor.cs │ └── PlayerProcessor.cs ├── Sample.cs ├── Sampler.cs ├── TfExample.cs ├── TfRecordWriter.cs └── protobuf │ ├── Example.cs │ ├── Feature.cs │ ├── README.md │ ├── example.proto │ └── feature.proto ├── MapleStoryDetectionSampleGenerator.sln ├── README.md └── pictures ├── chart_yolov4-custom.png └── result.png /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | */**/launchSettings.json 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Mono auto generated files 18 | mono_crash.* 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # StyleCop 66 | StyleCopReport.xml 67 | 68 | # Files built by Visual Studio 69 | *_i.c 70 | *_p.c 71 | *_h.h 72 | *.ilk 73 | *.meta 74 | *.obj 75 | *.iobj 76 | *.pch 77 | *.pdb 78 | *.ipdb 79 | *.pgc 80 | *.pgd 81 | *.rsp 82 | *.sbr 83 | *.tlb 84 | *.tli 85 | *.tlh 86 | *.tmp 87 | *.tmp_proj 88 | *_wpftmp.csproj 89 | *.log 90 | *.vspscc 91 | *.vssscc 92 | .builds 93 | *.pidb 94 | *.svclog 95 | *.scc 96 | 97 | # Chutzpah Test files 98 | _Chutzpah* 99 | 100 | # Visual C++ cache files 101 | ipch/ 102 | *.aps 103 | *.ncb 104 | *.opendb 105 | *.opensdf 106 | *.sdf 107 | *.cachefile 108 | *.VC.db 109 | *.VC.VC.opendb 110 | 111 | # Visual Studio profiler 112 | *.psess 113 | *.vsp 114 | *.vspx 115 | *.sap 116 | 117 | # Visual Studio Trace Files 118 | *.e2e 119 | 120 | # TFS 2012 Local Workspace 121 | $tf/ 122 | 123 | # Guidance Automation Toolkit 124 | *.gpState 125 | 126 | # ReSharper is a .NET coding add-in 127 | _ReSharper*/ 128 | *.[Rr]e[Ss]harper 129 | *.DotSettings.user 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # Installshield output folder 158 | [Ee]xpress/ 159 | 160 | # DocProject is a documentation generator add-in 161 | DocProject/buildhelp/ 162 | DocProject/Help/*.HxT 163 | DocProject/Help/*.HxC 164 | DocProject/Help/*.hhc 165 | DocProject/Help/*.hhk 166 | DocProject/Help/*.hhp 167 | DocProject/Help/Html2 168 | DocProject/Help/html 169 | 170 | # Click-Once directory 171 | publish/ 172 | 173 | # Publish Web Output 174 | *.[Pp]ublish.xml 175 | *.azurePubxml 176 | # Note: Comment the next line if you want to checkin your web deploy settings, 177 | # but database connection strings (with potential passwords) will be unencrypted 178 | *.pubxml 179 | *.publishproj 180 | 181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 182 | # checkin your Azure Web App publish settings, but sensitive information contained 183 | # in these scripts will be unencrypted 184 | PublishScripts/ 185 | 186 | # NuGet Packages 187 | *.nupkg 188 | # NuGet Symbol Packages 189 | *.snupkg 190 | # The packages folder can be ignored because of Package Restore 191 | **/[Pp]ackages/* 192 | # except build/, which is used as an MSBuild target. 193 | !**/[Pp]ackages/build/ 194 | # Uncomment if necessary however generally it will be regenerated when needed 195 | #!**/[Pp]ackages/repositories.config 196 | # NuGet v3's project.json files produces more ignorable files 197 | *.nuget.props 198 | *.nuget.targets 199 | 200 | # Microsoft Azure Build Output 201 | csx/ 202 | *.build.csdef 203 | 204 | # Microsoft Azure Emulator 205 | ecf/ 206 | rcf/ 207 | 208 | # Windows Store app package directories and files 209 | AppPackages/ 210 | BundleArtifacts/ 211 | Package.StoreAssociation.xml 212 | _pkginfo.txt 213 | *.appx 214 | *.appxbundle 215 | *.appxupload 216 | 217 | # Visual Studio cache files 218 | # files ending in .cache can be ignored 219 | *.[Cc]ache 220 | # but keep track of directories ending in .cache 221 | !?*.[Cc]ache/ 222 | 223 | # Others 224 | ClientBin/ 225 | ~$* 226 | *~ 227 | *.dbmdl 228 | *.dbproj.schemaview 229 | *.jfm 230 | *.pfx 231 | *.publishsettings 232 | orleans.codegen.cs 233 | 234 | # Including strong name files can present a security risk 235 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 236 | #*.snk 237 | 238 | # Since there are multiple workflows, uncomment next line to ignore bower_components 239 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 240 | #bower_components/ 241 | 242 | # RIA/Silverlight projects 243 | Generated_Code/ 244 | 245 | # Backup & report files from converting an old project file 246 | # to a newer Visual Studio version. Backup files are not needed, 247 | # because we have git ;-) 248 | _UpgradeReport_Files/ 249 | Backup*/ 250 | UpgradeLog*.XML 251 | UpgradeLog*.htm 252 | ServiceFabricBackup/ 253 | *.rptproj.bak 254 | 255 | # SQL Server files 256 | *.mdf 257 | *.ldf 258 | *.ndf 259 | 260 | # Business Intelligence projects 261 | *.rdl.data 262 | *.bim.layout 263 | *.bim_*.settings 264 | *.rptproj.rsuser 265 | *- [Bb]ackup.rdl 266 | *- [Bb]ackup ([0-9]).rdl 267 | *- [Bb]ackup ([0-9][0-9]).rdl 268 | 269 | # Microsoft Fakes 270 | FakesAssemblies/ 271 | 272 | # GhostDoc plugin setting file 273 | *.GhostDoc.xml 274 | 275 | # Node.js Tools for Visual Studio 276 | .ntvs_analysis.dat 277 | node_modules/ 278 | 279 | # Visual Studio 6 build log 280 | *.plg 281 | 282 | # Visual Studio 6 workspace options file 283 | *.opt 284 | 285 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 286 | *.vbw 287 | 288 | # Visual Studio LightSwitch build output 289 | **/*.HTMLClient/GeneratedArtifacts 290 | **/*.DesktopClient/GeneratedArtifacts 291 | **/*.DesktopClient/ModelManifest.xml 292 | **/*.Server/GeneratedArtifacts 293 | **/*.Server/ModelManifest.xml 294 | _Pvt_Extensions 295 | 296 | # Paket dependency manager 297 | .paket/paket.exe 298 | paket-files/ 299 | 300 | # FAKE - F# Make 301 | .fake/ 302 | 303 | # CodeRush personal settings 304 | .cr/personal 305 | 306 | # Python Tools for Visual Studio (PTVS) 307 | __pycache__/ 308 | *.pyc 309 | 310 | # Cake - Uncomment if you are using it 311 | # tools/** 312 | # !tools/packages.config 313 | 314 | # Tabs Studio 315 | *.tss 316 | 317 | # Telerik's JustMock configuration file 318 | *.jmconfig 319 | 320 | # BizTalk build output 321 | *.btp.cs 322 | *.btm.cs 323 | *.odx.cs 324 | *.xsd.cs 325 | 326 | # OpenCover UI analysis results 327 | OpenCover/ 328 | 329 | # Azure Stream Analytics local run output 330 | ASALocalRun/ 331 | 332 | # MSBuild Binary and Structured Log 333 | *.binlog 334 | 335 | # NVidia Nsight GPU debugger configuration file 336 | *.nvuser 337 | 338 | # MFractors (Xamarin productivity tool) working folder 339 | .mfractor/ 340 | 341 | # Local History for Visual Studio 342 | .localhistory/ 343 | 344 | # BeatPulse healthcheck temp database 345 | healthchecksdb 346 | 347 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 348 | MigrationBackup/ 349 | 350 | # Ionide (cross platform F# VS Code tools) working folder 351 | .ionide/ 352 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "WzComparerR2"] 2 | path = WzComparerR2 3 | url = git@github.com:charlescao460/WzComparerR2.git 4 | branch = . 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Charles Cao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MapRender.Invoker/MapRender.Invoker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows7.0 4 | x64 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ..\WzComparerR2\References\DevComponents.DotNetBar2.dll 18 | 19 | 20 | ..\WzComparerR2\References\EmptyKeys.UserInterface.dll 21 | 22 | 23 | ..\WzComparerR2\References\EmptyKeys.UserInterface.Core.dll 24 | 25 | 26 | ..\WzComparerR2\References\MonoGame.Framework.dll 27 | 28 | 29 | -------------------------------------------------------------------------------- /MapRender.Invoker/MapRender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.Xna.Framework; 9 | using Microsoft.Xna.Framework.Graphics; 10 | using WzComparerR2.Animation; 11 | using WzComparerR2.MapRender; 12 | using WzComparerR2.MapRender.Patches; 13 | using WzComparerR2.MapRender.Patches2; 14 | using WzComparerR2.WzLib; 15 | 16 | namespace MapRender.Invoker 17 | { 18 | internal class MapRender : FrmMapRender2 19 | { 20 | private volatile Stream _screenShotStream; 21 | private volatile ScreenShotData _screenShotData; 22 | 23 | public MapRender(Wz_Image img) : base() 24 | { 25 | LoadMap(img); 26 | } 27 | 28 | public void ChangeResolution(int width, int height) 29 | { 30 | GraphicsDeviceManager deviceManager = this.GraphicsManager; 31 | deviceManager.PreferredBackBufferWidth = width; 32 | deviceManager.PreferredBackBufferHeight = height; 33 | WzComparerR2.Rendering.D2DFactory.Instance.ReleaseContext(deviceManager.GraphicsDevice); 34 | deviceManager.ApplyChanges(); 35 | this.ui.Width = width; 36 | this.ui.Height = height; 37 | engine.Renderer.ResetNativeSize(); 38 | } 39 | 40 | /// 41 | /// Take Screen, save image to stream, and return items info on the entire map. 42 | /// 43 | /// Stream to save image 44 | public ScreenShotData TakeScreenShot(Stream stream) 45 | { 46 | _screenShotStream = stream; 47 | while (_screenShotStream != null) ; //Wait next Draw(), yield to GetScreenShotMapData() 48 | var ret = _screenShotData; 49 | _screenShotData = null; 50 | return ret; 51 | } 52 | 53 | /// 54 | /// Switch to a new map 55 | /// 56 | /// Wz img id 57 | public void SwitchToNewMap(int imgId) 58 | { 59 | MoveToPortal(imgId, null); 60 | WaitSceneLoading(); 61 | } 62 | 63 | public void WaitSceneLoading() 64 | { 65 | SpinWait spinWait = new SpinWait(); 66 | // Wait until new map loaded 67 | while (!SceneRunning) 68 | { 69 | spinWait.SpinOnce(); 70 | } 71 | } 72 | 73 | protected override void Draw(GameTime gameTime) 74 | { 75 | base.Draw(gameTime); 76 | if (_screenShotStream != null) 77 | { 78 | ScreenShotHelper(_screenShotStream, gameTime); 79 | _screenShotData = new ScreenShotData(new List(), this.renderEnv.Camera.ClipRect); 80 | GetScreenShotMapData(mapData.Scene, ref _screenShotData); 81 | _screenShotStream = null; 82 | } 83 | } 84 | 85 | private void ScreenShotHelper(Stream destination, GameTime gameTime) 86 | { 87 | int width = GraphicsDevice.PresentationParameters.BackBufferWidth; 88 | int height = GraphicsDevice.PresentationParameters.BackBufferHeight; 89 | using RenderTarget2D target = new RenderTarget2D(GraphicsDevice, width, height, false, 90 | SurfaceFormat.Rgba64, DepthFormat.None); 91 | var oldTarget = GraphicsDevice.GetRenderTargets(); 92 | GraphicsDevice.SetRenderTarget(target); 93 | GraphicsDevice.Clear(Color.Black); 94 | DrawScene(gameTime); 95 | DrawTooltipItems(gameTime); 96 | this.ui.Draw(gameTime.ElapsedGameTime.TotalMilliseconds); 97 | this.tooltip.Draw(gameTime, renderEnv); 98 | GraphicsDevice.SetRenderTargets(oldTarget); 99 | target.SaveAsPng(destination, width, height); 100 | 101 | } 102 | 103 | private void GetScreenShotMapData(SceneNode node, ref ScreenShotData screenShotData) 104 | { 105 | var itemsOnMap = screenShotData.Items; 106 | if (node is ContainerNode container) 107 | { 108 | foreach (var item in container.Slots) 109 | { 110 | if (item is LifeItem life) 111 | { 112 | var rectangle = this.GetLifeBoundingBox(life); 113 | if (rectangle.HasValue) 114 | { 115 | itemsOnMap.Add(new TargetItem(life, rectangle.Value, RenderObjectType.Mob) { Id = life.ID }); 116 | } 117 | } 118 | } 119 | } 120 | else 121 | { 122 | for (int i = 0, total = node.Nodes.Count; i < total; ++i) 123 | { 124 | GetScreenShotMapData(node.Nodes[i], ref screenShotData); 125 | } 126 | } 127 | } 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /MapRender.Invoker/MapRenderInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using MapleStory.Common; 11 | using Microsoft.Xna.Framework; 12 | using WzComparerR2.Common; 13 | using WzComparerR2.MapRender; 14 | using WzComparerR2.PluginBase; 15 | using WzComparerR2.WzLib; 16 | 17 | namespace MapRender.Invoker 18 | { 19 | public class MapRenderInvoker : MapRenderInvokerBase 20 | { 21 | private Wz_Image _currentMapImage; 22 | private StringLinker _stringLinker; 23 | private Thread _renderThread; 24 | private MapRender _mapRender; 25 | private Camera _camera; 26 | private volatile bool _isRunning; 27 | 28 | public bool IsRunning => _isRunning; 29 | 30 | public int ScreenWidth { private set; get; } 31 | 32 | public int ScreenHeight { private set; get; } 33 | 34 | public int WorldWidth => _camera.WorldRect.Width; 35 | 36 | public int WorldHeight => _camera.WorldRect.Height; 37 | 38 | public int WorldOriginX => _camera.WorldRect.X; 39 | 40 | public int WorldOriginY => _camera.WorldRect.Y; 41 | 42 | public int CurrentCameraX => (int)_camera.Center.X; 43 | 44 | public int CurrentCameraY => (int)_camera.Center.Y; 45 | 46 | public int CurrentMap { get; private set; } 47 | 48 | public MapRenderInvoker(string mapleStoryPath, Encoding encoding, bool disableImgCheck = false) 49 | : base(mapleStoryPath, encoding, disableImgCheck) 50 | { 51 | _isRunning = false; 52 | AddFindWzEventHandler(); 53 | } 54 | 55 | /// 56 | /// Attach event handler to PlugManager, let it throw if reflection fail so we know there are changes in WzComparerR2 57 | /// 58 | /// 59 | private void AddFindWzEventHandler() 60 | { 61 | EventInfo findWzEvent = typeof(PluginManager) 62 | .GetEvent("WzFileFinding", BindingFlags.Static | BindingFlags.NonPublic); 63 | MethodInfo findWzHandler = 64 | typeof(MapRenderInvoker).GetMethod("CharaSimLoader_WzFileFinding", BindingFlags.NonPublic | BindingFlags.Instance); 65 | Delegate findWzDelegate = Delegate.CreateDelegate(findWzEvent.EventHandlerType, this, findWzHandler); 66 | findWzEvent.AddMethod.Invoke(this, new[] { findWzDelegate }); 67 | } 68 | 69 | ~MapRenderInvoker() 70 | { 71 | _renderThread?.Abort(); 72 | } 73 | 74 | /// 75 | public override void LoadMap(string imgText) 76 | { 77 | CurrentMap = int.Parse(imgText); 78 | imgText = imgText.EndsWith(".img") ? imgText : (imgText + ".img"); 79 | _currentMapImage = WzTreeSearcher.SearchForMap(_wzStructure.WzNode, imgText); 80 | Exception ex; 81 | _currentMapImage.TryExtract(out ex); 82 | if (ex != null) 83 | { 84 | throw ex; 85 | } 86 | _stringLinker = new StringLinker(); 87 | _stringLinker.Load(PluginManager.FindWz(Wz_Type.String).GetValueEx(null)); 88 | } 89 | 90 | /// 91 | public override void Launch(int width, int height) 92 | { 93 | if (_currentMapImage == null) 94 | { 95 | throw new InvalidOperationException("MapRenderInvoker.LoadMap() must be called before Launch()."); 96 | } 97 | 98 | _isRunning = false; 99 | _renderThread = new Thread(() => 100 | { 101 | _mapRender = new MapRender(_currentMapImage) { StringLinker = _stringLinker }; 102 | _mapRender.Window.Title = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileName; 103 | try 104 | { 105 | using (_mapRender) 106 | { 107 | _mapRender.RunOneFrame(); // Initialize 108 | _mapRender.ChangeResolution(width, height); 109 | _camera = _mapRender.renderEnv.Camera; 110 | _mapRender.Run(); 111 | } 112 | } 113 | finally 114 | { 115 | _mapRender = null; 116 | } 117 | }); 118 | ScreenHeight = height; 119 | ScreenWidth = width; 120 | _renderThread.SetApartmentState(ApartmentState.STA); 121 | _renderThread.IsBackground = true; 122 | _renderThread.Start(); 123 | SpinWait spinWait = new SpinWait(); 124 | while (_mapRender == null) 125 | { 126 | spinWait.SpinOnce(); 127 | } 128 | _mapRender.WaitSceneLoading(); 129 | _isRunning = true; 130 | } 131 | 132 | public override void SwitchMap(string imgText) 133 | { 134 | CurrentMap = int.Parse(imgText); 135 | _mapRender.SwitchToNewMap(CurrentMap); 136 | } 137 | 138 | public void MoveCamera(int centerX, int centerY) 139 | { 140 | _camera.Center = new Vector2(centerX, centerY); 141 | _camera.AdjustToWorldRect(); 142 | } 143 | 144 | public ScreenShotData TakeScreenShot(Stream stream) 145 | { 146 | return _mapRender.TakeScreenShot(stream); 147 | } 148 | 149 | #region COPIED_CODE 150 | 151 | /// 152 | /// !!!!!!!!!!!!COPIED CODE!!!!!!!!!!!!!! 153 | /// Version: git@github.com:Kagamia/WzComparerR2.git:f6ecfb18cae661f125a189e527feea1964f5bda8 154 | /// 155 | /// 156 | private void CharaSimLoader_WzFileFinding(object sender, WzComparerR2.FindWzEventArgs e) 157 | { 158 | string[] fullPath = null; 159 | if (!string.IsNullOrEmpty(e.FullPath)) //用fullpath作为输入参数 160 | { 161 | fullPath = e.FullPath.Split('/', '\\'); 162 | try 163 | { 164 | e.WzType = (Wz_Type)Enum.Parse(typeof(Wz_Type), fullPath[0], true); 165 | } 166 | catch 167 | { 168 | e.WzType = Wz_Type.Unknown; 169 | } 170 | } 171 | 172 | List preSearch = new List(); 173 | if (e.WzType != Wz_Type.Unknown) //用wztype作为输入参数 174 | { 175 | IEnumerable preSearchWz = e.WzFile?.WzStructure != null ? 176 | Enumerable.Repeat(e.WzFile.WzStructure, 1) : new List() { _wzStructure }; 177 | foreach (var wzs in preSearchWz) 178 | { 179 | Wz_File baseWz = null; 180 | bool find = false; 181 | foreach (Wz_File wz_f in wzs.wz_files) 182 | { 183 | if (wz_f.Type == e.WzType) 184 | { 185 | preSearch.Add(wz_f.Node); 186 | find = true; 187 | //e.WzFile = wz_f; 188 | } 189 | if (wz_f.Type == Wz_Type.Base) 190 | { 191 | baseWz = wz_f; 192 | } 193 | } 194 | 195 | // detect data.wz 196 | if (baseWz != null && !find) 197 | { 198 | string key = e.WzType.ToString(); 199 | foreach (Wz_Node node in baseWz.Node.Nodes) 200 | { 201 | if (node.Text == key && node.Nodes.Count > 0) 202 | { 203 | preSearch.Add(node); 204 | } 205 | } 206 | } 207 | } 208 | } 209 | 210 | if (fullPath == null || fullPath.Length <= 1) 211 | { 212 | if (e.WzType != Wz_Type.Unknown && preSearch.Count > 0) //返回wzFile 213 | { 214 | e.WzNode = preSearch[0]; 215 | e.WzFile = preSearch[0].Value as Wz_File; 216 | } 217 | return; 218 | } 219 | 220 | if (preSearch.Count <= 0) 221 | { 222 | return; 223 | } 224 | 225 | foreach (var wzFileNode in preSearch) 226 | { 227 | var searchNode = wzFileNode; 228 | for (int i = 1; i < fullPath.Length && searchNode != null; i++) 229 | { 230 | searchNode = searchNode.Nodes[fullPath[i]]; 231 | var img = searchNode.GetValueEx(null); 232 | if (img != null) 233 | { 234 | searchNode = img.TryExtract() ? img.Node : null; 235 | } 236 | } 237 | 238 | if (searchNode != null) 239 | { 240 | e.WzNode = searchNode; 241 | e.WzFile = wzFileNode.Value as Wz_File; 242 | return; 243 | } 244 | } 245 | //寻找失败 246 | e.WzNode = null; 247 | } 248 | #endregion 249 | } 250 | 251 | public abstract class MapRenderInvokerBase 252 | { 253 | 254 | protected readonly Wz_Structure _wzStructure; 255 | 256 | /// 257 | /// Provide base methods to open MapleStory data directory. 258 | /// For maintenance with upstream WzR2, check 259 | /// 260 | /// 261 | /// 262 | /// 263 | /// 264 | protected MapRenderInvokerBase(string mapleStoryPath, Encoding encoding, bool disableImgCheck = false) 265 | { 266 | // Static settings for Wz_Structure :( 267 | Wz_Structure.DefaultAutoDetectExtFiles = true; 268 | Wz_Structure.DefaultEncoding = encoding; 269 | Wz_Structure.DefaultImgCheckDisabled = disableImgCheck; 270 | Wz_Structure.DefaultWzVersionVerifyMode = WzVersionVerifyMode.Fast; 271 | // Then our constructor 272 | string baseWzPath = Path.Combine(mapleStoryPath, MapleStoryPathHelper.MapleStoryBaseWzName); 273 | if (!File.Exists(baseWzPath)) 274 | { 275 | throw new ArgumentException($"Cannot find {MapleStoryPathHelper.MapleStoryBaseWzName} in given directory {mapleStoryPath}."); 276 | } 277 | // See WzComparerR2.MainForm.openWz() 278 | _wzStructure = new Wz_Structure(); 279 | if (string.Equals(Path.GetExtension(baseWzPath), ".ms", StringComparison.OrdinalIgnoreCase)) 280 | { 281 | _wzStructure.LoadMsFile(baseWzPath); 282 | } 283 | else if (_wzStructure.IsKMST1125WzFormat(baseWzPath)) 284 | { 285 | _wzStructure.LoadKMST1125DataWz(baseWzPath); 286 | if (string.Equals(Path.GetFileName(baseWzPath), "Base.wz", StringComparison.OrdinalIgnoreCase)) 287 | { 288 | string packsDir = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(baseWzPath)), "Packs"); 289 | if (Directory.Exists(packsDir)) 290 | { 291 | foreach (var msFile in Directory.GetFiles(packsDir, "*.ms")) 292 | { 293 | _wzStructure.LoadMsFile(msFile); 294 | } 295 | } 296 | } 297 | } 298 | else 299 | { 300 | _wzStructure.Load(baseWzPath, true); 301 | } 302 | } 303 | 304 | /// 305 | /// Load specified Wz map img to this invoker. 306 | /// 307 | /// The map node search. E.g. "450007010" 308 | /// If supplied img cannot be found. 309 | public abstract void LoadMap(string imgText); 310 | 311 | /// 312 | /// Lunch map render. Make sure we have loaded img. 313 | /// 314 | /// Width of resolution 315 | /// Height of resolution 316 | public abstract void Launch(int width, int height); 317 | 318 | /// 319 | /// Switch to a new map after 320 | /// 321 | /// The map node search. E.g. "450007010" 322 | public abstract void SwitchMap(string imgText); 323 | 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /MapRender.Invoker/ObjectClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MapRender.Invoker 8 | { 9 | public enum ObjectClass 10 | { 11 | Mob = 1, 12 | Player, 13 | Npc, 14 | InMapPortal, 15 | CrossMapPortal, 16 | Foothold, 17 | LadderRope, 18 | Unknown 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MapRender.Invoker/ScreenShotData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MapRender.Invoker 9 | { 10 | public class ScreenShotData 11 | { 12 | public List Items { get; private set; } 13 | 14 | public Rectangle CameraRectangle { get; private set; } 15 | 16 | public ScreenShotData(List items, Microsoft.Xna.Framework.Rectangle camRectangle) 17 | { 18 | Items = items; 19 | CameraRectangle = new Rectangle(camRectangle.X, camRectangle.Y, camRectangle.Width, camRectangle.Height); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MapRender.Invoker/TargetItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | using WzComparerR2.MapRender.Patches; 8 | using WzComparerR2.MapRender.Patches2; 9 | 10 | namespace MapRender.Invoker 11 | { 12 | //Object detection interests item 13 | public class TargetItem : SceneItem 14 | { 15 | public int Id { get; set; } 16 | 17 | public int X { get; set; } 18 | 19 | public int Y { get; set; } 20 | 21 | public ObjectClass Type { get; set; } 22 | 23 | public int Width { get; set; } 24 | 25 | public int Height { get; set; } 26 | 27 | public TargetItem() { } 28 | 29 | public TargetItem(SceneItem item) 30 | { 31 | Name = item.Name; 32 | Index = item.Index; 33 | Tags = item.Tags; 34 | } 35 | 36 | public TargetItem(SceneItem item, Rectangle rectangle) : this(item) 37 | { 38 | X = rectangle.X; 39 | Y = rectangle.Y; 40 | Width = rectangle.Width; 41 | Height = rectangle.Height; 42 | } 43 | 44 | public TargetItem(SceneItem item, Rectangle rectangle, RenderObjectType type) : this(item, rectangle) 45 | { 46 | Type = RenderTypeToObjectClass(type); 47 | } 48 | 49 | public static ObjectClass RenderTypeToObjectClass(RenderObjectType type) 50 | { 51 | switch (type) 52 | { 53 | case RenderObjectType.Mob: 54 | return ObjectClass.Mob; 55 | case RenderObjectType.Foothold: 56 | return ObjectClass.Foothold; 57 | case RenderObjectType.Npc: 58 | return ObjectClass.Npc; 59 | case RenderObjectType.LadderRope: 60 | return ObjectClass.LadderRope; 61 | case RenderObjectType.Portal: 62 | return ObjectClass.CrossMapPortal; //TODO: Classify portal 63 | } 64 | return ObjectClass.Unknown; 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MapleStory.Common/Exceptions/WzImgNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MapleStory.Common.Exceptions 9 | { 10 | /// 11 | /// Simple exception class indicating specified Wz Img cannot be found. 12 | /// 13 | public class WzImgNotFoundException : Exception 14 | { 15 | public WzImgNotFoundException() 16 | { 17 | } 18 | 19 | public WzImgNotFoundException(string message) : base(message) 20 | { 21 | } 22 | 23 | public WzImgNotFoundException(string message, Exception innerException) : base(message, innerException) 24 | { 25 | } 26 | 27 | protected WzImgNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) 28 | { 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /MapleStory.Common/MapleStory.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows7.0 4 | x64 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /MapleStory.Common/MapleStoryPathHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Win32; 8 | 9 | namespace MapleStory.Common 10 | { 11 | /// 12 | /// Helper class for finding MapleStory's path. 13 | /// 14 | public static class MapleStoryPathHelper 15 | { 16 | private static readonly string REG_KEY_PATH_CMS = 17 | @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\MapleStory"; // Tested for CMS 18 | private static readonly string REG_VALUE_NAME_CMS = @"UninstallString"; 19 | 20 | private static readonly string REG_KEY_PATH_GMS = @"SOFTWARE\WOW6432Node\Wizet\MapleStory"; 21 | private static readonly string REG_VALUE_NAME_GMS = @"ExecPath"; 22 | 23 | public static readonly string MapleStoryExecutableName = @"MapleStory.exe"; 24 | 25 | public static readonly string MapleStoryBaseWzName = @"Data\Base\Base.wz"; 26 | 27 | private static readonly Lazy _mapleStoryInstallDirectory = new Lazy(() => 28 | { 29 | // For CMS 30 | try 31 | { 32 | RegistryKey key = Registry.LocalMachine.OpenSubKey(REG_KEY_PATH_CMS); // Possible null 33 | string uninstallPath = key.GetValue(REG_VALUE_NAME_CMS) as string; // Possible invalid cast 34 | return Path.GetDirectoryName(uninstallPath); // Possible corrupted path 35 | } 36 | catch (Exception ex) when (ex is NullReferenceException || ex is InvalidCastException || ex is ArgumentException) 37 | { 38 | // ignored, not found 39 | } 40 | 41 | // For GMS 42 | try 43 | { 44 | RegistryKey key = Registry.LocalMachine.OpenSubKey(REG_KEY_PATH_GMS); // Possible null 45 | string execPath = key.GetValue(REG_VALUE_NAME_GMS) as string; // Possible invalid cast 46 | return execPath; 47 | } 48 | catch (Exception ex) when (ex is NullReferenceException || ex is InvalidCastException) 49 | { 50 | // ignored, not found 51 | } 52 | return string.Empty; 53 | }); 54 | 55 | /// 56 | /// Indicate if MapleStory installed location was found. 57 | /// 58 | public static bool FoundMapleStoryInstalled => Directory.Exists(_mapleStoryInstallDirectory.Value); 59 | 60 | /// 61 | /// Get MapleStory installed folder based on registry, might fail if no elevate privilege, in which case it will return empty string. 62 | /// 63 | public static string MapleStoryInstallDirectory => _mapleStoryInstallDirectory.Value; 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /MapleStory.Common/WzTreeSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using WzComparerR2.WzLib; 7 | using MapleStory.Common.Exceptions; 8 | 9 | namespace MapleStory.Common 10 | { 11 | public static class WzTreeSearcher 12 | { 13 | /// 14 | /// Generic searching based on BFS traversal. This is tree-search (only search for children). 15 | /// 16 | /// Root of target tree. The origin of search. 17 | /// Return true if the input node meet search criteria. 18 | /// Null if not found, otherwise the target node. 19 | public static Wz_Node GenericBfsSearcher(Wz_Node root, Func determinator) 20 | { 21 | Queue queue = new Queue(); 22 | queue.Enqueue(root); 23 | while (queue.Count != 0) 24 | { 25 | Wz_Node currNode = queue.Dequeue(); 26 | if (determinator(currNode)) 27 | { 28 | return currNode; 29 | } 30 | foreach (var child in currNode.Nodes) 31 | { 32 | queue.Enqueue(child); 33 | } 34 | } 35 | return null; 36 | } 37 | 38 | /// 39 | /// Search for specified map Img starting from Base.wz root. 40 | /// 41 | /// The root of Base.wz. 42 | /// The node text to search. E.g. "450007010.img" 43 | /// The Wz img containing desired map. 44 | /// If not found. 45 | public static Wz_Image SearchForMap(Wz_Node root, string imgText) 46 | { 47 | if (!imgText.Contains(".img")) 48 | { 49 | throw new ArgumentException("Supplied imgText is not legal.", nameof(imgText)); 50 | } 51 | // Filter map nodes, and find string wz file. 52 | IEnumerable mapNodes = root.Nodes 53 | .Where(n => n.GetNodeWzFile().Type == Wz_Type.Map); 54 | // Do search on each map node 55 | foreach (var mapRoot in mapNodes) 56 | { 57 | Wz_Node result = GenericBfsSearcher(mapRoot, (node) => node.Text == imgText); 58 | if (result != null) 59 | { 60 | return result.GetNodeWzImage(); 61 | } 62 | } 63 | // Throw if not found. 64 | throw new WzImgNotFoundException(string.Format("Target Img {0} cannot be found.", imgText)); 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MapleStory.MachineLearningSampleGenerator/MapleStory.MachineLearningSampleGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows7.0 4 | true 5 | x64 6 | Exe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /MapleStory.MachineLearningSampleGenerator/OutputFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MapleStory.MachineLearningSampleGenerator 8 | { 9 | internal enum OutputFormat 10 | { 11 | TfRecord, 12 | Darknet, 13 | Coco 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MapleStory.MachineLearningSampleGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using CommandLine; 12 | using CommandLine.Text; 13 | using MapleStory.Common; 14 | using MapleStory.Sampler; 15 | using MapleStory.Sampler.PostProcessor; 16 | using MapRender.Invoker; 17 | 18 | namespace MapleStory.MachineLearningSampleGenerator 19 | { 20 | internal static class Program 21 | { 22 | [DllImport("kernel32.dll")] 23 | static extern bool SetDllDirectory(string path); 24 | 25 | [DllImport("kernel32")] 26 | static extern bool AllocConsole(); 27 | 28 | [DllImport("kernel32.dll")] 29 | static extern bool AttachConsole(int dwProcessId); 30 | 31 | [DllImport("kernel32.dll")] 32 | static extern bool FreeConsole(); 33 | 34 | [DllImport("kernel32.dll")] 35 | static extern IntPtr GetConsoleWindow(); 36 | 37 | static Program() 38 | { 39 | AttachConsole(-1); // Try to attach to parent process's console 40 | if (GetConsoleWindow() == IntPtr.Zero) 41 | { 42 | AllocConsole(); 43 | } 44 | 45 | Console.OutputEncoding = Encoding.UTF8; // Correctly show non-English characters 46 | string libPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Lib", 47 | Environment.Is64BitProcess ? "x64" : "x86"); 48 | SetDllDirectory(libPath); // Add dll search path for WzComparerR2 49 | } 50 | 51 | private class Options 52 | { 53 | [Option('m', "map", Required = true, HelpText = "Space-separated Wz image ID of map(s) used for generating TFRecord.")] 54 | public IEnumerable Maps { get; set; } 55 | 56 | [Option('x', "xStep", Required = true, HelpText = "Step in X.")] 57 | public int StepX { get; set; } 58 | 59 | [Option('y', "yStep", Required = true, HelpText = "Step in Y.")] 60 | public int StepY { get; set; } 61 | 62 | [Option('f', "format", Required = true, HelpText = "Output format, must be one of: [tfrecord, darknet, coco]")] 63 | public string Format { get; set; } 64 | 65 | [Option('o', "output", Required = false, Default = ".", HelpText = "Data set output location")] 66 | public string OutputPath { get; set; } 67 | 68 | [Option('w', "width", Required = false, Default = 1366, HelpText = "Width of sample image.")] 69 | public int RenderWidth { get; set; } 70 | 71 | [Option('h', "height", Required = false, Default = 768, HelpText = "Height of sample image.")] 72 | public int RenderHeight { get; set; } 73 | 74 | [Option('i', "interval", Required = false, Default = 0, HelpText = "Time interval in ms between each sample")] 75 | public int SampleInterval { get; set; } 76 | 77 | [Option('p', "path", Required = false, Default = "", HelpText = "MapleStory Installed Path")] 78 | public string MapleStoryPath { get; set; } 79 | 80 | [Option("post", Required = false, Default = false, HelpText = "Indicate whether to enable post-processing.")] 81 | public bool PostProcessingEnable { get; set; } 82 | 83 | [Option("players", Required = false, Default = "", HelpText = "Directory where the post-processing player images stored.")] 84 | public string PlayerImageDirectory { get; set; } 85 | 86 | [Option('e', "encoding", Required = false, HelpText = "Encoding used to decode Wz strings. Using system default if not specified.")] 87 | public string Encoding { get; set; } = ""; 88 | } 89 | 90 | [STAThread] 91 | private static int Main(string[] args) 92 | { 93 | int ret = CommandLine.Parser.Default.ParseArguments(args).MapResult(RunAndReturn, OnParseError); 94 | Console.WriteLine("MapleStoryDetectionSampleGenerator exited with code= {0}", ret); 95 | FreeConsole(); 96 | return ret; 97 | } 98 | 99 | private static int OnParseError(IEnumerable errors) 100 | { 101 | foreach (var error in errors) 102 | { 103 | Console.Error.WriteLine(Enum.GetName(typeof(ErrorType), error.Tag)); 104 | } 105 | return -1; 106 | } 107 | 108 | /// 109 | /// Main logic here 110 | /// 111 | private static int RunAndReturn(Options options) 112 | { 113 | // Print program info 114 | Console.WriteLine(HeadingInfo.Default); 115 | Console.WriteLine(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).LegalCopyright); 116 | // Check arguments 117 | PreRunTest(options); 118 | Console.WriteLine("MapleStory Location: {0}", options.MapleStoryPath); 119 | // Initialize render 120 | MapRenderInvoker renderInvoker = new MapRenderInvoker(options.MapleStoryPath, 121 | options.Encoding == string.Empty ? Encoding.Default : Encoding.GetEncoding(options.Encoding), 122 | false); 123 | Queue maps = new Queue(options.Maps); 124 | var first = maps.Dequeue(); 125 | renderInvoker.LoadMap(first); 126 | renderInvoker.Launch(options.RenderWidth, options.RenderHeight); 127 | // Initialize sampler 128 | IDatasetWriter writer = GetDatasetWriter(options, first); 129 | Sampler.Sampler sampler = new Sampler.Sampler(renderInvoker); 130 | if (options.PostProcessingEnable && options.PlayerImageDirectory != "") 131 | { 132 | IPostProcessor postProcessor = new PlayerProcessor(options.PlayerImageDirectory); 133 | sampler.OnSampleCaptured += (s, e) => postProcessor.Process(s); 134 | } 135 | 136 | while (true) 137 | { 138 | sampler.SampleAll(options.StepX, options.StepY, writer, options.SampleInterval); 139 | if (maps.Count == 0) 140 | { 141 | break; 142 | } 143 | renderInvoker.SwitchMap(maps.Dequeue()); 144 | } 145 | writer.Finish(); 146 | return 0; 147 | } 148 | 149 | 150 | /// 151 | /// Throw if condition not meet. 152 | /// 153 | private static void PreRunTest(Options options) 154 | { 155 | // Check resolution 156 | if (options.RenderHeight <= 0) 157 | { 158 | throw new ArgumentException("Render size cannot exceed screen size. Height illegal.", 159 | nameof(options.RenderHeight)); 160 | } 161 | if (options.RenderWidth <= 0) 162 | { 163 | throw new ArgumentException("Render size cannot exceed screen size. Width illegal.", 164 | nameof(options.RenderWidth)); 165 | } 166 | 167 | // Check format 168 | Enum.Parse(typeof(OutputFormat), options.Format, true); 169 | 170 | // Check file path 171 | if (options.MapleStoryPath == string.Empty) 172 | { 173 | if (MapleStoryPathHelper.FoundMapleStoryInstalled) 174 | { 175 | options.MapleStoryPath = MapleStoryPathHelper.MapleStoryInstallDirectory; 176 | } 177 | else 178 | { 179 | throw new ArgumentException("Cannot find MapleStory installed location. Please specify it in commandline or retry as Administrator."); 180 | } 181 | } 182 | else if (!Directory.Exists(options.MapleStoryPath)) 183 | { 184 | throw new ArgumentException("Supplied MapleStory directory does not exist."); 185 | } 186 | 187 | // Check map id format 188 | foreach (var map in options.Maps) 189 | { 190 | string id = map.Replace(".img", string.Empty); 191 | if (!id.All(char.IsDigit)) 192 | { 193 | throw new ArgumentException("Supplied Map Id is not in correct format." + 194 | " --map parameter should be space-separated list of IDs. " + 195 | "E.g. --map 450007010 450007060"); 196 | } 197 | } 198 | 199 | // Check encoding 200 | if ((options.Encoding != string.Empty) && 201 | Encoding.GetEncodings() 202 | .Any(e => e.Name.Equals(options.Encoding, StringComparison.OrdinalIgnoreCase))) 203 | { 204 | throw new ArgumentException($"{options.Encoding} is not an available Encoding in your system."); 205 | } 206 | 207 | // Check output path 208 | if (!Directory.Exists(options.OutputPath)) 209 | { 210 | throw new ArgumentException($"OutputPath {options.OutputPath} does not exist."); 211 | } 212 | 213 | // Check interval 214 | if (options.SampleInterval < 0) 215 | { 216 | throw new ArgumentException("SampleInterval cannot be negative!"); 217 | } 218 | 219 | // Check for post-processing 220 | if (options.PostProcessingEnable) 221 | { 222 | if (options.PlayerImageDirectory != "") 223 | { 224 | if (!Directory.Exists(options.PlayerImageDirectory)) 225 | { 226 | throw new ArgumentException($"PlayerImageDirectory {options.PlayerImageDirectory} cannot be found!"); 227 | } 228 | } 229 | } 230 | } 231 | 232 | private static IDatasetWriter GetDatasetWriter(Options options, string map) 233 | { 234 | switch (Enum.Parse(typeof(OutputFormat), options.Format, true)) 235 | { 236 | case OutputFormat.TfRecord: 237 | return new TfRecordWriter(Path.Combine(options.OutputPath, map)); 238 | case OutputFormat.Darknet: 239 | return new DarknetWriter(options.OutputPath); 240 | case OutputFormat.Coco: 241 | return new CocoWriter(options.OutputPath, $"MapleStory {map}.img Object Detection Samples"); 242 | default: 243 | throw new ArgumentOutOfRangeException(nameof(options), options, null); 244 | } 245 | } 246 | 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /MapleStory.Sampler/CocoWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using System.Threading.Tasks; 9 | using MapRender.Invoker; 10 | 11 | namespace MapleStory.Sampler 12 | { 13 | public class CocoWriter : IDatasetWriter 14 | { 15 | private const double DefaultTestingPortion = 0.05; 16 | private const string DefaultRootDirectory = "coco"; 17 | private const string TrainingDirectory = "train2017"; 18 | private const string ValidationDirectory = "val2017"; 19 | private const string AnnotationDirectory = "annotations"; 20 | private const string TrainingJson = "instances_train2017.json"; 21 | private const string ValidationJson = "instances_val2017.json"; 22 | 23 | public string RootPath { get; } 24 | 25 | public string AnnotationsPath { get; } 26 | 27 | private readonly string _trainingImagesPath; 28 | private readonly string _validationImagesPath; 29 | private readonly Random _random; 30 | private readonly List _occurrenceClasses; 31 | private readonly string _datasetName; 32 | private readonly List _trainingImages; 33 | private readonly List _validationImages; 34 | private readonly List _categories; 35 | private readonly List _trainingAnnotations; 36 | private readonly List _validationAnnotations; 37 | 38 | public CocoWriter(string path, string name) 39 | { 40 | if (!Directory.Exists(path)) 41 | { 42 | Console.WriteLine($"Target path {path} does not exist. Creating one..."); 43 | Directory.CreateDirectory(path); 44 | } 45 | RootPath = Path.Combine(path, DefaultRootDirectory); 46 | if (!Directory.Exists(RootPath)) 47 | { 48 | Directory.CreateDirectory(RootPath); 49 | } 50 | CleanDirectory(RootPath); 51 | _trainingImagesPath = Path.Combine(RootPath, TrainingDirectory); 52 | Directory.CreateDirectory(_trainingImagesPath); 53 | _validationImagesPath = Path.Combine(RootPath, ValidationDirectory); 54 | Directory.CreateDirectory(_validationImagesPath); 55 | AnnotationsPath = Path.Combine(RootPath, AnnotationDirectory); 56 | Directory.CreateDirectory(AnnotationsPath); 57 | 58 | _random = new Random(); 59 | _occurrenceClasses = new List(); 60 | _datasetName = name; 61 | _trainingImages = new List(); 62 | _validationImages = new List(); 63 | _validationAnnotations = new List(); 64 | _categories = new List(); 65 | _trainingAnnotations = new List(); 66 | _validationAnnotations = new List(); 67 | } 68 | 69 | public void Write(Sample sample) 70 | { 71 | double rand = _random.NextDouble(); 72 | List annotationsToAdd; 73 | List imagesToAdd; 74 | string pathToWrite; 75 | if (rand < DefaultTestingPortion) 76 | { 77 | annotationsToAdd = _validationAnnotations; 78 | imagesToAdd = _validationImages; 79 | pathToWrite = _validationImagesPath; 80 | } 81 | else 82 | { 83 | annotationsToAdd = _trainingAnnotations; 84 | imagesToAdd = _trainingImages; 85 | pathToWrite = _trainingImagesPath; 86 | } 87 | // Write image 88 | string jpgFileName = sample.Guid + ".jpg"; 89 | using FileStream imageStream = new FileStream(Path.Combine(pathToWrite, jpgFileName), FileMode.CreateNew); 90 | sample.ImageStream.WriteTo(imageStream); 91 | imageStream.Flush(); 92 | CocoImage cocoImage = new CocoImage(jpgFileName, sample.Width, sample.Height); 93 | imagesToAdd.Add(cocoImage); 94 | 95 | // Write annotations 96 | GetAnnotationFromSample(sample, cocoImage.Id, ref annotationsToAdd); 97 | } 98 | 99 | public void Finish() 100 | { 101 | CocoLicense license = new CocoLicense(); 102 | // Write training set 103 | CocoJson trainingJson = new CocoJson(new CocoInfo($"{_datasetName} - Training"), 104 | license, 105 | _trainingImages, 106 | _categories, 107 | _trainingAnnotations); 108 | using FileStream trainingStream = 109 | new FileStream(Path.Combine(AnnotationsPath, TrainingJson), FileMode.CreateNew); 110 | using Utf8JsonWriter trainingWriter = new Utf8JsonWriter(trainingStream); 111 | JsonSerializer.Serialize(trainingWriter, trainingJson); 112 | 113 | // Write validation set 114 | CocoJson validationJson = new CocoJson(new CocoInfo($"{_datasetName} - Training"), 115 | license, 116 | _validationImages, 117 | _categories, 118 | _validationAnnotations); 119 | using FileStream validationStream = 120 | new FileStream(Path.Combine(AnnotationsPath, ValidationJson), FileMode.CreateNew); 121 | using Utf8JsonWriter validationWriter = new Utf8JsonWriter(validationStream); 122 | JsonSerializer.Serialize(validationWriter, validationJson); 123 | 124 | // Flush buffer (if any) 125 | trainingWriter.Flush(); 126 | trainingStream.Flush(); 127 | validationWriter.Flush(); 128 | validationStream.Flush(); 129 | } 130 | 131 | private void GetAnnotationFromSample(Sample sample, int imageId, ref List dstList) 132 | { 133 | foreach (var sampleItem in sample.Items) 134 | { 135 | ObjectClass type = sampleItem.Type; 136 | if (!_occurrenceClasses.Contains(type)) 137 | { 138 | _occurrenceClasses.Add(type); 139 | _categories.Add(new CocoCategory("element", type.ToString())); 140 | } 141 | int categoryId = _occurrenceClasses.IndexOf(type) + 1; 142 | CocoAnnotation toAdd = new CocoAnnotation(imageId, sampleItem.X, sampleItem.Y, sampleItem.Width, 143 | sampleItem.Height, categoryId); 144 | dstList.Add(toAdd); 145 | } 146 | } 147 | 148 | private static void CleanDirectory(string path) 149 | { 150 | if (Directory.EnumerateFileSystemEntries(path).Any()) 151 | { 152 | Console.WriteLine("Warning: Target RootPath Is Not Empty. Cleaning target path..."); 153 | DirectoryInfo directory = new DirectoryInfo(path); 154 | foreach (var fileInfo in directory.GetFiles()) 155 | { 156 | fileInfo.Delete(); 157 | } 158 | foreach (var directoryInfo in directory.GetDirectories()) 159 | { 160 | directoryInfo.Delete(true); 161 | } 162 | } 163 | } 164 | 165 | #region JsonDefinitions 166 | private class CocoInfo 167 | { 168 | [JsonPropertyName("description")] 169 | public string Description { get; } 170 | 171 | [JsonPropertyName("url")] 172 | public string Url { get; } = @"https://github.com/charlescao460/MapleStoryDetectionSampleGenerator"; 173 | 174 | [JsonPropertyName("version")] 175 | public string Version { get; } = "1.0"; 176 | 177 | [JsonPropertyName("year")] 178 | public int Year { get; } = DateTime.Today.Year; 179 | 180 | [JsonPropertyName("contributor")] 181 | public string Contributor { get; } = "CSR"; 182 | 183 | [JsonPropertyName("date_created")] 184 | public string DateCreated = DateTime.Today.ToLongDateString(); 185 | 186 | public CocoInfo(string description) 187 | { 188 | Description = description; 189 | } 190 | } 191 | 192 | private class CocoLicense 193 | { 194 | [JsonPropertyName("url")] 195 | public string Url { get; } = @"https://github.com/charlescao460/MapleStoryDetectionSampleGenerator/blob/main/LICENSE"; 196 | 197 | [JsonPropertyName("id")] 198 | public int Id { get; } = 1; 199 | 200 | [JsonPropertyName("name")] 201 | public string Name { get; } = "MIT License"; 202 | } 203 | 204 | private class CocoImage 205 | { 206 | [JsonPropertyName("license")] 207 | public int License { get; } = 1; 208 | 209 | [JsonPropertyName("file_name")] 210 | public string FileName { get; } 211 | 212 | [JsonPropertyName("coco_url")] 213 | public string CocoUrl { get; } = string.Empty; 214 | 215 | [JsonPropertyName("height")] 216 | public int Height { get; } 217 | 218 | [JsonPropertyName("width")] 219 | public int Width { get; } 220 | 221 | [JsonPropertyName("date_captured")] 222 | public string DateCaptured = $"{DateTime.Now:G}"; 223 | 224 | [JsonPropertyName("flickr_url")] 225 | public string FlickrUrl { get; } = string.Empty; 226 | 227 | [JsonPropertyName("id")] 228 | public int Id { get; } 229 | 230 | private static int _count = 1; 231 | 232 | public CocoImage(string fileName, int width, int height) 233 | { 234 | FileName = fileName; 235 | Width = width; 236 | Height = height; 237 | Id = _count++; 238 | } 239 | } 240 | 241 | private class CocoCategory 242 | { 243 | [JsonPropertyName("supercategory")] 244 | public string SuperCategory { get; } 245 | 246 | [JsonPropertyName("id")] 247 | public int Id { get; } 248 | 249 | [JsonPropertyName("name")] 250 | public string Name { get; } 251 | 252 | private static int _count = 1; 253 | 254 | public CocoCategory(string superCategory, string name) 255 | { 256 | SuperCategory = superCategory; 257 | Name = name; 258 | Id = _count++; 259 | } 260 | } 261 | 262 | private class CocoAnnotation 263 | { 264 | [JsonPropertyName("segmentation")] 265 | public float[][] Segmentation { get; } 266 | 267 | [JsonPropertyName("area")] 268 | public float Area { get; } 269 | 270 | [JsonPropertyName("iscrowd")] 271 | public int IsCrowd { get; } = 0; 272 | 273 | [JsonPropertyName("image_id")] 274 | public int ImageId { get; } 275 | 276 | [JsonPropertyName("bbox")] 277 | public float[] Bbox { get; } 278 | 279 | [JsonPropertyName("category_id")] 280 | public int CategoryId { get; } 281 | 282 | [JsonPropertyName("id")] 283 | public int Id { get; } 284 | 285 | private static int _count = 1; 286 | 287 | public CocoAnnotation(int imageId, float x, float y, float width, float height, int categoryId) 288 | { 289 | Segmentation = new float[][] 290 | { 291 | new float[] { x, y, x + width, y, x + width, y + height, x, y + height } 292 | }; 293 | Area = width * height; 294 | ImageId = imageId; 295 | Bbox = new float[] { x, y, width, height }; 296 | CategoryId = categoryId; 297 | Id = _count++; 298 | } 299 | } 300 | 301 | private class CocoJson 302 | { 303 | [JsonPropertyName("info")] 304 | public CocoInfo Info { get; } 305 | 306 | [JsonPropertyName("licenses")] 307 | public CocoLicense[] Licenses { get; } 308 | 309 | [JsonPropertyName("images")] 310 | public IList Images { get; } 311 | 312 | [JsonPropertyName("categories")] 313 | public IList Categories { get; } 314 | 315 | [JsonPropertyName("annotations")] 316 | public IList Annotations { get; } 317 | 318 | public CocoJson(CocoInfo info, CocoLicense license, IList images, IList categories, 319 | IList annotations) 320 | { 321 | Info = info; 322 | Licenses = new[] { license }; 323 | Images = images; 324 | Categories = categories; 325 | Annotations = annotations; 326 | } 327 | } 328 | 329 | #endregion 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /MapleStory.Sampler/DarknetWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using MapRender.Invoker; 8 | 9 | namespace MapleStory.Sampler 10 | { 11 | public class DarknetWriter : IDatasetWriter, IDisposable 12 | { 13 | private const double DefaultTestingPortion = 0.05; 14 | 15 | private const string DefaultRootDirectory = "data"; 16 | private const string ClassNamesFile = "obj.names"; 17 | private const string ObjectDataFile = "obj.data"; 18 | private const string TrainingDataFile = "train.txt"; 19 | private const string TestingDataFile = "test.txt"; 20 | private const string ObjDirectory = "obj"; 21 | 22 | private readonly List _occurrenceClasses; 23 | private bool _isFinished; 24 | private bool _disposed; 25 | private readonly StreamWriter _trainingDataListWriter; 26 | private readonly StreamWriter _testingDataListWriter; 27 | private readonly Random _random; 28 | 29 | public string RootPath { get; } 30 | 31 | public string ObjPath { get; } 32 | 33 | public DarknetWriter(string path) 34 | { 35 | if (!Directory.Exists(path)) 36 | { 37 | Console.WriteLine($"Target path {path} does not exist. Creating one..."); 38 | Directory.CreateDirectory(path); 39 | } 40 | RootPath = Path.Combine(path, DefaultRootDirectory); 41 | if (!Directory.Exists(RootPath)) 42 | { 43 | Directory.CreateDirectory(RootPath); 44 | } 45 | CleanDirectory(RootPath); 46 | ObjPath = Path.Combine(RootPath, ObjDirectory); 47 | Directory.CreateDirectory(ObjPath); 48 | _occurrenceClasses = new List(); 49 | _isFinished = false; 50 | _random = new Random(); 51 | _trainingDataListWriter = new StreamWriter(new FileStream(Path.Combine(RootPath, TrainingDataFile), FileMode.CreateNew)); 52 | _trainingDataListWriter.AutoFlush = true; 53 | _testingDataListWriter = new StreamWriter(new FileStream(Path.Combine(RootPath, TestingDataFile), FileMode.CreateNew)); 54 | _testingDataListWriter.AutoFlush = true; 55 | } 56 | 57 | 58 | public void Write(Sample sample) 59 | { 60 | // Write training list 61 | string dataLine = $"{DefaultRootDirectory}/{ObjDirectory}/{sample.Guid + ".jpg"}"; 62 | StreamWriter destination = _random.NextDouble() < DefaultTestingPortion 63 | ? _testingDataListWriter 64 | : _trainingDataListWriter; 65 | destination.WriteLine(dataLine); 66 | destination.Flush(); 67 | 68 | // Write images 69 | using (FileStream imageStream = 70 | new FileStream(Path.Combine(ObjPath, sample.Guid + ".jpg"), FileMode.CreateNew)) 71 | { 72 | sample.ImageStream.WriteTo(imageStream); 73 | } 74 | // Write labels 75 | using (FileStream lableStream = 76 | new FileStream(Path.Combine(ObjPath, sample.Guid + ".txt"), FileMode.CreateNew)) 77 | { 78 | using (StreamWriter lableStreamWriter = new StreamWriter(lableStream)) 79 | { 80 | WriteItems(sample, lableStreamWriter); 81 | } 82 | } 83 | } 84 | 85 | public void Finish() 86 | { 87 | WriteObjData(); 88 | WriteClassNames(); 89 | _trainingDataListWriter.Flush(); 90 | _testingDataListWriter.Flush(); 91 | _isFinished = true; 92 | } 93 | 94 | /// 95 | /// Write to imageName.txt 96 | /// 97 | /// 98 | private void WriteItems(Sample sample, StreamWriter writer) 99 | { 100 | double width = sample.Width; 101 | double height = sample.Height; 102 | foreach (var sampleItem in sample.Items) 103 | { 104 | ObjectClass type = sampleItem.Type; 105 | if (!_occurrenceClasses.Contains(type)) 106 | { 107 | _occurrenceClasses.Add(type); 108 | } 109 | 110 | double xCenter = ((double)sampleItem.X + sampleItem.Width / 2.0) / width; 111 | double yCenter = ((double)sampleItem.Y + sampleItem.Height / 2.0) / height; 112 | double sampleWidth = sampleItem.Width / width; 113 | double sampleHeight = sampleItem.Height / height; 114 | double[] numbers = { xCenter, yCenter, sampleWidth, sampleHeight }; 115 | if (numbers.Any(n => n < 0 || n > 1)) 116 | { 117 | throw new InvalidDataException("Size and coordinates must be positive number smaller than 1"); 118 | } 119 | 120 | writer.Write(_occurrenceClasses.IndexOf(type)); // 121 | writer.Write(' '); 122 | writer.Write(xCenter); // 123 | writer.Write(' '); 124 | writer.Write(yCenter); // 125 | writer.Write(' '); 126 | writer.Write(sampleWidth); // 127 | writer.Write(' '); 128 | writer.Write(sampleHeight); // 129 | writer.WriteLine(); 130 | } 131 | } 132 | 133 | private static void CleanDirectory(string path) 134 | { 135 | if (Directory.EnumerateFileSystemEntries(path).Any()) 136 | { 137 | Console.WriteLine("Warning: Target RootPath Is Not Empty. Cleaning target path..."); 138 | DirectoryInfo directory = new DirectoryInfo(path); 139 | foreach (var fileInfo in directory.GetFiles()) 140 | { 141 | fileInfo.Delete(); 142 | } 143 | foreach (var directoryInfo in directory.GetDirectories()) 144 | { 145 | directoryInfo.Delete(true); 146 | } 147 | } 148 | } 149 | 150 | private void WriteObjData() 151 | { 152 | using (FileStream file = new FileStream(Path.Combine(RootPath, ObjectDataFile), FileMode.CreateNew)) 153 | { 154 | using (StreamWriter writer = new StreamWriter(file)) 155 | { 156 | writer.WriteLine($"classes={_occurrenceClasses.Count}"); 157 | writer.WriteLine($"train={DefaultRootDirectory}/{TrainingDataFile}"); 158 | writer.WriteLine($"valid={DefaultRootDirectory}/{TestingDataFile}"); 159 | writer.WriteLine($"names={DefaultRootDirectory}/{ClassNamesFile}"); 160 | writer.WriteLine(@"backup = backup/"); 161 | } 162 | } 163 | } 164 | 165 | private void WriteClassNames() 166 | { 167 | using (FileStream file = new FileStream(Path.Combine(RootPath, ClassNamesFile), FileMode.CreateNew)) 168 | { 169 | using (StreamWriter writer = new StreamWriter(file)) 170 | { 171 | foreach (var occurenceClass in _occurrenceClasses) 172 | { 173 | writer.WriteLine(Enum.GetName(typeof(ObjectClass), occurenceClass)); 174 | } 175 | } 176 | } 177 | } 178 | 179 | public void Dispose() 180 | { 181 | if (!_isFinished) 182 | { 183 | Finish(); 184 | } 185 | _trainingDataListWriter.Dispose(); 186 | _testingDataListWriter.Dispose(); 187 | _disposed = true; 188 | } 189 | 190 | ~DarknetWriter() 191 | { 192 | if (!_disposed) 193 | { 194 | Dispose(); 195 | } 196 | } 197 | 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /MapleStory.Sampler/IDatasetWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MapleStory.Sampler 8 | { 9 | public interface IDatasetWriter 10 | { 11 | void Write(Sample sample); 12 | 13 | void Finish(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /MapleStory.Sampler/MapleStory.Sampler.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows7.0 4 | x64 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /MapleStory.Sampler/PostProcessor/IPostProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MapleStory.Sampler.PostProcessor 8 | { 9 | /// 10 | /// Interface for post-processor of samples 11 | /// 12 | public interface IPostProcessor 13 | { 14 | /// 15 | /// Post-process the sample. Post-processing could be adding items, resizing, etc.. 16 | /// 17 | /// Sample before processing. 18 | /// Sample after processing. 19 | Sample Process(Sample sample); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MapleStory.Sampler/PostProcessor/PlayerProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using MapRender.Invoker; 10 | using SharpDX.MediaFoundation; 11 | 12 | namespace MapleStory.Sampler.PostProcessor 13 | { 14 | /// 15 | /// Post-processor by adding player images and coordinates. 16 | /// 17 | public class PlayerProcessor : IPostProcessor 18 | { 19 | private const double VerticalRange = 0.85; 20 | private const double HorizontalRange = 0.85; 21 | private const int NumPlayers = 3; 22 | 23 | private IEnumerable _playerImages; 24 | private int _numImages; 25 | private Random _random; 26 | 27 | public PlayerProcessor(string directory) 28 | { 29 | if (!Directory.Exists(directory)) 30 | { 31 | throw new FileNotFoundException($"{directory} is not a valid directory!"); 32 | } 33 | _random = new Random(); 34 | _playerImages = Directory.GetFiles(directory, "*.*").Where(f => f.EndsWith(".png") || f.EndsWith(".bmp")).Select(Image.FromFile); 35 | _numImages = _playerImages.Count(); 36 | if (_numImages == 0) 37 | { 38 | throw new FileNotFoundException($"{directory} does not contain any .bmp images!"); 39 | } 40 | } 41 | 42 | public Sample Process(Sample sample) 43 | { 44 | // Draw player & replace stream 45 | sample.ImageStream = DrawPlayer(sample.ImageStream, sample); 46 | return sample; 47 | } 48 | 49 | private Image GetNextPlayer() 50 | { 51 | return _playerImages.ElementAt(_random.Next(0, _numImages)); 52 | } 53 | 54 | private MemoryStream DrawPlayer(MemoryStream source, Sample sample) 55 | { 56 | Bitmap result = new Bitmap(source); 57 | using (Graphics graphics = Graphics.FromImage(result)) 58 | { 59 | for (int i = 0; i < NumPlayers; ++i) 60 | { 61 | Image player = GetNextPlayer(); 62 | int drawX = _random.Next((int)(sample.Width * (1 - HorizontalRange)), (int)(sample.Width * HorizontalRange)); 63 | int drawY = _random.Next((int)(sample.Height * (1 - VerticalRange)), (int)(sample.Height * VerticalRange)); 64 | if (_random.NextDouble() > 0.5) 65 | { 66 | player.RotateFlip(RotateFlipType.RotateNoneFlipX); 67 | } 68 | Rectangle drawRegion = new Rectangle(drawX, drawY, player.Width, player.Height); 69 | graphics.DrawImageUnscaled(player, drawRegion); 70 | // Add coordinate information 71 | sample.Items.Add(new TargetItem() { Height = player.Height, Width = player.Width, X = drawX, Y = drawY, Type = ObjectClass.Player }); 72 | } 73 | } 74 | MemoryStream ret = new MemoryStream(); 75 | result.Save(ret, ImageFormat.Png); 76 | return ret; 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /MapleStory.Sampler/Sample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using MapRender.Invoker; 8 | 9 | namespace MapleStory.Sampler 10 | { 11 | public class Sample 12 | { 13 | public MemoryStream ImageStream { get; internal set; } 14 | 15 | public IList Items { get; internal set; } 16 | 17 | public int Width { get; internal set; } 18 | 19 | public int Height { get; internal set; } 20 | 21 | public Guid Guid { get; private set; } 22 | 23 | public Sample(MemoryStream imageStream, IEnumerable items, int width, int height) 24 | { 25 | ImageStream = imageStream; 26 | Items = new List(items); 27 | Width = width; 28 | Height = height; 29 | Guid = Guid.NewGuid(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MapleStory.Sampler/Sampler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using MapRender.Invoker; 12 | using SharpDX.MediaFoundation; 13 | using Encoder = System.Drawing.Imaging.Encoder; 14 | 15 | namespace MapleStory.Sampler 16 | { 17 | public class Sampler 18 | { 19 | private const long JPEG_RATIO = 90L; 20 | private const double ITEM_PARTIAL_AREA_THRESHOLD = 0.70; 21 | private readonly MapRenderInvoker _renderInvoker; 22 | 23 | public delegate void SamplePostProcessor(Sample sample, EventArgs e); 24 | 25 | public event SamplePostProcessor OnSampleCaptured; 26 | 27 | public Sampler(MapRenderInvoker renderInvoker) 28 | { 29 | _renderInvoker = renderInvoker; 30 | if (!renderInvoker.IsRunning) 31 | { 32 | throw new ArgumentException("Sampler must have a lunched render!"); 33 | } 34 | } 35 | 36 | public Task SampleSingleAsync() 37 | { 38 | MemoryStream stream = new MemoryStream(); 39 | var screenShotData = _renderInvoker.TakeScreenShot(stream); 40 | var items = FilterTargetsInCamera(screenShotData); 41 | int width = screenShotData.CameraRectangle.Width; 42 | int height = screenShotData.CameraRectangle.Height; 43 | return Task.Run(() => 44 | { 45 | Sample ret = new Sample(stream, items, width, height); 46 | OnSampleCaptured?.Invoke(ret, null); 47 | ret.ImageStream = EncodeScreenShot(ret.ImageStream); 48 | return ret; 49 | }); 50 | } 51 | 52 | /// 53 | /// Sample all based on provided step 54 | /// 55 | /// step in X to sample 56 | /// step in Y to sample 57 | /// Writer to save result 58 | /// Sampling time interval, in ms. 59 | public void SampleAll(int xStep, int yStep, IDatasetWriter writer, int interval = 0) 60 | { 61 | xStep = Math.Abs(xStep); 62 | yStep = Math.Abs(yStep); 63 | int initX = _renderInvoker.WorldOriginX + _renderInvoker.ScreenWidth / 2; 64 | int initY = _renderInvoker.WorldOriginY + _renderInvoker.ScreenHeight / 2; 65 | int endX = _renderInvoker.WorldOriginX + _renderInvoker.WorldWidth - _renderInvoker.ScreenWidth / 2; 66 | int endY = _renderInvoker.WorldOriginY + _renderInvoker.WorldHeight - _renderInvoker.ScreenHeight / 2; 67 | 68 | int count = 0; 69 | int total = (int)(Math.Round((double)(endX - initX) / xStep, MidpointRounding.ToPositiveInfinity) * 70 | Math.Round((double)(endY - initY) / yStep, MidpointRounding.ToPositiveInfinity)); 71 | HashSet writingTasks = new HashSet(); 72 | Queue completedTasks = new Queue(); 73 | 74 | for (int x = initX; x < endX; x += xStep) 75 | { 76 | for (int y = initY; y < endY; y += yStep) 77 | { 78 | Console.WriteLine($"Sampling at center x={x},y={y}...."); 79 | // Move Camera 80 | _renderInvoker.MoveCamera(x, y); 81 | // Do sample 82 | writingTasks.Add(SampleSingleAsync().ContinueWith(s => 83 | { 84 | lock (writer) 85 | { 86 | writer.Write(s.Result); 87 | } 88 | })); 89 | // Check pending tasks 90 | foreach (var task in writingTasks) 91 | { 92 | if (task.Exception != null) 93 | { 94 | throw task.Exception; 95 | } 96 | if (task.IsCompleted) 97 | { 98 | count++; 99 | completedTasks.Enqueue(task); 100 | Console.WriteLine($"Progress: {count}/{total}, {(double)count / total * 100}%\n"); 101 | } 102 | } 103 | foreach (var task in completedTasks) 104 | { 105 | writingTasks.Remove(task); 106 | } 107 | Thread.Sleep(interval); 108 | } 109 | } 110 | foreach (var task in writingTasks) 111 | { 112 | task.GetAwaiter().GetResult(); 113 | } 114 | Console.WriteLine("############## All Samples Captured ##############"); 115 | return; 116 | } 117 | 118 | private MemoryStream EncodeScreenShot(Stream screenShotStream) 119 | { 120 | using Bitmap source = new Bitmap(screenShotStream); 121 | using Bitmap result = new Bitmap(_renderInvoker.ScreenWidth, _renderInvoker.ScreenHeight); 122 | Rectangle rectangle = new Rectangle(Point.Empty, source.Size); 123 | using (Graphics graphics = Graphics.FromImage(result)) 124 | { 125 | // Dark background, to back alpha channel. 126 | graphics.Clear(Color.Black); 127 | graphics.DrawImageUnscaledAndClipped(source, rectangle); 128 | } 129 | MemoryStream ret = new MemoryStream(); 130 | ImageCodecInfo jpegCodecInfo = ImageCodecInfo.GetImageEncoders().First(i => i.MimeType == "image/jpeg"); 131 | using (EncoderParameters parameters = new EncoderParameters(1)) 132 | { 133 | EncoderParameter parameter = new EncoderParameter(Encoder.Quality, JPEG_RATIO); 134 | parameters.Param[0] = parameter; 135 | result.Save(ret, jpegCodecInfo, parameters); 136 | } 137 | 138 | return ret; 139 | } 140 | 141 | private static List FilterTargetsInCamera(ScreenShotData data) 142 | { 143 | List ret = new List(); 144 | List source = data.Items; 145 | Rectangle camRectangle = data.CameraRectangle; 146 | int imgWidth = camRectangle.Width; 147 | int imgHeight = camRectangle.Height; 148 | source.ForEach(i => 149 | { 150 | i.X -= camRectangle.X; 151 | i.Y -= camRectangle.Y; 152 | 153 | // Validation 154 | if (i.Height < 0 || i.Width < 0) 155 | { 156 | throw new InvalidDataException("Items Height or Width is negative!!"); 157 | } 158 | 159 | // Not show in screenshots at all 160 | if (i.X > imgWidth || i.Y > imgHeight) 161 | { 162 | return; 163 | } 164 | int inCameraWidth = i.Width; 165 | int inCameraHeight = i.Height; 166 | double itemArea = i.Width * i.Height; 167 | 168 | // Partial in X - left 169 | if (i.X < 0) 170 | { 171 | inCameraWidth = i.X + i.Width; 172 | if (inCameraWidth < 0) 173 | { 174 | return; 175 | } 176 | i.X = 0; 177 | } 178 | 179 | // Partial in X - right 180 | if (i.X + i.Width > imgWidth) 181 | { 182 | inCameraWidth = imgWidth - i.X; 183 | } 184 | 185 | // Partial in Y - up 186 | if (i.Y < 0) 187 | { 188 | inCameraHeight = i.Y + i.Height; 189 | if (inCameraHeight < 0) 190 | { 191 | return; 192 | } 193 | i.Y = 0; 194 | } 195 | 196 | // Partial in Y - bottom 197 | if (i.Y + i.Height > imgHeight) 198 | { 199 | inCameraHeight = imgHeight - i.Y; 200 | } 201 | 202 | double inCameraArea = inCameraHeight * imgWidth; 203 | if (inCameraArea / itemArea >= ITEM_PARTIAL_AREA_THRESHOLD) 204 | { 205 | i.Width = inCameraWidth; 206 | i.Height = inCameraHeight; 207 | ret.Add(i); 208 | } 209 | }); 210 | return ret; 211 | } 212 | 213 | 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /MapleStory.Sampler/TfExample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using MapRender.Invoker; 8 | using ProtoBuf; 9 | using Tensorflow; 10 | 11 | namespace MapleStory.Sampler 12 | { 13 | public class TfExample 14 | { 15 | #region KEYS 16 | private const string KeyHeight = @"image/height"; 17 | private const string KeyWidth = @"image/width"; 18 | private const string KeyFileName = @"image/filename"; 19 | private const string KeySourceId = @"image/source_id"; 20 | private const string KeyEncoded = @"image/encoded"; 21 | private const string KeyFormat = @"image/format"; 22 | private const string KeyMinX = @"image/object/bbox/xmin"; 23 | private const string KeyMaxX = @"image/object/bbox/xmax"; 24 | private const string KeyMinY = @"image/object/bbox/ymin"; 25 | private const string KeyMaxY = @"image/object/bbox/ymax"; 26 | private const string KeyClassText = @"image/object/class/text"; 27 | private const string KeyLabel = @"image/object/class/label"; 28 | #endregion 29 | 30 | private const string JpegFormatString = @"jpeg"; 31 | 32 | private Example _underlyingExample; 33 | private Features _features; 34 | private Dictionary _featureMap; 35 | private List _minXList; 36 | private List _minYList; 37 | private List _maxXList; 38 | private List _maxYList; 39 | private List _classTextList; 40 | private List _labelList; 41 | private float _sampleWidth; 42 | private float _sampleHeight; 43 | 44 | public Guid Guid { get; private set; } 45 | 46 | private TfExample(MemoryStream imageStream, int width, int height, int itemCount) 47 | { 48 | _underlyingExample = new Example(); 49 | _underlyingExample.Features = new Features(); 50 | _features = _underlyingExample.Features; 51 | _featureMap = _features.feature; 52 | _sampleHeight = height; 53 | _sampleWidth = width; 54 | Guid = Guid.NewGuid(); 55 | 56 | _minXList = new List(itemCount); 57 | _minYList = new List(itemCount); 58 | _maxXList = new List(itemCount); 59 | _maxYList = new List(itemCount); 60 | _classTextList = new List(itemCount); 61 | _labelList = new List(itemCount); 62 | 63 | _featureMap[KeyFormat] = NewBytesFeature(JpegFormatString); 64 | _featureMap[KeyHeight] = NewInt64Feature(height); 65 | _featureMap[KeyWidth] = NewInt64Feature(width); 66 | _featureMap[KeyEncoded] = NewBytesFeature(imageStream.ToArray()); 67 | var guidString = Guid.ToString(); 68 | _featureMap[KeyFileName] = NewBytesFeature(guidString); 69 | _featureMap[KeySourceId] = NewBytesFeature(guidString); 70 | } 71 | 72 | public static TfExample From(Sample sample) 73 | { 74 | TfExample ret = new TfExample(sample.ImageStream, sample.Width, sample.Height, sample.Items.Count()); 75 | foreach (var item in sample.Items) 76 | { 77 | ret.AddItem(item); 78 | } 79 | return ret; 80 | } 81 | 82 | public MemoryStream SerializeToStream() 83 | { 84 | _featureMap[KeyMinX] = NewFloatListFeature(_minXList.ToArray()); 85 | _featureMap[KeyMaxX] = NewFloatListFeature(_maxXList.ToArray()); 86 | _featureMap[KeyMinY] = NewFloatListFeature(_minYList.ToArray()); 87 | _featureMap[KeyMaxY] = NewFloatListFeature(_maxYList.ToArray()); 88 | _featureMap[KeyClassText] = NewBytesListFeature(_classTextList); 89 | _featureMap[KeyLabel] = NewInt64ListFeature(_labelList.ToArray()); 90 | 91 | MemoryStream ret = new MemoryStream(); 92 | Serializer.Serialize(ret, _underlyingExample); 93 | return ret; 94 | } 95 | 96 | private void AddItem(TargetItem item) 97 | { 98 | float x = item.X; 99 | float y = item.Y; 100 | float x2 = x + item.Width; 101 | float y2 = y + item.Height; 102 | _minXList.Add(x / _sampleWidth); 103 | _maxXList.Add(x2 / _sampleWidth); 104 | 105 | _minYList.Add(y / _sampleHeight); 106 | _maxYList.Add(y2 / _sampleHeight); 107 | 108 | if (item.Type == MapRender.Invoker.ObjectClass.Unknown || !Enum.IsDefined(typeof(ObjectClass), item.Type)) 109 | { 110 | throw new ArgumentException("TargetItem contains unknown type item!"); 111 | } 112 | _classTextList.Add(Encoding.UTF8.GetBytes(Enum.GetName(typeof(ObjectClass), item.Type))); 113 | _labelList.Add((long)item.Type); 114 | } 115 | 116 | private static Feature NewBytesListFeature(IEnumerable value) 117 | { 118 | var bytesList = new BytesList(); 119 | bytesList.Values.AddRange(value); 120 | return new Feature() { BytesList = bytesList }; 121 | } 122 | 123 | private static Feature NewFloatListFeature(float[] value) 124 | { 125 | return new Feature() { FloatList = new FloatList() { Values = value } }; 126 | } 127 | 128 | private static Feature NewInt64ListFeature(long[] value) 129 | { 130 | return new Feature() { Int64List = new Int64List() { Values = value } }; 131 | } 132 | 133 | private static Feature NewInt64Feature(long value) 134 | { 135 | return new Feature() { Int64List = new Int64List() { Values = new[] { value } } }; 136 | } 137 | 138 | private static Feature NewBytesFeature(byte[] value) 139 | { 140 | return new Feature() { BytesList = new BytesList() { Values = { value } } }; 141 | } 142 | 143 | private static Feature NewBytesFeature(string text) 144 | { 145 | return NewBytesFeature(Encoding.UTF8.GetBytes(text)); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /MapleStory.Sampler/TfRecordWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Force.Crc32; 8 | 9 | namespace MapleStory.Sampler 10 | { 11 | public class TfRecordWriter : IDisposable, IDatasetWriter 12 | { 13 | public const string TfRecordNameExtension = ".tfrecord"; 14 | private const int BufferSize = 5120; 15 | private FileStream _fileStream; 16 | 17 | /// 18 | /// Create a TfRecordWriter, by creating an underlying 19 | /// 20 | /// If path does not end with ".tfrecord", it will be added. 21 | public TfRecordWriter(string path) 22 | { 23 | if (!path.EndsWith(TfRecordNameExtension, StringComparison.InvariantCultureIgnoreCase)) 24 | { 25 | path += TfRecordNameExtension; 26 | } 27 | _fileStream = new FileStream(path, FileMode.CreateNew); 28 | } 29 | 30 | ~TfRecordWriter() 31 | { 32 | this.Dispose(); 33 | } 34 | 35 | void IDatasetWriter.Write(Sample sample) 36 | { 37 | Write(TfExample.From(sample)); 38 | } 39 | 40 | public void Write(TfExample example) 41 | { 42 | Write(example.SerializeToStream()); 43 | } 44 | 45 | public void Write(byte[] bytes) 46 | { 47 | Write(new MemoryStream(bytes)); 48 | } 49 | 50 | /// 51 | /// Write a single record. 52 | /// 53 | /// Stream containing record in binary form 54 | public void Write(Stream stream) 55 | { 56 | ulong length = (ulong)stream.Length; 57 | uint crcLength = Crc32CAlgorithm.Compute(BitConverter.GetBytes(length)); 58 | uint maskLength = MaskCrc32(crcLength); 59 | _fileStream.Write(BitConverter.GetBytes(length), 0, 8); // uint64 length 60 | _fileStream.Write(BitConverter.GetBytes(maskLength), 0, 4); // uint32 masked_crc32_of_length 61 | stream.Seek(0, SeekOrigin.Begin); // Read from head 62 | 63 | byte[] buffer = new byte[BufferSize]; 64 | int count = 0; 65 | int readSize = stream.Read(buffer, 0, buffer.Length); 66 | uint crcData = Crc32CAlgorithm.Compute(buffer, 0, readSize); 67 | for (bool firstRun = true; readSize > 0; 68 | count += readSize, readSize = stream.Read(buffer, 0, buffer.Length), firstRun = false) 69 | { 70 | if (!firstRun) 71 | { 72 | crcData = Crc32CAlgorithm.Append(crcData, buffer, 0, readSize); 73 | } 74 | _fileStream.Write(buffer, 0, readSize); // byte data[length] 75 | } 76 | 77 | if (count != (int)length) 78 | { 79 | throw new Exception("Stream length does not equal to read length."); 80 | } 81 | 82 | uint maskCrcData = MaskCrc32(crcData); 83 | _fileStream.Write(BitConverter.GetBytes(maskCrcData), 0, 4); // uint32 masked_crc32_of_data 84 | } 85 | 86 | /// 87 | /// See 88 | /// 89 | private uint MaskCrc32(uint crc) 90 | { 91 | return (uint)(((crc >> 15) | (crc << 17)) + 0xa282ead8UL); 92 | } 93 | 94 | public void Finish() 95 | { 96 | // Not needed for TfRecord. 97 | return; 98 | } 99 | 100 | public void Dispose() 101 | { 102 | _fileStream?.Dispose(); 103 | } 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /MapleStory.Sampler/protobuf/Example.cs: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by a tool; you should avoid making direct changes. 3 | // Consider using 'partial classes' to extend these types 4 | // Input: example.proto 5 | // 6 | 7 | #region Designer generated code 8 | #pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 9 | namespace Tensorflow 10 | { 11 | 12 | [global::ProtoBuf.ProtoContract()] 13 | public partial class Example : global::ProtoBuf.IExtensible 14 | { 15 | private global::ProtoBuf.IExtension __pbn__extensionData; 16 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 17 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 18 | 19 | [global::ProtoBuf.ProtoMember(1, Name = @"features")] 20 | public Features Features { get; set; } 21 | 22 | } 23 | 24 | [global::ProtoBuf.ProtoContract()] 25 | public partial class SequenceExample : global::ProtoBuf.IExtensible 26 | { 27 | private global::ProtoBuf.IExtension __pbn__extensionData; 28 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 29 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 30 | 31 | [global::ProtoBuf.ProtoMember(1, Name = @"context")] 32 | public Features Context { get; set; } 33 | 34 | [global::ProtoBuf.ProtoMember(2, Name = @"feature_lists")] 35 | public FeatureLists FeatureLists { get; set; } 36 | 37 | } 38 | 39 | } 40 | 41 | #pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 42 | #endregion 43 | -------------------------------------------------------------------------------- /MapleStory.Sampler/protobuf/Feature.cs: -------------------------------------------------------------------------------- 1 | // 2 | // This file was generated by a tool; you should avoid making direct changes. 3 | // Consider using 'partial classes' to extend these types 4 | // Input: feature.proto 5 | // 6 | 7 | #region Designer generated code 8 | #pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 9 | namespace Tensorflow 10 | { 11 | 12 | [global::ProtoBuf.ProtoContract()] 13 | public partial class BytesList : global::ProtoBuf.IExtensible 14 | { 15 | private global::ProtoBuf.IExtension __pbn__extensionData; 16 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 17 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 18 | 19 | [global::ProtoBuf.ProtoMember(1, Name = @"value")] 20 | public global::System.Collections.Generic.List Values { get; } = new global::System.Collections.Generic.List(); 21 | 22 | } 23 | 24 | [global::ProtoBuf.ProtoContract()] 25 | public partial class FloatList : global::ProtoBuf.IExtensible 26 | { 27 | private global::ProtoBuf.IExtension __pbn__extensionData; 28 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 29 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 30 | 31 | [global::ProtoBuf.ProtoMember(1, Name = @"value", IsPacked = true)] 32 | public float[] Values { get; set; } 33 | 34 | } 35 | 36 | [global::ProtoBuf.ProtoContract()] 37 | public partial class Int64List : global::ProtoBuf.IExtensible 38 | { 39 | private global::ProtoBuf.IExtension __pbn__extensionData; 40 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 41 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 42 | 43 | [global::ProtoBuf.ProtoMember(1, Name = @"value", IsPacked = true)] 44 | public long[] Values { get; set; } 45 | 46 | } 47 | 48 | [global::ProtoBuf.ProtoContract()] 49 | public partial class Feature : global::ProtoBuf.IExtensible 50 | { 51 | private global::ProtoBuf.IExtension __pbn__extensionData; 52 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 53 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 54 | 55 | [global::ProtoBuf.ProtoMember(1, Name = @"bytes_list")] 56 | public BytesList BytesList 57 | { 58 | get => __pbn__kind.Is(1) ? ((BytesList)__pbn__kind.Object) : default; 59 | set => __pbn__kind = new global::ProtoBuf.DiscriminatedUnionObject(1, value); 60 | } 61 | public bool ShouldSerializeBytesList() => __pbn__kind.Is(1); 62 | public void ResetBytesList() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__kind, 1); 63 | 64 | private global::ProtoBuf.DiscriminatedUnionObject __pbn__kind; 65 | 66 | [global::ProtoBuf.ProtoMember(2, Name = @"float_list")] 67 | public FloatList FloatList 68 | { 69 | get => __pbn__kind.Is(2) ? ((FloatList)__pbn__kind.Object) : default; 70 | set => __pbn__kind = new global::ProtoBuf.DiscriminatedUnionObject(2, value); 71 | } 72 | public bool ShouldSerializeFloatList() => __pbn__kind.Is(2); 73 | public void ResetFloatList() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__kind, 2); 74 | 75 | [global::ProtoBuf.ProtoMember(3, Name = @"int64_list")] 76 | public Int64List Int64List 77 | { 78 | get => __pbn__kind.Is(3) ? ((Int64List)__pbn__kind.Object) : default; 79 | set => __pbn__kind = new global::ProtoBuf.DiscriminatedUnionObject(3, value); 80 | } 81 | public bool ShouldSerializeInt64List() => __pbn__kind.Is(3); 82 | public void ResetInt64List() => global::ProtoBuf.DiscriminatedUnionObject.Reset(ref __pbn__kind, 3); 83 | 84 | } 85 | 86 | [global::ProtoBuf.ProtoContract()] 87 | public partial class Features : global::ProtoBuf.IExtensible 88 | { 89 | private global::ProtoBuf.IExtension __pbn__extensionData; 90 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 91 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 92 | 93 | [global::ProtoBuf.ProtoMember(1)] 94 | [global::ProtoBuf.ProtoMap] 95 | public global::System.Collections.Generic.Dictionary feature { get; } = new global::System.Collections.Generic.Dictionary(); 96 | 97 | } 98 | 99 | [global::ProtoBuf.ProtoContract()] 100 | public partial class FeatureList : global::ProtoBuf.IExtensible 101 | { 102 | private global::ProtoBuf.IExtension __pbn__extensionData; 103 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 104 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 105 | 106 | [global::ProtoBuf.ProtoMember(1, Name = @"feature")] 107 | public global::System.Collections.Generic.List Features { get; } = new global::System.Collections.Generic.List(); 108 | 109 | } 110 | 111 | [global::ProtoBuf.ProtoContract()] 112 | public partial class FeatureLists : global::ProtoBuf.IExtensible 113 | { 114 | private global::ProtoBuf.IExtension __pbn__extensionData; 115 | global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) 116 | => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); 117 | 118 | [global::ProtoBuf.ProtoMember(1)] 119 | [global::ProtoBuf.ProtoMap] 120 | public global::System.Collections.Generic.Dictionary feature_list { get; } = new global::System.Collections.Generic.Dictionary(); 121 | 122 | } 123 | 124 | } 125 | 126 | #pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 127 | #endregion 128 | -------------------------------------------------------------------------------- /MapleStory.Sampler/protobuf/README.md: -------------------------------------------------------------------------------- 1 | # NOTE 2 | Files in this folder are copied from https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/example -------------------------------------------------------------------------------- /MapleStory.Sampler/protobuf/example.proto: -------------------------------------------------------------------------------- 1 | // Protocol messages for describing input data Examples for machine learning 2 | // model training or inference. 3 | syntax = "proto3"; 4 | 5 | package tensorflow; 6 | 7 | import "feature.proto"; 8 | 9 | option cc_enable_arenas = true; 10 | option java_outer_classname = "ExampleProtos"; 11 | option java_multiple_files = true; 12 | option java_package = "org.tensorflow.example"; 13 | option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/example/example_protos_go_proto"; 14 | 15 | // LINT.IfChange 16 | // An Example is a mostly-normalized data format for storing data for 17 | // training and inference. It contains a key-value store (features); where 18 | // each key (string) maps to a Feature message (which is oneof packed BytesList, 19 | // FloatList, or Int64List). This flexible and compact format allows the 20 | // storage of large amounts of typed data, but requires that the data shape 21 | // and use be determined by the configuration files and parsers that are used to 22 | // read and write this format. That is, the Example is mostly *not* a 23 | // self-describing format. In TensorFlow, Examples are read in row-major 24 | // format, so any configuration that describes data with rank-2 or above 25 | // should keep this in mind. For example, to store an M x N matrix of Bytes, 26 | // the BytesList must contain M*N bytes, with M rows of N contiguous values 27 | // each. That is, the BytesList value must store the matrix as: 28 | // .... row 0 .... .... row 1 .... // ........... // ... row M-1 .... 29 | // 30 | // An Example for a movie recommendation application: 31 | // features { 32 | // feature { 33 | // key: "age" 34 | // value { float_list { 35 | // value: 29.0 36 | // }} 37 | // } 38 | // feature { 39 | // key: "movie" 40 | // value { bytes_list { 41 | // value: "The Shawshank Redemption" 42 | // value: "Fight Club" 43 | // }} 44 | // } 45 | // feature { 46 | // key: "movie_ratings" 47 | // value { float_list { 48 | // value: 9.0 49 | // value: 9.7 50 | // }} 51 | // } 52 | // feature { 53 | // key: "suggestion" 54 | // value { bytes_list { 55 | // value: "Inception" 56 | // }} 57 | // } 58 | // # Note that this feature exists to be used as a label in training. 59 | // # E.g., if training a logistic regression model to predict purchase 60 | // # probability in our learning tool we would set the label feature to 61 | // # "suggestion_purchased". 62 | // feature { 63 | // key: "suggestion_purchased" 64 | // value { float_list { 65 | // value: 1.0 66 | // }} 67 | // } 68 | // # Similar to "suggestion_purchased" above this feature exists to be used 69 | // # as a label in training. 70 | // # E.g., if training a linear regression model to predict purchase 71 | // # price in our learning tool we would set the label feature to 72 | // # "purchase_price". 73 | // feature { 74 | // key: "purchase_price" 75 | // value { float_list { 76 | // value: 9.99 77 | // }} 78 | // } 79 | // } 80 | // 81 | // A conformant Example data set obeys the following conventions: 82 | // - If a Feature K exists in one example with data type T, it must be of 83 | // type T in all other examples when present. It may be omitted. 84 | // - The number of instances of Feature K list data may vary across examples, 85 | // depending on the requirements of the model. 86 | // - If a Feature K doesn't exist in an example, a K-specific default will be 87 | // used, if configured. 88 | // - If a Feature K exists in an example but contains no items, the intent 89 | // is considered to be an empty tensor and no default will be used. 90 | 91 | message Example { 92 | Features features = 1; 93 | } 94 | 95 | // A SequenceExample is an Example representing one or more sequences, and 96 | // some context. The context contains features which apply to the entire 97 | // example. The feature_lists contain a key, value map where each key is 98 | // associated with a repeated set of Features (a FeatureList). 99 | // A FeatureList thus represents the values of a feature identified by its key 100 | // over time / frames. 101 | // 102 | // Below is a SequenceExample for a movie recommendation application recording a 103 | // sequence of ratings by a user. The time-independent features ("locale", 104 | // "age", "favorites") describing the user are part of the context. The sequence 105 | // of movies the user rated are part of the feature_lists. For each movie in the 106 | // sequence we have information on its name and actors and the user's rating. 107 | // This information is recorded in three separate feature_list(s). 108 | // In the example below there are only two movies. All three feature_list(s), 109 | // namely "movie_ratings", "movie_names", and "actors" have a feature value for 110 | // both movies. Note, that "actors" is itself a bytes_list with multiple 111 | // strings per movie. 112 | // 113 | // context: { 114 | // feature: { 115 | // key : "locale" 116 | // value: { 117 | // bytes_list: { 118 | // value: [ "pt_BR" ] 119 | // } 120 | // } 121 | // } 122 | // feature: { 123 | // key : "age" 124 | // value: { 125 | // float_list: { 126 | // value: [ 19.0 ] 127 | // } 128 | // } 129 | // } 130 | // feature: { 131 | // key : "favorites" 132 | // value: { 133 | // bytes_list: { 134 | // value: [ "Majesty Rose", "Savannah Outen", "One Direction" ] 135 | // } 136 | // } 137 | // } 138 | // } 139 | // feature_lists: { 140 | // feature_list: { 141 | // key : "movie_ratings" 142 | // value: { 143 | // feature: { 144 | // float_list: { 145 | // value: [ 4.5 ] 146 | // } 147 | // } 148 | // feature: { 149 | // float_list: { 150 | // value: [ 5.0 ] 151 | // } 152 | // } 153 | // } 154 | // } 155 | // feature_list: { 156 | // key : "movie_names" 157 | // value: { 158 | // feature: { 159 | // bytes_list: { 160 | // value: [ "The Shawshank Redemption" ] 161 | // } 162 | // } 163 | // feature: { 164 | // bytes_list: { 165 | // value: [ "Fight Club" ] 166 | // } 167 | // } 168 | // } 169 | // } 170 | // feature_list: { 171 | // key : "actors" 172 | // value: { 173 | // feature: { 174 | // bytes_list: { 175 | // value: [ "Tim Robbins", "Morgan Freeman" ] 176 | // } 177 | // } 178 | // feature: { 179 | // bytes_list: { 180 | // value: [ "Brad Pitt", "Edward Norton", "Helena Bonham Carter" ] 181 | // } 182 | // } 183 | // } 184 | // } 185 | // } 186 | // 187 | // A conformant SequenceExample data set obeys the following conventions: 188 | // 189 | // Context: 190 | // - All conformant context features K must obey the same conventions as 191 | // a conformant Example's features (see above). 192 | // Feature lists: 193 | // - A FeatureList L may be missing in an example; it is up to the 194 | // parser configuration to determine if this is allowed or considered 195 | // an empty list (zero length). 196 | // - If a FeatureList L exists, it may be empty (zero length). 197 | // - If a FeatureList L is non-empty, all features within the FeatureList 198 | // must have the same data type T. Even across SequenceExamples, the type T 199 | // of the FeatureList identified by the same key must be the same. An entry 200 | // without any values may serve as an empty feature. 201 | // - If a FeatureList L is non-empty, it is up to the parser configuration 202 | // to determine if all features within the FeatureList must 203 | // have the same size. The same holds for this FeatureList across multiple 204 | // examples. 205 | // - For sequence modeling, e.g.: 206 | // http://colah.github.io/posts/2015-08-Understanding-LSTMs/ 207 | // https://github.com/tensorflow/nmt 208 | // the feature lists represent a sequence of frames. 209 | // In this scenario, all FeatureLists in a SequenceExample have the same 210 | // number of Feature messages, so that the ith element in each FeatureList 211 | // is part of the ith frame (or time step). 212 | // Examples of conformant and non-conformant examples' FeatureLists: 213 | // 214 | // Conformant FeatureLists: 215 | // feature_lists: { feature_list: { 216 | // key: "movie_ratings" 217 | // value: { feature: { float_list: { value: [ 4.5 ] } } 218 | // feature: { float_list: { value: [ 5.0 ] } } } 219 | // } } 220 | // 221 | // Non-conformant FeatureLists (mismatched types): 222 | // feature_lists: { feature_list: { 223 | // key: "movie_ratings" 224 | // value: { feature: { float_list: { value: [ 4.5 ] } } 225 | // feature: { int64_list: { value: [ 5 ] } } } 226 | // } } 227 | // 228 | // Conditionally conformant FeatureLists, the parser configuration determines 229 | // if the feature sizes must match: 230 | // feature_lists: { feature_list: { 231 | // key: "movie_ratings" 232 | // value: { feature: { float_list: { value: [ 4.5 ] } } 233 | // feature: { float_list: { value: [ 5.0, 6.0 ] } } } 234 | // } } 235 | // 236 | // Conformant pair of SequenceExample 237 | // feature_lists: { feature_list: { 238 | // key: "movie_ratings" 239 | // value: { feature: { float_list: { value: [ 4.5 ] } } 240 | // feature: { float_list: { value: [ 5.0 ] } } } 241 | // } } 242 | // and: 243 | // feature_lists: { feature_list: { 244 | // key: "movie_ratings" 245 | // value: { feature: { float_list: { value: [ 4.5 ] } } 246 | // feature: { float_list: { value: [ 5.0 ] } } 247 | // feature: { float_list: { value: [ 2.0 ] } } } 248 | // } } 249 | // 250 | // Conformant pair of SequenceExample 251 | // feature_lists: { feature_list: { 252 | // key: "movie_ratings" 253 | // value: { feature: { float_list: { value: [ 4.5 ] } } 254 | // feature: { float_list: { value: [ 5.0 ] } } } 255 | // } } 256 | // and: 257 | // feature_lists: { feature_list: { 258 | // key: "movie_ratings" 259 | // value: { } 260 | // } } 261 | // 262 | // Conditionally conformant pair of SequenceExample, the parser configuration 263 | // determines if the second feature_lists is consistent (zero-length) or 264 | // invalid (missing "movie_ratings"): 265 | // feature_lists: { feature_list: { 266 | // key: "movie_ratings" 267 | // value: { feature: { float_list: { value: [ 4.5 ] } } 268 | // feature: { float_list: { value: [ 5.0 ] } } } 269 | // } } 270 | // and: 271 | // feature_lists: { } 272 | // 273 | // Non-conformant pair of SequenceExample (mismatched types) 274 | // feature_lists: { feature_list: { 275 | // key: "movie_ratings" 276 | // value: { feature: { float_list: { value: [ 4.5 ] } } 277 | // feature: { float_list: { value: [ 5.0 ] } } } 278 | // } } 279 | // and: 280 | // feature_lists: { feature_list: { 281 | // key: "movie_ratings" 282 | // value: { feature: { int64_list: { value: [ 4 ] } } 283 | // feature: { int64_list: { value: [ 5 ] } } 284 | // feature: { int64_list: { value: [ 2 ] } } } 285 | // } } 286 | // 287 | // Conditionally conformant pair of SequenceExample; the parser configuration 288 | // determines if the feature sizes must match: 289 | // feature_lists: { feature_list: { 290 | // key: "movie_ratings" 291 | // value: { feature: { float_list: { value: [ 4.5 ] } } 292 | // feature: { float_list: { value: [ 5.0 ] } } } 293 | // } } 294 | // and: 295 | // feature_lists: { feature_list: { 296 | // key: "movie_ratings" 297 | // value: { feature: { float_list: { value: [ 4.0 ] } } 298 | // feature: { float_list: { value: [ 5.0, 3.0 ] } } 299 | // } } 300 | 301 | message SequenceExample { 302 | Features context = 1; 303 | FeatureLists feature_lists = 2; 304 | } 305 | // LINT.ThenChange( 306 | // https://www.tensorflow.org/code/tensorflow/python/training/training.py) 307 | -------------------------------------------------------------------------------- /MapleStory.Sampler/protobuf/feature.proto: -------------------------------------------------------------------------------- 1 | // Protocol messages for describing features for machine learning model 2 | // training or inference. 3 | // 4 | // There are three base Feature types: 5 | // - bytes 6 | // - float 7 | // - int64 8 | // 9 | // A Feature contains Lists which may hold zero or more values. These 10 | // lists are the base values BytesList, FloatList, Int64List. 11 | // 12 | // Features are organized into categories by name. The Features message 13 | // contains the mapping from name to Feature. 14 | // 15 | // Example Features for a movie recommendation application: 16 | // feature { 17 | // key: "age" 18 | // value { float_list { 19 | // value: 29.0 20 | // }} 21 | // } 22 | // feature { 23 | // key: "movie" 24 | // value { bytes_list { 25 | // value: "The Shawshank Redemption" 26 | // value: "Fight Club" 27 | // }} 28 | // } 29 | // feature { 30 | // key: "movie_ratings" 31 | // value { float_list { 32 | // value: 9.0 33 | // value: 9.7 34 | // }} 35 | // } 36 | // feature { 37 | // key: "suggestion" 38 | // value { bytes_list { 39 | // value: "Inception" 40 | // }} 41 | // } 42 | // feature { 43 | // key: "suggestion_purchased" 44 | // value { int64_list { 45 | // value: 1 46 | // }} 47 | // } 48 | // feature { 49 | // key: "purchase_price" 50 | // value { float_list { 51 | // value: 9.99 52 | // }} 53 | // } 54 | // 55 | 56 | syntax = "proto3"; 57 | 58 | package tensorflow; 59 | 60 | option cc_enable_arenas = true; 61 | option java_outer_classname = "FeatureProtos"; 62 | option java_multiple_files = true; 63 | option java_package = "org.tensorflow.example"; 64 | option go_package = "github.com/tensorflow/tensorflow/tensorflow/go/core/example/example_protos_go_proto"; 65 | 66 | // LINT.IfChange 67 | // Containers to hold repeated fundamental values. 68 | message BytesList { 69 | repeated bytes value = 1; 70 | } 71 | message FloatList { 72 | repeated float value = 1 [packed = true]; 73 | } 74 | message Int64List { 75 | repeated int64 value = 1 [packed = true]; 76 | } 77 | 78 | // Containers for non-sequential data. 79 | message Feature { 80 | // Each feature can be exactly one kind. 81 | oneof kind { 82 | BytesList bytes_list = 1; 83 | FloatList float_list = 2; 84 | Int64List int64_list = 3; 85 | } 86 | } 87 | 88 | message Features { 89 | // Map from feature name to feature. 90 | map feature = 1; 91 | } 92 | 93 | // Containers for sequential data. 94 | // 95 | // A FeatureList contains lists of Features. These may hold zero or more 96 | // Feature values. 97 | // 98 | // FeatureLists are organized into categories by name. The FeatureLists message 99 | // contains the mapping from name to FeatureList. 100 | // 101 | message FeatureList { 102 | repeated Feature feature = 1; 103 | } 104 | 105 | message FeatureLists { 106 | // Map from feature name to feature list. 107 | map feature_list = 1; 108 | } 109 | // LINT.ThenChange( 110 | // https://www.tensorflow.org/code/tensorflow/python/training/training.py) 111 | -------------------------------------------------------------------------------- /MapleStoryDetectionSampleGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31112.23 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapleStory.Common", "MapleStory.Common\MapleStory.Common.csproj", "{43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CharaSimResource", "WzComparerR2\CharaSimResource\CharaSimResource.csproj", "{54797F38-A12C-4202-92A4-1A3DDCE914B7}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WzComparerR2", "WzComparerR2\WzComparerR2\WzComparerR2.csproj", "{5E883BE2-2009-4517-8026-4B90DEB83884}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WzComparerR2.Avatar", "WzComparerR2\WzComparerR2.Avatar\WzComparerR2.Avatar.csproj", "{A0753218-2C58-4E4A-9017-A435D2E5F639}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WzComparerR2.Common", "WzComparerR2\WzComparerR2.Common\WzComparerR2.Common.csproj", "{818060BC-404C-470A-94B3-5160716C5247}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WzComparerR2.PluginBase", "WzComparerR2\WzComparerR2.PluginBase\WzComparerR2.PluginBase.csproj", "{FA74A2FD-0250-4182-845D-DD98D829B525}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WzComparerR2.WzLib", "WzComparerR2\WzComparerR2.WzLib\WzComparerR2.WzLib.csproj", "{0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WzComparerR2.MapRender", "WzComparerR2\WzComparerR2.MapRender\WzComparerR2.MapRender.csproj", "{11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapRender.Invoker", "MapRender.Invoker\MapRender.Invoker.csproj", "{C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapleStory.Sampler", "MapleStory.Sampler\MapleStory.Sampler.csproj", "{6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapleStory.MachineLearningSampleGenerator", "MapleStory.MachineLearningSampleGenerator\MapleStory.MachineLearningSampleGenerator.csproj", "{5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Debug|x64 = Debug|x64 32 | Debug|x86 = Debug|x86 33 | Release|Any CPU = Release|Any CPU 34 | Release|x64 = Release|x64 35 | Release|x86 = Release|x86 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Debug|x64.ActiveCfg = Debug|Any CPU 41 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Debug|x64.Build.0 = Debug|Any CPU 42 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Debug|x86.Build.0 = Debug|Any CPU 44 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Release|x64.ActiveCfg = Release|Any CPU 47 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Release|x64.Build.0 = Release|Any CPU 48 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Release|x86.ActiveCfg = Release|Any CPU 49 | {43F1F84E-0B40-4AAD-9BC9-B6100CAB1D4C}.Release|x86.Build.0 = Release|Any CPU 50 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x64.ActiveCfg = Debug|Any CPU 53 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x64.Build.0 = Debug|Any CPU 54 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x86.ActiveCfg = Debug|Any CPU 55 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Debug|x86.Build.0 = Debug|Any CPU 56 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x64.ActiveCfg = Release|Any CPU 59 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x64.Build.0 = Release|Any CPU 60 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x86.ActiveCfg = Release|Any CPU 61 | {54797F38-A12C-4202-92A4-1A3DDCE914B7}.Release|x86.Build.0 = Release|Any CPU 62 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x64.ActiveCfg = Debug|x64 65 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x64.Build.0 = Debug|x64 66 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x86.ActiveCfg = Debug|x86 67 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Debug|x86.Build.0 = Debug|x86 68 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x64.ActiveCfg = Release|x64 71 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x64.Build.0 = Release|x64 72 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x86.ActiveCfg = Release|x86 73 | {5E883BE2-2009-4517-8026-4B90DEB83884}.Release|x86.Build.0 = Release|x86 74 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x64.ActiveCfg = Debug|Any CPU 77 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x64.Build.0 = Debug|Any CPU 78 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x86.ActiveCfg = Debug|Any CPU 79 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Debug|x86.Build.0 = Debug|Any CPU 80 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x64.ActiveCfg = Release|Any CPU 83 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x64.Build.0 = Release|Any CPU 84 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x86.ActiveCfg = Release|Any CPU 85 | {A0753218-2C58-4E4A-9017-A435D2E5F639}.Release|x86.Build.0 = Release|Any CPU 86 | {818060BC-404C-470A-94B3-5160716C5247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {818060BC-404C-470A-94B3-5160716C5247}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {818060BC-404C-470A-94B3-5160716C5247}.Debug|x64.ActiveCfg = Debug|Any CPU 89 | {818060BC-404C-470A-94B3-5160716C5247}.Debug|x64.Build.0 = Debug|Any CPU 90 | {818060BC-404C-470A-94B3-5160716C5247}.Debug|x86.ActiveCfg = Debug|Any CPU 91 | {818060BC-404C-470A-94B3-5160716C5247}.Debug|x86.Build.0 = Debug|Any CPU 92 | {818060BC-404C-470A-94B3-5160716C5247}.Release|Any CPU.ActiveCfg = Release|Any CPU 93 | {818060BC-404C-470A-94B3-5160716C5247}.Release|Any CPU.Build.0 = Release|Any CPU 94 | {818060BC-404C-470A-94B3-5160716C5247}.Release|x64.ActiveCfg = Release|Any CPU 95 | {818060BC-404C-470A-94B3-5160716C5247}.Release|x64.Build.0 = Release|Any CPU 96 | {818060BC-404C-470A-94B3-5160716C5247}.Release|x86.ActiveCfg = Release|Any CPU 97 | {818060BC-404C-470A-94B3-5160716C5247}.Release|x86.Build.0 = Release|Any CPU 98 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 99 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|Any CPU.Build.0 = Debug|Any CPU 100 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x64.ActiveCfg = Debug|Any CPU 101 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x64.Build.0 = Debug|Any CPU 102 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x86.ActiveCfg = Debug|Any CPU 103 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Debug|x86.Build.0 = Debug|Any CPU 104 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Release|Any CPU.ActiveCfg = Release|Any CPU 105 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Release|Any CPU.Build.0 = Release|Any CPU 106 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x64.ActiveCfg = Release|Any CPU 107 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x64.Build.0 = Release|Any CPU 108 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x86.ActiveCfg = Release|Any CPU 109 | {FA74A2FD-0250-4182-845D-DD98D829B525}.Release|x86.Build.0 = Release|Any CPU 110 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 111 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 112 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x64.ActiveCfg = Debug|Any CPU 113 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x64.Build.0 = Debug|Any CPU 114 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x86.ActiveCfg = Debug|Any CPU 115 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Debug|x86.Build.0 = Debug|Any CPU 116 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 117 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|Any CPU.Build.0 = Release|Any CPU 118 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x64.ActiveCfg = Release|Any CPU 119 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x64.Build.0 = Release|Any CPU 120 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x86.ActiveCfg = Release|Any CPU 121 | {0E9801FD-44A2-4AF8-AE91-D6E74BAD56B2}.Release|x86.Build.0 = Release|Any CPU 122 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 123 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|Any CPU.Build.0 = Debug|Any CPU 124 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x64.ActiveCfg = Debug|Any CPU 125 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x64.Build.0 = Debug|Any CPU 126 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x86.ActiveCfg = Debug|Any CPU 127 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Debug|x86.Build.0 = Debug|Any CPU 128 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|Any CPU.ActiveCfg = Release|Any CPU 129 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|Any CPU.Build.0 = Release|Any CPU 130 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x64.ActiveCfg = Release|Any CPU 131 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x64.Build.0 = Release|Any CPU 132 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x86.ActiveCfg = Release|Any CPU 133 | {11E362E4-B8FB-4BD4-B0D3-BA078D5FB002}.Release|x86.Build.0 = Release|Any CPU 134 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 135 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Debug|Any CPU.Build.0 = Debug|Any CPU 136 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Debug|x64.ActiveCfg = Debug|Any CPU 137 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Debug|x64.Build.0 = Debug|Any CPU 138 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Debug|x86.ActiveCfg = Debug|Any CPU 139 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Debug|x86.Build.0 = Debug|Any CPU 140 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Release|Any CPU.ActiveCfg = Release|Any CPU 141 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Release|Any CPU.Build.0 = Release|Any CPU 142 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Release|x64.ActiveCfg = Release|Any CPU 143 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Release|x64.Build.0 = Release|Any CPU 144 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Release|x86.ActiveCfg = Release|Any CPU 145 | {C28EA64C-2C4C-40CE-8FF9-EF74E50CEA5B}.Release|x86.Build.0 = Release|Any CPU 146 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 147 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 148 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Debug|x64.ActiveCfg = Debug|Any CPU 149 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Debug|x64.Build.0 = Debug|Any CPU 150 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Debug|x86.ActiveCfg = Debug|Any CPU 151 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Debug|x86.Build.0 = Debug|Any CPU 152 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 153 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Release|Any CPU.Build.0 = Release|Any CPU 154 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Release|x64.ActiveCfg = Release|Any CPU 155 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Release|x64.Build.0 = Release|Any CPU 156 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Release|x86.ActiveCfg = Release|Any CPU 157 | {6B6E65DC-B238-4925-A4FC-6C1DEC2920F5}.Release|x86.Build.0 = Release|Any CPU 158 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 159 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Debug|Any CPU.Build.0 = Debug|Any CPU 160 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Debug|x64.ActiveCfg = Debug|Any CPU 161 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Debug|x64.Build.0 = Debug|Any CPU 162 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Debug|x86.ActiveCfg = Debug|Any CPU 163 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Debug|x86.Build.0 = Debug|Any CPU 164 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Release|Any CPU.ActiveCfg = Release|Any CPU 165 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Release|Any CPU.Build.0 = Release|Any CPU 166 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Release|x64.ActiveCfg = Release|Any CPU 167 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Release|x64.Build.0 = Release|Any CPU 168 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Release|x86.ActiveCfg = Release|Any CPU 169 | {5DD2E446-DCD0-4104-A3F2-EDB6941A0B34}.Release|x86.Build.0 = Release|Any CPU 170 | EndGlobalSection 171 | GlobalSection(SolutionProperties) = preSolution 172 | HideSolutionNode = FALSE 173 | EndGlobalSection 174 | GlobalSection(ExtensibilityGlobals) = postSolution 175 | SolutionGuid = {58DAE377-DCA8-48A9-88E3-A8C9AE2197DB} 176 | EndGlobalSection 177 | EndGlobal 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MapleStoryDetectionSampleGenerator 2 | Generate Machine Learning Samples Object Detection In MapleStory 3 | ![](https://github.com/charlescao460/MapleStoryDetectionSampleGenerator/blob/main/pictures/result.png) 4 | 5 | 6 | 7 | # Performance 8 | This generator can generate arbitrarily many annotated samples. All bounding boxes are precisely annotated based on rendering coordinates. 9 | 10 | With [YOLOv4](https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4-custom.cfg) and ~5000 samples, it can achieve 99.8%mAP in test set. 11 | 12 | 13 | ![](https://github.com/charlescao460/MapleStoryDetectionSampleGenerator/blob/main/pictures/chart_yolov4-custom.png) 14 | 15 | # Requirement 16 | * Visual Studio 2022 v17.8 or above, with .NET workload installed 17 | * .NET 8.0 SDK (8.0.0 or above) 18 | 19 | # Build 20 | 1. Clone this repository with submodules by
`git clone --recursive git@github.com:charlescao460/MapleStoryDetectionSampleGenerator.git`.
Note that `--recursive` is necessary. 21 | 2. Build `WzComparerR2/WzComparerR2.sln` (submodule MUST be built first) 22 | 3. Build `MapleStoryDetectionSampleGenerator.sln` 23 | 4. Run `MapleStory.MachineLearningSampleGenerator\bin\Release\net8.0-windows7.0\WzComparerR2.exe`. Running `WzComparerR2.exe` will generate `Setting.config`, which is required for our MapRender. 24 | 25 | # Run 26 | (Assuming assemblies are built with `Release` configuration. `Debug` configuration is similar) 27 | 1. Cd into executable directory: `cd MapleStory.MachineLearningSampleGenerator\bin\Release\net8.0-windows7.0` 28 | 2. Use `WzComparerR2.exe` to find the desired map you want to sample. Assuming `993134200.img` is the map you want in Limina. 29 | 3. Prepare your player PNGs in a directory.
Since WzComparerR2 does not have Avatar supported inside MapRender, we have to draw player images in our post-processing steps. Player images should be transparent PNGs with only the player's appearance. You can get these PNGs by Photoshop or save from WzComparerR2's Avatar plugin. Assuming `.\players` is the directory containing all images 30 | 4. Run ```.\MapleStory.MachineLearningSampleGenerator.exe -m 993134200 -x 5 -y 5 -f coco -o ".\output" --post --players ".\players"```
31 | This means run the sampler in map 993134200.img with every 5 pixels in X and every 5 pixels in Y, outputing COCO format, and drawing players in post processor.
32 | You can run `.\MapleStory.MachineLearningSampleGenerator.exe --help` for usage hint. Also you can take a look of the entrypoint [Program.cs](https://github.com/charlescao460/MapleStoryDetectionSampleGenerator/blob/main/MapleStory.MachineLearningSampleGenerator/Program.cs) 33 | 34 | # Note 35 | * Since NPCs look like players, including them without annotation could result a negative effect on our model. If you want to hide all NPCs from generated samples, simply change [WzComparerR2.MapRender/MapData.cs](https://github.com/Kagamia/WzComparerR2/blob/main/WzComparerR2.MapRender/MapData.cs) to prevent any NPC data loaded into map render. 36 | 37 | # Output Formats 38 | ## Tensorflow TFRecord 39 | According to Tensorflow [official document](https://www.tensorflow.org/tutorials/load_data/tfrecord#tfrecords_format_details), the output .tfrecord contains multiple [tf.train.Example](https://www.tensorflow.org/api_docs/python/tf/train/Example) in single file. With each example store in the following formats: 40 | 41 | ``` 42 | uint64 length 43 | uint32 masked_crc32_of_length 44 | byte data[length] 45 | uint32 masked_crc32_of_data 46 | ``` 47 | And 48 | ``` 49 | masked_crc = ((crc >> 15) | (crc << 17)) + 0xa282ead8ul 50 | ``` 51 | Each `tf.train.Example` is generated by [protobuf-net](https://github.com/protobuf-net/protobuf-net) according to Tensorflow [example.proto](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto) 52 | 53 | ## Darknet 54 | Output directory structure: 55 | ``` 56 | data/ 57 | |---obj/ 58 | | |---1.jpg 59 | | |---1.txt 60 | | |---...... 61 | |---obj.data 62 | |---obj.names 63 | |---test.txt 64 | |---train.txt 65 | ``` 66 | `obj.data` contains 67 | ``` 68 | classes=2 69 | train=data/train.txt 70 | valid=data/test.txt 71 | names=data/obj.names 72 | backup = backup/ 73 | ``` 74 | And `obj.names` contains the class name for object. `test.txt` and `train.txt` contains samples for testing/training with ratio of 5:95 (5% of images in `obj/` are used for testing). 75 | 76 | ## COCO 77 | Output directory structure: 78 | ``` 79 | coco/ 80 | |---train2017/ 81 | | |---1.jpg 82 | | |---2.jpg 83 | | |---...... 84 | |---val2017/ 85 | | |---1000.jpg 86 | | |---1001.jpg 87 | | |---...... 88 | |---annotations/ 89 | | |---instances_train2017.json 90 | | |---instances_val2017.json 91 | ``` 92 | The COCO json is defined as following: 93 | ```json 94 | { 95 | "info": { 96 | "description": "MapleStory 993134100.img Object Detection Samples - Training", 97 | "url": "https://github.com/charlescao460/MapleStoryDetectionSampleGenerator", 98 | "version": "1.0", 99 | "year": 2021, 100 | "contributor": "CSR" 101 | }, 102 | "licenses": [ 103 | { 104 | "url": "https://github.com/charlescao460/MapleStoryDetectionSampleGenerator/blob/main/LICENSE", 105 | "id": 1, 106 | "name": "MIT License" 107 | } 108 | ], 109 | "images": [ 110 | { 111 | "license": 1, 112 | "file_name": "30a892e1-7f3d-4c65-bdd1-9d28f1ae5187.jpg", 113 | "coco_url": "", 114 | "height": 768, 115 | "width": 1366, 116 | "flickr_url": "", 117 | "id": 1 118 | }, 119 | ...], 120 | "categories": [ 121 | { 122 | "supercategory": "element", 123 | "id": 1, 124 | "name": "Mob" 125 | }, 126 | { 127 | "supercategory": "element", 128 | "id": 2, 129 | "name": "Player" 130 | } 131 | ], 132 | "annotations": [ 133 | { 134 | "segmentation": [ 135 | [ 136 | 524, 137 | 429, 138 | 664, 139 | 429, 140 | 664, 141 | 578, 142 | 524, 143 | 578 144 | ] 145 | ], 146 | "area": 20860, 147 | "iscrowd": 0, 148 | "image_id": 1, 149 | "bbox": [ 150 | 524, 151 | 429, 152 | 140, 153 | 149 154 | ], 155 | "category_id": 1, 156 | "id": 1 157 | }, 158 | ...] 159 | ``` 160 | Note that `segmentation` covers the area as the same as `bbox` does. No segmentation or masked implemented . 161 | -------------------------------------------------------------------------------- /pictures/chart_yolov4-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlescao460/MapleStoryDetectionSampleGenerator/bbbe97ba5fe52733ab0813d018f984fbb77d1838/pictures/chart_yolov4-custom.png -------------------------------------------------------------------------------- /pictures/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlescao460/MapleStoryDetectionSampleGenerator/bbbe97ba5fe52733ab0813d018f984fbb77d1838/pictures/result.png --------------------------------------------------------------------------------