├── .gitattributes ├── .gitignore ├── Component.cs ├── Components ├── LiveSplit.MemoryGraph.dll └── update.LiveSplit.MemoryGraph.xml ├── Downloader.cs ├── Factory.cs ├── LICENSE ├── LiveSplit.MemoryGraph.csproj ├── LiveSplit.MemoryGraph.sln ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs └── Resources.resx ├── README.md ├── Settings.Designer.cs ├── Settings.cs ├── Settings.resx ├── SpritesHandling ├── SonicHandling.cs ├── sonic_spirtes.psd ├── spirtes.png └── spirtesb.png ├── TextStyleOverrideControl.Designer.cs ├── TextStyleOverrideControl.cs ├── TextStyleOverrideControl.resx ├── Utils.cs ├── VectorStructs.cs ├── XML ├── LiveSplit.MemoryGraph.Games.xml └── LiveSplit.MemoryGraphList.xml └── images └── preview.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | .vs 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | x64/ 15 | build/ 16 | bld/ 17 | [Bb]in/ 18 | [Oo]bj/ 19 | 20 | # MSTest test Results 21 | [Tt]est[Rr]esult*/ 22 | [Bb]uild[Ll]og.* 23 | 24 | #NUNIT 25 | *.VisualState.xml 26 | TestResult.xml 27 | 28 | # Build Results of an ATL Project 29 | [Dd]ebugPS/ 30 | [Rr]eleasePS/ 31 | dlldata.c 32 | 33 | *_i.c 34 | *_p.c 35 | *_i.h 36 | *.ilk 37 | *.meta 38 | *.obj 39 | *.pch 40 | *.pdb 41 | *.pgc 42 | *.pgd 43 | *.rsp 44 | *.sbr 45 | *.tlb 46 | *.tli 47 | *.tlh 48 | *.tmp 49 | *.tmp_proj 50 | *.log 51 | *.vspscc 52 | *.vssscc 53 | .builds 54 | *.pidb 55 | *.svclog 56 | *.scc 57 | 58 | # Chutzpah Test files 59 | _Chutzpah* 60 | 61 | # Visual C++ cache files 62 | ipch/ 63 | *.aps 64 | *.ncb 65 | *.opensdf 66 | *.sdf 67 | *.cachefile 68 | 69 | # Visual Studio profiler 70 | *.psess 71 | *.vsp 72 | *.vspx 73 | 74 | # TFS 2012 Local Workspace 75 | $tf/ 76 | 77 | # Guidance Automation Toolkit 78 | *.gpState 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper*/ 82 | *.[Rr]e[Ss]harper 83 | *.DotSettings.user 84 | 85 | # JustCode is a .NET coding addin-in 86 | .JustCode 87 | 88 | # TeamCity is a build add-in 89 | _TeamCity* 90 | 91 | # DotCover is a Code Coverage Tool 92 | *.dotCover 93 | 94 | # NCrunch 95 | *.ncrunch* 96 | _NCrunch_* 97 | .*crunch*.local.xml 98 | 99 | # MightyMoose 100 | *.mm.* 101 | AutoTest.Net/ 102 | 103 | # Web workbench (sass) 104 | .sass-cache/ 105 | 106 | # Installshield output folder 107 | [Ee]xpress/ 108 | 109 | # DocProject is a documentation generator add-in 110 | DocProject/buildhelp/ 111 | DocProject/Help/*.HxT 112 | DocProject/Help/*.HxC 113 | DocProject/Help/*.hhc 114 | DocProject/Help/*.hhk 115 | DocProject/Help/*.hhp 116 | DocProject/Help/Html2 117 | DocProject/Help/html 118 | 119 | # Click-Once directory 120 | publish/ 121 | 122 | # Publish Web Output 123 | *.[Pp]ublish.xml 124 | *.azurePubxml 125 | 126 | # NuGet Packages Directory 127 | packages/ 128 | ## TODO: If the tool you use requires repositories.config uncomment the next line 129 | #!packages/repositories.config 130 | 131 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 132 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 133 | !packages/build/ 134 | 135 | # Windows Azure Build Output 136 | csx/ 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.dbproj.schemaview 151 | *.pfx 152 | *.publishsettings 153 | node_modules/ 154 | 155 | # RIA/Silverlight projects 156 | Generated_Code/ 157 | 158 | # Backup & report files from converting an old project file to a newer 159 | # Visual Studio version. Backup files are not needed, because we have git ;-) 160 | _UpgradeReport_Files/ 161 | Backup*/ 162 | UpgradeLog*.XML 163 | UpgradeLog*.htm 164 | 165 | # SQL Server files 166 | *.mdf 167 | *.ldf 168 | 169 | # Business Intelligence projects 170 | *.rdl.data 171 | *.bim.layout 172 | *.bim_*.settings 173 | 174 | # Microsoft Fakes 175 | FakesAssemblies/ 176 | 177 | # ========================= 178 | # Operating System Files 179 | # ========================= 180 | 181 | # OSX 182 | # ========================= 183 | 184 | .DS_Store 185 | .AppleDouble 186 | .LSOverride 187 | 188 | # Icon must ends with two \r. 189 | Icon 190 | 191 | 192 | # Thumbnails 193 | ._* 194 | 195 | # Files that might appear on external disk 196 | .Spotlight-V100 197 | .Trashes 198 | 199 | # Windows 200 | # ========================= 201 | 202 | # Windows image file caches 203 | Thumbs.db 204 | ehthumbs.db 205 | 206 | # Folder config file 207 | Desktop.ini 208 | 209 | # Recycle Bin used on file shares 210 | $RECYCLE.BIN/ 211 | 212 | # Windows Installer files 213 | *.cab 214 | *.msi 215 | *.msm 216 | *.msp 217 | -------------------------------------------------------------------------------- /Component.cs: -------------------------------------------------------------------------------- 1 | using LiveSplit.Model; 2 | using LiveSplit.UI; 3 | using LiveSplit.UI.Components; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Drawing; 7 | using System.Drawing.Drawing2D; 8 | using System.Linq; 9 | 10 | namespace LiveSplit.MemoryGraph 11 | { 12 | public class Component : IComponent 13 | { 14 | private Settings settings; 15 | 16 | SonicHandling sonic; 17 | 18 | public string ComponentName => "MemoryGraph"; 19 | 20 | public float PaddingTop => 0; 21 | public float PaddingLeft => 0; 22 | public float PaddingBottom => 0; 23 | public float PaddingRight => 0; 24 | 25 | public float VerticalHeight => settings.GraphHeight + 2 * settings.VerticalMargins; 26 | public float MinimumWidth => settings.GraphWidth + 2 * settings.HorizontalMargins; 27 | public float HorizontalWidth => settings.GraphWidth + 2 * settings.HorizontalMargins; 28 | public float MinimumHeight => settings.GraphHeight + 2 * settings.VerticalMargins; 29 | 30 | public IDictionary ContextMenuControls => null; 31 | bool firstLoad = true; 32 | 33 | private int graphHeight; 34 | private int graphWidth; 35 | private int drawCounter = 0; //For smoothing out 36 | private float averagedValue; //For smoothing out 37 | private float[] fake_particles; 38 | 39 | private Queue pastValues { get; } = new Queue(); 40 | private float? _localMax = 0; 41 | private float LocalMax => _localMax ?? (_localMax = pastValues.Max()).Value; 42 | private float _currentValue; 43 | private float currentValue 44 | { 45 | get => _currentValue; 46 | set 47 | { 48 | if (settings.LocalMax) 49 | { 50 | pastValues.Enqueue(value); 51 | 52 | if (!_localMax.HasValue || value == _localMax) 53 | { 54 | while (pastValues.Count > graphWidth) 55 | { 56 | pastValues.Dequeue(); 57 | } 58 | } 59 | else if (value > _localMax) 60 | { 61 | _localMax = value; 62 | 63 | while (pastValues.Count > graphWidth) 64 | { 65 | pastValues.Dequeue(); 66 | } 67 | } 68 | else 69 | { 70 | while (pastValues.Count > graphWidth) 71 | { 72 | // Don't bother recalculating the Max() until it looks like we've dequeued the Max value(). 73 | if (pastValues.Dequeue() == _localMax) 74 | { 75 | _localMax = null; 76 | } 77 | } 78 | } 79 | } 80 | 81 | _currentValue = value; 82 | } 83 | } 84 | private System.Diagnostics.Process process; 85 | 86 | private Bitmap bmpBuffer; 87 | private Graphics gBuffer; 88 | 89 | private Brush graphBrush; 90 | private StringFormat valueTextFormat; 91 | private StringFormat descriptiveTextFormat; 92 | private PointF[] polygon_points; 93 | private Pen graphPen; 94 | 95 | 96 | 97 | public Component(LiveSplitState state) 98 | { 99 | valueTextFormat = new StringFormat(StringFormatFlags.NoWrap); 100 | valueTextFormat.LineAlignment = StringAlignment.Center; 101 | 102 | descriptiveTextFormat = new StringFormat(StringFormatFlags.NoWrap); 103 | descriptiveTextFormat.LineAlignment = StringAlignment.Center; 104 | 105 | graphBrush = Brushes.Transparent; 106 | polygon_points = new PointF[4] { new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0), }; //LB, LU, RU, RB 107 | fake_particles = new float[5] { 185, 143, 6, 168, 91 }; 108 | graphPen = new Pen(graphBrush); 109 | sonic = new SonicHandling(); 110 | 111 | settings = new Settings(); 112 | settings.HandleDestroyed += SettingsUpdated; 113 | SettingsUpdated(null, null); 114 | } 115 | 116 | private void SettingsUpdated(object sender, EventArgs e) 117 | { 118 | if (graphHeight != settings.GraphHeight || graphWidth != settings.GraphWidth) 119 | { 120 | graphHeight = settings.GraphHeight; 121 | graphWidth = settings.GraphWidth; 122 | 123 | bmpBuffer = new Bitmap(graphWidth, graphHeight); 124 | gBuffer = Graphics.FromImage(bmpBuffer); 125 | 126 | gBuffer.Clear(Color.Transparent); 127 | gBuffer.CompositingMode = CompositingMode.SourceCopy; 128 | 129 | } 130 | } 131 | 132 | private static Color Blend(IEnumerable colors, float amount, bool sillyColors) 133 | { 134 | if (float.IsNaN(amount) || amount <= 0 || colors.Count() == 1) 135 | { 136 | // If the amount is in error, default to the first color. 137 | return colors.First(); 138 | } 139 | 140 | if (amount >= 1) 141 | { 142 | if (!sillyColors) 143 | { 144 | // No need to blend: we know the last color will provide 100% of the value. 145 | return colors.Last(); 146 | } 147 | else 148 | { 149 | return BlendTwo(colors.Reverse().Skip(1).First(), colors.Last(), amount); 150 | } 151 | } 152 | 153 | // Stretch the amount to cover then range (0, colors.Count() - 1). 154 | var floatingIndex = amount * (colors.Count() - 1); 155 | 156 | // Pick the highest index as the above value rounded up: [1, colors.Count() - 1] 157 | var index = (int)Math.Ceiling(floatingIndex); 158 | 159 | var color1 = colors.Skip(index - 1).First(); 160 | var color2 = colors.Skip(index).First(); 161 | 162 | // Blend with the decimal part of the floatingIndex. 163 | return BlendTwo(color1, color2, floatingIndex - (index - 1)); 164 | } 165 | 166 | private static Color BlendTwo(Color zeroColor, Color oneColor, double amount) 167 | { 168 | byte a = (byte)((oneColor.A * amount) + zeroColor.A * (1 - amount)); 169 | byte r = (byte)((oneColor.R * amount) + zeroColor.R * (1 - amount)); 170 | byte g = (byte)((oneColor.G * amount) + zeroColor.G * (1 - amount)); 171 | byte b = (byte)((oneColor.B * amount) + zeroColor.B * (1 - amount)); 172 | return Color.FromArgb(a, r, g, b); 173 | } 174 | 175 | public void DrawGraph(Graphics g, LiveSplitState state, float width, float height) 176 | { 177 | if(firstLoad) 178 | { 179 | SettingsUpdated(null, null); 180 | firstLoad = false; 181 | } 182 | // figure out where to draw the graph 183 | RectangleF graphRect = new RectangleF(); 184 | graphRect.Y = (height - graphHeight) / 2; 185 | graphRect.Width = graphWidth; 186 | graphRect.Height = graphHeight; 187 | if ((settings.DescriptiveTextPosition == Position.Left && settings.ValueTextPosition == Position.Right) || 188 | (settings.DescriptiveTextPosition == Position.Right && settings.ValueTextPosition == Position.Left)) 189 | { 190 | graphRect.X = (width - graphWidth) / 2; 191 | } 192 | else if (settings.DescriptiveTextPosition == Position.Left || 193 | settings.ValueTextPosition == Position.Left) 194 | { 195 | graphRect.X = width - graphWidth - settings.HorizontalMargins; 196 | } 197 | else 198 | { 199 | graphRect.X = settings.HorizontalMargins; 200 | } 201 | 202 | // shall there be text left or right of the graph? 203 | bool descriptiveNextToGraph = (settings.DescriptiveTextPosition == Position.Left || 204 | settings.DescriptiveTextPosition == Position.Right); 205 | bool valueNextToGraph = (settings.ValueTextPosition == Position.Left || 206 | settings.ValueTextPosition == Position.Right); 207 | 208 | // calculate relative value between 0 and 1 209 | float relativeValue = (currentValue - settings.MinimumValue) / (settings.MaximumValue - settings.MinimumValue); 210 | float relativeValueClamped = Math.Min(1.0f, Math.Max(0.0f, relativeValue)); 211 | 212 | // create brush 213 | switch (settings.GraphGradient) 214 | { 215 | case GraphGradientType.Plain: 216 | graphBrush = new SolidBrush(settings.GraphColorsEnumeration.First()); 217 | graphPen.Brush = graphBrush; 218 | break; 219 | case GraphGradientType.Horizontal: 220 | case GraphGradientType.Vertical: 221 | if (settings.GraphColorsEnumeration.Count() <= 1) 222 | { 223 | graphBrush = new SolidBrush(settings.GraphColorsEnumeration.First()); 224 | } 225 | else 226 | { 227 | var color_blend = new ColorBlend 228 | { 229 | Colors = settings.GraphColorsEnumeration.Reverse().ToArray() 230 | }; 231 | int position = 0; 232 | color_blend.Positions = color_blend.Colors.Select( 233 | x => position++ / (color_blend.Colors.Length - 1f)).ToArray(); 234 | 235 | LinearGradientBrush gradient_graph_brush; 236 | if (settings.GraphGradient == GraphGradientType.Horizontal) 237 | { 238 | gradient_graph_brush = new LinearGradientBrush( 239 | graphRect, Color.Black, Color.Black, LinearGradientMode.Horizontal); 240 | } 241 | else 242 | { 243 | gradient_graph_brush = new LinearGradientBrush( 244 | new Point(0, 0), new Point(0, graphHeight), Color.Black, Color.Black); 245 | } 246 | graphBrush = gradient_graph_brush; 247 | } 248 | graphPen.Brush = graphBrush; 249 | break; 250 | case GraphGradientType.ByValue: 251 | graphBrush = new SolidBrush(Blend(settings.GraphColorsEnumeration, 252 | relativeValue, settings.GraphSillyColors)); 253 | graphPen.Brush = graphBrush; 254 | break; 255 | } 256 | 257 | // draw actual graph 258 | switch (settings.GraphStyle) 259 | { 260 | #region Filled_Graph 261 | case GraphStyle.FilledGraph: 262 | gBuffer.DrawImageUnscaled(bmpBuffer, -1, 0); 263 | gBuffer.FillRectangle(Brushes.Transparent, graphWidth - 1, 0, 1, graphHeight); 264 | 265 | if (currentValue > settings.MinimumValue) 266 | { 267 | gBuffer.FillRectangle(graphBrush, 268 | graphWidth - 1, (1 - relativeValueClamped) * graphHeight, 269 | 1, relativeValueClamped * graphHeight); 270 | } 271 | 272 | if (descriptiveNextToGraph || valueNextToGraph) 273 | { 274 | g.DrawImageUnscaled(bmpBuffer, (int)graphRect.X, (int)graphRect.Y); 275 | } 276 | else 277 | { 278 | g.DrawImage(bmpBuffer, settings.HorizontalMargins, settings.VerticalMargins, 279 | width - 2 * settings.HorizontalMargins, height - 2 * settings.VerticalMargins); 280 | } 281 | break; 282 | #endregion 283 | #region SingleBar 284 | case GraphStyle.SingleBar: 285 | if (currentValue > settings.MinimumValue) 286 | { 287 | RectangleF barRect; 288 | if (descriptiveNextToGraph || valueNextToGraph) 289 | { 290 | barRect = graphRect; 291 | } 292 | else 293 | { 294 | barRect = g.ClipBounds; 295 | barRect.X += settings.HorizontalMargins; 296 | barRect.Y += settings.VerticalMargins; 297 | barRect.Width -= 2 * settings.HorizontalMargins; 298 | barRect.Height -= 2 * settings.VerticalMargins; 299 | } 300 | 301 | barRect.Width *= relativeValueClamped; 302 | g.FillRectangle(graphBrush, barRect); 303 | } 304 | break; 305 | #endregion 306 | #region Polygonal 307 | case GraphStyle.Polygonal: 308 | gBuffer.DrawImageUnscaled(bmpBuffer, -1, 0); 309 | gBuffer.FillRectangle(Brushes.Transparent, graphWidth - 1, 0, 1, graphHeight); 310 | 311 | averagedValue += relativeValueClamped; 312 | 313 | if (drawCounter==10) 314 | { 315 | averagedValue = averagedValue / 10.0f; 316 | //LU, LL, RB, RU 317 | //X,Y, width, height 318 | polygon_points[0].X = graphWidth - 11; 319 | polygon_points[0].Y = polygon_points[3].Y; 320 | polygon_points[1].X = graphWidth - 11; 321 | polygon_points[1].Y = graphHeight; 322 | polygon_points[2].X = graphWidth; 323 | polygon_points[2].Y = graphHeight; 324 | polygon_points[3].X = graphWidth; 325 | if (currentValue > settings.MinimumValue) 326 | polygon_points[3].Y = graphHeight - (averagedValue * graphHeight); 327 | else 328 | polygon_points[3].Y = graphHeight; 329 | gBuffer.FillPolygon(graphBrush, polygon_points); 330 | averagedValue = 0; 331 | drawCounter = 0; 332 | } 333 | else 334 | drawCounter++; 335 | 336 | 337 | 338 | 339 | if (descriptiveNextToGraph || valueNextToGraph) 340 | { 341 | g.DrawImageUnscaled(bmpBuffer, (int)graphRect.X, (int)graphRect.Y); 342 | } 343 | else 344 | { 345 | g.DrawImage(bmpBuffer, settings.HorizontalMargins, settings.VerticalMargins, 346 | width - 2 * settings.HorizontalMargins, height - 2 * settings.VerticalMargins); 347 | } 348 | break; 349 | #endregion 350 | #region PolygonalOverflow 351 | case GraphStyle.PolygonalOverflow: 352 | gBuffer.DrawImageUnscaled(bmpBuffer, -1, 0); 353 | gBuffer.FillRectangle(Brushes.Transparent, graphWidth - 1, 0, 1, graphHeight); 354 | 355 | 356 | //LU, LL, RB, RU 357 | //X,Y, width, height 358 | polygon_points[0].X = graphWidth - 15; 359 | polygon_points[0].Y = polygon_points[3].Y; 360 | polygon_points[1].X = graphWidth - 15; 361 | polygon_points[1].Y = graphHeight; 362 | polygon_points[2].X = graphWidth; 363 | polygon_points[2].Y = graphHeight; 364 | polygon_points[3].X = graphWidth; 365 | if (currentValue > settings.MinimumValue) 366 | polygon_points[3].Y = graphHeight - (relativeValueClamped * graphHeight); 367 | else 368 | polygon_points[3].Y = graphHeight; 369 | gBuffer.FillPolygon(graphBrush, polygon_points); 370 | 371 | if (descriptiveNextToGraph || valueNextToGraph) 372 | { 373 | g.DrawImageUnscaled(bmpBuffer, (int)graphRect.X, (int)graphRect.Y); 374 | } 375 | else 376 | { 377 | g.DrawImage(bmpBuffer, settings.HorizontalMargins, settings.VerticalMargins, 378 | width - 2 * settings.HorizontalMargins, height - 2 * settings.VerticalMargins); 379 | } 380 | break; 381 | #endregion 382 | #region Sonic_Graph 383 | case GraphStyle.Sonic: 384 | averagedValue += relativeValue; 385 | 386 | if (drawCounter==3) 387 | { 388 | averagedValue = averagedValue / 3; 389 | gBuffer.Clear(Color.Transparent); 390 | 391 | if(graphHeight>graphWidth) 392 | gBuffer.DrawImage(sonic.getBitmap(relativeValue), 0, 0, graphWidth, graphWidth*1.27f); 393 | else 394 | gBuffer.DrawImage(sonic.getBitmap(relativeValue), 0, 0, graphHeight/1.27f, graphHeight); 395 | 396 | 397 | gBuffer.DrawLine(graphPen, fake_particles[0] - 10 * averagedValue, graphHeight * 0.44f, fake_particles[0], graphHeight * 0.44f); 398 | gBuffer.DrawLine(graphPen, fake_particles[1] - 10 * averagedValue, graphHeight * 0.76f, fake_particles[1], graphHeight * 0.76f); 399 | gBuffer.DrawLine(graphPen, fake_particles[2] - 10 * averagedValue, graphHeight * 0.67f, fake_particles[2], graphHeight * 0.67f); 400 | gBuffer.DrawLine(graphPen, fake_particles[3] - 10 * averagedValue, graphHeight * 0.14f, fake_particles[3], graphHeight * 0.14f); 401 | gBuffer.DrawLine(graphPen, fake_particles[4] - 10 * averagedValue, graphHeight * 0.33f, fake_particles[4], graphHeight * 0.33f); 402 | 403 | drawCounter = 0; 404 | 405 | for (int i = 0; i < fake_particles.Length; i++) 406 | { 407 | fake_particles[i] = fake_particles[i] - 15 * averagedValue; 408 | if (fake_particles[i] < 0) 409 | fake_particles[i] += graphWidth + 5; 410 | } 411 | } 412 | else 413 | { 414 | drawCounter++; 415 | } 416 | 417 | //LU, LL, RB, RU 418 | //X,Y, width, height 419 | 420 | if (descriptiveNextToGraph || valueNextToGraph) 421 | { 422 | g.DrawImageUnscaled(bmpBuffer, (int)graphRect.X, (int)graphRect.Y); 423 | } 424 | else 425 | { 426 | g.DrawImage(bmpBuffer, settings.HorizontalMargins, settings.VerticalMargins, 427 | width - 2 * settings.HorizontalMargins, height - 2 * settings.VerticalMargins); 428 | } 429 | break; 430 | #endregion 431 | } 432 | 433 | // draw descriptive text 434 | if (settings.DescriptiveTextPosition != Position.None) 435 | { 436 | switch (settings.DescriptiveTextPosition) 437 | { 438 | case Position.Left: 439 | case Position.LeftInGraph: 440 | descriptiveTextFormat.Alignment = StringAlignment.Near; 441 | break; 442 | case Position.Right: 443 | case Position.RightInGraph: 444 | descriptiveTextFormat.Alignment = StringAlignment.Far; 445 | break; 446 | case Position.CenterInGraph: 447 | descriptiveTextFormat.Alignment = StringAlignment.Center; 448 | break; 449 | } 450 | 451 | Font font = (settings.DescriptiveTextOverrideFont ? 452 | settings.DescriptiveTextFont : 453 | state.LayoutSettings.TextFont); 454 | Brush brush = new SolidBrush(settings.DescriptiveTextOverrideColor ? 455 | settings.DescriptiveTextColor : 456 | state.LayoutSettings.TextColor); 457 | RectangleF rect = descriptiveNextToGraph ? g.ClipBounds : graphRect; 458 | rect.X += 5; 459 | rect.Width -= 10; 460 | g.DrawString(settings.DescriptiveText, font, brush, rect, descriptiveTextFormat); 461 | } 462 | 463 | // draw value text 464 | if (settings.ValueTextPosition != Position.None) 465 | { 466 | switch (settings.ValueTextPosition) 467 | { 468 | case Position.Left: 469 | case Position.LeftInGraph: 470 | valueTextFormat.Alignment = StringAlignment.Near; 471 | break; 472 | case Position.Right: 473 | case Position.RightInGraph: 474 | valueTextFormat.Alignment = StringAlignment.Far; 475 | break; 476 | case Position.CenterInGraph: 477 | valueTextFormat.Alignment = StringAlignment.Center; 478 | break; 479 | } 480 | 481 | Font font = (settings.ValueTextOverrideFont ? 482 | settings.ValueTextFont : 483 | state.LayoutSettings.TextFont); 484 | Brush brush = new SolidBrush(settings.ValueTextOverrideColor ? 485 | settings.ValueTextColor : 486 | state.LayoutSettings.TextColor); 487 | RectangleF rect = valueNextToGraph ? g.ClipBounds : graphRect; 488 | rect.X += 5; 489 | rect.Width -= 10; 490 | string str; 491 | if (!settings.UnitsConversionEnabled) 492 | { 493 | str = currentValue.ToString("n" + settings.ValueTextDecimals); 494 | if (settings.LocalMax) 495 | { 496 | str += " (" + LocalMax.ToString("n" + settings.ValueTextDecimals) + ")"; 497 | } 498 | } 499 | else 500 | { 501 | str = convertUnits(currentValue, settings.ValueTextDecimals); 502 | if (settings.LocalMax) 503 | { 504 | str += " (" + convertUnits(LocalMax, settings.ValueTextDecimals) + ")"; 505 | } 506 | } 507 | g.DrawString(str, font, brush, rect, valueTextFormat); 508 | } 509 | } 510 | 511 | private string convertUnits(float currentValue, int valueTextDecimals) 512 | { 513 | float multiplier = settings.MeterInGameUnits; 514 | Units unitsSelected = settings.UnitsUsed; 515 | switch (unitsSelected) 516 | { 517 | case Units.None: 518 | { 519 | return currentValue.ToString("n" + valueTextDecimals) + " (u)"; 520 | } 521 | case Units.MeterPerSecond: 522 | { 523 | float returnValue = currentValue / multiplier; 524 | return returnValue.ToString("n" + valueTextDecimals) + " (m/s)"; 525 | } 526 | case Units.KilometersPerHour: 527 | { 528 | float returnValue = (currentValue / multiplier) * 3.6f; 529 | return returnValue.ToString("n" + valueTextDecimals) + " (km/h)"; 530 | } 531 | case Units.MilesPerHour: 532 | { 533 | float returnValue = (currentValue / multiplier) * 2.23693629f; 534 | return returnValue.ToString("n" + valueTextDecimals) + " (mph)"; 535 | } 536 | case Units.FeetPerSecond: 537 | { 538 | float returnValue = (currentValue / multiplier) * 3.2808399f; 539 | return returnValue.ToString("n" + valueTextDecimals) + " (fps)"; 540 | } 541 | default: 542 | return currentValue.ToString("n" + valueTextDecimals) + " (u)"; 543 | } 544 | 545 | } 546 | 547 | private void DrawBackground(Graphics g, LiveSplitState state, float width, float height) 548 | { 549 | if ((settings.BackgroundColor.A == 0 || settings.BackgroundGradient != GradientType.Plain) && 550 | settings.BackgroundColor2.A == 0) 551 | { 552 | bool horizontal = (settings.BackgroundGradient == GradientType.Horizontal); 553 | bool plain = (settings.BackgroundGradient == GradientType.Plain); 554 | LinearGradientBrush gradientBrush = new LinearGradientBrush( 555 | new PointF(0, 0), 556 | horizontal ? new PointF(width, 0) : new PointF(0, height), 557 | settings.BackgroundColor, 558 | plain ? settings.BackgroundColor : settings.BackgroundColor2); 559 | g.FillRectangle(gradientBrush, 0, 0, width, height); 560 | } 561 | } 562 | 563 | public void DrawVertical(Graphics g, LiveSplitState state, float width, Region clipRegion) 564 | { 565 | DrawBackground(g, state, width, VerticalHeight); 566 | DrawGraph(g, state, width, VerticalHeight); 567 | } 568 | 569 | public void DrawHorizontal(Graphics g, LiveSplitState state, float height, Region clipRegion) 570 | { 571 | DrawBackground(g, state, HorizontalWidth, height); 572 | DrawGraph(g, state, HorizontalWidth, height); 573 | } 574 | 575 | public void Update(IInvalidator invalidator, LiveSplitState state, float width, float height, LayoutMode mode) 576 | { 577 | if (process != null && settings.Pointer != null && !process.HasExited && 578 | string.Equals(process.ProcessName, settings.ProcessName, StringComparison.OrdinalIgnoreCase)) 579 | { 580 | switch (settings.ValueType) 581 | { 582 | case MemoryType.Float: 583 | { 584 | currentValue = settings.Pointer.Deref(process); 585 | } 586 | break; 587 | case MemoryType.Int: 588 | { 589 | currentValue = settings.Pointer.Deref(process); 590 | } 591 | break; 592 | case MemoryType.FloatVec2: 593 | { 594 | currentValue = (float)settings.Pointer.Deref(process).Norm; 595 | } 596 | break; 597 | case MemoryType.FloatVec3: 598 | { 599 | currentValue = (float)settings.Pointer.Deref(process).Norm; 600 | } 601 | break; 602 | case MemoryType.IntVec2: 603 | { 604 | currentValue = (float)settings.Pointer.Deref(process).Norm; 605 | } 606 | 607 | break; 608 | case MemoryType.IntVec3: 609 | { 610 | currentValue = (float)settings.Pointer.Deref(process).Norm; 611 | } 612 | 613 | break; 614 | case MemoryType.FloatVec2XZY: 615 | { 616 | currentValue = (float)settings.Pointer.Deref(process).Norm; 617 | } 618 | break; 619 | case MemoryType.IntVec2XZY: 620 | { 621 | currentValue = (float)settings.Pointer.Deref(process).Norm; 622 | } 623 | break; 624 | } 625 | 626 | if (invalidator != null) 627 | { 628 | invalidator.Invalidate(0, 0, width, height); 629 | } 630 | } 631 | else 632 | { 633 | process = System.Diagnostics.Process.GetProcessesByName(settings.ProcessName).FirstOrDefault(); 634 | } 635 | } 636 | 637 | public System.Windows.Forms.Control GetSettingsControl(LayoutMode mode) 638 | { 639 | return settings; 640 | } 641 | 642 | public void SetSettings(System.Xml.XmlNode settings) 643 | { 644 | this.settings.SetSettings(settings); 645 | } 646 | 647 | public System.Xml.XmlNode GetSettings(System.Xml.XmlDocument document) 648 | { 649 | return settings.GetSettings(document); 650 | } 651 | 652 | public int GetSettingsHashCode() 653 | { 654 | return settings.GetSettingsHashCode(); 655 | } 656 | 657 | protected virtual void Dispose(bool disposing) 658 | { 659 | bmpBuffer.Dispose(); 660 | valueTextFormat.Dispose(); 661 | descriptiveTextFormat.Dispose(); 662 | graphBrush.Dispose(); 663 | settings.Dispose(); 664 | } 665 | 666 | public void Dispose() 667 | { 668 | Dispose(true); 669 | GC.SuppressFinalize(this); 670 | } 671 | } 672 | } 673 | -------------------------------------------------------------------------------- /Components/LiveSplit.MemoryGraph.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kugelrund/LiveSplit.MemoryGraph/503456ef592d5603662eab718dbc07f13d6bfccc/Components/LiveSplit.MemoryGraph.dll -------------------------------------------------------------------------------- /Components/update.LiveSplit.MemoryGraph.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Improved handling of float point decimal seperator for minimum and maximum value of the graph. 8 | Corrected typeos, cause Sui is dumb, ok? 9 | Minimum value can now be specified in XML by using minimumValue node. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Fix crash due to overflow if pointer gives unreasonable values. 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Allow using absolute base memory address by setting module to [absolute_base]. 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Ignore case of process name. 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Fix overflow of vector length when using integer types. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | Fix several bugs in the settings editor. 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Clean up XML game list by introducing different variations for games (by TravisDaily). 58 | Fix wrong conversion to feet (by TravisDaily). 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Add support for more than 2 graph colors (by TravisDaily). 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Apply fix by TravisDaily for proper graph scaling if the minimum value is greater than 0. 75 | Add 'LocalMax' functionality by TravisDaily that shows the value of the visible maximum of the graph in brackets behind the current value. 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Hotfix for last update. 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | Fix settings layout not scaling properly for high DPI settings. 92 | More cleanups for the settings layout. 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Fixed GUI not updating the Unit Conversion value when a position was chosen from XML file. 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Added unit converter value reading from XML database (specify as "unitConverter" float value with period instead of coma as decimal separator). 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | Added unit conversion to m/s, km/h, mph and feet per second (fps). 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Fixed bug, which would prevent FloatVec2XZY and IntVec2XZY from being loaded from XML file. 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | Added a link label display in case modifying game files is required. 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | Fixed ComboBox not being updated when updating XML. 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | Fake particles in Sonic Graph, should now use brush specified in settings. 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | Fixed transitions between walking and idle state. 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | Added half-speed animation for Sonic. 165 | Changed frame drawing order to display fake particles in front (due to transparency issues). 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | Added Vector Based graph. 174 | Are you Sonic? 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | Fixed graph going out of intended drawing area. 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | Option to disable Silly colors (related to gradient colors for values over the specified maximum). 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | Support for vectors in XZY format (position, height, depth) 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | XML list 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | Initial release. 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /Downloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Net; 6 | using System.Reflection; 7 | using System.Windows.Forms; 8 | 9 | namespace LiveSplit.MemoryGraph 10 | { 11 | class MonkeyDownloadingXML 12 | { 13 | public bool DownloadNew() 14 | { 15 | string uriToSource = "https://raw.githubusercontent.com/kugelrund/LiveSplit.MemoryGraph/master/XML/"; 16 | string xmlFileName = Settings.listsFile; 17 | bool result = false; 18 | string downloadedFileLocation = ""; 19 | if (CheckIfXMLExists(uriToSource+ xmlFileName)) 20 | { 21 | result = DownloadFiles(uriToSource, xmlFileName, out downloadedFileLocation); 22 | } 23 | else 24 | { 25 | MessageBox.Show("No XML server found on a server", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 26 | return false; 27 | } 28 | 29 | if (result) 30 | { 31 | string currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 32 | File.Copy(downloadedFileLocation, Path.Combine(currentPath,xmlFileName), true); 33 | Debug.WriteLine("COPY: " + downloadedFileLocation + " --> " + Path.Combine(currentPath, xmlFileName)); 34 | MessageBox.Show("Successfully downloaded new XML from a server", "Success!", MessageBoxButtons.OK, MessageBoxIcon.Information); 35 | return true; 36 | } 37 | else 38 | { 39 | MessageBox.Show("Failed to download the file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 40 | return false; 41 | } 42 | } 43 | 44 | private bool DownloadFiles(string sourceLocation, string file, out string tempLocation) 45 | { 46 | WebClient wbClient = new WebClient(); 47 | wbClient.DownloadFileCompleted += WbClient_DownloadFileCompleted; 48 | tempLocation = Path.GetTempFileName(); 49 | 50 | Uri tempUri; 51 | Uri.TryCreate(sourceLocation + file, UriKind.Absolute, out tempUri); 52 | 53 | try { wbClient.DownloadFile(tempUri, tempLocation); } 54 | catch { return false; } 55 | 56 | FileInfo tempFileInfo = new FileInfo(tempLocation); 57 | if (tempFileInfo.Length == 0) 58 | { 59 | return false; 60 | } 61 | 62 | return true; 63 | } 64 | 65 | private void WbClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) 66 | { 67 | if (e.Error != null) 68 | { 69 | Trace.WriteLine("Error downloading file!"); 70 | } 71 | else 72 | { 73 | Trace.WriteLine("Downloading file completed."); 74 | } 75 | } 76 | 77 | 78 | private bool CheckIfXMLExists(string uri) 79 | { 80 | try 81 | { 82 | HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri); 83 | HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); 84 | resp.Close(); 85 | 86 | return resp.StatusCode == HttpStatusCode.OK; 87 | } 88 | catch { return false; } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Factory.cs: -------------------------------------------------------------------------------- 1 | using LiveSplit.UI.Components; 2 | using System; 3 | 4 | namespace LiveSplit.MemoryGraph 5 | { 6 | public class Factory : IComponentFactory 7 | { 8 | public string ComponentName 9 | { 10 | get { return "MemoryGraph"; } 11 | } 12 | public ComponentCategory Category 13 | { 14 | get { return ComponentCategory.Information; } 15 | } 16 | public string Description 17 | { 18 | get { return "Shows Memory"; } 19 | } 20 | public IComponent Create(Model.LiveSplitState state) 21 | { 22 | return new Component(state); 23 | } 24 | public string UpdateName 25 | { 26 | get { return ComponentName; } 27 | } 28 | public string UpdateURL 29 | { 30 | get { return "https://raw.githubusercontent.com/kugelrund/LiveSplit.MemoryGraph/master/"; } 31 | } 32 | public Version Version 33 | { 34 | get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; } 35 | } 36 | public string XMLURL 37 | { 38 | get { return UpdateURL + "Components/update.LiveSplit.MemoryGraph.xml"; } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 kugelrund 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 | -------------------------------------------------------------------------------- /LiveSplit.MemoryGraph.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EC265AD9-9429-42CE-B27B-F45132029F38} 8 | Library 9 | Properties 10 | LiveSplit.MemoryGraph 11 | LiveSplit.MemoryGraph 12 | v4.8.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | C:\LiveSplit\Components\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | Components\ 30 | TRACE 31 | prompt 32 | 4 33 | AllRules.ruleset 34 | false 35 | 36 | 37 | 38 | False 39 | C:\LiveSplit\CustomFontDialog.dll 40 | False 41 | 42 | 43 | C:\LiveSplit\LiveSplit.Core.dll 44 | False 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | C:\LiveSplit\UpdateManager.dll 57 | False 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | True 67 | True 68 | Resources.resx 69 | 70 | 71 | UserControl 72 | 73 | 74 | Settings.cs 75 | 76 | 77 | 78 | UserControl 79 | 80 | 81 | TextStyleOverrideControl.cs 82 | 83 | 84 | 85 | 86 | 87 | 88 | ResXFileCodeGenerator 89 | Resources.Designer.cs 90 | 91 | 92 | Settings.cs 93 | 94 | 95 | TextStyleOverrideControl.cs 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /LiveSplit.MemoryGraph.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveSplit.MemoryGraph", "LiveSplit.MemoryGraph.csproj", "{EC265AD9-9429-42CE-B27B-F45132029F38}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EC265AD9-9429-42CE-B27B-F45132029F38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EC265AD9-9429-42CE-B27B-F45132029F38}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EC265AD9-9429-42CE-B27B-F45132029F38}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EC265AD9-9429-42CE-B27B-F45132029F38}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using LiveSplit.MemoryGraph; 2 | using LiveSplit.UI.Components; 3 | using System.Reflection; 4 | using System.Runtime.InteropServices; 5 | 6 | // Allgemeine Informationen über eine Assembly werden über die folgenden 7 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 8 | // die mit einer Assembly verknüpft sind. 9 | [assembly: AssemblyTitle("LiveSplit.MemoryGraph")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("LiveSplit.MemoryGraph")] 14 | [assembly: AssemblyCopyright("")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 19 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 20 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 21 | [assembly: ComVisible(false)] 22 | 23 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 24 | [assembly: Guid("1187283c-870a-455c-a029-58be9f62a64f")] 25 | 26 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 27 | // 28 | // Hauptversion 29 | // Nebenversion 30 | // Buildnummer 31 | // Revision 32 | // 33 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 34 | // übernehmen, indem Sie "*" eingeben: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("1.5.3")] 37 | [assembly: AssemblyFileVersion("1.5.3")] 38 | 39 | [assembly: ComponentFactory(typeof(Factory))] -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace LiveSplit.MemoryGraph.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LiveSplit.MemoryGraph.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap sonic_spirtes { 67 | get { 68 | object obj = ResourceManager.GetObject("sonic_spirtes", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\SpritesHandling\spirtes.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LiveSplit.MemoryGraph 2 | ===================== 3 | Shows a value of a game's memory in a graph. 4 | 5 | ![preview.png](/images/preview.png) 6 | 7 | Manual Installation 8 | ------------------- 9 | Download "Components/LiveSplit.MemoryGraph.dll" and place it into the subdirectory "Components" of your LiveSplit folder. You can then add it to your layout (category "Information"). 10 | 11 | Usage 12 | ----- 13 | You need to tell the plugin, where in the game's memory it can find the value that you want to display. 14 | For many games, some kind people have already looked for values worth displaying and added them to our [XML file](https://github.com/kugelrund/LiveSplit.MemoryGraph/blob/master/XML/LiveSplit.MemoryGraph.Games.xml). 15 | The settings of the component can be used to download that XML file, which then allows to select a game and one of its known values via the dropdown menu. 16 | 17 | If the address for the value that you want to display is not there yes, you can try to find it by yourself using for example the free program Cheat Engine. 18 | The process is very similar to that when writing your own [AutoSplitter](https://github.com/LiveSplit/LiveSplit.AutoSplitters/blob/master/README.md) for LiveSplit, so it might be worth to check out some of that documentation. If you were successful in finding your value, consider adding it to our [XML file](https://github.com/kugelrund/LiveSplit.MemoryGraph/blob/master/XML/LiveSplit.MemoryGraph.Games.xml) via a pull request, so that other people can use it in the future. 19 | 20 | Credits 21 | ----- 22 | * [SphereMJ / kugelrund](https://www.twitch.tv/spheremj) 23 | * [SuicideMachine](https://www.twitch.tv/suicidemachine) 24 | * [TravisDaily](https://github.com/TravisDaily) 25 | -------------------------------------------------------------------------------- /Settings.cs: -------------------------------------------------------------------------------- 1 | using LiveSplit.ComponentUtil; 2 | using LiveSplit.UI; 3 | using System; 4 | using System.IO; 5 | using System.Xml; 6 | using System.ComponentModel; 7 | using System.Drawing; 8 | using System.Globalization; 9 | using System.Windows.Forms; 10 | using System.Collections.Generic; 11 | using System.Diagnostics; 12 | using System.Linq; 13 | 14 | namespace LiveSplit.MemoryGraph 15 | { 16 | enum MemoryType 17 | { 18 | [Description("Float")] 19 | Float, 20 | [Description("Int")] 21 | Int, 22 | [Description("FloatVec2")] 23 | FloatVec2, 24 | [Description("IntVec2")] 25 | IntVec2, 26 | [Description("FloatVec3")] 27 | FloatVec3, 28 | [Description("IntVec3")] 29 | IntVec3, 30 | [Description("FloatVec2XZY")] 31 | FloatVec2XZY, 32 | [Description("IntVec2XZY")] 33 | IntVec2XZY 34 | } 35 | 36 | enum GraphStyle 37 | { 38 | [Description("Single Bar")] 39 | SingleBar, 40 | [Description("Filled Graph")] 41 | FilledGraph, 42 | [Description("Polygonal Graph")] 43 | Polygonal, 44 | [Description("Polygonal Overflowing Graph")] 45 | PolygonalOverflow, 46 | [Description("Sonic")] 47 | Sonic 48 | } 49 | 50 | enum GradientType 51 | { 52 | [Description("Plain")] 53 | Plain, 54 | [Description("Vertical")] 55 | Vertical, 56 | [Description("Horizontal")] 57 | Horizontal 58 | } 59 | 60 | enum GraphGradientType 61 | { 62 | [Description("Plain")] 63 | Plain, 64 | [Description("Vertical")] 65 | Vertical, 66 | [Description("Horizontal")] 67 | Horizontal, 68 | [Description("By Value")] 69 | ByValue 70 | } 71 | 72 | enum Position 73 | { 74 | [Description("None")] 75 | None, 76 | [Description("Left")] 77 | Left, 78 | [Description("Right")] 79 | Right, 80 | [Description("Center in Graph")] 81 | CenterInGraph, 82 | [Description("Left in Graph")] 83 | LeftInGraph, 84 | [Description("Right in Graph")] 85 | RightInGraph 86 | } 87 | 88 | enum Units 89 | { 90 | [Description("Game Units (u)")] 91 | None, 92 | [Description("Metres per second (m/s)")] 93 | MeterPerSecond, 94 | [Description("Kilometers per hour (km/h)")] 95 | KilometersPerHour, 96 | [Description("Miles per hour (mph)")] 97 | MilesPerHour, 98 | [Description("Feet per second (fps)")] 99 | FeetPerSecond, 100 | } 101 | 102 | partial class Settings : UserControl 103 | { 104 | CultureInfo ci = new CultureInfo(System.Threading.Thread.CurrentThread.CurrentCulture.Name); 105 | List gamesOnTheList = new List(); 106 | static string componentsFolder = "Components"; 107 | public static string listsFile = "LiveSplit.MemoryGraph.Games.xml"; 108 | public static string ListsFilePath 109 | { 110 | get 111 | { 112 | var listsFilePath = Path.Combine(componentsFolder, listsFile); 113 | if (File.Exists(listsFilePath)) 114 | { 115 | return listsFilePath; 116 | } 117 | else 118 | { 119 | // If the new file hasn't been downloaded, keep using the old one as a fallback. 120 | return Path.Combine(componentsFolder, "LiveSplit.MemoryGraphList.xml"); 121 | } 122 | } 123 | } 124 | 125 | public Color BackgroundColor { get; set; } 126 | public Color BackgroundColor2 { get; set; } 127 | public List GraphColors { get; set; } = new List(); 128 | /// 129 | /// Yields the GraphColors. If there are no GraphColors, yields Color.Red. 130 | /// 131 | public IEnumerable GraphColorsEnumeration => GraphColors.Any() ? GraphColors : new List { Color.Red }; 132 | 133 | public float MinimumValue { get; set; } 134 | public float MaximumValue { get; set; } 135 | public int GraphWidth { get; set; } 136 | public int GraphHeight { get; set; } 137 | public int HorizontalMargins { get; set; } 138 | public int VerticalMargins { get; set; } 139 | 140 | public GraphStyle GraphStyle { get; set; } 141 | public GradientType BackgroundGradient { get; set; } 142 | public GraphGradientType GraphGradient { get; set; } 143 | public bool GraphSillyColors { get; set; } 144 | public Position ValueTextPosition { get; set; } 145 | public Position DescriptiveTextPosition { get; set; } 146 | public bool LocalMax { get; set; } 147 | public MemoryType ValueType { get; set; } 148 | public bool UnitsConversionEnabled { get; set; } 149 | public Units UnitsUsed { get; set; } 150 | public float MeterInGameUnits { get; set; } 151 | 152 | 153 | public Color DescriptiveTextColor { get; set; } 154 | public Font DescriptiveTextFont { get; set; } 155 | public bool DescriptiveTextOverrideColor { get; set; } 156 | public bool DescriptiveTextOverrideFont { get; set; } 157 | 158 | public Color ValueTextColor { get; set; } 159 | public Font ValueTextFont { get; set; } 160 | public bool ValueTextOverrideColor { get; set; } 161 | public bool ValueTextOverrideFont { get; set; } 162 | public int ValueTextDecimals { get; set; } 163 | 164 | public string ProcessName { get; set; } 165 | public string DescriptiveText { get; set; } 166 | 167 | public string AdditionalRequirement { get; set; } 168 | 169 | public DeepPointer Pointer { get; set; } 170 | 171 | public Settings() 172 | { 173 | InitializeComponent(); 174 | 175 | if (File.Exists(ListsFilePath)) 176 | { 177 | loadXML(); 178 | } 179 | else 180 | { 181 | ComboBox_ListOfGames.Enabled = false; 182 | } 183 | 184 | 185 | HandleDestroyed += UpdatePointer; 186 | 187 | BackgroundColor = Color.Transparent; 188 | BackgroundColor2 = Color.Transparent; 189 | MinimumValue = 0; 190 | MaximumValue = 1000; 191 | GraphWidth = 200; 192 | GraphHeight = 30; 193 | HorizontalMargins = 0; 194 | VerticalMargins = 0; 195 | GraphStyle = GraphStyle.SingleBar; 196 | BackgroundGradient = GradientType.Plain; 197 | GraphGradient = GraphGradientType.Plain; 198 | GraphSillyColors = true; 199 | ValueTextPosition = Position.LeftInGraph; 200 | DescriptiveTextPosition = Position.Left; 201 | LocalMax = false; 202 | UnitsConversionEnabled = false; 203 | UnitsUsed = Units.None; 204 | MeterInGameUnits = 1.0f; 205 | ValueType = MemoryType.Float; 206 | ValueTextDecimals = 0; 207 | ProcessName = ""; 208 | DescriptiveText = ""; 209 | AdditionalRequirement = ""; 210 | DescriptiveTextFont = overrideControlDescriptiveText.OverridingFont; 211 | ValueTextFont = overrideControlValueText.OverridingFont; 212 | 213 | btnBackgroundColor1.DataBindings.Add("BackColor", this, nameof(BackgroundColor), false, DataSourceUpdateMode.OnPropertyChanged); 214 | btnBackgroundColor2.DataBindings.Add("BackColor", this, nameof(BackgroundColor2), false, DataSourceUpdateMode.OnPropertyChanged); 215 | 216 | txtMinimumValue.DataBindings.Add("Text", this, nameof(MinimumValue), false, DataSourceUpdateMode.OnPropertyChanged); 217 | txtMaximumValue.DataBindings.Add("Text", this, nameof(MaximumValue), false, DataSourceUpdateMode.OnPropertyChanged); 218 | numWidth.DataBindings.Add("Value", this, nameof(GraphWidth), false, DataSourceUpdateMode.OnPropertyChanged); 219 | numHeight.DataBindings.Add("Value", this, nameof(GraphHeight), false, DataSourceUpdateMode.OnPropertyChanged); 220 | numHorizontalMargins.DataBindings.Add("Value", this, nameof(HorizontalMargins), false, DataSourceUpdateMode.OnPropertyChanged); 221 | numVerticalMargins.DataBindings.Add("Value", this, nameof(VerticalMargins), false, DataSourceUpdateMode.OnPropertyChanged); 222 | numValueTextDecimals.DataBindings.Add("Value", this, nameof(ValueTextDecimals), false, DataSourceUpdateMode.OnPropertyChanged); 223 | 224 | cmbGraphStyle.DataBindings.Add("SelectedValue", this, nameof(GraphStyle), false, DataSourceUpdateMode.OnPropertyChanged); 225 | cmbBackgroundGradientType.DataBindings.Add("SelectedValue", this, nameof(BackgroundGradient), false, DataSourceUpdateMode.OnPropertyChanged); 226 | cmbGraphGradientType.DataBindings.Add("SelectedValue", this, nameof(GraphGradient), false, DataSourceUpdateMode.OnPropertyChanged); 227 | colorsCBSillyColors.DataBindings.Add("Checked", this, nameof(GraphSillyColors), false, DataSourceUpdateMode.OnPropertyChanged); 228 | cmbValueTextPosition.DataBindings.Add("SelectedValue", this, nameof(ValueTextPosition), false, DataSourceUpdateMode.OnPropertyChanged); 229 | 230 | unitConversionCB.DataBindings.Add("Checked", this, nameof(UnitsConversionEnabled), false, DataSourceUpdateMode.OnPropertyChanged); 231 | cmbUnitsUsed.DataBindings.Add("SelectedValue", this, nameof(UnitsUsed), false, DataSourceUpdateMode.OnPropertyChanged); 232 | tbMeterToGameUnit.DataBindings.Add("Text", this, nameof(MeterInGameUnits), true, DataSourceUpdateMode.OnPropertyChanged, null, "f"); 233 | 234 | cmbDescriptiveTextPosition.DataBindings.Add("SelectedValue", this, nameof(DescriptiveTextPosition), false, DataSourceUpdateMode.OnPropertyChanged); 235 | localMaxCB.DataBindings.Add("Checked", this, nameof(LocalMax), false, DataSourceUpdateMode.OnPropertyChanged); 236 | cmbType.DataBindings.Add("SelectedValue", this, nameof(ValueType), false, DataSourceUpdateMode.OnPropertyChanged); 237 | 238 | txtProcessName.DataBindings.Add("Text", this, nameof(ProcessName)); 239 | txtDescriptiveText.DataBindings.Add("Text", this, nameof(DescriptiveText)); 240 | linkLabel_AdditionalFiles.DataBindings.Add("Text", this, nameof(AdditionalRequirement)); 241 | 242 | overrideControlDescriptiveText.DataBindings.Add("OverridingColor", this, nameof(DescriptiveTextColor), false, DataSourceUpdateMode.OnPropertyChanged); 243 | overrideControlDescriptiveText.DataBindings.Add("OverridingFont", this, nameof(DescriptiveTextFont), false, DataSourceUpdateMode.OnPropertyChanged); 244 | overrideControlDescriptiveText.DataBindings.Add("OverrideColor", this, nameof(DescriptiveTextOverrideColor), false, DataSourceUpdateMode.OnPropertyChanged); 245 | overrideControlDescriptiveText.DataBindings.Add("OverrideFont", this, nameof(DescriptiveTextOverrideFont), false, DataSourceUpdateMode.OnPropertyChanged); 246 | 247 | overrideControlValueText.DataBindings.Add("OverridingColor", this, nameof(ValueTextColor), false, DataSourceUpdateMode.OnPropertyChanged); 248 | overrideControlValueText.DataBindings.Add("OverridingFont", this, nameof(ValueTextFont), false, DataSourceUpdateMode.OnPropertyChanged); 249 | overrideControlValueText.DataBindings.Add("OverrideColor", this, nameof(ValueTextOverrideColor), false, DataSourceUpdateMode.OnPropertyChanged); 250 | overrideControlValueText.DataBindings.Add("OverrideFont", this, nameof(ValueTextOverrideFont), false, DataSourceUpdateMode.OnPropertyChanged); 251 | 252 | AddComboboxDataSources(); 253 | } 254 | 255 | private void UpdatePointer(object sender, EventArgs e) 256 | { 257 | int baseAddress; 258 | int[] offsets; 259 | 260 | if (Utils.TryParseHex(txtBase.Text, out baseAddress)) 261 | { 262 | if (!string.IsNullOrWhiteSpace(txtOffsets.Text)) 263 | { 264 | string[] offsetStrings = txtOffsets.Text.Split(','); 265 | offsets = new int[offsetStrings.Length]; 266 | int j = 0; 267 | foreach (string offset in offsetStrings) 268 | { 269 | Utils.TryParseHex(offset.Trim(), out offsets[j]); 270 | j += 1; 271 | } 272 | } 273 | else 274 | { 275 | offsets = new int[0]; 276 | } 277 | 278 | if (string.IsNullOrWhiteSpace(txtModule.Text)) 279 | { 280 | Pointer = new DeepPointer(baseAddress, offsets); 281 | } 282 | else if (txtModule.Text == "[absolute_base]") 283 | { 284 | Pointer = new DeepPointer(new IntPtr(baseAddress), offsets); 285 | } 286 | else 287 | { 288 | Pointer = new DeepPointer(txtModule.Text, baseAddress, offsets); 289 | } 290 | } 291 | else 292 | { 293 | Pointer = null; 294 | } 295 | } 296 | 297 | private void ColorButtonClick(object sender, EventArgs e) 298 | { 299 | SettingsHelper.ColorButtonClick((Button)sender, this); 300 | } 301 | 302 | private List