diff --git a/.gitignore b/.gitignore index 03c9b93..f347999 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,17 @@ bin/ obj/ .vs/ + +*~ +*swp +/MiniRuntime.a +*.bc +/MiniRuntime.o +/MiniRuntimea +/zerosnake.html +/zerosnake.ilexe +/zerosnake.js +/SeeSharpSnake.sln +/zerosnake.pdb +/zerosnake.txt +/zerosnake.wasm diff --git a/Game/FrameBuffer.cs b/Game/FrameBuffer.cs deleted file mode 100644 index 5a34a01..0000000 --- a/Game/FrameBuffer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; - -unsafe struct FrameBuffer -{ - public const int Width = 40; - public const int Height = 20; - public const int Area = Width * Height; - - fixed char _chars[Area]; - - public void SetPixel(int x, int y, char character) - { - _chars[y * Width + x] = character; - } - - public void Clear() - { - for (int i = 0; i < Area; i++) - _chars[i] = ' '; - } - - public readonly void Render() - { - Console.SetCursorPosition(0, 0); - - const ConsoleColor snakeColor = ConsoleColor.Green; - - Console.ForegroundColor = snakeColor; - - for (int i = 1; i <= Area; i++) - { - char c = _chars[i - 1]; - - if (c == '*' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) - { - Console.ForegroundColor = c == '*' ? ConsoleColor.Red : ConsoleColor.White; - Console.Write(c); - Console.ForegroundColor = snakeColor; - } - else - Console.Write(c); - - if (i % Width == 0) - { - Console.SetCursorPosition(0, i / Width - 1); - } - } - } -} diff --git a/Game/Game.cs b/Game/Game.cs index a7e00f4..d9fb83a 100644 --- a/Game/Game.cs +++ b/Game/Game.cs @@ -1,116 +1,129 @@ using System; -using Thread = System.Threading.Thread; +using SeeSharpSnake; +using SeeSharpSnake.Game; -struct Game +unsafe struct Game { - enum Result - { - Win, Loss - } + static Music music; - private Random _random; - - private Game(uint randomSeed) + enum Result { - _random = new Random(randomSeed); + Win, Loss, None } - private Result Run(ref FrameBuffer fb) - { - Snake s = new Snake( - (byte)(_random.Next() % FrameBuffer.Width), - (byte)(_random.Next() % FrameBuffer.Height), - (Snake.Direction)(_random.Next() % 4)); - - MakeFood(s, out byte foodX, out byte foodY); + static Game game; - long gameTime = Environment.TickCount64; + // sprites are 4x4 + internal const byte boardWidth = 40; + internal const byte boardHeight = 40; - while (true) - { - fb.Clear(); + static Random _random; - if (!s.Update()) - { - s.Draw(ref fb); - return Result.Loss; - } + Snake s; + byte foodX, foodY; - s.Draw(ref fb); + static readonly byte[] foodSprite = new byte[] + { + 0b10100101, + 0b10100101, + }; + static readonly byte[] foodSprite2 = new byte[] + { + 0b11111111, + 0b11111111, + }; + + /// + /// runs once at startup + /// + [System.Runtime.InteropServices.UnmanagedCallersOnly(EntryPoint = "start")] + public static void start() + { + game = new Game(42); + } - if (Console.KeyAvailable) - { - ConsoleKeyInfo ki = Console.ReadKey(intercept: true); - switch (ki.Key) - { - case ConsoleKey.UpArrow: - s.Course = Snake.Direction.Up; break; - case ConsoleKey.DownArrow: - s.Course = Snake.Direction.Down; break; - case ConsoleKey.LeftArrow: - s.Course = Snake.Direction.Left; break; - case ConsoleKey.RightArrow: - s.Course = Snake.Direction.Right; break; - } - } + [System.Runtime.InteropServices.UnmanagedCallersOnly(EntryPoint = "update")] + public static void update() + { + game.Main(); + } - if (s.HitTest(foodX, foodY)) - { - if (s.Extend()) - MakeFood(s, out foodX, out foodY); - else - return Result.Win; - } - fb.SetPixel(foodX, foodY, '*'); + private Game(uint randomSeed) + { + _random = new Random(randomSeed); + music.SetUp(); + s = new Snake( + (byte)(_random.Next() % boardWidth), + (byte)(_random.Next() % boardHeight), + (Snake.Direction)(_random.Next() % 4)); - fb.Render(); + MakeFood(s, out foodX, out foodY); + } - gameTime += 100; + Result Update() + { + music.PlayTune(); - long delay = gameTime - Environment.TickCount64; - if (delay >= 0) - Thread.Sleep((int)delay); + if (!s.Update()) + { + s.Draw(); + return Result.Loss; + } + + s.Draw(); + + switch ((*W4.GAMEPAD1)) // just handle single key + { + case W4.BUTTON_UP: + s.Course = Snake.Direction.Up; break; + case W4.BUTTON_DOWN: + s.Course = Snake.Direction.Down; break; + case W4.BUTTON_LEFT: + s.Course = Snake.Direction.Left; break; + case W4.BUTTON_RIGHT: + s.Course = Snake.Direction.Right; break; + } + + if (s.HitTest(foodX, foodY)) + { + if (s.Extend()) + MakeFood(s, out foodX, out foodY); else - gameTime = Environment.TickCount64; + return Result.Win; } + + fixed (byte* spAddr = &(foodSprite[0])) + { + W4.blit(spAddr, foodX << 2, foodY << 2, 4, 4, W4.BLIT_1BPP); + } + + return Result.None; } - void MakeFood(in Snake snake, out byte foodX, out byte foodY) + static void MakeFood(in Snake snake, out byte foodX, out byte foodY) { do { - foodX = (byte)(_random.Next() % FrameBuffer.Width); - foodY = (byte)(_random.Next() % FrameBuffer.Height); + foodX = (byte)(_random.Next() % boardWidth); + foodY = (byte)(_random.Next() % boardHeight); } while (snake.HitTest(foodX, foodY)); } - public static void Main() + void Main() { - Console.SetWindowSize(FrameBuffer.Width, FrameBuffer.Height); - Console.SetBufferSize(FrameBuffer.Width, FrameBuffer.Height); - Console.Title = "See Sharp Snake"; - Console.CursorVisible = false; + Result result = Update(); - FrameBuffer fb = new FrameBuffer(); - - while (true) + if (result != Result.None) { - Game g = new Game((uint)Environment.TickCount64); - Result result = g.Run(ref fb); - string message = result == Result.Win ? "You win" : "You lose"; - int position = (FrameBuffer.Width - message.Length) / 2; - for (int i = 0; i < message.Length; i++) + fixed (char* fixedMsg = message) { - fb.SetPixel(position + i, FrameBuffer.Height / 2, message[i]); + W4.textUtf16((byte*) fixedMsg, (uint) message.Length * 2, 70, 40); } - - fb.Render(); - - Console.ReadKey(intercept: true); } } } + diff --git a/Game/Music.cs b/Game/Music.cs new file mode 100644 index 0000000..a0ce34f --- /dev/null +++ b/Game/Music.cs @@ -0,0 +1,92 @@ +namespace SeeSharpSnake.Game +{ + unsafe struct Music + { + internal const int notesLength = 26; + const int quarterNote = 16; + + internal fixed uint NoteFrequencies[notesLength]; + internal fixed uint NoteDurations[notesLength]; + + static int tuneLength = 60; + static int noteFrame = 0; + static int tuneFrame = 0; + static int tunePos = 0; + + void AddNote(uint f, uint d, int ix) + { + NoteFrequencies[ix] = f; + NoteDurations[ix] = d; + } + + // Thankyou Matthew Smith! + public void SetUp() + { + int i = 0; + // bar 1, 7 notes + AddNote(123, quarterNote, i++); // b3 + AddNote(138, quarterNote, i++); // c#4 + AddNote(146, quarterNote, i++); // d4 + AddNote(164, quarterNote, i++); // e4 + AddNote(184, quarterNote, i++); // F#4 + AddNote(146, quarterNote, i++); // d4 + AddNote(184, quarterNote * 2, i++); // F#4 + + // bar 2, 6 notes, 13 total + AddNote(174, quarterNote, i++); // F4 + AddNote(138, quarterNote, i++); // c#4 + AddNote(174, quarterNote * 2, i++); // F4 + AddNote(164, quarterNote, i++); // e4 + AddNote(130, quarterNote, i++); // c4 + AddNote(164, quarterNote * 2, i++); // e4 + + // bar 3, 8 notes, 21 total + AddNote(123, quarterNote, i++); // b3 + AddNote(138, quarterNote, i++); // c#4 + AddNote(146, quarterNote, i++); // d4 + AddNote(164, quarterNote, i++); // e4 + AddNote(184, quarterNote, i++); // F#4 + AddNote(146, quarterNote, i++); // d4 + AddNote(184, quarterNote, i++); // F#4 + AddNote(246, quarterNote, i++); // b4 + + // bar 4, 5 notes, 26 total, ok so this is the original not the MS version, shoot me. + AddNote(219, quarterNote, i++); // a4 + AddNote(184, quarterNote, i++); // F#4 + AddNote(219, quarterNote, i++); // a4 + AddNote(184, quarterNote, i++); // F#4 + AddNote(219, quarterNote * 4, i++); // a4 + } + + internal void PlayTune() + { + // next note? + if (noteFrame == NoteDurations[tunePos]) + { + tunePos++; + noteFrame = 0; + + // loop? + if (tunePos == notesLength) + { + tunePos = 0; + } + } + + if (noteFrame == 0) + { + W4.tone(NoteFrequencies[tunePos], NoteDurations[tunePos], 70, 0); + } + + noteFrame++; + tuneFrame++; + // loop + if (tuneFrame == tuneLength) + { + tuneFrame = 0; + } + } + + } + +} diff --git a/Game/Snake.cs b/Game/Snake.cs index 1fb5c90..398ca50 100644 --- a/Game/Snake.cs +++ b/Game/Snake.cs @@ -1,4 +1,7 @@ -struct Snake +using System; +using SeeSharpSnake; + +unsafe struct Snake { public const int MaxLength = 30; @@ -26,7 +29,7 @@ public Direction Course public unsafe Snake(byte x, byte y, Direction direction) { - _body[0] = new Part(x, y, DirectionToChar(direction, direction)).Pack(); + _body[0] = new Part(x, y, DirectionToSprite(direction, direction)).Pack(); _direction = direction; _oldDirection = direction; _length = 1; @@ -38,20 +41,20 @@ public unsafe bool Update() Part newHead = new Part( (byte)(_direction switch { - Direction.Left => oldHead.X == 0 ? FrameBuffer.Width - 1 : oldHead.X - 1, - Direction.Right => (oldHead.X + 1) % FrameBuffer.Width, + Direction.Left => oldHead.X == 0 ? Game.boardWidth - 1 : oldHead.X - 1, + Direction.Right => (oldHead.X + 1) % Game.boardHeight, _ => oldHead.X, }), (byte)(_direction switch { - Direction.Up => oldHead.Y == 0 ? FrameBuffer.Height - 1 : oldHead.Y - 1, - Direction.Down => (oldHead.Y + 1) % FrameBuffer.Height, + Direction.Up => oldHead.Y == 0 ? Game.boardWidth - 1 : oldHead.Y - 1, + Direction.Down => (oldHead.Y + 1) % Game.boardHeight, _ => oldHead.Y, }), - DirectionToChar(_direction, _direction) + DirectionToSprite(_direction, _direction) ); - oldHead = new Part(oldHead.X, oldHead.Y, DirectionToChar(_oldDirection, _direction)); + oldHead = new Part(oldHead.X, oldHead.Y, DirectionToSprite(_oldDirection, _direction)); bool result = true; @@ -76,12 +79,16 @@ public unsafe bool Update() return result; } - public unsafe readonly void Draw(ref FrameBuffer fb) + // divide w4 into 40x40, each square 4x4 pixels + public readonly void Draw() { for (int i = 0; i < _length; i++) { Part p = Part.Unpack(_body[i]); - fb.SetPixel(p.X, p.Y, p.Character); + fixed (byte* spriteAddress = &(DirectionSprites[0])) + { + W4.blit(spriteAddress, p.X << 2, p.Y << 2, 4, 4, W4.BLIT_1BPP); + } } } @@ -107,27 +114,41 @@ public unsafe readonly bool HitTest(int x, int y) return false; } - private static char DirectionToChar(Direction oldDirection, Direction newDirection) + static readonly byte[] DirectionSprites = new byte[] + { + 0b11111111, + 0b11111111, + }; + + //TODO: + // create sprites for these and place in DirectionSprites "│┌?┐┘─┐??└│┘└?┌─"; + private static byte DirectionToSprite(Direction oldDirection, Direction newDirection) { - const string DirectionChangeToChar = "│┌?┐┘─┐??└│┘└?┌─"; - return DirectionChangeToChar[(int)oldDirection * 4 + (int)newDirection]; + return 0; + // byte* a; + // fixed (byte* t = DirectionSprites) + // { + // a = t; + // } + // + // return a; } // Helper struct to pack and unpack the packed integer in _body. - readonly struct Part + readonly unsafe struct Part { public readonly byte X, Y; - public readonly char Character; + public readonly byte SpriteIx; - public Part(byte x, byte y, char c) + public Part(byte x, byte y, byte spriteIx) { X = x; Y = y; - Character = c; + SpriteIx = spriteIx; } - public int Pack() => X << 24 | Y << 16 | Character; - public static Part Unpack(int packed) => new Part((byte)(packed >> 24), (byte)(packed >> 16), (char)packed); + public int Pack() => X << 24 | Y << 16 | SpriteIx; + public static Part Unpack(int packed) => new Part((byte)(packed >> 24), (byte)(packed >> 16), (byte)packed); } public enum Direction diff --git a/MiniRuntime.cs b/MiniRuntime.cs index df55953..1d69fe7 100644 --- a/MiniRuntime.cs +++ b/MiniRuntime.cs @@ -3,17 +3,23 @@ namespace Internal.Runtime.CompilerHelpers // A class that the compiler looks for that has helpers to initialize the // process. The compiler can gracefully handle the helpers not being present, // but the class itself being absent is unhandled. Let's add an empty class. - class StartupCodeHelpers + unsafe class StartupCodeHelpers { - [System.Runtime.RuntimeExport("RhpReversePInvoke2")] - static void RhpReversePInvoke2(System.IntPtr frame) { } - [System.Runtime.RuntimeExport("RhpReversePInvokeReturn2")] - static void RhpReversePInvokeReturn2(System.IntPtr frame) { } + //[System.Runtime.RuntimeExport("RhpReversePInvoke2")] + ////static void RhpReversePInvoke2(ReversePInvokeFrame *frame) { } + // [System.Runtime.RuntimeExport("RhpReversePInvokeReturn2")] + // static void RhpReversePInvokeReturn2(ReversePInvokeFrame *frame) { } [System.Runtime.RuntimeExport("RhpPInvoke")] static void RhpPinvoke(System.IntPtr frame) { } [System.Runtime.RuntimeExport("RhpPInvokeReturn")] static void RhpPinvokeReturn(System.IntPtr frame) { } } + + unsafe class ThrowHelpers + { + static void ThrowNullReferenceException() + {} // just carry on ! + } } namespace System @@ -46,11 +52,11 @@ internal static class ClassConstructorRunner { private static unsafe IntPtr CheckStaticClassConstructionReturnNonGCStaticBase(ref StaticClassConstructionContext context, IntPtr nonGcStaticBase) { - CheckStaticClassConstruction(ref context); + EnsureClassConstructorRun(ref context); return nonGcStaticBase; } - private static unsafe void CheckStaticClassConstruction(ref StaticClassConstructionContext context) + private static unsafe void EnsureClassConstructorRun(ref StaticClassConstructionContext context) { // Very simplified class constructor runner. In real world, the class constructor runner // would need to be able to deal with potentially multiple threads racing to initialize diff --git a/MiniRuntime.wasm b/MiniRuntime.wasm new file mode 100644 index 0000000..a8082b1 Binary files /dev/null and b/MiniRuntime.wasm differ diff --git a/MiniRuntime.wasm.c b/MiniRuntime.wasm.c new file mode 100644 index 0000000..439fd8f --- /dev/null +++ b/MiniRuntime.wasm.c @@ -0,0 +1,26 @@ +#include + +struct ReversePInvokeFrame +{ + void* m_savedPInvokeTransitionFrame; + void* m_savedThread; +}; + +static int alloced = 0; + +void* malloc() +{ +// this is our shadow stack, no other calls to malloc are allowed + if(alloced) + { + return (void*)0x100000; + } + alloced = 1; + return (void*)0xFC00; +} + +void RhpReversePInvoke2(struct ReversePInvokeFrame *frame) { } + +void RhpPInvoke2(void *p) {} +void RhpPInvokeReturn2(void *p) {} +void RhpReversePInvokeReturn2(struct ReversePInvokeFrame *frame) {} diff --git a/Pal/Console.Dos.cs b/Pal/Console.Dos.cs deleted file mode 100644 index 6cac35d..0000000 --- a/Pal/Console.Dos.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace System -{ - static class Console - { - static ushort s_consoleAttribute; - static ushort s_cursorX, s_cursorY; - - public static unsafe string Title - { - set - { - } - } - - public static unsafe bool CursorVisible - { - set - { - } - } - - public static ConsoleColor ForegroundColor - { - set - { - s_consoleAttribute = (ushort)value; - } - } - - - public static unsafe bool KeyAvailable - { - get - { - // mov ah,0Bh - // int 21h - // ret - byte* pCode = stackalloc byte[] { 0xB4, 0x0B, 0xCD, 0x21, 0xC3 }; - return ClassConstructorRunner.Call(new IntPtr(pCode)) != 0; - } - } - - public static unsafe ConsoleKeyInfo ReadKey(bool intercept) - { - // mov ah,08h - // int 21h - // ret - byte* pCode = stackalloc byte[] { 0xB4, 0x08, 0xCD, 0x21, 0xC3 }; - char c = (char)ClassConstructorRunner.Call(new IntPtr(pCode)); - - // Interpret WASD as arrow keys. - ConsoleKey k = default; - if (c == 'w') - k = ConsoleKey.UpArrow; - else if (c == 'd') - k = ConsoleKey.RightArrow; - else if (c == 's') - k = ConsoleKey.DownArrow; - else if (c == 'a') - k = ConsoleKey.LeftArrow; - - return new ConsoleKeyInfo(c, k, false, false, false); - } - - public static unsafe void SetWindowSize(int x, int y) - { - } - - public static void SetBufferSize(int x, int y) - { - } - - public static void SetCursorPosition(int x, int y) - { - s_cursorX = (ushort)x; - s_cursorY = (ushort)y; - } - - public static unsafe void Write(char c) - { - byte* biosDataArea = (byte*)0x400; - - // Find the start of video RAM by reading the BIOS data area - byte* vram = (byte*)0xB8000; - if (*(biosDataArea+0x63) == 0xB4) - vram = (byte*)0xB0000; - - // Get the offset of the active video page - vram += *(ushort*)(biosDataArea + 0x4E); - - // Translate some unicode characters into the IBM hardware codepage - byte b = c switch - { - '│' => (byte)0xB3, - '┌' => (byte)0xDA, - '┐' => (byte)0xBF, - '─' => (byte)0xC4, - '└' => (byte)0xC0, - '┘' => (byte)0xD9, - _ => (byte)c, - }; - - // TODO: read number of columns from the bios data area - vram[(s_cursorY * 80 * 2) + (s_cursorX * 2)] = b; - vram[(s_cursorY * 80 * 2) + (s_cursorX * 2) + 1] = (byte)s_consoleAttribute; - - // TODO: wrap/scroll? - s_cursorX++; - } - } -} diff --git a/Pal/Console.Windows.cs b/Pal/Console.Windows.cs deleted file mode 100644 index a6f9620..0000000 --- a/Pal/Console.Windows.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System.Runtime.InteropServices; - -namespace System -{ - static class Console - { - private enum BOOL : int - { - FALSE = 0, - TRUE = 1, - } - - [DllImport("api-ms-win-core-processenvironment-l1-1-0")] - private static unsafe extern IntPtr GetStdHandle(int c); - - private readonly static IntPtr s_outputHandle = GetStdHandle(-11); - - private readonly static IntPtr s_inputHandle = GetStdHandle(-10); - - [DllImport("api-ms-win-core-console-l2-1-0.dll", EntryPoint = "SetConsoleTitleW")] - private static unsafe extern BOOL SetConsoleTitle(char* c); - - public static unsafe string Title - { - set - { - fixed (char* c = value) - SetConsoleTitle(c); - } - } - - [StructLayout(LayoutKind.Sequential)] - struct CONSOLE_CURSOR_INFO - { - public uint Size; - public BOOL Visible; - } - - [DllImport("api-ms-win-core-console-l2-1-0")] - private static unsafe extern BOOL SetConsoleCursorInfo(IntPtr handle, CONSOLE_CURSOR_INFO* cursorInfo); - - public static unsafe bool CursorVisible - { - set - { - CONSOLE_CURSOR_INFO cursorInfo = new CONSOLE_CURSOR_INFO - { - Size = 1, - Visible = value ? BOOL.TRUE : BOOL.FALSE - }; - SetConsoleCursorInfo(s_outputHandle, &cursorInfo); - } - } - - [DllImport("api-ms-win-core-console-l2-1-0")] - private static unsafe extern BOOL SetConsoleTextAttribute(IntPtr handle, ushort attribute); - - public static ConsoleColor ForegroundColor - { - set - { - SetConsoleTextAttribute(s_outputHandle, (ushort)value); - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct KEY_EVENT_RECORD - { - public BOOL KeyDown; - public short RepeatCount; - public short VirtualKeyCode; - public short VirtualScanCode; - public short UChar; - public int ControlKeyState; - } - - [StructLayout(LayoutKind.Sequential)] - private struct INPUT_RECORD - { - public short EventType; - public KEY_EVENT_RECORD KeyEvent; - } - - [DllImport("api-ms-win-core-console-l1-2-0", EntryPoint = "PeekConsoleInputW", CharSet = CharSet.Unicode)] - private static unsafe extern BOOL PeekConsoleInput(IntPtr hConsoleInput, INPUT_RECORD* lpBuffer, uint nLength, uint* lpNumberOfEventsRead); - - public static unsafe bool KeyAvailable - { - get - { - uint nRead; - INPUT_RECORD buffer; - while (true) - { - PeekConsoleInput(s_inputHandle, &buffer, 1, &nRead); - - if (nRead == 0) - return false; - - if (buffer.EventType == 1 && buffer.KeyEvent.KeyDown != BOOL.FALSE) - return true; - - ReadConsoleInput(s_inputHandle, &buffer, 1, &nRead); - } - } - } - - [DllImport("api-ms-win-core-console-l1-2-0", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] - private static unsafe extern BOOL ReadConsoleInput(IntPtr hConsoleInput, INPUT_RECORD* lpBuffer, uint nLength, uint* lpNumberOfEventsRead); - - public static unsafe ConsoleKeyInfo ReadKey(bool intercept) - { - uint nRead; - INPUT_RECORD buffer; - do - { - ReadConsoleInput(s_inputHandle, &buffer, 1, &nRead); - } - while (buffer.EventType != 1 || buffer.KeyEvent.KeyDown == BOOL.FALSE); - - return new ConsoleKeyInfo((char)buffer.KeyEvent.UChar, (ConsoleKey)buffer.KeyEvent.VirtualKeyCode, false, false, false); - } - - struct SMALL_RECT - { - public short Left, Top, Right, Bottom; - } - - [DllImport("api-ms-win-core-console-l2-1-0")] - private static unsafe extern BOOL SetConsoleWindowInfo(IntPtr handle, BOOL absolute, SMALL_RECT* consoleWindow); - - public static unsafe void SetWindowSize(int x, int y) - { - SMALL_RECT rect = new SMALL_RECT - { - Left = 0, - Top = 0, - Right = (short)(x - 1), - Bottom = (short)(y - 1), - }; - SetConsoleWindowInfo(s_outputHandle, BOOL.TRUE, &rect); - } - - [StructLayout(LayoutKind.Sequential)] - struct COORD - { - public short X, Y; - } - - [DllImport("api-ms-win-core-console-l2-1-0")] - private static unsafe extern BOOL SetConsoleScreenBufferSize(IntPtr handle, COORD size); - - public static void SetBufferSize(int x, int y) - { - SetConsoleScreenBufferSize(s_outputHandle, new COORD { X = (short)x, Y = (short)y }); - } - - [DllImport("api-ms-win-core-console-l2-1-0")] - private static unsafe extern BOOL SetConsoleCursorPosition(IntPtr handle, COORD position); - - public static void SetCursorPosition(int x, int y) - { - SetConsoleCursorPosition(s_outputHandle, new COORD { X = (short)x, Y = (short)y }); - } - - [DllImport("api-ms-win-core-console-l1-2-0", EntryPoint = "WriteConsoleW")] - private static unsafe extern BOOL WriteConsole(IntPtr handle, void* buffer, int numChars, int* charsWritten, void* reserved); - - public static unsafe void Write(char c) - { - int dummy; - WriteConsole(s_outputHandle, &c, 1, &dummy, null); - } - } -} diff --git a/Pal/Console.cs b/Pal/Console.cs deleted file mode 100644 index 2f38186..0000000 --- a/Pal/Console.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace System -{ - public enum ConsoleColor - { - Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, - Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White - } - - public enum ConsoleKey - { - Escape = 27, - LeftArrow = 37, - UpArrow = 38, - RightArrow = 39, - DownArrow = 40, - } - - public readonly struct ConsoleKeyInfo - { - public ConsoleKeyInfo(char keyChar, ConsoleKey key, bool shift, bool alt, bool control) - { - Key = key; - } - - public readonly ConsoleKey Key; - } -} diff --git a/Pal/Environment.Dos.cs b/Pal/Environment.Dos.cs deleted file mode 100644 index 9ccfffd..0000000 --- a/Pal/Environment.Dos.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace System -{ - static class Environment - { - public static unsafe long TickCount64 - { - // Mark no inlining so that we get "volatile" semantics - [MethodImpl(MethodImplOptions.NoInlining)] - get - { - // Read BIOS data area - 1Ah interrupt counter. - // - // Yep, we're just dereferencing memory at some "arbitrary" offset. - // - // BIOS data area is a documented data structure laid out by the BIOS - // when the computer resets. DOS apps are allowed to read it. - // - // Offset 0x46C keeps track of time in ~55 ms units. - uint timerTicks = *(uint*)0x46C; - return (long)timerTicks * 55; - } - } - } -} diff --git a/Pal/Environment.Windows.cs b/Pal/Environment.Windows.cs deleted file mode 100644 index 318a238..0000000 --- a/Pal/Environment.Windows.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace System -{ - static class Environment - { - [DllImport("api-ms-win-core-sysinfo-l1-1-0")] - private static extern long GetTickCount64(); - - public static long TickCount64 => GetTickCount64(); - } -} diff --git a/Pal/Thread.Dos.cs b/Pal/Thread.Dos.cs deleted file mode 100644 index fddd144..0000000 --- a/Pal/Thread.Dos.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -namespace System.Threading -{ - static class Thread - { - public static unsafe void Sleep(int delayMs) - { - // Place the sequence of bytes 0xF4, 0xC3 on the stack. - // The bytes correspond to the following assembly instruction sequence: - // - // hlt - // ret - // - ushort hlt = 0xc3f4; - - long expected = Environment.TickCount64 + delayMs; - while (Environment.TickCount64 < expected) - { - // Call the helper we placed on the stack to halt the processor - // for a little bit. - // (Security people are crying in a corner right now). - ClassConstructorRunner.Call(new IntPtr(&hlt)); - } - } - } -} diff --git a/Pal/Thread.Windows.cs b/Pal/Thread.Windows.cs deleted file mode 100644 index be7072a..0000000 --- a/Pal/Thread.Windows.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Runtime.InteropServices; - -namespace System.Threading -{ - static class Thread - { - [DllImport("api-ms-win-core-synch-l1-2-0")] - public static extern void Sleep(int delayMs); - } -} \ No newline at end of file diff --git a/README.md b/README.md index 96e0686..7b57267 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # A self-contained C# game in 8 kB -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. +This repo is a complement to Michal's [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. 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. @@ -10,96 +10,26 @@ The project files and scripts in this repo build the same game (Snake clone) in ## Building -### To build the 65 MB version of the game - -``` -dotnet publish -r win-x64 -c Release -``` - -### To build the 25 MB version of the game - -``` -dotnet publish -r win-x64 -c Release /p:PublishTrimmed=true -``` - -### ⚠️ WARNING: additional requirements needed for the below configuration - -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). - -### To build the 4.7 MB version of the game - -``` -dotnet publish -r win-x64 -c Release /p:Mode=CoreRT -``` - -### To build the 4.3 MB version of the game - -``` -dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-Moderate -``` - -### To build the 3.0 MB version of the game - -``` -dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-High -``` - -### To build the 1.2 MB version of the game - -``` -dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-ReflectionFree -``` - -### To build the 10 kB version of the game - -``` -dotnet publish -r win-x64 -c Release /p:Mode=CoreRT-NoRuntime -``` - -### To build the 8 kB version of the game +### To build the W4 version of the game 1. Open "x64 Native Tools Command Prompt for VS 2019" (it's in your Start menu) 2. CD into the repo root directory ``` -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 +csc.exe /O /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 MiniRuntime.cs MiniBCL.cs Game\Random.cs Game\Game.cs Game\Snake.cs W4.cs Game\Music.cs /out:zerosnake.ilexe /langversion:latest /unsafe /target:library ``` -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`). +Find ilc.exe (the [CoreRT](http://github.com/dotnet/corert) ahead of time compiler) on your machine. You can add it as a package to a project and do a publish and it will download to your nuget package location, e.g. https://stackoverflow.com/questions/70474778/compiling-c-sharp-project-to-webassembly ``` -[PATH_TO_ILC_EXE]\ilc.exe zerosnake.ilexe -o zerosnake.obj --systemmodule:zerosnake --Os -g -``` +[PATH_TO_ILC_EXE]\ilc.exe zerosnake.ilexe -o zerosnake.bc --systemmodule:zerosnake --preinitstatics -g --targetarch=wasm --nativelib --codegenopt:Target=wasm32-unknown-unknown --targetos:wasm -``` -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 -``` - -## Contributing -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. - -In general, I welcome: - -* 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) -* 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 -* Bug fixes -* Making the CSPROJ also handle the 8 kB case so that we don't need to mess with the command prompt -* Small experience improvements (e.g. handling ESC key to exit the game) - -## To build for DOS +e.g +..\wl2\.packages\runtime.win-x64.microsoft.dotnet.ilcompiler.llvm\7.0.0-alpha.1.22063.2\tools\ilc.exe zerosnake.ilexe -o zerosnake.bc --systemmodule:zerosnake --preinitstatics -g --targetarch=wasm --nativelib --codegenopt:Target=wasm32-unknown-unknown --targetos:wasm -Very similar instructions to the 8 kB version: - -``` -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 -``` - -``` -[PATH_TO_ILC_EXE]\ilc.exe zerosnake.ilexe --systemmodule:zerosnake -o zerosnake.obj ``` ``` -link /subsystem:native /entry:__managed__Main zerosnake.obj /stub:dos64stb.bin +%EMSDK%\upstream\bin\wasm-ld -o zerosnake.wasm zerosnake.o MiniRuntime.bc zerosnakeclrjit.o -mllvm -combiner-global-alias-analysis=false -mllvm -disable-lsr --import-undefined --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=fflush --export-table -z stack-size=1024 --import-memory --initial-memory=65536 --max-memory=65536 --global-base=6560 --export=update --export=start --no-entry ``` -The DOS64STB blob is https://github.com/Baron-von-Riedesel/Dos64-stub. diff --git a/ReadMeWasm.txt b/ReadMeWasm.txt new file mode 100644 index 0000000..f4fc768 --- /dev/null +++ b/ReadMeWasm.txt @@ -0,0 +1,15 @@ +Wasm port of the SeeSharpSnake +(https://github.com/MichalStrehovsky/SeeSharpSnake and +https://medium.com/@MStrehovsky/building-a-self-contained-game-in-c-under-8-kilobytes-74c3cf60ea04 +) + +Commands to build + +emcc MiniRuntime.wasm.c -c -o MiniRuntime.bc -s WASM=1 + +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.Wasm.cs Pal\Environment.Wasm.cs Pal\Console.Wasm.cs Pal\Console.cs /out:zerosnake.ilexe /langversion:latest /unsafe + +..\corert\bin\WebAssembly.wasm.Debug\tools\ilc --targetarch=wasm zerosnake.ilexe -o zerosnake.bc --systemmodule:zerosnake --Os -g + +"E:\GitHub\emsdk\upstream\emscripten\emcc.bat" "zerosnake.bc" -o "zerosnake.html" -s WASM=1 --emrun MiniRuntime.bc -s ASYNCIFY=1 --js-library pal\console.js --shell-file pal\shell_minimal.html -Os --llvm-lto 3 + diff --git a/SeeSharpSnake.csproj b/SeeSharpSnake.csproj index 6e660e8..21a7a8c 100644 --- a/SeeSharpSnake.csproj +++ b/SeeSharpSnake.csproj @@ -9,10 +9,11 @@ - + + diff --git a/W4.cs b/W4.cs new file mode 100644 index 0000000..4a7478b --- /dev/null +++ b/W4.cs @@ -0,0 +1,37 @@ +using System.Runtime.InteropServices; + +namespace SeeSharpSnake +{ + internal unsafe class W4 + { + internal static readonly byte* GAMEPAD1 = (byte*)0x16; + + internal static readonly byte* FRAMEBUFFER = (byte*)0x00a0; + + internal const byte BUTTON_LEFT = 16; + internal const byte BUTTON_RIGHT = 32; + internal const byte BUTTON_UP = 64; + internal const byte BUTTON_DOWN = 128; + + internal const uint BLIT_1BPP = 0; + + [DllImport("*")] + internal static extern void blit(byte* data, int x, int y, uint width, uint height, uint flags); + + [DllImport("*")] + internal static extern void textUtf16(byte* data, uint length, uint x, uint y); + + /** Plays a sound tone. */ + [DllImport("*")] + internal static extern void tone(uint frequency, uint duration, uint volume, uint flags); + } +} + +namespace System.Runtime.InteropServices +{ + public class UnmanagedCallersOnlyAttribute : Attribute + { + public string EntryPoint { get; set; } + } +} + diff --git a/w4.exe b/w4.exe new file mode 100644 index 0000000..ab1022b Binary files /dev/null and b/w4.exe differ diff --git a/zerosnake.clrjit.o b/zerosnake.clrjit.o new file mode 100644 index 0000000..ac026ca Binary files /dev/null and b/zerosnake.clrjit.o differ diff --git a/zerosnake.o b/zerosnake.o new file mode 100644 index 0000000..ee442a6 Binary files /dev/null and b/zerosnake.o differ diff --git a/zerosnake.obj b/zerosnake.obj new file mode 100644 index 0000000..2d474a3 Binary files /dev/null and b/zerosnake.obj differ diff --git a/zerosnakeclrjit.o b/zerosnakeclrjit.o new file mode 100644 index 0000000..88ff06b Binary files /dev/null and b/zerosnakeclrjit.o differ diff --git a/zerosnakeclrjit.txt b/zerosnakeclrjit.txt new file mode 100644 index 0000000..a7b2706 --- /dev/null +++ b/zerosnakeclrjit.txt @@ -0,0 +1,347 @@ +; ModuleID = 'netscripten-clrjit' +source_filename = "netscripten-clrjit" +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown" + +@__Str_You_lose_8FCE10111331EFAA78AC965D88ED5C6FC1DEE6B6408A9473F9D05314E707FB22___SYMBOL = external constant i32* +@__Str_You_win_3B962C67DDE260B72E445A27E41529EBE90453956BDF042DF48760A897DEFED6___SYMBOL = external constant i32* + +declare void @Internal_CompilerGenerated__Module___NativeLibraryStartup(i8*) + +define void @zerosnake_Internal_Runtime_CompilerHelpers_StartupCodeHelpers__RhpPinvokeReturn(i8* %0, i32 %1) { +Prolog: + br label %2 + +2: ; preds = %Prolog + br label %3 + +3: ; preds = %2 + ret void +} + +define void @zerosnake_Internal_Runtime_CompilerHelpers_StartupCodeHelpers__RhpPinvoke(i8* %0, i32 %1) { +Prolog: + br label %2 + +2: ; preds = %Prolog + br label %3 + +3: ; preds = %2 + ret void +} + +declare void @zerosnake_Game__update(i8*) + +define void @zerosnake_Game__Main(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + %3 = getelementptr i8, i8* %0, i32 0 + %4 = bitcast i8* %3 to i8** + %5 = load i8*, i8** %4, align 4 + %6 = getelementptr i8, i8* %0, i32 16 + %7 = getelementptr i8, i8* %0, i32 16 + %8 = bitcast i8* %7 to i8** + store i8* %5, i8** %8, align 4 + %9 = call i32 @zerosnake_Game__Update(i8* %6) + %10 = icmp eq i32 %9, 2 + br i1 %10, label %13, label %11 + +11: ; preds = %2 + %12 = icmp eq i32 %9, 0 + br i1 %12, label %19, label %14 + +13: ; preds = %45, %2 + ret void + +14: ; preds = %11 + %15 = load i32*, i32** @__Str_You_lose_8FCE10111331EFAA78AC965D88ED5C6FC1DEE6B6408A9473F9D05314E707FB22___SYMBOL, align 4 + %16 = getelementptr i8, i8* %0, i32 12 + %17 = bitcast i8* %16 to i8** + %18 = bitcast i32* %15 to i8* + store i8* %18, i8** %17, align 4 + br label %24 + +19: ; preds = %11 + %20 = load i32*, i32** @__Str_You_win_3B962C67DDE260B72E445A27E41529EBE90453956BDF042DF48760A897DEFED6___SYMBOL, align 4 + %21 = getelementptr i8, i8* %0, i32 12 + %22 = bitcast i8* %21 to i8** + %23 = bitcast i32* %20 to i8* + store i8* %23, i8** %22, align 4 + br label %24 + +24: ; preds = %19, %14 + %25 = getelementptr i8, i8* %0, i32 12 + %26 = bitcast i8* %25 to i8** + %27 = load i8*, i8** %26, align 4 + %28 = getelementptr i8, i8* %0, i32 4 + %29 = bitcast i8* %28 to i8** + store i8* %27, i8** %29, align 4 + %30 = getelementptr i8, i8* %0, i32 4 + %31 = bitcast i8* %30 to i8** + %32 = load i8*, i8** %31, align 4 + %33 = getelementptr i8, i8* %0, i32 8 + %34 = bitcast i8* %33 to i8** + store i8* %32, i8** %34, align 4 + %35 = getelementptr i8, i8* %0, i32 8 + %36 = bitcast i8* %35 to i8** + %37 = load i8*, i8** %36, align 4 + %38 = icmp eq i8* %37, null + %39 = ptrtoint i8* %37 to i32 + br i1 %38, label %45, label %40 + +40: ; preds = %24 + %41 = getelementptr i8, i8* %0, i32 16 + %42 = call i32 @zerosnake_System_Runtime_CompilerServices_RuntimeHelpers__get_OffsetToStringData(i8* %41) + %43 = getelementptr i8, i8* %37, i32 %42 + %44 = ptrtoint i8* %43 to i32 + br label %45 + +45: ; preds = %40, %24 + %46 = phi i32 [ %44, %40 ], [ %39, %24 ] + %47 = getelementptr i8, i8* %0, i32 4 + %48 = bitcast i8* %47 to i8** + %49 = load i8*, i8** %48, align 4 + %50 = getelementptr i8, i8* %49, i32 4 + %51 = bitcast i8* %50 to i32* + %52 = load i32, i32* %51, align 4 + %lsh = shl i32 %52, 1 + %53 = getelementptr i8, i8* %0, i32 16 + %54 = inttoptr i32 %46 to i8* + call void @zerosnake_SeeSharpSnake_W4__textUtf16(i8* %53, i8* %54, i32 %lsh, i32 70, i32 40) + %55 = getelementptr i8, i8* %0, i32 8 + %56 = bitcast i8* %55 to i8** + store i8* null, i8** %56, align 4 + br label %13 +} + +declare i32 @zerosnake_Game__Update(i8*) + +define i32 @zerosnake_System_Runtime_CompilerServices_RuntimeHelpers__get_OffsetToStringData(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + ret i32 8 +} + +declare void @zerosnake_SeeSharpSnake_W4__textUtf16(i8*, i8*, i32, i32, i32) + +define void @Object___ctor(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + ret void +} + +define void @String___ctor(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + %3 = getelementptr i8, i8* %0, i32 0 + %4 = bitcast i8* %3 to i8** + %5 = load i8*, i8** %4, align 4 + %6 = getelementptr i8, i8* %0, i32 4 + %7 = getelementptr i8, i8* %0, i32 4 + %8 = bitcast i8* %7 to i8** + store i8* %5, i8** %8, align 4 + call void @Object___ctor(i8* %6) + ret void +} + +define void @zerosnake_Internal_Runtime_CompilerHelpers_ThrowHelpers__ThrowNullReferenceException(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + ret void +} + +declare i32 @zerosnake_Random__Next(i8*) + +define void @zerosnake_Random___ctor(i8* %0, i32 %1) { +Prolog: + br label %2 + +2: ; preds = %Prolog + br label %3 + +3: ; preds = %2 + %4 = getelementptr i8, i8* %0, i32 0 + %5 = bitcast i8* %4 to i8** + %6 = load i8*, i8** %5, align 4 + %7 = bitcast i8* %6 to i32* + store i32 %1, i32* %7, align 4 + ret void +} + +declare void @zerosnake_SeeSharpSnake_Game_Music__AddNote(i8*, i32, i32, i32) + +define i32 @zerosnake_Snake_Part__Pack(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + %3 = getelementptr i8, i8* %0, i32 0 + %4 = bitcast i8* %3 to i8** + %5 = load i8*, i8** %4, align 4 + %6 = load i8, i8* %5, align 1 + %7 = zext i8 %6 to i32 + %lsh = shl i32 %7, 24 + %8 = getelementptr i8, i8* %0, i32 0 + %9 = bitcast i8* %8 to i8** + %10 = load i8*, i8** %9, align 4 + %11 = getelementptr i8, i8* %10, i32 1 + %12 = load i8, i8* %11, align 1 + %13 = zext i8 %12 to i32 + %lsh1 = shl i32 %13, 16 + %or = or i32 %lsh, %lsh1 + %14 = getelementptr i8, i8* %0, i32 0 + %15 = bitcast i8* %14 to i8** + %16 = load i8*, i8** %15, align 4 + %17 = getelementptr i8, i8* %16, i32 2 + %18 = load i8, i8* %17, align 1 + %19 = zext i8 %18 to i32 + %or2 = or i32 %or, %19 + ret i32 %or2 +} + +define void @zerosnake_Snake_Part___ctor(i8* %0, i8 %1, i8 %2, i8 %3) { +Prolog: + br label %4 + +4: ; preds = %Prolog + br label %5 + +5: ; preds = %4 + %6 = getelementptr i8, i8* %0, i32 0 + %7 = bitcast i8* %6 to i8** + %8 = load i8*, i8** %7, align 4 + store i8 %1, i8* %8, align 1 + %9 = getelementptr i8, i8* %0, i32 0 + %10 = bitcast i8* %9 to i8** + %11 = load i8*, i8** %10, align 4 + %12 = getelementptr i8, i8* %11, i32 1 + store i8 %2, i8* %12, align 1 + %13 = getelementptr i8, i8* %0, i32 0 + %14 = bitcast i8* %13 to i8** + %15 = load i8*, i8** %14, align 4 + %16 = getelementptr i8, i8* %15, i32 2 + store i8 %3, i8* %16, align 1 + ret void +} + +define i8 @zerosnake_Snake__DirectionToSprite(i8* %0, i32 %1, i32 %2) { +Prolog: + br label %3 + +3: ; preds = %Prolog + br label %4 + +4: ; preds = %3 + %5 = trunc i32 0 to i8 + ret i8 %5 +} + +declare void @zerosnake_Snake__set_Course(i8*, i32) + +define void @"zerosnake_System_Array_1___ctor"(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + %3 = getelementptr i8, i8* %0, i32 0 + %4 = bitcast i8* %3 to i8** + %5 = load i8*, i8** %4, align 4 + %6 = getelementptr i8, i8* %0, i32 4 + %7 = getelementptr i8, i8* %0, i32 4 + %8 = bitcast i8* %7 to i8** + store i8* %5, i8** %8, align 4 + call void @zerosnake_System_Array___ctor(i8* %6) + ret void +} + +define void @zerosnake_System_Array___ctor(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + %3 = getelementptr i8, i8* %0, i32 0 + %4 = bitcast i8* %3 to i8** + %5 = load i8*, i8** %4, align 4 + %6 = getelementptr i8, i8* %0, i32 4 + %7 = getelementptr i8, i8* %0, i32 4 + %8 = bitcast i8* %7 to i8** + store i8* %5, i8** %8, align 4 + call void @Object___ctor(i8* %6) + ret void +} + +define i8 @zerosnake_Snake__Extend(i8* %0) { +Prolog: + br label %1 + +1: ; preds = %Prolog + br label %2 + +2: ; preds = %1 + %3 = getelementptr i8, i8* %0, i32 0 + %4 = bitcast i8* %3 to i8** + %5 = load i8*, i8** %4, align 4 + %6 = bitcast i8* %5 to i32* + %7 = load i32, i32* %6, align 4 + %8 = icmp sge i32 %7, 30 + br i1 %8, label %21, label %9 + +9: ; preds = %2 + %10 = getelementptr i8, i8* %0, i32 0 + %11 = bitcast i8* %10 to i8** + %12 = load i8*, i8** %11, align 4 + %13 = bitcast i8* %12 to i32* + %14 = load i32, i32* %13, align 4 + %15 = add i32 %14, 1 + %16 = getelementptr i8, i8* %0, i32 0 + %17 = bitcast i8* %16 to i8** + %18 = load i8*, i8** %17, align 4 + %19 = bitcast i8* %18 to i32* + store i32 %15, i32* %19, align 4 + %20 = trunc i32 1 to i8 + ret i8 %20 + +21: ; preds = %2 + %22 = trunc i32 0 to i8 + ret i8 %22 +} + +declare i8 @zerosnake_Snake__HitTest(i8*, i32, i32) + +declare void @zerosnake_SeeSharpSnake_Game_Music__PlayTune(i8*) + +declare i8* @__GetNonGCStaticBase_zerosnake_SeeSharpSnake_Game_Music(i8*) diff --git a/zerosnakeh.html b/zerosnakeh.html new file mode 100644 index 0000000..d68e8f7 --- /dev/null +++ b/zerosnakeh.html @@ -0,0 +1,35 @@ + + + + + + + WASM-4 Game + + + +
+
+
+

+

+ +
+
+
+
+
+
+
+
+
+ + + +