├── .gitignore ├── DosSnake.gif ├── dos64stb.bin ├── SeeSharpSnake.gif ├── Game ├── Random.cs ├── FrameBuffer.cs ├── Game.cs └── Snake.cs ├── Pal ├── RuntimeInformation.Linux.cs ├── RuntimeInformation.Uefi.cs ├── RuntimeInformation.Windows.cs ├── Thread.Windows.cs ├── Environment.Windows.cs ├── UefiApplication.cs ├── Thread.Uefi.cs ├── Console.cs ├── Environment.Uefi.cs ├── Thread.Dos.cs ├── Environment.Dos.cs ├── Console.Uefi.cs ├── Console.Dos.cs ├── Console.Windows.cs └── UefiEnvironment.cs ├── MiniRuntime.Dos.cs ├── nuget.config ├── MiniBCL.cs ├── README.md ├── SeeSharpSnake.csproj └── MiniRuntime.cs /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | -------------------------------------------------------------------------------- /DosSnake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/SeeSharpSnake/HEAD/DosSnake.gif -------------------------------------------------------------------------------- /dos64stb.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/SeeSharpSnake/HEAD/dos64stb.bin -------------------------------------------------------------------------------- /SeeSharpSnake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichalStrehovsky/SeeSharpSnake/HEAD/SeeSharpSnake.gif -------------------------------------------------------------------------------- /Game/Random.cs: -------------------------------------------------------------------------------- 1 | struct Random 2 | { 3 | private uint _val; 4 | 5 | public Random(uint seed) 6 | { 7 | _val = seed; 8 | } 9 | 10 | public uint Next() => _val = (1103515245 * _val + 12345) % 2147483648; 11 | } -------------------------------------------------------------------------------- /Pal/RuntimeInformation.Linux.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.InteropServices 2 | { 3 | internal class RuntimeInformation 4 | { 5 | public static bool IsOSPlatform(OSPlatform platform) => platform == OSPlatform.Linux; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Pal/RuntimeInformation.Uefi.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.InteropServices 2 | { 3 | internal class RuntimeInformation 4 | { 5 | public static bool IsOSPlatform(OSPlatform platform) => platform == OSPlatform.Windows; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Pal/RuntimeInformation.Windows.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.InteropServices 2 | { 3 | internal class RuntimeInformation 4 | { 5 | public static bool IsOSPlatform(OSPlatform platform) => platform == OSPlatform.Windows; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Pal/Thread.Windows.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace System.Threading 4 | { 5 | static class Thread 6 | { 7 | [DllImport("api-ms-win-core-synch-l1-2-0")] 8 | public static extern void Sleep(int delayMs); 9 | } 10 | } -------------------------------------------------------------------------------- /Pal/Environment.Windows.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace System 4 | { 5 | static class Environment 6 | { 7 | [DllImport("api-ms-win-core-sysinfo-l1-1-0")] 8 | private static extern long GetTickCount64(); 9 | 10 | public static long TickCount64 => GetTickCount64(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MiniRuntime.Dos.cs: -------------------------------------------------------------------------------- 1 | class Stubs 2 | { 3 | [System.Runtime.RuntimeExport("__fail_fast")] 4 | static void FailFast() { while (true); } 5 | 6 | [System.Runtime.RuntimeExport("memset")] 7 | static unsafe void MemSet(byte* ptr, int c, int count) 8 | { 9 | for (byte* p = ptr; p < ptr + count; p++) 10 | *p = (byte)c; 11 | } 12 | } -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Pal/UefiApplication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | public unsafe static class EfiApplication 5 | { 6 | [System.Runtime.RuntimeExport("EfiMain")] 7 | unsafe static long EfiMain(IntPtr imageHandle, EFI_SYSTEM_TABLE* systemTable) 8 | { 9 | EfiRuntimeHost.Initialize(systemTable); 10 | 11 | // Change to 2 * 1000 once proper time will be implemented. 12 | Thread.Sleep(40 * 1000); 13 | Console.Clear(); 14 | 15 | Game.Main(); 16 | return 0; 17 | } 18 | } -------------------------------------------------------------------------------- /Pal/Thread.Uefi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace System.Threading 4 | { 5 | public static class Thread 6 | { 7 | public static unsafe void Sleep(int delayMs) 8 | { 9 | // Deliberately change sematics of function, to be able run game 10 | // Proper implementation should multiply by 1000, 11 | // But that require change to the calculation of ticks 12 | // and more UEFI related code. 13 | EfiRuntimeHost.SystemTable->BootServices->Stall(100 * (uint)delayMs); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Pal/Console.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | public enum ConsoleColor 4 | { 5 | Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, 6 | Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White 7 | } 8 | 9 | public enum ConsoleKey 10 | { 11 | Escape = 27, 12 | LeftArrow = 37, 13 | UpArrow = 38, 14 | RightArrow = 39, 15 | DownArrow = 40, 16 | } 17 | 18 | public readonly struct ConsoleKeyInfo 19 | { 20 | public ConsoleKeyInfo(char keyChar, ConsoleKey key, bool shift, bool alt, bool control) 21 | { 22 | Key = key; 23 | } 24 | 25 | public readonly ConsoleKey Key; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pal/Environment.Uefi.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | public static class Environment 4 | { 5 | public static unsafe long TickCount64 6 | { 7 | get 8 | { 9 | EfiRuntimeHost.SystemTable->RuntimeServices->GetTime(out var time, out var capabilities); 10 | var daysCount = time.Year * 365 + time.Month * 31 + time.Day; 11 | long secondsCount = daysCount * 24 * 60 * 60 + time.Hour * 60 * 60 + time.Minute * 60 + time.Second; 12 | long timerTicks = secondsCount * 1000 + time.Nanosecond / 1000_000; 13 | 14 | // Deliberately change time scale. 15 | // This increase game loop poll iteration, and make game smooth. 16 | return timerTicks / 1; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Pal/Thread.Dos.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace System.Threading 5 | { 6 | static class Thread 7 | { 8 | public static unsafe void Sleep(int delayMs) 9 | { 10 | // Place the sequence of bytes 0xF4, 0xC3 on the stack. 11 | // The bytes correspond to the following assembly instruction sequence: 12 | // 13 | // hlt 14 | // ret 15 | // 16 | ushort hlt = 0xc3f4; 17 | 18 | long expected = Environment.TickCount64 + delayMs; 19 | while (Environment.TickCount64 < expected) 20 | { 21 | // Call the helper we placed on the stack to halt the processor 22 | // for a little bit. 23 | // (Security people are crying in a corner right now). 24 | ClassConstructorRunner.Call(new IntPtr(&hlt)); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pal/Environment.Dos.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace System 4 | { 5 | static class Environment 6 | { 7 | public static unsafe long TickCount64 8 | { 9 | // Mark no inlining so that we get "volatile" semantics 10 | [MethodImpl(MethodImplOptions.NoInlining)] 11 | get 12 | { 13 | // Read BIOS data area - 1Ah interrupt counter. 14 | // 15 | // Yep, we're just dereferencing memory at some "arbitrary" offset. 16 | // 17 | // BIOS data area is a documented data structure laid out by the BIOS 18 | // when the computer resets. DOS apps are allowed to read it. 19 | // 20 | // Offset 0x46C keeps track of time in ~55 ms units. 21 | uint timerTicks = *(uint*)0x46C; 22 | return (long)timerTicks * 55; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Game/FrameBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | unsafe struct FrameBuffer 4 | { 5 | public const int Width = 40; 6 | public const int Height = 20; 7 | public const int Area = Width * Height; 8 | 9 | fixed char _chars[Area]; 10 | 11 | public void SetPixel(int x, int y, char character) 12 | { 13 | _chars[y * Width + x] = character; 14 | } 15 | 16 | public void Clear() 17 | { 18 | for (int i = 0; i < Area; i++) 19 | _chars[i] = ' '; 20 | } 21 | 22 | public readonly void Render() 23 | { 24 | const ConsoleColor snakeColor = ConsoleColor.Green; 25 | 26 | Console.ForegroundColor = snakeColor; 27 | 28 | for (int i = 0; i < Area; i++) 29 | { 30 | if (i % Width == 0) 31 | Console.SetCursorPosition(0, i / Width); 32 | 33 | char c = _chars[i]; 34 | 35 | if (c == '*' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) 36 | { 37 | Console.ForegroundColor = c == '*' ? ConsoleColor.Red : ConsoleColor.White; 38 | Console.Write(c); 39 | Console.ForegroundColor = snakeColor; 40 | } 41 | else 42 | Console.Write(c); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Pal/Console.Uefi.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | public static class Console 4 | { 5 | public static unsafe string Title 6 | { 7 | set 8 | { 9 | } 10 | } 11 | 12 | public static unsafe bool CursorVisible 13 | { 14 | set 15 | { 16 | EfiRuntimeHost.SystemTable->ConOut->EnableCursor(EfiRuntimeHost.SystemTable->ConOut, value); 17 | } 18 | } 19 | 20 | public unsafe static ConsoleColor ForegroundColor 21 | { 22 | set 23 | { 24 | uint color = (ushort)value; 25 | EfiRuntimeHost.SystemTable->ConOut->SetAttribute(EfiRuntimeHost.SystemTable->ConOut, color); 26 | } 27 | } 28 | 29 | 30 | static char lastKey = '\0'; 31 | static ushort lastScanCode; 32 | public static unsafe bool KeyAvailable 33 | { 34 | get 35 | { 36 | EFI_INPUT_KEY key; 37 | var errorCode = EfiRuntimeHost.SystemTable->ConIn->ReadKeyStroke(EfiRuntimeHost.SystemTable->ConIn, &key); 38 | lastKey = (char)key.UnicodeChar; 39 | lastScanCode = key.ScanCode; 40 | return errorCode == 0; 41 | } 42 | } 43 | 44 | public static unsafe void Clear() 45 | { 46 | EfiRuntimeHost.SystemTable->ConOut->ClearScreen(EfiRuntimeHost.SystemTable->ConOut); 47 | } 48 | 49 | public static unsafe ConsoleKeyInfo ReadKey(bool intercept) 50 | { 51 | char c = lastKey; 52 | ConsoleKey k = default; 53 | if (lastScanCode != 0) 54 | { 55 | k = lastScanCode switch 56 | { 57 | 1 => ConsoleKey.UpArrow, 58 | 2 => ConsoleKey.DownArrow, 59 | 3 => ConsoleKey.RightArrow, 60 | 4 => ConsoleKey.LeftArrow, 61 | _ => k, 62 | }; 63 | } 64 | 65 | lastKey = '\0'; 66 | return new ConsoleKeyInfo(c, k, false, false, false); 67 | } 68 | 69 | public static unsafe void SetWindowSize(int x, int y) 70 | { 71 | } 72 | 73 | public static void SetBufferSize(int x, int y) 74 | { 75 | } 76 | 77 | public unsafe static void SetCursorPosition(int x, int y) 78 | { 79 | EfiRuntimeHost.SystemTable->ConOut->SetCursorPosition( 80 | EfiRuntimeHost.SystemTable->ConOut, 81 | (uint)x, 82 | (uint)y); 83 | } 84 | 85 | unsafe static void WriteChar(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* ConOut, char data) 86 | { 87 | char* x = stackalloc char[2]; 88 | x[0] = data; 89 | x[1] = '\0'; 90 | ConOut->OutputString(ConOut, x); 91 | } 92 | 93 | public static unsafe void Write(char c) 94 | { 95 | WriteChar(EfiRuntimeHost.SystemTable->ConOut, c); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Pal/Console.Dos.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace System 4 | { 5 | static class Console 6 | { 7 | static ushort s_consoleAttribute; 8 | static ushort s_cursorX, s_cursorY; 9 | 10 | public static unsafe string Title 11 | { 12 | set 13 | { 14 | } 15 | } 16 | 17 | public static unsafe bool CursorVisible 18 | { 19 | set 20 | { 21 | } 22 | } 23 | 24 | public static ConsoleColor ForegroundColor 25 | { 26 | set 27 | { 28 | s_consoleAttribute = (ushort)value; 29 | } 30 | } 31 | 32 | 33 | public static unsafe bool KeyAvailable 34 | { 35 | get 36 | { 37 | // mov ah,0Bh 38 | // int 21h 39 | // ret 40 | byte* pCode = stackalloc byte[] { 0xB4, 0x0B, 0xCD, 0x21, 0xC3 }; 41 | return ClassConstructorRunner.Call(new IntPtr(pCode)) != 0; 42 | } 43 | } 44 | 45 | public static unsafe ConsoleKeyInfo ReadKey(bool intercept) 46 | { 47 | // mov ah,08h 48 | // int 21h 49 | // ret 50 | byte* pCode = stackalloc byte[] { 0xB4, 0x08, 0xCD, 0x21, 0xC3 }; 51 | char c = (char)ClassConstructorRunner.Call(new IntPtr(pCode)); 52 | 53 | // Interpret WASD as arrow keys. 54 | ConsoleKey k = default; 55 | if (c == 'w') 56 | k = ConsoleKey.UpArrow; 57 | else if (c == 'd') 58 | k = ConsoleKey.RightArrow; 59 | else if (c == 's') 60 | k = ConsoleKey.DownArrow; 61 | else if (c == 'a') 62 | k = ConsoleKey.LeftArrow; 63 | 64 | return new ConsoleKeyInfo(c, k, false, false, false); 65 | } 66 | 67 | public static unsafe void SetWindowSize(int x, int y) 68 | { 69 | } 70 | 71 | public static void SetBufferSize(int x, int y) 72 | { 73 | } 74 | 75 | public static void SetCursorPosition(int x, int y) 76 | { 77 | s_cursorX = (ushort)x; 78 | s_cursorY = (ushort)y; 79 | } 80 | 81 | public static unsafe void Write(char c) 82 | { 83 | byte* biosDataArea = (byte*)0x400; 84 | 85 | // Find the start of video RAM by reading the BIOS data area 86 | byte* vram = (byte*)0xB8000; 87 | if (*(biosDataArea+0x63) == 0xB4) 88 | vram = (byte*)0xB0000; 89 | 90 | // Get the offset of the active video page 91 | vram += *(ushort*)(biosDataArea + 0x4E); 92 | 93 | // Translate some unicode characters into the IBM hardware codepage 94 | byte b = c switch 95 | { 96 | '│' => (byte)0xB3, 97 | '┌' => (byte)0xDA, 98 | '┐' => (byte)0xBF, 99 | '─' => (byte)0xC4, 100 | '└' => (byte)0xC0, 101 | '┘' => (byte)0xD9, 102 | _ => (byte)c, 103 | }; 104 | 105 | // TODO: read number of columns from the bios data area 106 | vram[(s_cursorY * 80 * 2) + (s_cursorX * 2)] = b; 107 | vram[(s_cursorY * 80 * 2) + (s_cursorX * 2) + 1] = (byte)s_consoleAttribute; 108 | 109 | // TODO: wrap/scroll? 110 | s_cursorX++; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /MiniBCL.cs: -------------------------------------------------------------------------------- 1 | namespace System 2 | { 3 | public class Object 4 | { 5 | // The layout of object is a contract with the compiler. 6 | public IntPtr m_pEEType; 7 | } 8 | public struct Void { } 9 | 10 | // The layout of primitive types is special cased because it would be recursive. 11 | // These really don't need any fields to work. 12 | public struct Boolean { } 13 | public struct Char { } 14 | public struct SByte { } 15 | public struct Byte { } 16 | public struct Int16 { } 17 | public struct UInt16 { } 18 | public struct Int32 { } 19 | public struct UInt32 { } 20 | public struct Int64 { } 21 | public struct UInt64 { } 22 | public struct UIntPtr { } 23 | public struct Single { } 24 | public struct Double { } 25 | 26 | public unsafe struct IntPtr 27 | { 28 | private void* _value; 29 | public IntPtr(void* value) { _value = value; } 30 | } 31 | 32 | public abstract class ValueType { } 33 | public abstract class Enum : ValueType { } 34 | 35 | public struct Nullable where T : struct { } 36 | 37 | public sealed class String 38 | { 39 | // The layout of the string type is a contract with the compiler. 40 | public readonly int Length; 41 | public char _firstChar; 42 | 43 | public unsafe char this[int index] 44 | { 45 | [System.Runtime.CompilerServices.Intrinsic] 46 | get 47 | { 48 | return Internal.Runtime.CompilerServices.Unsafe.Add(ref _firstChar, index); 49 | } 50 | } 51 | } 52 | public abstract class Array { } 53 | public abstract class Delegate { } 54 | public abstract class MulticastDelegate : Delegate { } 55 | 56 | public struct RuntimeTypeHandle { } 57 | public struct RuntimeMethodHandle { } 58 | public struct RuntimeFieldHandle { } 59 | 60 | public class Attribute { } 61 | } 62 | 63 | namespace System.Runtime.CompilerServices 64 | { 65 | internal sealed class IntrinsicAttribute : Attribute { } 66 | 67 | public class RuntimeHelpers 68 | { 69 | public static unsafe int OffsetToStringData => sizeof(IntPtr) + sizeof(int); 70 | } 71 | 72 | public enum MethodImplOptions 73 | { 74 | NoInlining = 0x0008, 75 | } 76 | 77 | public sealed class MethodImplAttribute : Attribute 78 | { 79 | public MethodImplAttribute(MethodImplOptions methodImplOptions) { } 80 | } 81 | } 82 | 83 | namespace System.Runtime.InteropServices 84 | { 85 | public enum CharSet 86 | { 87 | None = 1, 88 | Ansi = 2, 89 | Unicode = 3, 90 | Auto = 4, 91 | } 92 | 93 | public sealed class DllImportAttribute : Attribute 94 | { 95 | public string EntryPoint; 96 | public CharSet CharSet; 97 | public DllImportAttribute(string dllName) { } 98 | } 99 | 100 | public enum LayoutKind 101 | { 102 | Sequential = 0, 103 | Explicit = 2, 104 | Auto = 3, 105 | } 106 | 107 | public sealed class StructLayoutAttribute : Attribute 108 | { 109 | public StructLayoutAttribute(LayoutKind layoutKind) { } 110 | } 111 | } 112 | namespace Internal.Runtime.CompilerServices 113 | { 114 | public static unsafe partial class Unsafe 115 | { 116 | // The body of this method is generated by the compiler. 117 | // It will do what Unsafe.Add is expected to do. It's just not possible to express it in C#. 118 | [System.Runtime.CompilerServices.Intrinsic] 119 | public static extern ref T Add(ref T source, int elementOffset); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Game/Game.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Thread = System.Threading.Thread; 4 | 5 | struct Game 6 | { 7 | enum Result 8 | { 9 | Win, Loss 10 | } 11 | 12 | private Random _random; 13 | 14 | private Game(uint randomSeed) 15 | { 16 | _random = new Random(randomSeed); 17 | } 18 | 19 | private Result Run(ref FrameBuffer fb) 20 | { 21 | Snake s = new Snake( 22 | (byte)(_random.Next() % FrameBuffer.Width), 23 | (byte)(_random.Next() % FrameBuffer.Height), 24 | (Snake.Direction)(_random.Next() % 4)); 25 | 26 | MakeFood(s, out byte foodX, out byte foodY); 27 | 28 | long gameTime = Environment.TickCount64; 29 | 30 | while (true) 31 | { 32 | fb.Clear(); 33 | 34 | if (!s.Update()) 35 | { 36 | s.Draw(ref fb); 37 | return Result.Loss; 38 | } 39 | 40 | s.Draw(ref fb); 41 | 42 | if (Console.KeyAvailable) 43 | { 44 | ConsoleKeyInfo ki = Console.ReadKey(intercept: true); 45 | switch (ki.Key) 46 | { 47 | case ConsoleKey.UpArrow: 48 | s.Course = Snake.Direction.Up; break; 49 | case ConsoleKey.DownArrow: 50 | s.Course = Snake.Direction.Down; break; 51 | case ConsoleKey.LeftArrow: 52 | s.Course = Snake.Direction.Left; break; 53 | case ConsoleKey.RightArrow: 54 | s.Course = Snake.Direction.Right; break; 55 | } 56 | } 57 | 58 | if (s.HitTest(foodX, foodY)) 59 | { 60 | if (s.Extend()) 61 | MakeFood(s, out foodX, out foodY); 62 | else 63 | return Result.Win; 64 | } 65 | 66 | fb.SetPixel(foodX, foodY, '*'); 67 | 68 | fb.Render(); 69 | 70 | gameTime += 100; 71 | 72 | long delay = gameTime - Environment.TickCount64; 73 | if (delay >= 0) 74 | Thread.Sleep((int)delay); 75 | else 76 | gameTime = Environment.TickCount64; 77 | } 78 | } 79 | 80 | void MakeFood(in Snake snake, out byte foodX, out byte foodY) 81 | { 82 | do 83 | { 84 | foodX = (byte)(_random.Next() % FrameBuffer.Width); 85 | foodY = (byte)(_random.Next() % FrameBuffer.Height); 86 | } 87 | while (snake.HitTest(foodX, foodY)); 88 | } 89 | 90 | public static void Main() 91 | { 92 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 93 | { 94 | Console.SetWindowSize(FrameBuffer.Width, FrameBuffer.Height + 1); 95 | Console.SetBufferSize(FrameBuffer.Width, FrameBuffer.Height + 1); 96 | Console.Title = "See Sharp Snake"; 97 | Console.CursorVisible = false; 98 | } 99 | 100 | FrameBuffer fb = new FrameBuffer(); 101 | 102 | while (true) 103 | { 104 | Game g = new Game((uint)Environment.TickCount64); 105 | Result result = g.Run(ref fb); 106 | 107 | string message = result == Result.Win ? "You win" : "You lose"; 108 | 109 | int position = (FrameBuffer.Width - message.Length) / 2; 110 | for (int i = 0; i < message.Length; i++) 111 | { 112 | fb.SetPixel(position + i, FrameBuffer.Height / 2, message[i]); 113 | } 114 | 115 | fb.Render(); 116 | 117 | Console.ReadKey(intercept: true); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Game/Snake.cs: -------------------------------------------------------------------------------- 1 | struct Snake 2 | { 3 | public const int MaxLength = 30; 4 | 5 | private int _length; 6 | 7 | // Body is a packed integer that packs the X coordinate, Y coordinate, and the character 8 | // for the snake's body. 9 | // Only primitive types can be used with C# `fixed`, hence this is an `int`. 10 | private unsafe fixed int _body[MaxLength]; 11 | 12 | private Direction _direction; 13 | private Direction _oldDirection; 14 | 15 | public Direction Course 16 | { 17 | set 18 | { 19 | if (_oldDirection != _direction) 20 | _oldDirection = _direction; 21 | 22 | if (_direction - value != 2 && value - _direction != 2) 23 | _direction = value; 24 | } 25 | } 26 | 27 | public unsafe Snake(byte x, byte y, Direction direction) 28 | { 29 | _body[0] = new Part(x, y, DirectionToChar(direction, direction)).Pack(); 30 | _direction = direction; 31 | _oldDirection = direction; 32 | _length = 1; 33 | } 34 | 35 | public unsafe bool Update() 36 | { 37 | Part oldHead = Part.Unpack(_body[0]); 38 | Part newHead = new Part( 39 | (byte)(_direction switch 40 | { 41 | Direction.Left => oldHead.X == 0 ? FrameBuffer.Width - 1 : oldHead.X - 1, 42 | Direction.Right => (oldHead.X + 1) % FrameBuffer.Width, 43 | _ => oldHead.X, 44 | }), 45 | (byte)(_direction switch 46 | { 47 | Direction.Up => oldHead.Y == 0 ? FrameBuffer.Height - 1 : oldHead.Y - 1, 48 | Direction.Down => (oldHead.Y + 1) % FrameBuffer.Height, 49 | _ => oldHead.Y, 50 | }), 51 | DirectionToChar(_direction, _direction) 52 | ); 53 | 54 | oldHead = new Part(oldHead.X, oldHead.Y, DirectionToChar(_oldDirection, _direction)); 55 | 56 | bool result = true; 57 | 58 | for (int i = 0; i < _length - 1; i++) 59 | { 60 | Part current = Part.Unpack(_body[i]); 61 | if (current.X == newHead.X && current.Y == newHead.Y) 62 | result = false; 63 | } 64 | 65 | _body[0] = oldHead.Pack(); 66 | 67 | for (int i = _length - 2; i >= 0; i--) 68 | { 69 | _body[i + 1] = _body[i]; 70 | } 71 | 72 | _body[0] = newHead.Pack(); 73 | 74 | _oldDirection = _direction; 75 | 76 | return result; 77 | } 78 | 79 | public unsafe readonly void Draw(ref FrameBuffer fb) 80 | { 81 | for (int i = 0; i < _length; i++) 82 | { 83 | Part p = Part.Unpack(_body[i]); 84 | fb.SetPixel(p.X, p.Y, p.Character); 85 | } 86 | } 87 | 88 | public bool Extend() 89 | { 90 | if (_length < MaxLength) 91 | { 92 | _length += 1; 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | public unsafe readonly bool HitTest(int x, int y) 99 | { 100 | for (int i = 0; i < _length; i++) 101 | { 102 | Part current = Part.Unpack(_body[i]); 103 | if (current.X == x && current.Y == y) 104 | return true; 105 | } 106 | 107 | return false; 108 | } 109 | 110 | private static char DirectionToChar(Direction oldDirection, Direction newDirection) 111 | { 112 | const string DirectionChangeToChar = "│┌?┐┘─┐??└│┘└?┌─"; 113 | return DirectionChangeToChar[(int)oldDirection * 4 + (int)newDirection]; 114 | } 115 | 116 | // Helper struct to pack and unpack the packed integer in _body. 117 | readonly struct Part 118 | { 119 | public readonly byte X, Y; 120 | public readonly char Character; 121 | 122 | public Part(byte x, byte y, char c) 123 | { 124 | X = x; 125 | Y = y; 126 | Character = c; 127 | } 128 | 129 | public int Pack() => X << 24 | Y << 16 | Character; 130 | public static Part Unpack(int packed) => new Part((byte)(packed >> 24), (byte)(packed >> 16), (char)packed); 131 | } 132 | 133 | public enum Direction 134 | { 135 | Up, Right, Down, Left 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A self-contained C# game in 8 kB 2 | 3 | This repo is a complement to my [article on building an 8 kB self-contained game in C#](https://medium.com/@MStrehovsky/building-a-self-contained-game-in-c-under-8-kilobytes-74c3cf60ea04?sk=334b06f72dad47f15d0ba0cc6a502487). By self-contained I mean _this 8 kB C# game binary doesn't need a .NET runtime to work_. See the article on how that's done. 4 | 5 | The project files and scripts in this repo build the same game (Snake clone) in several different configurations, each with a different size of the output. 6 | 7 | 😱 Scroll all the way down for instructions on how to run this on DOS. 8 | 9 | ![Snake game](SeeSharpSnake.gif) 10 | 11 | ## Building 12 | 13 | ### To build the 65 MB version of the game 14 | 15 | ``` 16 | dotnet publish -r win-x64 -c Release 17 | ``` 18 | 19 | ### To build the 25 MB version of the game 20 | 21 | ``` 22 | dotnet publish -r win-x64 -c Release /p:PublishTrimmed=true 23 | ``` 24 | 25 | ### ⚠️ WARNING: additional requirements needed for the below configuration 26 | 27 | Make sure you have Visual Studio 2019 installed (Community edition is free) and include C/C++ development tools with Windows SDK (we need a tiny fraction of that - the platform linker and Win32 import libraries). 28 | 29 | ### To build the 4.7 MB version of the game 30 | 31 | ``` 32 | dotnet publish -r win-x64 -c Release /p:Mode=CoreRT 33 | ``` 34 | 35 | ### To build the 4.3 MB version of the game 36 | 37 | ``` 38 | dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-Moderate 39 | ``` 40 | 41 | ### To build the 3.0 MB version of the game 42 | 43 | ``` 44 | dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-High 45 | ``` 46 | 47 | ### To build the 1.2 MB version of the game 48 | 49 | ``` 50 | dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-ReflectionFree 51 | ``` 52 | 53 | ### To build the 10 kB version of the game 54 | 55 | ``` 56 | dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-NoRuntime 57 | ``` 58 | 59 | ### To build the 8 kB version of the game 60 | 61 | 1. Open "x64 Native Tools Command Prompt for VS 2019" (it's in your Start menu) 62 | 2. CD into the repo root directory 63 | 64 | ``` 65 | csc.exe /debug /O /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 MiniRuntime.cs MiniBCL.cs Game\FrameBuffer.cs Game\Random.cs Game\Game.cs Game\Snake.cs Pal\Thread.Windows.cs Pal\Environment.Windows.cs Pal\Console.Windows.cs Pal\Console.cs /out:zerosnake.ilexe /langversion:latest /unsafe 66 | ``` 67 | 68 | Find ilc.exe (the [CoreRT](http://github.com/dotnet/corert) ahead of time compiler) on your machine. If you completed any of the above steps that produce outputs <= 4.7 MB, ilc.exe will be in your NuGet package cache (somewhere like `%USERPROFILE%\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\1.0.0-alpha-27402–01\tools`). 69 | 70 | ``` 71 | [PATH_TO_ILC_EXE]\ilc.exe zerosnake.ilexe -o zerosnake.obj --systemmodule:zerosnake --Os -g 72 | ``` 73 | 74 | ``` 75 | link.exe /debug:full /subsystem:console zerosnake.obj /entry:__managed__Main kernel32.lib ucrt.lib /merge:.modules=.rdata /merge:.pdata=.rdata /incremental:no /DYNAMICBASE:NO /filealign:16 /align:16 76 | ``` 77 | 78 | ## Contributing 79 | Contributions are welcome, but I would like to keep the game simple and small. If you would like to add features like levels or achievements, you might want to just fork this repo. 80 | 81 | In general, I welcome: 82 | 83 | * Making the 8 kB version of the game run on Linux and macOS (the bigger versions of the game should work just fine on Unixes, but the tiny version that p/invokes into the platform APIs is OS specific) 84 | * Adding a configuration that builds the game as an [EFI boot application](https://github.com/MichalStrehovsky/zerosharp/tree/master/efi-no-runtime) so that it can run without an OS 85 | * Bug fixes 86 | * Making the CSPROJ also handle the 8 kB case so that we don't need to mess with the command prompt 87 | * Small experience improvements (e.g. handling ESC key to exit the game) 88 | 89 | ## To build for DOS 90 | 91 | Very similar instructions to the 8 kB version: 92 | 93 | ``` 94 | csc.exe /debug /O /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 MiniRuntime.cs MiniRuntime.Dos.cs MiniBCL.cs Game\FrameBuffer.cs Game\Random.cs Game\Game.cs Game\Snake.cs Pal\Thread.Dos.cs Pal\Environment.Dos.cs Pal\Console.Dos.cs Pal\Console.cs /out:zerosnake.ilexe /langversion:latest /unsafe 95 | ``` 96 | 97 | ``` 98 | [PATH_TO_ILC_EXE]\ilc.exe zerosnake.ilexe --systemmodule:zerosnake -o zerosnake.obj 99 | ``` 100 | 101 | ``` 102 | link /subsystem:native /entry:__managed__Main zerosnake.obj /stub:dos64stb.bin 103 | ``` 104 | 105 | The DOS64STB blob is https://github.com/Baron-von-Riedesel/Dos64-stub. 106 | 107 | ## Run on UEFI 108 | 109 | ### To build the 11 kB Uefi version of the game 110 | 111 | ``` 112 | dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-Uefi 113 | ``` 114 | 115 | This produce UEFI executable stored on the VHDX file in the `bin\x64\Release\netcoreapp3.1\win-x64\native\seesharpsnake.vhdx` 116 | location, now it's time to create VM out of that VHDX file and launch it. 117 | 118 | Open Powershell or Powershell Core session and run commands below. 119 | 120 | ```powershell 121 | New-VM -Name SeeSharpSnake -MemoryStartupBytes 32MB -Generation 2 -VHDPath "bin\x64\Release\netcoreapp3.1\win-x64\native\seesharpsnake.vhdx" 122 | Set-VMFirmware -VMName SeeSharpSnake -EnableSecureBoot Off 123 | Set-VM -Name SeeSharpSnake -AutomaticCheckpointsEnabled $false -CheckpointType Disabled 124 | ``` 125 | 126 | and now connect to your VM using 127 | 128 | ``` 129 | vmconnect localhost SeeSharpSnake 130 | ``` 131 | 132 | Press Ctrl+S and enjoy! 133 | -------------------------------------------------------------------------------- /SeeSharpSnake.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | true 8 | false 9 | 8.0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | false 56 | Size 57 | 58 | 59 | 60 | false 61 | true 62 | true 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | true 72 | 73 | 74 | 75 | true 76 | true 77 | v4.0.30319 78 | true 79 | Size 80 | true 81 | false 82 | false 83 | SeeSharpSnake 84 | 85 | 86 | 87 | true 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | $(MSBuildProjectDirectory)\$(NativeOutputPath)seesharpsnake.vhdx 110 | 111 | create vdisk file=$(VHD) maximum=40 112 | select vdisk file=$(VHD) 113 | attach vdisk 114 | convert gpt 115 | create partition efi 116 | format quick fs=fat32 label="System" 117 | assign letter="X" 118 | exit 119 | 120 | 121 | select vdisk file=$(VHD) 122 | select partition 1 123 | remove letter=X 124 | detach vdisk 125 | exit 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /Pal/Console.Windows.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace System 4 | { 5 | static class Console 6 | { 7 | private enum BOOL : int 8 | { 9 | FALSE = 0, 10 | TRUE = 1, 11 | } 12 | 13 | [DllImport("api-ms-win-core-processenvironment-l1-1-0")] 14 | private static unsafe extern IntPtr GetStdHandle(int c); 15 | 16 | private readonly static IntPtr s_outputHandle = GetStdHandle(-11); 17 | 18 | private readonly static IntPtr s_inputHandle = GetStdHandle(-10); 19 | 20 | [DllImport("api-ms-win-core-console-l2-1-0.dll", EntryPoint = "SetConsoleTitleW")] 21 | private static unsafe extern BOOL SetConsoleTitle(char* c); 22 | 23 | public static unsafe string Title 24 | { 25 | set 26 | { 27 | fixed (char* c = value) 28 | SetConsoleTitle(c); 29 | } 30 | } 31 | 32 | [StructLayout(LayoutKind.Sequential)] 33 | struct CONSOLE_CURSOR_INFO 34 | { 35 | public uint Size; 36 | public BOOL Visible; 37 | } 38 | 39 | [DllImport("api-ms-win-core-console-l2-1-0")] 40 | private static unsafe extern BOOL SetConsoleCursorInfo(IntPtr handle, CONSOLE_CURSOR_INFO* cursorInfo); 41 | 42 | public static unsafe bool CursorVisible 43 | { 44 | set 45 | { 46 | CONSOLE_CURSOR_INFO cursorInfo = new CONSOLE_CURSOR_INFO 47 | { 48 | Size = 1, 49 | Visible = value ? BOOL.TRUE : BOOL.FALSE 50 | }; 51 | SetConsoleCursorInfo(s_outputHandle, &cursorInfo); 52 | } 53 | } 54 | 55 | [DllImport("api-ms-win-core-console-l2-1-0")] 56 | private static unsafe extern BOOL SetConsoleTextAttribute(IntPtr handle, ushort attribute); 57 | 58 | public static ConsoleColor ForegroundColor 59 | { 60 | set 61 | { 62 | SetConsoleTextAttribute(s_outputHandle, (ushort)value); 63 | } 64 | } 65 | 66 | [StructLayout(LayoutKind.Sequential)] 67 | private struct KEY_EVENT_RECORD 68 | { 69 | public BOOL KeyDown; 70 | public short RepeatCount; 71 | public short VirtualKeyCode; 72 | public short VirtualScanCode; 73 | public short UChar; 74 | public int ControlKeyState; 75 | } 76 | 77 | [StructLayout(LayoutKind.Sequential)] 78 | private struct INPUT_RECORD 79 | { 80 | public short EventType; 81 | public KEY_EVENT_RECORD KeyEvent; 82 | } 83 | 84 | [DllImport("api-ms-win-core-console-l1-2-0", EntryPoint = "PeekConsoleInputW", CharSet = CharSet.Unicode)] 85 | private static unsafe extern BOOL PeekConsoleInput(IntPtr hConsoleInput, INPUT_RECORD* lpBuffer, uint nLength, uint* lpNumberOfEventsRead); 86 | 87 | public static unsafe bool KeyAvailable 88 | { 89 | get 90 | { 91 | uint nRead; 92 | INPUT_RECORD buffer; 93 | while (true) 94 | { 95 | PeekConsoleInput(s_inputHandle, &buffer, 1, &nRead); 96 | 97 | if (nRead == 0) 98 | return false; 99 | 100 | if (buffer.EventType == 1 && buffer.KeyEvent.KeyDown != BOOL.FALSE) 101 | return true; 102 | 103 | ReadConsoleInput(s_inputHandle, &buffer, 1, &nRead); 104 | } 105 | } 106 | } 107 | 108 | [DllImport("api-ms-win-core-console-l1-2-0", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] 109 | private static unsafe extern BOOL ReadConsoleInput(IntPtr hConsoleInput, INPUT_RECORD* lpBuffer, uint nLength, uint* lpNumberOfEventsRead); 110 | 111 | public static unsafe ConsoleKeyInfo ReadKey(bool intercept) 112 | { 113 | uint nRead; 114 | INPUT_RECORD buffer; 115 | do 116 | { 117 | ReadConsoleInput(s_inputHandle, &buffer, 1, &nRead); 118 | } 119 | while (buffer.EventType != 1 || buffer.KeyEvent.KeyDown == BOOL.FALSE); 120 | 121 | return new ConsoleKeyInfo((char)buffer.KeyEvent.UChar, (ConsoleKey)buffer.KeyEvent.VirtualKeyCode, false, false, false); 122 | } 123 | 124 | struct SMALL_RECT 125 | { 126 | public short Left, Top, Right, Bottom; 127 | } 128 | 129 | [DllImport("api-ms-win-core-console-l2-1-0")] 130 | private static unsafe extern BOOL SetConsoleWindowInfo(IntPtr handle, BOOL absolute, SMALL_RECT* consoleWindow); 131 | 132 | public static unsafe void SetWindowSize(int x, int y) 133 | { 134 | SMALL_RECT rect = new SMALL_RECT 135 | { 136 | Left = 0, 137 | Top = 0, 138 | Right = (short)(x - 1), 139 | Bottom = (short)(y - 1), 140 | }; 141 | SetConsoleWindowInfo(s_outputHandle, BOOL.TRUE, &rect); 142 | } 143 | 144 | [StructLayout(LayoutKind.Sequential)] 145 | struct COORD 146 | { 147 | public short X, Y; 148 | } 149 | 150 | [DllImport("api-ms-win-core-console-l2-1-0")] 151 | private static unsafe extern BOOL SetConsoleScreenBufferSize(IntPtr handle, COORD size); 152 | 153 | public static void SetBufferSize(int x, int y) 154 | { 155 | SetConsoleScreenBufferSize(s_outputHandle, new COORD { X = (short)x, Y = (short)y }); 156 | } 157 | 158 | [DllImport("api-ms-win-core-console-l2-1-0")] 159 | private static unsafe extern BOOL SetConsoleCursorPosition(IntPtr handle, COORD position); 160 | 161 | public static void SetCursorPosition(int x, int y) 162 | { 163 | SetConsoleCursorPosition(s_outputHandle, new COORD { X = (short)x, Y = (short)y }); 164 | } 165 | 166 | [DllImport("api-ms-win-core-console-l1-2-0", EntryPoint = "WriteConsoleW")] 167 | private static unsafe extern BOOL WriteConsole(IntPtr handle, void* buffer, int numChars, int* charsWritten, void* reserved); 168 | 169 | public static unsafe void Write(char c) 170 | { 171 | int dummy; 172 | WriteConsole(s_outputHandle, &c, 1, &dummy, null); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /MiniRuntime.cs: -------------------------------------------------------------------------------- 1 | namespace Internal.Runtime.CompilerHelpers 2 | { 3 | // A class that the compiler looks for that has helpers to initialize the 4 | // process. The compiler can gracefully handle the helpers not being present, 5 | // but the class itself being absent is unhandled. Let's add an empty class. 6 | class StartupCodeHelpers 7 | { 8 | [System.Runtime.RuntimeExport("RhpReversePInvoke2")] 9 | static void RhpReversePInvoke2(System.IntPtr frame) { } 10 | [System.Runtime.RuntimeExport("RhpReversePInvokeReturn2")] 11 | static void RhpReversePInvokeReturn2(System.IntPtr frame) { } 12 | [System.Runtime.RuntimeExport("RhpPInvoke")] 13 | static void RhpPinvoke(System.IntPtr frame) { } 14 | [System.Runtime.RuntimeExport("RhpPInvokeReturn")] 15 | static void RhpPinvokeReturn(System.IntPtr frame) { } 16 | } 17 | } 18 | 19 | namespace System 20 | { 21 | class Array : Array { } 22 | } 23 | 24 | namespace System.Runtime 25 | { 26 | // Custom attribute that the compiler understands that instructs it 27 | // to export the method under the given symbolic name. 28 | internal sealed class RuntimeExportAttribute : Attribute 29 | { 30 | public RuntimeExportAttribute(string entry) { } 31 | } 32 | } 33 | 34 | namespace System.Runtime.InteropServices 35 | { 36 | public class UnmanagedType { } 37 | 38 | // Custom attribute that marks a class as having special "Call" intrinsics. 39 | internal class McgIntrinsicsAttribute : Attribute { } 40 | 41 | internal enum OSPlatform 42 | { 43 | Windows, 44 | Linux, 45 | } 46 | } 47 | 48 | namespace System.Runtime.CompilerServices 49 | { 50 | // A class responsible for running static constructors. The compiler will call into this 51 | // code to ensure static constructors run and that they only run once. 52 | [System.Runtime.InteropServices.McgIntrinsics] 53 | internal static class ClassConstructorRunner 54 | { 55 | private static unsafe IntPtr CheckStaticClassConstructionReturnNonGCStaticBase(ref StaticClassConstructionContext context, IntPtr nonGcStaticBase) 56 | { 57 | CheckStaticClassConstruction(ref context); 58 | return nonGcStaticBase; 59 | } 60 | 61 | private static unsafe void CheckStaticClassConstruction(ref StaticClassConstructionContext context) 62 | { 63 | // Very simplified class constructor runner. In real world, the class constructor runner 64 | // would need to be able to deal with potentially multiple threads racing to initialize 65 | // a single class, and would need to be able to deal with potential deadlocks 66 | // between class constructors. 67 | 68 | if (context.initialized == 1) 69 | return; 70 | 71 | context.initialized = 1; 72 | 73 | // Run the class constructor. 74 | Call(context.cctorMethodAddress); 75 | } 76 | 77 | // This is a special compiler intrinsic that calls method pointed to by pfn. 78 | [System.Runtime.CompilerServices.Intrinsic] 79 | public static extern T Call(System.IntPtr pfn); 80 | } 81 | 82 | // This data structure is a contract with the compiler. It holds the address of a static 83 | // constructor and a flag that specifies whether the constructor already executed. 84 | [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] 85 | public struct StaticClassConstructionContext 86 | { 87 | // Pointer to the code for the static class constructor method. This is initialized by the 88 | // binder/runtime. 89 | public IntPtr cctorMethodAddress; 90 | 91 | // Initialization state of the class. This is initialized to 0. Every time managed code checks the 92 | // cctor state the runtime will call the classlibrary's CheckStaticClassConstruction with this context 93 | // structure unless initialized == 1. This check is specific to allow the classlibrary to store more 94 | // than a binary state for each cctor if it so desires. 95 | public int initialized; 96 | } 97 | 98 | [System.Runtime.InteropServices.McgIntrinsicsAttribute] 99 | internal class RawCalliHelper 100 | { 101 | public static unsafe ulong StdCall(IntPtr pfn, T* arg1, U* arg2, W* arg3, X* arg4) where T : unmanaged where U : unmanaged where W : unmanaged where X : unmanaged 102 | { 103 | // This will be filled in by an IL transform 104 | return 0; 105 | } 106 | public static unsafe ulong StdCall(IntPtr pfn, T arg1, U* arg2, W* arg3, X* arg4) where T : struct where U : unmanaged where W : unmanaged where X : unmanaged 107 | { 108 | // This will be filled in by an IL transform 109 | return 0; 110 | } 111 | public static unsafe ulong StdCall(IntPtr pfn, T* arg1, U* arg2, W* arg3) where T : unmanaged where U : unmanaged where W : unmanaged 112 | { 113 | // This will be filled in by an IL transform 114 | return 0; 115 | } 116 | public static unsafe ulong StdCall(IntPtr pfn, T arg1, U* arg2, W* arg3) where T : unmanaged where U : unmanaged where W : unmanaged 117 | { 118 | // This will be filled in by an IL transform 119 | return 0; 120 | } 121 | public static unsafe ulong StdCall(IntPtr pfn, T* arg1, U arg2, W arg3) where T : unmanaged where U : unmanaged where W : unmanaged 122 | { 123 | // This will be filled in by an IL transform 124 | return 0; 125 | } 126 | public static unsafe ulong StdCall(IntPtr pfn, T* arg1, U* arg2) where T : unmanaged where U : unmanaged 127 | { 128 | // This will be filled in by an IL transform 129 | return 0; 130 | } 131 | public static unsafe ulong StdCall(IntPtr pfn, T* arg1, U arg2) where T : unmanaged where U : unmanaged 132 | { 133 | // This will be filled in by an IL transform 134 | return 0; 135 | } 136 | public static unsafe ulong StdCall(IntPtr pfn, T* arg1) where T : unmanaged 137 | { 138 | // This will be filled in by an IL transform 139 | return 0; 140 | } 141 | public static unsafe ulong StdCall(IntPtr pfn, T arg1) where T : unmanaged 142 | { 143 | // This will be filled in by an IL transform 144 | return 0; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Pal/UefiEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [StructLayout(LayoutKind.Sequential)] 6 | public struct EFI_HANDLE 7 | { 8 | private IntPtr _handle; 9 | } 10 | 11 | [StructLayout(LayoutKind.Sequential)] 12 | public readonly struct EFI_TABLE_HEADER 13 | { 14 | public readonly ulong Signature; 15 | public readonly uint Revision; 16 | public readonly uint HeaderSize; 17 | public readonly uint Crc32; 18 | public readonly uint Reserved; 19 | } 20 | 21 | [StructLayout(LayoutKind.Sequential)] 22 | public unsafe readonly struct EFI_SYSTEM_TABLE 23 | { 24 | public readonly EFI_TABLE_HEADER Hdr; 25 | public readonly char* FirmwareVendor; 26 | public readonly uint FirmwareRevision; 27 | public readonly EFI_HANDLE ConsoleInHandle; 28 | public readonly EFI_SIMPLE_TEXT_INPUT_PROTOCOL* ConIn; 29 | public readonly EFI_HANDLE ConsoleOutHandle; 30 | public readonly EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* ConOut; 31 | public readonly EFI_HANDLE StandardErrorHandle; 32 | public readonly EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* StdErr; 33 | public readonly EFI_RUNTIME_SERVICES* RuntimeServices; 34 | public readonly EFI_BOOT_SERVICES* BootServices; 35 | public readonly ulong NumberOfTableEntries; 36 | public readonly void* ConfigurationTable; 37 | } 38 | 39 | [StructLayout(LayoutKind.Sequential)] 40 | public unsafe readonly struct EFI_RUNTIME_SERVICES 41 | { 42 | public readonly EFI_TABLE_HEADER Hdr; 43 | private readonly IntPtr _GetTime; 44 | private readonly IntPtr _SetTime; 45 | private readonly IntPtr _GetWakeupTime; 46 | private readonly IntPtr _SetWakeupTime; 47 | private readonly IntPtr _SetVirtualAddressMap; 48 | private readonly IntPtr _ConvertPointer; 49 | private readonly IntPtr _GetVariable; 50 | private readonly IntPtr _GetNextVariableName; 51 | private readonly IntPtr _SetVariable; 52 | private readonly IntPtr _GetNextHighMonotonicCount; 53 | private readonly IntPtr _ResetSystem; 54 | private readonly IntPtr _UpdateCapsule; 55 | private readonly IntPtr _QueryCapsuleCapabilities; 56 | private readonly IntPtr _QueryVariableInfo; 57 | 58 | public ulong GetTime(out EFI_TIME time, out EFI_TIME_CAPABILITIES capabilities) 59 | { 60 | fixed (EFI_TIME* timeAddress = &time) 61 | fixed (EFI_TIME_CAPABILITIES* capabilitiesAddress = &capabilities) 62 | return RawCalliHelper.StdCall(_GetTime, timeAddress, capabilitiesAddress); 63 | } 64 | } 65 | 66 | [StructLayout(LayoutKind.Sequential)] 67 | public struct EFI_TIME 68 | { 69 | public ushort Year; 70 | public byte Month; 71 | public byte Day; 72 | public byte Hour; 73 | public byte Minute; 74 | public byte Second; 75 | public byte Pad1; 76 | public uint Nanosecond; 77 | public short TimeZone; 78 | public byte Daylight; 79 | public byte PAD2; 80 | } 81 | 82 | [StructLayout(LayoutKind.Sequential)] 83 | public struct SIMPLE_TEXT_OUTPUT_MODE 84 | { 85 | public readonly int MaxMode; 86 | public readonly int Mode; 87 | public readonly int Attribute; 88 | public readonly int CursorColumn; 89 | public readonly int CursorRow; 90 | public readonly bool CursorVisible; 91 | } 92 | 93 | [StructLayout(LayoutKind.Sequential)] 94 | public struct EFI_TIME_CAPABILITIES 95 | { 96 | public uint Resolution; 97 | public uint Accuracy; 98 | public bool SetsToZero; 99 | } 100 | 101 | [StructLayout(LayoutKind.Sequential)] 102 | public readonly struct EFI_INPUT_KEY 103 | { 104 | public readonly ushort ScanCode; 105 | public readonly ushort UnicodeChar; 106 | } 107 | 108 | [StructLayout(LayoutKind.Sequential)] 109 | public unsafe readonly struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL 110 | { 111 | private readonly IntPtr _reset; 112 | 113 | private readonly IntPtr _readKeyStroke; 114 | public readonly IntPtr WaitForKey; 115 | 116 | public void Reset(void* handle, bool ExtendedVerification) 117 | { 118 | RawCalliHelper.StdCall(_reset, (byte*)handle, ExtendedVerification); 119 | } 120 | 121 | public ulong ReadKeyStroke(void* handle, EFI_INPUT_KEY* Key) 122 | { 123 | return RawCalliHelper.StdCall(_readKeyStroke, (byte*)handle, Key); 124 | } 125 | } 126 | 127 | [StructLayout(LayoutKind.Sequential)] 128 | public unsafe readonly struct EFI_BOOT_SERVICES 129 | { 130 | readonly EFI_TABLE_HEADER Hdr; 131 | private readonly IntPtr _RaiseTPL; 132 | private readonly IntPtr _RestoreTPL; 133 | private readonly IntPtr _AllocatePages; 134 | private readonly IntPtr _FreePages; 135 | private readonly IntPtr _GetMemoryMap; 136 | private readonly IntPtr _AllocatePool; 137 | private readonly IntPtr _FreePool; 138 | private readonly IntPtr _CreateEvent; 139 | private readonly IntPtr _SetTimer; 140 | private readonly IntPtr _WaitForEvent; 141 | private readonly IntPtr _SignalEvent; 142 | private readonly IntPtr _CloseEvent; 143 | private readonly IntPtr _CheckEvent; 144 | private readonly IntPtr _InstallProtocolInterface; 145 | private readonly IntPtr _ReinstallProtocolInterface; 146 | private readonly IntPtr _UninstallProtocolInterface; 147 | private readonly IntPtr _HandleProtocol; 148 | private readonly IntPtr _Reserved; 149 | private readonly IntPtr _RegisterProtocolNotify; 150 | private readonly IntPtr _LocateHandle; 151 | private readonly IntPtr _LocateDevicePath; 152 | private readonly IntPtr _InstallConfigurationTable; 153 | private readonly IntPtr _LoadImage; 154 | private readonly IntPtr _StartImage; 155 | private readonly IntPtr _Exit; 156 | private readonly IntPtr _UnloadImage; 157 | private readonly IntPtr _ExitBootServices; 158 | private readonly IntPtr _GetNextMonotonicCount; 159 | private readonly IntPtr _Stall; 160 | private readonly IntPtr _SetWatchdogTimer; 161 | private readonly IntPtr _ConnectController; 162 | private readonly IntPtr _DisconnectController; 163 | private readonly IntPtr _OpenProtocol; 164 | private readonly IntPtr _CloseProtocol; 165 | private readonly IntPtr _OpenProtocolInformation; 166 | private readonly IntPtr _ProtocolsPerHandle; 167 | private readonly IntPtr _LocateHandleBuffer; 168 | private readonly IntPtr _LocateProtocol; 169 | private readonly IntPtr _InstallMultipleProtocolInterfaces; 170 | private readonly IntPtr _UninstallMultipleProtocolInterfaces; 171 | private readonly IntPtr _CalculateCrc32; 172 | private readonly IntPtr _CopyMem; 173 | private readonly IntPtr _SetMem; 174 | private readonly IntPtr _CreateEventEx; 175 | 176 | public ulong Stall(uint Microseconds) 177 | { 178 | return RawCalliHelper.StdCall(_Stall, Microseconds); 179 | } 180 | } 181 | 182 | [StructLayout(LayoutKind.Sequential)] 183 | public unsafe readonly struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 184 | { 185 | private readonly IntPtr _reset; 186 | private readonly IntPtr _outputString; 187 | private readonly IntPtr _testString; 188 | private readonly IntPtr _queryMode; 189 | private readonly IntPtr _setMode; 190 | private readonly IntPtr _setAttribute; 191 | private readonly IntPtr _clearScreen; 192 | private readonly IntPtr _setCursorPosition; 193 | private readonly IntPtr _enableCursor; 194 | 195 | public readonly SIMPLE_TEXT_OUTPUT_MODE* Mode; 196 | 197 | public ulong OutputString(void* handle, char* str) 198 | { 199 | return RawCalliHelper.StdCall(_outputString, (byte*)handle, str); 200 | } 201 | public void SetAttribute(void* handle, uint Attribute) 202 | { 203 | RawCalliHelper.StdCall(_setAttribute, (byte*)handle, Attribute); 204 | } 205 | public void ClearScreen(void* handle) 206 | { 207 | RawCalliHelper.StdCall(_clearScreen, (byte*)handle); 208 | } 209 | public void SetCursorPosition(void* handle, uint Column, uint Row) 210 | { 211 | RawCalliHelper.StdCall(_setCursorPosition, (byte*)handle, Column, Row); 212 | } 213 | public void EnableCursor(void* handle, bool Visible) 214 | { 215 | RawCalliHelper.StdCall(_enableCursor, (byte*)handle, Visible); 216 | } 217 | } 218 | 219 | public unsafe static class EfiRuntimeHost 220 | { 221 | public static EFI_SYSTEM_TABLE* SystemTable { get; private set; } 222 | 223 | public static void Initialize(EFI_SYSTEM_TABLE* systemTable) 224 | { 225 | SystemTable = systemTable; 226 | } 227 | } --------------------------------------------------------------------------------