├── .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 | 
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 | }
--------------------------------------------------------------------------------