diff --git a/.editorconfig b/.editorconfig
index 8e747e9..a784f75 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -237,6 +237,7 @@ dotnet_diagnostic.IDE0008.severity = silent
dotnet_diagnostic.IDE0055.severity = silent
dotnet_diagnostic.IDE0058.severity = silent
dotnet_diagnostic.IDE0160.severity = none
+dotnet_diagnostic.IDE0210.severity = none
dotnet_diagnostic.CA1707.severity = silent
dotnet_diagnostic.CA1852.severity = none
dotnet_diagnostic.CA1805.severity = none
diff --git a/.gitignore b/.gitignore
index a2b8d83..1b33c6c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -402,3 +402,4 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
+.idea
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj b/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj
new file mode 100644
index 0000000..c9bf1a8
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Console/DotTiled.Example.Console.csproj
@@ -0,0 +1,38 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs
new file mode 100644
index 0000000..234012a
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs
@@ -0,0 +1,81 @@
+using System.Reflection;
+using DotTiled.Serialization;
+
+namespace DotTiled.Example;
+
+public class Program
+{
+ private static void Main()
+ {
+ Quick();
+ Manual();
+ }
+
+ // QUICK START
+ // Automatic and easy way to load tilemaps.
+ private static void Quick()
+ {
+ var loader = Loader.Default();
+ var map = loader.LoadMap("tilemap.tmx");
+
+ // You can do stuff with it like...
+ Console.WriteLine($"Tile width and height: {map.TileWidth}x{map.TileHeight}");
+ TileLayer layer0 = (TileLayer)map.Layers[0]; // Get a layer
+ Console.WriteLine($"Tile in layer 0 at 0, 0: {layer0.Data.Value.GlobalTileIDs.Value[0]}");
+ }
+
+ // MANUAL
+ // Manually load a map, if you need to load from a custom source
+ private static void Manual()
+ {
+ using var mapFileReader = new StreamReader("tilemap.tmx");
+ var mapString = mapFileReader.ReadToEnd();
+ using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
+ var map = mapReader.ReadMap();
+
+ // Now do some other stuff with it...
+ StringProperty hello = map.GetProperty("hello");
+ Console.WriteLine($"Layer 1 name: {map.Layers[0].Name}");
+ Console.WriteLine($"Property 'hello': {hello.Value}");
+
+ // Now with tileset
+ Tileset tileset = map.Tilesets[0];
+ Console.WriteLine($"Tileset 0 source: {tileset.Source.Value}");
+ Console.WriteLine($"Tileset 0 image 0 source: {tileset.Image.Value.Source.Value}");
+ }
+
+ // This function is responsible for loading all tilesets required by a tilemap, if you
+ // want to use a custom source.
+ private static Tileset ResolveTileset(string source)
+ {
+ // Read a file from assembly
+ // You can use any other source for files, eg. compressed archive, or even file from internet.
+ using Stream? tilesetStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.embedded-{source}")
+ ?? throw new FileLoadException($"{source} not found in assembly.");
+ string tilesetString = new StreamReader(tilesetStream).ReadToEnd();
+
+ using TilesetReader tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); // Parse loaded tileset.
+
+ return tilesetReader.ReadTileset(); // Return loaded tileset
+ }
+
+ // This is pretty similar to above, but instead it loads templates, not tilesets.
+ private static Template ResolveTemplate(string source)
+ {
+ using Stream? templateStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"DotTiled.Example.{source}")
+ ?? throw new FileLoadException($"{source} not found in assembly.");
+ string templateString = new StreamReader(templateStream).ReadToEnd();
+
+ using TemplateReader templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType);
+ return templateReader.ReadTemplate();
+ }
+
+ private static ICustomTypeDefinition ResolveCustomType(string name)
+ {
+ ICustomTypeDefinition[] allDefinedTypes =
+ [
+ new CustomClassDefinition() { Name = "a" },
+ ];
+ return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException();
+ }
+}
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx
new file mode 100644
index 0000000..7a9a294
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tilemap.tmx
@@ -0,0 +1,12 @@
+
+
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png
new file mode 100644
index 0000000..67b9eee
Binary files /dev/null and b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.png differ
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.tsx b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.tsx
new file mode 100644
index 0000000..9fc1b9b
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Console/embedded-tileset.tsx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/tilemap.tmx b/src/DotTiled.Examples/DotTiled.Example.Console/tilemap.tmx
new file mode 100644
index 0000000..7a9a294
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Console/tilemap.tmx
@@ -0,0 +1,12 @@
+
+
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/tileset.png b/src/DotTiled.Examples/DotTiled.Example.Console/tileset.png
new file mode 100644
index 0000000..67b9eee
Binary files /dev/null and b/src/DotTiled.Examples/DotTiled.Example.Console/tileset.png differ
diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/tileset.tsx b/src/DotTiled.Examples/DotTiled.Example.Console/tileset.tsx
new file mode 100644
index 0000000..9fc1b9b
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Console/tileset.tsx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/.gitattributes b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitattributes
new file mode 100644
index 0000000..8ad74f7
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/.gitignore b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitignore
new file mode 100644
index 0000000..4709183
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/.gitignore
@@ -0,0 +1,2 @@
+# Godot 4+ specific ignores
+.godot/
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj
new file mode 100644
index 0000000..99e4b77
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.csproj
@@ -0,0 +1,11 @@
+
+
+ net8.0
+ net7.0
+ net8.0
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.sln b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.sln
new file mode 100644
index 0000000..c707fa2
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/DotTiled.Example.Godot.sln
@@ -0,0 +1,19 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Godot", "DotTiled.Example.Godot.csproj", "{61468FCF-ACC1-4E3B-B4B4-270279E45BF5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ ExportDebug|Any CPU = ExportDebug|Any CPU
+ ExportRelease|Any CPU = ExportRelease|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
+ {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
+ {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
+ {61468FCF-ACC1-4E3B-B4B4-270279E45BF5}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs
new file mode 100644
index 0000000..5ad960b
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using DotTiled.Serialization;
+using Godot;
+
+namespace DotTiled.Example.Godot;
+public partial class MapParser : Node2D
+{
+ public override void _Ready()
+ {
+ // Load map
+ var mapString = FileAccess.Open("res://tilemap.tmx", FileAccess.ModeFlags.Read).GetAsText(); //Get file from Godot filesystem
+ using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
+ var map = mapReader.ReadMap();
+
+ TileLayer layer0 = (TileLayer)map.Layers[0];
+
+ for (int y = 0; y < layer0.Height; y++)
+ {
+ for (int x = 0; x < layer0.Width; x++)
+ {
+ uint tile = layer0.Data.Value.GlobalTileIDs.Value[(y * layer0.Width) + x];
+ if (tile == 0) continue; // If block is 0, i.e. air, then continue
+
+ // Load actual block from Godot resources
+ Node2D block = (Node2D)GD.Load($"res://blocks/{tile}.tscn").Instantiate();
+
+ // Calculate where block should be
+ Vector2I scale = (Vector2I)block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Scale;
+ int blockX = (block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetWidth() * scale.X / 2) +
+ (x * block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetWidth() * scale.X);
+ int blockY = (block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetHeight() * scale.Y / 2) +
+ (y * block.GetNode(tile.ToString(CultureInfo.CurrentCulture)).Texture.GetHeight() * scale.Y);
+ block.Position = new Vector2(blockX, blockY);
+
+ // Add block to current scene
+ AddChild(block);
+ GD.Print($"{blockX}, {blockY}: {tile}");
+ }
+ }
+ }
+
+ private Tileset ResolveTileset(string source)
+ {
+ string tilesetString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText();
+ using TilesetReader tilesetReader =
+ new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
+ return tilesetReader.ReadTileset();
+ }
+
+ private Template ResolveTemplate(string source)
+ {
+ string templateString = FileAccess.Open($"res://{source}", FileAccess.ModeFlags.Read).GetAsText();
+ using TemplateReader templateReader =
+ new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType);
+ return templateReader.ReadTemplate();
+ }
+
+ private static ICustomTypeDefinition ResolveCustomType(string name)
+ {
+ ICustomTypeDefinition[] allDefinedTypes =
+ [
+ new CustomClassDefinition() { Name = "a" },
+ ];
+ return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException();
+ }
+}
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/blocks/1.tscn b/src/DotTiled.Examples/DotTiled.Example.Godot/blocks/1.tscn
new file mode 100644
index 0000000..8c10a13
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/blocks/1.tscn
@@ -0,0 +1,9 @@
+[gd_scene load_steps=2 format=3 uid="uid://ce10iald4cb3f"]
+
+[ext_resource type="Texture2D" uid="uid://da08vay832u8c" path="res://tileset.png" id="1_c5fs4"]
+
+[node name="1" type="Node2D"]
+
+[node name="1" type="Sprite2D" parent="."]
+scale = Vector2(2, 2)
+texture = ExtResource("1_c5fs4")
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg
new file mode 100644
index 0000000..3fe4f4a
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg
@@ -0,0 +1 @@
+
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg.import b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg.import
new file mode 100644
index 0000000..164e9de
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0kywmrvvqqyr"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/main.tscn b/src/DotTiled.Examples/DotTiled.Example.Godot/main.tscn
new file mode 100644
index 0000000..4fdfc60
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/main.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://p4rpwsvyslew"]
+
+[ext_resource type="Script" path="res://MapParser.cs" id="1_xjmxv"]
+
+[node name="Node2D" type="Node2D"]
+script = ExtResource("1_xjmxv")
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/project.godot b/src/DotTiled.Examples/DotTiled.Example.Godot/project.godot
new file mode 100644
index 0000000..539b623
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/project.godot
@@ -0,0 +1,20 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="DotTiled.Example.Godot"
+run/main_scene="res://main.tscn"
+config/features=PackedStringArray("4.3", "C#", "Forward Plus")
+config/icon="res://icon.svg"
+
+[dotnet]
+
+project/assembly_name="DotTiled.Example.Godot"
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/tilemap.tmx b/src/DotTiled.Examples/DotTiled.Example.Godot/tilemap.tmx
new file mode 100644
index 0000000..7a9a294
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/tilemap.tmx
@@ -0,0 +1,12 @@
+
+
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png b/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png
new file mode 100644
index 0000000..67b9eee
Binary files /dev/null and b/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png differ
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png.import b/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png.import
new file mode 100644
index 0000000..440f099
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://da08vay832u8c"
+path="res://.godot/imported/tileset.png-a39e944f25b35d62f55d4f98a36e2b5e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://tileset.png"
+dest_files=["res://.godot/imported/tileset.png-a39e944f25b35d62f55d4f98a36e2b5e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.tsx b/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.tsx
new file mode 100644
index 0000000..9fc1b9b
--- /dev/null
+++ b/src/DotTiled.Examples/DotTiled.Example.Godot/tileset.tsx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/DotTiled.sln b/src/DotTiled.sln
index 421e996..7208481 100644
--- a/src/DotTiled.sln
+++ b/src/DotTiled.sln
@@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Tests", "DotTiled.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Benchmark", "DotTiled.Benchmark\DotTiled.Benchmark.csproj", "{510F3077-8EA4-47D1-8D01-E2D538F1B899}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{8C54542E-3C2C-486C-9BEF-4C510391AFDA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Console", "DotTiled.Examples\DotTiled.Example.Console\DotTiled.Example.Console.csproj", "{F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Example.Godot", "DotTiled.Examples\DotTiled.Example.Godot\DotTiled.Example.Godot.csproj", "{7541A9B3-43A5-45A7-939E-6F542319D990}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +36,17 @@ Global
{510F3077-8EA4-47D1-8D01-E2D538F1B899}.Debug|Any CPU.Build.0 = Debug|Any CPU
{510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.ActiveCfg = Release|Any CPU
{510F3077-8EA4-47D1-8D01-E2D538F1B899}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7541A9B3-43A5-45A7-939E-6F542319D990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7541A9B3-43A5-45A7-939E-6F542319D990}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+ {7541A9B3-43A5-45A7-939E-6F542319D990}.Release|Any CPU.Build.0 = Debug|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {F9892295-6C2C-4ABD-9D6F-2AC81D2C6E67} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA}
+ {7541A9B3-43A5-45A7-939E-6F542319D990} = {8C54542E-3C2C-486C-9BEF-4C510391AFDA}
EndGlobalSection
EndGlobal