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
+
+
+
+
+
+
+
+
+